コード例 #1
0
ファイル: pool_wallet.py プロジェクト: rongou/chia-blockchain
    async def get_current_state(self) -> PoolWalletInfo:
        history: List[Tuple[uint32,
                            CoinSpend]] = await self.get_spend_history()
        all_spends: List[CoinSpend] = [cs for _, cs in history]

        # We must have at least the launcher spend
        assert len(all_spends) >= 1

        launcher_coin: Coin = all_spends[0].coin
        delayed_seconds, delayed_puzhash = get_delayed_puz_info_from_launcher_spend(
            all_spends[0])
        tip_singleton_coin: Optional[
            Coin] = get_most_recent_singleton_coin_from_coin_spend(
                all_spends[-1])
        launcher_id: bytes32 = launcher_coin.name()
        p2_singleton_puzzle_hash = launcher_id_to_p2_puzzle_hash(
            launcher_id, delayed_seconds, delayed_puzhash)
        assert tip_singleton_coin is not None

        curr_spend_i = len(all_spends) - 1
        extra_data: Optional[PoolState] = None
        last_singleton_spend_height = uint32(0)
        while extra_data is None:
            full_spend: CoinSpend = all_spends[curr_spend_i]
            extra_data = solution_to_extra_data(full_spend)
            last_singleton_spend_height = uint32(history[curr_spend_i][0])
            curr_spend_i -= 1

        assert extra_data is not None
        current_inner = pool_state_to_inner_puzzle(
            extra_data,
            launcher_coin.name(),
            self.wallet_state_manager.constants.GENESIS_CHALLENGE,
            delayed_seconds,
            delayed_puzhash,
        )
        return PoolWalletInfo(
            extra_data,
            self.target_state,
            launcher_coin,
            launcher_id,
            p2_singleton_puzzle_hash,
            current_inner,
            tip_singleton_coin.name(),
            last_singleton_spend_height,
        )
コード例 #2
0
    def test_pool_lifecycle(self):
        # START TESTS
        # Generate starting info
        key_lookup = KeyTool()
        sk: PrivateKey = PrivateKey.from_bytes(
            secret_exponent_for_index(1).to_bytes(32, "big"), )
        pk: G1Element = G1Element.from_bytes(
            public_key_for_index(1, key_lookup))
        starting_puzzle: Program = puzzle_for_pk(pk)
        starting_ph: bytes32 = starting_puzzle.get_tree_hash()

        # Get our starting standard coin created
        START_AMOUNT: uint64 = 1023
        coin_db = CoinStore()
        time = CoinTimestamp(10000000, 1)
        coin_db.farm_coin(starting_ph, time, START_AMOUNT)
        starting_coin: Coin = next(coin_db.all_unspent_coins())

        # LAUNCHING
        # Create the escaping inner puzzle
        GENESIS_CHALLENGE = bytes32.fromhex(
            "ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb")
        launcher_coin = singleton_top_layer.generate_launcher_coin(
            starting_coin,
            START_AMOUNT,
        )
        DELAY_TIME = uint64(60800)
        DELAY_PH = starting_ph
        launcher_id = launcher_coin.name()
        relative_lock_height: uint32 = uint32(5000)
        # use a dummy pool state
        pool_state = PoolState(
            owner_pubkey=pk,
            pool_url="",
            relative_lock_height=relative_lock_height,
            state=3,  # farming to pool
            target_puzzle_hash=starting_ph,
            version=1,
        )
        # create a new dummy pool state for travelling
        target_pool_state = PoolState(
            owner_pubkey=pk,
            pool_url="",
            relative_lock_height=relative_lock_height,
            state=2,  # Leaving pool
            target_puzzle_hash=starting_ph,
            version=1,
        )
        # Standard format comment
        comment = Program.to([("p", bytes(pool_state)), ("t", DELAY_TIME),
                              ("h", DELAY_PH)])
        pool_wr_innerpuz: bytes32 = create_waiting_room_inner_puzzle(
            starting_ph,
            relative_lock_height,
            pk,
            launcher_id,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,
        )
        pool_wr_inner_hash = pool_wr_innerpuz.get_tree_hash()
        pooling_innerpuz: Program = create_pooling_inner_puzzle(
            starting_ph,
            pool_wr_inner_hash,
            pk,
            launcher_id,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,
        )
        # Driver tests
        assert is_pool_singleton_inner_puzzle(pooling_innerpuz)
        assert is_pool_singleton_inner_puzzle(pool_wr_innerpuz)
        assert get_pubkey_from_member_inner_puzzle(pooling_innerpuz) == pk
        # Generating launcher information
        conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol(
            starting_coin, pooling_innerpuz, comment, START_AMOUNT)
        # Creating solution for standard transaction
        delegated_puzzle: Program = puzzle_for_conditions(conditions)
        full_solution: Program = solution_for_conditions(conditions)
        starting_coinsol = CoinSolution(
            starting_coin,
            starting_puzzle,
            full_solution,
        )
        # Create the spend bundle
        sig: G2Element = sign_delegated_puz(delegated_puzzle, starting_coin)
        spend_bundle = SpendBundle(
            [starting_coinsol, launcher_coinsol],
            sig,
        )
        # Spend it!
        coin_db.update_coin_store_for_spend_bundle(
            spend_bundle,
            time,
            DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
        )
        # Test that we can retrieve the extra data
        assert get_delayed_puz_info_from_launcher_spend(launcher_coinsol) == (
            DELAY_TIME, DELAY_PH)
        assert solution_to_extra_data(launcher_coinsol) == pool_state

        # TEST TRAVEL AFTER LAUNCH
        # fork the state
        fork_coin_db: CoinStore = copy.deepcopy(coin_db)
        post_launch_coinsol, _ = create_travel_spend(
            launcher_coinsol,
            launcher_coin,
            pool_state,
            target_pool_state,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,
        )
        # Spend it!
        fork_coin_db.update_coin_store_for_spend_bundle(
            SpendBundle([post_launch_coinsol], G2Element()),
            time,
            DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
        )

        # HONEST ABSORB
        time = CoinTimestamp(10000030, 2)
        # create the farming reward
        p2_singleton_puz: Program = create_p2_singleton_puzzle(
            SINGLETON_MOD_HASH,
            launcher_id,
            DELAY_TIME,
            DELAY_PH,
        )
        p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash()
        assert uncurry_pool_waitingroom_inner_puzzle(pool_wr_innerpuz) == (
            starting_ph,
            relative_lock_height,
            pk,
            p2_singleton_ph,
        )
        assert launcher_id_to_p2_puzzle_hash(launcher_id, DELAY_TIME,
                                             DELAY_PH) == p2_singleton_ph
        assert get_seconds_and_delayed_puzhash_from_p2_singleton_puzzle(
            p2_singleton_puz) == (DELAY_TIME, DELAY_PH)
        coin_db.farm_coin(p2_singleton_ph, time, 1750000000000)
        coin_sols: List[CoinSolution] = create_absorb_spend(
            launcher_coinsol,
            pool_state,
            launcher_coin,
            2,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,  # height
        )
        # Spend it!
        coin_db.update_coin_store_for_spend_bundle(
            SpendBundle(coin_sols, G2Element()),
            time,
            DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
        )

        # ABSORB A NON EXISTENT REWARD (Negative test)
        last_coinsol: CoinSolution = list(
            filter(
                lambda e: e.coin.amount == START_AMOUNT,
                coin_sols,
            ))[0]
        coin_sols: List[CoinSolution] = create_absorb_spend(
            last_coinsol,
            pool_state,
            launcher_coin,
            2,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,  # height
        )
        # filter for only the singleton solution
        singleton_coinsol: CoinSolution = list(
            filter(
                lambda e: e.coin.amount == START_AMOUNT,
                coin_sols,
            ))[0]
        # Spend it and hope it fails!
        try:
            coin_db.update_coin_store_for_spend_bundle(
                SpendBundle([singleton_coinsol], G2Element()),
                time,
                DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
            )
        except BadSpendBundleError as e:
            assert str(
                e
            ) == "condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED"

        # SPEND A NON-REWARD P2_SINGLETON (Negative test)
        # create the dummy coin
        non_reward_p2_singleton = Coin(
            bytes32(32 * b"3"),
            p2_singleton_ph,
            uint64(1337),
        )
        coin_db._add_coin_entry(non_reward_p2_singleton, time)
        # construct coin solution for the p2_singleton coin
        bad_coinsol = CoinSolution(
            non_reward_p2_singleton,
            p2_singleton_puz,
            Program.to([
                pooling_innerpuz.get_tree_hash(),
                non_reward_p2_singleton.name(),
            ]),
        )
        # Spend it and hope it fails!
        try:
            coin_db.update_coin_store_for_spend_bundle(
                SpendBundle([singleton_coinsol, bad_coinsol], G2Element()),
                time,
                DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
            )
        except BadSpendBundleError as e:
            assert str(
                e
            ) == "condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED"

        # ENTER WAITING ROOM
        # find the singleton
        singleton = get_most_recent_singleton_coin_from_coin_solution(
            last_coinsol)
        # get the relevant coin solution
        travel_coinsol, _ = create_travel_spend(
            last_coinsol,
            launcher_coin,
            pool_state,
            target_pool_state,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,
        )
        # Test that we can retrieve the extra data
        assert solution_to_extra_data(travel_coinsol) == target_pool_state
        # sign the serialized state
        data = Program.to(bytes(target_pool_state)).get_tree_hash()
        sig: G2Element = AugSchemeMPL.sign(
            sk,
            (data + singleton.name() +
             DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA),
        )
        # Spend it!
        coin_db.update_coin_store_for_spend_bundle(
            SpendBundle([travel_coinsol], sig),
            time,
            DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
        )

        # ESCAPE TOO FAST (Negative test)
        # find the singleton
        singleton = get_most_recent_singleton_coin_from_coin_solution(
            travel_coinsol)
        # get the relevant coin solution
        return_coinsol, _ = create_travel_spend(
            travel_coinsol,
            launcher_coin,
            target_pool_state,
            pool_state,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,
        )
        # sign the serialized target state
        sig = AugSchemeMPL.sign(
            sk,
            (data + singleton.name() +
             DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA),
        )
        # Spend it and hope it fails!
        try:
            coin_db.update_coin_store_for_spend_bundle(
                SpendBundle([return_coinsol], sig),
                time,
                DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
            )
        except BadSpendBundleError as e:
            assert str(
                e
            ) == "condition validation failure Err.ASSERT_HEIGHT_RELATIVE_FAILED"

        # ABSORB WHILE IN WAITING ROOM
        time = CoinTimestamp(10000060, 3)
        # create the farming reward
        coin_db.farm_coin(p2_singleton_ph, time, 1750000000000)
        # generate relevant coin solutions
        coin_sols: List[CoinSolution] = create_absorb_spend(
            travel_coinsol,
            target_pool_state,
            launcher_coin,
            3,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,  # height
        )
        # Spend it!
        coin_db.update_coin_store_for_spend_bundle(
            SpendBundle(coin_sols, G2Element()),
            time,
            DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
        )

        # LEAVE THE WAITING ROOM
        time = CoinTimestamp(20000000, 10000)
        # find the singleton
        singleton_coinsol: CoinSolution = list(
            filter(
                lambda e: e.coin.amount == START_AMOUNT,
                coin_sols,
            ))[0]
        singleton: Coin = get_most_recent_singleton_coin_from_coin_solution(
            singleton_coinsol)
        # get the relevant coin solution
        return_coinsol, _ = create_travel_spend(
            singleton_coinsol,
            launcher_coin,
            target_pool_state,
            pool_state,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,
        )
        # Test that we can retrieve the extra data
        assert solution_to_extra_data(return_coinsol) == pool_state
        # sign the serialized target state
        data = Program.to([
            pooling_innerpuz.get_tree_hash(), START_AMOUNT,
            bytes(pool_state)
        ]).get_tree_hash()
        sig: G2Element = AugSchemeMPL.sign(
            sk,
            (data + singleton.name() +
             DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA),
        )
        # Spend it!
        coin_db.update_coin_store_for_spend_bundle(
            SpendBundle([return_coinsol], sig),
            time,
            DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
        )

        # ABSORB ONCE MORE FOR GOOD MEASURE
        time = CoinTimestamp(20000000, 10005)
        # create the farming  reward
        coin_db.farm_coin(p2_singleton_ph, time, 1750000000000)
        coin_sols: List[CoinSolution] = create_absorb_spend(
            return_coinsol,
            pool_state,
            launcher_coin,
            10005,
            GENESIS_CHALLENGE,
            DELAY_TIME,
            DELAY_PH,  # height
        )
        # Spend it!
        coin_db.update_coin_store_for_spend_bundle(
            SpendBundle(coin_sols, G2Element()),
            time,
            DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
        )
コード例 #3
0
ファイル: pool_wallet.py プロジェクト: lyrl/chia-blockchain
    async def create_new_pool_wallet_transaction(
        wallet_state_manager: Any,
        main_wallet: Wallet,
        initial_target_state: PoolState,
        fee: uint64 = uint64(0),
        p2_singleton_delay_time: Optional[uint64] = None,
        p2_singleton_delayed_ph: Optional[bytes32] = None,
    ) -> Tuple[TransactionRecord, bytes32, bytes32]:
        """
        A "plot NFT", or pool wallet, represents the idea of a set of plots that all pay to
        the same pooling puzzle. This puzzle is a `chia singleton` that is
        parameterized with a public key controlled by the user's wallet
        (a `smart coin`). It contains an inner puzzle that can switch between
        paying block rewards to a pool, or to a user's own wallet.

        Call under the wallet state manger lock
        """
        amount = 1
        standard_wallet = main_wallet

        if p2_singleton_delayed_ph is None:
            p2_singleton_delayed_ph = await main_wallet.get_new_puzzlehash()
        if p2_singleton_delay_time is None:
            p2_singleton_delay_time = uint64(604800)

        unspent_records = await wallet_state_manager.coin_store.get_unspent_coins_for_wallet(standard_wallet.wallet_id)
        balance = await standard_wallet.get_confirmed_balance(unspent_records)
        if balance < PoolWallet.MINIMUM_INITIAL_BALANCE:
            raise ValueError("Not enough balance in main wallet to create a managed plotting pool.")
        if balance < fee:
            raise ValueError("Not enough balance in main wallet to create a managed plotting pool with fee {fee}.")

        # Verify Parameters - raise if invalid
        PoolWallet._verify_initial_target_state(initial_target_state)

        spend_bundle, singleton_puzzle_hash, launcher_coin_id = await PoolWallet.generate_launcher_spend(
            standard_wallet,
            uint64(1),
            initial_target_state,
            wallet_state_manager.constants.GENESIS_CHALLENGE,
            p2_singleton_delay_time,
            p2_singleton_delayed_ph,
        )

        if spend_bundle is None:
            raise ValueError("failed to generate ID for wallet")

        standard_wallet_record = TransactionRecord(
            confirmed_at_height=uint32(0),
            created_at_time=uint64(int(time.time())),
            to_puzzle_hash=singleton_puzzle_hash,
            amount=uint64(amount),
            fee_amount=fee,
            confirmed=False,
            sent=uint32(0),
            spend_bundle=spend_bundle,
            additions=spend_bundle.additions(),
            removals=spend_bundle.removals(),
            wallet_id=wallet_state_manager.main_wallet.id(),
            sent_to=[],
            memos=[],
            trade_id=None,
            type=uint32(TransactionType.OUTGOING_TX.value),
            name=spend_bundle.name(),
        )
        await standard_wallet.push_transaction(standard_wallet_record)
        p2_singleton_puzzle_hash: bytes32 = launcher_id_to_p2_puzzle_hash(
            launcher_coin_id, p2_singleton_delay_time, p2_singleton_delayed_ph
        )
        return standard_wallet_record, p2_singleton_puzzle_hash, launcher_coin_id
コード例 #4
0
    async def add_farmer(self, request: PostFarmerRequest) -> Dict:
        async with self.store.lock:
            farmer_record: Optional[
                FarmerRecord] = await self.store.get_farmer_record(
                    request.payload.launcher_id)
            if farmer_record is not None:
                return error_dict(
                    PoolErrorCode.FARMER_ALREADY_KNOWN,
                    f"Farmer with launcher_id {request.payload.launcher_id} already known.",
                )

            singleton_state_tuple: Optional[Tuple[
                CoinSolution,
                PoolState]] = await self.get_and_validate_singleton_state(
                    request.payload.launcher_id)

            if singleton_state_tuple is None:
                return error_dict(PoolErrorCode.INVALID_SINGLETON,
                                  f"Invalid singleton, or not a pool member")

            last_spend, last_state = singleton_state_tuple

            if (request.payload.suggested_difficulty is None
                    or request.payload.suggested_difficulty <
                    self.min_difficulty):
                difficulty: uint64 = self.default_difficulty
            else:
                difficulty = request.payload.suggested_difficulty

            if len(hexstr_to_bytes(request.payload.payout_instructions)) != 32:
                return error_dict(
                    PoolErrorCode.INVALID_PAYOUT_INSTRUCTIONS,
                    f"Payout instructions must be an xch address for this pool.",
                )

            if not AugSchemeMPL.verify(last_state.owner_pubkey,
                                       request.payload.get_hash(),
                                       request.signature):
                return error_dict(PoolErrorCode.INVALID_SIGNATURE,
                                  f"Invalid signature")

            launcher_coin: Optional[
                CoinRecord] = await self.node_rpc_client.get_coin_record_by_name(
                    request.payload.launcher_id)
            assert launcher_coin is not None and launcher_coin.spent

            launcher_solution: Optional[CoinSolution] = await get_coin_spend(
                self.node_rpc_client, launcher_coin)
            delay_time, delay_puzzle_hash = get_delayed_puz_info_from_launcher_spend(
                launcher_solution)

            if delay_time < 3600:
                return error_dict(
                    PoolErrorCode.DELAY_TIME_TOO_SHORT,
                    f"Delay time too short, must be at least 1 hour")

            p2_singleton_puzzle_hash = launcher_id_to_p2_puzzle_hash(
                request.payload.launcher_id, delay_time, delay_puzzle_hash)

            farmer_record = FarmerRecord(
                request.payload.launcher_id,
                p2_singleton_puzzle_hash,
                delay_time,
                delay_puzzle_hash,
                request.payload.authentication_public_key,
                last_spend,
                last_state,
                uint64(0),
                difficulty,
                request.payload.payout_instructions,
                True,
            )
            self.scan_p2_singleton_puzzle_hashes.add(p2_singleton_puzzle_hash)
            await self.store.add_farmer_record(farmer_record)

            return PostFarmerResponse(self.welcome_message).to_json_dict()