async def farm_new_block(self, request: FarmNewBlockProtocol): self.log.info("Farming new block!") top_tip = self.get_tip() if top_tip is None or self.server is None: return current_block = await self.get_current_blocks(top_tip) bundle: Optional[ SpendBundle] = await self.mempool_manager.create_bundle_for_tip( top_tip) dict_h = {} fees = 0 if bundle is not None: program = best_solution_program(bundle) dict_h[top_tip.height + 1] = (program, bundle.aggregated_signature) fees = bundle.fees() more_blocks = bt.get_consecutive_blocks( self.constants, 1, current_block, 10, reward_puzzlehash=request.puzzle_hash, transaction_data_at_height=dict_h, seed=token_bytes(), fees=uint64(fees), ) new_lca = more_blocks[-1] assert self.server is not None async for msg in self.respond_block( full_node_protocol.RespondBlock(new_lca)): self.server.push_message(msg)
async def test_basics(self): wallet_tool = WalletTool() receiver = WalletTool() num_blocks = 2 blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, reward_puzzlehash=wallet_tool.get_new_puzzlehash(), ) spend_bundle = wallet_tool.generate_signed_transaction( blocks[1].header.data.coinbase.amount, receiver.get_new_puzzlehash(), blocks[1].header.data.coinbase, ) assert spend_bundle is not None program = best_solution_program(spend_bundle) error, npc_list, clvm_cost = calculate_cost_of_program(program) error, npc_list, cost = get_name_puzzle_conditions(program) # Create condition + agg_sig_condition + length + cpu_cost ratio = constants["CLVM_COST_RATIO_CONSTANT"] assert (clvm_cost == 200 * ratio + 20 * ratio + len(bytes(program)) * ratio + cost)
async def test_validate_blockchain_spend_reorg_cb_coin(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() receiver_1_puzzlehash = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Spends a coinbase created in reorg new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks[:6], 10, b"reorg cb coin", coinbase_puzzlehash) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1])) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers spent_block = new_blocks[-1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase()) spend_bundle_2 = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_fees_coin()) block_spendbundle = SpendBundle.aggregate( [spend_bundle, spend_bundle_2]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {7: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks, 10, b"reorg cb coin", coinbase_puzzlehash, dic_h, ) error = await full_node_1.blockchain._validate_transactions( new_blocks[-1], new_blocks[-1].get_fees_coin().amount) assert error is None [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1])) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coins_created = [] for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_1_puzzlehash: coins_created.append(coin) assert len(coins_created) == 2
async def test_validate_blockchain_spend_reorg_since_genesis(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() receiver_1_puzzlehash = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1]) ) ] # Spends a coin in a genesis reorg, that was already spent dic_h = {5: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 12, [], 10, b"reorg since genesis", coinbase_puzzlehash, dic_h, ) for block in new_blocks: [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers
async def test_validate_blockchain_spend_reorg_cb_coin_freeze( self, two_nodes_standard_freeze ): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() receiver_1_puzzlehash = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes_standard_freeze for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Spends a coinbase created in reorg new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:6], 10, b"reorg cb coin", coinbase_puzzlehash ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1]) ) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers spent_block = new_blocks[-1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {7: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks, 10, b"reorg cb coin", coinbase_puzzlehash, dic_h, ) error = await full_node_1.blockchain._validate_transactions( new_blocks[-1], new_blocks[-1].get_coinbase().amount ) assert error is Err.COINBASE_NOT_YET_SPENDABLE
async def test_validate_blockchain_with_double_spend(self, two_nodes): num_blocks = 5 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase() ) spend_bundle_double = wallet_a.generate_signed_transaction( 1001, receiver_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle, spend_bundle_double]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {(num_blocks + 1): (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) next_block = new_blocks[num_blocks + 1] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.DOUBLE_SPEND
async def test_invalid_filter(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase()) assert spend_bundle is not None tx: full_node_protocol.RespondTransaction = ( full_node_protocol.RespondTransaction(spend_bundle)) async for _ in full_node_1.respond_transaction(tx): outbound: OutboundMessage = _ # Maybe transaction means that it's accepted in mempool assert outbound.message.function == "new_transaction" sb = full_node_1.mempool_manager.get_spendbundle(spend_bundle.name()) assert sb is spend_bundle last_block = blocks[10] next_spendbundle = await full_node_1.mempool_manager.create_bundle_for_tip( last_block.header) assert next_spendbundle is not None program = best_solution_program(next_spendbundle) aggsig = next_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h) next_block = new_blocks[11] bad_header = HeaderData( next_block.header.data.height, next_block.header.data.prev_header_hash, next_block.header.data.timestamp, bytes32(bytes([3] * 32)), next_block.header.data.proof_of_space_hash, next_block.header.data.weight, next_block.header.data.total_iters, next_block.header.data.additions_root, next_block.header.data.removals_root, next_block.header.data.farmer_rewards_puzzle_hash, next_block.header.data.total_transaction_fees, next_block.header.data.pool_target, next_block.header.data.aggregated_signature, next_block.header.data.cost, next_block.header.data.extension_data, next_block.header.data.generator_hash, ) bad_block = FullBlock( next_block.proof_of_space, next_block.proof_of_time, Header( bad_header, bt.get_plot_signature( bad_header, next_block.proof_of_space.plot_public_key), ), next_block.transactions_generator, next_block.transactions_filter, ) result, removed, error_code = await full_node_1.blockchain.receive_block( bad_block) assert result == ReceiveBlockResult.INVALID_BLOCK assert error_code == Err.INVALID_TRANSACTIONS_FILTER_HASH result, removed, error_code = await full_node_1.blockchain.receive_block( next_block) assert result == ReceiveBlockResult.ADDED_TO_HEAD
async def test_assert_time_exceeds(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Coinbase that gets spent block1 = blocks[1] # This condition requires block1 coinbase to be spent after 3 seconds from now current_time_plus3 = uint64(int(time.time() * 1000) + 3000) block1_cvp = ConditionVarPair(ConditionOpcode.ASSERT_TIME_EXCEEDS, int_to_bytes(current_time_plus3), None) block1_dic = {block1_cvp.opcode: [block1_cvp]} block1_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic) # program that will be sent to early assert block1_spend_bundle is not None program = best_solution_program(block1_spend_bundle) aggsig = block1_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h) # Try to validate that block before 3 sec next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.ASSERT_TIME_EXCEEDS_FAILED # wait 3 sec to pass await asyncio.sleep(3.1) dic_h = {12: (program, aggsig)} valid_new_blocks = bt.get_consecutive_blocks(test_constants, 2, blocks[:11], 10, b"", coinbase_puzzlehash, dic_h) for block in valid_new_blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Try to validate that block after 3 sec have passed next_block = valid_new_blocks[12] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is None
async def test_assert_block_age_exceeds(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Coinbase that gets spent block1 = blocks[1] # This condition requires block1 coinbase to be spent more than 10 block after it was farmed # block index has to be greater than (1 + 10 = 11) block1_cvp = ConditionVarPair(ConditionOpcode.ASSERT_BLOCK_AGE_EXCEEDS, int_to_bytes(10), None) block1_dic = {block1_cvp.opcode: [block1_cvp]} block1_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic) # program that will be sent to early assert block1_spend_bundle is not None program = best_solution_program(block1_spend_bundle) aggsig = block1_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h) # Try to validate that block at index 11 next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.ASSERT_BLOCK_AGE_EXCEEDS_FAILED dic_h = {12: (program, aggsig)} valid_new_blocks = bt.get_consecutive_blocks(test_constants, 2, blocks[:11], 10, b"", coinbase_puzzlehash, dic_h) for block in valid_new_blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Try to validate that block at index 12 next_block = valid_new_blocks[12] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is None
async def test_assert_coin_consumed(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Coinbase that gets spent block1 = blocks[1] block2 = blocks[2] # This condition requires block2 coinbase to be spent block1_cvp = ConditionVarPair( ConditionOpcode.ASSERT_COIN_CONSUMED, block2.get_coinbase().name(), None, ) block1_dic = {block1_cvp.opcode: [block1_cvp]} block1_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic) # This condition requires block1 coinbase to be spent block2_cvp = ConditionVarPair( ConditionOpcode.ASSERT_COIN_CONSUMED, block1.get_coinbase().name(), None, ) block2_dic = {block2_cvp.opcode: [block2_cvp]} block2_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block2.get_coinbase(), block2_dic) # Invalid block bundle assert block1_spend_bundle is not None solo_program = best_solution_program(block1_spend_bundle) aggsig = block1_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (solo_program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h) # Try to validate that block next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.ASSERT_COIN_CONSUMED_FAILED # bundle_together contains both transactions bundle_together = SpendBundle.aggregate( [block1_spend_bundle, block2_spend_bundle]) valid_program = best_solution_program(bundle_together) aggsig = bundle_together.aggregated_signature # Create another block that includes our transaction dic_h = {11: (valid_program, aggsig)} new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks[:11], 10, b"1", coinbase_puzzlehash, dic_h) # Try to validate newly created block next_block = new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is None
async def test_assert_my_coin_id(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Coinbase that gets spent spent_block = blocks[1] bad_block = blocks[2] valid_cvp = ConditionVarPair( ConditionOpcode.ASSERT_MY_COIN_ID, spent_block.get_coinbase().name(), None, ) valid_dic = {valid_cvp.opcode: [valid_cvp]} bad_cvp = ConditionVarPair( ConditionOpcode.ASSERT_MY_COIN_ID, bad_block.get_coinbase().name(), None, ) bad_dic = {bad_cvp.opcode: [bad_cvp]} bad_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase(), bad_dic) valid_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase(), valid_dic) # Invalid block bundle assert bad_spend_bundle is not None invalid_program = best_solution_program(bad_spend_bundle) aggsig = bad_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (invalid_program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h) # Try to validate that block next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.ASSERT_MY_COIN_ID_FAILED # Valid block bundle assert valid_spend_bundle is not None valid_program = best_solution_program(valid_spend_bundle) aggsig = valid_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (valid_program, aggsig)} new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks[:11], 10, b"1", coinbase_puzzlehash, dic_h) next_block = new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is None
async def test_new_transaction(self, two_nodes, wallet_blocks_five): full_node_1, full_node_2, server_1, server_2 = two_nodes wallet_a, wallet_receiver, blocks = wallet_blocks_five conditions_dict: Dict = {ConditionOpcode.CREATE_COIN: []} # Mempool has capacity of 100, make 110 unspents that we can use puzzle_hashes = [] for _ in range(110): receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() puzzle_hashes.append(receiver_puzzlehash) output = ConditionVarPair( ConditionOpcode.CREATE_COIN, receiver_puzzlehash, int_to_bytes(1000) ) conditions_dict[ConditionOpcode.CREATE_COIN].append(output) spend_bundle = wallet_a.generate_signed_transaction( 100, receiver_puzzlehash, blocks[1].get_coinbase(), condition_dic=conditions_dict, ) assert spend_bundle is not None new_transaction = fnp.NewTransaction( spend_bundle.get_hash(), uint64(100), uint64(100) ) # Not seen msgs = [x async for x in full_node_1.new_transaction(new_transaction)] assert len(msgs) == 1 assert msgs[0].message.data == fnp.RequestTransaction(spend_bundle.get_hash()) respond_transaction_2 = fnp.RespondTransaction(spend_bundle) [x async for x in full_node_1.respond_transaction(respond_transaction_2)] program = best_solution_program(spend_bundle) aggsig = spend_bundle.aggregated_signature dic_h = {5: (program, aggsig)} blocks_new = bt.get_consecutive_blocks( test_constants, 3, blocks[:-1], 10, transaction_data_at_height=dic_h, ) # Already seen msgs = [x async for x in full_node_1.new_transaction(new_transaction)] assert len(msgs) == 0 # Farm one block for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] spend_bundles = [] total_fee = 0 # Fill mempool for puzzle_hash in puzzle_hashes: coin_record = ( await full_node_1.coin_store.get_coin_records_by_puzzle_hash( puzzle_hash, blocks_new[-3].header ) )[0] receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() fee = random.randint(2, 499) spend_bundle = wallet_receiver.generate_signed_transaction( 500, receiver_puzzlehash, coin_record.coin, fee=fee ) respond_transaction = fnp.RespondTransaction(spend_bundle) res = [ x async for x in full_node_1.respond_transaction(respond_transaction) ] # Added to mempool if len(res) > 0: total_fee += fee spend_bundles.append(spend_bundle) # Mempool is full new_transaction = fnp.NewTransaction( token_bytes(32), uint64(1000000), uint64(1) ) msgs = [x async for x in full_node_1.new_transaction(new_transaction)] assert len(msgs) == 0 agg = SpendBundle.aggregate(spend_bundles) program = best_solution_program(agg) aggsig = agg.aggregated_signature dic_h = {8: (program, aggsig)} blocks_new = bt.get_consecutive_blocks( test_constants, 1, blocks_new, 10, transaction_data_at_height=dic_h, fees=uint64(total_fee), ) # Farm one block to clear mempool [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(blocks_new[-1]))]
async def test_validate_blockchain_spend_reorg_coin(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() receiver_1_puzzlehash = wallet_a.get_new_puzzlehash() receiver_2_puzzlehash = wallet_a.get_new_puzzlehash() receiver_3_puzzlehash = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase()) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {5: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:5], 10, b"spend_reorg_coin", coinbase_puzzlehash, dic_h, ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1])) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coin_2 = None for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_1_puzzlehash: coin_2 = coin break assert coin_2 is not None spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_2_puzzlehash, coin_2) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {6: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks[:6], 10, b"spend_reorg_coin", coinbase_puzzlehash, dic_h, ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1])) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coin_3 = None for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_2_puzzlehash: coin_3 = coin break assert coin_3 is not None spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_3_puzzlehash, coin_3) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {7: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks[:7], 10, b"spend_reorg_coin", coinbase_puzzlehash, dic_h, ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1])) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coin_4 = None for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_3_puzzlehash: coin_4 = coin break assert coin_4 is not None
async def test_validate_blockchain_with_reorg_double_spend( self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase()) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} blocks = bt.get_consecutive_blocks(test_constants, 10, blocks, 10, b"", coinbase_puzzlehash, dic_h) # Move chain to height 20, with a spend at height 11 for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Reorg at block 5, same spend at block 13 and 14 that was previously at block 11 dic_h = {13: (program, aggsig), 14: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 9, blocks[:6], 10, b"another seed", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:13]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass next_block = new_blocks[13] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is None [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[13])) ] next_block = new_blocks[14] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.DOUBLE_SPEND # Now test Reorg at block 5, same spend at block 9 that was previously at block 11 dic_h = {9: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:6], 10, b"another seed 2", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:9]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass next_block = new_blocks[9] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is None # Now test Reorg at block 10, same spend at block 11 that was previously at block 11 dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:11], 10, b"another seed 3", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:11]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass next_block = new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is None # Now test Reorg at block 11, same spend at block 12 that was previously at block 11 dic_h = {12: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:12], 10, b"another seed 4", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:12]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass next_block = new_blocks[12] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.DOUBLE_SPEND # Now test Reorg at block 11, same spend at block 15 that was previously at block 11 dic_h = {15: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:12], 10, b"another seed 5", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:15]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass next_block = new_blocks[15] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.DOUBLE_SPEND
async def test_assert_fee_condition(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Coinbase that gets spent block1 = blocks[1] # This condition requires fee to be 10 mojo cvp_fee = ConditionVarPair(ConditionOpcode.ASSERT_FEE, int_to_bytes(10), None) block1_dic = {cvp_fee.opcode: [cvp_fee]} # This spendbundle has 9 mojo as fee invalid_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic, 9) assert invalid_spend_bundle is not None program = best_solution_program(invalid_spend_bundle) aggsig = invalid_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h, fees=uint64(9), ) # Try to validate that block at index 11 next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.ASSERT_FEE_CONDITION_FAILED # This condition requires fee to be 10 mojo cvp_fee = ConditionVarPair(ConditionOpcode.ASSERT_FEE, int_to_bytes(10), None) condition_dict = {cvp_fee.opcode: [cvp_fee]} valid_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), condition_dict, 10) assert valid_spend_bundle is not None valid_program = best_solution_program(valid_spend_bundle) aggsig = valid_spend_bundle.aggregated_signature dic_h = {11: (valid_program, aggsig)} valid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:11], 10, b"", coinbase_puzzlehash, dic_h, fees=uint64(10), ) next_block = valid_new_blocks[11] fee_base = calculate_base_fee(next_block.height) error = await full_node_1.blockchain._validate_transactions( next_block, fee_base) assert error is None for block in valid_new_blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass
async def test_request_removals(self, two_nodes, wallet_blocks): full_node_1, full_node_2, server_1, server_2 = two_nodes wallet_a, wallet_receiver, blocks = wallet_blocks await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None) blocks_list = await get_block_path(full_node_1) blocks_new = bt.get_consecutive_blocks( test_constants, 5, seed=b"test_request_removals" ) # Request removals for nonexisting block fails msgs = [ _ async for _ in full_node_1.request_removals( wallet_protocol.RequestRemovals( blocks_new[-1].height, blocks_new[-1].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RejectRemovalsRequest) # Request removals for orphaned block fails for block in blocks_new: async for _ in full_node_1.respond_block(fnp.RespondBlock(block)): pass msgs = [ _ async for _ in full_node_1.request_removals( wallet_protocol.RequestRemovals( blocks_new[-1].height, blocks_new[-1].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RejectRemovalsRequest) # If there are no transactions, empty proof and coins blocks_new = bt.get_consecutive_blocks( test_constants, 10, block_list=blocks_list, ) for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] msgs = [ _ async for _ in full_node_1.request_removals( wallet_protocol.RequestRemovals( blocks_new[-4].height, blocks_new[-4].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals) assert len(msgs[0].message.data.coins) == 0 assert msgs[0].message.data.proofs is None # Add a block with transactions spend_bundles = [] for i in range(5): spend_bundles.append( wallet_a.generate_signed_transaction( 100, wallet_a.get_new_puzzlehash(), blocks_new[i - 8].get_coinbase(), ) ) height_with_transactions = len(blocks_new) + 1 agg = SpendBundle.aggregate(spend_bundles) dic_h = { height_with_transactions: ( best_solution_program(agg), agg.aggregated_signature, ) } blocks_new = bt.get_consecutive_blocks( test_constants, 5, block_list=blocks_new, transaction_data_at_height=dic_h ) for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] # If no coins requested, respond all coins and NO proof msgs = [ _ async for _ in full_node_1.request_removals( wallet_protocol.RequestRemovals( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, None, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals) assert len(msgs[0].message.data.coins) == 5 assert msgs[0].message.data.proofs is None removals_merkle_set = MerkleSet() for sb in spend_bundles: for coin in sb.removals(): if coin is not None: removals_merkle_set.add_already_hashed(coin.name()) # Ask for one coin and check PoI coin_list = [spend_bundles[0].removals()[0].name()] msgs = [ _ async for _ in full_node_1.request_removals( wallet_protocol.RequestRemovals( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, coin_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals) assert len(msgs[0].message.data.coins) == 1 assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 1 assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.removals_root, coin_list[0], msgs[0].message.data.proofs[0][1], ) # Ask for one coin and check PoE coin_list = [token_bytes(32)] msgs = [ _ async for _ in full_node_1.request_removals( wallet_protocol.RequestRemovals( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, coin_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals) assert len(msgs[0].message.data.coins) == 1 assert msgs[0].message.data.coins[0][1] is None assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 1 assert confirm_not_included_already_hashed( blocks_new[height_with_transactions].header.data.removals_root, coin_list[0], msgs[0].message.data.proofs[0][1], ) # Ask for two coins coin_list = [spend_bundles[0].removals()[0].name(), token_bytes(32)] msgs = [ _ async for _ in full_node_1.request_removals( wallet_protocol.RequestRemovals( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, coin_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals) assert len(msgs[0].message.data.coins) == 2 assert msgs[0].message.data.coins[0][1] is not None assert msgs[0].message.data.coins[1][1] is None assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 2 assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.removals_root, coin_list[0], msgs[0].message.data.proofs[0][1], ) assert confirm_not_included_already_hashed( blocks_new[height_with_transactions].header.data.removals_root, coin_list[1], msgs[0].message.data.proofs[1][1], )
async def test_short_sync_with_transactions_wallet(self, wallet_node): full_node_1, wallet_node, server_1, server_2 = wallet_node wallet_a = wallet_node.wallet_state_manager.main_wallet wallet_a_dummy = WalletTool() wallet_b = WalletTool() coinbase_puzzlehash = await wallet_a.get_new_puzzlehash() coinbase_puzzlehash_rest = wallet_b.get_new_puzzlehash() puzzle_hashes = [ await wallet_a.get_new_puzzlehash() for _ in range(10) ] puzzle_hashes.append(wallet_b.get_new_puzzlehash()) blocks = bt.get_consecutive_blocks(test_constants, 3, [], 10, b"", coinbase_puzzlehash) for block in blocks: [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)) ] await asyncio.sleep(2) await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) await asyncio.sleep(2) assert (wallet_node.wallet_state_manager.block_records[ wallet_node.wallet_state_manager.lca].height == 1) server_2.global_connections.close_all_connections() dic_h = {} prev_coin = blocks[1].header.data.coinbase for i in range(11): pk, sk = await wallet_a.wallet_state_manager.get_keys( prev_coin.puzzle_hash) transaction_unsigned = wallet_a_dummy.generate_unsigned_transaction( 1000, puzzle_hashes[i], prev_coin, {}, 0, secretkey=sk) spend_bundle = await wallet_a.sign_transaction(transaction_unsigned ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature prev_coin = Coin(prev_coin.name(), puzzle_hashes[i], uint64(1000)) dic_h[i + 4] = (program, aggsig) blocks = bt.get_consecutive_blocks(test_constants, 13, blocks, 10, b"", coinbase_puzzlehash_rest, dic_h) # Move chain to height 16, with consecutive transactions in blocks 4 to 14 for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Do a short sync from 0 to 14 await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) start = time.time() broke = False while time.time() - start < 60: if (wallet_node.wallet_state_manager.block_records[ wallet_node.wallet_state_manager.lca].height >= 14 # Tip at 16, LCA at 14 ): broke = True break await asyncio.sleep(0.1) if not broke: raise Exception( f"Took too long to process blocks, stopped at: {time.time() - start}" ) server_2.global_connections.close_all_connections() # 2 block rewards and 3 fees assert await wallet_a.get_confirmed_balance() == ( blocks[1].header.data.coinbase.amount * 2) + (blocks[1].header.data.fees_coin.amount * 3) # All of our coins are spent and puzzle hashes present for puzzle_hash in puzzle_hashes[:-1]: records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( puzzle_hash) assert len(records) == 1 assert records[0].spent and not records[0].coinbase # Then do the same but in a reorg chain dic_h = {} prev_coin = blocks[1].header.data.coinbase for i in range(11): pk, sk = await wallet_a.wallet_state_manager.get_keys( prev_coin.puzzle_hash) transaction_unsigned = wallet_a_dummy.generate_unsigned_transaction( 1000, puzzle_hashes[i], prev_coin, {}, 0, secretkey=sk) spend_bundle = await wallet_a.sign_transaction(transaction_unsigned ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature prev_coin = Coin(prev_coin.name(), puzzle_hashes[i], uint64(1000)) dic_h[i + 4] = (program, aggsig) blocks = bt.get_consecutive_blocks( test_constants, 31, blocks[:4], 10, b"this is a reorg", coinbase_puzzlehash_rest, dic_h, ) # Move chain to height 34, with consecutive transactions in blocks 4 to 14 for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Do a sync from 0 to 22 await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) broke = False while time.time() - start < 60: if (wallet_node.wallet_state_manager.block_records[ wallet_node.wallet_state_manager.lca].height >= 28 # Tip at 34, LCA at least 28 ): broke = True break await asyncio.sleep(0.1) if not broke: raise Exception( f"Took too long to process blocks, stopped at: {time.time() - start}" ) server_2.global_connections.close_all_connections() # 2 block rewards and 3 fees assert await wallet_a.get_confirmed_balance() == ( blocks[1].header.data.coinbase.amount * 2) + (blocks[1].header.data.fees_coin.amount * 3) # All of our coins are spent and puzzle hashes present for puzzle_hash in puzzle_hashes[:-1]: records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( puzzle_hash) assert len(records) == 1 assert records[0].spent and not records[0].coinbase # Test spending the rewards earned in reorg new_coinbase_puzzlehash = await wallet_a.get_new_puzzlehash() another_puzzlehash = await wallet_a.get_new_puzzlehash() dic_h = {} pk, sk = await wallet_a.wallet_state_manager.get_keys( new_coinbase_puzzlehash) coinbase_coin = create_coinbase_coin(25, new_coinbase_puzzlehash, uint64(14000000000000)) transaction_unsigned = wallet_a_dummy.generate_unsigned_transaction( 7000000000000, another_puzzlehash, coinbase_coin, {}, 0, secretkey=sk) spend_bundle = await wallet_a.sign_transaction(transaction_unsigned) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h[26] = (program, aggsig) # Farm a block (25) to ourselves blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:25], 10, b"this is yet another reorg", new_coinbase_puzzlehash, ) # Brings height up to 40, with block 31 having half our reward spent to us blocks = bt.get_consecutive_blocks( test_constants, 15, blocks, 10, b"this is yet another reorg more blocks", coinbase_puzzlehash_rest, dic_h, ) for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) broke = False while time.time() - start < 60: if (wallet_node.wallet_state_manager.block_records[ wallet_node.wallet_state_manager.lca].height >= 38 # Tip at 40, LCA at 38 ): broke = True break await asyncio.sleep(0.1) if not broke: raise Exception( f"Took too long to process blocks, stopped at: {time.time() - start}" ) # 2 block rewards and 4 fees, plus 7000000000000 coins assert (await wallet_a.get_confirmed_balance() == (blocks[1].header.data.coinbase.amount * 2) + (blocks[1].header.data.fees_coin.amount * 4) + 7000000000000) records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( new_coinbase_puzzlehash) # Fee and coinbase assert len(records) == 2 assert records[0].spent != records[1].spent assert records[0].coinbase == records[1].coinbase records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( another_puzzlehash) assert len(records) == 1 assert not records[0].spent assert not records[0].coinbase
async def test_basic_blockchain_tx(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase()) assert spend_bundle is not None tx: full_node_protocol.RespondTransaction = ( full_node_protocol.RespondTransaction(spend_bundle)) async for _ in full_node_1.respond_transaction(tx): outbound: OutboundMessage = _ # Maybe transaction means that it's accepted in mempool assert outbound.message.function == "new_transaction" sb = full_node_1.mempool_manager.get_spendbundle(spend_bundle.name()) assert sb is spend_bundle last_block = blocks[10] next_spendbundle = await full_node_1.mempool_manager.create_bundle_for_tip( last_block.header) assert next_spendbundle is not None program = best_solution_program(next_spendbundle) aggsig = next_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h) next_block = new_blocks[11] async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(next_block)): pass tips = full_node_1.blockchain.get_current_tips() assert next_block.header in tips added_coins = next_spendbundle.additions() # Two coins are added, main spend and change assert len(added_coins) == 2 for coin in added_coins: unspent = await full_node_1.coin_store.get_coin_record( coin.name(), next_block.header) assert unspent is not None full_tips = await full_node_1.blockchain.get_full_tips() in_full_tips = False farmed_block: Optional[FullBlock] = None for tip in full_tips: if tip.header == next_block.header: in_full_tips = True farmed_block = tip assert in_full_tips assert farmed_block is not None assert farmed_block.transactions_generator == program
async def add_spendbundle( self, new_spend: SpendBundle, to_pool: Mempool = None ) -> Tuple[Optional[uint64], MempoolInclusionStatus, Optional[Err]]: """ Tries to add spendbundle to either self.mempools or to_pool if it's specified. Returns true if it's added in any of pools, Returns error if it fails. """ self.seen_bundle_hashes[new_spend.name()] = new_spend.name() self.maybe_pop_seen() # Calculate the cost and fees program = best_solution_program(new_spend) # npc contains names of the coins removed, puzzle_hashes and their spend conditions fail_reason, npc_list, cost = calculate_cost_of_program(program) if fail_reason: return None, MempoolInclusionStatus.FAILED, fail_reason # build removal list removal_names: List[bytes32] = new_spend.removal_names() additions = new_spend.additions() additions_dict: Dict[bytes32, Coin] = {} for add in additions: additions_dict[add.name()] = add addition_amount = uint64(0) # Check additions for max coin amount for coin in additions: if coin.amount >= uint64.from_bytes(self.constants["MAX_COIN_AMOUNT"]): return ( None, MempoolInclusionStatus.FAILED, Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, ) addition_amount = uint64(addition_amount + coin.amount) # Check for duplicate outputs addition_counter = collections.Counter(_.name() for _ in additions) for k, v in addition_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DUPLICATE_OUTPUT # Spend might be valid for one pool but not for other added_count = 0 errors: List[Err] = [] targets: List[Mempool] # If the transaction is added to potential set (to be retried), this is set. added_to_potential: bool = False potential_error: Optional[Err] = None if to_pool is not None: targets = [to_pool] else: targets = list(self.mempools.values()) for pool in targets: # Skip if already added if new_spend.name() in pool.spends: added_count += 1 continue removal_record_dict: Dict[bytes32, CoinRecord] = {} removal_coin_dict: Dict[bytes32, Coin] = {} unknown_unspent_error: bool = False removal_amount = uint64(0) for name in removal_names: removal_record = await self.unspent_store.get_coin_record( name, pool.header ) if removal_record is None and name not in additions_dict: unknown_unspent_error = True break elif name in additions_dict: removal_coin = additions_dict[name] removal_record = CoinRecord( removal_coin, uint32(pool.header.height + 1), uint32(0), False, False, ) assert removal_record is not None removal_amount = uint64(removal_amount + removal_record.coin.amount) removal_record_dict[name] = removal_record removal_coin_dict[name] = removal_record.coin if unknown_unspent_error: errors.append(Err.UNKNOWN_UNSPENT) continue if addition_amount > removal_amount: return None, MempoolInclusionStatus.FAILED, Err.MINTING_COIN fees = removal_amount - addition_amount if cost == 0: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN fees_per_cost: float = fees / cost # If pool is at capacity check the fee, if not then accept even without the fee if pool.at_full_capacity(): if fees == 0: errors.append(Err.INVALID_FEE_LOW_FEE) continue if fees_per_cost < pool.get_min_fee_rate(): errors.append(Err.INVALID_FEE_LOW_FEE) continue # Check removals against UnspentDB + DiffStore + Mempool + SpendBundle # Use this information later when constructing a block fail_reason, conflicts = await self.check_removals( removal_record_dict, pool ) # If there is a mempool conflict check if this spendbundle has a higher fee per cost than all others tmp_error: Optional[Err] = None conflicting_pool_items: Dict[bytes32, MempoolItem] = {} if fail_reason is Err.MEMPOOL_CONFLICT: for conflicting in conflicts: sb: MempoolItem = pool.removals[conflicting.name()] conflicting_pool_items[sb.name] = sb for item in conflicting_pool_items.values(): if item.fee_per_cost >= fees_per_cost: tmp_error = Err.MEMPOOL_CONFLICT self.add_to_potential_tx_set(new_spend) added_to_potential = True potential_error = Err.MEMPOOL_CONFLICT break elif fail_reason: errors.append(fail_reason) continue if tmp_error: errors.append(tmp_error) continue # Verify conditions, create hash_key list for aggsig check hash_key_pairs = [] error: Optional[Err] = None for npc in npc_list: coin_record: CoinRecord = removal_record_dict[npc.coin_name] # Check that the revealed removal puzzles actually match the puzzle hash if npc.puzzle_hash != coin_record.coin.puzzle_hash: log.warning( f"Mempool rejecting transaction because of wrong puzzle_hash" ) log.warning(f"{npc.puzzle_hash} != {coin_record.coin.puzzle_hash}") return None, MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH error = mempool_check_conditions_dict( coin_record, new_spend, npc.condition_dict, pool ) if error: if ( error is Err.ASSERT_BLOCK_INDEX_EXCEEDS_FAILED or error is Err.ASSERT_BLOCK_AGE_EXCEEDS_FAILED ): self.add_to_potential_tx_set(new_spend) added_to_potential = True potential_error = error break hash_key_pairs.extend( hash_key_pairs_for_conditions_dict( npc.condition_dict, npc.coin_name ) ) if error: errors.append(error) continue # Verify aggregated signature if not new_spend.aggregated_signature.validate(hash_key_pairs): return None, MempoolInclusionStatus.FAILED, Err.BAD_AGGREGATE_SIGNATURE # Remove all conflicting Coins and SpendBundles if fail_reason: mitem: MempoolItem for mitem in conflicting_pool_items.values(): pool.remove_spend(mitem) new_item = MempoolItem(new_spend, fees_per_cost, uint64(fees), uint64(cost)) pool.add_to_pool(new_item, additions, removal_coin_dict) added_count += 1 if added_count > 0: return uint64(cost), MempoolInclusionStatus.SUCCESS, None elif added_to_potential: return uint64(cost), MempoolInclusionStatus.PENDING, potential_error else: return None, MempoolInclusionStatus.FAILED, errors[0]
async def test_request_additions(self, two_nodes, wallet_blocks): full_node_1, full_node_2, server_1, server_2 = two_nodes wallet_a, wallet_receiver, blocks = wallet_blocks await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None) blocks_list = await get_block_path(full_node_1) blocks_new = bt.get_consecutive_blocks( test_constants, 5, seed=b"test_request_additions" ) # Request additinos for nonexisting block fails msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[-1].height, blocks_new[-1].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RejectAdditionsRequest) # Request additions for orphaned block fails for block in blocks_new: async for _ in full_node_1.respond_block(fnp.RespondBlock(block)): pass msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[-1].height, blocks_new[-1].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RejectAdditionsRequest) # If there are no transactions, only cb and fees additions blocks_new = bt.get_consecutive_blocks( test_constants, 10, block_list=blocks_list, ) for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[-4].height, blocks_new[-4].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 2 assert msgs[0].message.data.proofs is None # Add a block with transactions spend_bundles = [] puzzle_hashes = [wallet_a.get_new_puzzlehash(), wallet_a.get_new_puzzlehash()] for i in range(5): spend_bundles.append( wallet_a.generate_signed_transaction( 100, puzzle_hashes[i % 2], blocks_new[i - 8].get_coinbase(), ) ) height_with_transactions = len(blocks_new) + 1 agg = SpendBundle.aggregate(spend_bundles) dic_h = { height_with_transactions: ( best_solution_program(agg), agg.aggregated_signature, ) } blocks_new = bt.get_consecutive_blocks( test_constants, 5, block_list=blocks_new, transaction_data_at_height=dic_h ) for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] # If no puzzle hashes requested, respond all coins and NO proof msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, None, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) # One puzzle hash with change and fee (x3) = 9, minus two repeated ph = 7 + coinbase and fees = 9 assert len(msgs[0].message.data.coins) == 9 assert msgs[0].message.data.proofs is None additions_merkle_set = MerkleSet() for sb in spend_bundles: for coin in sb.additions(): if coin is not None: additions_merkle_set.add_already_hashed(coin.name()) # Ask for one coin and check both PoI ph_list = [puzzle_hashes[0]] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, ph_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 1 assert len(msgs[0].message.data.coins[0][1]) == 3 assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 1 assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[0], msgs[0].message.data.proofs[0][1], ) coin_list_for_ph = [ coin for coin in blocks_new[height_with_transactions].additions() if coin.puzzle_hash == ph_list[0] ] assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, hash_coin_list(coin_list_for_ph), msgs[0].message.data.proofs[0][2], ) # Ask for one ph and check PoE ph_list = [token_bytes(32)] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, ph_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 1 assert len(msgs[0].message.data.coins[0][1]) == 0 assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 1 assert confirm_not_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[0], msgs[0].message.data.proofs[0][1], ) assert msgs[0].message.data.proofs[0][2] is None # Ask for two puzzle_hashes ph_list = [puzzle_hashes[0], token_bytes(32)] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, ph_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 2 assert len(msgs[0].message.data.coins[0][1]) == 3 assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 2 assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[0], msgs[0].message.data.proofs[0][1], ) assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, hash_coin_list(coin_list_for_ph), msgs[0].message.data.proofs[0][2], ) assert confirm_not_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[1], msgs[0].message.data.proofs[1][1], ) assert msgs[0].message.data.proofs[1][2] is None