Пример #1
0
    async def generate_login_link(self, launcher_id: bytes32) -> Optional[str]:
        for pool_state in self.pool_state.values():
            pool_config: PoolWalletConfig = pool_state["pool_config"]
            if pool_config.launcher_id == launcher_id:
                authentication_sk: Optional[PrivateKey] = await find_authentication_sk(
                    self.all_root_sks, pool_config.authentication_public_key
                )
                if authentication_sk is None:
                    self.log.error(f"Could not find authentication sk for pk: {pool_config.authentication_public_key}")
                    continue
                assert authentication_sk.get_g1() == pool_config.authentication_public_key
                authentication_token_timeout = pool_state["authentication_token_timeout"]
                authentication_token = get_current_authentication_token(authentication_token_timeout)
                message: bytes32 = std_hash(
                    AuthenticationPayload(
                        "get_login", pool_config.launcher_id, pool_config.target_puzzle_hash, authentication_token
                    )
                )
                signature: G2Element = AugSchemeMPL.sign(authentication_sk, message)
                return (
                    pool_config.pool_url
                    + f"/login?launcher_id={launcher_id.hex()}&authentication_token={authentication_token}"
                    f"&signature={bytes(signature).hex()}"
                )

        return None
Пример #2
0
    async def _pool_post_farmer(self, pool_config: PoolWalletConfig,
                                authentication_token_timeout: uint8,
                                owner_sk: PrivateKey) -> Optional[Dict]:
        post_farmer_payload: PostFarmerPayload = PostFarmerPayload(
            pool_config.launcher_id,
            get_current_authentication_token(authentication_token_timeout),
            pool_config.authentication_public_key,
            pool_config.payout_instructions,
            None,
        )
        assert owner_sk.get_g1() == pool_config.owner_public_key
        signature: G2Element = AugSchemeMPL.sign(
            owner_sk, post_farmer_payload.get_hash())
        post_farmer_request = PostFarmerRequest(post_farmer_payload, signature)
        post_farmer_body = json.dumps(post_farmer_request.to_json_dict())

        headers = {
            "content-type": "application/json;",
        }
        try:
            async with aiohttp.ClientSession() as session:
                async with session.post(
                        f"{pool_config.pool_url}/farmer",
                        data=post_farmer_body,
                        headers=headers,
                        ssl=ssl_context_for_root(get_mozilla_ca_crt()),
                ) as resp:
                    if resp.ok:
                        response: Dict = json.loads(await resp.text())
                        self.log.info(f"POST /farmer response: {response}")
                        if "error_code" in response:
                            self.pool_state[
                                pool_config.p2_singleton_puzzle_hash][
                                    "pool_errors_24h"].append(response)
                        return response
                    else:
                        self.handle_failed_pool_response(
                            pool_config.p2_singleton_puzzle_hash,
                            f"Error in POST /farmer {pool_config.pool_url}, {resp.status}",
                        )
        except Exception as e:
            self.handle_failed_pool_response(
                pool_config.p2_singleton_puzzle_hash,
                f"Exception in POST /farmer {pool_config.pool_url}, {e}")
        return None
Пример #3
0
 async def _pool_get_farmer(
         self, pool_config: PoolWalletConfig,
         authentication_token_timeout: uint8,
         authentication_sk: PrivateKey) -> Optional[Dict]:
     assert authentication_sk.get_g1(
     ) == pool_config.authentication_public_key
     authentication_token = get_current_authentication_token(
         authentication_token_timeout)
     message: bytes32 = std_hash(
         AuthenticationPayload("get_farmer", pool_config.launcher_id,
                               pool_config.target_puzzle_hash,
                               authentication_token))
     signature: G2Element = AugSchemeMPL.sign(authentication_sk, message)
     get_farmer_params = {
         "launcher_id": pool_config.launcher_id.hex(),
         "authentication_token": authentication_token,
         "signature": bytes(signature).hex(),
     }
     try:
         async with aiohttp.ClientSession(trust_env=True) as session:
             async with session.get(
                     f"{pool_config.pool_url}/farmer",
                     params=get_farmer_params,
                     ssl=ssl_context_for_root(get_mozilla_ca_crt(),
                                              log=self.log),
             ) as resp:
                 if resp.ok:
                     response: Dict = json.loads(await resp.text())
                     self.log.info(f"GET /farmer response: {response}")
                     if "error_code" in response:
                         self.pool_state[
                             pool_config.p2_singleton_puzzle_hash][
                                 "pool_errors_24h"].append(response)
                     return response
                 else:
                     self.handle_failed_pool_response(
                         pool_config.p2_singleton_puzzle_hash,
                         f"Error in GET /farmer {pool_config.pool_url}, {resp.status}",
                     )
     except Exception as e:
         self.handle_failed_pool_response(
             pool_config.p2_singleton_puzzle_hash,
             f"Exception in GET /farmer {pool_config.pool_url}, {e}")
     return None
Пример #4
0
    async def new_proof_of_space(
        self, new_proof_of_space: harvester_protocol.NewProofOfSpace, peer: ws.WSChiaConnection
    ):
        """
        This is a response from the harvester, for a NewChallenge. Here we check if the proof
        of space is sufficiently good, and if so, we ask for the whole proof.
        """
        if new_proof_of_space.sp_hash not in self.farmer.number_of_responses:
            self.farmer.number_of_responses[new_proof_of_space.sp_hash] = 0
            self.farmer.cache_add_time[new_proof_of_space.sp_hash] = uint64(int(time.time()))

        max_pos_per_sp = 5
        if self.farmer.number_of_responses[new_proof_of_space.sp_hash] > max_pos_per_sp:
            # This will likely never happen for any farmer with less than 10% of global space
            # It's meant to make testnets more stable
            self.farmer.log.info(
                f"Surpassed {max_pos_per_sp} PoSpace for one SP, no longer submitting PoSpace for signage point "
                f"{new_proof_of_space.sp_hash}"
            )
            return None

        if new_proof_of_space.sp_hash not in self.farmer.sps:
            self.farmer.log.warning(
                f"Received response for a signage point that we do not have {new_proof_of_space.sp_hash}"
            )
            return None

        sps = self.farmer.sps[new_proof_of_space.sp_hash]
        for sp in sps:
            computed_quality_string = new_proof_of_space.proof.verify_and_get_quality_string(
                self.farmer.constants,
                new_proof_of_space.challenge_hash,
                new_proof_of_space.sp_hash,
            )
            if computed_quality_string is None:
                self.farmer.log.error(f"Invalid proof of space {new_proof_of_space.proof}")
                return None

            self.farmer.number_of_responses[new_proof_of_space.sp_hash] += 1

            required_iters: uint64 = calculate_iterations_quality(
                self.farmer.constants.DIFFICULTY_CONSTANT_FACTOR,
                computed_quality_string,
                new_proof_of_space.proof.size,
                sp.difficulty,
                new_proof_of_space.sp_hash,
            )

            # If the iters are good enough to make a block, proceed with the block making flow
            if required_iters < calculate_sp_interval_iters(self.farmer.constants, sp.sub_slot_iters):
                # Proceed at getting the signatures for this PoSpace
                request = harvester_protocol.RequestSignatures(
                    new_proof_of_space.plot_identifier,
                    new_proof_of_space.challenge_hash,
                    new_proof_of_space.sp_hash,
                    [sp.challenge_chain_sp, sp.reward_chain_sp],
                )

                if new_proof_of_space.sp_hash not in self.farmer.proofs_of_space:
                    self.farmer.proofs_of_space[new_proof_of_space.sp_hash] = []
                self.farmer.proofs_of_space[new_proof_of_space.sp_hash].append(
                    (
                        new_proof_of_space.plot_identifier,
                        new_proof_of_space.proof,
                    )
                )
                self.farmer.cache_add_time[new_proof_of_space.sp_hash] = uint64(int(time.time()))
                self.farmer.quality_str_to_identifiers[computed_quality_string] = (
                    new_proof_of_space.plot_identifier,
                    new_proof_of_space.challenge_hash,
                    new_proof_of_space.sp_hash,
                    peer.peer_node_id,
                )
                self.farmer.cache_add_time[computed_quality_string] = uint64(int(time.time()))

                await peer.send_message(make_msg(ProtocolMessageTypes.request_signatures, request))

            p2_singleton_puzzle_hash = new_proof_of_space.proof.pool_contract_puzzle_hash
            if p2_singleton_puzzle_hash is not None:
                # Otherwise, send the proof of space to the pool
                # When we win a block, we also send the partial to the pool
                if p2_singleton_puzzle_hash not in self.farmer.pool_state:
                    self.farmer.log.info(f"Did not find pool info for {p2_singleton_puzzle_hash}")
                    return
                pool_state_dict: Dict = self.farmer.pool_state[p2_singleton_puzzle_hash]
                pool_url = pool_state_dict["pool_config"].pool_url
                if pool_url == "":
                    return

                if pool_state_dict["current_difficulty"] is None:
                    self.farmer.log.warning(
                        f"No pool specific difficulty has been set for {p2_singleton_puzzle_hash}, "
                        f"check communication with the pool, skipping this partial to {pool_url}."
                    )
                    return

                required_iters = calculate_iterations_quality(
                    self.farmer.constants.DIFFICULTY_CONSTANT_FACTOR,
                    computed_quality_string,
                    new_proof_of_space.proof.size,
                    pool_state_dict["current_difficulty"],
                    new_proof_of_space.sp_hash,
                )
                if required_iters >= calculate_sp_interval_iters(
                    self.farmer.constants, self.farmer.constants.POOL_SUB_SLOT_ITERS
                ):
                    self.farmer.log.info(
                        f"Proof of space not good enough for pool {pool_url}: {pool_state_dict['current_difficulty']}"
                    )
                    return

                authentication_token_timeout = pool_state_dict["authentication_token_timeout"]
                if authentication_token_timeout is None:
                    self.farmer.log.warning(
                        f"No pool specific authentication_token_timeout has been set for {p2_singleton_puzzle_hash}"
                        f", check communication with the pool."
                    )
                    return

                # Submit partial to pool
                is_eos = new_proof_of_space.signage_point_index == 0

                payload = PostPartialPayload(
                    pool_state_dict["pool_config"].launcher_id,
                    get_current_authentication_token(authentication_token_timeout),
                    new_proof_of_space.proof,
                    new_proof_of_space.sp_hash,
                    is_eos,
                    peer.peer_node_id,
                )

                # The plot key is 2/2 so we need the harvester's half of the signature
                m_to_sign = payload.get_hash()
                request = harvester_protocol.RequestSignatures(
                    new_proof_of_space.plot_identifier,
                    new_proof_of_space.challenge_hash,
                    new_proof_of_space.sp_hash,
                    [m_to_sign],
                )
                response: Any = await peer.request_signatures(request)
                if not isinstance(response, harvester_protocol.RespondSignatures):
                    self.farmer.log.error(f"Invalid response from harvester: {response}")
                    return

                assert len(response.message_signatures) == 1

                plot_signature: Optional[G2Element] = None
                for sk in self.farmer.get_private_keys():
                    pk = sk.get_g1()
                    if pk == response.farmer_pk:
                        agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk, True)
                        assert agg_pk == new_proof_of_space.proof.plot_public_key
                        sig_farmer = AugSchemeMPL.sign(sk, m_to_sign, agg_pk)
                        taproot_sk: PrivateKey = ProofOfSpace.generate_taproot_sk(response.local_pk, pk)
                        taproot_sig: G2Element = AugSchemeMPL.sign(taproot_sk, m_to_sign, agg_pk)

                        plot_signature = AugSchemeMPL.aggregate(
                            [sig_farmer, response.message_signatures[0][1], taproot_sig]
                        )
                        assert AugSchemeMPL.verify(agg_pk, m_to_sign, plot_signature)
                authentication_pk = pool_state_dict["pool_config"].authentication_public_key
                if bytes(authentication_pk) is None:
                    self.farmer.log.error(f"No authentication sk for {authentication_pk}")
                    return
                authentication_sk: PrivateKey = self.farmer.authentication_keys[bytes(authentication_pk)]
                authentication_signature = AugSchemeMPL.sign(authentication_sk, m_to_sign)

                assert plot_signature is not None

                agg_sig: G2Element = AugSchemeMPL.aggregate([plot_signature, authentication_signature])

                post_partial_request: PostPartialRequest = PostPartialRequest(payload, agg_sig)
                post_partial_body = json.dumps(post_partial_request.to_json_dict())
                self.farmer.log.info(
                    f"Submitting partial for {post_partial_request.payload.launcher_id.hex()} to {pool_url}"
                )
                pool_state_dict["points_found_since_start"] += pool_state_dict["current_difficulty"]
                pool_state_dict["points_found_24h"].append((time.time(), pool_state_dict["current_difficulty"]))
                headers = {
                    "content-type": "application/json;",
                }
                try:
                    async with aiohttp.ClientSession() as session:
                        async with session.post(f"{pool_url}/partial", data=post_partial_body, headers=headers) as resp:
                            if resp.ok:
                                pool_response: Dict = json.loads(await resp.text())
                                self.farmer.log.info(f"Pool response: {pool_response}")
                                if "error_code" in pool_response:
                                    self.farmer.log.error(
                                        f"Error in pooling: "
                                        f"{pool_response['error_code'], pool_response['error_message']}"
                                    )
                                    pool_state_dict["pool_errors_24h"].append(pool_response)
                                    if pool_response["error_code"] == PoolErrorCode.PROOF_NOT_GOOD_ENOUGH.value:
                                        self.farmer.log.error(
                                            "Partial not good enough, forcing pool farmer update to "
                                            "get our current difficulty."
                                        )
                                        pool_state_dict["next_farmer_update"] = 0
                                        await self.farmer.update_pool_state()
                                else:
                                    new_difficulty = pool_response["new_difficulty"]
                                    pool_state_dict["points_acknowledged_since_start"] += new_difficulty
                                    pool_state_dict["points_acknowledged_24h"].append((time.time(), new_difficulty))
                                    pool_state_dict["current_difficulty"] = new_difficulty
                            else:
                                self.farmer.log.error(f"Error sending partial to {pool_url}, {resp.status}")
                except Exception as e:
                    self.farmer.log.error(f"Error connecting to pool: {e}")
                    return

                return