def get_vdf_info_and_proof( constants: ConsensusConstants, vdf_input: ClassgroupElement, challenge_hash: bytes32, number_iters: uint64, normalized_to_identity: bool = False, ) -> 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, normalized_to_identity)
def new_signage_point( self, index: uint8, blocks: BlockchainInterface, peak: Optional[BlockRecord], next_sub_slot_iters: uint64, signage_point: SignagePoint, skip_vdf_validation=False, ) -> bool: """ Returns true if sp successfully added """ assert len(self.finished_sub_slots) >= 1 if peak is None or peak.height < 2: sub_slot_iters = self.constants.SUB_SLOT_ITERS_STARTING else: sub_slot_iters = peak.sub_slot_iters # If we don't have this slot, return False if index == 0 or index >= self.constants.NUM_SPS_SUB_SLOT: return False assert (signage_point.cc_vdf is not None and signage_point.cc_proof is not None and signage_point.rc_vdf is not None and signage_point.rc_proof is not None) for sub_slot, sp_arr, start_ss_total_iters in self.finished_sub_slots: if sub_slot is None: assert start_ss_total_iters == 0 ss_challenge_hash = self.constants.GENESIS_CHALLENGE ss_reward_hash = self.constants.GENESIS_CHALLENGE else: ss_challenge_hash = sub_slot.challenge_chain.get_hash() ss_reward_hash = sub_slot.reward_chain.get_hash() if ss_challenge_hash == signage_point.cc_vdf.challenge: # If we do have this slot, find the Prev block from SP and validate SP if peak is not None and start_ss_total_iters > peak.total_iters: # We are in a future sub slot from the peak, so maybe there is a new SSI checkpoint_size: uint64 = uint64( next_sub_slot_iters // self.constants.NUM_SPS_SUB_SLOT) delta_iters: uint64 = uint64(checkpoint_size * index) future_sub_slot: bool = True else: # We are not in a future sub slot from the peak, so there is no new SSI checkpoint_size = uint64(sub_slot_iters // self.constants.NUM_SPS_SUB_SLOT) delta_iters = uint64(checkpoint_size * index) future_sub_slot = False sp_total_iters = start_ss_total_iters + delta_iters curr = peak if peak is None or future_sub_slot: check_from_start_of_ss = True else: check_from_start_of_ss = False while (curr is not None and curr.total_iters > start_ss_total_iters and curr.total_iters > sp_total_iters): if curr.first_in_sub_slot: # Did not find a block where it's iters are before our sp_total_iters, in this ss check_from_start_of_ss = True break curr = blocks.block_record(curr.prev_hash) if check_from_start_of_ss: # Check VDFs from start of sub slot cc_vdf_info_expected = VDFInfo( ss_challenge_hash, delta_iters, signage_point.cc_vdf.output, ) rc_vdf_info_expected = VDFInfo( ss_reward_hash, delta_iters, signage_point.rc_vdf.output, ) else: # Check VDFs from curr assert curr is not None cc_vdf_info_expected = VDFInfo( ss_challenge_hash, uint64(sp_total_iters - curr.total_iters), signage_point.cc_vdf.output, ) rc_vdf_info_expected = VDFInfo( curr.reward_infusion_new_challenge, uint64(sp_total_iters - curr.total_iters), signage_point.rc_vdf.output, ) if not signage_point.cc_vdf == dataclasses.replace( cc_vdf_info_expected, number_of_iterations=delta_iters): self.add_to_future_sp(signage_point, index) return False if check_from_start_of_ss: start_ele = ClassgroupElement.get_default_element() else: assert curr is not None start_ele = curr.challenge_vdf_output if not skip_vdf_validation: if not signage_point.cc_proof.normalized_to_identity and not signage_point.cc_proof.is_valid( self.constants, start_ele, cc_vdf_info_expected, ): self.add_to_future_sp(signage_point, index) return False if signage_point.cc_proof.normalized_to_identity and not signage_point.cc_proof.is_valid( self.constants, ClassgroupElement.get_default_element(), signage_point.cc_vdf, ): self.add_to_future_sp(signage_point, index) return False if rc_vdf_info_expected.challenge != signage_point.rc_vdf.challenge: # This signage point is probably outdated self.add_to_future_sp(signage_point, index) return False if not skip_vdf_validation: if not signage_point.rc_proof.is_valid( self.constants, ClassgroupElement.get_default_element(), signage_point.rc_vdf, rc_vdf_info_expected, ): self.add_to_future_sp(signage_point, index) return False sp_arr[index] = signage_point return True self.add_to_future_sp(signage_point, index) return False
def new_finished_sub_slot( self, eos: EndOfSubSlotBundle, blocks: BlockchainInterface, peak: Optional[BlockRecord], peak_full_block: Optional[FullBlock], ) -> Optional[List[timelord_protocol.NewInfusionPointVDF]]: """ Returns false if not added. Returns a list if added. The list contains all infusion points that depended on this sub slot """ assert len(self.finished_sub_slots) >= 1 assert (peak is None) == (peak_full_block is None) last_slot, _, last_slot_iters = self.finished_sub_slots[-1] cc_challenge: bytes32 = (last_slot.challenge_chain.get_hash() if last_slot is not None else self.constants.GENESIS_CHALLENGE) rc_challenge: bytes32 = (last_slot.reward_chain.get_hash() if last_slot is not None else self.constants.GENESIS_CHALLENGE) icc_challenge: Optional[bytes32] = None icc_iters: Optional[uint64] = None # Skip if already present for slot, _, _ in self.finished_sub_slots: if slot == eos: return [] if eos.challenge_chain.challenge_chain_end_of_slot_vdf.challenge != cc_challenge: # This slot does not append to our next slot # This prevent other peers from appending fake VDFs to our cache return None if peak is None: sub_slot_iters = self.constants.SUB_SLOT_ITERS_STARTING else: sub_slot_iters = peak.sub_slot_iters total_iters = uint128(last_slot_iters + sub_slot_iters) if peak is not None and peak.total_iters > last_slot_iters: # Peak is in this slot rc_challenge = eos.reward_chain.end_of_slot_vdf.challenge cc_start_element = peak.challenge_vdf_output iters = uint64(total_iters - peak.total_iters) if peak.reward_infusion_new_challenge != rc_challenge: # We don't have this challenge hash yet if rc_challenge not in self.future_eos_cache: self.future_eos_cache[rc_challenge] = [] self.future_eos_cache[rc_challenge].append(eos) self.future_cache_key_times[rc_challenge] = int(time.time()) log.info( f"Don't have challenge hash {rc_challenge}, caching EOS") return None if peak.deficit == self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: icc_start_element = None elif peak.deficit == self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK - 1: icc_start_element = ClassgroupElement.get_default_element() else: icc_start_element = peak.infused_challenge_vdf_output if peak.deficit < self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: curr = peak while not curr.first_in_sub_slot and not curr.is_challenge_block( self.constants): curr = blocks.block_record(curr.prev_hash) if curr.is_challenge_block(self.constants): icc_challenge = curr.challenge_block_info_hash icc_iters = uint64(total_iters - curr.total_iters) else: assert curr.finished_infused_challenge_slot_hashes is not None icc_challenge = curr.finished_infused_challenge_slot_hashes[ -1] icc_iters = sub_slot_iters assert icc_challenge is not None if can_finish_sub_and_full_epoch( self.constants, blocks, peak.height, peak.prev_hash, peak.deficit, peak.sub_epoch_summary_included is not None, )[0]: assert peak_full_block is not None ses: Optional[SubEpochSummary] = next_sub_epoch_summary( self.constants, blocks, peak.required_iters, peak_full_block, True) if ses is not None: if eos.challenge_chain.subepoch_summary_hash != ses.get_hash( ): log.warning( f"SES not correct {ses.get_hash(), eos.challenge_chain}" ) return None else: if eos.challenge_chain.subepoch_summary_hash is not None: log.warning("SES not correct, should be None") return None else: # This is on an empty slot cc_start_element = ClassgroupElement.get_default_element() icc_start_element = ClassgroupElement.get_default_element() iters = sub_slot_iters icc_iters = sub_slot_iters # The icc should only be present if the previous slot had an icc too, and not deficit 0 (just finished slot) icc_challenge = (last_slot.infused_challenge_chain.get_hash() if last_slot is not None and last_slot.infused_challenge_chain is not None and last_slot.reward_chain.deficit != self.constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK else None) # Validate cc VDF partial_cc_vdf_info = VDFInfo( cc_challenge, iters, eos.challenge_chain.challenge_chain_end_of_slot_vdf.output, ) # The EOS will have the whole sub-slot iters, but the proof is only the delta, from the last peak if eos.challenge_chain.challenge_chain_end_of_slot_vdf != dataclasses.replace( partial_cc_vdf_info, number_of_iterations=sub_slot_iters, ): return None if (not eos.proofs.challenge_chain_slot_proof.normalized_to_identity and not eos.proofs.challenge_chain_slot_proof.is_valid( self.constants, cc_start_element, partial_cc_vdf_info, )): return None if (eos.proofs.challenge_chain_slot_proof.normalized_to_identity and not eos.proofs.challenge_chain_slot_proof.is_valid( self.constants, ClassgroupElement.get_default_element(), eos.challenge_chain.challenge_chain_end_of_slot_vdf, )): return None # Validate reward chain VDF if not eos.proofs.reward_chain_slot_proof.is_valid( self.constants, ClassgroupElement.get_default_element(), eos.reward_chain.end_of_slot_vdf, VDFInfo(rc_challenge, iters, eos.reward_chain.end_of_slot_vdf.output), ): return None if icc_challenge is not None: assert icc_start_element is not None assert icc_iters is not None assert eos.infused_challenge_chain is not None assert eos.infused_challenge_chain is not None assert eos.proofs.infused_challenge_chain_slot_proof is not None partial_icc_vdf_info = VDFInfo( icc_challenge, iters, eos.infused_challenge_chain. infused_challenge_chain_end_of_slot_vdf.output, ) # The EOS will have the whole sub-slot iters, but the proof is only the delta, from the last peak if eos.infused_challenge_chain.infused_challenge_chain_end_of_slot_vdf != dataclasses.replace( partial_icc_vdf_info, number_of_iterations=icc_iters, ): return None if (not eos.proofs.infused_challenge_chain_slot_proof. normalized_to_identity and not eos.proofs.infused_challenge_chain_slot_proof.is_valid( self.constants, icc_start_element, partial_icc_vdf_info)): return None if (eos.proofs.infused_challenge_chain_slot_proof. normalized_to_identity and not eos.proofs.infused_challenge_chain_slot_proof.is_valid( self.constants, ClassgroupElement.get_default_element(), eos.infused_challenge_chain. infused_challenge_chain_end_of_slot_vdf, )): return None else: # This is the first sub slot and it's empty, therefore there is no ICC if eos.infused_challenge_chain is not None or eos.proofs.infused_challenge_chain_slot_proof is not None: return None self.finished_sub_slots.append( (eos, [None] * self.constants.NUM_SPS_SUB_SLOT, total_iters)) new_ips: List[timelord_protocol.NewInfusionPointVDF] = [] for ip in self.future_ip_cache.get(eos.reward_chain.get_hash(), []): new_ips.append(ip) return new_ips
async def _do_process_communication( self, chain: Chain, challenge: bytes32, initial_form: ClassgroupElement, ip: str, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, # Data specific only when running in bluebox mode. bluebox_iteration: Optional[uint64] = None, header_hash: Optional[bytes32] = None, height: Optional[uint32] = None, field_vdf: Optional[uint8] = None, # Labels a proof to the current state only proof_label: Optional[int] = None, ): 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.sanitizer_mode: writer.write(b"S") else: 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, proof_label)) self.vdf_failures_count += 1 return None if ok.decode() != "OK": return None log.debug("Got handshake with VDF client.") if not self.sanitizer_mode: async with self.lock: self.allows_iters.append(chain) else: async with self.lock: assert chain is Chain.BLUEBOX assert bluebox_iteration is not None prefix = str(len(str(bluebox_iteration))) if len(str(bluebox_iteration)) < 10: prefix = "0" + prefix iter_str = prefix + str(bluebox_iteration) writer.write(iter_str.encode()) await writer.drain() # 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, proof_label)) self.vdf_failures_count += 1 break msg = "" try: msg = data.decode() except Exception: pass if msg == "STOP": log.debug(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, proof_label)) 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]) if not self.sanitizer_mode: 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, self.sanitizer_mode, ) if not vdf_proof.is_valid(self.constants, initial_form, vdf_info): log.error("Invalid proof of time!") if not self.sanitizer_mode: async with self.lock: assert proof_label is not None self.proofs_finished.append((chain, vdf_info, vdf_proof, proof_label)) else: async with self.lock: writer.write(b"010") await writer.drain() assert header_hash is not None assert field_vdf is not None assert height is not None response = timelord_protocol.RespondCompactProofOfTime( vdf_info, vdf_proof, header_hash, height, field_vdf ) if self.server is not None: message = make_msg(ProtocolMessageTypes.respond_compact_proof_of_time, response) await self.server.send_to_all([message], NodeType.FULL_NODE) except ConnectionResetError as e: log.debug(f"Connection reset with VDF client {e}")
def rand_vdf() -> VDFInfo: return VDFInfo(rand_hash(), uint64(random.randint(100000, 1000000000)), rand_class_group_element())
) sub_epochs = SubEpochData( bytes32( bytes.fromhex( "6fdcfaabeb149f9c44c80c230c44771e14b3d4e1b361dcca9c823b7ea7887ffe") ), uint8(190), uint64(10527522631566046685), uint64(989988965238543242), ) vdf_info = VDFInfo( bytes32( bytes.fromhex( "7cbd5905838c1dc2becd00298a5b3a6e42b6a306d574c8897cd721f84d429972") ), uint64(14708638287767651172), ClassgroupElement.get_default_element(), ) vdf_proof = VDFProof( uint8(197), bytes(b"0" * 100), False, ) sub_slot_data = SubSlotData( proof_of_space, vdf_proof, vdf_proof, vdf_proof,