def test_is_tx_valid_when_invalid_outputs():
    sender_wallet = Wallet()
    transaction = Transaction(sender_wallet, "b64e8ac4", 50)
    transaction.output[sender_wallet.address] = 9001

    with pytest.raises(Exception, match="Invalid transaction."):
        Transaction.is_tx_valid(transaction)
def test_block_reward_tx_when_invalid_reward_amt():
    wallet = Wallet()
    reward_tx = Transaction.generate_reward_transaction(wallet)
    reward_tx.output[wallet.address] = 100000

    with pytest.raises(Exception, match="Invalid block reward transaction."):
        Transaction.is_tx_valid(reward_tx)
    def is_tx_chain_valid(blockchain):
        """
        Validate incoming blockchain comprised of blocks with Tx therein qua the following ruleset:
            - each Tx occurs once in the blockchain (i.e. 'double spend')
            - there is only one valid reward Tx per block 
            - transaction obj must be intrinsically valid
        """
        # Tx tracked by id, raise if duplicate
        tx_tracking_pool = set()
        # unwrap blockchain, unwrap blocks therein, deserialize ea block's Tx and parse them
        for i in range(len(blockchain)):
            block = blockchain[i]
            mining_reward_extant = False
            for serialized_tx in block.data:
                deserialized_tx = Transaction.deserialize_from_json(
                    serialized_tx)

                # if Tx already exists
                if (deserialized_tx.id in tx_tracking_pool):
                    raise Exception(
                        f"Transaction {deserialized_tx.id} is not unique; this transaction is therefore invalid."
                    )
                # add Tx to tracking pool
                tx_tracking_pool.add(deserialized_tx.id)

                # if Tx is a block reward, only validate against reward fields
                if (deserialized_tx.input == MINING_REWARD_INPUT):
                    if (mining_reward_extant):
                        raise Exception(f"""
                            There can only be one mining reward per block. 
                            Evaluation of block with hash: {block.hash} recommended."""
                                        )
                    mining_reward_extant = True
                else:
                    # recalc balance after every Tx to prevent input tamper
                    blockchain_provenance = Blockchain()
                    blockchain_provenance.chain = blockchain[0:i]
                    balance_provenance = Wallet.calculate_balance(
                        blockchain_provenance,
                        deserialized_tx.input["address"])

                    if (balance_provenance != deserialized_tx.input["amount"]):
                        raise Exception(
                            f"Transaction {deserialized_tx.id} contains an invalid input amount."
                        )
                # last, run validator to check format
                Transaction.is_tx_valid(deserialized_tx)
def test_block_reward_tx_when_valid_tx():
    reward_tx = Transaction.generate_reward_transaction(Wallet())
    Transaction.is_tx_valid(reward_tx)
def test_is_tx_valid():
    Transaction.is_tx_valid(Transaction(Wallet(), "b64e8ac4", 50))
def test_block_reward_tx_when_exceeds_recipients():
    reward_tx = Transaction.generate_reward_transaction(Wallet())
    reward_tx.output["second_and_invalid_recipient"] = 100

    with pytest.raises(Exception, match="Invalid block reward transaction."):
        Transaction.is_tx_valid(reward_tx)