async def coins_of_interest_added(self, coins: List[Coin], height: uint32) -> List[Coin]: ( trade_removals, trade_additions, ) = await self.trade_manager.get_coins_of_interest() trade_adds: List[Coin] = [] block: Optional[BlockRecord] = await self.blockchain.get_block_record_from_db( self.blockchain.height_to_hash(height) ) assert block is not None pool_rewards = set() farmer_rewards = set() prev = await self.blockchain.get_block_record_from_db(block.prev_hash) # [block 1] [block 2] [tx block 3] [block 4] [block 5] [tx block 6] # [tx block 6] will contain rewards for [block 1] [block 2] [tx block 3] while prev is not None: # step 1 find previous block if prev.is_transaction_block: break prev = await self.blockchain.get_block_record_from_db(prev.prev_hash) if prev is not None: # include last block pool_parent = pool_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) farmer_parent = farmer_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) pool_rewards.add(pool_parent) farmer_rewards.add(farmer_parent) prev = await self.blockchain.get_block_record_from_db(prev.prev_hash) while prev is not None: # step 2 traverse from previous block to the block before it pool_parent = pool_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) farmer_parent = farmer_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) pool_rewards.add(pool_parent) farmer_rewards.add(farmer_parent) if prev.is_transaction_block: break prev = await self.blockchain.get_block_record_from_db(prev.prev_hash) for coin in coins: if coin.name() in trade_additions: trade_adds.append(coin) is_coinbase = False is_fee_reward = False if coin.parent_coin_info in pool_rewards: is_coinbase = True if coin.parent_coin_info in farmer_rewards: is_fee_reward = True info = await self.puzzle_store.wallet_info_for_puzzle_hash(coin.puzzle_hash) if info is not None: wallet_id, wallet_type = info await self.coin_added(coin, is_coinbase, is_fee_reward, uint32(wallet_id), wallet_type, height) return trade_adds
def get_farmed_height(reward_coin_record: CoinRecord, genesis_challenge: bytes32) -> Optional[uint32]: # Returns the height farmed if it's a coinbase reward, otherwise None for block_index in range(reward_coin_record.confirmed_block_index, reward_coin_record.confirmed_block_index - 128, -1): if block_index < 0: break pool_parent = pool_parent_id(uint32(block_index), genesis_challenge) if pool_parent == reward_coin_record.coin.parent_coin_info: return uint32(block_index) return None
def height_farmed(self, genesis_challenge) -> Optional[uint32]: if not self.confirmed: return None if self.type == TransactionType.FEE_REWARD or self.type == TransactionType.COINBASE_REWARD: for block_index in range(self.confirmed_at_height, self.confirmed_at_height - 100, -1): if block_index < 0: return None pool_parent = pool_parent_id(uint32(block_index), genesis_challenge) farmer_parent = farmer_parent_id(uint32(block_index), genesis_challenge) if pool_parent == self.additions[0].parent_coin_info: return uint32(block_index) if farmer_parent == self.additions[0].parent_coin_info: return uint32(block_index) return None
async def create_absorb_transaction( node_rpc_client: FullNodeRpcClient, farmer_record: FarmerRecord, peak_height: uint32, reward_coin_records: List[CoinRecord], genesis_challenge: bytes32, ) -> SpendBundle: last_solution, last_state, _, _ = await get_and_validate_singleton_state_inner( node_rpc_client, farmer_record.launcher_id, farmer_record, peak_height, 0, None) launcher_coin_record: Optional[ CoinRecord] = await node_rpc_client.get_coin_record_by_name( farmer_record.launcher_id) assert launcher_coin_record is not None all_spends: List[CoinSolution] = [] for reward_coin_record in reward_coin_records: found_block_index: Optional[uint32] = None for block_index in range( reward_coin_record.confirmed_block_index, reward_coin_record.confirmed_block_index - 100, -1): if block_index < 0: break pool_parent = pool_parent_id(uint32(block_index), genesis_challenge) if pool_parent == reward_coin_record.coin.parent_coin_info: found_block_index = uint32(block_index) if not found_block_index: # The puzzle does not allow spending coins that are not a coinbase reward log.info( f"Received reward {reward_coin_record.coin} that is not a pool reward." ) continue absorb_spend: List[CoinSolution] = create_absorb_spend( last_solution, last_state, launcher_coin_record.coin, found_block_index, genesis_challenge, farmer_record.delay_time, farmer_record.delay_puzzle_hash, ) last_solution = absorb_spend[0] all_spends += absorb_spend # TODO(pool): handle the case where the cost exceeds the size of the block return SpendBundle(all_spends, G2Element())
async def coins_of_interest_added( self, coins: List[Coin], height: uint32 ) -> Tuple[List[Coin], List[WalletCoinRecord]]: ( trade_removals, trade_additions, ) = await self.trade_manager.get_coins_of_interest() trade_adds: List[Coin] = [] block: Optional[BlockRecord] = await self.blockchain.get_block_record_from_db( self.blockchain.height_to_hash(height) ) assert block is not None pool_rewards = set() farmer_rewards = set() added = [] prev = await self.blockchain.get_block_record_from_db(block.prev_hash) # [block 1] [block 2] [tx block 3] [block 4] [block 5] [tx block 6] # [tx block 6] will contain rewards for [block 1] [block 2] [tx block 3] while prev is not None: # step 1 find previous block if prev.is_transaction_block: break prev = await self.blockchain.get_block_record_from_db(prev.prev_hash) if prev is not None: # include last block pool_parent = pool_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) farmer_parent = farmer_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) pool_rewards.add(pool_parent) farmer_rewards.add(farmer_parent) prev = await self.blockchain.get_block_record_from_db(prev.prev_hash) while prev is not None: # step 2 traverse from previous block to the block before it pool_parent = pool_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) farmer_parent = farmer_parent_id(uint32(prev.height), self.constants.GENESIS_CHALLENGE) pool_rewards.add(pool_parent) farmer_rewards.add(farmer_parent) if prev.is_transaction_block: break prev = await self.blockchain.get_block_record_from_db(prev.prev_hash) wallet_ids: Set[int] = set() for coin in coins: info = await self.puzzle_store.wallet_info_for_puzzle_hash(coin.puzzle_hash) if info is not None: wallet_ids.add(info[0]) all_outgoing_tx: Dict[int, List[TransactionRecord]] = {} for wallet_id in wallet_ids: all_outgoing_tx[wallet_id] = await self.tx_store.get_all_transactions_for_wallet( wallet_id, TransactionType.OUTGOING_TX ) for coin in coins: if coin.name() in trade_additions: trade_adds.append(coin) is_coinbase = False is_fee_reward = False if coin.parent_coin_info in pool_rewards: is_coinbase = True if coin.parent_coin_info in farmer_rewards: is_fee_reward = True info = await self.puzzle_store.wallet_info_for_puzzle_hash(coin.puzzle_hash) if info is not None: wallet_id, wallet_type = info added_coin_record = await self.coin_added( coin, is_coinbase, is_fee_reward, uint32(wallet_id), wallet_type, height, all_outgoing_tx[wallet_id] ) added.append(added_coin_record) derivation_index = await self.puzzle_store.index_for_puzzle_hash(coin.puzzle_hash) if derivation_index is not None: await self.puzzle_store.set_used_up_to(derivation_index, True) return trade_adds, added
async def create_absorb_transaction( self, farmer_record: FarmerRecord, singleton_coin: Coin, reward_coin_records: List[CoinRecord]) -> SpendBundle: # We assume that the farmer record singleton state is updated to the latest escape_inner_puzzle: Program = POOL_ESCAPING_MOD.curry( farmer_record.pool_puzzle_hash, self.relative_lock_height, bytes(farmer_record.owner_public_key), farmer_record.p2_singleton_puzzle_hash, ) committed_inner_puzzle: Program = POOL_COMMITED_MOD.curry( farmer_record.pool_puzzle_hash, escape_inner_puzzle.get_tree_hash(), farmer_record.p2_singleton_puzzle_hash, bytes(farmer_record.owner_public_key), ) aggregate_spend_bundle: SpendBundle = SpendBundle([], G2Element()) for reward_coin_record in reward_coin_records: found_block_index: Optional[uint32] = None for block_index in range( reward_coin_record.confirmed_block_index, reward_coin_record.confirmed_block_index - 100, -1): if block_index < 0: break pool_parent = pool_parent_id(uint32(block_index), self.constants.GENESIS_CHALLENGE) if pool_parent == reward_coin_record.coin.parent_coin_info: found_block_index = uint32(block_index) if not found_block_index: self.log.info( f"Received reward {reward_coin_record.coin} that is not a pool reward." ) singleton_full = SINGLETON_MOD.curry( singleton_mod_hash, farmer_record.singleton_genesis, committed_inner_puzzle) inner_sol = Program.to([ 0, singleton_full.get_tree_hash(), singleton_coin.amount, reward_coin_record.amount, found_block_index ]) full_sol = Program.to([ farmer_record.singleton_genesis, singleton_coin.amount, inner_sol ]) new_spend = SpendBundle( [ CoinSolution( singleton_coin, SerializedProgram.from_bytes(bytes(singleton_full)), full_sol) ], G2Element(), ) # TODO(pool): handle the case where the cost exceeds the size of the block aggregate_spend_bundle = SpendBundle.aggregate( [aggregate_spend_bundle, new_spend]) singleton_coin = await self.get_next_singleton_coin(new_spend) cost, result = singleton_full.run_with_cost( INFINITE_COST, full_sol) self.log.info(f"Cost: {cost}, result {result}") return aggregate_spend_bundle
async def create_absorb_transaction( node_rpc_client: FullNodeRpcClient, farmer_record: FarmerRecord, peak_height: uint32, reward_coin_records: List[CoinRecord], genesis_challenge: bytes32, ) -> Optional[SpendBundle]: singleton_state_tuple: Optional[Tuple[ CoinSolution, PoolState, PoolState]] = await get_singleton_state(node_rpc_client, farmer_record.launcher_id, farmer_record, peak_height, 0, genesis_challenge) if singleton_state_tuple is None: log.info(f"Invalid singleton {farmer_record.launcher_id}.") return None last_solution, last_state, last_state_2 = singleton_state_tuple # Here the buried state is equivalent to the latest state, because we use 0 as the security_threshold assert last_state == last_state_2 if last_state.state == PoolSingletonState.SELF_POOLING: log.info( f"Don't try to absorb from former farmer {farmer_record.launcher_id}." ) return None launcher_coin_record: Optional[ CoinRecord] = await node_rpc_client.get_coin_record_by_name( farmer_record.launcher_id) assert launcher_coin_record is not None all_spends: List[CoinSolution] = [] for reward_coin_record in reward_coin_records: found_block_index: Optional[uint32] = None for block_index in range( reward_coin_record.confirmed_block_index, reward_coin_record.confirmed_block_index - 100, -1): if block_index < 0: break pool_parent = pool_parent_id(uint32(block_index), genesis_challenge) if pool_parent == reward_coin_record.coin.parent_coin_info: found_block_index = uint32(block_index) if not found_block_index: # The puzzle does not allow spending coins that are not a coinbase reward log.info( f"Received reward {reward_coin_record.coin} that is not a pool reward." ) continue absorb_spend: List[CoinSolution] = create_absorb_spend( last_solution, last_state, launcher_coin_record.coin, found_block_index, genesis_challenge, farmer_record.delay_time, farmer_record.delay_puzzle_hash, ) last_solution = absorb_spend[0] all_spends += absorb_spend # TODO(pool): handle the case where the cost exceeds the size of the block # TODO(pool): If you want to add a fee, you should do the following: # - only absorb one reward at a time # - spend the coin that you are receiving in the same spend bundle that it is created # - create an output with slightly less XCH, to yourself. for example, 1.7499 XCH # - The remaining value will automatically be used as a fee if len(all_spends) == 0: return None return SpendBundle(all_spends, G2Element())
def create_absorb_spend( last_coin_spend: CoinSpend, current_state: PoolState, launcher_coin: Coin, height: uint32, genesis_challenge: bytes32, delay_time: uint64, delay_ph: bytes32, ) -> List[CoinSpend]: inner_puzzle: Program = pool_state_to_inner_puzzle(current_state, launcher_coin.name(), genesis_challenge, delay_time, delay_ph) reward_amount: uint64 = calculate_pool_reward(height) if is_pool_member_inner_puzzle(inner_puzzle): # inner sol is (spend_type, pool_reward_amount, pool_reward_height, extra_data) inner_sol: Program = Program.to([reward_amount, height]) elif is_pool_waitingroom_inner_puzzle(inner_puzzle): # inner sol is (spend_type, destination_puzhash, pool_reward_amount, pool_reward_height, extra_data) inner_sol = Program.to([0, reward_amount, height]) else: raise ValueError # full sol = (parent_info, my_amount, inner_solution) coin: Optional[Coin] = get_most_recent_singleton_coin_from_coin_spend( last_coin_spend) assert coin is not None if coin.parent_coin_info == launcher_coin.name(): parent_info: Program = Program.to( [launcher_coin.parent_coin_info, launcher_coin.amount]) else: p = Program.from_bytes(bytes(last_coin_spend.puzzle_reveal)) last_coin_spend_inner_puzzle: Optional[ Program] = get_inner_puzzle_from_puzzle(p) assert last_coin_spend_inner_puzzle is not None parent_info = Program.to([ last_coin_spend.coin.parent_coin_info, last_coin_spend_inner_puzzle.get_tree_hash(), last_coin_spend.coin.amount, ]) full_solution: SerializedProgram = SerializedProgram.from_program( Program.to([parent_info, last_coin_spend.coin.amount, inner_sol])) full_puzzle: SerializedProgram = SerializedProgram.from_program( create_full_puzzle(inner_puzzle, launcher_coin.name())) assert coin.puzzle_hash == full_puzzle.get_tree_hash() reward_parent: bytes32 = pool_parent_id(height, genesis_challenge) p2_singleton_puzzle: SerializedProgram = SerializedProgram.from_program( create_p2_singleton_puzzle(SINGLETON_MOD_HASH, launcher_coin.name(), delay_time, delay_ph)) reward_coin: Coin = Coin(reward_parent, p2_singleton_puzzle.get_tree_hash(), reward_amount) p2_singleton_solution: SerializedProgram = SerializedProgram.from_program( Program.to([inner_puzzle.get_tree_hash(), reward_coin.name()])) assert p2_singleton_puzzle.get_tree_hash() == reward_coin.puzzle_hash assert full_puzzle.get_tree_hash() == coin.puzzle_hash assert get_inner_puzzle_from_puzzle(Program.from_bytes( bytes(full_puzzle))) is not None coin_spends = [ CoinSpend(coin, full_puzzle, full_solution), CoinSpend(reward_coin, p2_singleton_puzzle, p2_singleton_solution), ] return coin_spends