async def perform_handshake( self, network_id: bytes32, protocol_version: str, server_port: int, local_type: NodeType ): if self.is_outbound: outbound_handshake = make_msg( ProtocolMessageTypes.handshake, Handshake( network_id, protocol_version, chia_full_version_str(), uint16(server_port), uint8(local_type.value), ), ) payload: Optional[Payload] = Payload(outbound_handshake, None) assert payload is not None await self._send_message(payload) payload = await self._read_one_message() if payload is None: raise ProtocolError(Err.INVALID_HANDSHAKE) inbound_handshake = Handshake.from_bytes(payload.msg.data) if ProtocolMessageTypes(payload.msg.type) != ProtocolMessageTypes.handshake: raise ProtocolError(Err.INVALID_HANDSHAKE) if inbound_handshake.protocol_version != protocol_version: raise ProtocolError(Err.INCOMPATIBLE_PROTOCOL_VERSION) self.peer_server_port = inbound_handshake.server_port self.connection_type = NodeType(inbound_handshake.node_type) else: try: payload = await self._read_one_message() except Exception: raise ProtocolError(Err.INVALID_HANDSHAKE) if payload is None: raise ProtocolError(Err.INVALID_HANDSHAKE) inbound_handshake = Handshake.from_bytes(payload.msg.data) if ProtocolMessageTypes(payload.msg.type) != ProtocolMessageTypes.handshake: raise ProtocolError(Err.INVALID_HANDSHAKE) if inbound_handshake.protocol_version != protocol_version: raise ProtocolError(Err.INCOMPATIBLE_PROTOCOL_VERSION) outbound_handshake = make_msg( ProtocolMessageTypes.handshake, Handshake( network_id, protocol_version, chia_full_version_str(), uint16(server_port), uint8(local_type.value), ), ) payload = Payload(outbound_handshake, None) await self._send_message(payload) self.peer_server_port = inbound_handshake.server_port self.connection_type = NodeType(inbound_handshake.node_type) self.outbound_task = asyncio.create_task(self.outbound_handler()) self.inbound_task = asyncio.create_task(self.inbound_handler()) return True
def test_calculate_ip_iters(self): ssi: uint64 = uint64(100001 * 64 * 4) sp_interval_iters = ssi // test_constants.NUM_SPS_SUB_SLOT with raises(ValueError): # Invalid signage point index calculate_ip_iters(test_constants, ssi, uint8(123), uint64(100000)) sp_iters = sp_interval_iters * 13 with raises(ValueError): # required_iters too high calculate_ip_iters(test_constants, ssi, sp_interval_iters, sp_interval_iters) with raises(ValueError): # required_iters too high calculate_ip_iters(test_constants, ssi, sp_interval_iters, sp_interval_iters * 12) with raises(ValueError): # required_iters too low (0) calculate_ip_iters(test_constants, ssi, sp_interval_iters, uint64(0)) required_iters = sp_interval_iters - 1 ip_iters = calculate_ip_iters(test_constants, ssi, uint8(13), required_iters) assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters required_iters = uint64(1) ip_iters = calculate_ip_iters(test_constants, ssi, uint8(13), required_iters) assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters required_iters = uint64(int(ssi * 4 / 300)) ip_iters = calculate_ip_iters(test_constants, ssi, uint8(13), required_iters) assert ip_iters == sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters assert sp_iters < ip_iters # Overflow sp_iters = sp_interval_iters * (test_constants.NUM_SPS_SUB_SLOT - 1) ip_iters = calculate_ip_iters( test_constants, ssi, uint8(test_constants.NUM_SPS_SUB_SLOT - 1), required_iters, ) assert ip_iters == (sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters) % ssi assert sp_iters > ip_iters
def get_vdf_info_and_proof( constants: ConsensusConstants, vdf_input: ClassgroupElement, challenge_hash: bytes32, number_iters: uint64, ) -> Tuple[VDFInfo, VDFProof]: int_size = (constants.DISCRIMINANT_SIZE_BITS + 16) >> 4 result: bytes = prove( bytes(challenge_hash), str(vdf_input.a), str(vdf_input.b), 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] return VDFInfo(challenge_hash, number_iters, output), VDFProof(uint8(0), proof_bytes)
async def farm_new_block(self, request: FarmNewBlockProtocol): async with self.lock: self.log.info("Farming new block!") current_blocks = await self.get_all_full_blocks() if len(current_blocks) == 0: genesis = self.bt.get_consecutive_blocks(uint8(1))[0] await self.full_node.blockchain.receive_block(genesis) peak = self.full_node.blockchain.get_peak() assert peak is not None mempool_bundle = await self.full_node.mempool_manager.create_bundle_from_mempool( peak.header_hash) if mempool_bundle is None: spend_bundle = None else: spend_bundle = mempool_bundle[0] current_blocks = await self.get_all_full_blocks() target = request.puzzle_hash more = self.bt.get_consecutive_blocks( 1, transaction_data=spend_bundle, farmer_reward_puzzle_hash=target, pool_reward_puzzle_hash=target, block_list_input=current_blocks, current_time=self.use_current_time, ) rr = RespondBlock(more[-1]) await self.full_node.respond_block(rr)
async def invoke(*args, **kwargs): timeout = 60 if "timeout" in kwargs: timeout = kwargs["timeout"] attribute = getattr(class_for_type(self.connection_type), attr_name, None) if attribute is None: raise AttributeError( f"Node type {self.connection_type} does not have method {attr_name}" ) msg = Message( uint8(getattr(ProtocolMessageTypes, attr_name).value), args[0], None) request_start_t = time.time() result = await self.create_request(msg, timeout) self.log.debug( f"Time for request {attr_name}: {self.get_peer_info()} = {time.time() - request_start_t}, " f"None? {result is None}") if result is not None: ret_attr = getattr(class_for_type(self.local_type), ProtocolMessageTypes(result.type).name, None) req_annotations = ret_attr.__annotations__ req = None for key in req_annotations: if key == "return" or key == "peer": continue else: req = req_annotations[key] assert req is not None result = req.from_bytes(result.data) return result
async def farm_new_block(self, request: FarmNewBlockProtocol): await self.lock.acquire() try: self.log.info("Farming new block!") current_blocks = await self.get_all_full_blocks() if len(current_blocks) == 0: genesis = self.bt.get_consecutive_blocks(uint8(1))[0] await self.full_node.blockchain.receive_block(genesis) peak = self.full_node.blockchain.get_peak() assert peak is not None bundle: Optional[SpendBundle] = await self.full_node.mempool_manager.create_bundle_from_mempool( peak.header_hash ) current_blocks = await self.get_all_full_blocks() target = request.puzzle_hash more = self.bt.get_consecutive_blocks( 1, transaction_data=bundle, farmer_reward_puzzle_hash=target, pool_reward_puzzle_hash=target, block_list_input=current_blocks, guarantee_block=True, ) rr = RespondSubBlock(more[-1]) await self.full_node.respond_sub_block(rr) except Exception as e: self.log.error(f"Error while farming block: {e}") finally: self.lock.release()
def make_sub_epoch_summary( constants: ConsensusConstants, blocks: BlockchainInterface, blocks_included_height: uint32, prev_prev_block: BlockRecord, new_difficulty: Optional[uint64], new_sub_slot_iters: Optional[uint64], ) -> SubEpochSummary: """ Creates a sub-epoch-summary object, assuming that the first block in the new sub-epoch is at height "blocks_included_height". Prev_prev_b is the second to last block in the previous sub-epoch. On a new epoch, new_difficulty and new_sub_slot_iters are also added. Args: constants: consensus constants being used for this chain blocks: dictionary from header hash to SBR of all included SBR blocks_included_height: block height in which the SES will be included prev_prev_block: second to last block in epoch new_difficulty: difficulty in new epoch new_sub_slot_iters: sub slot iters in new epoch """ assert prev_prev_block.height == blocks_included_height - 2 # First sub_epoch # This is not technically because more blocks can potentially be included than 2*MAX_SUB_SLOT_BLOCKS, # But assuming less than 128 overflow blocks get infused in the first 2 slots, it's not an issue if (blocks_included_height + constants.MAX_SUB_SLOT_BLOCKS) // constants.SUB_EPOCH_BLOCKS <= 1: return SubEpochSummary( constants.GENESIS_CHALLENGE, constants.GENESIS_CHALLENGE, uint8(0), None, None, ) curr: BlockRecord = prev_prev_block while curr.sub_epoch_summary_included is None: curr = blocks.block_record(curr.prev_hash) assert curr is not None assert curr.finished_reward_slot_hashes is not None prev_ses = curr.sub_epoch_summary_included.get_hash() return SubEpochSummary( prev_ses, curr.finished_reward_slot_hashes[-1], uint8(curr.height % constants.SUB_EPOCH_BLOCKS), new_difficulty, new_sub_slot_iters, )
class WalletType(Enum): # Condition Costs STANDARD_WALLET = uint8(0) RATE_LIMITED = uint8(1) ATOMIC_SWAP = uint8(2) AUTHORIZED_PAYEE = uint8(3) MULTI_SIG = uint8(4) CUSTODY = uint8(5) COLOURED_COIN = uint8(6) RECOVERABLE = uint8(7)
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 {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( self.harvester.constants.DIFFICULTY_CONSTANT_FACTOR, 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, plot_info.pool_contract_puzzle_hash, 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 []
async def request_proof_of_space( self, request: harvester_protocol.RequestProofOfSpace): """ The farmer requests a signature on the header hash, for one of the proofs that we found. We look up the correct plot based on the quality, lookup the proof, and return it. """ response: Optional[harvester_protocol.RespondProofOfSpace] = None try: # Using the quality find the right plot and index from our solutions challenge_hash, filename, index = self.challenge_hashes[ request.quality] except KeyError: log.warning(f"Quality {request.quality} not found") return if index is not None: proof_xs: bytes try: proof_xs = self.provers[filename].get_full_proof( challenge_hash, index) except RuntimeError: self.provers[filename] = DiskProver(filename) proof_xs = self.provers[filename].get_full_proof( challenge_hash, index) pool_pubkey = PublicKey.from_bytes( bytes.fromhex(self.plot_config["plots"][filename]["pool_pk"])) plot_pubkey = PrivateKey.from_bytes( bytes.fromhex(self.plot_config["plots"][filename] ["sk"])).get_public_key() proof_of_space: ProofOfSpace = ProofOfSpace( challenge_hash, pool_pubkey, plot_pubkey, uint8(self.provers[filename].get_size()), [uint8(b) for b in proof_xs], ) response = harvester_protocol.RespondProofOfSpace( request.quality, proof_of_space) if response: yield OutboundMessage( NodeType.FARMER, Message("respond_proof_of_space", response), Delivery.RESPOND, )
def make_sub_epoch_summary( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], blocks_included_height: uint32, prev_prev_sub_block: SubBlockRecord, new_difficulty: Optional[uint64], new_sub_slot_iters: Optional[uint64], ) -> SubEpochSummary: """ Creates a sub-epoch-summary object, assuming that the first sub-block in the new sub-epoch is at height "blocks_included_height". Prev_prev_sb is the second to last sub block in the previous sub-epoch. On a new epoch, new_difficulty and new_sub_slot_iters are also added. Args: constants: consensus constants being used for this chain sub_blocks: dictionary from header hash to SBR of all included SBR blocks_included_height: sub_block height in which the SES will be included prev_prev_sub_block: second to last sub-block in epoch new_difficulty: difficulty in new epoch new_sub_slot_iters: sub slot iters in new epoch """ assert prev_prev_sub_block.sub_block_height == blocks_included_height - 2 # If first sub_epoch. Adds MAX_SUB_SLOT_SUB_BLOCKS because blocks_included_height might be behind if (blocks_included_height + constants.MAX_SUB_SLOT_SUB_BLOCKS ) // constants.SUB_EPOCH_SUB_BLOCKS == 1: return SubEpochSummary( constants.GENESIS_SES_HASH, constants.FIRST_RC_CHALLENGE, uint8(0), None, None, ) curr: SubBlockRecord = prev_prev_sub_block while curr.sub_epoch_summary_included is None: curr = sub_blocks[curr.prev_hash] assert curr is not None assert curr.finished_reward_slot_hashes is not None prev_ses = curr.sub_epoch_summary_included.get_hash() return SubEpochSummary( prev_ses, curr.finished_reward_slot_hashes[-1], uint8(curr.sub_block_height % constants.SUB_EPOCH_SUB_BLOCKS), new_difficulty, new_sub_slot_iters, )
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, )
async def test_new_pot(self, two_nodes, wallet_blocks): full_node_1, full_node_2, server_1, server_2 = two_nodes wallet_a, wallet_receiver, _ = wallet_blocks no_unf_block = fnp.NewProofOfTime( uint32(5), bytes(32 * [1]), uint64(124512), uint8(2) ) assert len([x async for x in full_node_1.new_proof_of_time(no_unf_block)]) == 0 blocks = await get_block_path(full_node_1) blocks_new = bt.get_consecutive_blocks( test_constants, 1, blocks[:-1], 10, seed=b"1212412", ) unf_block = FullBlock( blocks_new[-1].proof_of_space, None, blocks_new[-1].header, blocks_new[-1].transactions_generator, blocks_new[-1].transactions_filter, ) unf_block_req = fnp.RespondUnfinishedBlock(unf_block) res = [x async for x in full_node_1.respond_unfinished_block(unf_block_req)] dont_have = fnp.NewProofOfTime( unf_block.height, unf_block.proof_of_space.challenge_hash, res[0].message.data.iterations_needed, uint8(2), ) assert len([x async for x in full_node_1.new_proof_of_time(dont_have)]) == 1 [x async for x in full_node_1.respond_block(fnp.RespondBlock(blocks_new[-1]))] assert blocks_new[-1].proof_of_time is not None already_have = fnp.NewProofOfTime( unf_block.height, unf_block.proof_of_space.challenge_hash, res[0].message.data.iterations_needed, blocks_new[-1].proof_of_time.witness_type, ) assert len([x async for x in full_node_1.new_proof_of_time(already_have)]) == 0
def calculate_deficit( constants: ConsensusConstants, sub_block_height: uint32, prev_sb: Optional[SubBlockRecord], overflow: bool, num_finished_sub_slots: int, ) -> uint8: """ Returns the deficit of the sub-block to be created at sub_block_height. Args: constants: consensus constants being used for this chain sub_block_height: sub-block height of the block that we care about prev_sb: previous sub-block overflow: whether or not this is an overflow sub-block num_finished_sub_slots: the number of finished slots between infusion points of prev and current """ if sub_block_height == 0: return uint8(constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1) else: assert prev_sb is not None prev_deficit: uint8 = prev_sb.deficit if prev_deficit == constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: # Prev sb must be an overflow sb. However maybe it's in a different sub-slot if overflow: if num_finished_sub_slots > 0: # We are an overflow sub-block, but in a new sub-slot, so we can decrease the deficit return uint8(prev_deficit - 1) # Still overflowed, so we cannot decrease the deficit return uint8(prev_deficit) else: # We are no longer overflow, can decrease return uint8(prev_deficit - 1) elif prev_deficit == 0: if num_finished_sub_slots == 0: return uint8(0) elif num_finished_sub_slots == 1: if overflow: return uint8(constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK) else: return uint8(constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1) else: # More than one finished sub slot, we can decrease deficit return uint8(constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1) else: return uint8(prev_deficit - 1)
async def increment_sent( self, tx_id: bytes32, name: str, send_status: MempoolInclusionStatus, err: Optional[Err], ) -> bool: """ Updates transaction sent count (Full Node has received spend_bundle and sent ack). """ current: Optional[ TransactionRecord] = await self.get_transaction_record(tx_id) if current is None: return False sent_to = current.sent_to.copy() current_peers = set() err_str = err.name if err is not None else None append_data = (name, uint8(send_status.value), err_str) for peer_id, status, error in sent_to: current_peers.add(peer_id) if name in current_peers: sent_count = uint32(current.sent) else: sent_count = uint32(current.sent + 1) sent_to.append(append_data) tx: TransactionRecord = TransactionRecord( confirmed_at_height=current.confirmed_at_height, created_at_time=current.created_at_time, to_puzzle_hash=current.to_puzzle_hash, amount=current.amount, fee_amount=current.fee_amount, confirmed=current.confirmed, sent=sent_count, spend_bundle=current.spend_bundle, additions=current.additions, removals=current.removals, wallet_id=current.wallet_id, sent_to=sent_to, trade_id=None, type=current.type, name=current.name, ) await self.add_transaction_record(tx) return True
async def new_challenge(self, new_challenge: harvester_protocol.NewChallenge): """ The harvester receives a new challenge from the farmer, and looks up the quality for any proofs of space that are are found in the plots. If proofs are found, a ChallengeResponse message is sent for each of the proofs found. """ challenge_size = len(new_challenge.challenge_hash) if challenge_size != 32: raise ValueError( f"Invalid challenge size {challenge_size}, 32 was expected") all_responses = [] for filename, prover in self.provers.items(): try: quality_strings = prover.get_qualities_for_challenge( new_challenge.challenge_hash) except RuntimeError: log.error( f"Error using prover object on {filename}. Reinitializing prover object." ) quality_strings = None try: self.provers[filename] = DiskProver(filename) quality_strings = prover.get_qualities_for_challenge( new_challenge.challenge_hash) except RuntimeError: log.error( f"Retry-Error using prover object on {filename}. Giving up." ) quality_strings = None if quality_strings is not None: for index, quality_str in enumerate(quality_strings): quality = ProofOfSpace.quality_str_to_quality( new_challenge.challenge_hash, quality_str) self.challenge_hashes[quality] = ( new_challenge.challenge_hash, filename, uint8(index), ) response: harvester_protocol.ChallengeResponse = harvester_protocol.ChallengeResponse( new_challenge.challenge_hash, quality, prover.get_size()) all_responses.append(response) for response in all_responses: yield OutboundMessage( NodeType.FARMER, Message("challenge_response", response), Delivery.RESPOND, )
def test_is_overflow_sub_block(self): assert not is_overflow_sub_block(test_constants, uint8(27)) assert not is_overflow_sub_block(test_constants, uint8(28)) assert is_overflow_sub_block(test_constants, uint8(29)) assert is_overflow_sub_block(test_constants, uint8(30)) assert is_overflow_sub_block(test_constants, uint8(31)) with raises(ValueError): assert is_overflow_sub_block(test_constants, uint8(32))
async def test_invalid_pos_hash(self, initial_blockchain): blocks, b = initial_blockchain bad_pos_proof = bytearray([i for i in blocks[9].proof_of_space.proof]) bad_pos_proof[0] = uint8((bad_pos_proof[0] + 1) % 256) bad_pos = ProofOfSpace( blocks[9].proof_of_space.challenge_hash, blocks[9].proof_of_space.pool_public_key, blocks[9].proof_of_space.plot_public_key, blocks[9].proof_of_space.size, bytes(bad_pos_proof), ) 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, bad_pos.get_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.farmer_rewards_puzzle_hash, blocks[9].header.data.total_transaction_fees, blocks[9].header.data.pool_target, blocks[9].header.data.aggregated_signature, blocks[9].header.data.cost, blocks[9].header.data.extension_data, blocks[9].header.data.generator_hash, ) # Proof of space has invalid block_bad = FullBlock( blocks[9].proof_of_space, blocks[9].proof_of_time, Header( new_header_data, bt.get_plot_signature( new_header_data, blocks[9].proof_of_space.plot_public_key ), ), 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_POSPACE_HASH
def test_StrictDataClassLists(self): @dataclass(frozen=True) @strictdataclass class TestClass: a: List[int] b: List[List[uint8]] assert TestClass([1, 2, 3], [[uint8(200), uint8(25)], [uint8(25)]]) try: TestClass([1, 2, 3], [[uint8(200), uint8(25)], [uint8(25)]]) assert False except AssertionError: pass try: TestClass([1, 2, 3], [uint8(200), uint8(25)]) # type: ignore assert False except ValueError: pass
async def increment_sent( self, id: bytes32, name: str, send_status: MempoolInclusionStatus, err: Optional[Err], ) -> bool: """ Updates transaction sent count (Full Node has received spend_bundle and sent ack). """ current: Optional[ TransactionRecord] = await self.get_transaction_record(id) if current is None: return False sent_to = current.sent_to.copy() err_str = err.name if err is not None else None append_data = (name, uint8(send_status.value), err_str) # Don't increment count if it's already sent to othis peer if append_data in sent_to: return False sent_to.append(append_data) tx: TransactionRecord = TransactionRecord( confirmed_at_index=current.confirmed_at_index, created_at_time=current.created_at_time, to_puzzle_hash=current.to_puzzle_hash, amount=current.amount, fee_amount=current.fee_amount, incoming=current.incoming, confirmed=current.confirmed, sent=uint32(current.sent + 1), spend_bundle=current.spend_bundle, additions=current.additions, removals=current.removals, wallet_id=current.wallet_id, sent_to=sent_to, trade_id=None, ) await self.add_transaction_record(tx) return True
async def lookup_challenge( filename: Path, prover: DiskProver ) -> List[harvester_protocol.ChallengeResponse]: # Exectures a DiskProverLookup in a threadpool, and returns responses all_responses: List[harvester_protocol.ChallengeResponse] = [] quality_strings = await loop.run_in_executor( self.executor, blocking_lookup, filename, prover) if quality_strings is not None: for index, quality_str in enumerate(quality_strings): response: harvester_protocol.ChallengeResponse = harvester_protocol.ChallengeResponse( new_challenge.challenge_hash, str(filename), uint8(index), quality_str, prover.get_size(), ) all_responses.append(response) return all_responses
def get_vdf_info_and_proof( constants: ConsensusConstants, vdf_input: ClassgroupElement, challenge_hash: bytes32, number_iters: uint64, ) -> Tuple[VDFInfo, VDFProof]: form_size = ClassgroupElement.get_size(constants) result: bytes = prove( bytes(challenge_hash), vdf_input.data, constants.DISCRIMINANT_SIZE_BITS, number_iters, ) output = ClassgroupElement.from_bytes(result[:form_size]) proof_bytes = result[form_size:2 * form_size] return VDFInfo(challenge_hash, number_iters, output), VDFProof(uint8(0), proof_bytes)
def test_win_percentage(self): """ Tests that the percentage of blocks won is proportional to the space of each farmer, with the assumption that all farmers have access to the same VDF speed. """ farmer_ks = { uint8(32): 100, uint8(33): 100, uint8(34): 100, uint8(35): 100, uint8(36): 100, } farmer_space = { k: _expected_plot_size(uint8(k)) * count for k, count in farmer_ks.items() } total_space = sum(farmer_space.values()) percentage_space = { k: float(sp / total_space) for k, sp in farmer_space.items() } wins = {k: 0 for k in farmer_ks.keys()} total_slots = 50 num_sps = 16 sp_interval_iters = uint64(100000000 // 32) difficulty = uint64(500000000000) for slot_index in range(total_slots): total_wins_in_slot = 0 for sp_index in range(num_sps): sp_hash = std_hash( slot_index.to_bytes(4, "big") + sp_index.to_bytes(4, "big")) for k, count in farmer_ks.items(): for farmer_index in range(count): quality = std_hash( slot_index.to_bytes(4, "big") + k.to_bytes(1, "big") + bytes(farmer_index)) required_iters = calculate_iterations_quality( 2**25, quality, k, difficulty, sp_hash) if required_iters < sp_interval_iters: wins[k] += 1 total_wins_in_slot += 1 win_percentage = { k: wins[k] / sum(wins.values()) for k in farmer_ks.keys() } for k in farmer_ks.keys(): # Win rate is proportional to percentage of space assert abs(win_percentage[k] - percentage_space[k]) < 0.01
async def increment_sent( self, id: bytes32, name: str, send_status: MempoolInclusionStatus, err: Optional[Err], ) -> bool: """ Updates trade sent count (Full Node has received spend_bundle and sent ack). """ current: Optional[TradeRecord] = await self.get_trade_record(id) if current is None: return False sent_to = current.sent_to.copy() err_str = err.name if err is not None else None append_data = (name, uint8(send_status.value), err_str) # Don't increment count if it's already sent to this peer if append_data in sent_to: return False sent_to.append(append_data) tx: TradeRecord = TradeRecord( confirmed_at_index=current.confirmed_at_index, accepted_at_time=current.accepted_at_time, created_at_time=current.created_at_time, my_offer=current.my_offer, sent=uint32(current.sent + 1), spend_bundle=current.spend_bundle, tx_spend_bundle=current.tx_spend_bundle, additions=current.additions, removals=current.removals, trade_id=current.trade_id, status=current.status, sent_to=sent_to, ) await self.add_trade_record(tx) return True
async def test_invalid_pos(self, initial_blockchain): blocks, b = initial_blockchain bad_pos = [i for i in blocks[9].header_block.proof_of_space.proof] bad_pos[0] = uint8((bad_pos[0] + 1) % 256) # Proof of space invalid block_bad = FullBlock( HeaderBlock( ProofOfSpace( blocks[9].header_block.proof_of_space.challenge_hash, blocks[9].header_block.proof_of_space.pool_pubkey, blocks[9].header_block.proof_of_space.plot_pubkey, blocks[9].header_block.proof_of_space.size, bad_pos, ), blocks[9].header_block.proof_of_time, blocks[9].header_block.challenge, blocks[9].header_block.header, ), blocks[9].body, ) assert (await b.receive_block(block_bad)) == ReceiveBlockResult.INVALID_BLOCK
def validate_unfinished_header_block( constants: ConsensusConstants, blocks: BlockchainInterface, header_block: UnfinishedHeaderBlock, check_filter: bool, expected_difficulty: uint64, expected_sub_slot_iters: uint64, skip_overflow_last_ss_validation: bool = False, skip_vdf_is_valid: bool = False, ) -> Tuple[Optional[uint64], Optional[ValidationError]]: """ Validates an unfinished header block. This is a block without the infusion VDFs (unfinished) and without transactions and transaction info (header). Returns (required_iters, error). This method is meant to validate only the unfinished part of the block. However, the finished_sub_slots refers to all sub-slots that were finishes from the previous block's infusion point, up to this blocks infusion point. Therefore, in the case where this is an overflow block, and the last sub-slot is not yet released, header_block.finished_sub_slots will be missing one sub-slot. In this case, skip_overflow_last_ss_validation must be set to True. This will skip validation of end of slots, sub-epochs, and lead to other small tweaks in validation. """ # 1. Check that the previous block exists in the blockchain, or that it is correct prev_b = blocks.try_block_record(header_block.prev_header_hash) genesis_block = prev_b is None if genesis_block and header_block.prev_header_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) overflow = is_overflow_block( constants, header_block.reward_chain_block.signage_point_index) if skip_overflow_last_ss_validation and overflow: if final_eos_is_already_included(header_block, blocks, expected_sub_slot_iters): skip_overflow_last_ss_validation = False finished_sub_slots_since_prev = len( header_block.finished_sub_slots) else: finished_sub_slots_since_prev = len( header_block.finished_sub_slots) + 1 else: finished_sub_slots_since_prev = len(header_block.finished_sub_slots) new_sub_slot: bool = finished_sub_slots_since_prev > 0 can_finish_se: bool = False can_finish_epoch: bool = False if genesis_block: height: uint32 = uint32(0) assert expected_difficulty == constants.DIFFICULTY_STARTING assert expected_sub_slot_iters == constants.SUB_SLOT_ITERS_STARTING else: assert prev_b is not None height = uint32(prev_b.height + 1) if prev_b.sub_epoch_summary_included is not None: can_finish_se, can_finish_epoch = False, False else: if new_sub_slot: can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch( constants, prev_b.height, prev_b.deficit, blocks, prev_b.prev_hash, False, ) else: can_finish_se = False can_finish_epoch = False # 2. Check finished slots that have been crossed since prev_b ses_hash: Optional[bytes32] = None if new_sub_slot and not skip_overflow_last_ss_validation: # Finished a slot(s) since previous block. The first sub-slot must have at least one block, and all # subsequent sub-slots must be empty for finished_sub_slot_n, sub_slot in enumerate( header_block.finished_sub_slots): # Start of slot challenge is fetched from SP challenge_hash: bytes32 = sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf.challenge if finished_sub_slot_n == 0: if genesis_block: # 2a. check sub-slot challenge hash for genesis block if challenge_hash != constants.GENESIS_CHALLENGE: return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) else: assert prev_b is not None curr: BlockRecord = prev_b while not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) assert curr.finished_challenge_slot_hashes is not None # 2b. check sub-slot challenge hash for non-genesis block if not curr.finished_challenge_slot_hashes[ -1] == challenge_hash: print(curr.finished_challenge_slot_hashes[-1], challenge_hash) return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) else: # 2c. check sub-slot challenge hash for empty slot if (not header_block.finished_sub_slots[ finished_sub_slot_n - 1].challenge_chain.get_hash() == challenge_hash): return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) if genesis_block: # 2d. Validate that genesis block has no ICC if sub_slot.infused_challenge_chain is not None: return None, ValidationError(Err.SHOULD_NOT_HAVE_ICC) else: assert prev_b is not None icc_iters_committed: Optional[uint64] = None icc_iters_proof: Optional[uint64] = None icc_challenge_hash: Optional[bytes32] = None icc_vdf_input = None if prev_b.deficit < constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: # There should be no ICC chain if the last block's deficit is 16 # Prev sb's deficit is 0, 1, 2, 3, or 4 if finished_sub_slot_n == 0: # This is the first sub slot after the last sb, which must have deficit 1-4, and thus an ICC curr = prev_b while not curr.is_challenge_block( constants) and not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) if curr.is_challenge_block(constants): icc_challenge_hash = curr.challenge_block_info_hash icc_iters_committed = uint64( prev_b.sub_slot_iters - curr.ip_iters(constants)) else: assert curr.finished_infused_challenge_slot_hashes is not None icc_challenge_hash = curr.finished_infused_challenge_slot_hashes[ -1] icc_iters_committed = prev_b.sub_slot_iters icc_iters_proof = uint64(prev_b.sub_slot_iters - prev_b.ip_iters(constants)) if prev_b.is_challenge_block(constants): icc_vdf_input = ClassgroupElement.get_default_element( ) else: icc_vdf_input = prev_b.infused_challenge_vdf_output else: # This is not the first sub slot after the last block, so we might not have an ICC if (header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.deficit < constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK): finished_ss = header_block.finished_sub_slots[ finished_sub_slot_n - 1] assert finished_ss.infused_challenge_chain is not None # Only sets the icc iff the previous sub slots deficit is 4 or less icc_challenge_hash = finished_ss.infused_challenge_chain.get_hash( ) icc_iters_committed = prev_b.sub_slot_iters icc_iters_proof = icc_iters_committed icc_vdf_input = ClassgroupElement.get_default_element( ) # 2e. Validate that there is not icc iff icc_challenge hash is None assert (sub_slot.infused_challenge_chain is None) == (icc_challenge_hash is None) if sub_slot.infused_challenge_chain is not None: assert icc_vdf_input is not None assert icc_iters_proof is not None assert icc_challenge_hash is not None assert sub_slot.proofs.infused_challenge_chain_slot_proof is not None # 2f. Check infused challenge chain sub-slot VDF # Only validate from prev_b to optimize target_vdf_info = VDFInfo( icc_challenge_hash, icc_iters_proof, sub_slot.infused_challenge_chain. infused_challenge_chain_end_of_slot_vdf.output, ) if sub_slot.infused_challenge_chain.infused_challenge_chain_end_of_slot_vdf != dataclasses.replace( target_vdf_info, number_of_iterations=icc_iters_committed, ): return None, ValidationError(Err.INVALID_ICC_EOS_VDF) if not skip_vdf_is_valid and not sub_slot.proofs.infused_challenge_chain_slot_proof.is_valid( constants, icc_vdf_input, target_vdf_info, None): return None, ValidationError(Err.INVALID_ICC_EOS_VDF) if sub_slot.reward_chain.deficit == constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: # 2g. Check infused challenge sub-slot hash in challenge chain, deficit 16 if (sub_slot.infused_challenge_chain.get_hash() != sub_slot.challenge_chain. infused_challenge_chain_sub_slot_hash): return None, ValidationError( Err.INVALID_ICC_HASH_CC) else: # 2h. Check infused challenge sub-slot hash not included for other deficits if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError( Err.INVALID_ICC_HASH_CC) # 2i. Check infused challenge sub-slot hash in reward sub-slot if (sub_slot.infused_challenge_chain.get_hash() != sub_slot.reward_chain. infused_challenge_chain_sub_slot_hash): return None, ValidationError(Err.INVALID_ICC_HASH_RC) else: # 2j. If no icc, check that the cc doesn't include it if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError(Err.INVALID_ICC_HASH_CC) # 2k. If no icc, check that the cc doesn't include it if sub_slot.reward_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError(Err.INVALID_ICC_HASH_RC) if sub_slot.challenge_chain.subepoch_summary_hash is not None: assert ses_hash is None # Only one of the slots can have it ses_hash = sub_slot.challenge_chain.subepoch_summary_hash # 2l. check sub-epoch summary hash is None for empty slots if finished_sub_slot_n != 0: if sub_slot.challenge_chain.subepoch_summary_hash is not None: return None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH) if can_finish_epoch and sub_slot.challenge_chain.subepoch_summary_hash is not None: # 2m. Check new difficulty and ssi if sub_slot.challenge_chain.new_sub_slot_iters != expected_sub_slot_iters: return None, ValidationError( Err.INVALID_NEW_SUB_SLOT_ITERS) if sub_slot.challenge_chain.new_difficulty != expected_difficulty: return None, ValidationError(Err.INVALID_NEW_DIFFICULTY) else: # 2n. Check new difficulty and ssi are None if we don't finish epoch if sub_slot.challenge_chain.new_sub_slot_iters is not None: return None, ValidationError( Err.INVALID_NEW_SUB_SLOT_ITERS) if sub_slot.challenge_chain.new_difficulty is not None: return None, ValidationError(Err.INVALID_NEW_DIFFICULTY) # 2o. Check challenge sub-slot hash in reward sub-slot if sub_slot.challenge_chain.get_hash( ) != sub_slot.reward_chain.challenge_chain_sub_slot_hash: return ( None, ValidationError( Err.INVALID_CHALLENGE_SLOT_HASH_RC, "sub-slot hash in reward sub-slot mismatch", ), ) eos_vdf_iters: uint64 = expected_sub_slot_iters cc_start_element: ClassgroupElement = ClassgroupElement.get_default_element( ) cc_eos_vdf_challenge: bytes32 = challenge_hash if genesis_block: if finished_sub_slot_n == 0: # First block, one empty slot. prior_point is the initial challenge rc_eos_vdf_challenge: bytes32 = constants.GENESIS_CHALLENGE cc_eos_vdf_challenge = constants.GENESIS_CHALLENGE else: # First block, but have at least two empty slots rc_eos_vdf_challenge = header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.get_hash() else: assert prev_b is not None if finished_sub_slot_n == 0: # No empty slots, so the starting point of VDF is the last reward block. Uses # the same IPS as the previous block, since it's the same slot rc_eos_vdf_challenge = prev_b.reward_infusion_new_challenge eos_vdf_iters = uint64(prev_b.sub_slot_iters - prev_b.ip_iters(constants)) cc_start_element = prev_b.challenge_vdf_output else: # At least one empty slot, so use previous slot hash. IPS might change because it's a new slot rc_eos_vdf_challenge = header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.get_hash() # 2p. Check end of reward slot VDF target_vdf_info = VDFInfo( rc_eos_vdf_challenge, eos_vdf_iters, sub_slot.reward_chain.end_of_slot_vdf.output, ) if not skip_vdf_is_valid and not sub_slot.proofs.reward_chain_slot_proof.is_valid( constants, ClassgroupElement.get_default_element(), sub_slot.reward_chain.end_of_slot_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_EOS_VDF) # 2q. Check challenge chain sub-slot VDF partial_cc_vdf_info = VDFInfo( cc_eos_vdf_challenge, eos_vdf_iters, sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf. output, ) if genesis_block: cc_eos_vdf_info_iters = constants.SUB_SLOT_ITERS_STARTING else: assert prev_b is not None if finished_sub_slot_n == 0: cc_eos_vdf_info_iters = prev_b.sub_slot_iters else: cc_eos_vdf_info_iters = expected_sub_slot_iters # Check that the modified data is correct if sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf != dataclasses.replace( partial_cc_vdf_info, number_of_iterations=cc_eos_vdf_info_iters, ): return None, ValidationError( Err.INVALID_CC_EOS_VDF, "wrong challenge chain end of slot vdf") # Pass in None for target info since we are only checking the proof from the temporary point, # but the challenge_chain_end_of_slot_vdf actually starts from the start of slot (for light clients) if not skip_vdf_is_valid and not sub_slot.proofs.challenge_chain_slot_proof.is_valid( constants, cc_start_element, partial_cc_vdf_info, None): return None, ValidationError(Err.INVALID_CC_EOS_VDF) if genesis_block: # 2r. Check deficit (MIN_SUB.. deficit edge case for genesis block) if sub_slot.reward_chain.deficit != constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: return ( None, ValidationError( Err.INVALID_DEFICIT, f"genesis, expected deficit {constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK}", ), ) else: assert prev_b is not None if prev_b.deficit == 0: # 2s. If prev sb had deficit 0, resets deficit to MIN_BLOCK_PER_CHALLENGE_BLOCK if sub_slot.reward_chain.deficit != constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: log.error(constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK, ) return ( None, ValidationError( Err.INVALID_DEFICIT, f"expected deficit {constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK}, saw " f"{sub_slot.reward_chain.deficit}", ), ) else: # 2t. Otherwise, deficit stays the same at the slot ends, cannot reset until 0 if sub_slot.reward_chain.deficit != prev_b.deficit: return None, ValidationError( Err.INVALID_DEFICIT, "deficit is wrong at slot end") # 3. Check sub-epoch summary # Note that the subepoch summary is the summary of the previous subepoch (not the one that just finished) if not skip_overflow_last_ss_validation: if ses_hash is not None: # 3a. Check that genesis block does not have sub-epoch summary if genesis_block: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH, "genesis with sub-epoch-summary hash", ), ) assert prev_b is not None # 3b. Check that we finished a slot and we finished a sub-epoch if not new_sub_slot or not can_finish_se: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH, f"new sub-slot: {new_sub_slot} finishes sub-epoch {can_finish_se}", ), ) # 3c. Check the actual sub-epoch is correct expected_sub_epoch_summary = make_sub_epoch_summary( constants, blocks, height, blocks.block_record(prev_b.prev_hash), expected_difficulty if can_finish_epoch else None, expected_sub_slot_iters if can_finish_epoch else None, ) expected_hash = expected_sub_epoch_summary.get_hash() if expected_hash != ses_hash: log.error(f"{expected_sub_epoch_summary}") return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY, f"expected ses hash: {expected_hash} got {ses_hash} ", ), ) elif new_sub_slot and not genesis_block: # 3d. Check that we don't have to include a sub-epoch summary if can_finish_se or can_finish_epoch: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY, "block finishes sub-epoch but ses-hash is None", ), ) # 4. Check if the number of blocks is less than the max if not new_sub_slot and not genesis_block: assert prev_b is not None num_blocks = 2 # This includes the current block and the prev block curr = prev_b while not curr.first_in_sub_slot: num_blocks += 1 curr = blocks.block_record(curr.prev_hash) if num_blocks > constants.MAX_SUB_SLOT_BLOCKS: return None, ValidationError(Err.TOO_MANY_BLOCKS) # If block state is correct, we should always find a challenge here # This computes what the challenge should be for this block challenge = get_block_challenge( constants, header_block, blocks, genesis_block, overflow, skip_overflow_last_ss_validation, ) # 5a. Check proof of space if challenge != header_block.reward_chain_block.pos_ss_cc_challenge_hash: log.error(f"Finished slots: {header_block.finished_sub_slots}") log.error( f"Data: {genesis_block} {overflow} {skip_overflow_last_ss_validation} {header_block.total_iters} " f"{header_block.reward_chain_block.signage_point_index}" f"Prev: {prev_b}") log.error( f"Challenge {challenge} provided {header_block.reward_chain_block.pos_ss_cc_challenge_hash}" ) return None, ValidationError(Err.INVALID_CC_CHALLENGE) # 5b. Check proof of space if header_block.reward_chain_block.challenge_chain_sp_vdf is None: # Edge case of first sp (start of slot), where sp_iters == 0 cc_sp_hash: bytes32 = challenge else: cc_sp_hash = header_block.reward_chain_block.challenge_chain_sp_vdf.output.get_hash( ) q_str: Optional[ bytes32] = header_block.reward_chain_block.proof_of_space.verify_and_get_quality_string( constants, challenge, cc_sp_hash) if q_str is None: return None, ValidationError(Err.INVALID_POSPACE) # 6. check signage point index # no need to check negative values as this is uint 8 if header_block.reward_chain_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT: return None, ValidationError(Err.INVALID_SP_INDEX) # Note that required iters might be from the previous slot (if we are in an overflow block) required_iters: uint64 = calculate_iterations_quality( constants.DIFFICULTY_CONSTANT_FACTOR, q_str, header_block.reward_chain_block.proof_of_space.size, expected_difficulty, cc_sp_hash, ) # 7. check signage point index # no need to check negative values as this is uint8. (Assumes types are checked) if header_block.reward_chain_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT: return None, ValidationError(Err.INVALID_SP_INDEX) # 8a. check signage point index 0 has no cc sp if (header_block.reward_chain_block.signage_point_index == 0) != ( header_block.reward_chain_block.challenge_chain_sp_vdf is None): return None, ValidationError(Err.INVALID_SP_INDEX) # 8b. check signage point index 0 has no rc sp if (header_block.reward_chain_block.signage_point_index == 0) != ( header_block.reward_chain_block.reward_chain_sp_vdf is None): return None, ValidationError(Err.INVALID_SP_INDEX) sp_iters: uint64 = calculate_sp_iters( constants, expected_sub_slot_iters, header_block.reward_chain_block.signage_point_index, ) ip_iters: uint64 = calculate_ip_iters( constants, expected_sub_slot_iters, header_block.reward_chain_block.signage_point_index, required_iters, ) if header_block.reward_chain_block.challenge_chain_sp_vdf is None: # Blocks with very low required iters are not overflow blocks assert not overflow # 9. Check no overflows in the first sub-slot of a new epoch # (although they are OK in the second sub-slot), this is important if overflow and can_finish_epoch: if finished_sub_slots_since_prev < 2: return None, ValidationError( Err.NO_OVERFLOWS_IN_FIRST_SUB_SLOT_NEW_EPOCH) # 10. Check total iters if genesis_block: total_iters: uint128 = uint128(expected_sub_slot_iters * finished_sub_slots_since_prev) else: assert prev_b is not None if new_sub_slot: total_iters = prev_b.total_iters # Add the rest of the slot of prev_b total_iters = uint128(total_iters + prev_b.sub_slot_iters - prev_b.ip_iters(constants)) # Add other empty slots total_iters = uint128(total_iters + (expected_sub_slot_iters * (finished_sub_slots_since_prev - 1))) else: # Slot iters is guaranteed to be the same for header_block and prev_b # This takes the beginning of the slot, and adds ip_iters total_iters = uint128(prev_b.total_iters - prev_b.ip_iters(constants)) total_iters = uint128(total_iters + ip_iters) if total_iters != header_block.reward_chain_block.total_iters: return ( None, ValidationError( Err.INVALID_TOTAL_ITERS, f"expected {total_iters} got {header_block.reward_chain_block.total_iters}", ), ) sp_total_iters: uint128 = uint128(total_iters - ip_iters + sp_iters - ( expected_sub_slot_iters if overflow else 0)) if overflow and skip_overflow_last_ss_validation: dummy_vdf_info = VDFInfo( bytes32([0] * 32), uint64(1), ClassgroupElement.get_default_element(), ) dummy_sub_slot = EndOfSubSlotBundle( ChallengeChainSubSlot(dummy_vdf_info, None, None, None, None), None, RewardChainSubSlot(dummy_vdf_info, bytes32([0] * 32), None, uint8(0)), SubSlotProofs(VDFProof(uint8(0), b""), None, VDFProof(uint8(0), b"")), ) sub_slots_to_pass_in = header_block.finished_sub_slots + [ dummy_sub_slot ] else: sub_slots_to_pass_in = header_block.finished_sub_slots ( cc_vdf_challenge, rc_vdf_challenge, cc_vdf_input, rc_vdf_input, cc_vdf_iters, rc_vdf_iters, ) = get_signage_point_vdf_info( constants, sub_slots_to_pass_in, overflow, prev_b, blocks, sp_total_iters, sp_iters, ) # 11. Check reward chain sp proof if sp_iters != 0: assert (header_block.reward_chain_block.reward_chain_sp_vdf is not None and header_block.reward_chain_sp_proof is not None) target_vdf_info = VDFInfo( rc_vdf_challenge, rc_vdf_iters, header_block.reward_chain_block.reward_chain_sp_vdf.output, ) if not skip_vdf_is_valid and not header_block.reward_chain_sp_proof.is_valid( constants, rc_vdf_input, header_block.reward_chain_block.reward_chain_sp_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_SP_VDF) rc_sp_hash = header_block.reward_chain_block.reward_chain_sp_vdf.output.get_hash( ) else: # Edge case of first sp (start of slot), where sp_iters == 0 assert overflow is not None if header_block.reward_chain_block.reward_chain_sp_vdf is not None: return None, ValidationError(Err.INVALID_RC_SP_VDF) if new_sub_slot: rc_sp_hash = header_block.finished_sub_slots[ -1].reward_chain.get_hash() else: if genesis_block: rc_sp_hash = constants.GENESIS_CHALLENGE else: assert prev_b is not None curr = prev_b while not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) assert curr.finished_reward_slot_hashes is not None rc_sp_hash = curr.finished_reward_slot_hashes[-1] # 12. Check reward chain sp signature if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, rc_sp_hash, header_block.reward_chain_block.reward_chain_sp_signature, ): return None, ValidationError(Err.INVALID_RC_SIGNATURE) # 13. Check cc sp vdf if sp_iters != 0: assert header_block.reward_chain_block.challenge_chain_sp_vdf is not None assert header_block.challenge_chain_sp_proof is not None target_vdf_info = VDFInfo( cc_vdf_challenge, cc_vdf_iters, header_block.reward_chain_block.challenge_chain_sp_vdf.output, ) if header_block.reward_chain_block.challenge_chain_sp_vdf != dataclasses.replace( target_vdf_info, number_of_iterations=sp_iters, ): return None, ValidationError(Err.INVALID_CC_SP_VDF) if not skip_vdf_is_valid and not header_block.challenge_chain_sp_proof.is_valid( constants, cc_vdf_input, target_vdf_info, None): return None, ValidationError(Err.INVALID_CC_SP_VDF) else: assert overflow is not None if header_block.reward_chain_block.challenge_chain_sp_vdf is not None: return None, ValidationError(Err.INVALID_CC_SP_VDF) # 14. Check cc sp sig if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, cc_sp_hash, header_block.reward_chain_block.challenge_chain_sp_signature, ): return None, ValidationError(Err.INVALID_CC_SIGNATURE, "invalid cc sp sig") # 15. Check is_transaction_block if genesis_block: if header_block.foliage.foliage_transaction_block_hash is None: return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK, "invalid genesis") else: assert prev_b is not None # Finds the previous block curr = prev_b while not curr.is_transaction_block: curr = blocks.block_record(curr.prev_hash) # The first block to have an sp > the last tx block's infusion iters, is a tx block if overflow: our_sp_total_iters: uint128 = uint128(total_iters - ip_iters + sp_iters - expected_sub_slot_iters) else: our_sp_total_iters = uint128(total_iters - ip_iters + sp_iters) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage.foliage_transaction_block_hash is not None): return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage.foliage_transaction_block_signature is not None): return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK) # 16. Check foliage block signature by plot key if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, header_block.foliage.foliage_block_data.get_hash(), header_block.foliage.foliage_block_data_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 17. Check foliage block signature by plot key if header_block.foliage.foliage_transaction_block_hash is not None: if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, header_block.foliage.foliage_transaction_block_hash, header_block.foliage.foliage_transaction_block_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 18. Check unfinished reward chain block hash if (header_block.reward_chain_block.get_hash() != header_block.foliage. foliage_block_data.unfinished_reward_block_hash): return None, ValidationError(Err.INVALID_URSB_HASH) # 19. Check pool target max height if (header_block.foliage.foliage_block_data.pool_target.max_height != 0 and header_block.foliage.foliage_block_data.pool_target.max_height < height): return None, ValidationError(Err.OLD_POOL_TARGET) # 20a. Check pre-farm puzzle hashes for genesis block. if genesis_block: if (header_block.foliage.foliage_block_data.pool_target.puzzle_hash != constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH): log.error( f"Pool target {header_block.foliage.foliage_block_data.pool_target} hb {header_block}" ) return None, ValidationError(Err.INVALID_PREFARM) if (header_block.foliage.foliage_block_data.farmer_reward_puzzle_hash != constants.GENESIS_PRE_FARM_FARMER_PUZZLE_HASH): return None, ValidationError(Err.INVALID_PREFARM) else: # 20b. If pospace has a pool pk, heck pool target signature. Should not check this for genesis block. if header_block.reward_chain_block.proof_of_space.pool_public_key is not None: assert header_block.reward_chain_block.proof_of_space.pool_contract_puzzle_hash is None if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space. pool_public_key, bytes(header_block.foliage.foliage_block_data.pool_target), header_block.foliage.foliage_block_data.pool_signature, ): return None, ValidationError(Err.INVALID_POOL_SIGNATURE) else: # 20c. Otherwise, the plot is associated with a contract puzzle hash, not a public key assert header_block.reward_chain_block.proof_of_space.pool_contract_puzzle_hash is not None if (header_block.foliage.foliage_block_data.pool_target.puzzle_hash != header_block.reward_chain_block.proof_of_space. pool_contract_puzzle_hash): return None, ValidationError(Err.INVALID_POOL_TARGET) # 21. Check extension data if applicable. None for mainnet. # 22. Check if foliage block is present if (header_block.foliage.foliage_transaction_block_hash is not None) != (header_block.foliage_transaction_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if (header_block.foliage.foliage_transaction_block_signature is not None) != (header_block.foliage_transaction_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if header_block.foliage_transaction_block is not None: # 23. Check foliage block hash if header_block.foliage_transaction_block.get_hash( ) != header_block.foliage.foliage_transaction_block_hash: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_HASH) if genesis_block: # 24a. Check prev block hash for genesis if header_block.foliage_transaction_block.prev_transaction_block_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) else: assert prev_b is not None # 24b. Check prev block hash for non-genesis curr_b: BlockRecord = prev_b while not curr_b.is_transaction_block: curr_b = blocks.block_record(curr_b.prev_hash) if not header_block.foliage_transaction_block.prev_transaction_block_hash == curr_b.header_hash: log.error( f"Prev BH: {header_block.foliage_transaction_block.prev_transaction_block_hash} " f"{curr_b.header_hash} curr sb: {curr_b}") return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) # 25. The filter hash in the Foliage Block must be the hash of the filter if check_filter: if header_block.foliage_transaction_block.filter_hash != std_hash( header_block.transactions_filter): return None, ValidationError( Err.INVALID_TRANSACTIONS_FILTER_HASH) # 26. The timestamp in Foliage Block must comply with the timestamp rules if prev_b is not None: last_timestamps: List[uint64] = [] curr_b = blocks.block_record( header_block.foliage_transaction_block. prev_transaction_block_hash) assert curr_b.timestamp is not None while len(last_timestamps) < constants.NUMBER_OF_TIMESTAMPS: last_timestamps.append(curr_b.timestamp) fetched: Optional[BlockRecord] = blocks.try_block_record( curr_b.prev_transaction_block_hash) if not fetched: break curr_b = fetched if len(last_timestamps) != constants.NUMBER_OF_TIMESTAMPS: # For blocks 1 to 10, average timestamps of all previous blocks assert curr_b.height == 0 prev_time: uint64 = uint64( int(sum(last_timestamps) // len(last_timestamps))) if header_block.foliage_transaction_block.timestamp <= prev_time: return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_PAST) if header_block.foliage_transaction_block.timestamp > int( time.time() + constants.MAX_FUTURE_TIME): return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_FUTURE) return required_iters, None # Valid unfinished header block
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 type(cls) -> uint8: return uint8(WalletType.RATE_LIMITED)
def type(cls) -> uint8: return uint8(WalletType.STANDARD_WALLET)
async def _do_process_communication( self, chain: Chain, challenge: bytes32, initial_form: ClassgroupElement, ip: str, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, ): disc: int = create_discriminant(challenge, self.constants.DISCRIMINANT_SIZE_BITS) try: # Depending on the flags 'fast_algorithm' and 'sanitizer_mode', # the timelord tells the vdf_client what to execute. async with self.lock: if self.config["fast_algorithm"]: # Run n-wesolowski (fast) algorithm. writer.write(b"N") else: # Run two-wesolowski (slow) algorithm. writer.write(b"T") await writer.drain() prefix = str(len(str(disc))) if len(prefix) == 1: prefix = "00" + prefix if len(prefix) == 2: prefix = "0" + prefix async with self.lock: writer.write((prefix + str(disc)).encode()) await writer.drain() # Send initial_form prefixed with its length. async with self.lock: writer.write(bytes([len(initial_form.data)]) + initial_form.data) await writer.drain() try: ok = await reader.readexactly(2) except (asyncio.IncompleteReadError, ConnectionResetError, Exception) as e: log.warning(f"{type(e)} {e}") async with self.lock: self.vdf_failures.append(chain) self.vdf_failures_count += 1 return if ok.decode() != "OK": return log.info("Got handshake with VDF client.") async with self.lock: self.allows_iters.append(chain) # Listen to the client until "STOP" is received. while True: try: data = await reader.readexactly(4) except ( asyncio.IncompleteReadError, ConnectionResetError, Exception, ) as e: log.warning(f"{type(e)} {e}") async with self.lock: self.vdf_failures.append(chain) self.vdf_failures_count += 1 break msg = "" try: msg = data.decode() except Exception: pass if msg == "STOP": log.info(f"Stopped client running on ip {ip}.") async with self.lock: writer.write(b"ACK") await writer.drain() break else: try: # This must be a proof, 4 bytes is length prefix length = int.from_bytes(data, "big") proof = await reader.readexactly(length) stdout_bytes_io: io.BytesIO = io.BytesIO(bytes.fromhex(proof.decode())) except ( asyncio.IncompleteReadError, ConnectionResetError, Exception, ) as e: log.warning(f"{type(e)} {e}") async with self.lock: self.vdf_failures.append(chain) self.vdf_failures_count += 1 break iterations_needed = uint64(int.from_bytes(stdout_bytes_io.read(8), "big", signed=True)) y_size_bytes = stdout_bytes_io.read(8) y_size = uint64(int.from_bytes(y_size_bytes, "big", signed=True)) y_bytes = stdout_bytes_io.read(y_size) witness_type = uint8(int.from_bytes(stdout_bytes_io.read(1), "big", signed=True)) proof_bytes: bytes = stdout_bytes_io.read() # Verifies our own proof just in case form_size = ClassgroupElement.get_size(self.constants) output = ClassgroupElement.from_bytes(y_bytes[:form_size]) time_taken = time.time() - self.chain_start_time[chain] ips = int(iterations_needed / time_taken * 10) / 10 log.info( f"Finished PoT chall:{challenge[:10].hex()}.. {iterations_needed}" f" iters, " f"Estimated IPS: {ips}, Chain: {chain}" ) vdf_info: VDFInfo = VDFInfo( challenge, iterations_needed, output, ) vdf_proof: VDFProof = VDFProof( witness_type, proof_bytes, ) if not vdf_proof.is_valid(self.constants, initial_form, vdf_info): log.error("Invalid proof of time!") async with self.lock: self.proofs_finished.append((chain, vdf_info, vdf_proof)) except ConnectionResetError as e: log.info(f"Connection reset with VDF client {e}")