async def test_validate_blockchain_spend_reorg_cb_coin(self, two_nodes): num_blocks = 10 wallet_a = WALLET_A coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0] receiver_1_puzzlehash = WALLET_A_PUZZLE_HASHES[1] blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Spends a coinbase created in reorg new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks[:6], 10, b"reorg cb coin", coinbase_puzzlehash) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1])) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers spent_block = new_blocks[-1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase()) spend_bundle_2 = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_fees_coin()) block_spendbundle = SpendBundle.aggregate( [spend_bundle, spend_bundle_2]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {7: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks, 10, b"reorg cb coin", coinbase_puzzlehash, dic_h, ) error = await full_node_1.blockchain._validate_transactions( new_blocks[-1], new_blocks[-1].get_fees_coin().amount) assert error is None [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1])) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coins_created = [] for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_1_puzzlehash: coins_created.append(coin) assert len(coins_created) == 2
async def get_max_send_amount(self, records=None): spendable: List[WalletCoinRecord] = list( await self.wallet_state_manager.get_spendable_coins_for_wallet(self.id(), records) ) if len(spendable) == 0: return 0 spendable.sort(reverse=True, key=lambda record: record.coin.amount) if self.cost_of_single_tx is None: coin = spendable[0].coin tx = await self.generate_signed_transaction( coin.amount, coin.puzzle_hash, coins={coin}, ignore_max_send_amount=True ) program = best_solution_program(tx.spend_bundle) # npc contains names of the coins removed, puzzle_hashes and their spend conditions cost_result: CostResult = calculate_cost_of_program( program, self.wallet_state_manager.constants.CLVM_COST_RATIO_CONSTANT, True ) self.cost_of_single_tx = cost_result.cost self.log.info(f"Cost of a single tx for standard wallet: {self.cost_of_single_tx}") max_cost = self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM / 2 # avoid full block TXs current_cost = 0 total_amount = 0 total_coin_count = 0 for record in spendable: current_cost += self.cost_of_single_tx total_amount += record.coin.amount total_coin_count += 1 if current_cost + self.cost_of_single_tx > max_cost: break return total_amount
async def farm_new_block(self, request: FarmNewBlockProtocol): self.log.info("Farming new block!") top_tip = self.get_tip() if top_tip is None or self.server is None: return current_block = await self.get_current_blocks(top_tip) bundle: Optional[ SpendBundle ] = await self.mempool_manager.create_bundle_for_tip(top_tip) dict_h = {} fees = 0 if bundle is not None: program = best_solution_program(bundle) dict_h[top_tip.height + 1] = (program, bundle.aggregated_signature) fees = bundle.fees() more_blocks = self.bt.get_consecutive_blocks( self.constants, 1, current_block, 10, reward_puzzlehash=request.puzzle_hash, transaction_data_at_height=dict_h, seed=token_bytes(), fees=uint64(fees), ) new_lca = more_blocks[-1] assert self.server is not None async for msg in self.respond_block(full_node_protocol.RespondBlock(new_lca)): self.server.push_message(msg)
async def test_basics(self): wallet_tool = bt.get_pool_wallet_tool() num_blocks = 2 blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, ) spend_bundle = wallet_tool.generate_signed_transaction( blocks[1].get_coinbase().amount, BURN_PUZZLE_HASH, blocks[1].get_coinbase(), ) assert spend_bundle is not None program = best_solution_program(spend_bundle) ratio = test_constants.CLVM_COST_RATIO_CONSTANT error, npc_list, clvm_cost = calculate_cost_of_program(program, ratio) error, npc_list, cost = get_name_puzzle_conditions(program) # Create condition + agg_sig_condition + length + cpu_cost assert (clvm_cost == 200 * ratio + 20 * ratio + len(bytes(program)) * ratio + cost)
def validate_transaction_multiprocess( constants_dict: Dict, spend_bundle_bytes: bytes, ) -> bytes: constants: ConsensusConstants = dataclass_from_dict(ConsensusConstants, constants_dict) # Calculate the cost and fees program = best_solution_program(SpendBundle.from_bytes(spend_bundle_bytes)) # npc contains names of the coins removed, puzzle_hashes and their spend conditions return bytes(calculate_cost_of_program(program, constants.CLVM_COST_RATIO_CONSTANT, True))
async def test_validate_blockchain_spend_reorg_since_genesis(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() receiver_1_puzzlehash = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1]) ) ] # Spends a coin in a genesis reorg, that was already spent dic_h = {5: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 12, [], 10, b"reorg since genesis", coinbase_puzzlehash, dic_h, ) for block in new_blocks: [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers
async def test_validate_blockchain_spend_reorg_cb_coin_freeze( self, two_nodes_standard_freeze ): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() receiver_1_puzzlehash = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes_standard_freeze for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Spends a coinbase created in reorg new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:6], 10, b"reorg cb coin", coinbase_puzzlehash ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1]) ) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers spent_block = new_blocks[-1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {7: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks, 10, b"reorg cb coin", coinbase_puzzlehash, dic_h, ) error = await full_node_1.blockchain._validate_transactions( new_blocks[-1], new_blocks[-1].get_coinbase().amount ) assert error is Err.COINBASE_NOT_YET_SPENDABLE
def make_block_generator(count: int) -> SerializedProgram: puzzle_hash_db: Dict = dict() coins = [make_fake_coin(_, puzzle_hash_db) for _ in range(count)] coin_solutions = [] for coin in coins: puzzle_reveal = puzzle_hash_db[coin.puzzle_hash] conditions = conditions_for_payment(coin) solution = solution_for_conditions(conditions) coin_solution = CoinSolution(coin, puzzle_reveal, solution) coin_solutions.append(coin_solution) spend_bundle = SpendBundle(coin_solutions, blspy.G2Element()) return best_solution_program(spend_bundle)
async def test_validate_blockchain_with_double_spend(self, two_nodes): num_blocks = 5 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase() ) spend_bundle_double = wallet_a.generate_signed_transaction( 1001, receiver_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle, spend_bundle_double]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {(num_blocks + 1): (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) next_block = new_blocks[num_blocks + 1] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.DOUBLE_SPEND
async def test_validate_blockchain_duplicate_output(self, two_nodes): num_blocks = 10 wallet_a = WALLET_A coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0] receiver_puzzlehash = BURN_PUZZLE_HASH blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase()) spend_bundle_double = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase()) block_spendbundle = SpendBundle.aggregate( [spend_bundle, spend_bundle_double]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {(num_blocks + 1): (program, aggsig)} new_blocks = bt.get_consecutive_blocks(test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h) next_block = new_blocks[(num_blocks + 1)] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount) assert error is Err.DUPLICATE_OUTPUT
async def test_basics(self): wallet_tool = bt.get_pool_wallet_tool() ph = wallet_tool.get_new_puzzlehash() num_blocks = 3 blocks = bt.get_consecutive_blocks(num_blocks, [], guarantee_transaction_block=True, pool_reward_puzzle_hash=ph, farmer_reward_puzzle_hash=ph) coinbase = None for coin in blocks[2].get_included_reward_coins(): if coin.puzzle_hash == ph: coinbase = coin break assert coinbase is not None spend_bundle = wallet_tool.generate_signed_transaction( coinbase.amount, BURN_PUZZLE_HASH, coinbase, ) assert spend_bundle is not None program = best_solution_program(spend_bundle) ratio = test_constants.CLVM_COST_RATIO_CONSTANT result: CostResult = calculate_cost_of_program(program, ratio) clvm_cost = result.cost error, npc_list, cost = get_name_puzzle_conditions(program, False) assert error is None coin_name = npc_list[0].coin_name error, puzzle, solution = get_puzzle_and_solution_for_coin( program, coin_name) assert error is None # Create condition + agg_sig_condition + length + cpu_cost assert clvm_cost == 200 * ratio + 92 * ratio + len( bytes(program)) * ratio + cost
async def test_assert_my_coin_id(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Coinbase that gets spent spent_block = blocks[1] bad_block = blocks[2] valid_cvp = ConditionVarPair( ConditionOpcode.ASSERT_MY_COIN_ID, spent_block.get_coinbase().name(), None, ) valid_dic = {valid_cvp.opcode: [valid_cvp]} bad_cvp = ConditionVarPair( ConditionOpcode.ASSERT_MY_COIN_ID, bad_block.get_coinbase().name(), None, ) bad_dic = {bad_cvp.opcode: [bad_cvp]} bad_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase(), bad_dic ) valid_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase(), valid_dic ) # Invalid block bundle assert bad_spend_bundle is not None invalid_program = best_solution_program(bad_spend_bundle) aggsig = bad_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (invalid_program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) # Try to validate that block next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.ASSERT_MY_COIN_ID_FAILED # Valid block bundle assert valid_spend_bundle is not None valid_program = best_solution_program(valid_spend_bundle) aggsig = valid_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (valid_program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:11], 10, b"1", coinbase_puzzlehash, dic_h ) next_block = new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is None
def create_foliage( constants: ConsensusConstants, reward_block_unfinished: RewardChainBlockUnfinished, spend_bundle: Optional[SpendBundle], additions: List[Coin], removals: List[Coin], prev_block: Optional[BlockRecord], blocks: BlockchainInterface, total_iters_sp: uint128, timestamp: uint64, farmer_reward_puzzlehash: bytes32, pool_target: PoolTarget, get_plot_signature: Callable[[bytes32, G1Element], G2Element], get_pool_signature: Callable[[PoolTarget, Optional[G1Element]], Optional[G2Element]], seed: bytes32 = b"", ) -> Tuple[Foliage, Optional[FoliageTransactionBlock], Optional[TransactionsInfo], Optional[SerializedProgram]]: """ Creates a foliage for a given reward chain block. This may or may not be a tx block. In the case of a tx block, the return values are not None. This is called at the signage point, so some of this information may be tweaked at the infusion point. Args: constants: consensus constants being used for this chain reward_block_unfinished: the reward block to look at, potentially at the signage point spend_bundle: the spend bundle including all transactions prev_block: the previous block at the signage point blocks: dict from header hash to blocks, of all ancestor blocks total_iters_sp: total iters at the signage point timestamp: timestamp to put into the foliage block farmer_reward_puzzlehash: where to pay out farming reward pool_target: where to pay out pool reward get_plot_signature: retrieve the signature corresponding to the plot public key get_pool_signature: retrieve the signature corresponding to the pool public key seed: seed to randomize block """ if prev_block is not None: res = get_prev_transaction_block(prev_block, blocks, total_iters_sp) is_transaction_block: bool = res[0] prev_transaction_block: Optional[BlockRecord] = res[1] else: # Genesis is a transaction block prev_transaction_block = None is_transaction_block = True random.seed(seed) # Use the extension data to create different blocks based on header hash extension_data: bytes32 = random.randint(0, 100000000).to_bytes(32, "big") if prev_block is None: height: uint32 = uint32(0) else: height = uint32(prev_block.height + 1) # Create filter byte_array_tx: List[bytes32] = [] tx_additions: List[Coin] = [] tx_removals: List[bytes32] = [] pool_target_signature: Optional[G2Element] = get_pool_signature( pool_target, reward_block_unfinished.proof_of_space.pool_public_key) foliage_data = FoliageBlockData( reward_block_unfinished.get_hash(), pool_target, pool_target_signature, farmer_reward_puzzlehash, extension_data, ) foliage_block_data_signature: G2Element = get_plot_signature( foliage_data.get_hash(), reward_block_unfinished.proof_of_space.plot_public_key, ) prev_block_hash: bytes32 = constants.GENESIS_CHALLENGE if height != 0: assert prev_block is not None prev_block_hash = prev_block.header_hash solution_program: Optional[SerializedProgram] = None if is_transaction_block: spend_bundle_fees: int = 0 aggregate_sig: G2Element = G2Element.infinity() cost = uint64(0) if spend_bundle is not None: solution_program = best_solution_program(spend_bundle) aggregate_sig = spend_bundle.aggregated_signature # Calculate the cost of transactions if solution_program is not None: result: CostResult = calculate_cost_of_program( solution_program, constants.CLVM_COST_RATIO_CONSTANT) cost = result.cost removal_amount = 0 addition_amount = 0 for coin in removals: removal_amount += coin.amount for coin in additions: addition_amount += coin.amount spend_bundle_fees = removal_amount - addition_amount else: spend_bundle_fees = 0 # TODO: prev generators root reward_claims_incorporated = [] if height > 0: assert prev_transaction_block is not None assert prev_block is not None curr: BlockRecord = prev_block while not curr.is_transaction_block: curr = blocks.block_record(curr.prev_hash) assert curr.fees is not None pool_coin = create_pool_coin( curr.height, curr.pool_puzzle_hash, calculate_pool_reward(curr.height), ) farmer_coin = create_farmer_coin( curr.height, curr.farmer_puzzle_hash, uint64(calculate_base_farmer_reward(curr.height) + curr.fees), ) assert curr.header_hash == prev_transaction_block.header_hash reward_claims_incorporated += [pool_coin, farmer_coin] if curr.height > 0: curr = blocks.block_record(curr.prev_hash) # Prev block is not genesis while not curr.is_transaction_block: pool_coin = create_pool_coin( curr.height, curr.pool_puzzle_hash, calculate_pool_reward(curr.height), ) farmer_coin = create_farmer_coin( curr.height, curr.farmer_puzzle_hash, calculate_base_farmer_reward(curr.height), ) reward_claims_incorporated += [pool_coin, farmer_coin] curr = blocks.block_record(curr.prev_hash) additions.extend(reward_claims_incorporated.copy()) for coin in additions: tx_additions.append(coin) byte_array_tx.append(bytearray(coin.puzzle_hash)) for coin in removals: tx_removals.append(coin.name()) byte_array_tx.append(bytearray(coin.name())) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded = bytes(bip158.GetEncoded()) removal_merkle_set = MerkleSet() addition_merkle_set = MerkleSet() # Create removal Merkle set for coin_name in tx_removals: removal_merkle_set.add_already_hashed(coin_name) # Create addition Merkle set puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {} for coin in tx_additions: if coin.puzzle_hash in puzzlehash_coin_map: puzzlehash_coin_map[coin.puzzle_hash].append(coin) else: puzzlehash_coin_map[coin.puzzle_hash] = [coin] # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash for puzzle, coins in puzzlehash_coin_map.items(): addition_merkle_set.add_already_hashed(puzzle) addition_merkle_set.add_already_hashed(hash_coin_list(coins)) additions_root = addition_merkle_set.get_root() removals_root = removal_merkle_set.get_root() generator_hash = solution_program.get_tree_hash( ) if solution_program is not None else bytes32([0] * 32) filter_hash: bytes32 = std_hash(encoded) transactions_info: Optional[TransactionsInfo] = TransactionsInfo( bytes([0] * 32), generator_hash, aggregate_sig, uint64(spend_bundle_fees), cost, reward_claims_incorporated, ) if prev_transaction_block is None: prev_transaction_block_hash: bytes32 = constants.GENESIS_CHALLENGE else: prev_transaction_block_hash = prev_transaction_block.header_hash assert transactions_info is not None foliage_transaction_block: Optional[ FoliageTransactionBlock] = FoliageTransactionBlock( prev_transaction_block_hash, timestamp, filter_hash, additions_root, removals_root, transactions_info.get_hash(), ) assert foliage_transaction_block is not None foliage_transaction_block_hash: Optional[ bytes32] = foliage_transaction_block.get_hash() foliage_transaction_block_signature: Optional[ G2Element] = get_plot_signature( foliage_transaction_block_hash, reward_block_unfinished.proof_of_space.plot_public_key) assert foliage_transaction_block_signature is not None else: foliage_transaction_block_hash = None foliage_transaction_block_signature = None foliage_transaction_block = None transactions_info = None assert (foliage_transaction_block_hash is None) == (foliage_transaction_block_signature is None) foliage = Foliage( prev_block_hash, reward_block_unfinished.get_hash(), foliage_data, foliage_block_data_signature, foliage_transaction_block_hash, foliage_transaction_block_signature, ) return foliage, foliage_transaction_block, transactions_info, solution_program
async def add_spendbundle( self, new_spend: SpendBundle, cached_result: Optional[CostResult] = None ) -> Tuple[Optional[uint64], MempoolInclusionStatus, Optional[Err]]: """ Tries to add spendbundle to either self.mempools or to_pool if it's specified. Returns true if it's added in any of pools, Returns error if it fails. """ if self.peak is None: return None, MempoolInclusionStatus.FAILED, Err.MEMPOOL_NOT_INITIALIZED self.seen_bundle_hashes[new_spend.name()] = new_spend.name() self.maybe_pop_seen() if cached_result is None: # Calculate the cost and fees program = best_solution_program(new_spend) # npc contains names of the coins removed, puzzle_hashes and their spend conditions cached_result = calculate_cost_of_program( program, self.constants.CLVM_COST_RATIO_CONSTANT, True) npc_list = cached_result.npc_list cost = cached_result.cost if cached_result.error is not None: return None, MempoolInclusionStatus.FAILED, Err( cached_result.error) # build removal list removal_names: List[bytes32] = new_spend.removal_names() additions = new_spend.additions() additions_dict: Dict[bytes32, Coin] = {} for add in additions: additions_dict[add.name()] = add addition_amount = uint64(0) # Check additions for max coin amount for coin in additions: if coin.amount >= self.constants.MAX_COIN_AMOUNT: return ( None, MempoolInclusionStatus.FAILED, Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, ) addition_amount = uint64(addition_amount + coin.amount) # Check for duplicate outputs addition_counter = collections.Counter(_.name() for _ in additions) for k, v in addition_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DUPLICATE_OUTPUT # Check for duplicate inputs removal_counter = collections.Counter(name for name in removal_names) for k, v in removal_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DOUBLE_SPEND # Skip if already added if new_spend.name() in self.mempool.spends: return uint64(cost), MempoolInclusionStatus.SUCCESS, None removal_record_dict: Dict[bytes32, CoinRecord] = {} removal_coin_dict: Dict[bytes32, Coin] = {} unknown_unspent_error: bool = False removal_amount = uint64(0) for name in removal_names: removal_record = await self.coin_store.get_coin_record(name) if removal_record is None and name not in additions_dict: unknown_unspent_error = True break elif name in additions_dict: removal_coin = additions_dict[name] # TODO(straya): what timestamp to use here? removal_record = CoinRecord( removal_coin, uint32(self.peak.height + 1), uint32(0), False, False, uint64(int(time.time())), ) assert removal_record is not None removal_amount = uint64(removal_amount + removal_record.coin.amount) removal_record_dict[name] = removal_record removal_coin_dict[name] = removal_record.coin if unknown_unspent_error: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN_UNSPENT if addition_amount > removal_amount: print(addition_amount, removal_amount) return None, MempoolInclusionStatus.FAILED, Err.MINTING_COIN fees = removal_amount - addition_amount assert_fee_sum: uint64 = uint64(0) for npc in npc_list: if ConditionOpcode.ASSERT_FEE in npc.condition_dict: fee_list: List[ConditionVarPair] = npc.condition_dict[ ConditionOpcode.ASSERT_FEE] for cvp in fee_list: fee = int_from_bytes(cvp.vars[0]) assert_fee_sum = assert_fee_sum + fee if fees < assert_fee_sum: return ( None, MempoolInclusionStatus.FAILED, Err.ASSERT_FEE_CONDITION_FAILED, ) if cost == 0: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN fees_per_cost: float = fees / cost # If pool is at capacity check the fee, if not then accept even without the fee if self.mempool.at_full_capacity(): if fees == 0: return None, MempoolInclusionStatus.FAILED, Err.INVALID_FEE_LOW_FEE if fees_per_cost < self.mempool.get_min_fee_rate(): return None, MempoolInclusionStatus.FAILED, Err.INVALID_FEE_LOW_FEE # Check removals against UnspentDB + DiffStore + Mempool + SpendBundle # Use this information later when constructing a block fail_reason, conflicts = await self.check_removals(removal_record_dict) # If there is a mempool conflict check if this spendbundle has a higher fee per cost than all others tmp_error: Optional[Err] = None conflicting_pool_items: Dict[bytes32, MempoolItem] = {} if fail_reason is Err.MEMPOOL_CONFLICT: for conflicting in conflicts: sb: MempoolItem = self.mempool.removals[conflicting.name()] conflicting_pool_items[sb.name] = sb for item in conflicting_pool_items.values(): if item.fee_per_cost >= fees_per_cost: self.add_to_potential_tx_set(new_spend, cached_result) return ( uint64(cost), MempoolInclusionStatus.PENDING, Err.MEMPOOL_CONFLICT, ) elif fail_reason: return None, MempoolInclusionStatus.FAILED, fail_reason if tmp_error: return None, MempoolInclusionStatus.FAILED, tmp_error # Verify conditions, create hash_key list for aggsig check pks: List[G1Element] = [] msgs: List[bytes32] = [] error: Optional[Err] = None for npc in npc_list: coin_record: CoinRecord = removal_record_dict[npc.coin_name] # Check that the revealed removal puzzles actually match the puzzle hash if npc.puzzle_hash != coin_record.coin.puzzle_hash: log.warning( "Mempool rejecting transaction because of wrong puzzle_hash" ) log.warning( f"{npc.puzzle_hash} != {coin_record.coin.puzzle_hash}") return None, MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH error = mempool_check_conditions_dict(coin_record, new_spend, npc.condition_dict, uint32(self.peak.height + 1)) if error: if error is Err.ASSERT_BLOCK_INDEX_EXCEEDS_FAILED or error is Err.ASSERT_BLOCK_AGE_EXCEEDS_FAILED: self.add_to_potential_tx_set(new_spend, cached_result) return uint64(cost), MempoolInclusionStatus.PENDING, error break for pk, message in pkm_pairs_for_conditions_dict( npc.condition_dict, npc.coin_name): pks.append(pk) msgs.append(message) if error: return None, MempoolInclusionStatus.FAILED, error # Verify aggregated signature if len(pks) == 0 and len(msgs) == 0: validates = new_spend.aggregated_signature == G2Element.infinity() else: validates = AugSchemeMPL.aggregate_verify( pks, msgs, new_spend.aggregated_signature) if not validates: log.warning(f"Aggsig validation error {pks} {msgs} {new_spend}") return None, MempoolInclusionStatus.FAILED, Err.BAD_AGGREGATE_SIGNATURE # Remove all conflicting Coins and SpendBundles if fail_reason: mempool_item: MempoolItem for mempool_item in conflicting_pool_items.values(): self.mempool.remove_spend(mempool_item) new_item = MempoolItem(new_spend, fees_per_cost, uint64(fees), cached_result) self.mempool.add_to_pool(new_item, additions, removal_coin_dict) return uint64(cost), MempoolInclusionStatus.SUCCESS, None
async def test_invalid_filter(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase() ) assert spend_bundle is not None tx: full_node_protocol.RespondTransaction = ( full_node_protocol.RespondTransaction(spend_bundle) ) async for _ in full_node_1.respond_transaction(tx): outbound: OutboundMessage = _ # Maybe transaction means that it's accepted in mempool assert outbound.message.function == "new_transaction" sb = full_node_1.mempool_manager.get_spendbundle(spend_bundle.name()) assert sb is spend_bundle last_block = blocks[10] next_spendbundle = await full_node_1.mempool_manager.create_bundle_for_tip( last_block.header ) assert next_spendbundle is not None program = best_solution_program(next_spendbundle) aggsig = next_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) next_block = new_blocks[11] bad_header = HeaderData( next_block.header.data.height, next_block.header.data.prev_header_hash, next_block.header.data.timestamp, bytes32(bytes([3] * 32)), next_block.header.data.proof_of_space_hash, next_block.header.data.weight, next_block.header.data.total_iters, next_block.header.data.additions_root, next_block.header.data.removals_root, next_block.header.data.farmer_rewards_puzzle_hash, next_block.header.data.total_transaction_fees, next_block.header.data.pool_target, next_block.header.data.aggregated_signature, next_block.header.data.cost, next_block.header.data.extension_data, next_block.header.data.generator_hash, ) bad_block = FullBlock( next_block.proof_of_space, next_block.proof_of_time, Header( bad_header, bt.get_plot_signature( bad_header, next_block.proof_of_space.plot_public_key ), ), next_block.transactions_generator, next_block.transactions_filter, ) result, removed, error_code = await full_node_1.blockchain.receive_block( bad_block ) assert result == ReceiveBlockResult.INVALID_BLOCK assert error_code == Err.INVALID_TRANSACTIONS_FILTER_HASH result, removed, error_code = await full_node_1.blockchain.receive_block( next_block ) assert result == ReceiveBlockResult.ADDED_TO_HEAD
async def test_assert_fee_condition(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Coinbase that gets spent block1 = blocks[1] # This condition requires fee to be 10 mojo cvp_fee = ConditionVarPair(ConditionOpcode.ASSERT_FEE, int_to_bytes(10), None) block1_dic = {cvp_fee.opcode: [cvp_fee]} # This spendbundle has 9 mojo as fee invalid_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic, 9 ) assert invalid_spend_bundle is not None program = best_solution_program(invalid_spend_bundle) aggsig = invalid_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h, fees=uint64(9), ) # Try to validate that block at index 11 next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.ASSERT_FEE_CONDITION_FAILED # This condition requires fee to be 10 mojo cvp_fee = ConditionVarPair(ConditionOpcode.ASSERT_FEE, int_to_bytes(10), None) condition_dict = {cvp_fee.opcode: [cvp_fee]} valid_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), condition_dict, 10 ) assert valid_spend_bundle is not None valid_program = best_solution_program(valid_spend_bundle) aggsig = valid_spend_bundle.aggregated_signature dic_h = {11: (valid_program, aggsig)} valid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:11], 10, b"", coinbase_puzzlehash, dic_h, fees=uint64(10), ) next_block = valid_new_blocks[11] fee_base = calculate_base_fee(next_block.height) error = await full_node_1.blockchain._validate_transactions( next_block, fee_base ) assert error is None for block in valid_new_blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass
async def test_short_sync_with_transactions_wallet(self, wallet_node): full_node_1, wallet_node, server_1, server_2 = wallet_node wallet_a = wallet_node.wallet_state_manager.main_wallet wallet_a_dummy = WalletTool() wallet_b = WalletTool() coinbase_puzzlehash = await wallet_a.get_new_puzzlehash() coinbase_puzzlehash_rest = wallet_b.get_new_puzzlehash() puzzle_hashes = [ await wallet_a.get_new_puzzlehash() for _ in range(10) ] puzzle_hashes.append(wallet_b.get_new_puzzlehash()) blocks = bt.get_consecutive_blocks(test_constants, 3, [], 10, b"", coinbase_puzzlehash) for block in blocks: [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)) ] await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) await time_out_assert(60, wallet_height_at_least, True, wallet_node, 1) server_2.global_connections.close_all_connections() dic_h = {} prev_coin = blocks[1].get_coinbase() for i in range(11): pk, sk = await wallet_a.wallet_state_manager.get_keys( prev_coin.puzzle_hash) transaction_unsigned = wallet_a_dummy.generate_unsigned_transaction( 1000, puzzle_hashes[i], prev_coin, {}, 0, secretkey=sk) spend_bundle = await wallet_a.sign_transaction(transaction_unsigned ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature prev_coin = Coin(prev_coin.name(), puzzle_hashes[i], uint64(1000)) dic_h[i + 4] = (program, aggsig) blocks = bt.get_consecutive_blocks(test_constants, 13, blocks, 10, b"", coinbase_puzzlehash_rest, dic_h) # Move chain to height 16, with consecutive transactions in blocks 4 to 14 for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Do a short sync from 0 to 14 await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) await time_out_assert(60, wallet_height_at_least, True, wallet_node, 14) server_2.global_connections.close_all_connections() # 2 block rewards and 3 fees assert await wallet_a.get_confirmed_balance() == ( blocks[1].get_coinbase().amount * 2) + (blocks[1].get_fees_coin().amount * 3) # All of our coins are spent and puzzle hashes present for puzzle_hash in puzzle_hashes[:-1]: records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( puzzle_hash) assert len(records) == 1 assert records[0].spent and not records[0].coinbase # Then do the same but in a reorg chain dic_h = {} prev_coin = blocks[1].get_coinbase() for i in range(11): pk, sk = await wallet_a.wallet_state_manager.get_keys( prev_coin.puzzle_hash) transaction_unsigned = wallet_a_dummy.generate_unsigned_transaction( 1000, puzzle_hashes[i], prev_coin, {}, 0, secretkey=sk) spend_bundle = await wallet_a.sign_transaction(transaction_unsigned ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature prev_coin = Coin(prev_coin.name(), puzzle_hashes[i], uint64(1000)) dic_h[i + 4] = (program, aggsig) blocks = bt.get_consecutive_blocks( test_constants, 31, blocks[:4], 10, b"this is a reorg", coinbase_puzzlehash_rest, dic_h, ) # Move chain to height 34, with consecutive transactions in blocks 4 to 14 for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass # Do a sync from 0 to 22 await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) await time_out_assert(60, wallet_height_at_least, True, wallet_node, 28) server_2.global_connections.close_all_connections() # 2 block rewards and 3 fees assert await wallet_a.get_confirmed_balance() == ( blocks[1].get_coinbase().amount * 2) + (blocks[1].get_fees_coin().amount * 3) # All of our coins are spent and puzzle hashes present for puzzle_hash in puzzle_hashes[:-1]: records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( puzzle_hash) assert len(records) == 1 assert records[0].spent and not records[0].coinbase # Test spending the rewards earned in reorg new_coinbase_puzzlehash = await wallet_a.get_new_puzzlehash() another_puzzlehash = await wallet_a.get_new_puzzlehash() dic_h = {} pk, sk = await wallet_a.wallet_state_manager.get_keys( new_coinbase_puzzlehash) coinbase_coin = create_coinbase_coin(uint32(25), new_coinbase_puzzlehash, uint64(14000000000000)) transaction_unsigned = wallet_a_dummy.generate_unsigned_transaction( 7000000000000, another_puzzlehash, coinbase_coin, {}, 0, secretkey=sk) spend_bundle = await wallet_a.sign_transaction(transaction_unsigned) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h[26] = (program, aggsig) # Farm a block (25) to ourselves blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:25], 10, b"this is yet another reorg", new_coinbase_puzzlehash, ) # Brings height up to 40, with block 31 having half our reward spent to us blocks = bt.get_consecutive_blocks( test_constants, 15, blocks, 10, b"this is yet another reorg more blocks", coinbase_puzzlehash_rest, dic_h, ) for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block)): pass await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) await time_out_assert(60, wallet_height_at_least, True, wallet_node, 38) # 2 block rewards and 4 fees, plus 7000000000000 coins assert (await wallet_a.get_confirmed_balance() == (blocks[1].get_coinbase().amount * 2) + (blocks[1].get_fees_coin().amount * 4) + 7000000000000) records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( new_coinbase_puzzlehash) # Fee and coinbase assert len(records) == 2 print(records) assert records[0].spent != records[1].spent assert records[0].coinbase == records[1].coinbase records = await wallet_node.wallet_state_manager.wallet_store.get_coin_records_by_puzzle_hash( another_puzzlehash) assert len(records) == 1 assert not records[0].spent assert not records[0].coinbase
async def test_validate_blockchain_with_reorg_double_spend(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} blocks = bt.get_consecutive_blocks( test_constants, 10, blocks, 10, b"", coinbase_puzzlehash, dic_h ) # Move chain to height 20, with a spend at height 11 for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Reorg at block 5, same spend at block 13 and 14 that was previously at block 11 dic_h = {13: (program, aggsig), 14: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 9, blocks[:6], 10, b"another seed", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:13]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass next_block = new_blocks[13] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is None [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[13]) ) ] next_block = new_blocks[14] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.DOUBLE_SPEND # Now test Reorg at block 5, same spend at block 9 that was previously at block 11 dic_h = {9: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:6], 10, b"another seed 2", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:9]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass next_block = new_blocks[9] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is None # Now test Reorg at block 10, same spend at block 11 that was previously at block 11 dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:11], 10, b"another seed 3", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:11]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass next_block = new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is None # Now test Reorg at block 11, same spend at block 12 that was previously at block 11 dic_h = {12: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:12], 10, b"another seed 4", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:12]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass next_block = new_blocks[12] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.DOUBLE_SPEND # Now test Reorg at block 11, same spend at block 15 that was previously at block 11 dic_h = {15: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 4, blocks[:12], 10, b"another seed 5", coinbase_puzzlehash, dic_h, ) for block in new_blocks[:15]: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass next_block = new_blocks[15] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.DOUBLE_SPEND
async def test_assert_time_exceeds(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Coinbase that gets spent block1 = blocks[1] # This condition requires block1 coinbase to be spent after 3 seconds from now current_time_plus3 = uint64(int(time.time() * 1000) + 3000) block1_cvp = ConditionVarPair( ConditionOpcode.ASSERT_TIME_EXCEEDS, int_to_bytes(current_time_plus3), None ) block1_dic = {block1_cvp.opcode: [block1_cvp]} block1_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic ) # program that will be sent to early assert block1_spend_bundle is not None program = best_solution_program(block1_spend_bundle) aggsig = block1_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) # Try to validate that block before 3 sec next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.ASSERT_TIME_EXCEEDS_FAILED # wait 3 sec to pass await asyncio.sleep(3.1) dic_h = {12: (program, aggsig)} valid_new_blocks = bt.get_consecutive_blocks( test_constants, 2, blocks[:11], 10, b"", coinbase_puzzlehash, dic_h ) for block in valid_new_blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Try to validate that block after 3 sec have passed next_block = valid_new_blocks[12] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is None
async def test_assert_block_age_exceeds(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Coinbase that gets spent block1 = blocks[1] # This condition requires block1 coinbase to be spent more than 10 block after it was farmed # block index has to be greater than (1 + 10 = 11) block1_cvp = ConditionVarPair( ConditionOpcode.ASSERT_BLOCK_AGE_EXCEEDS, int_to_bytes(10), None ) block1_dic = {block1_cvp.opcode: [block1_cvp]} block1_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic ) # program that will be sent to early assert block1_spend_bundle is not None program = best_solution_program(block1_spend_bundle) aggsig = block1_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) # Try to validate that block at index 11 next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.ASSERT_BLOCK_AGE_EXCEEDS_FAILED dic_h = {12: (program, aggsig)} valid_new_blocks = bt.get_consecutive_blocks( test_constants, 2, blocks[:11], 10, b"", coinbase_puzzlehash, dic_h ) for block in valid_new_blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Try to validate that block at index 12 next_block = valid_new_blocks[12] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is None
async def test_assert_coin_consumed(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() # Farm blocks blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass # Coinbase that gets spent block1 = blocks[1] block2 = blocks[2] # This condition requires block2 coinbase to be spent block1_cvp = ConditionVarPair( ConditionOpcode.ASSERT_COIN_CONSUMED, block2.get_coinbase().name(), None, ) block1_dic = {block1_cvp.opcode: [block1_cvp]} block1_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block1.get_coinbase(), block1_dic ) # This condition requires block1 coinbase to be spent block2_cvp = ConditionVarPair( ConditionOpcode.ASSERT_COIN_CONSUMED, block1.get_coinbase().name(), None, ) block2_dic = {block2_cvp.opcode: [block2_cvp]} block2_spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block2.get_coinbase(), block2_dic ) # Invalid block bundle assert block1_spend_bundle is not None solo_program = best_solution_program(block1_spend_bundle) aggsig = block1_spend_bundle.aggregated_signature # Create another block that includes our transaction dic_h = {11: (solo_program, aggsig)} invalid_new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) # Try to validate that block next_block = invalid_new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is Err.ASSERT_COIN_CONSUMED_FAILED # bundle_together contains both transactions bundle_together = SpendBundle.aggregate( [block1_spend_bundle, block2_spend_bundle] ) valid_program = best_solution_program(bundle_together) aggsig = bundle_together.aggregated_signature # Create another block that includes our transaction dic_h = {11: (valid_program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:11], 10, b"1", coinbase_puzzlehash, dic_h ) # Try to validate newly created block next_block = new_blocks[11] error = await full_node_1.blockchain._validate_transactions( next_block, next_block.get_fees_coin().amount ) assert error is None
async def test_validate_blockchain_spend_reorg_coin(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() receiver_1_puzzlehash = wallet_a.get_new_puzzlehash() receiver_2_puzzlehash = wallet_a.get_new_puzzlehash() receiver_3_puzzlehash = wallet_a.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_1_puzzlehash, spent_block.get_coinbase() ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {5: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks[:5], 10, b"spend_reorg_coin", coinbase_puzzlehash, dic_h, ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1]) ) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coin_2 = None for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_1_puzzlehash: coin_2 = coin break assert coin_2 is not None spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_2_puzzlehash, coin_2 ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {6: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks[:6], 10, b"spend_reorg_coin", coinbase_puzzlehash, dic_h, ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1]) ) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coin_3 = None for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_2_puzzlehash: coin_3 = coin break assert coin_3 is not None spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_3_puzzlehash, coin_3 ) block_spendbundle = SpendBundle.aggregate([spend_bundle]) program = best_solution_program(block_spendbundle) aggsig = block_spendbundle.aggregated_signature dic_h = {7: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, new_blocks[:7], 10, b"spend_reorg_coin", coinbase_puzzlehash, dic_h, ) [ _ async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(new_blocks[-1]) ) ] assert new_blocks[-1].header_hash in full_node_1.blockchain.headers coin_4 = None for coin in new_blocks[-1].additions(): if coin.puzzle_hash == receiver_3_puzzlehash: coin_4 = coin break assert coin_4 is not None
async def test_basic_blockchain_tx(self, two_nodes): num_blocks = 10 wallet_a = WalletTool() coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() blocks = bt.get_consecutive_blocks( test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash ) full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spent_block = blocks[1] spend_bundle = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, spent_block.get_coinbase() ) assert spend_bundle is not None tx: full_node_protocol.RespondTransaction = ( full_node_protocol.RespondTransaction(spend_bundle) ) async for _ in full_node_1.respond_transaction(tx): outbound: OutboundMessage = _ # Maybe transaction means that it's accepted in mempool assert outbound.message.function == "new_transaction" sb = full_node_1.mempool_manager.get_spendbundle(spend_bundle.name()) assert sb is spend_bundle last_block = blocks[10] next_spendbundle = await full_node_1.mempool_manager.create_bundle_for_tip( last_block.header ) assert next_spendbundle is not None program = best_solution_program(next_spendbundle) aggsig = next_spendbundle.aggregated_signature dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h ) next_block = new_blocks[11] async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(next_block) ): pass tips = full_node_1.blockchain.get_current_tips() assert next_block.header in tips added_coins = next_spendbundle.additions() # Two coins are added, main spend and change assert len(added_coins) == 2 for coin in added_coins: unspent = await full_node_1.coin_store.get_coin_record( coin.name(), next_block.header ) assert unspent is not None full_tips = await full_node_1.blockchain.get_full_tips() in_full_tips = False farmed_block: Optional[FullBlock] = None for tip in full_tips: if tip.header == next_block.header: in_full_tips = True farmed_block = tip assert in_full_tips assert farmed_block is not None assert farmed_block.transactions_generator == program