async def create_pool_args(pool_url: str) -> Dict: try: async with aiohttp.ClientSession() as session: async with session.get(f"{pool_url}/pool_info", ssl=ssl_context_for_root( get_mozilla_ca_crt())) as response: if response.ok: json_dict = json.loads(await response.text()) else: raise ValueError( f"Response from {pool_url} not OK: {response.status}") except Exception as e: raise ValueError(f"Error connecting to pool {pool_url}: {e}") if json_dict["relative_lock_height"] > 1000: raise ValueError( "Relative lock height too high for this pool, cannot join") if json_dict["protocol_version"] != POOL_PROTOCOL_VERSION: raise ValueError( f"Incorrect version: {json_dict['protocol_version']}, should be {POOL_PROTOCOL_VERSION}" ) header_msg = f"\n---- Pool parameters fetched from {pool_url} ----" print(header_msg) pprint(json_dict) print("-" * len(header_msg)) return json_dict
async def fetch(url: str): async with ClientSession() as session: try: mozzila_root = get_mozzila_ca_crt() ssl_context = ssl_context_for_root(mozzila_root) response = await session.get(url, ssl=ssl_context) if not response.ok: log.warning("Response not OK.") return None return await response.text() except Exception as e: log.error(f"Exception while fetching {url}, exception: {e}") return None
async def join_pool(args: dict, wallet_client: WalletRpcClient, fingerprint: int) -> None: config = load_config(DEFAULT_ROOT_PATH, "config.yaml") enforce_https = config["full_node"]["selected_network"] == "mainnet" pool_url: str = args["pool_url"] fee = Decimal(args.get("fee", 0)) fee_mojos = uint64(int(fee * units["chia"])) if enforce_https and not pool_url.startswith("https://"): print(f"Pool URLs must be HTTPS on mainnet {pool_url}. Aborting.") return wallet_id = args.get("id", None) prompt = not args.get("yes", False) try: async with aiohttp.ClientSession() as session: async with session.get(f"{pool_url}/pool_info", ssl=ssl_context_for_root( get_mozilla_ca_crt())) as response: if response.ok: json_dict = json.loads(await response.text()) else: print(f"Response not OK: {response.status}") return except Exception as e: print(f"Error connecting to pool {pool_url}: {e}") return if json_dict["relative_lock_height"] > 1000: print("Relative lock height too high for this pool, cannot join") return if json_dict["protocol_version"] != POOL_PROTOCOL_VERSION: print( f"Incorrect version: {json_dict['protocol_version']}, should be {POOL_PROTOCOL_VERSION}" ) return pprint(json_dict) msg = f"\nWill join pool: {pool_url} with Plot NFT {fingerprint}." func = functools.partial( wallet_client.pw_join_pool, wallet_id, hexstr_to_bytes(json_dict["target_puzzle_hash"]), pool_url, json_dict["relative_lock_height"], fee_mojos, ) await submit_tx_with_confirmation(msg, prompt, func, wallet_client, fingerprint, wallet_id)
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
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
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 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