async def test_wallet_coinbase_reorg(self, wallet_node): num_blocks = 5 full_nodes, wallets = wallet_node full_node_api = full_nodes[0] fn_server = full_node_api.full_node.server wallet_node, server_2 = wallets[0] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client( PeerInfo(self_hostname, uint16(fn_server._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert(5, wallet.get_confirmed_balance, funds) await full_node_api.reorg_from_index_to_new_index( ReorgProtocol(uint32(3), uint32(num_blocks + 6), 32 * b"0")) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 2) ]) await time_out_assert(5, wallet.get_confirmed_balance, funds)
async def test_wallet_make_transaction(self, two_wallet_nodes): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] server_1 = full_node_api.full_node.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client( PeerInfo(self_hostname, uint16(server_1._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds) tx = await wallet.generate_signed_transaction( 10, await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash( ), 0, ) await wallet.push_transaction(tx) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds - 10) await time_out_assert( 5, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) new_funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, (2 * num_blocks)) ]) await time_out_assert(5, wallet.get_confirmed_balance, new_funds - 10) await time_out_assert(5, wallet.get_unconfirmed_balance, new_funds - 10)
async def test_wallet_make_transaction_with_fee(self, two_wallet_nodes): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client(PeerInfo(self_hostname, uint16(full_node_1.full_node.server._port)), None) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) funds = sum( [calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)] ) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds) assert await wallet.get_confirmed_balance() == funds assert await wallet.get_unconfirmed_balance() == funds tx_amount = 3200000000000 tx_fee = 10 tx = await wallet.generate_signed_transaction( tx_amount, await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash(), tx_fee, ) fees = tx.spend_bundle.fees() assert fees == tx_fee await wallet.push_transaction(tx) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds - tx_amount - tx_fee) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0")) new_funds = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ] ) await time_out_assert(5, wallet.get_confirmed_balance, new_funds - tx_amount - tx_fee) await time_out_assert(5, wallet.get_unconfirmed_balance, new_funds - tx_amount - tx_fee)
async def test_get_wallet_for_colour(self, two_wallet_nodes): num_blocks = 3 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node, server_2 = wallets[0] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client( PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ]) await time_out_assert(15, wallet.get_confirmed_balance, funds) cc_wallet: CCWallet = await CCWallet.create_new_cc( wallet_node.wallet_state_manager, wallet, uint64(100)) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) colour = cc_wallet.get_colour() assert await wallet_node.wallet_state_manager.get_wallet_for_colour( colour) == cc_wallet
async def test_colour_creation(self, two_wallet_nodes): num_blocks = 3 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node, server_2 = wallets[0] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) funds = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ] ) await time_out_assert(15, wallet.get_confirmed_balance, funds) cc_wallet: CCWallet = await CCWallet.create_new_cc(wallet_node.wallet_state_manager, wallet, uint64(100)) tx_queue: List[TransactionRecord] = await wallet_node.wallet_state_manager.tx_store.get_not_sent() tx_record = tx_queue[0] await time_out_assert( 15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name() ) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(32 * b"0")) await time_out_assert(15, cc_wallet.get_confirmed_balance, 100) await time_out_assert(15, cc_wallet.get_unconfirmed_balance, 100)
async def get_total_block_rewards(self, num_blocks): funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) return funds
async def test_cat_creation(self, two_wallet_nodes, trusted): num_blocks = 3 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node, server_2 = wallets[0] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { full_node_server.node_id.hex(): full_node_server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} await server_2.start_client( PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ]) await time_out_assert(15, wallet.get_confirmed_balance, funds) async with wallet_node.wallet_state_manager.lock: cat_wallet: CATWallet = await CATWallet.create_new_cat_wallet( wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100)) # The next 2 lines are basically a noop, it just adds test coverage cat_wallet = await CATWallet.create( wallet_node.wallet_state_manager, wallet, cat_wallet.wallet_info) await wallet_node.wallet_state_manager.add_new_wallet( cat_wallet, cat_wallet.id()) tx_queue: List[ TransactionRecord] = await wallet_node.wallet_state_manager.tx_store.get_not_sent( ) tx_record = tx_queue[0] await time_out_assert(15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx_record.spend_bundle.name()) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) await time_out_assert(15, cat_wallet.get_confirmed_balance, 100) await time_out_assert(15, cat_wallet.get_spendable_balance, 100) await time_out_assert(15, cat_wallet.get_unconfirmed_balance, 100)
async def test_wallet_reorg_sync(self, wallet_node_simulator, default_400_blocks, trusted): num_blocks = 5 full_nodes, wallets = wallet_node_simulator full_node_api = full_nodes[0] wallet_node, server_2 = wallets[0] fn_server = full_node_api.full_node.server wsm: WalletStateManager = wallet_node.wallet_state_manager wallet = wsm.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { fn_server.node_id.hex(): fn_server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} await server_2.start_client( PeerInfo(self_hostname, uint16(fn_server._port)), None) # Insert 400 blocks for block in default_400_blocks: await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(block)) # Farm few more with reward for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) # Confirm we have the funds funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert(5, wallet.get_confirmed_balance, funds) async def get_tx_count(wallet_id): txs = await wsm.get_all_transactions(wallet_id) return len(txs) await time_out_assert(5, get_tx_count, 2 * (num_blocks - 1), 1) # Reorg blocks that carry reward num_blocks = 30 blocks_reorg = bt.get_consecutive_blocks( num_blocks, block_list_input=default_400_blocks[:-5]) for block in blocks_reorg[-30:]: await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(block)) await time_out_assert(5, get_tx_count, 0, 1) await time_out_assert(5, wallet.get_confirmed_balance, 0)
async def test_generate_zero_val(self, two_wallet_nodes): num_blocks = 4 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet wallet2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) await server_3.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) funds = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ] ) await time_out_assert(15, wallet.get_confirmed_balance, funds) cc_wallet: CCWallet = await CCWallet.create_new_cc(wallet_node.wallet_state_manager, wallet, uint64(100)) ph = await wallet2.get_new_puzzlehash() for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, cc_wallet.get_confirmed_balance, 100) await time_out_assert(15, cc_wallet.get_unconfirmed_balance, 100) assert cc_wallet.cc_info.my_genesis_checker is not None colour = cc_wallet.get_colour() cc_wallet_2: CCWallet = await CCWallet.create_wallet_for_cc(wallet_node_2.wallet_state_manager, wallet2, colour) assert cc_wallet.cc_info.my_genesis_checker == cc_wallet_2.cc_info.my_genesis_checker spend_bundle = await cc_wallet_2.generate_zero_val_coin() await time_out_assert(15, tx_in_pool, True, full_node_api.full_node.mempool_manager, spend_bundle.name()) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) async def unspent_count(): unspent: List[WalletCoinRecord] = list( await cc_wallet_2.wallet_state_manager.get_spendable_coins_for_wallet(cc_wallet_2.id()) ) return len(unspent) await time_out_assert(15, unspent_count, 1) unspent: List[WalletCoinRecord] = list( await cc_wallet_2.wallet_state_manager.get_spendable_coins_for_wallet(cc_wallet_2.id()) ) assert unspent.pop().coin.amount == 0
async def main() -> None: rpc_port: uint16 = uint16(8555) self_hostname = "localhost" path = DEFAULT_ROOT_PATH config = load_config(path, "config.yaml") client = await FullNodeRpcClient.create(self_hostname, rpc_port, path, config) try: farmer_prefarm = ( await client.get_block_record_by_height(1)).reward_claims_incorporated[1] pool_prefarm = ( await client.get_block_record_by_height(1)).reward_claims_incorporated[0] pool_amounts = int(calculate_pool_reward(uint32(0)) / 2) farmer_amounts = int(calculate_base_farmer_reward(uint32(0)) / 2) print(farmer_prefarm.amount, farmer_amounts) assert farmer_amounts == farmer_prefarm.amount // 2 assert pool_amounts == pool_prefarm.amount // 2 address1 = "xch1rdatypul5c642jkeh4yp933zu3hw8vv8tfup8ta6zfampnyhjnusxdgns6" # Key 1 address2 = "xch1duvy5ur5eyj7lp5geetfg84cj2d7xgpxt7pya3lr2y6ke3696w9qvda66e" # Key 2 ph1 = decode_puzzle_hash(address1) ph2 = decode_puzzle_hash(address2) p_farmer_2 = Program.to( binutils.assemble( f"(q . ((51 0x{ph1.hex()} {farmer_amounts}) (51 0x{ph2.hex()} {farmer_amounts})))" )) p_pool_2 = Program.to( binutils.assemble( f"(q . ((51 0x{ph1.hex()} {pool_amounts}) (51 0x{ph2.hex()} {pool_amounts})))" )) p_solution = Program.to(binutils.assemble("()")) sb_farmer = SpendBundle( [CoinSolution(farmer_prefarm, p_farmer_2, p_solution)], G2Element()) sb_pool = SpendBundle( [CoinSolution(pool_prefarm, p_pool_2, p_solution)], G2Element()) print(sb_pool, sb_farmer) res = await client.push_tx(sb_farmer) # res = await client.push_tx(sb_pool) print(res) up = await client.get_coin_records_by_puzzle_hash( farmer_prefarm.puzzle_hash, True) uf = await client.get_coin_records_by_puzzle_hash( pool_prefarm.puzzle_hash, True) print(up) print(uf) finally: client.close()
async def test_wallet_reorg_get_coinbase(self, wallet_node_simulator, default_400_blocks): full_nodes, wallets = wallet_node_simulator full_node_api = full_nodes[0] wallet_node, server_2 = wallets[0] fn_server = full_node_api.full_node.server wsm = wallet_node.wallet_state_manager wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client( PeerInfo(self_hostname, uint16(fn_server._port)), None) # Insert 400 blocks for block in default_400_blocks: await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(block)) # Reorg blocks that carry reward num_blocks_reorg = 30 blocks_reorg = bt.get_consecutive_blocks( num_blocks_reorg, block_list_input=default_400_blocks[:-5]) for block in blocks_reorg[:-5]: await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(block)) async def get_tx_count(wallet_id): txs = await wsm.get_all_transactions(wallet_id) return len(txs) await time_out_assert(10, get_tx_count, 0, 1) num_blocks_reorg_1 = 40 blocks_reorg_1 = bt.get_consecutive_blocks( 1, pool_reward_puzzle_hash=ph, farmer_reward_puzzle_hash=ph, block_list_input=blocks_reorg[:-30]) blocks_reorg_2 = bt.get_consecutive_blocks( num_blocks_reorg_1, block_list_input=blocks_reorg_1) for block in blocks_reorg_2[-41:]: await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(block)) await disconnect_all_and_reconnect(server_2, fn_server) # Confirm we have the funds funds = calculate_pool_reward(uint32( len(blocks_reorg_1))) + calculate_base_farmer_reward( uint32(len(blocks_reorg_1))) await time_out_assert(10, get_tx_count, 2, 1) await time_out_assert(10, wallet.get_confirmed_balance, funds)
async def test_get_wallet_for_asset_id(self, two_wallet_nodes, trusted): num_blocks = 3 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node, server_2 = wallets[0] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { full_node_server.node_id.hex(): full_node_server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} await server_2.start_client( PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ]) await time_out_assert(15, wallet.get_confirmed_balance, funds) async with wallet_node.wallet_state_manager.lock: cat_wallet: CATWallet = await CATWallet.create_new_cat_wallet( wallet_node.wallet_state_manager, wallet, {"identifier": "genesis_by_id"}, uint64(100)) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) asset_id = cat_wallet.get_asset_id() await cat_wallet.set_tail_program( bytes(cat_wallet.cat_info.my_tail).hex()) assert await wallet_node.wallet_state_manager.get_wallet_for_asset_id( asset_id) == cat_wallet # Test that the a default CAT will initialize correctly asset = DEFAULT_CATS[next(iter(DEFAULT_CATS))] asset_id = asset["asset_id"] cat_wallet_2 = await CATWallet.create_wallet_for_cat( wallet_node.wallet_state_manager, wallet, asset_id) assert await cat_wallet_2.get_name() == asset["name"] await cat_wallet_2.set_name("Test Name") assert await cat_wallet_2.get_name() == "Test Name"
async def test_wallet_coinbase(self, wallet_node, trusted): num_blocks = 10 full_nodes, wallets = wallet_node full_node_api = full_nodes[0] server_1: ChiaServer = full_node_api.full_node.server wallet_node, server_2 = wallets[0] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { server_1.node_id.hex(): server_1.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} await server_2.start_client( PeerInfo(self_hostname, uint16(server_1._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_block(FarmNewBlockProtocol(ph)) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph) ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph) ) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 2) ]) async def check_tx_are_pool_farm_rewards(): wsm: WalletStateManager = wallet_node.wallet_state_manager all_txs = await wsm.get_all_transactions(1) expected_count = (num_blocks + 1) * 2 if len(all_txs) != expected_count: return False pool_rewards = 0 farm_rewards = 0 for tx in all_txs: if tx.type == TransactionType.COINBASE_REWARD: pool_rewards += 1 elif tx.type == TransactionType.FEE_REWARD: farm_rewards += 1 if pool_rewards != expected_count / 2: return False if farm_rewards != expected_count / 2: return False return True await time_out_assert(10, check_tx_are_pool_farm_rewards, True) await time_out_assert(5, wallet.get_confirmed_balance, funds)
async def test_wallet_send_to_three_peers(self, three_sim_two_wallets): num_blocks = 10 full_nodes, wallets = three_sim_two_wallets wallet_0, wallet_server_0 = wallets[0] full_node_api_0 = full_nodes[0] full_node_api_1 = full_nodes[1] full_node_api_2 = full_nodes[2] full_node_0 = full_node_api_0.full_node full_node_1 = full_node_api_1.full_node full_node_2 = full_node_api_2.full_node server_0 = full_node_0.server server_1 = full_node_1.server server_2 = full_node_2.server ph = await wallet_0.wallet_state_manager.main_wallet.get_new_puzzlehash() # wallet0 <-> sever0 await wallet_server_0.start_client(PeerInfo(self_hostname, uint16(server_0._port)), None) for i in range(0, num_blocks): await full_node_api_0.farm_new_transaction_block(FarmNewBlockProtocol(ph)) all_blocks = await full_node_api_0.get_all_full_blocks() for block in all_blocks: await full_node_1.respond_block(RespondBlock(block)) await full_node_2.respond_block(RespondBlock(block)) funds = sum( [calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)] ) await time_out_assert(5, wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance, funds) tx = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction(10, 32 * b"0", 0) await wallet_0.wallet_state_manager.main_wallet.push_transaction(tx) await time_out_assert_not_none(5, full_node_0.mempool_manager.get_spendbundle, tx.spend_bundle.name()) # wallet0 <-> sever1 await wallet_server_0.start_client(PeerInfo(self_hostname, uint16(server_1._port)), wallet_0.on_connect) await time_out_assert_not_none(5, full_node_1.mempool_manager.get_spendbundle, tx.spend_bundle.name()) # wallet0 <-> sever2 await wallet_server_0.start_client(PeerInfo(self_hostname, uint16(server_2._port)), wallet_0.on_connect) await time_out_assert_not_none(5, full_node_2.mempool_manager.get_spendbundle, tx.spend_bundle.name())
async def test_did_recovery_with_empty_set(self, two_wallet_nodes): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] server_1 = full_node_1.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) await server_3.start_client( PeerInfo("localhost", uint16(server_1._port)), None) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ]) await time_out_assert(15, wallet.get_confirmed_balance, funds) async with wallet_node.wallet_state_manager.lock: did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node.wallet_state_manager, wallet, uint64(101)) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) coins = await did_wallet.select_coins(1) coin = coins.pop() info = Program.to([]) pubkey = (await did_wallet.wallet_state_manager.get_unused_derivation_record( did_wallet.wallet_info.id)).pubkey spend_bundle = await did_wallet.recovery_spend( coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))) additions = spend_bundle.additions() assert additions == []
def get_future_reward_coins(block: FullBlock) -> Tuple[Coin, Coin]: pool_amount = calculate_pool_reward(block.height) farmer_amount = calculate_base_farmer_reward(block.height) if block.is_transaction_block(): assert block.transactions_info is not None farmer_amount = uint64(farmer_amount + block.transactions_info.fees) pool_coin: Coin = create_pool_coin( block.height, block.foliage.foliage_block_data.pool_target.puzzle_hash, pool_amount, constants.GENESIS_CHALLENGE) farmer_coin: Coin = create_farmer_coin( block.height, block.foliage.foliage_block_data.farmer_reward_puzzle_hash, farmer_amount, constants.GENESIS_CHALLENGE, ) return pool_coin, farmer_coin
async def test_almost_recent(self, wallet_node, default_1000_blocks, trusted): # Tests the edge case of receiving funds right before the recent blocks in weight proof full_node_api, wallet_node, full_node_server, wallet_server = wallet_node for block in default_1000_blocks: await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(block)) wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { full_node_server.node_id.hex(): full_node_server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} # Tests a reorg with the wallet num_blocks = 20 new_blocks = bt.get_consecutive_blocks( num_blocks, block_list_input=default_1000_blocks, pool_reward_puzzle_hash=ph) for i in range(1000, len(new_blocks)): await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(new_blocks[i])) new_blocks = bt.get_consecutive_blocks( test_constants.WEIGHT_PROOF_RECENT_BLOCKS + 10, block_list_input=new_blocks) for i in range(1020, len(new_blocks)): await full_node_api.full_node.respond_block( full_node_protocol.RespondBlock(new_blocks[i])) await wallet_server.start_client( PeerInfo(self_hostname, uint16(full_node_server._port)), None) await time_out_assert(30, wallet.get_confirmed_balance, 20 * calculate_pool_reward(1000))
async def test_wallet_coinbase(self, wallet_node): num_blocks = 5 full_nodes, wallets = wallet_node full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node, server_2 = wallets[0] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client( PeerInfo(self_hostname, uint16(full_node_server._port)), None) for i in range(num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) # funds += calculate_base_farmer_reward(0) await asyncio.sleep(2) print(await wallet.get_confirmed_balance(), funds) await time_out_assert(10, wallet.get_confirmed_balance, funds)
async def validate_block_body( constants: ConsensusConstants, blocks: BlockchainInterface, block_store: BlockStore, coin_store: CoinStore, peak: Optional[BlockRecord], block: Union[FullBlock, UnfinishedBlock], height: uint32, npc_result: Optional[NPCResult], fork_point_with_peak: Optional[uint32], get_block_generator: Callable, validate_signature=True, ) -> Tuple[Optional[Err], Optional[NPCResult]]: """ This assumes the header block has been completely validated. Validates the transactions and body of the block. Returns None for the first value if everything validates correctly, or an Err if something does not validate. For the second value, returns a CostResult only if validation succeeded, and there are transactions. In other cases it returns None. The NPC result is the result of running the generator with the previous generators refs. It is only present for transaction blocks which have spent coins. """ if isinstance(block, FullBlock): assert height == block.height prev_transaction_block_height: uint32 = uint32(0) # 1. For non transaction-blocs: foliage block, transaction filter, transactions info, and generator must # be empty. If it is a block but not a transaction block, there is no body to validate. Check that all fields are # None if block.foliage.foliage_transaction_block_hash is None: if (block.foliage_transaction_block is not None or block.transactions_info is not None or block.transactions_generator is not None): return Err.NOT_BLOCK_BUT_HAS_DATA, None prev_tb: BlockRecord = blocks.block_record(block.prev_header_hash) while not prev_tb.is_transaction_block: prev_tb = blocks.block_record(prev_tb.prev_hash) assert prev_tb.timestamp is not None if len(block.transactions_generator_ref_list) > 0: return Err.NOT_BLOCK_BUT_HAS_DATA, None return None, None # This means the block is valid # All checks below this point correspond to transaction blocks # 2. For blocks, foliage block, transactions info must not be empty if block.foliage_transaction_block is None or block.transactions_info is None: return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA, None assert block.foliage_transaction_block is not None # keeps track of the reward coins that need to be incorporated expected_reward_coins: Set[Coin] = set() # 3. The transaction info hash in the Foliage block must match the transaction info if block.foliage_transaction_block.transactions_info_hash != std_hash( block.transactions_info): return Err.INVALID_TRANSACTIONS_INFO_HASH, None # 4. The foliage block hash in the foliage block must match the foliage block if block.foliage.foliage_transaction_block_hash != std_hash( block.foliage_transaction_block): return Err.INVALID_FOLIAGE_BLOCK_HASH, None # 5. The reward claims must be valid for the previous blocks, and current block fees # If height == 0, expected_reward_coins will be left empty if height > 0: # Add reward claims for all blocks from the prev prev block, until the prev block (including the latter) prev_transaction_block = blocks.block_record( block.foliage_transaction_block.prev_transaction_block_hash) prev_transaction_block_height = prev_transaction_block.height assert prev_transaction_block.fees is not None pool_coin = create_pool_coin( prev_transaction_block_height, prev_transaction_block.pool_puzzle_hash, calculate_pool_reward(prev_transaction_block.height), constants.GENESIS_CHALLENGE, ) farmer_coin = create_farmer_coin( prev_transaction_block_height, prev_transaction_block.farmer_puzzle_hash, uint64( calculate_base_farmer_reward(prev_transaction_block.height) + prev_transaction_block.fees), constants.GENESIS_CHALLENGE, ) # Adds the previous block expected_reward_coins.add(pool_coin) expected_reward_coins.add(farmer_coin) # For the second block in the chain, don't go back further if prev_transaction_block.height > 0: curr_b = blocks.block_record(prev_transaction_block.prev_hash) while not curr_b.is_transaction_block: expected_reward_coins.add( create_pool_coin( curr_b.height, curr_b.pool_puzzle_hash, calculate_pool_reward(curr_b.height), constants.GENESIS_CHALLENGE, )) expected_reward_coins.add( create_farmer_coin( curr_b.height, curr_b.farmer_puzzle_hash, calculate_base_farmer_reward(curr_b.height), constants.GENESIS_CHALLENGE, )) curr_b = blocks.block_record(curr_b.prev_hash) if set(block.transactions_info.reward_claims_incorporated ) != expected_reward_coins: return Err.INVALID_REWARD_COINS, None if len(block.transactions_info.reward_claims_incorporated) != len( expected_reward_coins): return Err.INVALID_REWARD_COINS, None removals: List[bytes32] = [] coinbase_additions: List[Coin] = list(expected_reward_coins) additions: List[Coin] = [] npc_list: List[NPC] = [] removals_puzzle_dic: Dict[bytes32, bytes32] = {} cost: uint64 = uint64(0) # In header validation we check that timestamp is not more that 10 minutes into the future # 6. No transactions before INITIAL_TRANSACTION_FREEZE timestamp # (this test has been removed) # 7a. The generator root must be the hash of the serialized bytes of # the generator for this block (or zeroes if no generator) if block.transactions_generator is not None: if std_hash(bytes(block.transactions_generator) ) != block.transactions_info.generator_root: return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None else: if block.transactions_info.generator_root != bytes([0] * 32): return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None # 8a. The generator_ref_list must be the hash of the serialized bytes of # the generator ref list for this block (or 'one' bytes [0x01] if no generator) # 8b. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries # 8c. The generator ref list must not point to a height >= this block's height if block.transactions_generator_ref_list in (None, []): if block.transactions_info.generator_refs_root != bytes([1] * 32): return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None else: # If we have a generator reference list, we must have a generator if block.transactions_generator is None: return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None # The generator_refs_root must be the hash of the concatenation of the List[uint32] generator_refs_hash = std_hash(b"".join( [bytes(i) for i in block.transactions_generator_ref_list])) if block.transactions_info.generator_refs_root != generator_refs_hash: return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None if len(block.transactions_generator_ref_list ) > constants.MAX_GENERATOR_REF_LIST_SIZE: return Err.TOO_MANY_GENERATOR_REFS, None if any([ index >= height for index in block.transactions_generator_ref_list ]): return Err.FUTURE_GENERATOR_REFS, None if block.transactions_generator is not None: # Get List of names removed, puzzles hashes for removed coins and conditions created assert npc_result is not None cost = calculate_cost_of_program(block.transactions_generator, npc_result, constants.COST_PER_BYTE) npc_list = npc_result.npc_list # 7. Check that cost <= MAX_BLOCK_COST_CLVM log.debug( f"Cost: {cost} max: {constants.MAX_BLOCK_COST_CLVM} " f"percent full: {round(100 * (cost / constants.MAX_BLOCK_COST_CLVM), 2)}%" ) if cost > constants.MAX_BLOCK_COST_CLVM: return Err.BLOCK_COST_EXCEEDS_MAX, None # 8. The CLVM program must not return any errors if npc_result.error is not None: return Err(npc_result.error), None for npc in npc_list: removals.append(npc.coin_name) removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash additions = additions_for_npc(npc_list) else: assert npc_result is None # 9. Check that the correct cost is in the transactions info if block.transactions_info.cost != cost: return Err.INVALID_BLOCK_COST, None additions_dic: Dict[bytes32, Coin] = {} # 10. Check additions for max coin amount # Be careful to check for 64 bit overflows in other languages. This is the max 64 bit unsigned integer # We will not even reach here because Coins do type checking (uint64) for coin in additions + coinbase_additions: additions_dic[coin.name()] = coin if coin.amount < 0: return Err.COIN_AMOUNT_NEGATIVE, None if coin.amount > constants.MAX_COIN_AMOUNT: return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None # 11. Validate addition and removal roots root_error = validate_block_merkle_roots( block.foliage_transaction_block.additions_root, block.foliage_transaction_block.removals_root, additions + coinbase_additions, removals, ) if root_error: return root_error, None # 12. The additions and removals must result in the correct filter byte_array_tx: List[bytes32] = [] for coin in additions + coinbase_additions: # TODO: address hint error and remove ignore # error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32" # [arg-type] byte_array_tx.append(bytearray( coin.puzzle_hash)) # type: ignore[arg-type] for coin_name in removals: # TODO: address hint error and remove ignore # error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32" # [arg-type] byte_array_tx.append(bytearray(coin_name)) # type: ignore[arg-type] bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded_filter = bytes(bip158.GetEncoded()) filter_hash = std_hash(encoded_filter) if filter_hash != block.foliage_transaction_block.filter_hash: return Err.INVALID_TRANSACTIONS_FILTER_HASH, None # 13. Check for duplicate outputs in additions addition_counter = collections.Counter( _.name() for _ in additions + coinbase_additions) for k, v in addition_counter.items(): if v > 1: return Err.DUPLICATE_OUTPUT, None # 14. Check for duplicate spends inside block removal_counter = collections.Counter(removals) for k, v in removal_counter.items(): if v > 1: return Err.DOUBLE_SPEND, None # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block) # The fork point is the last block in common between the peak chain and the chain of `block` if peak is None or height == 0: fork_h: int = -1 elif fork_point_with_peak is not None: fork_h = fork_point_with_peak else: fork_h = find_fork_point_in_chain( blocks, peak, blocks.block_record(block.prev_header_hash)) # Get additions and removals since (after) fork_h but not including this block # The values include: the coin that was added, the height of the block in which it was confirmed, and the # timestamp of the block in which it was confirmed additions_since_fork: Dict[bytes32, Tuple[Coin, uint32, uint64]] = { } # This includes coinbase additions removals_since_fork: Set[bytes32] = set() # For height 0, there are no additions and removals before this block, so we can skip if height > 0: # First, get all the blocks in the fork > fork_h, < block.height prev_block: Optional[FullBlock] = await block_store.get_full_block( block.prev_header_hash) reorg_blocks: Dict[uint32, FullBlock] = {} curr: Optional[FullBlock] = prev_block assert curr is not None while curr.height > fork_h: if curr.height == 0: break curr = await block_store.get_full_block(curr.prev_header_hash) assert curr is not None reorg_blocks[curr.height] = curr if fork_h != -1: assert len(reorg_blocks) == height - fork_h - 1 curr = prev_block assert curr is not None while curr.height > fork_h: # Coin store doesn't contain coins from fork, we have to run generator for each block in fork if curr.transactions_generator is not None: # These blocks are in the past and therefore assumed to be valid, so get_block_generator won't raise curr_block_generator: Optional[ BlockGenerator] = await get_block_generator(curr) assert curr_block_generator is not None and curr.transactions_info is not None curr_npc_result = get_name_puzzle_conditions( curr_block_generator, min(constants.MAX_BLOCK_COST_CLVM, curr.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, ) removals_in_curr, additions_in_curr = tx_removals_and_additions( curr_npc_result.npc_list) else: removals_in_curr = [] additions_in_curr = [] for c_name in removals_in_curr: assert c_name not in removals_since_fork removals_since_fork.add(c_name) for c in additions_in_curr: assert c.name() not in additions_since_fork assert curr.foliage_transaction_block is not None additions_since_fork[c.name()] = ( c, curr.height, curr.foliage_transaction_block.timestamp) for coinbase_coin in curr.get_included_reward_coins(): assert coinbase_coin.name() not in additions_since_fork assert curr.foliage_transaction_block is not None additions_since_fork[coinbase_coin.name()] = ( coinbase_coin, curr.height, curr.foliage_transaction_block.timestamp, ) if curr.height == 0: break curr = reorg_blocks[curr.height - 1] assert curr is not None removal_coin_records: Dict[bytes32, CoinRecord] = {} for rem in removals: if rem in additions_dic: # Ephemeral coin rem_coin: Coin = additions_dic[rem] new_unspent: CoinRecord = CoinRecord( rem_coin, height, height, False, block.foliage_transaction_block.timestamp, ) removal_coin_records[new_unspent.name] = new_unspent else: unspent = await coin_store.get_coin_record(rem) if unspent is not None and unspent.confirmed_block_index <= fork_h: # Spending something in the current chain, confirmed before fork # (We ignore all coins confirmed after fork) if unspent.spent == 1 and unspent.spent_block_index <= fork_h: # Check for coins spent in an ancestor block return Err.DOUBLE_SPEND, None removal_coin_records[unspent.name] = unspent else: # This coin is not in the current heaviest chain, so it must be in the fork if rem not in additions_since_fork: # Check for spending a coin that does not exist in this fork log.error( f"Err.UNKNOWN_UNSPENT: COIN ID: {rem} NPC RESULT: {npc_result}" ) return Err.UNKNOWN_UNSPENT, None new_coin, confirmed_height, confirmed_timestamp = additions_since_fork[ rem] new_coin_record: CoinRecord = CoinRecord( new_coin, confirmed_height, uint32(0), False, confirmed_timestamp, ) removal_coin_records[new_coin_record.name] = new_coin_record # This check applies to both coins created before fork (pulled from coin_store), # and coins created after fork (additions_since_fork) if rem in removals_since_fork: # This coin was spent in the fork return Err.DOUBLE_SPEND_IN_FORK, None removed = 0 for unspent in removal_coin_records.values(): removed += unspent.coin.amount added = 0 for coin in additions: added += coin.amount # 16. Check that the total coin amount for added is <= removed if removed < added: return Err.MINTING_COIN, None fees = removed - added assert fees >= 0 assert_fee_sum: uint128 = uint128(0) for npc in npc_list: if ConditionOpcode.RESERVE_FEE in npc.condition_dict: fee_list: List[ConditionWithArgs] = npc.condition_dict[ ConditionOpcode.RESERVE_FEE] for cvp in fee_list: fee = int_from_bytes(cvp.vars[0]) if fee < 0: return Err.RESERVE_FEE_CONDITION_FAILED, None assert_fee_sum = uint128(assert_fee_sum + fee) # 17. Check that the assert fee sum <= fees, and that each reserved fee is non-negative if fees < assert_fee_sum: return Err.RESERVE_FEE_CONDITION_FAILED, None # 18. Check that the fee amount + farmer reward < maximum coin amount if fees + calculate_base_farmer_reward(height) > constants.MAX_COIN_AMOUNT: return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None # 19. Check that the computed fees are equal to the fees in the block header if block.transactions_info.fees != fees: return Err.INVALID_BLOCK_FEE_AMOUNT, None # 20. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes for unspent in removal_coin_records.values(): if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]: return Err.WRONG_PUZZLE_HASH, None # 21. Verify conditions for npc in npc_list: assert height is not None unspent = removal_coin_records[npc.coin_name] error = mempool_check_conditions_dict( unspent, npc.condition_dict, prev_transaction_block_height, block.foliage_transaction_block.timestamp, ) if error: return error, None # create hash_key list for aggsig check pairs_pks, pairs_msgs = pkm_pairs(npc_list, constants.AGG_SIG_ME_ADDITIONAL_DATA) # 22. Verify aggregated signature # TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster if not block.transactions_info.aggregated_signature: return Err.BAD_AGGREGATE_SIGNATURE, None # The pairing cache is not useful while syncing as each pairing is seen # only once, so the extra effort of populating it is not justified. # However, we force caching of pairings just for unfinished blocks # as the cache is likely to be useful when validating the corresponding # finished blocks later. if validate_signature: force_cache: bool = isinstance(block, UnfinishedBlock) if not cached_bls.aggregate_verify( pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature, force_cache): return Err.BAD_AGGREGATE_SIGNATURE, None return None, npc_result
async def test_mempool_tx_sync(self, three_nodes_two_wallets): num_blocks = 5 full_nodes, wallets = three_nodes_two_wallets wallet_0, wallet_server_0 = wallets[0] full_node_api_0 = full_nodes[0] server_0 = full_node_api_0.server full_node_api_1 = full_nodes[1] server_1 = full_node_api_1.server full_node_api_2 = full_nodes[2] server_2 = full_node_api_2.server ph = await wallet_0.wallet_state_manager.main_wallet.get_new_puzzlehash( ) # wallet0 <-> sever0 <-> server1 await wallet_server_0.start_client( PeerInfo(self_hostname, uint16(server_0._port)), None) await server_0.start_client( PeerInfo(self_hostname, uint16(server_1._port)), None) for i in range(num_blocks): await full_node_api_0.farm_new_transaction_block( FarmNewBlockProtocol(ph)) all_blocks = await full_node_api_0.get_all_full_blocks() for block in all_blocks: await full_node_api_2.full_node.respond_block( full_node_protocol.RespondBlock(block)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert( 10, wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance, funds) tx = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( 10, token_bytes(), 0) await wallet_0.wallet_state_manager.main_wallet.push_transaction(tx) await time_out_assert( 10, full_node_api_0.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, ) await time_out_assert( 10, full_node_api_1.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, ) await time_out_assert( 10, full_node_api_2.full_node.mempool_manager.get_spendbundle, None, tx.name, ) # make a final connection. # wallet0 <-> sever0 <-> server1 <-> server2 await server_1.start_client( PeerInfo(self_hostname, uint16(server_2._port)), None) await time_out_assert( 10, full_node_api_0.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, ) await time_out_assert( 10, full_node_api_1.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, ) await time_out_assert( 10, full_node_api_2.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, )
async def test_wallet_tx_reorg(self, two_wallet_nodes, trusted): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] fn_server = full_node_api.full_node.server wallet_node, server_2 = wallets[0] wallet_node: WalletNode = wallet_node wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet wallet_2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() ph2 = await wallet_2.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { fn_server.node_id.hex(): fn_server.node_id.hex() } wallet_node_2.config["trusted_peers"] = { fn_server.node_id.hex(): fn_server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} wallet_node_2.config["trusted_peers"] = {} await server_2.start_client( PeerInfo(self_hostname, uint16(fn_server._port)), None) await server_3.start_client( PeerInfo(self_hostname, uint16(fn_server._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) # Waits a few seconds to receive rewards all_blocks = await full_node_api.get_all_full_blocks() # Ensure that we use a coin that we will not reorg out coin = list(all_blocks[-3].get_included_reward_coins())[0] await asyncio.sleep(5) tx = await wallet.generate_signed_transaction(1000, ph2, coins={coin}) await wallet.push_transaction(tx) await full_node_api.full_node.respond_transaction( tx.spend_bundle, tx.name) await time_out_assert( 5, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name) await time_out_assert(5, wallet.get_confirmed_balance, funds) for i in range(0, 2): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) await time_out_assert(5, wallet_2.get_confirmed_balance, 1000) await time_out_assert( 5, wallet_node.wallet_state_manager.blockchain.get_peak_height, 7) peak_height = full_node_api.full_node.blockchain.get_peak().height print(peak_height) # Perform a reorg, which will revert the transaction in the full node and wallet, and cause wallet to resubmit await full_node_api.reorg_from_index_to_new_index( ReorgProtocol(uint32(peak_height - 3), uint32(peak_height + 3), 32 * b"0")) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, peak_height - 2) ]) await time_out_assert( 7, full_node_api.full_node.blockchain.get_peak_height, peak_height + 3) await time_out_assert( 7, wallet_node.wallet_state_manager.blockchain.get_peak_height, peak_height + 3) # Farm a few blocks so we can confirm the resubmitted transaction for i in range(0, num_blocks): await asyncio.sleep(1) await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) # By this point, the transaction should be confirmed await time_out_assert(15, wallet.get_confirmed_balance, funds - 1000) unconfirmed = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( int(wallet.id())) assert len(unconfirmed) == 0 tx_record = await wallet_node.wallet_state_manager.tx_store.get_transaction_record( tx.name) removed = tx_record.removals[0] added = tx_record.additions[0] added_1 = tx_record.additions[1] wallet_coin_record_rem = await wallet_node.wallet_state_manager.coin_store.get_coin_record( removed.name()) assert wallet_coin_record_rem.spent coin_record_full_node = await full_node_api.full_node.coin_store.get_coin_record( removed.name()) assert coin_record_full_node.spent add_1_coin_record_full_node = await full_node_api.full_node.coin_store.get_coin_record( added.name()) assert add_1_coin_record_full_node is not None assert add_1_coin_record_full_node.confirmed_block_index > 0 add_2_coin_record_full_node = await full_node_api.full_node.coin_store.get_coin_record( added_1.name()) assert add_2_coin_record_full_node is not None assert add_2_coin_record_full_node.confirmed_block_index > 0
async def test_wallet_prevent_fee_theft(self, two_wallet_nodes, trusted): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { full_node_1.full_node.server.node_id.hex(): full_node_1.full_node.server.node_id.hex() } wallet_node_2.config["trusted_peers"] = { full_node_1.full_node.server.node_id.hex(): full_node_1.full_node.server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} wallet_node_2.config["trusted_peers"] = {} await server_2.start_client( PeerInfo(self_hostname, uint16(full_node_1.full_node.server._port)), None) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds) assert await wallet.get_confirmed_balance() == funds assert await wallet.get_unconfirmed_balance() == funds tx_amount = 3200000000000 tx_fee = 300000000000 tx = await wallet.generate_signed_transaction( tx_amount, await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash( ), tx_fee, ) # extract coin_spend from generated spend_bundle for cs in tx.spend_bundle.coin_spends: if cs.additions() == []: stolen_cs = cs # get a legit signature stolen_sb = await wallet.sign_transaction([stolen_cs]) now = uint64(int(time.time())) add_list = list(stolen_sb.additions()) rem_list = list(stolen_sb.removals()) name = stolen_sb.name() stolen_tx = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=now, to_puzzle_hash=32 * b"0", amount=0, fee_amount=stolen_cs.coin.amount, confirmed=False, sent=uint32(0), spend_bundle=stolen_sb, additions=add_list, removals=rem_list, wallet_id=wallet.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=name, memos=list(compute_memos(stolen_sb).items()), ) await wallet.push_transaction(stolen_tx) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds - stolen_cs.coin.amount) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) # Funds have not decreased because stolen_tx was rejected outstanding_coinbase_rewards = 2000000000000 await time_out_assert(20, wallet.get_confirmed_balance, funds + outstanding_coinbase_rewards)
async def test_wallet_create_hit_max_send_amount(self, two_wallet_nodes, trusted): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { full_node_1.full_node.server.node_id.hex(): full_node_1.full_node.server.node_id.hex() } wallet_node_2.config["trusted_peers"] = { full_node_1.full_node.server.node_id.hex(): full_node_1.full_node.server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} wallet_node_2.config["trusted_peers"] = {} await server_2.start_client( PeerInfo(self_hostname, uint16(full_node_1.full_node.server._port)), None) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert(5, wallet.get_confirmed_balance, funds) primaries = [] for i in range(0, 600): primaries.append({"puzzlehash": ph, "amount": 100000000 + i}) tx_split_coins = await wallet.generate_signed_transaction( 1, ph, 0, primaries=primaries) await wallet.push_transaction(tx_split_coins) await time_out_assert(15, tx_in_pool, True, full_node_1.full_node.mempool_manager, tx_split_coins.spend_bundle.name()) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ]) await time_out_assert(90, wallet.get_confirmed_balance, funds) max_sent_amount = await wallet.get_max_send_amount() # 1) Generate transaction that is under the limit under_limit_tx = None try: under_limit_tx = await wallet.generate_signed_transaction( max_sent_amount - 1, ph, 0, ) except ValueError: assert ValueError assert under_limit_tx is not None # 2) Generate transaction that is equal to limit at_limit_tx = None try: at_limit_tx = await wallet.generate_signed_transaction( max_sent_amount, ph, 0, ) except ValueError: assert ValueError assert at_limit_tx is not None # 3) Generate transaction that is greater than limit above_limit_tx = None try: above_limit_tx = await wallet.generate_signed_transaction( max_sent_amount + 1, ph, 0, ) except ValueError: pass assert above_limit_tx is None
async def test_wallet_make_transaction_hop(self, two_wallet_nodes_five_freeze, trusted): num_blocks = 10 full_nodes, wallets = two_wallet_nodes_five_freeze full_node_api_0 = full_nodes[0] full_node_0 = full_node_api_0.full_node server_0 = full_node_0.server wallet_node_0, wallet_0_server = wallets[0] wallet_node_1, wallet_1_server = wallets[1] wallet_0 = wallet_node_0.wallet_state_manager.main_wallet wallet_1 = wallet_node_1.wallet_state_manager.main_wallet ph = await wallet_0.get_new_puzzlehash() if trusted: wallet_node_0.config["trusted_peers"] = { server_0.node_id.hex(): server_0.node_id.hex() } wallet_node_1.config["trusted_peers"] = { server_0.node_id.hex(): server_0.node_id.hex() } else: wallet_node_0.config["trusted_peers"] = {} wallet_node_1.config["trusted_peers"] = {} await wallet_0_server.start_client( PeerInfo(self_hostname, uint16(server_0._port)), None) await wallet_1_server.start_client( PeerInfo(self_hostname, uint16(server_0._port)), None) for i in range(0, num_blocks): await full_node_api_0.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert(5, wallet_0.get_confirmed_balance, funds) await time_out_assert(5, wallet_0.get_unconfirmed_balance, funds) assert await wallet_0.get_confirmed_balance() == funds assert await wallet_0.get_unconfirmed_balance() == funds tx = await wallet_0.generate_signed_transaction( 10, await wallet_node_1.wallet_state_manager.main_wallet.get_new_puzzlehash( ), 0, ) await wallet_0.push_transaction(tx) await time_out_assert(5, full_node_0.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name) # Full node height 11, wallet height 9 await time_out_assert(5, wallet_0.get_confirmed_balance, funds) await time_out_assert(5, wallet_0.get_unconfirmed_balance, funds - 10) for i in range(0, 4): await full_node_api_0.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) # here it's num_blocks + 1 because our last reward is included in the first block that we just farmed new_funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ]) # Full node height 17, wallet height 15 await time_out_assert(5, wallet_0.get_confirmed_balance, new_funds - 10) await time_out_assert(5, wallet_0.get_unconfirmed_balance, new_funds - 10) await time_out_assert(5, wallet_1.get_confirmed_balance, 10) tx = await wallet_1.generate_signed_transaction( 5, await wallet_0.get_new_puzzlehash(), 0) await wallet_1.push_transaction(tx) await time_out_assert(5, full_node_0.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name) for i in range(0, 4): await full_node_api_0.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) await wallet_0.get_confirmed_balance() await wallet_0.get_unconfirmed_balance() await wallet_1.get_confirmed_balance() await time_out_assert(5, wallet_0.get_confirmed_balance, new_funds - 5) await time_out_assert(5, wallet_0.get_unconfirmed_balance, new_funds - 5) await time_out_assert(5, wallet_1.get_confirmed_balance, 5)
async def test_creation_from_backup_file(self, three_wallet_nodes): num_blocks = 5 full_nodes, wallets = three_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.server wallet_node_0, server_0 = wallets[0] wallet_node_1, server_1 = wallets[1] wallet_node_2, server_2 = wallets[2] wallet_0 = wallet_node_0.wallet_state_manager.main_wallet wallet_1 = wallet_node_1.wallet_state_manager.main_wallet wallet_2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet_0.get_new_puzzlehash() ph1 = await wallet_1.get_new_puzzlehash() ph2 = await wallet_2.get_new_puzzlehash() await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) funds = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ] ) await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds) await time_out_assert(10, wallet_0.get_confirmed_balance, funds) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) # Wallet1 sets up DIDWallet1 without any backup set async with wallet_node_0.wallet_state_manager.lock: did_wallet_0: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(101) ) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet_0.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_0.get_unconfirmed_balance, 101) await time_out_assert(15, did_wallet_0.get_pending_change_balance, 0) # Wallet1 sets up DIDWallet_1 with DIDWallet_0 as backup backup_ids = [bytes.fromhex(did_wallet_0.get_my_DID())] async with wallet_node_1.wallet_state_manager.lock: did_wallet_1: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node_1.wallet_state_manager, wallet_1, uint64(201), backup_ids ) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 201) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 201) await time_out_assert(15, did_wallet_1.get_pending_change_balance, 0) filename = "test.backup" did_wallet_1.create_backup(filename) # Wallet2 recovers DIDWallet2 to a new set of keys async with wallet_node_2.wallet_state_manager.lock: did_wallet_2 = await DIDWallet.create_new_did_wallet_from_recovery( wallet_node_2.wallet_state_manager, wallet_2, filename ) coins = await did_wallet_1.select_coins(1) coin = coins.copy().pop() assert did_wallet_2.did_info.temp_coin == coin newpuzhash = await did_wallet_2.get_new_inner_hash() pubkey = bytes( (await did_wallet_2.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)).pubkey ) message_spend_bundle = await did_wallet_0.create_attestment( did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey, "test.attest" ) print(f"pubkey: {pubkey}") for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) ( test_info_list, test_message_spend_bundle, ) = await did_wallet_2.load_attest_files_for_recovery_spend(["test.attest"]) assert message_spend_bundle == test_message_spend_bundle await did_wallet_2.recovery_spend( did_wallet_2.did_info.temp_coin, newpuzhash, test_info_list, pubkey, test_message_spend_bundle, ) print(f"pubkey: {did_wallet_2}") for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(45, did_wallet_2.get_confirmed_balance, 201) await time_out_assert(45, did_wallet_2.get_unconfirmed_balance, 201) some_ph = 32 * b"\2" await did_wallet_2.create_exit_spend(some_ph) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) async def get_coins_with_ph(): coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(True, some_ph) if len(coins) == 1: return True return False await time_out_assert(15, get_coins_with_ph, True) await time_out_assert(45, did_wallet_2.get_confirmed_balance, 0) await time_out_assert(45, did_wallet_2.get_unconfirmed_balance, 0)
async def test_did_attest_after_recovery(self, two_wallet_nodes): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] server_1 = full_node_1.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet wallet2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None) await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) funds = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ] ) await time_out_assert(15, wallet.get_confirmed_balance, funds) async with wallet_node.wallet_state_manager.lock: did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node.wallet_state_manager, wallet, uint64(101) ) ph2 = await wallet2.get_new_puzzlehash() for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) recovery_list = [bytes.fromhex(did_wallet.get_my_DID())] async with wallet_node_2.wallet_state_manager.lock: did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node_2.wallet_state_manager, wallet2, uint64(101), recovery_list ) ph = await wallet.get_new_puzzlehash() for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet_2.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_2.get_unconfirmed_balance, 101) assert did_wallet_2.did_info.backup_ids == recovery_list # Update coin with new ID info recovery_list = [bytes.fromhex(did_wallet_2.get_my_DID())] await did_wallet.update_recovery_list(recovery_list, uint64(1)) assert did_wallet.did_info.backup_ids == recovery_list await did_wallet.create_update_spend() for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) # DID Wallet 2 recovers into DID Wallet 3 with new innerpuz filename = "test.backup" did_wallet_2.create_backup(filename) async with wallet_node.wallet_state_manager.lock: did_wallet_3 = await DIDWallet.create_new_did_wallet_from_recovery( wallet_node.wallet_state_manager, wallet, filename, ) new_ph = await did_wallet_3.get_new_inner_hash() coins = await did_wallet_2.select_coins(1) coin = coins.pop() pubkey = ( await did_wallet_3.wallet_state_manager.get_unused_derivation_record(did_wallet_3.wallet_info.id) ).pubkey message_spend_bundle = await did_wallet.create_attestment(coin.name(), new_ph, pubkey, "test.attest") for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) ( info, message_spend_bundle, ) = await did_wallet_3.load_attest_files_for_recovery_spend(["test.attest"]) await did_wallet_3.recovery_spend(coin, new_ph, info, pubkey, message_spend_bundle) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet_3.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_3.get_unconfirmed_balance, 101) # DID Wallet 1 recovery spends into DID Wallet 4 filename = "test.backup" did_wallet.create_backup(filename) async with wallet_node_2.wallet_state_manager.lock: did_wallet_4 = await DIDWallet.create_new_did_wallet_from_recovery( wallet_node_2.wallet_state_manager, wallet2, filename, ) coins = await did_wallet.select_coins(1) coin = coins.pop() new_ph = await did_wallet_4.get_new_inner_hash() pubkey = ( await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_4.wallet_info.id) ).pubkey await did_wallet_3.create_attestment(coin.name(), new_ph, pubkey, "test.attest") for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) ( test_info_list, test_message_spend_bundle, ) = await did_wallet_4.load_attest_files_for_recovery_spend(["test.attest"]) spend_bundle = await did_wallet_4.recovery_spend( coin, new_ph, test_info_list, pubkey, test_message_spend_bundle ) await time_out_assert_not_none(15, full_node_1.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet_4.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 101) await time_out_assert(15, did_wallet.get_confirmed_balance, 0) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 0)
async def test_did_recovery_with_multiple_backup_dids(self, two_wallet_nodes): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] server_1 = full_node_1.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet wallet2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None) await server_3.start_client(PeerInfo("localhost", uint16(server_1._port)), None) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) funds = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ] ) await time_out_assert(15, wallet.get_confirmed_balance, funds) async with wallet_node.wallet_state_manager.lock: did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node.wallet_state_manager, wallet, uint64(101) ) ph = await wallet2.get_new_puzzlehash() for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) recovery_list = [bytes.fromhex(did_wallet.get_my_DID())] async with wallet_node_2.wallet_state_manager.lock: did_wallet_2: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node_2.wallet_state_manager, wallet2, uint64(101), recovery_list ) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet_2.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_2.get_unconfirmed_balance, 101) assert did_wallet_2.did_info.backup_ids == recovery_list recovery_list.append(bytes.fromhex(did_wallet_2.get_my_DID())) async with wallet_node_2.wallet_state_manager.lock: did_wallet_3: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node_2.wallet_state_manager, wallet2, uint64(201), recovery_list ) ph2 = await wallet.get_new_puzzlehash() for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) assert did_wallet_3.did_info.backup_ids == recovery_list await time_out_assert(15, did_wallet_3.get_confirmed_balance, 201) await time_out_assert(15, did_wallet_3.get_unconfirmed_balance, 201) coins = await did_wallet_3.select_coins(1) coin = coins.pop() filename = "test.backup" did_wallet_3.create_backup(filename) async with wallet_node.wallet_state_manager.lock: did_wallet_4 = await DIDWallet.create_new_did_wallet_from_recovery( wallet_node.wallet_state_manager, wallet, filename, ) pubkey = ( await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id) ).pubkey new_ph = await did_wallet_4.get_new_inner_hash() message_spend_bundle = await did_wallet.create_attestment(coin.name(), new_ph, pubkey, "test1.attest") message_spend_bundle2 = await did_wallet_2.create_attestment(coin.name(), new_ph, pubkey, "test2.attest") message_spend_bundle = message_spend_bundle.aggregate([message_spend_bundle, message_spend_bundle2]) ( test_info_list, test_message_spend_bundle, ) = await did_wallet_4.load_attest_files_for_recovery_spend(["test1.attest", "test2.attest"]) assert message_spend_bundle == test_message_spend_bundle for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, message_spend_bundle) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block(FarmNewBlockProtocol(ph2)) await time_out_assert(15, did_wallet_4.get_confirmed_balance, 201) await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 201) await time_out_assert(15, did_wallet_3.get_confirmed_balance, 0) await time_out_assert(15, did_wallet_3.get_unconfirmed_balance, 0)
async def test_wallet_make_transaction(self, two_wallet_nodes): test_rpc_port = uint16(21529) test_rpc_port_node = uint16(21530) num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.full_node.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet wallet_2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() ph_2 = await wallet_2.get_new_puzzlehash() await server_2.start_client( PeerInfo("localhost", uint16(full_node_server._port)), None) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph)) initial_funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) initial_funds_eventually = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ]) wallet_rpc_api = WalletRpcApi(wallet_node) config = bt.config hostname = config["self_hostname"] daemon_port = config["daemon_port"] def stop_node_cb(): pass full_node_rpc_api = FullNodeRpcApi(full_node_api.full_node) rpc_cleanup_node = await start_rpc_server( full_node_rpc_api, hostname, daemon_port, test_rpc_port_node, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) rpc_cleanup = await start_rpc_server( wallet_rpc_api, hostname, daemon_port, test_rpc_port, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) await time_out_assert(5, wallet.get_confirmed_balance, initial_funds) await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds) client = await WalletRpcClient.create(self_hostname, test_rpc_port, bt.root_path, config) await validate_get_routes(client, wallet_rpc_api) client_node = await FullNodeRpcClient.create(self_hostname, test_rpc_port_node, bt.root_path, config) try: addr = encode_puzzle_hash( await wallet_node_2.wallet_state_manager.main_wallet. get_new_puzzlehash(), "xch") tx_amount = 15600000 try: await client.send_transaction("1", 100000000000000001, addr) raise Exception("Should not create high value tx") except ValueError: pass # Tests sending a basic transaction tx = await client.send_transaction("1", tx_amount, addr) transaction_id = tx.name async def tx_in_mempool(): tx = await client.get_transaction("1", transaction_id) return tx.is_in_mempool() await time_out_assert(5, tx_in_mempool, True) await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds - tx_amount) assert ( await client.get_wallet_balance("1") )["unconfirmed_wallet_balance"] == initial_funds - tx_amount assert (await client.get_wallet_balance("1") )["confirmed_wallet_balance"] == initial_funds for i in range(0, 5): await full_node_api.farm_new_transaction_block( FarmNewBlockProtocol(ph_2)) async def eventual_balance(): return ( await client.get_wallet_balance("1"))["confirmed_wallet_balance"] await time_out_assert(5, eventual_balance, initial_funds_eventually - tx_amount) # Tests offline signing ph_3 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash( ) ph_4 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash( ) ph_5 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash( ) # Test basic transaction to one output signed_tx_amount = 888000 tx_res: TransactionRecord = await client.create_signed_transaction( [{ "amount": signed_tx_amount, "puzzle_hash": ph_3 }]) assert tx_res.fee_amount == 0 assert tx_res.amount == signed_tx_amount assert len(tx_res.additions) == 2 # The output and the change assert any([ addition.amount == signed_tx_amount for addition in tx_res.additions ]) push_res = await client_node.push_tx(tx_res.spend_bundle) assert push_res["success"] assert ( await client.get_wallet_balance("1") )["confirmed_wallet_balance"] == initial_funds_eventually - tx_amount for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) await time_out_assert( 5, eventual_balance, initial_funds_eventually - tx_amount - signed_tx_amount) # Test transaction to two outputs, from a specified coin, with a fee coin_to_spend = None for addition in tx_res.additions: if addition.amount != signed_tx_amount: coin_to_spend = addition assert coin_to_spend is not None tx_res = await client.create_signed_transaction( [{ "amount": 444, "puzzle_hash": ph_4 }, { "amount": 999, "puzzle_hash": ph_5 }], coins=[coin_to_spend], fee=100, ) assert tx_res.fee_amount == 100 assert tx_res.amount == 444 + 999 assert len(tx_res.additions) == 3 # The outputs and the change assert any( [addition.amount == 444 for addition in tx_res.additions]) assert any( [addition.amount == 999 for addition in tx_res.additions]) assert sum([rem.amount for rem in tx_res.removals]) - sum( [ad.amount for ad in tx_res.additions]) == 100 push_res = await client_node.push_tx(tx_res.spend_bundle) assert push_res["success"] for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) new_balance = initial_funds_eventually - tx_amount - signed_tx_amount - 444 - 999 - 100 await time_out_assert(5, eventual_balance, new_balance) send_tx_res: TransactionRecord = await client.send_transaction_multi( "1", [{ "amount": 555, "puzzle_hash": ph_4 }, { "amount": 666, "puzzle_hash": ph_5 }], fee=200) assert send_tx_res is not None assert send_tx_res.fee_amount == 200 assert send_tx_res.amount == 555 + 666 assert len( send_tx_res.additions) == 3 # The outputs and the change assert any( [addition.amount == 555 for addition in send_tx_res.additions]) assert any( [addition.amount == 666 for addition in send_tx_res.additions]) assert (sum([rem.amount for rem in send_tx_res.removals]) - sum([ad.amount for ad in send_tx_res.additions]) == 200) await asyncio.sleep(3) for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) new_balance = new_balance - 555 - 666 - 200 await time_out_assert(5, eventual_balance, new_balance) address = await client.get_next_address("1", True) assert len(address) > 10 transactions = await client.get_transactions("1") assert len(transactions) > 1 all_transactions = await client.get_transactions("1") # Test transaction pagination some_transactions = await client.get_transactions("1", 0, 5) some_transactions_2 = await client.get_transactions("1", 5, 10) assert some_transactions == all_transactions[0:5] assert some_transactions_2 == all_transactions[5:10] # Testing sorts # Test the default sort (CONFIRMED_AT_HEIGHT) assert all_transactions == sorted( all_transactions, key=attrgetter("confirmed_at_height")) all_transactions = await client.get_transactions("1", reverse=True) assert all_transactions == sorted( all_transactions, key=attrgetter("confirmed_at_height"), reverse=True) # Test RELEVANCE await client.send_transaction("1", 1, encode_puzzle_hash(ph_2, "xch") ) # Create a pending tx all_transactions = await client.get_transactions( "1", sort_key=SortKey.RELEVANCE) sorted_transactions = sorted(all_transactions, key=attrgetter("created_at_time"), reverse=True) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed_at_height"), reverse=True) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed")) assert all_transactions == sorted_transactions all_transactions = await client.get_transactions( "1", sort_key=SortKey.RELEVANCE, reverse=True) sorted_transactions = sorted(all_transactions, key=attrgetter("created_at_time")) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed_at_height")) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed"), reverse=True) assert all_transactions == sorted_transactions pks = await client.get_public_keys() assert len(pks) == 1 assert (await client.get_height_info()) > 0 created_tx = await client.send_transaction("1", tx_amount, addr) async def tx_in_mempool_2(): tx = await client.get_transaction("1", created_tx.name) return tx.is_in_mempool() await time_out_assert(5, tx_in_mempool_2, True) assert len(await wallet.wallet_state_manager.tx_store. get_unconfirmed_for_wallet(1)) == 2 await client.delete_unconfirmed_transactions("1") assert len(await wallet.wallet_state_manager.tx_store. get_unconfirmed_for_wallet(1)) == 0 sk_dict = await client.get_private_key(pks[0]) assert sk_dict["fingerprint"] == pks[0] assert sk_dict["sk"] is not None assert sk_dict["pk"] is not None assert sk_dict["seed"] is not None mnemonic = await client.generate_mnemonic() assert len(mnemonic) == 24 await client.add_key(mnemonic) pks = await client.get_public_keys() assert len(pks) == 2 await client.log_in_and_skip(pks[1]) sk_dict = await client.get_private_key(pks[1]) assert sk_dict["fingerprint"] == pks[1] fingerprint = await client.get_logged_in_fingerprint() assert fingerprint == pks[1] # Add in reward addresses into farmer and pool for testing delete key checks # set farmer to first private key sk = await wallet_node.get_key_for_fingerprint(pks[0]) test_ph = create_puzzlehash_for_pk( master_sk_to_wallet_sk(sk, uint32(0)).get_g1()) test_config = load_config(wallet_node.root_path, "config.yaml") test_config["farmer"]["xch_target_address"] = encode_puzzle_hash( test_ph, "txch") # set pool to second private key sk = await wallet_node.get_key_for_fingerprint(pks[1]) test_ph = create_puzzlehash_for_pk( master_sk_to_wallet_sk(sk, uint32(0)).get_g1()) test_config["pool"]["xch_target_address"] = encode_puzzle_hash( test_ph, "txch") save_config(wallet_node.root_path, "config.yaml", test_config) # Check first key sk_dict = await client.check_delete_key(pks[0]) assert sk_dict["fingerprint"] == pks[0] assert sk_dict["used_for_farmer_rewards"] is True assert sk_dict["used_for_pool_rewards"] is False # Check second key sk_dict = await client.check_delete_key(pks[1]) assert sk_dict["fingerprint"] == pks[1] assert sk_dict["used_for_farmer_rewards"] is False assert sk_dict["used_for_pool_rewards"] is True # Check unknown key sk_dict = await client.check_delete_key(123456) assert sk_dict["fingerprint"] == 123456 assert sk_dict["used_for_farmer_rewards"] is False assert sk_dict["used_for_pool_rewards"] is False await client.delete_key(pks[0]) await client.log_in_and_skip(pks[1]) assert len(await client.get_public_keys()) == 1 assert not (await client.get_sync_status()) wallets = await client.get_wallets() assert len(wallets) == 1 balance = await client.get_wallet_balance(wallets[0]["id"]) assert balance["unconfirmed_wallet_balance"] == 0 test_wallet_backup_path = Path("test_wallet_backup_file") await client.create_backup(test_wallet_backup_path) assert test_wallet_backup_path.exists() test_wallet_backup_path.unlink() try: await client.send_transaction(wallets[0]["id"], 100, addr) raise Exception("Should not create tx if no balance") except ValueError: pass await client.delete_all_keys() assert len(await client.get_public_keys()) == 0 finally: # Checks that the RPC manages to stop the node client.close() client_node.close() await client.await_closed() await client_node.await_closed() await rpc_cleanup() await rpc_cleanup_node()
async def validate_block_body( constants: ConsensusConstants, blocks: BlockchainInterface, block_store: BlockStore, coin_store: CoinStore, peak: Optional[BlockRecord], block: Union[FullBlock, UnfinishedBlock], height: uint32, npc_result: Optional[NPCResult], fork_point_with_peak: Optional[uint32], get_block_generator: Callable, ) -> Tuple[Optional[Err], Optional[NPCResult]]: """ This assumes the header block has been completely validated. Validates the transactions and body of the block. Returns None for the first value if everything validates correctly, or an Err if something does not validate. For the second value, returns a CostResult if validation succeeded, and there are transactions """ if isinstance(block, FullBlock): assert height == block.height prev_transaction_block_height: uint32 = uint32(0) # 1. For non block blocks, foliage block, transaction filter, transactions info, and generator must be empty # If it is a block but not a transaction block, there is no body to validate. Check that all fields are None if block.foliage.foliage_transaction_block_hash is None: if ( block.foliage_transaction_block is not None or block.transactions_info is not None or block.transactions_generator is not None ): return Err.NOT_BLOCK_BUT_HAS_DATA, None return None, None # This means the block is valid # 2. For blocks, foliage block, transaction filter, transactions info must not be empty if ( block.foliage_transaction_block is None or block.foliage_transaction_block.filter_hash is None or block.transactions_info is None ): return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA, None # keeps track of the reward coins that need to be incorporated expected_reward_coins: Set[Coin] = set() # 3. The transaction info hash in the Foliage block must match the transaction info if block.foliage_transaction_block.transactions_info_hash != std_hash(block.transactions_info): return Err.INVALID_TRANSACTIONS_INFO_HASH, None # 4. The foliage block hash in the foliage block must match the foliage block if block.foliage.foliage_transaction_block_hash != std_hash(block.foliage_transaction_block): return Err.INVALID_FOLIAGE_BLOCK_HASH, None # 7. The reward claims must be valid for the previous blocks, and current block fees if height > 0: # Add reward claims for all blocks from the prev prev block, until the prev block (including the latter) prev_transaction_block = blocks.block_record(block.foliage_transaction_block.prev_transaction_block_hash) prev_transaction_block_height = prev_transaction_block.height assert prev_transaction_block.fees is not None pool_coin = create_pool_coin( prev_transaction_block_height, prev_transaction_block.pool_puzzle_hash, calculate_pool_reward(prev_transaction_block.height), constants.GENESIS_CHALLENGE, ) farmer_coin = create_farmer_coin( prev_transaction_block_height, prev_transaction_block.farmer_puzzle_hash, uint64(calculate_base_farmer_reward(prev_transaction_block.height) + prev_transaction_block.fees), constants.GENESIS_CHALLENGE, ) # Adds the previous block expected_reward_coins.add(pool_coin) expected_reward_coins.add(farmer_coin) # For the second block in the chain, don't go back further if prev_transaction_block.height > 0: curr_b = blocks.block_record(prev_transaction_block.prev_hash) while not curr_b.is_transaction_block: expected_reward_coins.add( create_pool_coin( curr_b.height, curr_b.pool_puzzle_hash, calculate_pool_reward(curr_b.height), constants.GENESIS_CHALLENGE, ) ) expected_reward_coins.add( create_farmer_coin( curr_b.height, curr_b.farmer_puzzle_hash, calculate_base_farmer_reward(curr_b.height), constants.GENESIS_CHALLENGE, ) ) curr_b = blocks.block_record(curr_b.prev_hash) if set(block.transactions_info.reward_claims_incorporated) != expected_reward_coins: return Err.INVALID_REWARD_COINS, None removals: List[bytes32] = [] coinbase_additions: List[Coin] = list(expected_reward_coins) additions: List[Coin] = [] coin_announcement_names: Set[bytes32] = set() puzzle_announcement_names: Set[bytes32] = set() npc_list: List[NPC] = [] removals_puzzle_dic: Dict[bytes32, bytes32] = {} cost: uint64 = uint64(0) if height <= constants.INITIAL_FREEZE_PERIOD and block.transactions_generator is not None: return Err.INITIAL_TRANSACTION_FREEZE, None if height > constants.INITIAL_FREEZE_PERIOD and constants.NETWORK_TYPE == NetworkType.MAINNET: return Err.INITIAL_TRANSACTION_FREEZE, None else: # 6a. The generator root must be the hash of the serialized bytes of # the generator for this block (or zeroes if no generator) if block.transactions_generator is not None: if std_hash(bytes(block.transactions_generator)) != block.transactions_info.generator_root: return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT, None else: if block.transactions_info.generator_root != bytes([0] * 32): return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT, None # 6b. The generator_ref_list must be the hash of the serialized bytes of # the generator ref list for this block (or 'one' bytes [0x01] if no generator) # 6c. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries if block.transactions_generator_ref_list in (None, []): if block.transactions_info.generator_refs_root != bytes([1] * 32): return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None else: # If we have a generator reference list, we must have a generator if block.transactions_generator is None: return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None # The generator_refs_root must be the hash of the concatenation of the List[uint32] generator_refs_hash = std_hash(b"".join([bytes(i) for i in block.transactions_generator_ref_list])) if block.transactions_info.generator_refs_root != generator_refs_hash: return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None if len(block.transactions_generator_ref_list) > constants.MAX_GENERATOR_REF_LIST_SIZE: return Err.PRE_SOFT_FORK_TOO_MANY_GENERATOR_REFS, None if block.transactions_generator is not None: # Get List of names removed, puzzles hashes for removed coins and conditions created assert npc_result is not None cost = calculate_cost_of_program(block.transactions_generator, npc_result, constants.COST_PER_BYTE) npc_list = npc_result.npc_list # 8. Check that cost <= MAX_BLOCK_COST_CLVM log.warning(f"Cost: {cost} max: {constants.MAX_BLOCK_COST_CLVM}") if cost > constants.MAX_BLOCK_COST_CLVM: return Err.BLOCK_COST_EXCEEDS_MAX, None if npc_result.error is not None: return Err.GENERATOR_RUNTIME_ERROR, None for npc in npc_list: removals.append(npc.coin_name) removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash additions = additions_for_npc(npc_list) coin_announcement_names = coin_announcements_names_for_npc(npc_list) puzzle_announcement_names = puzzle_announcements_names_for_npc(npc_list) else: assert npc_result is None # 9. Check that the correct cost is in the transactions info if block.transactions_info.cost != cost: return Err.INVALID_BLOCK_COST, None additions_dic: Dict[bytes32, Coin] = {} # 10. Check additions for max coin amount # Be careful to check for 64 bit overflows in other languages. This is the max 64 bit unsigned integer for coin in additions + coinbase_additions: additions_dic[coin.name()] = coin if coin.amount > constants.MAX_COIN_AMOUNT: return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None # 11. Validate addition and removal roots root_error = validate_block_merkle_roots( block.foliage_transaction_block.additions_root, block.foliage_transaction_block.removals_root, additions + coinbase_additions, removals, ) if root_error: return root_error, None # 12. The additions and removals must result in the correct filter byte_array_tx: List[bytes32] = [] for coin in additions + coinbase_additions: byte_array_tx.append(bytearray(coin.puzzle_hash)) for coin_name in removals: byte_array_tx.append(bytearray(coin_name)) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded_filter = bytes(bip158.GetEncoded()) filter_hash = std_hash(encoded_filter) if filter_hash != block.foliage_transaction_block.filter_hash: return Err.INVALID_TRANSACTIONS_FILTER_HASH, None # 13. Check for duplicate outputs in additions addition_counter = collections.Counter(_.name() for _ in additions + coinbase_additions) for k, v in addition_counter.items(): if v > 1: return Err.DUPLICATE_OUTPUT, None # 14. Check for duplicate spends inside block removal_counter = collections.Counter(removals) for k, v in removal_counter.items(): if v > 1: return Err.DOUBLE_SPEND, None # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block) if peak is None or height == 0: fork_h: int = -1 elif fork_point_with_peak is not None: fork_h = fork_point_with_peak else: fork_h = find_fork_point_in_chain(blocks, peak, blocks.block_record(block.prev_header_hash)) if fork_h == -1: coin_store_reorg_height = -1 else: last_block_in_common = await blocks.get_block_record_from_db(blocks.height_to_hash(uint32(fork_h))) assert last_block_in_common is not None coin_store_reorg_height = last_block_in_common.height # Get additions and removals since (after) fork_h but not including this block additions_since_fork: Dict[bytes32, Tuple[Coin, uint32]] = {} removals_since_fork: Set[bytes32] = set() coinbases_since_fork: Dict[bytes32, uint32] = {} if height > 0: prev_block: Optional[FullBlock] = await block_store.get_full_block(block.prev_header_hash) reorg_blocks: Dict[int, FullBlock] = {} curr: Optional[FullBlock] = prev_block assert curr is not None reorg_blocks[curr.height] = curr while curr.height > fork_h: if curr.height == 0: break curr = await block_store.get_full_block(curr.prev_header_hash) assert curr is not None reorg_blocks[curr.height] = curr curr = prev_block assert curr is not None while curr.height > fork_h: # Coin store doesn't contain coins from fork, we have to run generator for each block in fork if curr.transactions_generator is not None: curr_block_generator: Optional[BlockGenerator] = await get_block_generator(curr) assert curr_block_generator is not None npc_result = get_name_puzzle_conditions(curr_block_generator, False) removals_in_curr, additions_in_curr = tx_removals_and_additions(npc_result.npc_list) else: removals_in_curr = [] additions_in_curr = [] for c_name in removals_in_curr: removals_since_fork.add(c_name) for c in additions_in_curr: additions_since_fork[c.name()] = (c, curr.height) for coinbase_coin in curr.get_included_reward_coins(): additions_since_fork[coinbase_coin.name()] = (coinbase_coin, curr.height) coinbases_since_fork[coinbase_coin.name()] = curr.height if curr.height == 0: break curr = reorg_blocks[curr.height - 1] assert curr is not None removal_coin_records: Dict[bytes32, CoinRecord] = {} for rem in removals: if rem in additions_dic: # Ephemeral coin rem_coin: Coin = additions_dic[rem] new_unspent: CoinRecord = CoinRecord( rem_coin, height, uint32(0), False, (rem in coinbases_since_fork), block.foliage_transaction_block.timestamp, ) removal_coin_records[new_unspent.name] = new_unspent else: unspent = await coin_store.get_coin_record(rem) if unspent is not None and unspent.confirmed_block_index <= coin_store_reorg_height: # Spending something in the current chain, confirmed before fork # (We ignore all coins confirmed after fork) if unspent.spent == 1 and unspent.spent_block_index <= coin_store_reorg_height: # Check for coins spent in an ancestor block return Err.DOUBLE_SPEND, None removal_coin_records[unspent.name] = unspent else: # This coin is not in the current heaviest chain, so it must be in the fork if rem not in additions_since_fork: # Check for spending a coin that does not exist in this fork # TODO: fix this, there is a consensus bug here return Err.UNKNOWN_UNSPENT, None new_coin, confirmed_height = additions_since_fork[rem] new_coin_record: CoinRecord = CoinRecord( new_coin, confirmed_height, uint32(0), False, (rem in coinbases_since_fork), block.foliage_transaction_block.timestamp, ) removal_coin_records[new_coin_record.name] = new_coin_record # This check applies to both coins created before fork (pulled from coin_store), # and coins created after fork (additions_since_fork)> if rem in removals_since_fork: # This coin was spent in the fork return Err.DOUBLE_SPEND, None removed = 0 for unspent in removal_coin_records.values(): removed += unspent.coin.amount added = 0 for coin in additions: added += coin.amount # 16. Check that the total coin amount for added is <= removed if removed < added: return Err.MINTING_COIN, None fees = removed - added assert_fee_sum: uint64 = uint64(0) for npc in npc_list: if ConditionOpcode.RESERVE_FEE in npc.condition_dict: fee_list: List[ConditionWithArgs] = npc.condition_dict[ConditionOpcode.RESERVE_FEE] for cvp in fee_list: fee = int_from_bytes(cvp.vars[0]) assert_fee_sum = assert_fee_sum + fee # 17. Check that the assert fee sum <= fees if fees < assert_fee_sum: return Err.RESERVE_FEE_CONDITION_FAILED, None # 18. Check that the assert fee amount < maximum coin amount if fees > constants.MAX_COIN_AMOUNT: return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None # 19. Check that the computed fees are equal to the fees in the block header if block.transactions_info.fees != fees: return Err.INVALID_BLOCK_FEE_AMOUNT, None # 20. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes for unspent in removal_coin_records.values(): if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]: return Err.WRONG_PUZZLE_HASH, None # 21. Verify conditions # create hash_key list for aggsig check pairs_pks = [] pairs_msgs = [] for npc in npc_list: assert height is not None unspent = removal_coin_records[npc.coin_name] error = mempool_check_conditions_dict( unspent, coin_announcement_names, puzzle_announcement_names, npc.condition_dict, prev_transaction_block_height, block.foliage_transaction_block.timestamp, ) if error: return error, None for pk, m in pkm_pairs_for_conditions_dict( npc.condition_dict, npc.coin_name, constants.AGG_SIG_ME_ADDITIONAL_DATA ): pairs_pks.append(pk) pairs_msgs.append(m) # 22. Verify aggregated signature # TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster if not block.transactions_info.aggregated_signature: return Err.BAD_AGGREGATE_SIGNATURE, None # noinspection PyTypeChecker if not AugSchemeMPL.aggregate_verify(pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature): return Err.BAD_AGGREGATE_SIGNATURE, None return None, npc_result
async def test_tx_propagation(self, three_nodes_two_wallets): num_blocks = 5 full_nodes, wallets = three_nodes_two_wallets wallet_0, wallet_server_0 = wallets[0] wallet_1, wallet_server_1 = wallets[1] full_node_api_0 = full_nodes[0] server_0 = full_node_api_0.server full_node_api_1 = full_nodes[1] server_1 = full_node_api_1.server full_node_api_2 = full_nodes[2] server_2 = full_node_api_2.server ph = await wallet_0.wallet_state_manager.main_wallet.get_new_puzzlehash( ) ph1 = await wallet_1.wallet_state_manager.main_wallet.get_new_puzzlehash( ) # # wallet0 <-> sever0 <-> server1 <-> server2 <-> wallet1 # await wallet_server_0.start_client( PeerInfo(self_hostname, uint16(server_0._port)), None) await server_0.start_client( PeerInfo(self_hostname, uint16(server_1._port)), None) await server_1.start_client( PeerInfo(self_hostname, uint16(server_2._port)), None) await wallet_server_1.start_client( PeerInfo(self_hostname, uint16(server_2._port)), None) for i in range(num_blocks): await full_node_api_0.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert( 10, wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance, funds) async def peak_height(fna: FullNodeAPI): peak: Optional[BlockRecord] = fna.full_node.blockchain.get_peak() if peak is None: return -1 peak_height = peak.height return peak_height await time_out_assert(10, peak_height, num_blocks, full_node_api_1) await time_out_assert(10, peak_height, num_blocks, full_node_api_2) tx = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( 10, ph1, 0) await wallet_0.wallet_state_manager.main_wallet.push_transaction(tx) await time_out_assert( 10, full_node_api_0.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, ) await time_out_assert( 10, full_node_api_1.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, ) await time_out_assert( 10, full_node_api_2.full_node.mempool_manager.get_spendbundle, tx.spend_bundle, tx.name, ) # Farm another block for i in range(1, 8): await full_node_api_1.farm_new_transaction_block( FarmNewBlockProtocol(token_bytes())) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ]) print(f"Funds: {funds}") await time_out_assert( 10, wallet_0.wallet_state_manager.main_wallet.get_confirmed_balance, (funds - 10), ) await time_out_assert( 15, wallet_1.wallet_state_manager.main_wallet.get_confirmed_balance, 10)