Esempio n. 1
0
    def add_block(self, block: Block) -> Tuple[bool, Optional[str]]:
        """
        When a new block is received via a broadcast, the receiving nodes must validate the
        block to make sure it is valid, and then add it to their chains.

        This also makes sure that there are not open transactions on any of the nodes
        that match a transaction in the broadcasted block.
        """
        if not Verification.valid_nonce(block.header):
            return False, "Nonce is not valid"
        if (not Verification.hash_block_header(self.last_block.header)
                == block.header.previous_hash):
            return (
                False,
                "Hash of last block does not equal previous hash in the current block",
            )
        self.add_block_to_chain(block)

        # Always work off a copy as to not disrupt the current list of open transactions
        stored_transactions = self.__open_transactions[:]
        for itx in block.transactions:
            for opentx in stored_transactions:
                if opentx.transaction_hash == itx:
                    try:
                        self.__open_transactions.remove(opentx)
                    except ValueError:
                        logger.warning("Item was already removed: %s", opentx)

        self.save_data()
        return True, "success"
Esempio n. 2
0
def test_correct_nonce():
    timestamp = datetime.utcfromtimestamp(0)

    block_one = Block(
        index=0,
        block_hash="",
        size=0,
        header=Header(
            timestamp=timestamp,
            transaction_merkle_root="",
            nonce=100,
            previous_hash="",
            difficulty=4,
            version=1,
        ),
        transaction_count=0,
        transactions=[],
    )

    previous_hash = Verification.hash_block_header(block_one.header)

    open_transactions = [
        SignedRawTransaction(
            details=Details(
                sender="test2",
                recipient="test",
                amount=2.5,
                nonce=0,
                timestamp=timestamp,
                public_key="pub_key",
            ),
            signature="sig",
        )
    ]

    block_header = Header(
        version=1,
        difficulty=4,
        timestamp=datetime.utcfromtimestamp(1),
        transaction_merkle_root=get_merkle_root(open_transactions),
        previous_hash=previous_hash,
        nonce=0,
    )

    block_header = Verification.proof_of_work(block_header)

    block_two = Block(
        index=1,
        block_hash="",
        size=0,
        header=block_header,
        transaction_count=len(open_transactions),
        transactions=[
            Verification.hash_transaction(t) for t in open_transactions
        ],
    )

    assert Verification.valid_nonce(block_two.header)
Esempio n. 3
0
def test_block_hash_happy_path():
    timestamp = datetime.utcfromtimestamp(0)

    block = Block(
        index=0,
        block_hash="",
        size=0,
        header=Header(
            timestamp=timestamp,
            transaction_merkle_root="",
            nonce=100,
            previous_hash="",
            difficulty=4,
            version=1,
        ),
        transaction_count=0,
        transactions=[],
    )

    # Just asserting that an error is not thrown when hashing the block
    Verification.hash_block_header(block.header)
Esempio n. 4
0
    def __init__(
        self,
        address: str,
        node_id: UUID,
        is_test: bool = False,
        *,
        difficulty: int = 4,
        version: int = 1,
        timestamp: Optional[datetime] = None,
    ) -> None:
        # Generate a globally unique UUID for this node
        self.chain_identifier = node_id
        self.__open_transactions = []  # type: List[FinalTransaction]
        self.nodes = set()  # type: Set[str]
        self.difficulty = difficulty
        self.address = address
        self.version = version
        self.data_location = (f"data/{node_id}" if not is_test else
                              f"{tempfile.tempdir}/blockchain/{node_id}")

        if is_test:
            try:
                shutil.rmtree(self.data_location)
            except Exception:
                pass

        # Create the 'genesis' block. This is the inital block.
        header = Header(
            timestamp=timestamp
            if timestamp is not None else datetime.utcnow(),
            transaction_merkle_root=get_merkle_root([]),
            nonce=100,
            previous_hash="",
            difficulty=difficulty,
            version=version,
        )

        self.chain = [
            Block(
                index=0,
                block_hash=Verification.hash_block_header(header),
                size=len(str(header)),
                header=header,
                transaction_count=0,
                transactions=[],
            )
        ]

        self.load_data()
Esempio n. 5
0
def test_block_hash_mutliple_transaction_field_order_doesnt_matter():
    timestamp = datetime.utcfromtimestamp(0)

    transactions = [
        SignedRawTransaction(
            details=Details(
                sender="test",
                recipient="test2",
                amount=5.0,
                nonce=0,
                timestamp=timestamp,
                public_key="pub_key",
            ),
            signature="sig",
        ),
        SignedRawTransaction(
            details=Details(
                sender="test2",
                recipient="test",
                amount=2.5,
                nonce=0,
                timestamp=timestamp,
                public_key="pub_key",
            ),
            signature="sig",
        ),
    ]

    tx_merkle_root = get_merkle_root(transactions)

    block = Block(
        index=0,
        block_hash="",
        size=0,
        header=Header(
            timestamp=timestamp,
            transaction_merkle_root=tx_merkle_root,
            nonce=100,
            previous_hash="",
            difficulty=4,
            version=1,
        ),
        transaction_count=2,
        transactions=[Verification.hash_transaction(t) for t in transactions],
    )

    first_hash = Verification.hash_block_header(block.header)

    block = Block(
        index=0,
        block_hash="",
        size=0,
        header=Header(
            timestamp=timestamp,
            transaction_merkle_root=tx_merkle_root,
            nonce=100,
            previous_hash="",
            difficulty=4,
            version=1,
        ),
        transaction_count=2,
        transactions=[Verification.hash_transaction(t) for t in transactions],
    )

    second_hash = Verification.hash_block_header(block.header)

    assert first_hash == second_hash
Esempio n. 6
0
    def mine_block(
        self,
        address: Optional[str] = None,
        difficulty: Optional[int] = None,
        version: Optional[int] = None,
    ) -> Optional[Block]:
        """
        The current node runs the mining protocol, and depending on the difficulty, this
        could take a lot of processing power.

        Once the nonce is discovered, or "mined", the reward transaction is created.

        Then all of the open transactions are validated and verified, ensuring that
        the senders in all of the transactions have enough coin to conduct the transaction.

        Once the transactions are validated, the reward block is added to the list of
        open transactions. This is because Mining transactions do not need to be validated
        since they are created by the node itself.

        The block is then added directy to the node's chain and the open_transactions is
        cleared and ready for a new block to be mined

        Finally, the new block is broadcasted to all connected nodes.
        """
        if not address:
            address = self.address

        if not address:
            return None

        difficulty = difficulty if difficulty is not None else self.difficulty
        version = version if version is not None else self.version
        last_block = self.last_block
        transaction_merkle_root = get_merkle_root(
            [tx.signed_transaction for tx in self.get_open_transactions])
        previous_hash = Verification.hash_block_header(last_block.header)

        block_header = Header(
            version=version,
            difficulty=difficulty,
            timestamp=datetime.utcnow(),
            transaction_merkle_root=transaction_merkle_root,
            previous_hash=previous_hash,
            nonce=0,
        )

        # We run the PoW algorithm to get the next nonce and return an updated block_header
        block_header = Verification.proof_of_work(block_header)

        # Create the transaction that will be rewarded to the miners for their work
        # The sender is "0" or "Mining" to signify that this node has mined a new coin.
        reward_signed = SignedRawTransaction(
            details=Details(
                sender="0",
                recipient=address,
                nonce=0,
                amount=MINING_REWARD,
                timestamp=datetime.utcnow(),
                public_key="coinbase",
            ),
            signature="coinbase",
        )

        reward_transaction = FinalTransaction(
            transaction_hash=Verification.hash_transaction(reward_signed),
            transaction_id=Verification.hash_transaction(reward_signed),
            signed_transaction=reward_signed,
        )

        # Copy transactions instead of manipulating the original open_transactions list
        # This ensures that if for some reason the mining should fail,
        # we don't have the reward transaction stored in the pending transactions
        copied_open_transactions = self.get_open_transactions
        for tx in copied_open_transactions:
            if not Wallet.verify_transaction(tx.signed_transaction,
                                             self.get_last_tx_nonce,
                                             exclude_from_open=True):
                return None

        FinalTransaction.SaveTransaction(self.data_location,
                                         reward_transaction, "mining")
        self.__broadcast_transaction(reward_transaction.signed_transaction,
                                     "mining")

        for t in copied_open_transactions:
            self.__broadcast_transaction(t.signed_transaction, "confirmed")

        copied_open_transactions.append(reward_transaction)

        block = Block(
            index=self.next_index,
            header=block_header,
            block_hash=Verification.hash_block_header(block_header),
            size=len(str(block_header)),
            transaction_count=len(copied_open_transactions),
            transactions=[
                t.transaction_hash for t in copied_open_transactions
            ],
        )

        # Add the block to the node's chain
        self.add_block_to_chain(block)

        # Reset the open list of transactions
        logger.info("Moving open transaction to confirmed storage at %s",
                    self.data_location)

        FinalTransaction.MoveOpenTransactions(self.data_location)
        self.__open_transactions = []
        self.save_data()

        self.__broadcast_block(block)

        return block