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_validate_non_coinbase_transaction_by_itself_no_inputs(): transaction = Transaction( inputs=[], outputs=[Output(30, example_public_key)], ) with pytest.raises(ValidateTransactionError, match=".*No inputs.*"): validate_non_coinbase_transaction_by_itself(transaction)
def test_validate_non_coinbase_transaction_by_itself(): transaction = Transaction( inputs=[Input( OutputReference(b'a' * 32, 1), SignableEquivalent(), )], outputs=[Output(30, example_public_key)]) with pytest.raises(ValidateTransactionError, match=".*Non-signature Signature class used.*"): validate_non_coinbase_transaction_by_itself(transaction)
def test_validate_non_coinbase_transaction_by_itself_max_size(): transaction = Transaction(inputs=[ Input( OutputReference(b'a' * 32, 1), SECP256k1Signature(b'y' * 64), ) ] * 30_000, outputs=[Output(30, example_public_key)]) with pytest.raises(ValidateTransactionError, match=".*MAX_BLOCK_SIZE.*"): validate_non_coinbase_transaction_by_itself(transaction)
def test_validate_non_coinbase_transaction_by_itself_is_not_coinbase(): transaction = Transaction(inputs=[ Input( OutputReference(b'\x00' * 32, 0), SECP256k1Signature(b'y' * 64), ) ], outputs=[Output(30, example_public_key)]) with pytest.raises(ValidateTransactionError, match=".*null-reference in non-coinbase transaction.*"): validate_non_coinbase_transaction_by_itself(transaction)
def test_validate_non_coinbase_transaction_by_itself_max_total_output(): transaction = Transaction( inputs=[ Input( OutputReference(b'a' * 32, 1), SECP256k1Signature(b'y' * 64), ) ], outputs=[Output(21_000_000 * SASHIMI_PER_COIN, example_public_key)]) with pytest.raises(ValidationError, match=".out of range.*"): validate_non_coinbase_transaction_by_itself(transaction)
def test_validate_non_coinbase_transaction_by_itself_no_duplicate_output_references( ): transaction = Transaction(inputs=[ Input( OutputReference(b'a' * 32, 1), SECP256k1Signature(b'y' * 64), ) ] * 2, outputs=[Output(30, example_public_key)]) with pytest.raises(ValidateTransactionError, match=".*output_reference referenced more than once.*"): validate_non_coinbase_transaction_by_itself(transaction)
def test_validate_non_coinbase_transaction_by_itself_no_outputs(): transaction = Transaction( inputs=[ Input( OutputReference(b'a' * 32, 1), SECP256k1Signature(b'y' * 64), ) ], outputs=[], ) with pytest.raises(ValidateTransactionError, match=".*No outputs.*"): validate_non_coinbase_transaction_by_itself(transaction)
def main() -> None: parser = DefaultArgumentParser() parser.add_argument("amount", help="The amount of to send", type=int) parser.add_argument("denomination", help="'skepticoin' or 'sashimi'", choices=['skepticoin', 'sashimi']) parser.add_argument("address", help="The address to send to") args = parser.parse_args() configure_logging_from_args(args) value = args.amount * (SASHIMI_PER_COIN if args.denomination == 'skepticoin' else 1) if not is_valid_address(args.address): print("Invalid address") return check_chain_dir() coinstate = read_chain_from_disk() wallet = open_or_init_wallet() thread = start_networking_peer_in_background(args, coinstate) try: # we need a fresh chain because our wallet doesn't track spending/receiving, so we need to look at the real # blockchain to know what we can spend. check_for_fresh_chain(thread) print("Chain up to date") target_address = SECP256k1PublicKey(parse_address(args.address)) change_address = SECP256k1PublicKey(wallet.get_annotated_public_key("change")) save_wallet(wallet) transaction = create_spend_transaction( wallet, coinstate, value, 0, # we'll get to paying fees later target_address, change_address, ) validate_non_coinbase_transaction_by_itself(transaction) assert coinstate.current_chain_hash validate_non_coinbase_transaction_in_coinstate(transaction, coinstate.current_chain_hash, coinstate) print("Broadcasting transaction on the network", transaction) thread.local_peer.network_manager.broadcast_transaction(transaction) print("Monitoring...") while True: sleep(5) # it's late and I'm too lazy for the efficient & correct implementation. coinstate = thread.local_peer.chain_manager.coinstate max_height = coinstate.head().height for i in range(10): block = coinstate.by_height_at_head()[max(max_height - i, 0)] if transaction in block.transactions: print("Transaction confirmed at", block.height, "with", i, "confirmation blocks") if i == 6: # this is the magic number of confirmations according to the "literature" on the subject thread.stop() return except KeyboardInterrupt: print("KeyboardInterrupt") finally: print("Stopping networking thread") thread.stop() print("Waiting for networking thread to stop") thread.join() print("Done; waiting for Python-exit")