async def _pool_get_pool_info(self, pool_config: PoolWalletConfig) -> Optional[Dict]: try: async with aiohttp.ClientSession(trust_env=True) as session: async with session.get( f"{pool_config.pool_url}/pool_info", 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 /pool_info response: {response}") return response else: self.handle_failed_pool_response( pool_config.p2_singleton_puzzle_hash, f"Error in GET /pool_info {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 /pool_info {pool_config.pool_url}, {e}" ) return None
async def _pool_get_farmer( self, pool_config: PoolWalletConfig, authentication_token_timeout: uint8, authentication_sk: PrivateKey ) -> Optional[Dict]: 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
async def _pool_put_farmer( self, pool_config: PoolWalletConfig, authentication_token_timeout: uint8, owner_sk: PrivateKey ) -> Optional[Dict]: put_farmer_payload: PutFarmerPayload = PutFarmerPayload( 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, put_farmer_payload.get_hash()) put_farmer_request = PutFarmerRequest(put_farmer_payload, signature) try: async with aiohttp.ClientSession() as session: async with session.put( f"{pool_config.pool_url}/farmer", json=put_farmer_request.to_json_dict(), 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"PUT /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 PUT /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 PUT /farmer {pool_config.pool_url}, {e}" ) return None
async def post(session: aiohttp.ClientSession, url: str, data: Any): mozilla_root = get_mozilla_ca_crt() ssl_context = ssl_context_for_root(mozilla_root) response = await session.post(url, json=data, ssl=ssl_context) return await response.json()
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.constants.NETWORK_TYPE != NetworkType.MAINNET: # This is meant to make testnets more stable, when difficulty is very low if self.farmer.number_of_responses[ new_proof_of_space.sp_hash] > max_pos_per_sp: 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) 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"])) try: async with aiohttp.ClientSession() as session: async with session.post( f"{pool_url}/partial", json=post_partial_request.to_json_dict(), ssl=ssl_context_for_root(get_mozilla_ca_crt(), log=self.farmer.log), ) 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