コード例 #1
0
def test_nonce_should_be_exactly_previous_plus_1():
    node_id = uuid4()
    w = Wallet(test=True)
    w2 = Wallet(test=True)
    chain = Blockchain(w.address, node_id, difficulty=1, is_test=True)
    chain.mine_block()

    timestamp = datetime.utcfromtimestamp(0)

    d = Details(
        sender=w.address,
        recipient=w2.address,
        amount=4.5,
        nonce=0,
        timestamp=timestamp,
        public_key=w.public_key.hex(),
    )

    t = w.sign_transaction(d)

    chain.add_transaction(t)
    try:
        d.nonce = 2
        t2 = w.sign_transaction(d)
        chain.add_transaction(t2)
        raise ValueError("Double spending transaction should fail, but didn't")
    except InvalidNonceError:
        pass
コード例 #2
0
def test_merkle_happy_path():
    w1 = Wallet(test=True)
    w2 = Wallet(test=True)
    transactions = [
        SignedRawTransaction(
            details=Details(
                sender=w1.address,
                recipient=w2.address,
                amount=4.5,
                nonce=0,
                timestamp=datetime.utcnow(),
                public_key=w1.public_key.hex(),
            ),
            signature="sig",
        ),
        SignedRawTransaction(
            details=Details(
                sender=w2.address,
                recipient=w1.address,
                amount=1.87,
                nonce=0,
                timestamp=datetime.utcnow(),
                public_key=w2.public_key.hex(),
            ),
            signature="sig",
        ),
    ]

    m = convert_to_merkle(transactions)
    assert m.is_ready
    print(m.get_merkle_root())
    print(m.get_proof(0))
    SignedRawTransaction.ParseFromHex(m.get_proof(0)[0]["right"])
    print(m.get_proof(1))
    SignedRawTransaction.ParseFromHex(m.get_proof(1)[0]["left"])
コード例 #3
0
    def sign_transaction(self, details: Details) -> SignedRawTransaction:
        """
        Sign a transaction and return the signature
        A signature is generated using the contents of the rest of the transaction. This means
        that the signature will always be able to be decoded and will match the transaction.
        """
        logger.info("Signing transaction")
        if self.private_key is None:
            message = "Unable to sign transaction without a private key"
            logger.error(message)
            raise ValueError(message)

        d = details.SerializeToString()
        signature = self.private_key.sign(d)

        expected_nonce = self.get_nonce()
        if details.nonce != expected_nonce:
            raise InvalidNonceError(
                details.sender,
                details.nonce,
                expected_nonce,
                "The transaction nonce must match the stored nonce before signing the transaction",
            )

        self.save_new_nonce(expected_nonce)
        logger.debug("Transaction signed successfully")
        return SignedRawTransaction(details=details, signature=signature.hex())
コード例 #4
0
def test_transaction_fails_validation():
    w = Wallet(test=True)
    w2 = Wallet(test=True)

    timestamp = datetime.utcfromtimestamp(0)

    d = Details(
        sender=w.address,
        recipient=w2.address,
        amount=4.5,
        nonce=0,
        timestamp=timestamp,
        public_key=w.public_key.hex(),
    )

    t = w.sign_transaction(d)

    t_hex = t.SerializeToHex()

    parsed_t = SignedRawTransaction.ParseFromHex(t_hex)

    assert parsed_t == t
    assert w.verify_transaction(t, return_test_nonce)

    t.details.amount = 2.5
    try:
        assert w.verify_transaction(t, return_test_nonce)
        raise Exception("Expected to fail but did not")
    except ecdsa.keys.BadSignatureError:
        assert True
コード例 #5
0
    def new_transaction():  # pylint: disable=unused-variable
        values = request.get_json()

        # Check for required fields
        required = ["transaction"]
        if not values or not all(k in values for k in required):
            return "Missing values", 400

        details = values["transaction"]["details"]
        # Create a new Transaction

        index = blockchain.add_transaction(
            SignedRawTransaction(
                details=Details(
                    sender=details["sender"],
                    recipient=details["recipient"],
                    amount=details["amount"],
                    nonce=details["nonce"],
                    timestamp=details["timestamp"],
                    public_key=details["public_key"],
                ),
                signature=values["transaction"]["signature"],
            ),
            "open",
        )

        response = {"message": f"Transaction will be added to Block {index}"}
        return jsonify(response), 201
コード例 #6
0
def test_mining_block_with_open_transactions():
    timestamp = datetime.utcfromtimestamp(0)
    node_id = uuid4()
    w1 = Wallet(test=True)
    w2 = Wallet(test=True)

    chain = Blockchain(w1.address, node_id, difficulty=1, is_test=True)

    tx_details_1 = Details(
        sender=w1.address,
        recipient=w2.address,
        nonce=0,
        amount=0.5,
        timestamp=timestamp,
        public_key=w1.public_key.hex(),
    )
    transaction_1 = w1.sign_transaction(tx_details_1)

    tx_details_2 = Details(
        sender=w2.address,
        recipient=w1.address,
        nonce=0,
        amount=0.5,
        timestamp=timestamp,
        public_key=w2.public_key.hex(),
    )
    transaction_2 = w2.sign_transaction(tx_details_2)

    assert Verification.verify_chain(chain.chain)
    # Need to give the w1 at least 1.0 coin in their balance
    chain.mine_block()
    assert Verification.verify_chain(chain.chain)
    chain.add_transaction(transaction_1, is_receiving=True)
    chain.mine_block()
    assert Verification.verify_chain(chain.chain)
    chain_transactions = []
    for block in chain.chain:
        for tx in block.transactions:
            chain_transactions.append(tx)
    assert Verification.hash_transaction(transaction_1) in chain_transactions
    chain.add_transaction(transaction_2, is_receiving=True)
    chain.mine_block()
    chain_transactions = []
    for block in chain.chain:
        for tx in block.transactions:
            chain_transactions.append(tx)
    assert Verification.hash_transaction(transaction_2) in chain_transactions
コード例 #7
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)
コード例 #8
0
def test_multiple_transaction_happy_path():
    node_id = uuid4()
    w = Wallet(test=True)
    w2 = Wallet(test=True)
    chain = Blockchain(w.address, node_id, difficulty=1, is_test=True)
    chain.mine_block()

    timestamp = datetime.utcfromtimestamp(0)

    d = Details(
        sender=w.address,
        recipient=w2.address,
        amount=4.5,
        nonce=0,
        timestamp=timestamp,
        public_key=w.public_key.hex(),
    )

    t = w.sign_transaction(d)
    chain.add_transaction(t)

    d.nonce = 1
    t2 = w.sign_transaction(d)
    chain.add_transaction(t2)
コード例 #9
0
        def submit_transaction():
            if not self.wallet.logged_in:
                logging.error(
                    "No Wallet is currently logged in. Unable to create a transaction"
                    "without one")
                clear_transaction()
            else:
                tx_details = Details(
                    sender=self.wallet.address,
                    recipient=recipient.text(),
                    amount=float(amount.text()),
                    nonce=self.wallet.get_nonce(),
                    timestamp=datetime.utcnow(),
                    public_key=self.wallet.public_key.hex(),
                )
                transaction = self.wallet.sign_transaction(tx_details)

                if self.blockchain.add_transaction(transaction, "open"):
                    logging.info("Added transaction!")
                    clear_transaction()
                else:
                    logging.info("Transaction failed!")
コード例 #10
0
def test_broadcasting_block():
    timestamp = datetime.utcfromtimestamp(0)
    node_id = uuid4()
    w1 = Wallet(test=True)
    w2 = Wallet(test=True)

    chain1 = Blockchain(w1.address, node_id, difficulty=1, is_test=True)
    chain2 = Blockchain(w2.address, node_id, difficulty=1, is_test=True)

    chain2.chain = chain1.chain
    assert chain1.chain == chain2.chain

    details = Details(
        sender=w1.address,
        recipient=w2.address,
        nonce=0,
        amount=0.5,
        timestamp=timestamp,
        public_key=w1.public_key.hex(),
    )
    transaction_1 = w1.sign_transaction(details)

    assert Verification.verify_chain(chain1.chain)
    b = chain1.mine_block()

    result, _ = chain2.add_block(b)
    assert result

    assert Verification.verify_chain(chain1.chain)
    assert Verification.verify_chain(chain2.chain)

    chain1.add_transaction(transaction_1, is_receiving=True)
    chain1.mine_block()
    chain2.add_block(chain1.last_block)

    assert Verification.verify_chain(chain1.chain)
    assert Verification.verify_chain(chain2.chain)

    assert chain1.chain == chain2.chain
コード例 #11
0
def test_not_enough_coin():
    timestamp = datetime.utcfromtimestamp(0)
    node_id = uuid4()
    w = Wallet(test=True)
    w2 = Wallet(test=True)
    chain = Blockchain(w.address, node_id, difficulty=1, is_test=True)
    tx_details = Details(
        sender=w.address,
        recipient=w2.address,
        nonce=0,
        amount=2995.0,
        timestamp=timestamp,
        public_key=w.public_key.hex(),
    )
    transaction = w.sign_transaction(tx_details)

    assert Verification.verify_chain(chain.chain)
    try:
        chain.add_transaction(transaction, is_receiving=True)
        assert "This was expected to throw a ValueError exception but didn't"
    except ValueError:
        pass
コード例 #12
0
def test_transaction_with_signature_to_protobuf_and_back():
    w = Wallet(test=True)
    w2 = Wallet(test=True)

    timestamp = datetime.utcfromtimestamp(0)

    d = Details(
        sender=w.address,
        recipient=w2.address,
        amount=4.5,
        nonce=0,
        timestamp=timestamp,
        public_key=w.public_key.hex(),
    )

    t = w.sign_transaction(d)

    t_hex = t.SerializeToHex()

    parsed_t = SignedRawTransaction.ParseFromHex(t_hex)

    assert parsed_t == t

    assert w.verify_transaction(t, return_test_nonce)
コード例 #13
0
def test_transaction_to_protobuf_and_back():
    timestamp = datetime.utcfromtimestamp(0)

    t = SignedRawTransaction(
        details=Details(
            sender="test",
            recipient="test2",
            amount=4.5,
            nonce=0,
            timestamp=timestamp,
            public_key="pub_key",
        ),
        signature="sig",
    )
    t_hex = t.SerializeToHex()

    assert (
        t_hex
        == "0a230a04746573741205746573743219000000000000124020002a0032077075625f6b65791203736967"
    )

    parsed_t = SignedRawTransaction.ParseFromHex(t_hex)

    assert parsed_t == t
コード例 #14
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
コード例 #15
0
def test_block_to_protobuf_and_back_with_transactions():
    timestamp = datetime.utcfromtimestamp(0)
    transactions = [
        FinalTransaction(
            transaction_hash="tx_hash_1",
            transaction_id="tx_hash_1",
            signed_transaction=SignedRawTransaction(
                details=Details(
                    sender="test",
                    recipient="test2",
                    amount=4.5,
                    nonce=0,
                    timestamp=timestamp,
                    public_key="pub_key",
                ),
                signature="sig",
            ),
        )
    ]

    header = Header(
        version=1,
        previous_hash="",
        timestamp=timestamp,
        transaction_merkle_root=get_merkle_root(
            [t.signed_transaction for t in transactions]),
        difficulty=4,
        nonce=100,
    )

    block = Block(
        index=0,
        block_hash="",
        size=0,
        header=header,
        transaction_count=len(transactions),
        transactions=[t.transaction_hash for t in transactions],
    )

    p_block = block.SerializeToHex()
    assert (
        p_block ==
        "080010001a002260080112001a543061323330613034373436353733373431323035373436353733373433323139303030303030303030303030313234303230303032613030333230373730373536323566366236353739313230333733363936372200280430642801320974785f686173685f31"  # noqa: E501
    )

    og_block = Block.ParseFromHex(p_block)

    assert og_block == Block(
        index=0,
        block_hash="",
        size=0,
        header=Header(
            version=1,
            previous_hash="",
            timestamp=timestamp,
            transaction_merkle_root=get_merkle_root(
                [t.signed_transaction for t in transactions]),
            difficulty=4,
            nonce=100,
        ),
        transaction_count=len(transactions),
        transactions=[t.transaction_hash for t in transactions],
    )
コード例 #16
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