DEV Community

Discussion on: Creating a blockchain in 60 lines of Python

Collapse
 
imjoseangel profile image
Jose Angel Munoz • Edited

Thanks @theflanman for your contribution. It is always cool to see code like yours to learn.

Do you think adding typing could be a good addition to your code? like:

    def __init__(self, **kwargs) -> None:
        pass
Enter fullscreen mode Exit fullscreen mode

Also I ♥ to see how you are using @property decorator in the example. I have prepared a small snippet to see the effect when using it:

class Test():
    def __init__(self, **kwargs) -> None:
        pass

    @property
    def hash(self) -> None:
        return sha256().hexdigest()

    def __repr__(self) -> str:
        return self.hash


def main():
    print(Test())
Enter fullscreen mode Exit fullscreen mode

I really like how you are using the iterator in the __repr__ and other dunder methods.

Have Fun!

Collapse
 
theflanman profile image
Connor Flanigan

The consensus we've reached at work has mostly been that typing should really only be bothered to help users and devs where it's not clear what expected input and output is. Things like __init__ methods or other dunder methods are expected to return a certain type of object so they don't need it.

What I might recommend is typing your inputs a little more. As an example, explicitly use ints to represent timestamps, and use the function time.time_ns() for timestamps, then use structs to generate more accurate representations to hash.

class Block:

    def __init__(self, data: Optional[List[Any]] = None, timestamp: Optional[float]=None, prevHash: Optional[bytes] = None):
        """
        Create a new block for the Blockchain

        :param timestamp: Timestamp of the block, defaults to the time the block object is created
        :param data: Data to store in the block, defaults to an empty list
        :param prevHash: Hash of the previous block, defaults to None.  Should always be specefied except for the genesis block.
        """
        if datais None:
            self.data= []
        else:
            self.data= data

        if timestamp is None:
            self.timestamp = time_ns()
        else:
            self.timestamp = timestamp

        if prevHashis None:
            self.prevHash= b''
        else:
            self.prevHash= prevHash

        self._hash = None
        self.nonce = 0

    @property
    def hash(self) -> bytes:
        """
        Return the (non-python) hash of the block

        :return: The bytes of the hash of this block
        """
        if self._hash is None:
            hashFun = sha256()
            hashFun.update(self.prevHash)
            hashFun.update(struct.pack('@l', self.timestamp))
            hashFun.update(self.encode(self.data))
            hashFun.update(struct.pack('@l', self.nonce))
            self._hash = hashFun.hexdigest()
        return self._hash

Enter fullscreen mode Exit fullscreen mode

Here I've specified that data is a list of anything, timestamp is an int, and prevHash is bytes, and that Block.hash generates bytes. This informs a user/developer that's interfacing with it that this is what the methods are expecting to take or should be expected to return. The only thing I might also add for type hinting in the Block class is specifying the difficulty parameter in Block.mine() is an integer, as well as a check that it's greater than or equal to 1, raising an exception if it isn't.