def _can_infuse_unfinished_block( self, block: timelord_protocol.NewUnfinishedSubBlock ) -> Optional[uint64]: sub_slot_iters = self.last_state.get_sub_slot_iters() difficulty = self.last_state.get_difficulty() ip_iters = self.last_state.get_last_ip() rc_block = block.reward_chain_sub_block try: block_sp_iters, block_ip_iters = iters_from_sub_block( self.constants, rc_block, sub_slot_iters, difficulty, ) except Exception as e: log.warning(f"Received invalid unfinished block: {e}.") return None block_sp_total_iters = self.last_state.total_iters - ip_iters + block_sp_iters if is_overflow_sub_block( self.constants, block.reward_chain_sub_block.signage_point_index): block_sp_total_iters -= self.last_state.get_sub_slot_iters() found_index = -1 for index, (rc, total_iters) in enumerate( self.last_state.reward_challenge_cache): if rc == block.rc_prev: found_index = index break if found_index == -1: log.warning( f"Will not infuse {block.rc_prev} because it's reward chain challenge is not in the chain" ) return None new_block_iters = uint64(block_ip_iters - ip_iters) if len(self.last_state.reward_challenge_cache) > found_index + 1: if self.last_state.reward_challenge_cache[ found_index + 1][1] < block_sp_total_iters: log.warning( f"Will not infuse unfinished block {block.rc_prev} sp total iters {block_sp_total_iters}, " f"because there is another infusion before it's SP") return None if self.last_state.reward_challenge_cache[found_index][ 1] > block_sp_total_iters: log.error( f"Will not infuse unfinished block {block.rc_prev}, sp total iters: {block_sp_total_iters}, " f"because it's iters are too low") return None if new_block_iters > 0: return new_block_iters return None
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))
def get_signage_point( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], latest_sub_block: Optional[SubBlockRecord], sub_slot_start_total_iters: uint128, signage_point_index: uint8, finished_sub_slots: List[EndOfSubSlotBundle], sub_slot_iters: uint64, ) -> SignagePoint: if signage_point_index == 0: return SignagePoint(None, None, None, None) sp_iters = calculate_sp_iters(constants, sub_slot_iters, signage_point_index) overflow = is_overflow_sub_block(constants, signage_point_index) sp_total_iters = uint128( sub_slot_start_total_iters + calculate_sp_iters(constants, sub_slot_iters, signage_point_index) ) ( cc_vdf_challenge, rc_vdf_challenge, cc_vdf_input, rc_vdf_input, cc_vdf_iters, rc_vdf_iters, ) = get_signage_point_vdf_info( constants, finished_sub_slots, overflow, latest_sub_block, sub_blocks, sp_total_iters, sp_iters, ) cc_sp_vdf, cc_sp_proof = get_vdf_info_and_proof( constants, cc_vdf_input, cc_vdf_challenge, cc_vdf_iters, ) rc_sp_vdf, rc_sp_proof = get_vdf_info_and_proof( constants, rc_vdf_input, rc_vdf_challenge, rc_vdf_iters, ) cc_sp_vdf = replace(cc_sp_vdf, number_of_iterations=sp_iters) return SignagePoint(cc_sp_vdf, cc_sp_proof, rc_sp_vdf, rc_sp_proof)
async def test_basic_store(self, empty_blockchain): blockchain = empty_blockchain blocks = bt.get_consecutive_blocks(10, seed=b"1234") store = await FullNodeStore.create(test_constants) unfinished_blocks = [] for block in blocks: unfinished_blocks.append( UnfinishedBlock( block.finished_sub_slots, block.reward_chain_sub_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage_sub_block, block.foliage_block, block.transactions_info, block.transactions_generator, )) # Add/get candidate block assert store.get_candidate_block( unfinished_blocks[0].get_hash()) is None for height, unf_block in enumerate(unfinished_blocks): store.add_candidate_block(unf_block.get_hash(), height, unf_block) assert store.get_candidate_block( unfinished_blocks[4].get_hash()) == unfinished_blocks[4] store.clear_candidate_blocks_below(uint32(8)) assert store.get_candidate_block( unfinished_blocks[5].get_hash()) is None assert store.get_candidate_block( unfinished_blocks[8].get_hash()) is not None # Test seen unfinished blocks h_hash_1 = bytes32(token_bytes(32)) assert not store.seen_unfinished_block(h_hash_1) assert store.seen_unfinished_block(h_hash_1) store.clear_seen_unfinished_blocks() assert not store.seen_unfinished_block(h_hash_1) # Disconnected blocks assert store.get_disconnected_block(blocks[0].prev_header_hash) is None for block in blocks: store.add_disconnected_block(block) assert store.get_disconnected_block_by_prev( block.prev_header_hash) == block assert store.get_disconnected_block(block.header_hash) == block # Add/get unfinished block for height, unf_block in enumerate(unfinished_blocks): assert store.get_unfinished_block(unf_block.partial_hash) is None store.add_unfinished_block(height, unf_block) assert store.get_unfinished_block( unf_block.partial_hash) == unf_block store.remove_unfinished_block(unf_block.partial_hash) assert store.get_unfinished_block(unf_block.partial_hash) is None blocks = bt.get_consecutive_blocks(1, skip_slots=5) sub_slots = blocks[0].finished_sub_slots assert len(sub_slots) == 5 assert (store.get_finished_sub_slots( None, {}, sub_slots[0].challenge_chain.challenge_chain_end_of_slot_vdf. challenge, False, ) == []) # Test adding non-connecting sub-slots genesis assert store.get_sub_slot(test_constants.FIRST_CC_CHALLENGE) is None assert store.get_sub_slot( sub_slots[0].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[1].challenge_chain.get_hash()) is None assert store.new_finished_sub_slot(sub_slots[1], {}, None) is None assert store.new_finished_sub_slot(sub_slots[2], {}, None) is None # Test adding sub-slots after genesis assert store.new_finished_sub_slot(sub_slots[0], {}, None) is not None assert store.get_sub_slot( sub_slots[0].challenge_chain.get_hash())[0] == sub_slots[0] assert store.get_sub_slot( sub_slots[1].challenge_chain.get_hash()) is None assert store.new_finished_sub_slot(sub_slots[1], {}, None) is not None for i in range(len(sub_slots)): assert store.new_finished_sub_slot(sub_slots[i], {}, None) is not None assert store.get_sub_slot( sub_slots[i].challenge_chain.get_hash())[0] == sub_slots[i] assert store.get_finished_sub_slots( None, {}, sub_slots[-1].challenge_chain.get_hash(), False) == sub_slots with raises(ValueError): store.get_finished_sub_slots( None, {}, sub_slots[-1].challenge_chain.get_hash(), True) assert store.get_finished_sub_slots( None, {}, sub_slots[-2].challenge_chain.get_hash(), False) == sub_slots[:-1] # Test adding genesis peak await blockchain.receive_block(blocks[0]) peak = blockchain.get_peak() if peak.overflow: store.new_peak(peak, sub_slots[-2], sub_slots[-1], False, {}) else: store.new_peak(peak, None, sub_slots[-1], False, {}) assert store.get_sub_slot( sub_slots[0].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[1].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[2].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[3].challenge_chain.get_hash())[0] == sub_slots[3] assert store.get_sub_slot( sub_slots[4].challenge_chain.get_hash())[0] == sub_slots[4] assert (store.get_finished_sub_slots( peak, blockchain.sub_blocks, sub_slots[-1].challenge_chain.get_hash(), False, ) == []) # Test adding non genesis peak directly blocks = bt.get_consecutive_blocks(2, skip_slots=2) blocks = bt.get_consecutive_blocks(3, block_list_input=blocks) for block in blocks: await blockchain.receive_block(block) sb = blockchain.sub_blocks[block.header_hash] sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain.sub_blocks) assert res[0] is None # Add reorg blocks blocks_reorg = bt.get_consecutive_blocks(20) for block in blocks_reorg: res, _, _ = await blockchain.receive_block(block) if res == ReceiveBlockResult.NEW_PEAK: sb = blockchain.sub_blocks[block.header_hash] sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, True, blockchain.sub_blocks) assert res[0] is None # Add slots to the end blocks_2 = bt.get_consecutive_blocks(1, block_list_input=blocks_reorg, skip_slots=2) for slot in blocks_2[-1].finished_sub_slots: store.new_finished_sub_slot(slot, blockchain.sub_blocks, blockchain.get_peak()) assert store.get_sub_slot( sub_slots[3].challenge_chain.get_hash()) is None assert store.get_sub_slot( sub_slots[4].challenge_chain.get_hash()) is None # Test adding signage point peak = blockchain.get_peak() ss_start_iters = peak.sp_sub_slot_total_iters(test_constants) for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, blockchain.sub_blocks, peak, ss_start_iters, uint8(i), [], peak.sub_slot_iters, ) assert store.new_signage_point(i, blockchain.sub_blocks, peak, peak.sub_slot_iters, sp) blocks = blocks_reorg while True: blocks = bt.get_consecutive_blocks(1, block_list_input=blocks) res, _, _ = await blockchain.receive_block(blocks[-1]) if res == ReceiveBlockResult.NEW_PEAK: sb = blockchain.sub_blocks[blocks[-1].header_hash] sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( blocks[-1].header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, True, blockchain.sub_blocks) assert res[0] is None if sb.overflow and sp_sub_slot is not None: assert sp_sub_slot != ip_sub_slot break peak = blockchain.get_peak() assert peak.overflow # Overflow peak should result in 2 finished sub slots assert len(store.finished_sub_slots) == 2 # Add slots to the end, except for the last one, which we will use to test invalid SP blocks_2 = bt.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=3) for slot in blocks_2[-1].finished_sub_slots[:-1]: store.new_finished_sub_slot(slot, blockchain.sub_blocks, blockchain.get_peak()) finished_sub_slots = blocks_2[-1].finished_sub_slots assert len(store.finished_sub_slots) == 4 # Test adding signage points for overflow blocks (sp_sub_slot) ss_start_iters = peak.sp_sub_slot_total_iters(test_constants) # for i in range(peak.signage_point_index, test_constants.NUM_SPS_SUB_SLOT): # if i < peak.signage_point_index: # continue # latest = peak # while latest.total_iters > peak.sp_total_iters(test_constants): # latest = blockchain.sub_blocks[latest.prev_hash] # sp = get_signage_point( # test_constants, # blockchain.sub_blocks, # latest, # ss_start_iters, # uint8(i), # [], # peak.sub_slot_iters, # ) # assert store.new_signage_point(i, blockchain.sub_blocks, peak, peak.sub_slot_iters, sp) # Test adding signage points for overflow blocks (ip_sub_slot) for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, blockchain.sub_blocks, peak, peak.ip_sub_slot_total_iters(test_constants), uint8(i), [], peak.sub_slot_iters, ) assert store.new_signage_point(i, blockchain.sub_blocks, peak, peak.sub_slot_iters, sp) # Test adding future signage point, a few slots forward (good) saved_sp_hash = None for slot_offset in range(1, len(finished_sub_slots)): for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA, ): sp = get_signage_point( test_constants, blockchain.sub_blocks, peak, peak.ip_sub_slot_total_iters(test_constants) + slot_offset * peak.sub_slot_iters, uint8(i), finished_sub_slots[:slot_offset], peak.sub_slot_iters, ) saved_sp_hash = sp.cc_vdf.output.get_hash() assert store.new_signage_point(i, blockchain.sub_blocks, peak, peak.sub_slot_iters, sp) # Test adding future signage point (bad) for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, blockchain.sub_blocks, peak, peak.ip_sub_slot_total_iters(test_constants) + len(finished_sub_slots) * peak.sub_slot_iters, uint8(i), finished_sub_slots[:len(finished_sub_slots)], peak.sub_slot_iters, ) assert not store.new_signage_point(i, blockchain.sub_blocks, peak, peak.sub_slot_iters, sp) # Test adding past signage point sp = SignagePoint( blocks[1].reward_chain_sub_block.challenge_chain_sp_vdf, blocks[1].challenge_chain_sp_proof, blocks[1].reward_chain_sub_block.reward_chain_sp_vdf, blocks[1].reward_chain_sp_proof, ) assert not store.new_signage_point( blocks[1].reward_chain_sub_block.signage_point_index, {}, peak, blockchain.sub_blocks[ blocks[1].header_hash].sp_sub_slot_total_iters(test_constants), sp, ) # Get signage point by index assert (store.get_signage_point_by_index( finished_sub_slots[0].challenge_chain.get_hash(), 4, finished_sub_slots[0].reward_chain.get_hash(), ) is not None) assert (store.get_signage_point_by_index( finished_sub_slots[0].challenge_chain.get_hash(), 4, std_hash(b"1")) is None) # Get signage point by hash assert store.get_signage_point(saved_sp_hash) is not None assert store.get_signage_point(std_hash(b"2")) is None # Test adding signage points before genesis store.initialize_genesis_sub_slot() assert len(store.finished_sub_slots) == 1 for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA): sp = get_signage_point( test_constants, {}, None, uint128(0), uint8(i), [], peak.sub_slot_iters, ) assert store.new_signage_point(i, {}, None, peak.sub_slot_iters, sp) blocks_3 = bt.get_consecutive_blocks(1, skip_slots=2) for slot in blocks_3[-1].finished_sub_slots: store.new_finished_sub_slot(slot, {}, None) assert len(store.finished_sub_slots) == 3 finished_sub_slots = blocks_3[-1].finished_sub_slots for slot_offset in range(1, len(finished_sub_slots) + 1): for i in range( 1, test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA, ): sp = get_signage_point( test_constants, {}, None, slot_offset * peak.sub_slot_iters, uint8(i), finished_sub_slots[:slot_offset], peak.sub_slot_iters, ) assert store.new_signage_point(i, {}, None, peak.sub_slot_iters, sp) # Test adding signage points after genesis blocks_4 = bt.get_consecutive_blocks(1) blocks_5 = bt.get_consecutive_blocks(1, block_list_input=blocks_4, skip_slots=1) # If this is not the case, fix test to find a block that is assert (blocks_4[-1].reward_chain_sub_block.signage_point_index < test_constants.NUM_SPS_SUB_SLOT - test_constants.NUM_SP_INTERVALS_EXTRA) await blockchain.receive_block(blocks_4[-1]) sb = blockchain.sub_blocks[blocks_4[-1].header_hash] store.new_peak(sb, None, None, False, blockchain.sub_blocks) for i in range( sb.signage_point_index + test_constants.NUM_SP_INTERVALS_EXTRA, test_constants.NUM_SPS_SUB_SLOT, ): if is_overflow_sub_block(test_constants, i): finished_sub_slots = blocks_5[-1].finished_sub_slots else: finished_sub_slots = [] sp = get_signage_point( test_constants, blockchain.sub_blocks, sb, uint128(0), uint8(i), finished_sub_slots, peak.sub_slot_iters, ) assert store.new_signage_point(i, empty_blockchain.sub_blocks, sb, peak.sub_slot_iters, sp) # Test future EOS cache store.initialize_genesis_sub_slot() blocks = bt.get_consecutive_blocks(1) await blockchain.receive_block(blocks[-1]) while True: blocks = bt.get_consecutive_blocks(1, block_list_input=blocks) await blockchain.receive_block(blocks[-1]) sb = blockchain.sub_blocks[blocks[-1].header_hash] if sb.first_in_sub_slot: break assert len(blocks) >= 3 dependant_sub_slots = blocks[-1].finished_sub_slots for block in blocks[:-2]: sb = blockchain.sub_blocks[block.header_hash] sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) peak = sb res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain.sub_blocks) assert res[0] is None assert store.new_finished_sub_slot(dependant_sub_slots[0], blockchain.sub_blocks, peak) is None block = blocks[-2] sb = blockchain.sub_blocks[block.header_hash] sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain.sub_blocks) assert res[0] == dependant_sub_slots[0] assert res[1] == res[2] == [] # Test future IP cache store.initialize_genesis_sub_slot() blocks = bt.get_consecutive_blocks(60) for block in blocks[:5]: await blockchain.receive_block(block) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( block.header_hash) res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain.sub_blocks) assert res[0] is None case_0, case_1 = False, False for i in range(5, len(blocks) - 1): prev_block = blocks[i] block = blocks[i + 1] new_ip = NewInfusionPointVDF( block.reward_chain_sub_block.get_unfinished().get_hash(), block.reward_chain_sub_block.challenge_chain_ip_vdf, block.challenge_chain_ip_proof, block.reward_chain_sub_block.reward_chain_ip_vdf, block.reward_chain_ip_proof, block.reward_chain_sub_block.infused_challenge_chain_ip_vdf, block.infused_challenge_chain_ip_proof, ) store.add_to_future_ip(new_ip) await blockchain.receive_block(prev_block) sp_sub_slot, ip_sub_slot = await blockchain.get_sp_and_ip_sub_slots( prev_block.header_hash) sb = blockchain.sub_blocks[prev_block.header_hash] res = store.new_peak(sb, sp_sub_slot, ip_sub_slot, False, blockchain.sub_blocks) if len(block.finished_sub_slots) == 0: case_0 = True assert res[2] == [new_ip] else: case_1 = True assert res[2] == [] found_ips = [] for ss in block.finished_sub_slots: found_ips += store.new_finished_sub_slot( ss, blockchain.sub_blocks, sb) assert found_ips == [new_ip] # If flaky, increase the number of blocks created assert case_0 and case_1
async def test1(self, two_nodes): num_blocks = 5 test_rpc_port = uint16(21522) full_node_api_1, full_node_api_2, server_1, server_2 = two_nodes def stop_node_cb(): full_node_api_1._close() server_1.close_all() full_node_rpc_api = FullNodeRpcApi(full_node_api_1.full_node) config = bt.config hostname = config["self_hostname"] daemon_port = config["daemon_port"] rpc_cleanup = await start_rpc_server( full_node_rpc_api, hostname, daemon_port, test_rpc_port, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) try: client = await FullNodeRpcClient.create(self_hostname, test_rpc_port, bt.root_path, config) state = await client.get_blockchain_state() assert state["peak"] is None assert not state["sync"]["sync_mode"] assert state["difficulty"] > 0 assert state["sub_slot_iters"] > 0 blocks = bt.get_consecutive_blocks(num_blocks) blocks = bt.get_consecutive_blocks(1, block_list_input=blocks, guarantee_block=True) assert len(await client.get_unfinished_sub_block_headers()) == 0 assert len((await client.get_sub_block_records(0, 100))) == 0 for block in blocks: if is_overflow_sub_block( test_constants, block.reward_chain_sub_block.signage_point_index): finished_ss = block.finished_sub_slots[:-1] else: finished_ss = block.finished_sub_slots unf = UnfinishedBlock( finished_ss, block.reward_chain_sub_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage_sub_block, block.foliage_block, block.transactions_info, block.transactions_generator, ) await full_node_api_1.full_node.respond_unfinished_sub_block( full_node_protocol.RespondUnfinishedSubBlock(unf), None) await full_node_api_1.full_node.respond_sub_block( full_node_protocol.RespondSubBlock(block), None) assert len(await client.get_unfinished_sub_block_headers()) > 0 assert len(await client.get_all_block(0, 2)) == 2 state = await client.get_blockchain_state() block = await client.get_sub_block(state["peak"].header_hash) assert block == blocks[-1] assert (await client.get_sub_block(bytes([1] * 32))) is None assert (await client.get_sub_block_record_by_sub_height(2) ).header_hash == blocks[2].header_hash assert len( (await client.get_sub_block_records(0, 100))) == num_blocks + 1 assert (await client.get_sub_block_record_by_sub_height(100)) is None ph = list(blocks[-1].get_included_reward_coins())[0].puzzle_hash coins = await client.get_unspent_coins(ph) assert len(coins) >= 1 additions, removals = await client.get_additions_and_removals( blocks[-1].header_hash) assert len(additions) >= 2 and len(removals) == 0 assert len(await client.get_connections()) == 0 await client.open_connection(self_hostname, server_2._port) async def num_connections(): return len(await client.get_connections()) await time_out_assert(10, num_connections, 1) connections = await client.get_connections() await client.close_connection(connections[0]["node_id"]) await time_out_assert(10, num_connections, 0) finally: # Checks that the RPC manages to stop the node client.close() await client.await_closed() await rpc_cleanup()
async def pre_validate_blocks_multiprocessing( constants: ConsensusConstants, constants_json: Dict, sub_blocks: Dict[bytes32, SubBlockRecord], sub_height_to_hash: Dict[uint32, bytes32], blocks: Sequence[Union[FullBlock, HeaderBlock]], pool: ProcessPoolExecutor, ) -> Optional[List[PreValidationResult]]: """ This method must be called under the blockchain lock If all the full blocks pass pre-validation, (only validates header), returns the list of required iters. if any validation issue occurs, returns False. Args: constants_json: pool: sub_height_to_hash: constants: sub_blocks: blocks: list of full blocks to validate (must be connected to current chain) """ batch_size = 4 prev_sb: Optional[SubBlockRecord] = None # Collects all the recent sub-blocks (up to the previous sub-epoch) recent_sub_blocks: Dict[bytes32, SubBlockRecord] = {} recent_sub_blocks_compressed: Dict[bytes32, SubBlockRecord] = {} num_sub_slots_found = 0 num_blocks_seen = 0 if blocks[0].sub_block_height > 0: if blocks[0].prev_header_hash not in sub_blocks: return [ PreValidationResult(uint16(Err.INVALID_PREV_BLOCK_HASH.value), None, None) ] curr = sub_blocks[blocks[0].prev_header_hash] num_sub_slots_to_look_for = 3 if curr.overflow else 2 while (curr.sub_epoch_summary_included is None or num_blocks_seen < constants.NUMBER_OF_TIMESTAMPS or num_sub_slots_found < num_sub_slots_to_look_for ) and curr.sub_block_height > 0: if num_blocks_seen < constants.NUMBER_OF_TIMESTAMPS or num_sub_slots_found < num_sub_slots_to_look_for: recent_sub_blocks_compressed[curr.header_hash] = curr if curr.first_in_sub_slot: assert curr.finished_challenge_slot_hashes is not None num_sub_slots_found += len(curr.finished_challenge_slot_hashes) recent_sub_blocks[curr.header_hash] = curr if curr.is_block: num_blocks_seen += 1 curr = sub_blocks[curr.prev_hash] recent_sub_blocks[curr.header_hash] = curr recent_sub_blocks_compressed[curr.header_hash] = curr sub_block_was_present = [] for block in blocks: sub_block_was_present.append(block.header_hash in sub_blocks) diff_ssis: List[Tuple[uint64, uint64]] = [] for sub_block in blocks: if sub_block.sub_block_height != 0 and prev_sb is None: prev_sb = sub_blocks[sub_block.prev_header_hash] sub_slot_iters, difficulty = get_sub_slot_iters_and_difficulty( constants, sub_block, sub_height_to_hash, prev_sb, sub_blocks) overflow = is_overflow_sub_block( constants, sub_block.reward_chain_sub_block.signage_point_index) challenge = get_block_challenge( constants, sub_block, recent_sub_blocks, prev_sb is None, overflow, False, ) if sub_block.reward_chain_sub_block.challenge_chain_sp_vdf is None: cc_sp_hash: bytes32 = challenge else: cc_sp_hash = sub_block.reward_chain_sub_block.challenge_chain_sp_vdf.output.get_hash( ) q_str: Optional[ bytes32] = sub_block.reward_chain_sub_block.proof_of_space.verify_and_get_quality_string( constants, challenge, cc_sp_hash) if q_str is None: for i, block_i in enumerate(blocks): if not sub_block_was_present[ i] and block_i.header_hash in sub_blocks: del sub_blocks[block_i.header_hash] return None required_iters: uint64 = calculate_iterations_quality( q_str, sub_block.reward_chain_sub_block.proof_of_space.size, difficulty, cc_sp_hash, ) sub_block_rec = block_to_sub_block_record( constants, sub_blocks, sub_height_to_hash, required_iters, sub_block, None, ) recent_sub_blocks[sub_block_rec.header_hash] = sub_block_rec recent_sub_blocks_compressed[sub_block_rec.header_hash] = sub_block_rec sub_blocks[ sub_block_rec. header_hash] = sub_block_rec # Temporarily add sub block to dict prev_sb = sub_block_rec diff_ssis.append((difficulty, sub_slot_iters)) for i, block in enumerate(blocks): if not sub_block_was_present[i]: del sub_blocks[block.header_hash] recent_sb_compressed_pickled = { bytes(k): bytes(v) for k, v in recent_sub_blocks_compressed.items() } futures = [] # Pool of workers to validate blocks concurrently for i in range(0, len(blocks), batch_size): end_i = min(i + batch_size, len(blocks)) blocks_to_validate = blocks[i:end_i] if any([ len(block.finished_sub_slots) > 0 for block in blocks_to_validate ]): final_pickled = { bytes(k): bytes(v) for k, v in recent_sub_blocks.items() } else: final_pickled = recent_sb_compressed_pickled hb_pickled: List[bytes] = [] generators: List[Optional[bytes]] = [] for block in blocks_to_validate: if isinstance(block, FullBlock): hb_pickled.append(bytes(block.get_block_header())) generators.append( bytes(block.transactions_generator) if block. transactions_generator is not None else None) else: hb_pickled.append(bytes(block)) generators.append(None) futures.append(asyncio.get_running_loop().run_in_executor( pool, batch_pre_validate_sub_blocks, constants_json, final_pickled, hb_pickled, generators, True, [diff_ssis[j][0] for j in range(i, end_i)], [diff_ssis[j][1] for j in range(i, end_i)], )) # Collect all results into one flat list return [ PreValidationResult.from_bytes(result) for batch_result in (await asyncio.gather(*futures)) for result in batch_result ]
def validate_finished_header_block( constants: ConsensusConstants, sub_blocks: BlockchainInterface, header_block: HeaderBlock, check_filter: bool, expected_difficulty: uint64, expected_sub_slot_iters: uint64, ) -> Tuple[Optional[uint64], Optional[ValidationError]]: """ Fully validates the header of a sub-block. A header block is the same as a full block, but without transactions and transaction info. Returns (required_iters, error). """ unfinished_header_block = UnfinishedHeaderBlock( header_block.finished_sub_slots, header_block.reward_chain_sub_block.get_unfinished(), header_block.challenge_chain_sp_proof, header_block.reward_chain_sp_proof, header_block.foliage_sub_block, header_block.foliage_block, header_block.transactions_filter, ) required_iters, validate_unfinished_err = validate_unfinished_header_block( constants, sub_blocks, unfinished_header_block, check_filter, expected_difficulty, expected_sub_slot_iters, False, ) genesis_block = False if validate_unfinished_err is not None: return None, validate_unfinished_err assert required_iters is not None if header_block.height == 0: prev_sb: Optional[SubBlockRecord] = None genesis_block = True else: prev_sb = sub_blocks.sub_block_record(header_block.prev_header_hash) new_sub_slot: bool = len(header_block.finished_sub_slots) > 0 ip_iters: uint64 = calculate_ip_iters( constants, expected_sub_slot_iters, header_block.reward_chain_sub_block.signage_point_index, required_iters, ) if not genesis_block: assert prev_sb is not None # 27. Check sub-block height if header_block.height != prev_sb.height + 1: return None, ValidationError(Err.INVALID_HEIGHT) # 28. Check weight if header_block.weight != prev_sb.weight + expected_difficulty: log.error( f"INVALID WEIGHT: {header_block} {prev_sb} {expected_difficulty}" ) return None, ValidationError(Err.INVALID_WEIGHT) else: if header_block.height != uint32(0): return None, ValidationError(Err.INVALID_HEIGHT) if header_block.weight != constants.DIFFICULTY_STARTING: return None, ValidationError(Err.INVALID_WEIGHT) # RC vdf challenge is taken from more recent of (slot start, prev_block) if genesis_block: cc_vdf_output = ClassgroupElement.get_default_element() ip_vdf_iters = ip_iters if new_sub_slot: rc_vdf_challenge = header_block.finished_sub_slots[ -1].reward_chain.get_hash() else: rc_vdf_challenge = constants.GENESIS_CHALLENGE else: assert prev_sb is not None if new_sub_slot: # slot start is more recent rc_vdf_challenge = header_block.finished_sub_slots[ -1].reward_chain.get_hash() ip_vdf_iters = ip_iters cc_vdf_output = ClassgroupElement.get_default_element() else: # Prev sb is more recent rc_vdf_challenge = prev_sb.reward_infusion_new_challenge ip_vdf_iters = uint64( header_block.reward_chain_sub_block.total_iters - prev_sb.total_iters) cc_vdf_output = prev_sb.challenge_vdf_output # 29. Check challenge chain infusion point VDF if new_sub_slot: cc_vdf_challenge = header_block.finished_sub_slots[ -1].challenge_chain.get_hash() else: # Not first sub-block in slot if genesis_block: # genesis block cc_vdf_challenge = constants.GENESIS_CHALLENGE else: assert prev_sb is not None # Not genesis block, go back to first sub-block in slot curr = prev_sb while curr.finished_challenge_slot_hashes is None: curr = sub_blocks.sub_block_record(curr.prev_hash) cc_vdf_challenge = curr.finished_challenge_slot_hashes[-1] cc_target_vdf_info = VDFInfo( cc_vdf_challenge, ip_vdf_iters, header_block.reward_chain_sub_block.challenge_chain_ip_vdf.output, ) if header_block.reward_chain_sub_block.challenge_chain_ip_vdf != dataclasses.replace( cc_target_vdf_info, number_of_iterations=ip_iters, ): expected = dataclasses.replace( cc_target_vdf_info, number_of_iterations=ip_iters, ) log.error( f"{header_block.reward_chain_sub_block.challenge_chain_ip_vdf }. expected {expected}" ) log.error(f"Block: {header_block}") return None, ValidationError(Err.INVALID_CC_IP_VDF) if not header_block.challenge_chain_ip_proof.is_valid( constants, cc_vdf_output, cc_target_vdf_info, None, ): log.error(f"Did not validate, output {cc_vdf_output}") log.error(f"Block: {header_block}") return None, ValidationError(Err.INVALID_CC_IP_VDF) # 30. Check reward chain infusion point VDF rc_target_vdf_info = VDFInfo( rc_vdf_challenge, ip_vdf_iters, header_block.reward_chain_sub_block.reward_chain_ip_vdf.output, ) if not header_block.reward_chain_ip_proof.is_valid( constants, ClassgroupElement.get_default_element(), header_block.reward_chain_sub_block.reward_chain_ip_vdf, rc_target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_IP_VDF) # 31. Check infused challenge chain infusion point VDF if not genesis_block: overflow = is_overflow_sub_block( constants, header_block.reward_chain_sub_block.signage_point_index) deficit = calculate_deficit( constants, header_block.height, prev_sb, overflow, len(header_block.finished_sub_slots), ) if header_block.reward_chain_sub_block.infused_challenge_chain_ip_vdf is None: # If we don't have an ICC chain, deficit must be 4 or 5 if deficit < constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1: return None, ValidationError(Err.INVALID_ICC_VDF) else: assert header_block.infused_challenge_chain_ip_proof is not None # If we have an ICC chain, deficit must be 0, 1, 2 or 3 if deficit >= constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1: return ( None, ValidationError( Err.INVALID_ICC_VDF, f"icc vdf and deficit is bigger or equal to {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1}", ), ) if new_sub_slot: last_ss = header_block.finished_sub_slots[-1] assert last_ss.infused_challenge_chain is not None icc_vdf_challenge: bytes32 = last_ss.infused_challenge_chain.get_hash( ) icc_vdf_input = ClassgroupElement.get_default_element() else: assert prev_sb is not None if prev_sb.is_challenge_sub_block(constants): icc_vdf_input = ClassgroupElement.get_default_element() else: icc_vdf_input = prev_sb.infused_challenge_vdf_output curr = prev_sb while curr.finished_infused_challenge_slot_hashes is None and not curr.is_challenge_sub_block( constants): curr = sub_blocks.sub_block_record(curr.prev_hash) if curr.is_challenge_sub_block(constants): icc_vdf_challenge = curr.challenge_block_info_hash else: assert curr.finished_infused_challenge_slot_hashes is not None icc_vdf_challenge = curr.finished_infused_challenge_slot_hashes[ -1] icc_target_vdf_info = VDFInfo( icc_vdf_challenge, ip_vdf_iters, header_block.reward_chain_sub_block. infused_challenge_chain_ip_vdf.output, ) if not header_block.infused_challenge_chain_ip_proof.is_valid( constants, icc_vdf_input, header_block.reward_chain_sub_block. infused_challenge_chain_ip_vdf, icc_target_vdf_info, ): return None, ValidationError(Err.INVALID_ICC_VDF, "invalid icc proof") else: if header_block.infused_challenge_chain_ip_proof is not None: return None, ValidationError(Err.INVALID_ICC_VDF) # 32. Check reward block hash if header_block.foliage_sub_block.reward_block_hash != header_block.reward_chain_sub_block.get_hash( ): return None, ValidationError(Err.INVALID_REWARD_BLOCK_HASH) # 33. Check reward block is_block if (header_block.foliage_sub_block.foliage_block_hash is not None) != header_block.reward_chain_sub_block.is_block: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) return required_iters, None
def validate_unfinished_header_block( constants: ConsensusConstants, sub_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 sub-block. However, the finished_sub_slots refers to all sub-slots that were finishes from the previous sub-block's infusion point, up to this sub-blocks infusion point. Therefore, in the case where this is an overflow sub-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_sb = sub_blocks.try_sub_block(header_block.prev_header_hash) genesis_block = prev_sb 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_sub_block( constants, header_block.reward_chain_sub_block.signage_point_index) if skip_overflow_last_ss_validation and overflow: 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_sb is not None height = uint32(prev_sb.height + 1) if prev_sb.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_sb.height, prev_sb.deficit, sub_blocks, prev_sb.prev_hash, False, ) else: can_finish_se = False can_finish_epoch = False # 2. Check finished slots that have been crossed since prev_sb 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 sub-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_sb is not None curr: SubBlockRecord = prev_sb while not curr.first_in_sub_slot: curr = sub_blocks.sub_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_sb 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_sb.deficit < constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: # There should be no ICC chain if the last sub 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_sb while not curr.is_challenge_sub_block( constants) and not curr.first_in_sub_slot: curr = sub_blocks.sub_block_record(curr.prev_hash) if curr.is_challenge_sub_block(constants): icc_challenge_hash = curr.challenge_block_info_hash icc_iters_committed = uint64( prev_sb.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_sb.sub_slot_iters icc_iters_proof = uint64(prev_sb.sub_slot_iters - prev_sb.ip_iters(constants)) if prev_sb.is_challenge_sub_block(constants): icc_vdf_input = ClassgroupElement.get_default_element( ) else: icc_vdf_input = prev_sb.infused_challenge_vdf_output else: # This is not the first sub slot after the last sub block, so we might not have an ICC if (header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.deficit < constants.MIN_SUB_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_sb.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_sb 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_SUB_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_sb 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_sb.reward_infusion_new_challenge eos_vdf_iters = uint64(prev_sb.sub_slot_iters - prev_sb.ip_iters(constants)) cc_start_element = prev_sb.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_sb is not None if finished_sub_slot_n == 0: cc_eos_vdf_info_iters = prev_sb.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_SUB_BLOCKS_PER_CHALLENGE_BLOCK: return ( None, ValidationError( Err.INVALID_DEFICIT, f"genesis, expected deficit {constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK}", ), ) else: assert prev_sb is not None if prev_sb.deficit == 0: # 2s. If prev sb had deficit 0, resets deficit to MIN_SUB_BLOCK_PER_CHALLENGE_BLOCK if sub_slot.reward_chain.deficit != constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: log.error( constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK, ) return ( None, ValidationError( Err.INVALID_DEFICIT, f"expected deficit {constants.MIN_SUB_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_sb.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_sb 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, sub_blocks, height, sub_blocks.sub_block_record(prev_sb.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 sub-blocks is less than the max if not new_sub_slot and not genesis_block: assert prev_sb is not None num_sub_blocks = 2 # This includes the current sub-block and the prev sub-block curr = prev_sb while not curr.first_in_sub_slot: num_sub_blocks += 1 curr = sub_blocks.sub_block_record(curr.prev_hash) if num_sub_blocks > constants.MAX_SUB_SLOT_SUB_BLOCKS: return None, ValidationError(Err.TOO_MANY_SUB_BLOCKS) # If sub_block state is correct, we should always find a challenge here # This computes what the challenge should be for this sub-block challenge = get_block_challenge( constants, header_block, sub_blocks, genesis_block, overflow, skip_overflow_last_ss_validation, ) # 5a. Check proof of space if challenge != header_block.reward_chain_sub_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_sub_block.signage_point_index}" f"Prev: {prev_sb}") log.error( f"Challenge {challenge} provided {header_block.reward_chain_sub_block.pos_ss_cc_challenge_hash}" ) return None, ValidationError(Err.INVALID_CC_CHALLENGE) # 5b. Check proof of space if header_block.reward_chain_sub_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_sub_block.challenge_chain_sp_vdf.output.get_hash( ) q_str: Optional[ bytes32] = header_block.reward_chain_sub_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_sub_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 sub-block) required_iters: uint64 = calculate_iterations_quality( q_str, header_block.reward_chain_sub_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_sub_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_sub_block.signage_point_index == 0) != ( header_block.reward_chain_sub_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_sub_block.signage_point_index == 0) != ( header_block.reward_chain_sub_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_sub_block.signage_point_index, ) ip_iters: uint64 = calculate_ip_iters( constants, expected_sub_slot_iters, header_block.reward_chain_sub_block.signage_point_index, required_iters, ) if header_block.reward_chain_sub_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_sb is not None if new_sub_slot: total_iters = prev_sb.total_iters # Add the rest of the slot of prev_sb total_iters = uint128(total_iters + prev_sb.sub_slot_iters - prev_sb.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_sb # This takes the beginning of the slot, and adds ip_iters total_iters = uint128(prev_sb.total_iters - prev_sb.ip_iters(constants)) total_iters = uint128(total_iters + ip_iters) if total_iters != header_block.reward_chain_sub_block.total_iters: return ( None, ValidationError( Err.INVALID_TOTAL_ITERS, f"expected {total_iters} got {header_block.reward_chain_sub_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_sb, sub_blocks, sp_total_iters, sp_iters, ) # 11. Check reward chain sp proof if sp_iters != 0: assert (header_block.reward_chain_sub_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_sub_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_sub_block.reward_chain_sp_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_SP_VDF) rc_sp_hash = header_block.reward_chain_sub_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_sub_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_sb is not None curr = prev_sb while not curr.first_in_sub_slot: curr = sub_blocks.sub_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_sub_block.proof_of_space.plot_public_key, rc_sp_hash, header_block.reward_chain_sub_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_sub_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_sub_block.challenge_chain_sp_vdf.output, ) if header_block.reward_chain_sub_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_sub_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_sub_block.proof_of_space.plot_public_key, cc_sp_hash, header_block.reward_chain_sub_block.challenge_chain_sp_signature, ): return None, ValidationError(Err.INVALID_CC_SIGNATURE, "invalid cc sp sig") # 15. Check is_block if genesis_block: if header_block.foliage_sub_block.foliage_block_hash is None: return None, ValidationError(Err.INVALID_IS_BLOCK, "invalid genesis") else: assert prev_sb is not None # Finds the previous block curr = prev_sb while not curr.is_block: curr = sub_blocks.sub_block_record(curr.prev_hash) # The first sub-block to have an sp > the last block's infusion iters, is a 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_sub_block.foliage_block_hash is not None): return None, ValidationError(Err.INVALID_IS_BLOCK) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage_sub_block.foliage_block_signature is not None): return None, ValidationError(Err.INVALID_IS_BLOCK) # 16. Check foliage sub block signature by plot key if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space.plot_public_key, header_block.foliage_sub_block.foliage_sub_block_data.get_hash(), header_block.foliage_sub_block.foliage_sub_block_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 17. Check foliage block signature by plot key if header_block.foliage_sub_block.foliage_block_hash is not None: if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space. plot_public_key, header_block.foliage_sub_block.foliage_block_hash, header_block.foliage_sub_block.foliage_block_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 18. Check unfinished reward chain sub block hash if (header_block.reward_chain_sub_block.get_hash() != header_block.foliage_sub_block.foliage_sub_block_data. unfinished_reward_block_hash): return None, ValidationError(Err.INVALID_URSB_HASH) # 19. Check pool target max height if (header_block.foliage_sub_block.foliage_sub_block_data.pool_target. max_height != 0 and header_block.foliage_sub_block. foliage_sub_block_data.pool_target.max_height < height): return None, ValidationError(Err.OLD_POOL_TARGET) # 20a. Check pre-farm puzzle hashes for genesis sub-block. if genesis_block: if (header_block.foliage_sub_block.foliage_sub_block_data.pool_target. puzzle_hash != constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH): log.error( f"Pool target {header_block.foliage_sub_block.foliage_sub_block_data.pool_target} hb {header_block}" ) return None, ValidationError(Err.INVALID_PREFARM) if (header_block.foliage_sub_block.foliage_sub_block_data. farmer_reward_puzzle_hash != constants.GENESIS_PRE_FARM_FARMER_PUZZLE_HASH): return None, ValidationError(Err.INVALID_PREFARM) else: # 20b. Check pool target signature. Should not check this for genesis sub-block. if not AugSchemeMPL.verify( header_block.reward_chain_sub_block.proof_of_space. pool_public_key, bytes(header_block.foliage_sub_block.foliage_sub_block_data. pool_target), header_block.foliage_sub_block.foliage_sub_block_data. pool_signature, ): return None, ValidationError(Err.INVALID_POOL_SIGNATURE) # 21. Check extension data if applicable. None for mainnet. # 22. Check if foliage block is present if (header_block.foliage_sub_block.foliage_block_hash is not None) != (header_block.foliage_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if (header_block.foliage_sub_block.foliage_block_signature is not None) != (header_block.foliage_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if header_block.foliage_block is not None: # 23. Check foliage block hash if header_block.foliage_block.get_hash( ) != header_block.foliage_sub_block.foliage_block_hash: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_HASH) if genesis_block: # 24a. Check prev block hash for genesis if header_block.foliage_block.prev_block_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) else: assert prev_sb is not None # 24b. Check prev block hash for non-genesis curr_sb: SubBlockRecord = prev_sb while not curr_sb.is_block: curr_sb = sub_blocks.sub_block_record(curr_sb.prev_hash) if not header_block.foliage_block.prev_block_hash == curr_sb.header_hash: log.error( f"Prev BH: {header_block.foliage_block.prev_block_hash} {curr_sb.header_hash} curr sb: {curr_sb}" ) 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_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_sb is not None: last_timestamps: List[uint64] = [] curr_sb = sub_blocks.sub_block_record( header_block.foliage_block.prev_block_hash) assert curr_sb.timestamp is not None while len(last_timestamps) < constants.NUMBER_OF_TIMESTAMPS: last_timestamps.append(curr_sb.timestamp) fetched: Optional[SubBlockRecord] = sub_blocks.try_sub_block( curr_sb.prev_block_hash) if not fetched: break curr_sb = fetched if len(last_timestamps) != constants.NUMBER_OF_TIMESTAMPS: # For blocks 1 to 10, average timestamps of all previous blocks assert curr_sb.height == 0 prev_time: uint64 = uint64( int(sum(last_timestamps) // len(last_timestamps))) if header_block.foliage_block.timestamp <= prev_time: return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_PAST) if header_block.foliage_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 block_to_sub_block_record( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], height_to_hash: Dict[uint32, bytes32], required_iters: uint64, full_block: Optional[Union[FullBlock, HeaderBlock]], header_block: Optional[HeaderBlock], ): if full_block is None: assert header_block is not None block: Union[HeaderBlock, FullBlock] = header_block else: block = full_block if block.sub_block_height == 0: prev_sb: Optional[SubBlockRecord] = None sub_slot_iters: uint64 = uint64(constants.SUB_SLOT_ITERS_STARTING) height = 0 else: prev_sb = sub_blocks[block.prev_header_hash] assert prev_sb is not None if prev_sb.is_block: height = prev_sb.height + 1 else: height = prev_sb.height sub_slot_iters = get_next_sub_slot_iters( constants, sub_blocks, height_to_hash, prev_sb.prev_hash, prev_sb.sub_block_height, prev_sb.sub_slot_iters, prev_sb.deficit, len(block.finished_sub_slots) > 0, prev_sb.sp_total_iters(constants), ) overflow = is_overflow_sub_block( constants, block.reward_chain_sub_block.signage_point_index) deficit = calculate_deficit( constants, block.sub_block_height, prev_sb, overflow, len(block.finished_sub_slots), ) prev_block_hash = block.foliage_block.prev_block_hash if block.foliage_block is not None else None timestamp = block.foliage_block.timestamp if block.foliage_block is not None else None fees = block.transactions_info.fees if block.transactions_info is not None else None # reward_claims_incorporated = ( # block.transactions_info.reward_claims_incorporated if block.transactions_info is not None else None # ) if len(block.finished_sub_slots) > 0: finished_challenge_slot_hashes: Optional[List[bytes32]] = [ sub_slot.challenge_chain.get_hash() for sub_slot in block.finished_sub_slots ] finished_reward_slot_hashes: Optional[List[bytes32]] = [ sub_slot.reward_chain.get_hash() for sub_slot in block.finished_sub_slots ] finished_infused_challenge_slot_hashes: Optional[List[bytes32]] = [ sub_slot.infused_challenge_chain.get_hash() for sub_slot in block.finished_sub_slots if sub_slot.infused_challenge_chain is not None ] elif block.sub_block_height == 0: finished_challenge_slot_hashes = [constants.FIRST_CC_CHALLENGE] finished_reward_slot_hashes = [constants.FIRST_RC_CHALLENGE] finished_infused_challenge_slot_hashes = None else: finished_challenge_slot_hashes = None finished_reward_slot_hashes = None finished_infused_challenge_slot_hashes = None found_ses_hash: Optional[bytes32] = None ses: Optional[SubEpochSummary] = None if len(block.finished_sub_slots) > 0: for sub_slot in block.finished_sub_slots: if sub_slot.challenge_chain.subepoch_summary_hash is not None: found_ses_hash = sub_slot.challenge_chain.subepoch_summary_hash if found_ses_hash: assert prev_sb is not None assert len(block.finished_sub_slots) > 0 ses = make_sub_epoch_summary( constants, sub_blocks, block.sub_block_height, sub_blocks[prev_sb.prev_hash], block.finished_sub_slots[0].challenge_chain.new_difficulty, block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters, ) assert ses.get_hash() == found_ses_hash cbi = ChallengeBlockInfo( block.reward_chain_sub_block.proof_of_space, block.reward_chain_sub_block.challenge_chain_sp_vdf, block.reward_chain_sub_block.challenge_chain_sp_signature, block.reward_chain_sub_block.challenge_chain_ip_vdf, ) if block.reward_chain_sub_block.infused_challenge_chain_ip_vdf is not None: icc_output: Optional[ ClassgroupElement] = block.reward_chain_sub_block.infused_challenge_chain_ip_vdf.output else: icc_output = None return SubBlockRecord( block.header_hash, block.prev_header_hash, block.sub_block_height, uint32(height), block.weight, block.total_iters, block.reward_chain_sub_block.signage_point_index, block.reward_chain_sub_block.challenge_chain_ip_vdf.output, icc_output, block.reward_chain_sub_block.get_hash(), cbi.get_hash(), sub_slot_iters, block.foliage_sub_block.foliage_sub_block_data.pool_target.puzzle_hash, block.foliage_sub_block.foliage_sub_block_data. farmer_reward_puzzle_hash, required_iters, deficit, overflow, timestamp, prev_block_hash, fees, # reward_claims_incorporated, finished_challenge_slot_hashes, finished_infused_challenge_slot_hashes, finished_reward_slot_hashes, ses, )
def finish_sub_block( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], height_to_hash: Dict[uint32, bytes32], finished_sub_slots: List[EndOfSubSlotBundle], sub_slot_start_total_iters: uint128, signage_point_index: uint8, unfinished_block: UnfinishedBlock, required_iters: uint64, ip_iters: uint64, slot_cc_challenge: bytes32, slot_rc_challenge: bytes32, latest_sub_block: SubBlockRecord, sub_slot_iters: uint64, difficulty: uint64, ): is_overflow = is_overflow_sub_block(constants, signage_point_index) cc_vdf_challenge = slot_cc_challenge if len(finished_sub_slots) == 0: new_ip_iters = unfinished_block.total_iters - latest_sub_block.total_iters cc_vdf_input = latest_sub_block.challenge_vdf_output rc_vdf_challenge = latest_sub_block.reward_infusion_new_challenge else: new_ip_iters = ip_iters cc_vdf_input = ClassgroupElement.get_default_element() rc_vdf_challenge = slot_rc_challenge cc_ip_vdf, cc_ip_proof = get_vdf_info_and_proof( constants, cc_vdf_input, cc_vdf_challenge, new_ip_iters, ) cc_ip_vdf = replace(cc_ip_vdf, number_of_iterations=ip_iters) deficit = calculate_deficit( constants, uint32(latest_sub_block.sub_block_height + 1), latest_sub_block, is_overflow, len(finished_sub_slots), ) icc_ip_vdf, icc_ip_proof = get_icc( constants, unfinished_block.total_iters, finished_sub_slots, latest_sub_block, sub_blocks, uint128(sub_slot_start_total_iters + sub_slot_iters) if is_overflow else sub_slot_start_total_iters, deficit, ) rc_ip_vdf, rc_ip_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), rc_vdf_challenge, new_ip_iters, ) assert unfinished_block is not None sp_total_iters = uint128( sub_slot_start_total_iters + calculate_sp_iters(constants, sub_slot_iters, signage_point_index) ) full_block: FullBlock = unfinished_block_to_full_block( unfinished_block, cc_ip_vdf, cc_ip_proof, rc_ip_vdf, rc_ip_proof, icc_ip_vdf, icc_ip_proof, finished_sub_slots, latest_sub_block, sub_blocks, sp_total_iters, difficulty, ) sub_block_record = block_to_sub_block_record( constants, sub_blocks, height_to_hash, required_iters, full_block, None ) return full_block, sub_block_record
def create_genesis_block( self, constants: ConsensusConstants, seed: bytes32 = b"", timestamp: Optional[uint64] = None, farmer_reward_puzzle_hash: Optional[bytes32] = None, force_overflow: bool = False, skip_slots: int = 0, ) -> FullBlock: if timestamp is None: timestamp = uint64(int(time.time())) if farmer_reward_puzzle_hash is None: farmer_reward_puzzle_hash = self.farmer_ph finished_sub_slots: List[EndOfSubSlotBundle] = [] unfinished_block: Optional[UnfinishedBlock] = None ip_iters: uint64 = uint64(0) sub_slot_total_iters: uint128 = uint128(0) # Keep trying until we get a good proof of space that also passes sp filter while True: cc_challenge, rc_challenge = get_challenges(constants, {}, finished_sub_slots, None) for signage_point_index in range(0, constants.NUM_SPS_SUB_SLOT): signage_point: SignagePoint = get_signage_point( constants, {}, None, sub_slot_total_iters, uint8(signage_point_index), finished_sub_slots, constants.SUB_SLOT_ITERS_STARTING, ) if signage_point_index == 0: cc_sp_output_hash: bytes32 = cc_challenge else: assert signage_point is not None assert signage_point.cc_vdf is not None cc_sp_output_hash = signage_point.cc_vdf.output.get_hash() # If did not reach the target slots to skip, don't make any proofs for this sub-slot qualified_proofs: List[Tuple[uint64, ProofOfSpace]] = self.get_pospaces_for_challenge( constants, cc_challenge, cc_sp_output_hash, seed, constants.DIFFICULTY_STARTING, constants.SUB_SLOT_ITERS_STARTING, ) # Try each of the proofs of space for required_iters, proof_of_space in qualified_proofs: sp_iters: uint64 = calculate_sp_iters( constants, uint64(constants.SUB_SLOT_ITERS_STARTING), uint8(signage_point_index), ) ip_iters = calculate_ip_iters( constants, uint64(constants.SUB_SLOT_ITERS_STARTING), uint8(signage_point_index), required_iters, ) is_overflow_block = is_overflow_sub_block(constants, uint8(signage_point_index)) if force_overflow and not is_overflow_block: continue if len(finished_sub_slots) < skip_slots: continue unfinished_block = create_unfinished_block( constants, sub_slot_total_iters, constants.SUB_SLOT_ITERS_STARTING, uint8(signage_point_index), sp_iters, ip_iters, proof_of_space, cc_challenge, farmer_reward_puzzle_hash, PoolTarget(constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH, uint32(0)), self.get_plot_signature, self.get_pool_key_signature, signage_point, timestamp, seed, finished_sub_slots_input=finished_sub_slots, ) assert unfinished_block is not None if not is_overflow_block: cc_ip_vdf, cc_ip_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), cc_challenge, ip_iters, ) cc_ip_vdf = replace(cc_ip_vdf, number_of_iterations=ip_iters) rc_ip_vdf, rc_ip_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), rc_challenge, ip_iters, ) assert unfinished_block is not None total_iters_sp = uint128(sub_slot_total_iters + sp_iters) return unfinished_block_to_full_block( unfinished_block, cc_ip_vdf, cc_ip_proof, rc_ip_vdf, rc_ip_proof, None, None, finished_sub_slots, None, {}, total_iters_sp, constants.DIFFICULTY_STARTING, ) if signage_point_index == constants.NUM_SPS_SUB_SLOT - constants.NUM_SP_INTERVALS_EXTRA - 1: # Finish the end of sub-slot and try again next sub-slot cc_vdf, cc_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), cc_challenge, constants.SUB_SLOT_ITERS_STARTING, ) rc_vdf, rc_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), rc_challenge, constants.SUB_SLOT_ITERS_STARTING, ) cc_slot = ChallengeChainSubSlot(cc_vdf, None, None, None, None) finished_sub_slots.append( EndOfSubSlotBundle( cc_slot, None, RewardChainSubSlot( rc_vdf, cc_slot.get_hash(), None, uint8(constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK), ), SubSlotProofs(cc_proof, None, rc_proof), ) ) if unfinished_block is not None: cc_ip_vdf, cc_ip_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), finished_sub_slots[-1].challenge_chain.get_hash(), ip_iters, ) rc_ip_vdf, rc_ip_proof = get_vdf_info_and_proof( constants, ClassgroupElement.get_default_element(), finished_sub_slots[-1].reward_chain.get_hash(), ip_iters, ) total_iters_sp = uint128( sub_slot_total_iters + calculate_sp_iters( self.constants, self.constants.SUB_SLOT_ITERS_STARTING, unfinished_block.reward_chain_sub_block.signage_point_index, ) ) return unfinished_block_to_full_block( unfinished_block, cc_ip_vdf, cc_ip_proof, rc_ip_vdf, rc_ip_proof, None, None, finished_sub_slots, None, {}, total_iters_sp, constants.DIFFICULTY_STARTING, ) sub_slot_total_iters = uint128(sub_slot_total_iters + constants.SUB_SLOT_ITERS_STARTING)
def next_sub_epoch_summary( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], height_to_hash: Dict[uint32, bytes32], required_iters: uint64, block: Union[UnfinishedBlock, FullBlock], can_finish_soon: bool = False, ) -> Optional[SubEpochSummary]: """ Returns the sub-epoch summary that can be included in the sub-block after block. If it should include one. Block must be eligible to be the last sub-block in the epoch. If not, returns None. Assumes that there is a new slot ending after block. Args: constants: consensus constants being used for this chain sub_blocks: dictionary from header hash to SBR of all included SBR height_to_hash: dictionary from sub-block height to header hash required_iters: required iters of the proof of space in block block: the (potentially) last sub-block in the new epoch can_finish_soon: this is useful when sending SES to timelords. We might not be able to finish it, but we will soon (within MAX_SUB_SLOT_SUB_BLOCKS) Returns: object: the new sub-epoch summary """ signage_point_index = block.reward_chain_sub_block.signage_point_index prev_sb: Optional[SubBlockRecord] = sub_blocks.get(block.prev_header_hash, None) if prev_sb is None or prev_sb.sub_block_height == 0: return None if len(block.finished_sub_slots) > 0 and block.finished_sub_slots[ 0].challenge_chain.new_difficulty is not None: # We just included a sub-epoch summary return None assert prev_sb is not None # This is the ssi of the current block sub_slot_iters = get_next_sub_slot_iters( constants, sub_blocks, height_to_hash, prev_sb.prev_hash, prev_sb.sub_block_height, prev_sb.sub_slot_iters, prev_sb.deficit, len(block.finished_sub_slots) > 0, prev_sb.sp_total_iters(constants), ) overflow = is_overflow_sub_block(constants, signage_point_index) deficit = calculate_deficit( constants, uint32(prev_sb.sub_block_height + 1), prev_sb, overflow, len(block.finished_sub_slots), ) can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch( constants, uint32(prev_sb.sub_block_height + 1), deficit, sub_blocks, prev_sb.header_hash if prev_sb is not None else None, can_finish_soon, ) # can't finish se, no summary if not can_finish_se: return None next_difficulty = None next_sub_slot_iters = None # if can finish epoch, new difficulty and ssi if can_finish_epoch: sp_iters = calculate_sp_iters(constants, sub_slot_iters, signage_point_index) ip_iters = calculate_ip_iters(constants, sub_slot_iters, signage_point_index, required_iters) next_difficulty = get_next_difficulty( constants, sub_blocks, height_to_hash, block.prev_header_hash, uint32(prev_sb.sub_block_height + 1), uint64(prev_sb.weight - sub_blocks[prev_sb.prev_hash].weight), deficit, True, uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)), True, ) next_sub_slot_iters = get_next_sub_slot_iters( constants, sub_blocks, height_to_hash, block.prev_header_hash, uint32(prev_sb.sub_block_height + 1), sub_slot_iters, deficit, True, uint128(block.total_iters - ip_iters + sp_iters - (sub_slot_iters if overflow else 0)), True, ) return make_sub_epoch_summary( constants, sub_blocks, uint32(prev_sb.sub_block_height + 2), prev_sb, next_difficulty, next_sub_slot_iters, )
async def _check_for_new_ip(self): infusion_iters = [ iteration for iteration, t in self.iteration_to_proof_type.items() if t == IterationType.INFUSION_POINT ] for iteration in infusion_iters: proofs_with_iter = [(chain, info, proof) for chain, info, proof in self.proofs_finished if info.number_of_iterations == iteration] if self.last_state.get_challenge( Chain.INFUSED_CHALLENGE_CHAIN) is not None: chain_count = 3 else: chain_count = 2 if len(proofs_with_iter) == chain_count: block = None ip_iters = None for unfinished_block in self.unfinished_blocks: try: _, ip_iters = iters_from_sub_block( self.constants, unfinished_block.reward_chain_sub_block, self.last_state.get_sub_slot_iters(), self.last_state.get_difficulty(), ) except Exception: continue if ip_iters - self.last_state.get_last_ip() == iteration: block = unfinished_block break if block is not None: ip_total_iters = self.last_state.get_total_iters( ) + iteration challenge = block.reward_chain_sub_block.get_hash() icc_info: Optional[VDFInfo] = None icc_proof: Optional[VDFProof] = None cc_info: Optional[VDFInfo] = None cc_proof: Optional[VDFProof] = None rc_info: Optional[VDFInfo] = None rc_proof: Optional[VDFProof] = None for chain, info, proof in proofs_with_iter: if chain == Chain.CHALLENGE_CHAIN: cc_info = info cc_proof = proof if chain == Chain.REWARD_CHAIN: rc_info = info rc_proof = proof if chain == Chain.INFUSED_CHALLENGE_CHAIN: icc_info = info icc_proof = proof if cc_info is None or cc_proof is None or rc_info is None or rc_proof is None: log.error( f"Insufficient VDF proofs for infusion point ch: {challenge} iterations:{iteration}" ) return if rc_info.challenge != self.last_state.get_challenge( Chain.REWARD_CHAIN): log.warning( f"Do not have correct challenge {self.last_state.get_challenge(Chain.REWARD_CHAIN).hex()} " f"has {rc_info.challenge}, partial hash {block.reward_chain_sub_block.get_hash()}" ) # This proof is on an outdated challenge, so don't use it continue self.unfinished_blocks.remove(block) self.total_infused += 1 log.info( f"Generated infusion point for challenge: {challenge} iterations: {iteration}." ) if not self.last_state.can_infuse_sub_block(): log.warning( "Too many sub-blocks, cannot infuse, discarding") # Too many sub blocks return overflow = is_overflow_sub_block( self.constants, block.reward_chain_sub_block.signage_point_index) cc_info = dataclasses.replace( cc_info, number_of_iterations=ip_iters) response = timelord_protocol.NewInfusionPointVDF( challenge, cc_info, cc_proof, rc_info, rc_proof, icc_info, icc_proof, ) msg = Message("new_infusion_point_vdf", response) if self.server is not None: await self.server.send_to_all([msg], NodeType.FULL_NODE) self.proofs_finished = self._clear_proof_list(iteration) if (self.last_state.get_last_block_total_iters() is None and not self.last_state.state_type == StateType.FIRST_SUB_SLOT): # We don't know when the last block was, so we can't make peaks return sp_total_iters = ( ip_total_iters - ip_iters + calculate_sp_iters( self.constants, block.sub_slot_iters, block.reward_chain_sub_block.signage_point_index, ) - (block.sub_slot_iters if overflow else 0)) if self.last_state.state_type == StateType.FIRST_SUB_SLOT: is_block = True sub_block_height: uint32 = uint32(0) else: is_block = self.last_state.get_last_block_total_iters( ) < sp_total_iters sub_block_height: uint32 = self.last_state.get_height( ) + 1 if sub_block_height < 5: # Don't directly update our state for the first few sub-blocks, because we cannot validate # whether the pre-farm is correct return new_reward_chain_sub_block = RewardChainSubBlock( self.last_state.get_weight() + block.difficulty, sub_block_height, ip_total_iters, block.reward_chain_sub_block.signage_point_index, block.reward_chain_sub_block.pos_ss_cc_challenge_hash, block.reward_chain_sub_block.proof_of_space, block.reward_chain_sub_block.challenge_chain_sp_vdf, block.reward_chain_sub_block. challenge_chain_sp_signature, cc_info, block.reward_chain_sub_block.reward_chain_sp_vdf, block.reward_chain_sub_block.reward_chain_sp_signature, rc_info, icc_info, is_block, ) if self.last_state.state_type == StateType.FIRST_SUB_SLOT: # Genesis new_deficit = self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1 elif overflow and self.last_state.deficit == self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: if self.last_state.peak is not None: assert self.last_state.subslot_end is None # This means the previous sub-block is also an overflow sub-block, and did not manage # to lower the deficit, therefore we cannot lower it either. (new slot) new_deficit = self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK else: # This means we are the first infusion in this sub-slot. This may be a new slot or not. assert self.last_state.subslot_end is not None if self.last_state.subslot_end.infused_challenge_chain is None: # There is no ICC, which means we are not finishing a slot. We can reduce the deficit. new_deficit = self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1 else: # There is an ICC, which means we are finishing a slot. Different slot, so can't change # the deficit new_deficit = self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK else: new_deficit = max(self.last_state.deficit - 1, 0) if new_deficit == self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK - 1: last_csb_or_eos = ip_total_iters else: last_csb_or_eos = self.last_state.last_challenge_sb_or_eos_total_iters if self.last_state.get_infused_sub_epoch_summary( ) is not None: new_sub_epoch_summary = None else: new_sub_epoch_summary = block.sub_epoch_summary self.new_peak = timelord_protocol.NewPeak( new_reward_chain_sub_block, block.difficulty, new_deficit, block.sub_slot_iters, new_sub_epoch_summary, self.last_state.reward_challenge_cache, last_csb_or_eos, ) if self.total_unfinished > 0: infusion_rate = int(self.total_infused / self.total_unfinished * 100) log.info( f"Total unfinished blocks: {self.total_unfinished}." f"Total infused blocks: {self.total_infused}." f"Infusion rate: {infusion_rate}.") await self._handle_new_peak() # Break so we alternate between checking SP and IP break