def test_truncate_to_significant_bits(self): a = -0b001101 assert truncate_to_significant_bits(a, 2) == -0b1100 a = -0b001111 assert truncate_to_significant_bits(a, 2) == -0b1100 a = 0b1111 assert truncate_to_significant_bits(a, 2) == 0b1100 a = 0b1000000111 assert truncate_to_significant_bits(a, 8) == 0b1000000100 a = 0b1000000111 assert truncate_to_significant_bits(a, 0) == 0b0 a = 0b1000000111 assert truncate_to_significant_bits(a, 500) == a a = 0b10101 assert truncate_to_significant_bits(a, 5) == a a = 0b10101 assert truncate_to_significant_bits(a, 4) == 0b10100
def get_min_iters(self, block_record: BlockRecord) -> uint64: """ Returns the min_iters value, which is calculated every epoch. This requires looking up the epoch barrier blocks, and taking 10% of the total iterations in the previous epoch. """ curr = block_record if (curr.height < self.constants["DIFFICULTY_EPOCH"] + self.constants["DIFFICULTY_DELAY"]): return self.constants["MIN_ITERS_STARTING"] if (curr.height % self.constants["DIFFICULTY_EPOCH"] < self.constants["DIFFICULTY_DELAY"]): # First few blocks of epoch (using old difficulty and min_iters) height2 = (curr.height - (curr.height % self.constants["DIFFICULTY_EPOCH"]) - self.constants["DIFFICULTY_EPOCH"] - 1) else: # The rest of the blocks of epoch (using new difficulty and min iters) height2 = (curr.height - (curr.height % self.constants["DIFFICULTY_EPOCH"]) - 1) height1 = height2 - self.constants["DIFFICULTY_EPOCH"] assert height2 > 0 iters1: Optional[uint64] = uint64(0) iters2: Optional[uint64] = None while curr.height > height1 and curr.height > 0: if curr.height == height2: iters2 = curr.total_iters curr = self.block_records[curr.prev_header_hash] if height1 > -1: # For height of -1, total iters is 0 iters1 = curr.total_iters assert iters1 is not None assert iters2 is not None min_iters_precise = uint64( (iters2 - iters1) // (self.constants["DIFFICULTY_EPOCH"] * self.constants["MIN_ITERS_PROPORTION"])) # Truncates to only 12 bits plus 0s. This prevents grinding attacks. return uint64( truncate_to_significant_bits(min_iters_precise, self.constants["SIGNIFICANT_BITS"]))
def get_consecutive_blocks( self, test_constants: ConsensusConstants, num_blocks: int, block_list: List[FullBlock] = [], seconds_per_block=None, seed: bytes = b"", reward_puzzlehash: bytes32 = None, transaction_data_at_height: Dict[int, Tuple[Program, G2Element]] = None, fees: uint64 = uint64(0), ) -> List[FullBlock]: if transaction_data_at_height is None: transaction_data_at_height = {} if seconds_per_block is None: seconds_per_block = test_constants.BLOCK_TIME_TARGET if len(block_list) == 0: block_list.append( FullBlock.from_bytes(test_constants.GENESIS_BLOCK)) prev_difficulty = test_constants.DIFFICULTY_STARTING curr_difficulty = prev_difficulty curr_min_iters = test_constants.MIN_ITERS_STARTING elif len(block_list) < (test_constants.DIFFICULTY_EPOCH + test_constants.DIFFICULTY_DELAY): # First epoch (+delay), so just get first difficulty prev_difficulty = block_list[0].weight curr_difficulty = block_list[0].weight assert test_constants.DIFFICULTY_STARTING == prev_difficulty curr_min_iters = test_constants.MIN_ITERS_STARTING else: curr_difficulty = block_list[-1].weight - block_list[-2].weight prev_difficulty = ( block_list[-1 - test_constants.DIFFICULTY_EPOCH].weight - block_list[-2 - test_constants.DIFFICULTY_EPOCH].weight) assert block_list[-1].proof_of_time is not None curr_min_iters = calculate_min_iters_from_iterations( block_list[-1].proof_of_space, curr_difficulty, block_list[-1].proof_of_time.number_of_iterations, test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG, ) starting_height = block_list[-1].height + 1 timestamp = block_list[-1].header.data.timestamp for next_height in range(starting_height, starting_height + num_blocks): if (next_height > test_constants.DIFFICULTY_EPOCH and next_height % test_constants.DIFFICULTY_EPOCH == test_constants.DIFFICULTY_DELAY): # Calculates new difficulty height1 = uint64(next_height - (test_constants.DIFFICULTY_EPOCH + test_constants.DIFFICULTY_DELAY) - 1) height2 = uint64(next_height - (test_constants.DIFFICULTY_EPOCH) - 1) height3 = uint64(next_height - (test_constants.DIFFICULTY_DELAY) - 1) if height1 >= 0: block1 = block_list[height1] iters1 = block1.header.data.total_iters timestamp1 = block1.header.data.timestamp else: block1 = block_list[0] timestamp1 = uint64(block1.header.data.timestamp - test_constants.BLOCK_TIME_TARGET) iters1 = uint64(0) timestamp2 = block_list[height2].header.data.timestamp timestamp3 = block_list[height3].header.data.timestamp block3 = block_list[height3] iters3 = block3.header.data.total_iters term1 = (test_constants.DIFFICULTY_DELAY * prev_difficulty * (timestamp3 - timestamp2) * test_constants.BLOCK_TIME_TARGET) term2 = ((test_constants.DIFFICULTY_WARP_FACTOR - 1) * (test_constants.DIFFICULTY_EPOCH - test_constants.DIFFICULTY_DELAY) * curr_difficulty * (timestamp2 - timestamp1) * test_constants.BLOCK_TIME_TARGET) # Round down after the division new_difficulty_precise: uint64 = uint64( (term1 + term2) // (test_constants.DIFFICULTY_WARP_FACTOR * (timestamp3 - timestamp2) * (timestamp2 - timestamp1))) new_difficulty = uint64( truncate_to_significant_bits( new_difficulty_precise, test_constants.SIGNIFICANT_BITS)) max_diff = uint64( truncate_to_significant_bits( test_constants.DIFFICULTY_FACTOR * curr_difficulty, test_constants.SIGNIFICANT_BITS, )) min_diff = uint64( truncate_to_significant_bits( curr_difficulty // test_constants.DIFFICULTY_FACTOR, test_constants.SIGNIFICANT_BITS, )) if new_difficulty >= curr_difficulty: new_difficulty = min( new_difficulty, max_diff, ) else: new_difficulty = max([uint64(1), new_difficulty, min_diff]) min_iters_precise = uint64( (iters3 - iters1) // (test_constants.DIFFICULTY_EPOCH * test_constants.MIN_ITERS_PROPORTION)) curr_min_iters = uint64( truncate_to_significant_bits( min_iters_precise, test_constants.SIGNIFICANT_BITS)) prev_difficulty = curr_difficulty curr_difficulty = new_difficulty time_taken = seconds_per_block timestamp += time_taken transactions: Optional[Program] = None aggsig: Optional[G2Element] = None if next_height in transaction_data_at_height: transactions, aggsig = transaction_data_at_height[next_height] update_difficulty = (next_height % test_constants.DIFFICULTY_EPOCH == test_constants.DIFFICULTY_DELAY) block_list.append( self.create_next_block( test_constants, block_list[-1], timestamp, update_difficulty, curr_difficulty, curr_min_iters, seed, reward_puzzlehash, transactions, aggsig, fees, )) return block_list
def _get_next_difficulty( constants: ConsensusConstants, blocks: BlockchainInterface, prev_header_hash: bytes32, height: uint32, current_difficulty: uint64, deficit: uint8, block_at_height_included_ses: bool, new_slot: bool, signage_point_total_iters: uint128, skip_epoch_check=False, ) -> uint64: """ Returns the difficulty of the next block that extends onto block. Used to calculate the number of iterations. WARNING: assumes that the block at height is not the first block in a sub-epoch. Args: constants: consensus constants being used for this chain blocks: dictionary from header hash to SBR of all included SBR prev_header_hash: header hash of the previous block height: the block height of the block to look at deficit: deficit of block at height height current_difficulty: difficulty at the infusion point of the block at height new_slot: whether or not there is a new slot after height signage_point_total_iters: signage point iters of the block at height skip_epoch_check: don't check correct epoch """ next_height: uint32 = uint32(height + 1) if next_height < constants.EPOCH_BLOCKS: # We are in the first epoch return uint64(constants.DIFFICULTY_STARTING) if not blocks.contains_block(prev_header_hash): raise ValueError(f"Header hash {prev_header_hash} not in blocks") prev_b: BlockRecord = blocks.block_record(prev_header_hash) # If we are in the same slot as previous block, return same difficulty if not skip_epoch_check: _, can_finish_epoch = can_finish_sub_and_full_epoch( constants, blocks, height, prev_header_hash, deficit, block_at_height_included_ses) if not new_slot or not can_finish_epoch: return current_difficulty last_block_prev: BlockRecord = _get_second_to_last_transaction_block_in_previous_epoch( constants, blocks, prev_b) # This gets the last transaction block before this block's signage point. Assuming the block at height height # is the last block infused in the epoch: If this block ends up being a # transaction block, then last_block_curr will be the second to last tx block in the epoch. If this block # is not a transaction block, that means there was exactly one other tx block included in between our signage # point and infusion point, and therefore last_block_curr is the second to last as well. last_block_curr = prev_b while last_block_curr.total_iters > signage_point_total_iters or not last_block_curr.is_transaction_block: last_block_curr = blocks.block_record(last_block_curr.prev_hash) assert last_block_curr.timestamp is not None assert last_block_prev.timestamp is not None actual_epoch_time: uint64 = uint64(last_block_curr.timestamp - last_block_prev.timestamp) old_difficulty = uint64(prev_b.weight - blocks.block_record(prev_b.prev_hash).weight) # Terms are rearranged so there is only one division. new_difficulty_precise = uint64( (last_block_curr.weight - last_block_prev.weight) * constants.SUB_SLOT_TIME_TARGET // (constants.SLOT_BLOCKS_TARGET * actual_epoch_time)) # Only change by a max factor, to prevent attacks, as in greenpaper, and must be at least 1 max_diff = uint64(constants.DIFFICULTY_CHANGE_MAX_FACTOR * old_difficulty) min_diff = uint64(old_difficulty // constants.DIFFICULTY_CHANGE_MAX_FACTOR) if new_difficulty_precise >= old_difficulty: new_difficulty_precise = uint64(min(new_difficulty_precise, max_diff)) else: new_difficulty_precise = uint64( max([uint64(1), new_difficulty_precise, min_diff])) new_difficulty = truncate_to_significant_bits(new_difficulty_precise, constants.SIGNIFICANT_BITS) assert count_significant_bits(new_difficulty) <= constants.SIGNIFICANT_BITS return uint64(new_difficulty)
def _get_next_sub_slot_iters( constants: ConsensusConstants, blocks: BlockchainInterface, prev_header_hash: bytes32, height: uint32, curr_sub_slot_iters: uint64, deficit: uint8, block_at_height_included_ses: bool, new_slot: bool, signage_point_total_iters: uint128, skip_epoch_check=False, ) -> uint64: """ Returns the slot iterations required for the next block after the one at height, where new_slot is true iff the next block will be in the next slot. WARNING: assumes that the block at height is not the first block in a sub-epoch. Args: constants: consensus constants being used for this chain blocks: dictionary from header hash to SBR of all included SBR prev_header_hash: header hash of the previous block height: the block height of the block to look at curr_sub_slot_iters: sub-slot iters at the infusion point of the block at height deficit: deficit of block at height height new_slot: whether or not there is a new slot after height signage_point_total_iters: signage point iters of the block at height skip_epoch_check: don't check correct epoch """ next_height: uint32 = uint32(height + 1) if next_height < constants.EPOCH_BLOCKS: return uint64(constants.SUB_SLOT_ITERS_STARTING) if not blocks.contains_block(prev_header_hash): raise ValueError(f"Header hash {prev_header_hash} not in blocks") prev_b: BlockRecord = blocks.block_record(prev_header_hash) # If we are in the same epoch, return same ssi if not skip_epoch_check: _, can_finish_epoch = can_finish_sub_and_full_epoch( constants, blocks, height, prev_header_hash, deficit, block_at_height_included_ses) if not new_slot or not can_finish_epoch: return curr_sub_slot_iters last_block_prev: BlockRecord = _get_second_to_last_transaction_block_in_previous_epoch( constants, blocks, prev_b) # This gets the last transaction block before this block's signage point. Assuming the block at height height # is the last block infused in the epoch: If this block ends up being a # transaction block, then last_block_curr will be the second to last tx block in the epoch. If this block # is not a transaction block, that means there was exactly one other tx block included in between our signage # point and infusion point, and therefore last_block_curr is the second to last as well. last_block_curr = prev_b while last_block_curr.total_iters > signage_point_total_iters or not last_block_curr.is_transaction_block: last_block_curr = blocks.block_record(last_block_curr.prev_hash) assert last_block_curr.timestamp is not None and last_block_prev.timestamp is not None # This is computed as the iterations per second in last epoch, times the target number of seconds per slot new_ssi_precise: uint64 = uint64( constants.SUB_SLOT_TIME_TARGET * (last_block_curr.total_iters - last_block_prev.total_iters) // (last_block_curr.timestamp - last_block_prev.timestamp)) # Only change by a max factor as a sanity check max_ssi = uint64(constants.DIFFICULTY_CHANGE_MAX_FACTOR * last_block_curr.sub_slot_iters) min_ssi = uint64(last_block_curr.sub_slot_iters // constants.DIFFICULTY_CHANGE_MAX_FACTOR) if new_ssi_precise >= last_block_curr.sub_slot_iters: new_ssi_precise = uint64(min(new_ssi_precise, max_ssi)) else: new_ssi_precise = uint64( max([constants.NUM_SPS_SUB_SLOT, new_ssi_precise, min_ssi])) new_ssi = truncate_to_significant_bits(new_ssi_precise, constants.SIGNIFICANT_BITS) new_ssi = uint64( new_ssi - new_ssi % constants.NUM_SPS_SUB_SLOT) # Must divide the sub slot assert count_significant_bits(new_ssi) <= constants.SIGNIFICANT_BITS return new_ssi
def get_next_min_iters( constants: Dict, headers: Dict[bytes32, Header], height_to_hash: Dict[uint32, bytes32], block: Union[FullBlock, HeaderBlock], ) -> uint64: """ Returns the VDF speed in iterations per seconds, to be used for the next block. This depends on the number of iterations of the last epoch, and changes at the same block as the difficulty. """ next_height: uint32 = uint32(block.height + 1) if next_height < constants["DIFFICULTY_EPOCH"]: # First epoch has a hardcoded vdf speed return constants["MIN_ITERS_STARTING"] prev_block_header: Header = headers[block.prev_header_hash] proof_of_space = block.proof_of_space difficulty = get_next_difficulty( constants, headers, height_to_hash, prev_block_header ) iterations = uint64( block.header.data.total_iters - prev_block_header.data.total_iters ) prev_min_iters = calculate_min_iters_from_iterations( proof_of_space, difficulty, iterations ) if next_height % constants["DIFFICULTY_EPOCH"] != constants["DIFFICULTY_DELAY"]: # Not at a point where ips would change, so return the previous ips # TODO: cache this for efficiency return prev_min_iters # min iters (along with difficulty) will change in this block, so we need to calculate the new one. # The calculation is (iters_2 - iters_1) // epoch size # 1 and 2 correspond to height_1 and height_2, being the last block of the second to last, and last # block of the last epochs. Basically, it's total iterations per block on average. # Height1 is the last block 2 epochs ago, so we can include the iterations taken for mining first block in epoch height1 = uint32( next_height - constants["DIFFICULTY_EPOCH"] - constants["DIFFICULTY_DELAY"] - 1 ) # Height2 is the last block in the previous epoch height2 = uint32(next_height - constants["DIFFICULTY_DELAY"] - 1) block1: Optional[Header] = None block2: Optional[Header] = None # We need to backtrack until we merge with the LCA chain, so we can use the height_to_hash dict. # This is important if we are on a fork, or beyond the LCA. curr: Optional[Header] = block.header assert curr is not None while ( curr.height not in height_to_hash or height_to_hash[curr.height] != curr.header_hash ): if curr.height == height1: block1 = curr elif curr.height == height2: block2 = curr curr = headers.get(curr.prev_header_hash, None) assert curr is not None # Once we are before the fork point (and before the LCA), we can use the height_to_hash map if block1 is None and height1 >= 0: # height1 could be -1, for the first difficulty calculation block1 = headers.get(height_to_hash[height1], None) if block2 is None: block2 = headers.get(height_to_hash[height2], None) assert block2 is not None if block1 is not None: iters1 = block1.data.total_iters else: # In the case of height == -1, iters = 0 iters1 = uint64(0) iters2 = block2.data.total_iters min_iters_precise = uint64( (iters2 - iters1) // (constants["DIFFICULTY_EPOCH"] * constants["MIN_ITERS_PROPORTION"]) ) min_iters = uint64( truncate_to_significant_bits(min_iters_precise, constants["SIGNIFICANT_BITS"]) ) assert count_significant_bits(min_iters) <= constants["SIGNIFICANT_BITS"] return min_iters
def get_next_difficulty( constants: Dict, headers: Dict[bytes32, Header], height_to_hash: Dict[uint32, bytes32], block: Header, ) -> uint64: """ Returns the difficulty of the next block that extends onto block. Used to calculate the number of iterations. When changing this, also change the implementation in wallet_state_manager.py. """ next_height: uint32 = uint32(block.height + 1) if next_height < constants["DIFFICULTY_EPOCH"]: # We are in the first epoch return uint64(constants["DIFFICULTY_STARTING"]) # Epochs are diffined as intervals of DIFFICULTY_EPOCH blocks, inclusive and indexed at 0. # For example, [0-2047], [2048-4095], etc. The difficulty changes DIFFICULTY_DELAY into the # epoch, as opposed to the first block (as in Bitcoin). elif next_height % constants["DIFFICULTY_EPOCH"] != constants["DIFFICULTY_DELAY"]: # Not at a point where difficulty would change prev_block: Header = headers[block.prev_header_hash] return uint64(block.weight - prev_block.weight) # old diff curr diff new diff # ----------|-----|----------------------|-----|-----... # h1 h2 h3 i-1 # Height1 is the last block 2 epochs ago, so we can include the time to mine 1st block in previous epoch height1 = uint32( next_height - constants["DIFFICULTY_EPOCH"] - constants["DIFFICULTY_DELAY"] - 1 ) # Height2 is the DIFFICULTY DELAYth block in the previous epoch height2 = uint32(next_height - constants["DIFFICULTY_EPOCH"] - 1) # Height3 is the last block in the previous epoch height3 = uint32(next_height - constants["DIFFICULTY_DELAY"] - 1) # h1 to h2 timestamps are mined on previous difficulty, while and h2 to h3 timestamps are mined on the # current difficulty block1, block2, block3 = None, None, None # We need to backtrack until we merge with the LCA chain, so we can use the height_to_hash dict. # This is important if we are on a fork, or beyond the LCA. curr: Optional[Header] = block assert curr is not None while ( curr.height not in height_to_hash or height_to_hash[curr.height] != curr.header_hash ): if curr.height == height1: block1 = curr elif curr.height == height2: block2 = curr elif curr.height == height3: block3 = curr curr = headers.get(curr.prev_header_hash, None) assert curr is not None # Once we are before the fork point (and before the LCA), we can use the height_to_hash map if not block1 and height1 >= 0: # height1 could be -1, for the first difficulty calculation block1 = headers[height_to_hash[height1]] if not block2: block2 = headers[height_to_hash[height2]] if not block3: block3 = headers[height_to_hash[height3]] assert block2 is not None and block3 is not None # Current difficulty parameter (diff of block h = i - 1) Tc = get_next_difficulty( constants, headers, height_to_hash, headers[block.prev_header_hash] ) # Previous difficulty parameter (diff of block h = i - 2048 - 1) Tp = get_next_difficulty( constants, headers, height_to_hash, headers[block2.prev_header_hash] ) if block1: timestamp1 = block1.data.timestamp # i - 512 - 1 else: # In the case of height == -1, there is no timestamp here, so assume the genesis block # took constants["BLOCK_TIME_TARGET"] seconds to mine. genesis = headers[height_to_hash[uint32(0)]] timestamp1 = genesis.data.timestamp - constants["BLOCK_TIME_TARGET"] timestamp2 = block2.data.timestamp # i - 2048 + 512 - 1 timestamp3 = block3.data.timestamp # i - 512 - 1 # Numerator fits in 128 bits, so big int is not necessary # We multiply by the denominators here, so we only have one fraction in the end (avoiding floating point) term1 = ( constants["DIFFICULTY_DELAY"] * Tp * (timestamp3 - timestamp2) * constants["BLOCK_TIME_TARGET"] ) term2 = ( (constants["DIFFICULTY_WARP_FACTOR"] - 1) * (constants["DIFFICULTY_EPOCH"] - constants["DIFFICULTY_DELAY"]) * Tc * (timestamp2 - timestamp1) * constants["BLOCK_TIME_TARGET"] ) # Round down after the division new_difficulty_precise: uint64 = uint64( (term1 + term2) // ( constants["DIFFICULTY_WARP_FACTOR"] * (timestamp3 - timestamp2) * (timestamp2 - timestamp1) ) ) # Take only DIFFICULTY_SIGNIFICANT_BITS significant bits new_difficulty = uint64( truncate_to_significant_bits( new_difficulty_precise, constants["SIGNIFICANT_BITS"] ) ) assert count_significant_bits(new_difficulty) <= constants["SIGNIFICANT_BITS"] # Only change by a max factor, to prevent attacks, as in greenpaper, and must be at least 1 max_diff = uint64( truncate_to_significant_bits( constants["DIFFICULTY_FACTOR"] * Tc, constants["SIGNIFICANT_BITS"], ) ) min_diff = uint64( truncate_to_significant_bits( Tc // constants["DIFFICULTY_FACTOR"], constants["SIGNIFICANT_BITS"], ) ) if new_difficulty >= Tc: return min(new_difficulty, max_diff) else: return max([uint64(1), new_difficulty, min_diff])
def get_consecutive_blocks( self, input_constants: Dict, num_blocks: int, block_list: List[FullBlock] = [], seconds_per_block=None, seed: bytes = b"", reward_puzzlehash: bytes32 = None, transaction_data_at_height: Dict[int, Tuple[Program, BLSSignature]] = None, fees: uint64 = uint64(0), ) -> List[FullBlock]: if transaction_data_at_height is None: transaction_data_at_height = {} test_constants: Dict[str, Any] = constants.copy() for key, value in input_constants.items(): test_constants[key] = value if seconds_per_block is None: seconds_per_block = test_constants["BLOCK_TIME_TARGET"] if len(block_list) == 0: if "GENESIS_BLOCK" in test_constants: block_list.append(FullBlock.from_bytes(test_constants["GENESIS_BLOCK"])) else: block_list.append( self.create_genesis_block(test_constants, std_hash(seed), seed) ) prev_difficulty = test_constants["DIFFICULTY_STARTING"] curr_difficulty = prev_difficulty curr_min_iters = test_constants["MIN_ITERS_STARTING"] elif len(block_list) < ( test_constants["DIFFICULTY_EPOCH"] + test_constants["DIFFICULTY_DELAY"] ): # First epoch (+delay), so just get first difficulty prev_difficulty = block_list[0].weight curr_difficulty = block_list[0].weight assert test_constants["DIFFICULTY_STARTING"] == prev_difficulty curr_min_iters = test_constants["MIN_ITERS_STARTING"] else: curr_difficulty = block_list[-1].weight - block_list[-2].weight prev_difficulty = ( block_list[-1 - test_constants["DIFFICULTY_EPOCH"]].weight - block_list[-2 - test_constants["DIFFICULTY_EPOCH"]].weight ) assert block_list[-1].proof_of_time is not None curr_min_iters = calculate_min_iters_from_iterations( block_list[-1].proof_of_space, curr_difficulty, block_list[-1].proof_of_time.number_of_iterations, ) starting_height = block_list[-1].height + 1 timestamp = block_list[-1].header.data.timestamp for next_height in range(starting_height, starting_height + num_blocks): if ( next_height > test_constants["DIFFICULTY_EPOCH"] and next_height % test_constants["DIFFICULTY_EPOCH"] == test_constants["DIFFICULTY_DELAY"] ): # Calculates new difficulty height1 = uint64( next_height - ( test_constants["DIFFICULTY_EPOCH"] + test_constants["DIFFICULTY_DELAY"] ) - 1 ) height2 = uint64(next_height - (test_constants["DIFFICULTY_EPOCH"]) - 1) height3 = uint64(next_height - (test_constants["DIFFICULTY_DELAY"]) - 1) if height1 >= 0: block1 = block_list[height1] iters1 = block1.header.data.total_iters timestamp1 = block1.header.data.timestamp else: block1 = block_list[0] timestamp1 = ( block1.header.data.timestamp - test_constants["BLOCK_TIME_TARGET"] ) iters1 = uint64(0) timestamp2 = block_list[height2].header.data.timestamp timestamp3 = block_list[height3].header.data.timestamp block3 = block_list[height3] iters3 = block3.header.data.total_iters term1 = ( test_constants["DIFFICULTY_DELAY"] * prev_difficulty * (timestamp3 - timestamp2) * test_constants["BLOCK_TIME_TARGET"] ) term2 = ( (test_constants["DIFFICULTY_WARP_FACTOR"] - 1) * ( test_constants["DIFFICULTY_EPOCH"] - test_constants["DIFFICULTY_DELAY"] ) * curr_difficulty * (timestamp2 - timestamp1) * test_constants["BLOCK_TIME_TARGET"] ) # Round down after the division new_difficulty_precise: uint64 = uint64( (term1 + term2) // ( test_constants["DIFFICULTY_WARP_FACTOR"] * (timestamp3 - timestamp2) * (timestamp2 - timestamp1) ) ) new_difficulty = uint64( truncate_to_significant_bits( new_difficulty_precise, test_constants["SIGNIFICANT_BITS"] ) ) max_diff = uint64( truncate_to_significant_bits( test_constants["DIFFICULTY_FACTOR"] * curr_difficulty, test_constants["SIGNIFICANT_BITS"], ) ) min_diff = uint64( truncate_to_significant_bits( curr_difficulty // test_constants["DIFFICULTY_FACTOR"], test_constants["SIGNIFICANT_BITS"], ) ) if new_difficulty >= curr_difficulty: new_difficulty = min(new_difficulty, max_diff,) else: new_difficulty = max([uint64(1), new_difficulty, min_diff]) min_iters_precise = uint64( (iters3 - iters1) // ( test_constants["DIFFICULTY_EPOCH"] * test_constants["MIN_ITERS_PROPORTION"] ) ) curr_min_iters = uint64( truncate_to_significant_bits( min_iters_precise, test_constants["SIGNIFICANT_BITS"] ) ) prev_difficulty = curr_difficulty curr_difficulty = new_difficulty time_taken = seconds_per_block timestamp += time_taken transactions: Optional[Program] = None aggsig: Optional[BLSSignature] = None if next_height in transaction_data_at_height: transactions, aggsig = transaction_data_at_height[next_height] update_difficulty = ( next_height % test_constants["DIFFICULTY_EPOCH"] == test_constants["DIFFICULTY_DELAY"] ) block_list.append( self.create_next_block( test_constants, block_list[-1], timestamp, update_difficulty, curr_difficulty, curr_min_iters, seed, reward_puzzlehash, transactions, aggsig, fees, ) ) return block_list
def get_next_difficulty( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], height_to_hash: Dict[uint32, bytes32], prev_header_hash: bytes32, sub_block_height: uint32, current_difficulty: uint64, deficit: uint8, new_slot: bool, signage_point_total_iters: uint128, skip_epoch_check=False, ) -> uint64: """ Returns the difficulty of the next sub-block that extends onto sub-block. Used to calculate the number of iterations. When changing this, also change the implementation in wallet_state_manager.py. Args: constants: consensus constants being used for this chain sub_blocks: dictionary from header hash to SBR of all included SBR height_to_hash: sub-block height to header hash map for sub-blocks in peak path prev_header_hash: header hash of the previous sub-block sub_block_height: the sub-block height of the sub-block to look at current_difficulty: difficulty at the infusion point of the sub_block at sub_block_height deficit: deficit of the sub_block at sub_block_height new_slot: whether or not there is a new slot after sub_block_height signage_point_total_iters: signage point iters of the sub_block at sub_block_height skip_epoch_check: don't check correct epoch """ next_sub_block_height: uint32 = uint32(sub_block_height + 1) if next_sub_block_height < (constants.EPOCH_SUB_BLOCKS - constants.MAX_SUB_SLOT_SUB_BLOCKS): # We are in the first epoch return uint64(constants.DIFFICULTY_STARTING) if prev_header_hash not in sub_blocks: raise ValueError(f"Header hash {prev_header_hash} not in sub blocks") prev_sb: SubBlockRecord = sub_blocks[prev_header_hash] # If we are in the same slot as previous sub-block, return same difficulty if not skip_epoch_check: _, can_finish_epoch = can_finish_sub_and_full_epoch( constants, sub_block_height, deficit, sub_blocks, prev_header_hash, False) if not new_slot or not can_finish_epoch: return current_difficulty last_block_prev: SubBlockRecord = _get_last_block_in_previous_epoch( constants, height_to_hash, sub_blocks, prev_sb) # Ensure we get a block for the last block as well, and that it is before the signage point last_block_curr = prev_sb while last_block_curr.total_iters > signage_point_total_iters or not last_block_curr.is_block: last_block_curr = sub_blocks[last_block_curr.prev_hash] assert last_block_curr.timestamp is not None assert last_block_prev.timestamp is not None actual_epoch_time: uint64 = uint64(last_block_curr.timestamp - last_block_prev.timestamp) old_difficulty = uint64(prev_sb.weight - sub_blocks[prev_sb.prev_hash].weight) # Terms are rearranged so there is only one division. new_difficulty_precise = ( (last_block_curr.weight - last_block_prev.weight) * constants.SUB_SLOT_TIME_TARGET // (constants.SLOT_SUB_BLOCKS_TARGET * actual_epoch_time)) # Take only DIFFICULTY_SIGNIFICANT_BITS significant bits new_difficulty = uint64( truncate_to_significant_bits(new_difficulty_precise, constants.SIGNIFICANT_BITS)) assert count_significant_bits(new_difficulty) <= constants.SIGNIFICANT_BITS # Only change by a max factor, to prevent attacks, as in greenpaper, and must be at least 1 max_diff = uint64( truncate_to_significant_bits( constants.DIFFICULTY_FACTOR * old_difficulty, constants.SIGNIFICANT_BITS, )) min_diff = uint64( truncate_to_significant_bits( old_difficulty // constants.DIFFICULTY_FACTOR, constants.SIGNIFICANT_BITS, )) if new_difficulty >= old_difficulty: return min(new_difficulty, max_diff) else: return max([uint64(1), new_difficulty, min_diff])
def get_next_sub_slot_iters( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], height_to_hash: Dict[uint32, bytes32], prev_header_hash: bytes32, sub_block_height: uint32, curr_sub_slot_iters: uint64, deficit: uint8, new_slot: bool, signage_point_total_iters: uint128, skip_epoch_check=False, ) -> uint64: """ Returns the slot iterations required for the next block after the one at sub_block_height, where new_slot is true iff the next block will be in the next slot. Args: constants: consensus constants being used for this chain sub_blocks: dictionary from header hash to SBR of all included SBR height_to_hash: sub-block height to header hash map for sub-blocks in peak path prev_header_hash: header hash of the previous sub-block sub_block_height: the sub-block height of the sub-block to look at curr_sub_slot_iters: sub-slot iters at the infusion point of the sub_block at sub_block_height deficit: deficit of the sub_block at sub_block_height new_slot: whether or not there is a new slot after sub_block_height signage_point_total_iters: signage point iters of the sub_block at sub_block_height skip_epoch_check: don't check correct epoch """ next_sub_block_height: uint32 = uint32(sub_block_height + 1) if next_sub_block_height < (constants.EPOCH_SUB_BLOCKS - constants.MAX_SUB_SLOT_SUB_BLOCKS): return uint64(constants.SUB_SLOT_ITERS_STARTING) if prev_header_hash not in sub_blocks: raise ValueError(f"Header hash {prev_header_hash} not in sub blocks") prev_sb: SubBlockRecord = sub_blocks[prev_header_hash] # If we are in the same epoch, return same ssi if not skip_epoch_check: _, can_finish_epoch = can_finish_sub_and_full_epoch( constants, sub_block_height, deficit, sub_blocks, prev_header_hash, False) if not new_slot or not can_finish_epoch: return curr_sub_slot_iters last_block_prev: SubBlockRecord = _get_last_block_in_previous_epoch( constants, height_to_hash, sub_blocks, prev_sb) # Ensure we get a block for the last block as well, and that it is before the signage point last_block_curr = prev_sb while last_block_curr.total_iters > signage_point_total_iters or not last_block_curr.is_block: last_block_curr = sub_blocks[last_block_curr.prev_hash] assert last_block_curr.timestamp is not None and last_block_prev.timestamp is not None # This is computed as the iterations per second in last epoch, times the target number of seconds per slot new_ssi_precise: uint64 = uint64( constants.SUB_SLOT_TIME_TARGET * (last_block_curr.total_iters - last_block_prev.total_iters) // (last_block_curr.timestamp - last_block_prev.timestamp)) new_ssi = uint64( truncate_to_significant_bits(new_ssi_precise, constants.SIGNIFICANT_BITS)) # Only change by a max factor as a sanity check max_ssi = uint64( truncate_to_significant_bits( constants.DIFFICULTY_FACTOR * last_block_curr.sub_slot_iters, constants.SIGNIFICANT_BITS, )) min_ssi = uint64( truncate_to_significant_bits( last_block_curr.sub_slot_iters // constants.DIFFICULTY_FACTOR, constants.SIGNIFICANT_BITS, )) if new_ssi >= last_block_curr.sub_slot_iters: new_ssi = min(new_ssi, max_ssi) else: new_ssi = uint64(max([constants.NUM_SPS_SUB_SLOT, new_ssi, min_ssi])) new_ssi = uint64( new_ssi - new_ssi % constants.NUM_SPS_SUB_SLOT) # Must divide the sub slot assert count_significant_bits(new_ssi) <= constants.SIGNIFICANT_BITS return new_ssi
async def validate_header_block(self, br: BlockRecord, header_block: HeaderBlock) -> bool: """ Fully validates a header block. This requires the ancestors to be present in the blockchain. This method also validates that the header block is consistent with the block record. """ # POS challenge hash == POT challenge hash == Challenge prev challenge hash if (header_block.proof_of_space.challenge_hash != header_block.proof_of_time.challenge_hash): return False if (header_block.proof_of_space.challenge_hash != header_block.challenge.prev_challenge_hash): return False if br.height > 0: prev_br = self.block_records[br.prev_header_hash] # If prev header block, check prev header block hash matches if prev_br.new_challenge_hash is not None: if (header_block.proof_of_space.challenge_hash != prev_br.new_challenge_hash): return False # Validate PoS and get quality quality_str: Optional[ bytes32] = header_block.proof_of_space.verify_and_get_quality_string( ) if quality_str is None: return False difficulty: uint64 min_iters: uint64 = self.get_min_iters(br) prev_block: Optional[BlockRecord] if (br.height % self.constants["DIFFICULTY_EPOCH"] != self.constants["DIFFICULTY_DELAY"]): # Only allow difficulty changes once per epoch if br.height > 1: prev_block = self.block_records[br.prev_header_hash] assert prev_block is not None prev_prev_block = self.block_records[ prev_block.prev_header_hash] assert prev_prev_block is not None difficulty = uint64(br.weight - prev_block.weight) assert difficulty == prev_block.weight - prev_prev_block.weight elif br.height == 1: prev_block = self.block_records[br.prev_header_hash] assert prev_block is not None difficulty = uint64(br.weight - prev_block.weight) assert difficulty == prev_block.weight else: difficulty = uint64(br.weight) assert difficulty == self.constants["DIFFICULTY_STARTING"] else: # This is a difficulty change, so check whether it's within the allowed range. # (But don't check whether it's the right amount). prev_block = self.block_records[br.prev_header_hash] assert prev_block is not None prev_prev_block = self.block_records[prev_block.prev_header_hash] assert prev_prev_block is not None difficulty = uint64(br.weight - prev_block.weight) prev_difficulty = uint64(prev_block.weight - prev_prev_block.weight) # Ensures the challenge for this block is valid (contains correct diff reset) if prev_block.header_hash in self.difficulty_resets_prev: if self.difficulty_resets_prev[ prev_block.header_hash] != difficulty: return False max_diff = uint64( truncate_to_significant_bits( prev_difficulty * self.constants["DIFFICULTY_FACTOR"], self.constants["SIGNIFICANT_BITS"], )) min_diff = uint64( truncate_to_significant_bits( prev_difficulty // self.constants["DIFFICULTY_FACTOR"], self.constants["SIGNIFICANT_BITS"], )) if difficulty < min_diff or difficulty > max_diff: return False number_of_iters: uint64 = calculate_iterations_quality( quality_str, header_block.proof_of_space.size, difficulty, min_iters, ) if header_block.proof_of_time is None: return False if number_of_iters != header_block.proof_of_time.number_of_iterations: return False # Check PoT if not header_block.proof_of_time.is_valid( self.constants["DISCRIMINANT_SIZE_BITS"]): return False # Validate challenge proofs_hash = std_hash(header_block.proof_of_space.get_hash() + header_block.proof_of_time.output.get_hash()) if proofs_hash != header_block.challenge.proofs_hash: return False # Note that we are not validating the work difficulty reset (since we don't know the # next block yet. When we process the next block, we will check that it matches). # Validate header: if header_block.header.header_hash != br.header_hash: return False if header_block.header.prev_header_hash != br.prev_header_hash: return False if header_block.height != br.height: return False if header_block.weight != br.weight: return False if br.height > 0: assert prev_block is not None if prev_block.weight + difficulty != br.weight: return False if prev_block.total_iters is not None and br.total_iters is not None: if prev_block.total_iters + number_of_iters != br.total_iters: return False if prev_block.height + 1 != br.height: return False else: if br.weight != difficulty: return False if br.total_iters != number_of_iters: return False # Check that block is not far in the future if (header_block.header.data.timestamp > time.time() + self.constants["MAX_FUTURE_TIME"]): return False # Check header pos hash if (header_block.proof_of_space.get_hash() != header_block.header.data.proof_of_space_hash): return False # Check coinbase sig pair = header_block.header.data.coinbase_signature.PkMessagePair( header_block.proof_of_space.pool_pubkey, header_block.header.data.coinbase.name(), ) if not header_block.header.data.coinbase_signature.validate([pair]): return False # Check coinbase and fees amount coinbase_reward = calculate_block_reward(br.height) if coinbase_reward != header_block.header.data.coinbase.amount: return False return True