async def test_double_spend_same_bundle(self, two_nodes): reward_ph = WALLET_A.get_new_puzzlehash() full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() start_height = blocks[-1].height blocks = bt.get_consecutive_blocks( 3, block_list_input=blocks, guarantee_transaction_block=True, farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, ) peer = await connect_and_get_peer(server_1, server_2) for block in blocks: await full_node_1.full_node.respond_block( full_node_protocol.RespondBlock(block)) await time_out_assert(60, node_height_at_least, True, full_node_1, start_height + 3) coin = list(blocks[-1].get_included_reward_coins())[0] spend_bundle1 = generate_test_spend_bundle(coin) assert spend_bundle1 is not None spend_bundle2 = generate_test_spend_bundle( coin, new_puzzle_hash=BURN_PUZZLE_HASH_2, ) assert spend_bundle2 is not None spend_bundle_combined = SpendBundle.aggregate( [spend_bundle1, spend_bundle2]) tx: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction( spend_bundle_combined) await full_node_1.respond_transaction(tx, peer) sb = full_node_1.full_node.mempool_manager.get_spendbundle( spend_bundle_combined.name()) assert sb is None
async def test_correct_announcement_consumed(self, two_nodes): reward_ph = WALLET_A.get_new_puzzlehash() full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() start_height = blocks[-1].height if len(blocks) > 0 else -1 blocks = bt.get_consecutive_blocks( 3, block_list_input=blocks, guarantee_transaction_block=True, farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, ) peer = await connect_and_get_peer(server_1, server_2) for block in blocks: await full_node_1.full_node.respond_block(full_node_protocol.RespondBlock(block)) await time_out_assert(60, node_height_at_least, True, full_node_1, start_height + 3) coin_1 = list(blocks[-2].get_included_reward_coins())[0] coin_2 = list(blocks[-1].get_included_reward_coins())[0] announce = Announcement(coin_2.name(), bytes("test", "utf-8")) cvp = ConditionVarPair(ConditionOpcode.ASSERT_ANNOUNCEMENT, [announce.name()]) dic = {cvp.opcode: [cvp]} cvp2 = ConditionVarPair(ConditionOpcode.CREATE_ANNOUNCEMENT, [bytes("test", "utf-8")]) dic2 = {cvp.opcode: [cvp2]} spend_bundle1 = generate_test_spend_bundle(coin_1, dic) spend_bundle2 = generate_test_spend_bundle(coin_2, dic2) bundle = SpendBundle.aggregate([spend_bundle1, spend_bundle2]) tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(bundle) await full_node_1.respond_transaction(tx1, peer) mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name()) assert mempool_bundle is bundle
async def create_spend_bundle_relative_chia( self, chia_amount: int, exclude: List[Coin] ): list_of_solutions = [] utxos = None # If we're losing value then get coins with at least that much value # If we're gaining value then our amount doesn't matter if chia_amount < 0: utxos = await self.select_coins(abs(chia_amount), exclude) else: utxos = await self.select_coins(0, exclude) if utxos is None: return None # Calculate output amount given sum of utxos spend_value = sum([coin.amount for coin in utxos]) chia_amount = spend_value + chia_amount # Create coin solutions for each utxo output_created = None sigs: List[G2Element] = [] for coin in utxos: pubkey, secretkey = await self.wallet_state_manager.get_keys( coin.puzzle_hash ) puzzle = self.puzzle_for_pk(bytes(pubkey)) if output_created is None: newpuzhash = await self.get_new_puzzlehash() primaries = [{"puzzlehash": newpuzhash, "amount": chia_amount}] solution = self.make_solution(primaries=primaries) output_created = coin else: solution = self.make_solution(consumed=[output_created.name()]) list_of_solutions.append(CoinSolution(coin, Program.to([puzzle, solution]))) new_sigs = await self.get_sigs_for_innerpuz_with_innersol(puzzle, solution) sigs = sigs + new_sigs aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) return spend_bundle
async def test_double_spend_same_bundle(self, two_nodes): num_blocks = 2 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 block = blocks[1] async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(block) ): pass spend_bundle1 = wallet_a.generate_signed_transaction( 1000, receiver_puzzlehash, block.header.data.coinbase ) assert spend_bundle1 is not None other_receiver = WalletTool() spend_bundle2 = wallet_a.generate_signed_transaction( 1000, other_receiver.get_new_puzzlehash(), block.header.data.coinbase ) assert spend_bundle2 is not None spend_bundle_combined = SpendBundle.aggregate([spend_bundle1, spend_bundle2]) tx: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction( spend_bundle_combined ) messages = [] async for outbound in full_node_1.respond_transaction(tx): messages.append(outbound) sb = full_node_1.mempool_manager.get_spendbundle(spend_bundle_combined.name()) assert sb is None
async def test_validate_blockchain_duplicate_output(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() ) 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 rl_sign_transaction(self, spends: List[Tuple[Program, CoinSolution]]): sigs = [] for puzzle, solution in spends: pubkey, secretkey = await self.get_keys(solution.coin.puzzle_hash) signature = AugSchemeMPL.sign( secretkey, Program(solution.solution).get_tree_hash()) sigs.append(signature) aggsig = AugSchemeMPL.aggregate(sigs) solution_list: List[CoinSolution] = [] for puzzle, coin_solution in spends: solution_list.append( CoinSolution(coin_solution.coin, Program.to([puzzle, coin_solution.solution]))) spend_bundle = SpendBundle(solution_list, aggsig) return spend_bundle
def sign_transaction(self, coin_solutions: List[CoinSolution]) -> SpendBundle: sigs = [] solution: Program puzzle: Program for coin_solution in coin_solutions: # type: ignore # noqa secretkey = self.get_private_key_for_puzzle_hash( coin_solution.coin.puzzle_hash) err, con, cost = conditions_for_solution(coin_solution.solution) if not con: raise ValueError(err) conditions_dict = conditions_by_opcode(con) for _, msg in pkm_pairs_for_conditions_dict( conditions_dict, bytes(coin_solution.coin)): signature = AugSchemeMPL.sign(secretkey, msg) sigs.append(signature) aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(coin_solutions, aggsig) return spend_bundle
async def sign_clawback_transaction( self, spends: List[Tuple[Program, CoinSolution]], clawback_pubkey ) -> SpendBundle: sigs = [] for puzzle, solution in spends: pubkey, secretkey = await self.get_keys_pk(clawback_pubkey) signature = AugSchemeMPL.sign( secretkey, Program(solution.solution).get_tree_hash() ) sigs.append(signature) aggsig = AugSchemeMPL.aggregate(sigs) solution_list = [] for puzzle, coin_solution in spends: solution_list.append( CoinSolution( coin_solution.coin, Program.to([puzzle, coin_solution.solution]) ) ) return SpendBundle(solution_list, aggsig)
async def test_correct_coin_consumed(self, two_nodes): num_blocks = 2 blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"") full_node_1, full_node_2, server_1, server_2 = two_nodes block = blocks[1] block2 = blocks[2] for b in blocks: async for _ in full_node_1.respond_block( full_node_protocol.RespondBlock(b) ): pass cvp = ConditionVarPair( ConditionOpcode.ASSERT_COIN_CONSUMED, block2.get_coinbase().name(), None, ) dic = {cvp.opcode: [cvp]} spend_bundle1 = generate_test_spend_bundle(block.get_coinbase(), dic) spend_bundle2 = generate_test_spend_bundle(block2.get_coinbase()) bundle = SpendBundle.aggregate([spend_bundle1, spend_bundle2]) tx1: full_node_protocol.RespondTransaction = ( full_node_protocol.RespondTransaction(bundle) ) async for _ in full_node_1.respond_transaction(tx1): outbound: OutboundMessage = _ # Maybe transaction means that it's accepted in mempool assert outbound.message.function == "new_transaction" mempool_bundle = full_node_1.mempool_manager.get_spendbundle(bundle.name()) assert mempool_bundle is bundle
async def create_bundle_for_tip(self, header: Header) -> Optional[SpendBundle]: """ Returns aggregated spendbundle that can be used for creating new block """ if header.header_hash in self.mempools: mempool: Mempool = self.mempools[header.header_hash] cost_sum = 0 spend_bundles: List[SpendBundle] = [] for dic in mempool.sorted_spends.values(): for item in dic.values(): if item.cost + cost_sum <= self.constants["MAX_BLOCK_COST_CLVM"]: spend_bundles.append(item.spend_bundle) cost_sum += item.cost else: break if len(spend_bundles) > 0: block_bundle = SpendBundle.aggregate(spend_bundles) return block_bundle else: return None else: return None
async def create_bundle_from_mempool( self, peak_header_hash: bytes32) -> Optional[SpendBundle]: """ Returns aggregated spendbundle that can be used for creating new block """ if (self.peak is None or self.peak.header_hash != peak_header_hash or self.peak.height <= self.constants.INITIAL_FREEZE_PERIOD): return None cost_sum = 0 spend_bundles: List[SpendBundle] = [] for dic in self.mempool.sorted_spends.values(): for item in dic.values(): if item.cost_result.cost + cost_sum <= self.constants.MAX_BLOCK_COST_CLVM: spend_bundles.append(item.spend_bundle) cost_sum += item.cost_result.cost else: break if len(spend_bundles) > 0: return SpendBundle.aggregate(spend_bundles) else: return None
async def test_validate_blockchain_duplicate_output(self, two_nodes): num_blocks = 3 wallet_a = WALLET_A coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0] receiver_puzzlehash = BURN_PUZZLE_HASH blocks = bt.get_consecutive_blocks( num_blocks, farmer_reward_puzzle_hash=coinbase_puzzlehash, guarantee_transaction_block=True ) full_node_api_1, full_node_api_2, server_1, server_2 = two_nodes full_node_1 = full_node_api_1.full_node for block in blocks: await full_node_api_1.full_node.respond_block(full_node_protocol.RespondBlock(block)) spend_block = blocks[2] spend_coin = None for coin in list(spend_block.get_included_reward_coins()): if coin.puzzle_hash == coinbase_puzzlehash: spend_coin = coin spend_bundle = wallet_a.generate_signed_transaction(1000, receiver_puzzlehash, spend_coin) spend_bundle_double = wallet_a.generate_signed_transaction(1000, receiver_puzzlehash, spend_coin) block_spendbundle = SpendBundle.aggregate([spend_bundle, spend_bundle_double]) new_blocks = bt.get_consecutive_blocks( 1, block_list_input=blocks, farmer_reward_puzzle_hash=coinbase_puzzlehash, transaction_data=block_spendbundle, guarantee_transaction_block=True, ) next_block = new_blocks[-1] res, err, _ = await full_node_1.blockchain.receive_block(next_block) assert res == ReceiveBlockResult.INVALID_BLOCK assert err == Err.DUPLICATE_OUTPUT
async def test_validate_blockchain_with_double_spend(self, two_nodes): num_blocks = 5 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( 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 generate_new_coloured_coin(self, amount: uint64) -> Optional[SpendBundle]: coins = await self.standard_wallet.select_coins(amount) if coins is None: return None origin = coins.copy().pop() origin_id = origin.name() # self.add_parent(origin_id, origin_id) cc_core = cc_wallet_puzzles.cc_make_core(origin_id) parent_info = {} parent_info[origin_id] = ( origin.parent_coin_info, origin.puzzle_hash, origin.amount, ) cc_info: CCInfo = CCInfo(cc_core, [], origin_id.hex()) await self.save_info(cc_info) cc_inner = await self.get_new_inner_hash() cc_puzzle = cc_wallet_puzzles.cc_make_puzzle(cc_inner, cc_core) cc_puzzle_hash = cc_puzzle.get_tree_hash() tx_record: Optional[ TransactionRecord ] = await self.standard_wallet.generate_signed_transaction( amount, cc_puzzle_hash, uint64(0), origin_id, coins ) self.log.warning(f"cc_puzzle_hash is {cc_puzzle_hash}") eve_coin = Coin(origin_id, cc_puzzle_hash, amount) if tx_record is None or tx_record.spend_bundle is None: return None eve_spend = cc_generate_eve_spend(eve_coin, cc_puzzle) full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend]) return full_spend
def sign_transaction(self, coin_solutions: List[CoinSolution]) -> SpendBundle: signatures = [] solution: Program puzzle: Program for coin_solution in coin_solutions: # type: ignore # noqa secret_key = self.get_private_key_for_puzzle_hash( coin_solution.coin.puzzle_hash) synthetic_secret_key = calculate_synthetic_secret_key( secret_key, DEFAULT_HIDDEN_PUZZLE_HASH) err, con, cost = conditions_for_solution( coin_solution.puzzle_reveal, coin_solution.solution) if not con: raise ValueError(err) conditions_dict = conditions_by_opcode(con) for _, msg in pkm_pairs_for_conditions_dict( conditions_dict, bytes(coin_solution.coin.name())): signature = AugSchemeMPL.sign(synthetic_secret_key, msg) signatures.append(signature) aggsig = AugSchemeMPL.aggregate(signatures) spend_bundle = SpendBundle(coin_solutions, aggsig) return spend_bundle
def issue_cc_from_farmed_coin( mod_code: Program, coin_checker_for_farmed_coin, block_id: int, inner_puzzle_hash: bytes32, amount: int, ) -> Tuple[Program, SpendBundle]: """ This is an example of how to issue a cc. """ # get a farmed coin farmed_puzzle = ANYONE_CAN_SPEND_PUZZLE farmed_puzzle_hash = farmed_puzzle.get_tree_hash() # mint a cc farmed_coin = generate_farmed_coin(block_id, farmed_puzzle_hash, amount=uint64(amount)) genesis_coin_checker = coin_checker_for_farmed_coin(farmed_coin) minted_cc_puzzle_hash = cc_puzzle_hash_for_inner_puzzle_hash( mod_code, genesis_coin_checker, inner_puzzle_hash) output_conditions = [[ ConditionOpcode.CREATE_COIN, minted_cc_puzzle_hash, farmed_coin.amount ]] # for this very simple puzzle, the solution is simply the output conditions # this is just a coincidence... for more complicated puzzles, you'll likely have to do some real work solution = Program.to(output_conditions) coin_solution = CoinSolution(farmed_coin, Program.to([farmed_puzzle, solution])) spend_bundle = SpendBundle([coin_solution], NULL_SIGNATURE) return genesis_coin_checker, spend_bundle
async def test_correct_coin_consumed(self, two_nodes): reward_ph = WALLET_A.get_new_puzzlehash() full_node_1, full_node_2, server_1, server_2 = two_nodes blocks = await full_node_1.get_all_full_blocks() start_sub_height = blocks[-1].sub_block_height blocks = bt.get_consecutive_blocks( 4, block_list_input=blocks, guarantee_block=True, farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, ) peer = await connect_and_get_peer(server_1, server_2) for block in blocks: await full_node_1.full_node.respond_sub_block(full_node_protocol.RespondSubBlock(block)) await time_out_assert(60, node_height_at_least, True, full_node_1, start_sub_height + 4) coin_1 = list(blocks[-2].get_included_reward_coins())[0] coin_2 = list(blocks[-1].get_included_reward_coins())[0] cvp = ConditionVarPair(ConditionOpcode.ASSERT_COIN_CONSUMED, [coin_2.name()]) dic = {cvp.opcode: [cvp]} spend_bundle1 = generate_test_spend_bundle(coin_1, dic) spend_bundle2 = generate_test_spend_bundle(coin_2) bundle = SpendBundle.aggregate([spend_bundle1, spend_bundle2]) tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(bundle) await full_node_1.respond_transaction(tx1, peer) mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name()) assert mempool_bundle is bundle
async def create_bundle_from_mempool( self, peak_header_hash: bytes32 ) -> Optional[Tuple[SpendBundle, List[Coin], List[Coin]]]: """ Returns aggregated spendbundle that can be used for creating new block, additions and removals in that spend_bundle """ if ( self.peak is None or self.peak.header_hash != peak_header_hash or self.peak.height <= self.constants.INITIAL_FREEZE_PERIOD ): return None cost_sum = 0 # Checks that total cost does not exceed block maximum fee_sum = 0 # Checks that total fees don't exceed 64 bits spend_bundles: List[SpendBundle] = [] removals = [] additions = [] for dic in self.mempool.sorted_spends.values(): for item in dic.values(): if ( item.cost_result.cost + cost_sum <= self.constants.MAX_BLOCK_COST_CLVM and item.fee + fee_sum <= self.constants.MAX_COIN_AMOUNT ): spend_bundles.append(item.spend_bundle) cost_sum += item.cost_result.cost fee_sum += item.fee removals.extend(item.removals) additions.extend(item.additions) else: break if len(spend_bundles) > 0: return SpendBundle.aggregate(spend_bundles), additions, removals else: return None
async def generate_zero_val_coin( self, send=True, exclude: List[Coin] = None) -> Optional[SpendBundle]: if self.cc_info.my_core is None: return None if exclude is None: exclude = [] coins = await self.standard_wallet.select_coins(0, exclude) if coins == set() or coins is None: return None origin = coins.copy().pop() origin_id = origin.name() parent_info = {} parent_info[origin_id] = ( origin.parent_coin_info, origin.puzzle_hash, origin.amount, ) cc_inner = await self.get_new_inner_hash() cc_puzzle = cc_wallet_puzzles.cc_make_puzzle(cc_inner, self.cc_info.my_core) cc_puzzle_hash = cc_puzzle.get_tree_hash() tx = await self.standard_wallet.generate_signed_transaction( uint64(0), cc_puzzle_hash, uint64(0), origin_id, coins) self.log.info( f"Generate zero val coin: cc_puzzle_hash is {cc_puzzle_hash}") eve_coin = Coin(origin_id, cc_puzzle_hash, uint64(0)) if tx is None or tx.spend_bundle is None: return None eve_spend = cc_generate_eve_spend(eve_coin, cc_puzzle) full_spend = SpendBundle.aggregate([tx.spend_bundle, eve_spend]) future_parent = CCParent(eve_coin.parent_coin_info, cc_inner, eve_coin.amount) eve_parent = CCParent(origin.parent_coin_info, origin.puzzle_hash, origin.amount) await self.add_parent(eve_coin.name(), future_parent) await self.add_parent(eve_coin.parent_coin_info, eve_parent) if send: regular_record = TransactionRecord( confirmed_at_index=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=cc_puzzle_hash, amount=uint64(0), fee_amount=uint64(0), incoming=False, confirmed=False, sent=uint32(10), spend_bundle=full_spend, additions=full_spend.additions(), removals=full_spend.removals(), wallet_id=uint32(1), sent_to=[], trade_id=None, ) cc_record = TransactionRecord( confirmed_at_index=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=cc_puzzle_hash, amount=uint64(0), fee_amount=uint64(0), incoming=True, confirmed=False, sent=uint32(0), spend_bundle=full_spend, additions=full_spend.additions(), removals=full_spend.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, ) await self.wallet_state_manager.add_transaction(regular_record) await self.wallet_state_manager.add_pending_transaction(cc_record) return full_spend
def spend_bundle_for_spendable_ccs( mod_code: Program, genesis_coin_checker: Program, spendable_cc_list: List[SpendableCC], inner_solutions: List[Program], sigs: Optional[List[G2Element]] = [], ) -> SpendBundle: """ Given a list of `SpendableCC` objects and inner solutions for those objects, create a `SpendBundle` that spends all those coins. Note that it the signature is not calculated it, so the caller is responsible for fixing it. """ N = len(spendable_cc_list) if len(inner_solutions) != N: raise ValueError("spendable_cc_list and inner_solutions are different lengths") input_coins = [_.coin for _ in spendable_cc_list] # figure out what the output amounts are by running the inner puzzles & solutions output_amounts = [] for cc_spend_info, inner_solution in zip(spendable_cc_list, inner_solutions): error, conditions, cost = conditions_dict_for_solution(Program.to([cc_spend_info.inner_puzzle, inner_solution])) total = 0 if conditions: for _ in conditions.get(ConditionOpcode.CREATE_COIN, []): total += Program.to(_.vars[1]).as_int() output_amounts.append(total) coin_solutions = [] deltas = [input_coins[_].amount - output_amounts[_] for _ in range(N)] subtotals = subtotals_for_deltas(deltas) if sum(deltas) != 0: raise ValueError("input and output amounts don't match") bundles = [bundle_for_spendable_cc_list(_) for _ in spendable_cc_list] for index in range(N): cc_spend_info = spendable_cc_list[index] puzzle_reveal = cc_puzzle_for_inner_puzzle(mod_code, genesis_coin_checker, cc_spend_info.inner_puzzle) prev_index = (index - 1) % N next_index = (index + 1) % N prev_bundle = bundles[prev_index] my_bundle = bundles[index] next_bundle = bundles[next_index] solution = [ inner_solutions[index], prev_bundle, my_bundle, next_bundle, subtotals[index], ] full_solution = Program.to([puzzle_reveal, solution]) coin_solution = CoinSolution(input_coins[index], full_solution) coin_solutions.append(coin_solution) # now add solutions to consume the lock coins for _ in range(N): prev_index = (_ - 1) % N prev_coin = spendable_cc_list[prev_index].coin this_coin = spendable_cc_list[_].coin subtotal = subtotals[_] coin_solution = coin_solution_for_lock_coin(prev_coin, subtotal, this_coin) coin_solutions.append(coin_solution) if sigs is None or sigs == []: return SpendBundle(coin_solutions, NULL_SIGNATURE) else: return SpendBundle(coin_solutions, AugSchemeMPL.aggregate(sigs))
async def add_spendbundle( self, new_spend: SpendBundle, to_pool: Mempool = None ) -> Tuple[Optional[uint64], MempoolInclusionStatus, Optional[Err]]: """ Tries to add spendbundle to either self.mempools or to_pool if it's specified. Returns true if it's added in any of pools, Returns error if it fails. """ self.seen_bundle_hashes[new_spend.name()] = new_spend.name() self.maybe_pop_seen() # Calculate the cost and fees program = best_solution_program(new_spend) # npc contains names of the coins removed, puzzle_hashes and their spend conditions fail_reason, npc_list, cost = calculate_cost_of_program(program) if fail_reason: return None, MempoolInclusionStatus.FAILED, fail_reason # build removal list removal_names: List[bytes32] = new_spend.removal_names() additions = new_spend.additions() additions_dict: Dict[bytes32, Coin] = {} for add in additions: additions_dict[add.name()] = add addition_amount = uint64(0) # Check additions for max coin amount for coin in additions: if coin.amount >= uint64.from_bytes(self.constants["MAX_COIN_AMOUNT"]): return ( None, MempoolInclusionStatus.FAILED, Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, ) addition_amount = uint64(addition_amount + coin.amount) # Check for duplicate outputs addition_counter = collections.Counter(_.name() for _ in additions) for k, v in addition_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DUPLICATE_OUTPUT # Spend might be valid for one pool but not for other added_count = 0 errors: List[Err] = [] targets: List[Mempool] # If the transaction is added to potential set (to be retried), this is set. added_to_potential: bool = False potential_error: Optional[Err] = None if to_pool is not None: targets = [to_pool] else: targets = list(self.mempools.values()) for pool in targets: # Skip if already added if new_spend.name() in pool.spends: added_count += 1 continue removal_record_dict: Dict[bytes32, CoinRecord] = {} removal_coin_dict: Dict[bytes32, Coin] = {} unknown_unspent_error: bool = False removal_amount = uint64(0) for name in removal_names: removal_record = await self.unspent_store.get_coin_record( name, pool.header ) if removal_record is None and name not in additions_dict: unknown_unspent_error = True break elif name in additions_dict: removal_coin = additions_dict[name] removal_record = CoinRecord( removal_coin, uint32(pool.header.height + 1), uint32(0), False, False, ) assert removal_record is not None removal_amount = uint64(removal_amount + removal_record.coin.amount) removal_record_dict[name] = removal_record removal_coin_dict[name] = removal_record.coin if unknown_unspent_error: errors.append(Err.UNKNOWN_UNSPENT) continue if addition_amount > removal_amount: return None, MempoolInclusionStatus.FAILED, Err.MINTING_COIN fees = removal_amount - addition_amount if cost == 0: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN fees_per_cost: float = fees / cost # If pool is at capacity check the fee, if not then accept even without the fee if pool.at_full_capacity(): if fees == 0: errors.append(Err.INVALID_FEE_LOW_FEE) continue if fees_per_cost < pool.get_min_fee_rate(): errors.append(Err.INVALID_FEE_LOW_FEE) continue # Check removals against UnspentDB + DiffStore + Mempool + SpendBundle # Use this information later when constructing a block fail_reason, conflicts = await self.check_removals( removal_record_dict, pool ) # If there is a mempool conflict check if this spendbundle has a higher fee per cost than all others tmp_error: Optional[Err] = None conflicting_pool_items: Dict[bytes32, MempoolItem] = {} if fail_reason is Err.MEMPOOL_CONFLICT: for conflicting in conflicts: sb: MempoolItem = pool.removals[conflicting.name()] conflicting_pool_items[sb.name] = sb for item in conflicting_pool_items.values(): if item.fee_per_cost >= fees_per_cost: tmp_error = Err.MEMPOOL_CONFLICT self.add_to_potential_tx_set(new_spend) added_to_potential = True potential_error = Err.MEMPOOL_CONFLICT break elif fail_reason: errors.append(fail_reason) continue if tmp_error: errors.append(tmp_error) continue # Verify conditions, create hash_key list for aggsig check hash_key_pairs = [] error: Optional[Err] = None for npc in npc_list: coin_record: CoinRecord = removal_record_dict[npc.coin_name] # Check that the revealed removal puzzles actually match the puzzle hash if npc.puzzle_hash != coin_record.coin.puzzle_hash: log.warning( f"Mempool rejecting transaction because of wrong puzzle_hash" ) log.warning(f"{npc.puzzle_hash} != {coin_record.coin.puzzle_hash}") return None, MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH error = mempool_check_conditions_dict( coin_record, new_spend, npc.condition_dict, pool ) if error: if ( error is Err.ASSERT_BLOCK_INDEX_EXCEEDS_FAILED or error is Err.ASSERT_BLOCK_AGE_EXCEEDS_FAILED ): self.add_to_potential_tx_set(new_spend) added_to_potential = True potential_error = error break hash_key_pairs.extend( hash_key_pairs_for_conditions_dict( npc.condition_dict, npc.coin_name ) ) if error: errors.append(error) continue # Verify aggregated signature if not new_spend.aggregated_signature.validate(hash_key_pairs): return None, MempoolInclusionStatus.FAILED, Err.BAD_AGGREGATE_SIGNATURE # Remove all conflicting Coins and SpendBundles if fail_reason: mitem: MempoolItem for mitem in conflicting_pool_items.values(): pool.remove_spend(mitem) new_item = MempoolItem(new_spend, fees_per_cost, uint64(fees), uint64(cost)) pool.add_to_pool(new_item, additions, removal_coin_dict) added_count += 1 if added_count > 0: return uint64(cost), MempoolInclusionStatus.SUCCESS, None elif added_to_potential: return uint64(cost), MempoolInclusionStatus.PENDING, potential_error else: return None, MempoolInclusionStatus.FAILED, errors[0]
def build_spend_bundle(coin, solution, keychain=DEFAULT_KEYTOOL): coin_solution = CoinSolution(coin, solution) signature = keychain.signature_for_solution(solution, bytes(coin)) return SpendBundle([coin_solution], signature)
async def add_spendbundle( self, new_spend: SpendBundle, cost_result: CostResult, spend_name: bytes32, validate_signature=True, ) -> 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. """ start_time = time.time() if self.peak is None: return None, MempoolInclusionStatus.FAILED, Err.MEMPOOL_NOT_INITIALIZED npc_list = cost_result.npc_list cost = cost_result.cost log.debug(f"Cost: {cost}") if cost > self.constants.MAX_BLOCK_COST_CLVM: return None, MempoolInclusionStatus.FAILED, Err.BLOCK_COST_EXCEEDS_MAX if cost_result.error is not None: return None, MempoolInclusionStatus.FAILED, Err(cost_result.error) # build removal list removal_names: List[bytes32] = new_spend.removal_names() additions = additions_for_npc(npc_list) 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 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), # In mempool, so will be included in next height 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.RESERVE_FEE in npc.condition_dict: fee_list: List[ConditionVarPair] = 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 if fees < assert_fee_sum: return ( None, MempoolInclusionStatus.FAILED, Err.RESERVE_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, spend_name, cost_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 chialisp_height = ( self.peak.prev_transaction_block_height if not self.peak.is_transaction_block else self.peak.height ) error = mempool_check_conditions_dict(coin_record, new_spend, npc.condition_dict, uint32(chialisp_height)) if error: if error is Err.ASSERT_HEIGHT_NOW_EXCEEDS_FAILED or error is Err.ASSERT_HEIGHT_AGE_EXCEEDS_FAILED: self.add_to_potential_tx_set(new_spend, spend_name, cost_result) return uint64(cost), MempoolInclusionStatus.PENDING, error break if validate_signature: 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 if validate_signature: # Verify aggregated signature if not AugSchemeMPL.aggregate_verify(pks, msgs, new_spend.aggregated_signature): 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) removals: List[Coin] = [coin for coin in removal_coin_dict.values()] new_item = MempoolItem(new_spend, uint64(fees), cost_result, spend_name, additions, removals) self.mempool.add_to_pool(new_item, additions, removal_coin_dict) log.info(f"add_spendbundle took {time.time() - start_time} seconds") return uint64(cost), MempoolInclusionStatus.SUCCESS, None
async def push_tx(self, spend_bundle: SpendBundle): return await self.fetch("push_tx", {"spend_bundle": spend_bundle.to_json_dict()})
async def respond_to_offer( self, file_path: Path ) -> Tuple[bool, Optional[TradeRecord], Optional[str]]: has_wallets = await self.maybe_create_wallets_for_offer(file_path) if not has_wallets: return False, None, "Unknown Error" trade_offer = None try: trade_offer_hex = file_path.read_text() trade_offer = TradeRecord.from_bytes( hexstr_to_bytes(trade_offer_hex)) except Exception as e: return False, None, f"Error: {e}" if trade_offer is not None: offer_spend_bundle: SpendBundle = trade_offer.spend_bundle coinsols = [] # [] of CoinSolutions cc_coinsol_outamounts: Dict[bytes32, List[Tuple[Any, int]]] = dict() aggsig = offer_spend_bundle.aggregated_signature cc_discrepancies: Dict[bytes32, int] = dict() chia_discrepancy = None wallets: Dict[bytes32, Any] = dict() # colour to wallet dict for coinsol in offer_spend_bundle.coin_solutions: puzzle: Program = coinsol.solution.first() solution: Program = coinsol.solution.rest().first() # work out the deficits between coin amount and expected output for each r = cc_utils.uncurry_cc(puzzle) if r: # Calculate output amounts mod_hash, genesis_checker, inner_puzzle = r colour = bytes(genesis_checker).hex() if colour not in wallets: wallets[ colour] = await self.wallet_state_manager.get_wallet_for_colour( colour) unspent = await self.wallet_state_manager.get_spendable_coins_for_wallet( wallets[colour].id()) if coinsol.coin in [record.coin for record in unspent]: return False, None, "can't respond to own offer" innersol = solution.first() total = get_output_amount_for_puzzle_and_solution( inner_puzzle, innersol) if colour in cc_discrepancies: cc_discrepancies[colour] += coinsol.coin.amount - total else: cc_discrepancies[colour] = coinsol.coin.amount - total # Store coinsol and output amount for later if colour in cc_coinsol_outamounts: cc_coinsol_outamounts[colour].append((coinsol, total)) else: cc_coinsol_outamounts[colour] = [(coinsol, total)] else: # standard chia coin unspent = await self.wallet_state_manager.get_spendable_coins_for_wallet( 1) if coinsol.coin in [record.coin for record in unspent]: return False, None, "can't respond to own offer" if chia_discrepancy is None: chia_discrepancy = get_output_discrepancy_for_puzzle_and_solution( coinsol.coin, puzzle, solution) else: chia_discrepancy += get_output_discrepancy_for_puzzle_and_solution( coinsol.coin, puzzle, solution) coinsols.append(coinsol) chia_spend_bundle: Optional[SpendBundle] = None if chia_discrepancy is not None: chia_spend_bundle = await self.wallet_state_manager.main_wallet.create_spend_bundle_relative_chia( chia_discrepancy, []) if chia_spend_bundle is not None: for coinsol in coinsols: chia_spend_bundle.coin_solutions.append(coinsol) zero_spend_list: List[SpendBundle] = [] spend_bundle = None # create coloured coin self.log.info(cc_discrepancies) for colour in cc_discrepancies.keys(): if cc_discrepancies[colour] < 0: my_cc_spends = await wallets[colour].select_coins( abs(cc_discrepancies[colour])) else: if chia_spend_bundle is None: to_exclude: List = [] else: to_exclude = chia_spend_bundle.removals() my_cc_spends = await wallets[colour].select_coins(0) if my_cc_spends is None or my_cc_spends == set(): zero_spend_bundle: SpendBundle = await wallets[ colour].generate_zero_val_coin(False, to_exclude) if zero_spend_bundle is None: return ( False, None, "Unable to generate zero value coin. Confirm that you have chia available", ) zero_spend_list.append(zero_spend_bundle) additions = zero_spend_bundle.additions() removals = zero_spend_bundle.removals() my_cc_spends = set() for add in additions: if add not in removals and add.amount == 0: my_cc_spends.add(add) if my_cc_spends == set() or my_cc_spends is None: return False, None, "insufficient funds" # Create SpendableCC list and innersol_list with both my coins and the offered coins # Firstly get the output coin my_output_coin = my_cc_spends.pop() spendable_cc_list = [] innersol_list = [] genesis_id = genesis_coin_id_for_genesis_coin_checker( Program.from_bytes(bytes.fromhex(colour))) # Make the rest of the coins assert the output coin is consumed for coloured_coin in my_cc_spends: inner_solution = self.wallet_state_manager.main_wallet.make_solution( consumed=[my_output_coin.name()]) inner_puzzle = await self.get_inner_puzzle_for_puzzle_hash( coloured_coin.puzzle_hash) assert inner_puzzle is not None sigs = await wallets[colour].get_sigs(inner_puzzle, inner_solution, coloured_coin.name()) sigs.append(aggsig) aggsig = AugSchemeMPL.aggregate(sigs) lineage_proof = await wallets[ colour].get_lineage_proof_for_coin(coloured_coin) spendable_cc_list.append( SpendableCC(coloured_coin, genesis_id, inner_puzzle, lineage_proof)) innersol_list.append(inner_solution) # Create SpendableCC for each of the coloured coins received for cc_coinsol_out in cc_coinsol_outamounts[colour]: cc_coinsol = cc_coinsol_out[0] puzzle = cc_coinsol.solution.first() solution = cc_coinsol.solution.rest().first() r = uncurry_cc(puzzle) if r: mod_hash, genesis_coin_checker, inner_puzzle = r inner_solution = solution.first() lineage_proof = solution.rest().rest().first() spendable_cc_list.append( SpendableCC(cc_coinsol.coin, genesis_id, inner_puzzle, lineage_proof)) innersol_list.append(inner_solution) # Finish the output coin SpendableCC with new information newinnerpuzhash = await wallets[colour].get_new_inner_hash() outputamount = sum([ c.amount for c in my_cc_spends ]) + cc_discrepancies[colour] + my_output_coin.amount inner_solution = self.wallet_state_manager.main_wallet.make_solution( primaries=[{ "puzzlehash": newinnerpuzhash, "amount": outputamount }]) inner_puzzle = await self.get_inner_puzzle_for_puzzle_hash( my_output_coin.puzzle_hash) assert inner_puzzle is not None lineage_proof = await wallets[colour].get_lineage_proof_for_coin( my_output_coin) spendable_cc_list.append( SpendableCC(my_output_coin, genesis_id, inner_puzzle, lineage_proof)) innersol_list.append(inner_solution) sigs = await wallets[colour].get_sigs(inner_puzzle, inner_solution, my_output_coin.name()) sigs.append(aggsig) aggsig = AugSchemeMPL.aggregate(sigs) if spend_bundle is None: spend_bundle = spend_bundle_for_spendable_ccs( CC_MOD, Program.from_bytes(bytes.fromhex(colour)), spendable_cc_list, innersol_list, [aggsig], ) else: new_spend_bundle = spend_bundle_for_spendable_ccs( CC_MOD, Program.from_bytes(bytes.fromhex(colour)), spendable_cc_list, innersol_list, [aggsig], ) spend_bundle = SpendBundle.aggregate( [spend_bundle, new_spend_bundle]) # reset sigs and aggsig so that they aren't included next time around sigs = [] aggsig = AugSchemeMPL.aggregate(sigs) my_tx_records = [] if zero_spend_list is not None and spend_bundle is not None: zero_spend_list.append(spend_bundle) spend_bundle = SpendBundle.aggregate(zero_spend_list) if spend_bundle is None: return False, None, "spend_bundle missing" # Add transaction history for this trade now = uint64(int(time.time())) if chia_spend_bundle is not None: spend_bundle = SpendBundle.aggregate( [spend_bundle, chia_spend_bundle]) # debug_spend_bundle(spend_bundle) if chia_discrepancy < 0: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=now, to_puzzle_hash=token_bytes(), amount=uint64(abs(chia_discrepancy)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=chia_spend_bundle, additions=chia_spend_bundle.additions(), removals=chia_spend_bundle.removals(), wallet_id=uint32(1), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.OUTGOING_TRADE.value), name=chia_spend_bundle.name(), ) else: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(abs(chia_discrepancy)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=chia_spend_bundle, additions=chia_spend_bundle.additions(), removals=chia_spend_bundle.removals(), wallet_id=uint32(1), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.INCOMING_TRADE.value), name=chia_spend_bundle.name(), ) my_tx_records.append(tx_record) for colour, amount in cc_discrepancies.items(): wallet = wallets[colour] if chia_discrepancy > 0: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(abs(amount)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=wallet.id(), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.OUTGOING_TRADE.value), name=spend_bundle.name(), ) else: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(abs(amount)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=wallet.id(), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.INCOMING_TRADE.value), name=token_bytes(), ) my_tx_records.append(tx_record) tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(0), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=uint32(0), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.OUTGOING_TRADE.value), name=spend_bundle.name(), ) now = uint64(int(time.time())) trade_record: TradeRecord = TradeRecord( confirmed_at_index=uint32(0), accepted_at_time=now, created_at_time=now, my_offer=False, sent=uint32(0), spend_bundle=offer_spend_bundle, tx_spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), trade_id=std_hash(spend_bundle.name() + bytes(now)), status=uint32(TradeStatus.PENDING_CONFIRM.value), sent_to=[], ) await self.save_trade(trade_record) await self.wallet_state_manager.add_pending_transaction(tx_record) for tx in my_tx_records: await self.wallet_state_manager.add_transaction(tx) return True, trade_record, None
def debug_spend_bundle(spend_bundle: SpendBundle) -> None: """ Print a lot of useful information about a `SpendBundle` that might help with debugging its clvm. """ assert_consumed_set = set() print("=" * 80) for coin_solution in spend_bundle.coin_solutions: coin, solution_pair = coin_solution.coin, coin_solution.solution puzzle_reveal = solution_pair.first() solution = solution_pair.rest().first() print(f"consuming coin {dump_coin(coin)}") print(f" with id {coin.name()}") print() print( f"\nbrun -y main.sym '{bu_disassemble(puzzle_reveal)}' '{bu_disassemble(solution)}'" ) error, conditions, cost = conditions_dict_for_solution( Program.to([puzzle_reveal, solution])) if error: print(f"*** error {error}") else: print() cost, r = run_program(puzzle_reveal, solution) print(disassemble(r)) print() if conditions and len(conditions) > 0: print("grouped conditions:") for condition_programs in conditions.values(): print() for c in condition_programs: as_prog = Program.to([c.opcode, c.var1, c.var2]) print(f" {disassemble(as_prog)}") print() for _ in conditions.get(ConditionOpcode.ASSERT_COIN_CONSUMED, []): assert_consumed_set.add(bytes32(_.var1)) else: print("(no output conditions generated)") print() print("-------") created = set(spend_bundle.additions()) spent = set(spend_bundle.removals()) zero_coin_set = set(coin.name() for coin in created if coin.amount == 0) ephemeral = created.intersection(spent) created.difference_update(ephemeral) spent.difference_update(ephemeral) print() print("spent coins") for coin in sorted(spent, key=lambda _: _.name()): print(f" {dump_coin(coin)}") print(f" => spent coin id {coin.name()}") print() print("created coins") for coin in sorted(created, key=lambda _: _.name()): print(f" {dump_coin(coin)}") print(f" => created coin id {coin.name()}") if ephemeral: print() print("ephemeral coins") for coin in sorted(ephemeral, key=lambda _: _.name()): print(f" {dump_coin(coin)}") print(f" => created coin id {coin.name()}") print() print(f"assert_consumed_set = {sorted(assert_consumed_set)}") print() print(f"zero_coin_set = {sorted(zero_coin_set)}") print() set_difference = zero_coin_set ^ assert_consumed_set print(f"zero_coin_set ^ assert_consumed_set = {sorted(set_difference)}") if len(set_difference): print( "not all zero coins asserted consumed or vice versa, entering debugger" ) breakpoint() print() print("=" * 80)
async def generate_signed_transaction( self, amount: uint64, to_address: bytes32, fee: uint64 = uint64(0), origin_id: bytes32 = None, coins: Set[Coin] = None, ) -> Optional[TransactionRecord]: sigs: List[G2Element] = [] # Get coins and calculate amount of change required if coins is None: selected_coins: Optional[Set[Coin]] = await self.select_coins( amount) else: selected_coins = coins if selected_coins is None: return None total_amount = sum([x.amount for x in selected_coins]) change = total_amount - amount # first coin becomes the auditor special case auditor = selected_coins.pop() puzzle_hash = auditor.puzzle_hash inner_puzzle: Program = await self.inner_puzzle_for_cc_puzzle( puzzle_hash) auditor_info = ( auditor.parent_coin_info, inner_puzzle.get_tree_hash(), auditor.amount, ) list_of_solutions = [] # auditees should be (primary_input, innerpuzhash, coin_amount, output_amount) auditees = [( auditor.parent_coin_info, inner_puzzle.get_tree_hash(), auditor.amount, total_amount, )] for coin in selected_coins: coin_inner_puzzle: Program = await self.inner_puzzle_for_cc_puzzle( coin.puzzle_hash) auditees.append(( coin.parent_coin_info, coin_inner_puzzle[coin], coin.amount, 0, )) primaries = [{"puzzlehash": to_address, "amount": amount}] if change > 0: changepuzzlehash = await self.get_new_inner_hash() primaries.append({ "puzzlehash": changepuzzlehash, "amount": change }) innersol = self.standard_wallet.make_solution(primaries=primaries) sigs = sigs + await self.get_sigs(inner_puzzle, innersol) parent_info = await self.get_parent_for_coin(auditor) assert parent_info is not None assert self.cc_info.my_core is not None solution = cc_wallet_puzzles.cc_make_solution( self.cc_info.my_core, ( parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ), auditor.amount, binutils.disassemble(inner_puzzle), binutils.disassemble(innersol), auditor_info, auditees, False, ) main_coin_solution = CoinSolution( auditor, Program.to([ cc_wallet_puzzles.cc_make_puzzle( inner_puzzle.get_tree_hash(), self.cc_info.my_core, ), solution, ]), ) list_of_solutions.append(main_coin_solution) # main = SpendBundle([main_coin_solution], ZERO96) ephemeral_coin_solution = create_spend_for_ephemeral( auditor, auditor, total_amount) list_of_solutions.append(ephemeral_coin_solution) # eph = SpendBundle([ephemeral_coin_solution], ZERO96) auditor_coin_colution = create_spend_for_auditor(auditor, auditor) list_of_solutions.append(auditor_coin_colution) # aud = SpendBundle([auditor_coin_colution], ZERO96) # loop through remaining spends, treating them as aggregatees for coin in selected_coins: coin_inner_puzzle = await self.inner_puzzle_for_cc_puzzle( coin.puzzle_hash) innersol = self.standard_wallet.make_solution() parent_info = await self.get_parent_for_coin(coin) assert parent_info is not None sigs = sigs + await self.get_sigs(coin_inner_puzzle, innersol) solution = cc_wallet_puzzles.cc_make_solution( self.cc_info.my_core, ( parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ), coin.amount, binutils.disassemble(coin_inner_puzzle), binutils.disassemble(innersol), auditor_info, None, ) list_of_solutions.append( CoinSolution( coin, Program.to([ cc_wallet_puzzles.cc_make_puzzle( coin_inner_puzzle.get_tree_hash(), self.cc_info.my_core, ), solution, ]), )) list_of_solutions.append( create_spend_for_ephemeral(coin, auditor, 0)) list_of_solutions.append(create_spend_for_auditor(auditor, coin)) aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) tx_record = TransactionRecord( confirmed_at_index=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=to_address, amount=uint64(amount), fee_amount=uint64(0), incoming=False, confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, ) return tx_record
async def rl_generate_signed_aggregation_transaction( self, rl_info, consolidating_coin, rl_parent, rl_coin ): if ( rl_info.limit is None or rl_info.interval is None or rl_info.user_pubkey is None or rl_info.admin_pubkey is None ): raise ValueError("One or more of the elements of rl_info is None") if self.rl_coin_record is None: raise ValueError("Rl coin record is None") list_of_coinsolutions = [] self.rl_coin_record = await self._get_rl_coin_record() pubkey, secretkey = await self.get_keys(self.rl_coin_record.coin.puzzle_hash) # Spend wallet coin puzzle = rl_puzzle_for_pk( rl_info.user_pubkey, rl_info.limit, rl_info.interval, rl_info.rl_origin_id, rl_info.admin_pubkey, ) solution = rl_make_solution_mode_2( rl_coin.puzzle_hash, consolidating_coin.parent_coin_info, consolidating_coin.puzzle_hash, consolidating_coin.amount, rl_coin.parent_coin_info, rl_coin.amount, rl_parent.amount, rl_parent.parent_coin_info, ) signature = AugSchemeMPL.sign(secretkey, solution.get_tree_hash()) rl_spend = CoinSolution( self.rl_coin_record.coin, Program.to([puzzle, solution]) ) list_of_coinsolutions.append(rl_spend) # Spend consolidating coin puzzle = rl_make_aggregation_puzzle(self.rl_coin_record.coin.puzzle_hash) solution = rl_make_aggregation_solution( consolidating_coin.name(), self.rl_coin_record.coin.parent_coin_info, self.rl_coin_record.coin.amount, ) agg_spend = CoinSolution(consolidating_coin, Program.to([puzzle, solution])) list_of_coinsolutions.append(agg_spend) # Spend lock puzstring = f"(r (c (q 0x{consolidating_coin.name().hex()}) (q ())))" puzzle = Program(binutils.assemble(puzstring)) solution = Program(binutils.assemble("()")) ephemeral = CoinSolution( Coin(self.rl_coin_record.coin.name(), puzzle.get_tree_hash(), uint64(0)), Program.to([puzzle, solution]), ) list_of_coinsolutions.append(ephemeral) aggsig = AugSchemeMPL.aggregate([signature]) return SpendBundle(list_of_coinsolutions, aggsig)
async def create_spend_bundle_relative_amount(self, cc_amount, zero_coin: Coin = None): # If we're losing value then get coloured coins with at least that much value # If we're gaining value then our amount doesn't matter if cc_amount < 0: cc_spends = await self.select_coins(abs(cc_amount)) else: if zero_coin is None: return None cc_spends = set() cc_spends.add(zero_coin) if cc_spends is None: return None # Calculate output amount given relative difference and sum of actual values spend_value = sum([coin.amount for coin in cc_spends]) cc_amount = spend_value + cc_amount # Loop through coins and create solution for innerpuzzle list_of_solutions = [] output_created = None sigs: List[G2Element] = [] for coin in cc_spends: if output_created is None: newinnerpuzhash = await self.get_new_inner_hash() innersol = self.standard_wallet.make_solution( primaries=[{ "puzzlehash": newinnerpuzhash, "amount": cc_amount }]) output_created = coin else: innersol = self.standard_wallet.make_solution() innerpuz: Program = await self.inner_puzzle_for_cc_puzzle( coin.puzzle_hash) parent_info = await self.get_parent_for_coin(coin) assert parent_info is not None assert self.cc_info.my_core is not None # Use coin info to create solution and add coin and solution to list of CoinSolutions solution = cc_wallet_puzzles.cc_make_solution( self.cc_info.my_core, ( parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ), coin.amount, binutils.disassemble(innerpuz), binutils.disassemble(innersol), None, None, ) list_of_solutions.append( CoinSolution( coin, Program.to([ cc_wallet_puzzles.cc_make_puzzle( innerpuz.get_tree_hash(), self.cc_info.my_core), solution, ]), )) sigs = sigs + await self.get_sigs(innerpuz, innersol) aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) return spend_bundle
async def _create_offer_for_ids( self, offer: Dict[int, int] ) -> Tuple[bool, Optional[TradeRecord], Optional[str]]: """ Offer is dictionary of wallet ids and amount """ spend_bundle = None try: for id in offer.keys(): amount = offer[id] wallet_id = uint32(int(id)) wallet = self.wallet_state_manager.wallets[wallet_id] if isinstance(wallet, CCWallet): balance = await wallet.get_confirmed_balance() if balance < abs(amount) and amount < 0: raise Exception( f"insufficient funds in wallet {wallet_id}") if amount > 0: if spend_bundle is None: to_exclude: List[Coin] = [] else: to_exclude = spend_bundle.removals() zero_spend_bundle: SpendBundle = await wallet.generate_zero_val_coin( False, to_exclude) if spend_bundle is None: spend_bundle = zero_spend_bundle else: spend_bundle = SpendBundle.aggregate( [spend_bundle, zero_spend_bundle]) additions = zero_spend_bundle.additions() removals = zero_spend_bundle.removals() zero_val_coin: Optional[Coin] = None for add in additions: if add not in removals and add.amount == 0: zero_val_coin = add new_spend_bundle = await wallet.create_spend_bundle_relative_amount( amount, zero_val_coin) else: new_spend_bundle = await wallet.create_spend_bundle_relative_amount( amount) elif isinstance(wallet, Wallet): if spend_bundle is None: to_exclude = [] else: to_exclude = spend_bundle.removals() new_spend_bundle = await wallet.create_spend_bundle_relative_chia( amount, to_exclude) else: return False, None, "unsupported wallet type" if new_spend_bundle is None or new_spend_bundle.removals( ) == []: raise Exception(f"Wallet {id} was unable to create offer.") if spend_bundle is None: spend_bundle = new_spend_bundle else: spend_bundle = SpendBundle.aggregate( [spend_bundle, new_spend_bundle]) if spend_bundle is None: return False, None, None now = uint64(int(time.time())) trade_offer: TradeRecord = TradeRecord( confirmed_at_index=uint32(0), accepted_at_time=None, created_at_time=now, my_offer=True, sent=uint32(0), spend_bundle=spend_bundle, tx_spend_bundle=None, additions=spend_bundle.additions(), removals=spend_bundle.removals(), trade_id=std_hash(spend_bundle.name() + bytes(now)), status=uint32(TradeStatus.PENDING_ACCEPT.value), sent_to=[], ) return True, trade_offer, None except Exception as e: tb = traceback.format_exc() self.log.error(f"Error with creating trade offer: {type(e)}{tb}") return False, None, str(e)