def test_uto_apply_transaction_on_coinbase(): public_key = SECP256k1PublicKey(b'x' * 64) output_0 = Output(40, public_key) output_1 = Output(34, public_key) unspent_transaction_outs = immutables.Map() transaction = Transaction(inputs=[ Input( construct_reference_to_thin_air(), CoinbaseData(0, b'coinbase of the first block'), ) ], outputs=[output_0, output_1]) result = uto_apply_transaction(unspent_transaction_outs, transaction, is_coinbase=True) assert OutputReference(transaction.hash(), 0) in result assert OutputReference(transaction.hash(), 1) in result assert result[OutputReference(transaction.hash(), 0)] == output_0 assert result[OutputReference(transaction.hash(), 1)] == output_1
def test_uto_apply_transaction_on_non_coinbase_transaction(): public_key = SECP256k1PublicKey(b'x' * 64) output_0 = Output(40, public_key) output_1 = Output(34, public_key) output_2 = Output(30, public_key) previous_transaction_hash = b'a' * 32 unspent_transaction_outs = immutables.Map({ OutputReference(previous_transaction_hash, 0): output_0, OutputReference(previous_transaction_hash, 1): output_1, }) transaction = Transaction(inputs=[ Input( OutputReference(previous_transaction_hash, 1), SECP256k1Signature(b'y' * 64), ) ], outputs=[output_2]) result = uto_apply_transaction(unspent_transaction_outs, transaction, is_coinbase=False) assert OutputReference(previous_transaction_hash, 0) in result assert OutputReference(previous_transaction_hash, 1) not in result # spent assert OutputReference(transaction.hash(), 0) in result assert result[OutputReference(previous_transaction_hash, 0)] == output_0 assert result[OutputReference(transaction.hash(), 0)] == output_2
def add_transaction_to_pool(self, transaction: Transaction) -> bool: with self.lock: self.local_peer.logger.info( "%15s ChainManager.add_transaction_to_pool(%s)" % ("", human(transaction.hash()))) try: validate_non_coinbase_transaction_by_itself(transaction) assert self.coinstate.current_chain_hash validate_non_coinbase_transaction_in_coinstate( transaction, self.coinstate.current_chain_hash, self.coinstate) # Horribly inefficiently implemented (AKA 'room for improvement) validate_no_duplicate_output_references_in_transactions( self.transaction_pool + [transaction]) # we don't do validate_no_duplicate_transactions here (assuming it's just been done before # add_transaction_to_pool). except ValidateTransactionError as e: # TODO: dirty hack at this particular point... to allow for e.g. out-of-order transactions to not take # down the whole peer, but this should more specifically match for a short list of OK problems. self.local_peer.logger.warning("%15s INVALID transaction %s" % ("", str(e))) self.local_peer.disk_interface.save_transaction_for_debugging( transaction) return False # not successful self.transaction_pool.append(transaction) return True # successfully added
def test_pkb_apply_transaction_on_non_coinbase_transaction(): public_key_0 = SECP256k1PublicKey(b'\x00' * 64) public_key_1 = SECP256k1PublicKey(b'\x01' * 64) public_key_2 = SECP256k1PublicKey(b'\x02' * 64) output_0 = Output(40, public_key_0) output_1 = Output(34, public_key_1) output_3 = Output(66, public_key_1) final_output = Output(30, public_key_2) previous_transaction_hash = b'a' * 32 unspent_transaction_outs = immutables.Map({ OutputReference(previous_transaction_hash, 0): output_0, OutputReference(previous_transaction_hash, 1): output_1, OutputReference(previous_transaction_hash, 2): output_3, }) public_key_balances = immutables.Map({ public_key_0: PKBalance(0, []), public_key_1: PKBalance(100, [ OutputReference(previous_transaction_hash, 1), OutputReference(previous_transaction_hash, 2), ]), }) transaction = Transaction(inputs=[ Input( OutputReference(previous_transaction_hash, 1), SECP256k1Signature(b'y' * 64), ) ], outputs=[final_output]) result = pkb_apply_transaction(unspent_transaction_outs, public_key_balances, transaction, is_coinbase=False) assert result[ public_key_0].value == 0 # not referenced in the transaction under consideration assert result[public_key_0].output_references == [] assert result[public_key_1].value == 100 - 34 assert result[public_key_1].output_references == [ OutputReference(previous_transaction_hash, 2) ] assert result[ public_key_2].value == 30 # the value of the transaction output assert result[public_key_2].output_references == [ OutputReference(transaction.hash(), 0) ]
def pkb2_apply_transaction( unspent_transaction_outs: immutables.Map[OutputReference, Output], public_key_balances: immutables.Map[PublicKey, PKBalance2], transaction: Transaction, is_coinbase: bool, ) -> immutables.Map[PublicKey, PKBalance2]: with public_key_balances.mutate() as mutable_public_key_balances: # for coinbase we must skip the input-removal because the input references "thin air" rather than an output. if not is_coinbase: for input in transaction.inputs: previously_unspent_output: Output = unspent_transaction_outs[ input.output_reference] public_key = previously_unspent_output.public_key mutable_public_key_balances[public_key] = PKBalance2( value=mutable_public_key_balances[public_key].value - previously_unspent_output.value, all_output_references=mutable_public_key_balances[ public_key].all_output_references, unspent_output_references=([ to for to in mutable_public_key_balances[public_key]. unspent_output_references if to != input.output_reference ]), # in principle a single public_key could be spent more than once in a single transaction, we # could change the below into a set (or alternatively, note an input index) spent_in_transactions=mutable_public_key_balances[ public_key].spent_in_transactions + [transaction], ) for i, output in enumerate(transaction.outputs): output_reference = OutputReference(transaction.hash(), i) if output.public_key not in mutable_public_key_balances: mutable_public_key_balances[output.public_key] = PKBalance2( 0, [], [], []) mutable_public_key_balances[output.public_key] = PKBalance2( value=mutable_public_key_balances[output.public_key].value + output.value, all_output_references=(mutable_public_key_balances[ output.public_key].all_output_references + [output_reference]), unspent_output_references=(mutable_public_key_balances[ output.public_key].unspent_output_references + [output_reference]), spent_in_transactions=mutable_public_key_balances[ output.public_key].spent_in_transactions, ) return mutable_public_key_balances.finish()
def save_transaction_for_debugging(self, transaction: Transaction) -> None: with open("/tmp/%s.transaction" % human(transaction.hash()), 'wb') as f: f.write(transaction.serialize())