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
Example #2
0
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
Example #3
0
 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
Example #4
0
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())
Example #5
0
    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
Example #6
0
    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
Example #7
0
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