async def test_invalid_coinbase_signature(self, initial_blockchain): blocks, b = initial_blockchain coinbase_coin, coinbase_signature = create_coinbase_coin_and_signature( blocks[9].header.data.height, blocks[9].header.data.coinbase.puzzle_hash, uint64(9991), PrivateKey.from_bytes( bytes.fromhex(list(bt.plot_config["plots"].values())[0]["pool_sk"]) ), ) new_header_data = HeaderData( blocks[9].header.data.height, blocks[9].header.data.prev_header_hash, blocks[9].header.data.timestamp, blocks[9].header.data.filter_hash, blocks[9].header.data.proof_of_space_hash, blocks[9].header.data.weight, blocks[9].header.data.total_iters, blocks[9].header.data.additions_root, blocks[9].header.data.removals_root, blocks[9].header.data.coinbase, coinbase_signature, blocks[9].header.data.fees_coin, blocks[9].header.data.aggregated_signature, blocks[9].header.data.cost, blocks[9].header.data.extension_data, blocks[9].header.data.generator_hash, ) # Coinbase amount invalid block_bad = FullBlock( blocks[9].proof_of_space, blocks[9].proof_of_time, Header( new_header_data, bt.get_harvester_signature( new_header_data, blocks[9].proof_of_space.plot_pubkey ), ), blocks[9].transactions_generator, blocks[9].transactions_filter, ) result, removed, error_code = await b.receive_block(block_bad) assert result == ReceiveBlockResult.INVALID_BLOCK assert error_code == Err.INVALID_COINBASE_SIGNATURE
async def proof_of_space_finalized( self, proof_of_space_finalized: farmer_protocol.ProofOfSpaceFinalized): """ Full node notifies farmer that a proof of space has been completed. It gets added to the challenges list at that weight, and weight is updated if necessary """ get_proofs: bool = False if (proof_of_space_finalized.weight >= self.current_weight and proof_of_space_finalized.challenge_hash not in self.seen_challenges): # Only get proofs for new challenges, at a current or new weight get_proofs = True if proof_of_space_finalized.weight > self.current_weight: self.current_weight = proof_of_space_finalized.weight # TODO: ask the pool for this information pool_sks: List[PrivateKey] = [ PrivateKey.from_bytes(bytes.fromhex(ce)) # type: ignore # noqa for ce in self.key_config["pool_sks"] ] coinbase_reward = uint64( calculate_block_reward(proof_of_space_finalized.height)) coinbase_coin, coinbase_signature = create_coinbase_coin_and_signature( proof_of_space_finalized.height + 1, bytes.fromhex(self.key_config["pool_target"]), coinbase_reward, pool_sks[0], ) self.coinbase_rewards[uint32(proof_of_space_finalized.height + 1)] = ( coinbase_coin, coinbase_signature, ) log.info(f"\tCurrent weight set to {self.current_weight}") self.seen_challenges.add(proof_of_space_finalized.challenge_hash) if proof_of_space_finalized.weight not in self.challenges: self.challenges[proof_of_space_finalized.weight] = [ proof_of_space_finalized ] else: self.challenges[proof_of_space_finalized.weight].append( proof_of_space_finalized) self.challenge_to_weight[ proof_of_space_finalized. challenge_hash] = proof_of_space_finalized.weight self.challenge_to_height[ proof_of_space_finalized. challenge_hash] = proof_of_space_finalized.height if get_proofs: message = harvester_protocol.NewChallenge( proof_of_space_finalized.challenge_hash) yield OutboundMessage( NodeType.HARVESTER, Message("new_challenge", message), Delivery.BROADCAST, )
def _create_block( self, test_constants: Dict, challenge_hash: bytes32, height: uint32, prev_header_hash: bytes32, prev_iters: uint64, prev_weight: uint128, timestamp: uint64, difficulty: uint64, min_iters: uint64, seed: bytes, genesis: bool = False, reward_puzzlehash: bytes32 = None, transactions: Program = None, aggsig: BLSSignature = 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_prover = None selected_plot_sk = None selected_pool_sk = None selected_proof_index = 0 plots = list(self.plot_config["plots"].items()) selected_quality: Optional[bytes] = None best_quality = 0 if self.use_any_pos: for i in range(len(plots) * 3): # Allow passing in seed, to create reorgs and different chains random.seed(seed + i.to_bytes(4, "big")) seeded_pn = random.randint(0, len(plots) - 1) pool_sk = PrivateKey.from_bytes( bytes.fromhex(plots[seeded_pn][1]["pool_sk"]) ) plot_sk = PrivateKey.from_bytes( bytes.fromhex(plots[seeded_pn][1]["sk"]) ) prover = DiskProver(plots[seeded_pn][0]) qualities = prover.get_qualities_for_challenge(challenge_hash) if len(qualities) > 0: if self.use_any_pos: selected_quality = qualities[0] selected_prover = prover selected_pool_sk = pool_sk selected_plot_sk = plot_sk break else: for i in range(len(plots)): pool_sk = PrivateKey.from_bytes(bytes.fromhex(plots[i][1]["pool_sk"])) plot_sk = PrivateKey.from_bytes(bytes.fromhex(plots[i][1]["sk"])) prover = DiskProver(plots[i][0]) qualities = prover.get_qualities_for_challenge(challenge_hash) j = 0 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_prover = prover selected_pool_sk = pool_sk selected_plot_sk = plot_sk selected_proof_index = j j += 1 assert selected_prover assert selected_pool_sk assert selected_plot_sk pool_pk = selected_pool_sk.get_public_key() plot_pk = selected_plot_sk.get_public_key() if selected_quality is None: raise RuntimeError("No proofs for this challenge") proof_xs: bytes = selected_prover.get_full_proof( challenge_hash, selected_proof_index ) proof_of_space: ProofOfSpace = ProofOfSpace( challenge_hash, pool_pk, plot_pk, selected_prover.get_size(), proof_xs ) number_iters: uint64 = pot_iterations.calculate_iterations( proof_of_space, difficulty, min_iters ) # print("Doing iters", number_iters) 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, self.n_wesolowski, proof_bytes, ) if not reward_puzzlehash: reward_puzzlehash = self.fee_target # 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) coinbase_reward = block_rewards.calculate_block_reward(height) fee_reward = uint64(block_rewards.calculate_base_fee(height) + fees) coinbase_coin, coinbase_signature = create_coinbase_coin_and_signature( height, reward_puzzlehash, coinbase_reward, selected_pool_sk ) parent_coin_name = std_hash(std_hash(height)) fees_coin = Coin(parent_coin_name, reward_puzzlehash, uint64(fee_reward)) # Create filter byte_array_tx: List[bytes32] = [] tx_additions: List[Coin] = [] tx_removals: List[bytes32] = [] encoded = None 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)) 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]] = {} for coin in tx_additions: 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) if encoded is not None else bytes32([0] * 32) 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, coinbase_coin, coinbase_signature, fees_coin, aggsig, cost, extension_data, generator_hash, ) header_hash_sig: PrependSignature = selected_plot_sk.sign_prepend( header_data.get_hash() ) header: Header = Header(header_data, header_hash_sig) full_block: FullBlock = FullBlock( proof_of_space, proof_of_time, header, transactions, encoded ) return full_block
async def respond_proof_of_space( self, response: harvester_protocol.RespondProofOfSpace): """ This is a response from the harvester with a proof of space. We check it's validity, and request a pool partial, a header signature, or both, if the proof is good enough. """ if response.proof.pool_pubkey not in self.pool_public_keys: raise RuntimeError("Pool pubkey not in list of approved keys") challenge_hash: bytes32 = self.harvester_responses_challenge[ response.quality_string] challenge_weight: uint128 = self.challenge_to_weight[challenge_hash] challenge_height: uint32 = self.challenge_to_height[challenge_hash] new_proof_height: uint32 = uint32(challenge_height + 1) difficulty: uint64 = uint64(0) for posf in self.challenges[challenge_weight]: if posf.challenge_hash == challenge_hash: difficulty = posf.difficulty if difficulty == 0: raise RuntimeError("Did not find challenge") computed_quality_string = response.proof.verify_and_get_quality_string( ) if response.quality_string != computed_quality_string: raise RuntimeError("Invalid quality for proof of space") self.harvester_responses_proofs[ response.quality_string] = response.proof self.harvester_responses_proof_hash_to_qual[ response.proof.get_hash()] = response.quality_string estimate_min = (self.proof_of_time_estimate_ips * self.constants["BLOCK_TIME_TARGET"] / self.constants["MIN_ITERS_PROPORTION"]) number_iters: uint64 = calculate_iterations_quality( computed_quality_string, response.proof.size, difficulty, estimate_min, ) estimate_secs: float = number_iters / self.proof_of_time_estimate_ips if estimate_secs < self.config["pool_share_threshold"]: request1 = harvester_protocol.RequestPartialProof( response.quality_string, self.wallet_target, ) yield OutboundMessage( NodeType.HARVESTER, Message("request_partial_proof", request1), Delivery.RESPOND, ) if estimate_secs < self.config["propagate_threshold"]: pool_pk = bytes(response.proof.pool_pubkey) if pool_pk not in self.pool_sks_map: log.error( f"Don't have the private key for the pool key used by harvester: {pool_pk.hex()}" ) return sk = self.pool_sks_map[pool_pk] coinbase_reward = uint64( calculate_block_reward(uint32(new_proof_height))) coinbase, signature = create_coinbase_coin_and_signature( new_proof_height, self.pool_target, coinbase_reward, sk, ) request2 = farmer_protocol.RequestHeaderHash( challenge_hash, coinbase, signature, self.wallet_target, response.proof, ) yield OutboundMessage( NodeType.FULL_NODE, Message("request_header_hash", request2), Delivery.BROADCAST, )