async def respond_signature(self, response: harvester_protocol.RespondSignature): """ Receives a signature on a block header hash, which is required for submitting a block to the blockchain. """ header_hash = response.message proof_of_space: bytes32 = self.header_hash_to_pos[header_hash] validates: bool = False for sk in self._get_private_keys(): pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk) assert agg_pk == proof_of_space.plot_public_key farmer_share = AugSchemeMPL.sign(sk, header_hash, agg_pk) agg_sig = AugSchemeMPL.aggregate( [response.message_signature, farmer_share] ) validates = AugSchemeMPL.verify(agg_pk, header_hash, agg_sig) if validates: break assert validates pos_hash: bytes32 = proof_of_space.get_hash() request = farmer_protocol.HeaderSignature(pos_hash, header_hash, agg_sig) yield OutboundMessage( NodeType.FULL_NODE, Message("header_signature", request), Delivery.BROADCAST )
async def request_signature(self, request: harvester_protocol.RequestSignature): """ The farmer requests a signature on the header hash, for one of the proofs that we found. A signature is created on the header hash using the harvester private key. This can also be used for pooling. """ plot_info = self.provers[Path(request.plot_id).resolve()] local_sk = plot_info.local_sk agg_pk = ProofOfSpace.generate_plot_public_key( local_sk.get_g1(), plot_info.farmer_public_key) # This is only a partial signature. When combined with the farmer's half, it will # form a complete PrependSignature. signature: G2Element = AugSchemeMPL.sign(local_sk, request.message, agg_pk) response: harvester_protocol.RespondSignature = harvester_protocol.RespondSignature( request.plot_id, request.message, local_sk.get_g1(), plot_info.farmer_public_key, signature, ) yield OutboundMessage( NodeType.FARMER, Message("respond_signature", response), Delivery.RESPOND, )
async def request_proof_of_space( self, request: harvester_protocol.RequestProofOfSpace): """ The farmer requests a proof of space, for one of the plots. We look up the correct plot based on the plot id and response number, lookup the proof, and return it. """ response: Optional[harvester_protocol.RespondProofOfSpace] = None challenge_hash = request.challenge_hash filename = Path(request.plot_id).resolve() index = request.response_number proof_xs: bytes plot_info = self.provers[filename] try: try: proof_xs = plot_info.prover.get_full_proof( challenge_hash, index) except RuntimeError: prover = DiskProver(str(filename)) self.provers[filename] = PlotInfo( prover, plot_info.pool_public_key, plot_info.farmer_public_key, plot_info.plot_public_key, plot_info.local_sk, plot_info.file_size, plot_info.time_modified, ) proof_xs = self.provers[filename].prover.get_full_proof( challenge_hash, index) except KeyError: log.warning(f"KeyError plot {filename} does not exist.") plot_info = self.provers[filename] plot_public_key = ProofOfSpace.generate_plot_public_key( plot_info.local_sk.get_g1(), plot_info.farmer_public_key) proof_of_space: ProofOfSpace = ProofOfSpace( challenge_hash, plot_info.pool_public_key, plot_public_key, uint8(self.provers[filename].prover.get_size()), proof_xs, ) response = harvester_protocol.RespondProofOfSpace( request.plot_id, request.response_number, proof_of_space, ) if response: yield OutboundMessage( NodeType.FARMER, Message("respond_proof_of_space", response), Delivery.RESPOND, )
def get_plot_signature(self, m: bytes32, plot_pk: G1Element) -> G2Element: """ Returns the plot signature of the header data. """ farmer_sk = master_sk_to_farmer_sk(self.all_sks[0]) for _, plot_info in self.plots.items(): agg_pk = ProofOfSpace.generate_plot_public_key(plot_info.local_sk.get_g1(), plot_info.farmer_public_key) if agg_pk == plot_pk: harv_share = AugSchemeMPL.sign(plot_info.local_sk, m, agg_pk) farm_share = AugSchemeMPL.sign(farmer_sk, m, agg_pk) return AugSchemeMPL.aggregate([harv_share, farm_share]) raise ValueError(f"Do not have key {plot_pk}")
def get_pospaces_for_challenge( self, constants: ConsensusConstants, challenge_hash: bytes32, signage_point: bytes32, seed: bytes, difficulty: uint64, sub_slot_iters: uint64, ) -> List[Tuple[uint64, ProofOfSpace]]: found_proofs: List[Tuple[uint64, ProofOfSpace]] = [] plots: List[PlotInfo] = [ plot_info for _, plot_info in sorted(list(self.plots.items()), key=lambda x: str(x[0])) ] random.seed(seed) for plot_info in plots: plot_id = plot_info.prover.get_id() if ProofOfSpace.passes_plot_filter(constants, plot_id, challenge_hash, signage_point): new_challenge: bytes32 = ProofOfSpace.calculate_pos_challenge(plot_id, challenge_hash, signage_point) qualities = plot_info.prover.get_qualities_for_challenge(new_challenge) for proof_index, quality_str in enumerate(qualities): required_iters = calculate_iterations_quality( quality_str, plot_info.prover.get_size(), difficulty, signage_point, ) if required_iters < calculate_sp_interval_iters(constants, sub_slot_iters): proof_xs: bytes = plot_info.prover.get_full_proof(new_challenge, proof_index) plot_pk = ProofOfSpace.generate_plot_public_key( plot_info.local_sk.get_g1(), plot_info.farmer_public_key, ) proof_of_space: ProofOfSpace = ProofOfSpace( new_challenge, plot_info.pool_public_key, None, plot_pk, plot_info.prover.get_size(), proof_xs, ) found_proofs.append((required_iters, proof_of_space)) random_sample = found_proofs if len(found_proofs) >= 1: if random.random() < 0.1: # Removes some proofs of space to create "random" chains, based on the seed random_sample = random.sample(found_proofs, len(found_proofs) - 1) return random_sample
def get_plot_signature(self, header_data: HeaderData, plot_pk: G1Element) -> Optional[G2Element]: """ Returns the plot signature of the header data. """ farmer_sk = master_sk_to_farmer_sk(self.all_sks[0][0]) for _, plot_info in self.plots.items(): agg_pk = ProofOfSpace.generate_plot_public_key( plot_info.local_sk.get_g1(), plot_info.farmer_public_key) if agg_pk == plot_pk: m = header_data.get_hash() harv_share = AugSchemeMPL.sign(plot_info.local_sk, m, agg_pk) farm_share = AugSchemeMPL.sign(farmer_sk, m, agg_pk) return AugSchemeMPL.aggregate([harv_share, farm_share]) return None
async def request_signatures( self, request: harvester_protocol.RequestSignatures): """ The farmer requests a signature on the header hash, for one of the proofs that we found. A signature is created on the header hash using the harvester private key. This can also be used for pooling. """ plot_filename = Path(request.plot_identifier[64:]).resolve() try: plot_info = self.harvester.provers[plot_filename] except KeyError: self.harvester.log.warning( f"KeyError plot {plot_filename} does not exist.") return local_sk = plot_info.local_sk agg_pk = ProofOfSpace.generate_plot_public_key( local_sk.get_g1(), plot_info.farmer_public_key) # This is only a partial signature. When combined with the farmer's half, it will # form a complete PrependSignature. message_signatures: List[Tuple[bytes32, G2Element]] = [] for message in request.messages: signature: G2Element = AugSchemeMPL.sign(local_sk, message, agg_pk) message_signatures.append((message, signature)) response: harvester_protocol.RespondSignatures = harvester_protocol.RespondSignatures( request.plot_identifier, request.challenge_hash, request.sp_hash, local_sk.get_g1(), plot_info.farmer_public_key, message_signatures, ) msg = Message("respond_signatures", response) return msg
def _create_block( self, test_constants: ConsensusConstants, challenge_hash: bytes32, height: uint32, prev_header_hash: bytes32, prev_iters: uint64, prev_weight: uint128, timestamp: uint64, difficulty: int, min_iters: int, seed: bytes, genesis: bool = False, reward_puzzlehash: bytes32 = None, transactions: Program = None, aggsig: G2Element = None, fees: uint64 = uint64(0), ) -> FullBlock: """ Creates a block with the specified details. Uses the stored plots to create a proof of space, and also evaluates the VDF for the proof of time. """ selected_plot_info = None selected_proof_index = 0 selected_quality: Optional[bytes] = None best_quality = 0 plots = [ pinfo for _, pinfo in sorted(list(self.plots.items()), key=lambda x: str(x[0])) ] if self.use_any_pos: random.seed(seed) for i in range(len(plots) * 3): # Allow passing in seed, to create reorgs and different chains seeded_pn = random.randint(0, len(plots) - 1) plot_info = plots[seeded_pn] plot_id = plot_info.prover.get_id() ccp = ProofOfSpace.can_create_proof( plot_id, challenge_hash, test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG, ) if not ccp: continue qualities = plot_info.prover.get_qualities_for_challenge( challenge_hash) if len(qualities) > 0: selected_plot_info = plot_info selected_quality = qualities[0] break else: for i in range(len(plots)): plot_info = plots[i] j = 0 plot_id = plot_info.prover.get_id() ccp = ProofOfSpace.can_create_proof( plot_id, challenge_hash, test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG, ) if not ccp: continue qualities = plot_info.prover.get_qualities_for_challenge( challenge_hash) for quality in qualities: qual_int = int.from_bytes(quality, "big", signed=False) if qual_int > best_quality: best_quality = qual_int selected_quality = quality selected_plot_info = plot_info selected_proof_index = j j += 1 assert selected_plot_info is not None if selected_quality is None: raise RuntimeError("No proofs for this challenge") proof_xs: bytes = selected_plot_info.prover.get_full_proof( challenge_hash, selected_proof_index) plot_pk = ProofOfSpace.generate_plot_public_key( selected_plot_info.local_sk.get_g1(), selected_plot_info.farmer_public_key, ) proof_of_space: ProofOfSpace = ProofOfSpace( challenge_hash, selected_plot_info.pool_public_key, plot_pk, selected_plot_info.prover.get_size(), proof_xs, ) number_iters: uint64 = pot_iterations.calculate_iterations( proof_of_space, difficulty, min_iters, test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG, ) if self.real_plots: print(f"Performing {number_iters} VDF iterations") int_size = (test_constants.DISCRIMINANT_SIZE_BITS + 16) >> 4 result = prove(challenge_hash, test_constants.DISCRIMINANT_SIZE_BITS, number_iters) output = ClassgroupElement( int512(int.from_bytes( result[0:int_size], "big", signed=True, )), int512( int.from_bytes( result[int_size:2 * int_size], "big", signed=True, )), ) proof_bytes = result[2 * int_size:4 * int_size] proof_of_time = ProofOfTime( challenge_hash, number_iters, output, uint8(0), proof_bytes, ) # Use the extension data to create different blocks based on header hash extension_data: bytes32 = bytes32( [random.randint(0, 255) for _ in range(32)]) cost = uint64(0) fee_reward = uint64(block_rewards.calculate_base_fee(height) + fees) std_hash(std_hash(height)) # Create filter byte_array_tx: List[bytes32] = [] tx_additions: List[Coin] = [] tx_removals: List[bytes32] = [] if transactions: error, npc_list, _ = get_name_puzzle_conditions(transactions) additions: List[Coin] = additions_for_npc(npc_list) for coin in additions: tx_additions.append(coin) byte_array_tx.append(bytearray(coin.puzzle_hash)) for npc in npc_list: tx_removals.append(npc.coin_name) byte_array_tx.append(bytearray(npc.coin_name)) farmer_ph = self.farmer_ph pool_ph = self.pool_ph if reward_puzzlehash is not None: farmer_ph = reward_puzzlehash pool_ph = reward_puzzlehash byte_array_tx.append(bytearray(farmer_ph)) byte_array_tx.append(bytearray(pool_ph)) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded = bytes(bip158.GetEncoded()) removal_merkle_set = MerkleSet() addition_merkle_set = MerkleSet() # Create removal Merkle set for coin_name in tx_removals: removal_merkle_set.add_already_hashed(coin_name) # Create addition Merkle set puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {} cb_reward = calculate_block_reward(height) cb_coin = create_coinbase_coin(height, pool_ph, cb_reward) fees_coin = create_fees_coin(height, farmer_ph, fee_reward) for coin in tx_additions + [cb_coin, fees_coin]: if coin.puzzle_hash in puzzlehash_coin_map: puzzlehash_coin_map[coin.puzzle_hash].append(coin) else: puzzlehash_coin_map[coin.puzzle_hash] = [coin] # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash for puzzle, coins in puzzlehash_coin_map.items(): addition_merkle_set.add_already_hashed(puzzle) addition_merkle_set.add_already_hashed(hash_coin_list(coins)) additions_root = addition_merkle_set.get_root() removal_root = removal_merkle_set.get_root() generator_hash = (transactions.get_tree_hash() if transactions is not None else bytes32([0] * 32)) filter_hash = std_hash(encoded) pool_target = PoolTarget(pool_ph, uint32(height)) pool_target_signature = self.get_pool_key_signature( pool_target, proof_of_space.pool_public_key) assert pool_target_signature is not None final_aggsig: G2Element = pool_target_signature if aggsig is not None: final_aggsig = AugSchemeMPL.aggregate([final_aggsig, aggsig]) header_data: HeaderData = HeaderData( height, prev_header_hash, timestamp, filter_hash, proof_of_space.get_hash(), uint128(prev_weight + difficulty), uint64(prev_iters + number_iters), additions_root, removal_root, farmer_ph, fee_reward, pool_target, final_aggsig, cost, extension_data, generator_hash, ) header_hash_sig: G2Element = self.get_plot_signature( header_data, plot_pk) header: Header = Header(header_data, header_hash_sig) full_block: FullBlock = FullBlock(proof_of_space, proof_of_time, header, transactions, encoded) return full_block
def load_plots( provers: Dict[Path, PlotInfo], failed_to_open_filenames: Dict[Path, int], farmer_public_keys: Optional[List[G1Element]], pool_public_keys: Optional[List[G1Element]], match_str: Optional[str], root_path: Path, open_no_key_filenames=False, ) -> Tuple[bool, Dict[Path, PlotInfo], Dict[Path, int], Set[Path]]: start_time = time.time() config_file = load_config(root_path, "config.yaml", "harvester") changed = False no_key_filenames: Set[Path] = set() log.info(f'Searching directories {config_file["plot_directories"]}') plot_filenames: Dict[Path, List[Path]] = get_plot_filenames(config_file) all_filenames: List[Path] = [] for paths in plot_filenames.values(): all_filenames += paths total_size = 0 new_provers: Dict[Path, PlotInfo] = {} if match_str is not None: log.info( f'Only loading plots that contain "{match_str}" in the file or directory name' ) for filename in all_filenames: filename_str = str(filename) if match_str is not None and match_str not in filename_str: continue if filename.exists(): if filename in failed_to_open_filenames and ( time.time() - failed_to_open_filenames[filename]) < 1200: # Try once every 20 minutes to open the file continue if filename in provers: stat_info = filename.stat() if stat_info.st_mtime == provers[filename].time_modified: total_size += stat_info.st_size new_provers[filename] = provers[filename] continue try: prover = DiskProver(str(filename)) expected_size = _expected_plot_size( prover.get_size()) * UI_ACTUAL_SPACE_CONSTANT_FACTOR / 2.0 stat_info = filename.stat() # TODO: consider checking if the file was just written to (which would mean that the file is still # being copied). A segfault might happen in this edge case. if prover.get_size( ) >= 30 and stat_info.st_size < 0.98 * expected_size: log.warning( f"Not farming plot {filename}. Size is {stat_info.st_size / (1024**3)} GiB, but expected" f" at least: {expected_size / (1024 ** 3)} GiB. We assume the file is being copied." ) continue ( pool_public_key, farmer_public_key, local_master_sk, ) = parse_plot_info(prover.get_memo()) # Only use plots that correct keys associated with them if farmer_public_keys is not None and farmer_public_key not in farmer_public_keys: log.warning( f"Plot {filename} has a farmer public key that is not in the farmer's pk list." ) no_key_filenames.add(filename) if not open_no_key_filenames: continue if pool_public_keys is not None and pool_public_key not in pool_public_keys: log.warning( f"Plot {filename} has a pool public key that is not in the farmer's pool pk list." ) no_key_filenames.add(filename) if not open_no_key_filenames: continue stat_info = filename.stat() local_sk = master_sk_to_local_sk(local_master_sk) plot_public_key: G1Element = ProofOfSpace.generate_plot_public_key( local_sk.get_g1(), farmer_public_key) new_provers[filename] = PlotInfo( prover, pool_public_key, farmer_public_key, plot_public_key, local_sk, stat_info.st_size, stat_info.st_mtime, ) total_size += stat_info.st_size changed = True except Exception as e: tb = traceback.format_exc() log.error(f"Failed to open file {filename}. {e} {tb}") failed_to_open_filenames[filename] = int(time.time()) continue log.info( f"Found plot {filename} of size {new_provers[filename].prover.get_size()}" ) log.info( f"Loaded a total of {len(new_provers)} plots of size {total_size / (1024 ** 4)} TiB, in" f" {time.time()-start_time} seconds") return changed, new_provers, failed_to_open_filenames, no_key_filenames
def create_plots(args, root_path, use_datetime=True, test_private_keys: Optional[List] = None): config_filename = config_path_for_filename(root_path, "config.yaml") config = load_config(root_path, config_filename) if args.tmp2_dir is None: args.tmp2_dir = args.tmp_dir farmer_public_key: G1Element if args.farmer_public_key is not None: farmer_public_key = G1Element.from_bytes( bytes.fromhex(args.farmer_public_key)) else: farmer_public_key = get_farmer_public_key(args.alt_fingerprint) pool_public_key: G1Element if args.pool_public_key is not None: pool_public_key = bytes.fromhex(args.pool_public_key) else: pool_public_key = get_pool_public_key(args.alt_fingerprint) if args.num is not None: num = args.num else: num = 1 if args.size < config["min_mainnet_k_size"] and test_private_keys is None: log.warning( f"Creating plots with size k={args.size}, which is less than the minimum required for mainnet" ) if args.size < 22: log.warning("k under 22 is not supported. Increasing k to 22") args.size = 22 log.info( f"Creating {num} plots of size {args.size}, pool public key: " f"{bytes(pool_public_key).hex()} farmer public key: {bytes(farmer_public_key).hex()}" ) tmp_dir_created = False if not args.tmp_dir.exists(): mkdir(args.tmp_dir) tmp_dir_created = True tmp2_dir_created = False if not args.tmp2_dir.exists(): mkdir(args.tmp2_dir) tmp2_dir_created = True mkdir(args.final_dir) finished_filenames = [] for i in range(num): # Generate a random master secret key if test_private_keys is not None: assert len(test_private_keys) == num sk: PrivateKey = test_private_keys[i] else: sk = AugSchemeMPL.key_gen(token_bytes(32)) # The plot public key is the combination of the harvester and farmer keys plot_public_key = ProofOfSpace.generate_plot_public_key( master_sk_to_local_sk(sk).get_g1(), farmer_public_key) # The plot id is based on the harvester, farmer, and pool keys plot_id: bytes32 = ProofOfSpace.calculate_plot_id_pk( pool_public_key, plot_public_key) if args.plotid is not None: log.info(f"Debug plot ID: {args.plotid}") plot_id = bytes32(bytes.fromhex(args.plotid)) plot_memo: bytes32 = stream_plot_info(pool_public_key, farmer_public_key, sk) if args.memo is not None: log.info(f"Debug memo: {args.memo}") plot_memo = bytes.fromhex(args.memo) dt_string = datetime.now().strftime("%Y-%m-%d-%H-%M") if use_datetime: filename: str = f"plot-k{args.size}-{dt_string}-{plot_id}.plot" else: filename = f"plot-k{args.size}-{plot_id}.plot" full_path: Path = args.final_dir / filename resolved_final_dir: str = str(Path(args.final_dir).resolve()) plot_directories_list: str = config["harvester"]["plot_directories"] if args.exclude_final_dir: log.info( f"NOT adding directory {resolved_final_dir} to harvester for farming" ) if resolved_final_dir in plot_directories_list: log.warning( f"Directory {resolved_final_dir} already exists for harvester, please remove it manually" ) else: if resolved_final_dir not in plot_directories_list: # Adds the directory to the plot directories if it is not present log.info( f"Adding directory {resolved_final_dir} to harvester for farming" ) config = add_plot_directory(resolved_final_dir, root_path) if not full_path.exists(): log.info(f"Starting plot {i + 1}/{num}") # Creates the plot. This will take a long time for larger plots. plotter: DiskPlotter = DiskPlotter() plotter.create_plot_disk( str(args.tmp_dir), str(args.tmp2_dir), str(args.final_dir), filename, args.size, plot_memo, plot_id, args.buffer, args.buckets, args.stripe_size, args.num_threads, args.nobitfield, ) finished_filenames.append(filename) else: log.info(f"Plot {filename} already exists") log.info("Summary:") if tmp_dir_created: try: args.tmp_dir.rmdir() except Exception: log.info( f"warning: did not remove primary temporary folder {args.tmp_dir}, it may not be empty." ) if tmp2_dir_created: try: args.tmp2_dir.rmdir() except Exception: log.info( f"warning: did not remove secondary temporary folder {args.tmp2_dir}, it may not be empty." ) log.info(f"Created a total of {len(finished_filenames)} new plots") for filename in finished_filenames: log.info(filename)
def blocking_lookup( filename: Path, plot_info: PlotInfo) -> List[Tuple[bytes32, ProofOfSpace]]: # Uses the DiskProver object to lookup qualities. This is a blocking call, # so it should be run in a thread pool. try: sp_challenge_hash = ProofOfSpace.calculate_pos_challenge( plot_info.prover.get_id(), new_challenge.challenge_hash, new_challenge.sp_hash, ) try: quality_strings = plot_info.prover.get_qualities_for_challenge( sp_challenge_hash) except Exception as e: self.harvester.log.error( f"Error using prover object. Reinitializing prover object. {e}" ) try: self.harvester.provers[filename] = dataclasses.replace( plot_info, prover=DiskProver(str(filename))) quality_strings = plot_info.prover.get_qualities_for_challenge( sp_challenge_hash) except Exception as e: self.harvester.log.error( f"Error reinitializing plot {filename}. {e}") return [] responses: List[Tuple[bytes32, ProofOfSpace]] = [] if quality_strings is not None: # Found proofs of space (on average 1 is expected per plot) for index, quality_str in enumerate(quality_strings): required_iters: uint64 = calculate_iterations_quality( quality_str, plot_info.prover.get_size(), new_challenge.difficulty, new_challenge.sp_hash, ) sp_interval_iters = calculate_sp_interval_iters( self.harvester.constants, new_challenge.sub_slot_iters) if required_iters < sp_interval_iters: # Found a very good proof of space! will fetch the whole proof from disk, # then send to farmer try: proof_xs = plot_info.prover.get_full_proof( sp_challenge_hash, index) except RuntimeError: self.harvester.log.error( f"Exception fetching full proof for {filename}" ) continue plot_public_key = ProofOfSpace.generate_plot_public_key( plot_info.local_sk.get_g1(), plot_info.farmer_public_key) responses.append(( quality_str, ProofOfSpace( sp_challenge_hash, plot_info.pool_public_key, None, plot_public_key, uint8(plot_info.prover.get_size()), proof_xs, ), )) return responses except Exception as e: self.harvester.log.error(f"Unknown error: {e}") return []
def load_plots( provers: Dict[Path, PlotInfo], failed_to_open_filenames: Set[Path], farmer_public_keys: Optional[List[G1Element]], pool_public_keys: Optional[List[G1Element]], root_path: Path, open_no_key_filenames=False, ) -> Tuple[bool, Dict[Path, PlotInfo], Set[Path], Set[Path]]: config_file = load_config(root_path, "config.yaml", "harvester") changed = False no_key_filenames: Set[Path] = set() log.info(f'Searching directories {config_file["plot_directories"]}') plot_filenames: Dict[Path, List[Path]] = get_plot_filenames(config_file) all_filenames: List[Path] = [] for paths in plot_filenames.values(): all_filenames += paths total_size = 0 for filename in all_filenames: if filename in provers: stat_info = filename.stat() if stat_info.st_mtime == provers[filename].time_modified: total_size += stat_info.st_size continue if filename in failed_to_open_filenames: continue if filename.exists(): try: prover = DiskProver(str(filename)) ( pool_public_key, farmer_public_key, local_master_sk, ) = parse_plot_info(prover.get_memo()) # Only use plots that correct keys associated with them if (farmer_public_keys is not None and farmer_public_key not in farmer_public_keys): log.warning( f"Plot {filename} has a farmer public key that is not in the farmer's pk list." ) no_key_filenames.add(filename) if not open_no_key_filenames: continue if (pool_public_keys is not None and pool_public_key not in pool_public_keys): log.warning( f"Plot {filename} has a pool public key that is not in the farmer's pool pk list." ) no_key_filenames.add(filename) if not open_no_key_filenames: continue stat_info = filename.stat() local_sk = master_sk_to_local_sk(local_master_sk) plot_public_key: G1Element = ProofOfSpace.generate_plot_public_key( local_sk.get_g1(), farmer_public_key) provers[filename] = PlotInfo( prover, pool_public_key, farmer_public_key, plot_public_key, local_sk, stat_info.st_size, stat_info.st_mtime, ) total_size += stat_info.st_size changed = True except Exception as e: tb = traceback.format_exc() log.error(f"Failed to open file {filename}. {e} {tb}") failed_to_open_filenames.add(filename) continue log.info( f"Found plot {filename} of size {provers[filename].prover.get_size()}" ) log.info( f"Loaded a total of {len(provers)} plots of size {total_size / (1024 ** 4)} TB" ) return (changed, provers, failed_to_open_filenames, no_key_filenames)
def create_plots(args, root_path, use_datetime=True, test_private_keys: Optional[List] = None): config_filename = config_path_for_filename(root_path, "config.yaml") if args.tmp2_dir is None: args.tmp2_dir = args.final_dir farmer_public_key: G1Element if args.farmer_public_key is not None: farmer_public_key = G1Element.from_bytes( bytes.fromhex(args.farmer_public_key)) else: farmer_public_key = get_default_farmer_public_key() pool_public_key: G1Element if args.pool_public_key is not None: pool_public_key = bytes.fromhex(args.pool_public_key) else: pool_public_key = get_default_pool_public_key() if args.num is not None: num = args.num else: num = 1 log.info( f"Creating {num} plots of size {args.size}, pool public key: " f"{bytes(pool_public_key).hex()} farmer public key: {bytes(farmer_public_key).hex()}" ) tmp_dir_created = False if not args.tmp_dir.exists(): mkdir(args.tmp_dir) tmp_dir_created = True tmp2_dir_created = False if not args.tmp2_dir.exists(): mkdir(args.tmp2_dir) tmp2_dir_created = True mkdir(args.final_dir) finished_filenames = [] config = load_config(root_path, config_filename) plot_filenames = get_plot_filenames(config["harvester"]) for i in range(num): # Generate a random master secret key if test_private_keys is not None: assert len(test_private_keys) == num sk: PrivateKey = test_private_keys[i] else: sk = AugSchemeMPL.key_gen(token_bytes(32)) # The plot public key is the combination of the harvester and farmer keys plot_public_key = ProofOfSpace.generate_plot_public_key( master_sk_to_local_sk(sk).get_g1(), farmer_public_key) # The plot id is based on the harvester, farmer, and pool keys plot_id: bytes32 = ProofOfSpace.calculate_plot_id( pool_public_key, plot_public_key) if args.plotid is not None: log.info(f"Debug plot ID: {args.plotid}") plot_id: bytes32 = bytes32(bytes.fromhex(args.plotid)) plot_memo: bytes32 = stream_plot_info(pool_public_key, farmer_public_key, sk) if args.memo is not None: log.info(f"Debug memo: {args.memo}") plot_memo: bytes32 = bytes.fromhex(args.memo) dt_string = datetime.now().strftime("%Y-%m-%d-%H-%M") if use_datetime: filename: str = f"plot-k{args.size}-{dt_string}-{plot_id}.plot" else: filename = f"plot-k{args.size}-{plot_id}.plot" full_path: Path = args.final_dir / filename if args.final_dir.resolve() not in plot_filenames: if (str(args.final_dir.resolve()) not in config["harvester"]["plot_directories"]): # Adds the directory to the plot directories if it is not present config = add_plot_directory(str(args.final_dir.resolve()), root_path) if not full_path.exists(): log.info(f"Starting plot {i + 1}/{num}") # Creates the plot. This will take a long time for larger plots. plotter: DiskPlotter = DiskPlotter() plotter.create_plot_disk( str(args.tmp_dir), str(args.tmp2_dir), str(args.final_dir), filename, args.size, plot_memo, plot_id, args.buffer, args.buckets, args.stripe_size, args.num_threads, ) finished_filenames.append(filename) else: log.info(f"Plot {filename} already exists") log.info("Summary:") if tmp_dir_created: try: args.tmp_dir.rmdir() except Exception: log.info( f"warning: did not remove primary temporary folder {args.tmp_dir}, it may not be empty." ) if tmp2_dir_created: try: args.tmp2_dir.rmdir() except Exception: log.info( f"warning: did not remove secondary temporary folder {args.tmp2_dir}, it may not be empty." ) log.info(f"Created a total of {len(finished_filenames)} new plots") for filename in finished_filenames: log.info(filename)
async def respond_signatures( self, response: harvester_protocol.RespondSignatures): """ There are two cases: receiving signatures for sps, or receiving signatures for the block. """ if response.sp_hash not in self.farmer.sps: self.farmer.log.warning( f"Do not have challenge hash {response.challenge_hash}") return is_sp_signatures: bool = False sps = self.farmer.sps[response.sp_hash] signage_point_index = sps[0].signage_point_index found_sp_hash_debug = False for sp_candidate in sps: if response.sp_hash == response.message_signatures[0][0]: found_sp_hash_debug = True if sp_candidate.reward_chain_sp == response.message_signatures[ 1][0]: is_sp_signatures = True if found_sp_hash_debug: assert is_sp_signatures pospace = None for plot_identifier, candidate_pospace in self.farmer.proofs_of_space[ response.sp_hash]: if plot_identifier == response.plot_identifier: pospace = candidate_pospace assert pospace is not None computed_quality_string = pospace.verify_and_get_quality_string( self.farmer.constants, response.challenge_hash, response.sp_hash) if computed_quality_string is None: self.farmer.log.warning(f"Have invalid PoSpace {pospace}") return if is_sp_signatures: ( challenge_chain_sp, challenge_chain_sp_harv_sig, ) = response.message_signatures[0] reward_chain_sp, reward_chain_sp_harv_sig = response.message_signatures[ 1] 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) assert agg_pk == pospace.plot_public_key farmer_share_cc_sp = AugSchemeMPL.sign( sk, challenge_chain_sp, agg_pk) agg_sig_cc_sp = AugSchemeMPL.aggregate( [challenge_chain_sp_harv_sig, farmer_share_cc_sp]) assert AugSchemeMPL.verify(agg_pk, challenge_chain_sp, agg_sig_cc_sp) # This means it passes the sp filter farmer_share_rc_sp = AugSchemeMPL.sign( sk, reward_chain_sp, agg_pk) agg_sig_rc_sp = AugSchemeMPL.aggregate( [reward_chain_sp_harv_sig, farmer_share_rc_sp]) assert AugSchemeMPL.verify(agg_pk, reward_chain_sp, agg_sig_rc_sp) assert pospace.pool_public_key is not None pool_pk = bytes(pospace.pool_public_key) if pool_pk not in self.farmer.pool_sks_map: self.farmer.log.error( f"Don't have the private key for the pool key used by harvester: {pool_pk.hex()}" ) return pool_target: PoolTarget = PoolTarget( self.farmer.pool_target, uint32(0)) pool_target_signature: G2Element = AugSchemeMPL.sign( self.farmer.pool_sks_map[pool_pk], bytes(pool_target)) request = farmer_protocol.DeclareProofOfSpace( response.challenge_hash, challenge_chain_sp, signage_point_index, reward_chain_sp, pospace, agg_sig_cc_sp, agg_sig_rc_sp, self.farmer.wallet_target, pool_target, pool_target_signature, ) msg = Message("declare_proof_of_space", request) await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE) return else: # This is a response with block signatures for sk in self.farmer.get_private_keys(): ( foliage_sub_block_hash, foliage_sub_block_sig_harvester, ) = response.message_signatures[0] ( foliage_block_hash, foliage_block_sig_harvester, ) = response.message_signatures[1] pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key( response.local_pk, pk) assert agg_pk == pospace.plot_public_key foliage_sub_block_sig_farmer = AugSchemeMPL.sign( sk, foliage_sub_block_hash, agg_pk) foliage_block_sig_farmer = AugSchemeMPL.sign( sk, foliage_block_hash, agg_pk) foliage_sub_block_agg_sig = AugSchemeMPL.aggregate([ foliage_sub_block_sig_harvester, foliage_sub_block_sig_farmer ]) foliage_block_agg_sig = AugSchemeMPL.aggregate([ foliage_block_sig_harvester, foliage_block_sig_farmer ]) assert AugSchemeMPL.verify(agg_pk, foliage_sub_block_hash, foliage_sub_block_agg_sig) assert AugSchemeMPL.verify(agg_pk, foliage_block_hash, foliage_block_agg_sig) request_to_nodes = farmer_protocol.SignedValues( computed_quality_string, foliage_sub_block_agg_sig, foliage_block_agg_sig, ) msg = Message("signed_values", request_to_nodes) await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE)