Пример #1
0
    async def update_farmer(self, request: PutFarmerRequest) -> Dict:
        farmer_record: Optional[
            FarmerRecord] = await self.store.get_farmer_record(
                request.payload.launcher_id)
        if farmer_record is None:
            return error_dict(
                PoolErrorCode.FARMER_NOT_KNOWN,
                f"Farmer with launcher_id {request.payload.launcher_id} not known."
            )

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

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

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

        farmer_dict = farmer_record.to_json_dict()
        response_dict = {}
        if request.payload.authentication_public_key is not None:
            is_new_value = farmer_record.authentication_public_key != request.payload.authentication_public_key
            response_dict["authentication_public_key"] = is_new_value
            if is_new_value:
                farmer_dict[
                    "authentication_public_key"] = request.payload.authentication_public_key

        if request.payload.payout_instructions is not None:
            is_new_value = (
                farmer_record.payout_instructions !=
                request.payload.payout_instructions
                and request.payload.payout_instructions is not None
                and len(hexstr_to_bytes(
                    request.payload.payout_instructions)) == 32)
            response_dict["payout_instructions"] = is_new_value
            if is_new_value:
                farmer_dict[
                    "payout_instructions"] = request.payload.payout_instructions

        if request.payload.suggested_difficulty is not None:
            is_new_value = (
                farmer_record.suggested_difficulty !=
                request.payload.suggested_difficulty
                and request.payload.suggested_difficulty is not None and
                request.payload.suggested_difficulty >= self.min_difficulty)
            response_dict["suggested_difficulty"] = is_new_value
            if is_new_value:
                farmer_dict[
                    "suggested_difficulty"] = request.payload.suggested_difficulty

        self.log.info(f"Updated farmer: {response_dict}")
        await self.store.add_farmer_record(
            FarmerRecord.from_json_dict(farmer_dict))

        return PutFarmerResponse.from_json_dict(response_dict).from_json_dict()
Пример #2
0
    async def process_partial(
        self,
        partial: PostPartialRequest,
        farmer_record: FarmerRecord,
        time_received_partial: uint64,
    ) -> Dict:
        # Validate signatures
        message: bytes32 = partial.payload.get_hash()
        pk1: G1Element = partial.payload.proof_of_space.plot_public_key
        pk2: G1Element = farmer_record.authentication_public_key
        valid_sig = AugSchemeMPL.aggregate_verify([pk1, pk2],
                                                  [message, message],
                                                  partial.aggregate_signature)
        if not valid_sig:
            return error_dict(
                PoolErrorCode.INVALID_SIGNATURE,
                f"The aggregate signature is invalid {partial.aggregate_signature}",
            )

        # TODO (chia-dev): Check DB p2_singleton_puzzle_hash and compare
        # if partial.payload.proof_of_space.pool_contract_puzzle_hash != p2_singleton_puzzle_hash:
        #     return error_dict(
        #       PoolErrorCode.INVALID_P2_SINGLETON_PUZZLE_HASH,
        #       f"Invalid plot pool contract puzzle hash {partial.payload.proof_of_space.pool_contract_puzzle_hash}"
        #     )

        async def get_signage_point_or_eos():
            if partial.payload.end_of_sub_slot:
                return await self.node_rpc_client.get_recent_signage_point_or_eos(
                    None, partial.payload.sp_hash)
            else:
                return await self.node_rpc_client.get_recent_signage_point_or_eos(
                    partial.payload.sp_hash, None)

        response = await get_signage_point_or_eos()
        if response is None:
            # Try again after 10 seconds in case we just didn't yet receive the signage point
            await asyncio.sleep(10)
            response = await get_signage_point_or_eos()

        if response is None or response["reverted"]:
            return error_dict(
                PoolErrorCode.NOT_FOUND,
                f"Did not find signage point or EOS {partial.payload.sp_hash}, {response}"
            )
        node_time_received_sp = response["time_received"]

        signage_point: Optional[SignagePoint] = response.get(
            "signage_point", None)
        end_of_sub_slot: Optional[EndOfSubSlotBundle] = response.get(
            "eos", None)

        if time_received_partial - node_time_received_sp > self.partial_time_limit:
            return error_dict(
                PoolErrorCode.TOO_LATE,
                f"Received partial in {time_received_partial - node_time_received_sp}. "
                f"Make sure your proof of space lookups are fast, and network connectivity is good."
                f"Response must happen in less than {self.partial_time_limit} seconds. NAS or network"
                f" farming can be an issue",
            )

        # Validate the proof
        if signage_point is not None:
            challenge_hash: bytes32 = signage_point.cc_vdf.challenge
        else:
            challenge_hash = end_of_sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf.get_hash(
            )

        quality_string: Optional[
            bytes32] = partial.payload.proof_of_space.verify_and_get_quality_string(
                self.constants, challenge_hash, partial.payload.sp_hash)
        if quality_string is None:
            return error_dict(
                PoolErrorCode.INVALID_PROOF,
                f"Invalid proof of space {partial.payload.sp_hash}")

        current_difficulty = farmer_record.difficulty
        required_iters: uint64 = calculate_iterations_quality(
            self.constants.DIFFICULTY_CONSTANT_FACTOR,
            quality_string,
            partial.payload.proof_of_space.size,
            current_difficulty,
            partial.payload.sp_hash,
        )

        if required_iters >= self.iters_limit:
            return error_dict(
                PoolErrorCode.PROOF_NOT_GOOD_ENOUGH,
                f"Proof of space has required iters {required_iters}, too high for difficulty "
                f"{current_difficulty}",
            )

        await self.pending_point_partials.put(
            (partial, time_received_partial, current_difficulty))

        async with self.store.lock:
            # Obtains the new record in case we just updated difficulty
            farmer_record: Optional[
                FarmerRecord] = await self.store.get_farmer_record(
                    partial.payload.launcher_id)
            if farmer_record is not None:
                current_difficulty = farmer_record.difficulty
                # Decide whether to update the difficulty
                recent_partials = await self.store.get_recent_partials(
                    partial.payload.launcher_id,
                    self.number_of_partials_target)
                # Only update the difficulty if we meet certain conditions
                new_difficulty: uint64 = get_new_difficulty(
                    recent_partials,
                    int(self.number_of_partials_target),
                    int(self.time_target),
                    current_difficulty,
                    time_received_partial,
                    self.min_difficulty,
                )

                if current_difficulty != new_difficulty:
                    await self.store.update_difficulty(
                        partial.payload.launcher_id, new_difficulty)

        return PostPartialResponse(current_difficulty).to_json_dict()
Пример #3
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()