diff --git a/chia/_tests/conftest.py b/chia/_tests/conftest.py index 63f262b9fc6d..0afe804dc097 100644 --- a/chia/_tests/conftest.py +++ b/chia/_tests/conftest.py @@ -200,7 +200,8 @@ class ConsensusMode(ComparableEnum): @pytest.fixture( scope="session", - params=[ConsensusMode.PLAIN, ConsensusMode.HARD_FORK_2_0, ConsensusMode.HARD_FORK_3_0], + # TODO: todo_v2_plots add HARD_FORK_3_0 mode as well as after phase-out + params=[ConsensusMode.PLAIN, ConsensusMode.HARD_FORK_2_0], ) def consensus_mode(request): return request.param diff --git a/chia/_tests/core/consensus/test_pot_iterations.py b/chia/_tests/core/consensus/test_pot_iterations.py index 7c96604b5947..8b17ad131ccf 100644 --- a/chia/_tests/core/consensus/test_pot_iterations.py +++ b/chia/_tests/core/consensus/test_pot_iterations.py @@ -1,6 +1,5 @@ from __future__ import annotations -import pytest from chia_rs import PlotSize from chia_rs.sized_ints import uint8, uint16, uint32, uint64, uint128 from pytest import raises @@ -10,7 +9,6 @@ from chia.consensus.pot_iterations import ( calculate_ip_iters, calculate_iterations_quality, - calculate_phase_out, calculate_sp_interval_iters, calculate_sp_iters, is_overflow_block, @@ -84,17 +82,7 @@ def test_calculate_ip_iters(self): assert ip_iters == (sp_iters + test_constants.NUM_SP_INTERVALS_EXTRA * sp_interval_iters + required_iters) % ssi assert sp_iters > ip_iters - @pytest.mark.parametrize( - "height", - [ - uint32(0), - test_constants.HARD_FORK2_HEIGHT - 1, - test_constants.HARD_FORK2_HEIGHT, - test_constants.HARD_FORK2_HEIGHT + test_constants.PLOT_V1_PHASE_OUT, - test_constants.HARD_FORK2_HEIGHT + test_constants.PLOT_V1_PHASE_OUT + 1, - ], - ) - def test_win_percentage(self, height: uint32): + def test_win_percentage(self): """ Tests that the percentage of blocks won is proportional to the space of each farmer, with the assumption that all farmers have access to the same VDF speed. @@ -129,59 +117,19 @@ def test_win_percentage(self, height: uint32): quality = std_hash( slot_index.to_bytes(4, "big") + plot_k_val.to_bytes(1, "big") + bytes(farmer_index) ) - required_iters = calculate_iterations_quality( - constants, quality, k, difficulty, sp_hash, sub_slot_iters, height - ) + required_iters = calculate_iterations_quality(constants, quality, k, difficulty, sp_hash) if required_iters < sp_interval_iters: wins[k] += 1 total_wins_in_slot += 1 - if height < test_constants.HARD_FORK2_HEIGHT + test_constants.PLOT_V1_PHASE_OUT: - total_space = sum(farmer_space.values()) - percentage_space = {k: float(sp / total_space) for k, sp in farmer_space.items()} - else: - # after the phase-out, v1 plots don't count - # all wins are by v2 plots - total_space = sum(0 if k.size_v2 is None else sp for k, sp in farmer_space.items()) - percentage_space = { - k: 0.0 if k.size_v2 is None else float(sp / total_space) for k, sp in farmer_space.items() - } + total_space = sum(farmer_space.values()) + percentage_space = {k: float(sp / total_space) for k, sp in farmer_space.items()} win_percentage = {k: wins[k] / sum(wins.values()) for k in farmer_ks.keys()} for k in farmer_ks.keys(): # Win rate is proportional to percentage of space assert abs(win_percentage[k] - percentage_space[k]) < 0.01 - @pytest.mark.parametrize("sp_interval", [uint64(6250000000), uint64(1), uint64(2), uint64(10), uint64(10000000000)]) - def test_calculate_phase_out(self, sp_interval: uint64): - constants = test_constants - sub_slot_iters = uint64(sp_interval * constants.NUM_SPS_SUB_SLOT) - # Before or at HARD_FORK2_HEIGHT, should return 0 - assert calculate_phase_out(constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT - 1)) == 0 - assert calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT) == 0 - # after HARD_FORK2_HEIGHT, should return value = delta/phase_out_period * sp_interval - assert ( - calculate_phase_out(constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT + 1)) - == sp_interval // constants.PLOT_V1_PHASE_OUT - ) - assert ( - calculate_phase_out( - constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT // 2) - ) - == sp_interval // 2 - ) - assert ( - calculate_phase_out( - constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT) - ) - == sp_interval - ) - - # Test with maximum uint32 height to ensure no overflow - max_uint32_height = uint32(0xFFFFFFFF) - result_max_height = calculate_phase_out(constants, sub_slot_iters, max_uint32_height) - assert result_max_height == sp_interval # Should cap at sp_interval - def test_expected_plot_size_v1() -> None: last_size = 2_400_000 diff --git a/chia/_tests/core/custom_types/test_proof_of_space.py b/chia/_tests/core/custom_types/test_proof_of_space.py index 67e8ffad2879..3812da982e36 100644 --- a/chia/_tests/core/custom_types/test_proof_of_space.py +++ b/chia/_tests/core/custom_types/test_proof_of_space.py @@ -14,6 +14,7 @@ from chia.types.blockchain_format.proof_of_space import ( calculate_prefix_bits, check_plot_size, + is_v1_phased_out, make_pos, passes_plot_filter, verify_and_get_quality_string, @@ -150,6 +151,7 @@ def test_verify_and_get_quality_string(caplog: pytest.LogCaptureFixture, case: P original_challenge_hash=b32("0x73490e166d0b88347c37d921660b216c27316aae9a3450933d3ff3b854e5831a"), signage_point=b32("0x7b3e23dbd438f9aceefa9827e2c5538898189987f49b06eceb7a43067e77b531"), height=case.height, + prev_transaction_block_height=case.height, ) assert quality_string is None assert len(caplog.text) == 0 if case.expected_error is None else case.expected_error in caplog.text @@ -187,6 +189,7 @@ def test_verify_and_get_quality_string_v2(caplog: pytest.LogCaptureFixture, case original_challenge_hash=b32("0x73490e166d0b88347c37d921660b216c27316aae9a3450933d3ff3b854e5831a"), signage_point=b32("0x7b3e23dbd438f9aceefa9827e2c5538898189987f49b06eceb7a43067e77b531"), height=case.height, + prev_transaction_block_height=case.height, ) except NotImplementedError as e: assert case.expected_error is not None @@ -281,3 +284,30 @@ def test_calculate_prefix_bits_v1(height: uint32, expected: int) -> None: ) def test_calculate_prefix_bits_v2(height: uint32, expected: int) -> None: assert calculate_prefix_bits(DEFAULT_CONSTANTS, height, PlotSize.make_v2(28)) == expected + + +def test_v1_phase_out() -> None: + constants = DEFAULT_CONSTANTS.replace(HARD_FORK2_HEIGHT=uint32(500000)) + rng = random.Random() + + phase_out_epochs = 1 << (constants.PLOT_V1_PHASE_OUT // constants.EPOCH_BLOCKS).bit_length() + print(f"phase-out epochs: {phase_out_epochs}") + + for epoch in range(-5, phase_out_epochs + 5): + prev_tx_height = uint32(constants.HARD_FORK2_HEIGHT + epoch * constants.EPOCH_BLOCKS) + num_phased_out = 0 + rng.seed(1337) + for i in range(1000): + proof = rng.randbytes(32) + if is_v1_phased_out(proof, prev_tx_height, constants): + num_phased_out += 1 + + expect = min(1.0, max(0.0, epoch / phase_out_epochs)) + + print( + f"height: {prev_tx_height} " + f"epoch: {epoch} " + f"phased-out: {num_phased_out / 10:0.2f}% " + f"expect: {expect * 100.0:0.2f}%" + ) + assert abs((num_phased_out / 1000) - expect) < 0.05 diff --git a/chia/_tests/core/full_node/test_full_node.py b/chia/_tests/core/full_node/test_full_node.py index b7a007b83c0f..9e7f218f4bf9 100644 --- a/chia/_tests/core/full_node/test_full_node.py +++ b/chia/_tests/core/full_node/test_full_node.py @@ -3224,12 +3224,15 @@ async def declare_pos_unfinished_block( include_signature_source_data=True, ) await full_node_api.declare_proof_of_space(pospace, dummy_peer) + tx_peak = blockchain.get_tx_peak() + assert tx_peak is not None q_str: Optional[bytes32] = verify_and_get_quality_string( block.reward_chain_block.proof_of_space, blockchain.constants, challenge, challenge_chain_sp, height=block.reward_chain_block.height, + prev_transaction_block_height=tx_peak.height, ) assert q_str is not None unfinised_block = None diff --git a/chia/_tests/core/server/test_rate_limits.py b/chia/_tests/core/server/test_rate_limits.py index c915b2ca05dd..b90d2afdd36d 100644 --- a/chia/_tests/core/server/test_rate_limits.py +++ b/chia/_tests/core/server/test_rate_limits.py @@ -24,7 +24,6 @@ rl_v2 = [Capability.BASE, Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2] rl_v1 = [Capability.BASE] node_with_params_b = node_with_params -test_different_versions_results: list[int] = [] @dataclass @@ -418,11 +417,14 @@ async def test_different_versions( # The following code checks whether all of the runs resulted in the same number of items in "rate_limits_tx", # which would mean the same rate limits are always used. This should not happen, since two nodes with V2 # will use V2. - total_tx_msg_count = len(get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities)) + rate_limits = get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities)[0] + limit = rate_limits[ProtocolMessageTypes.request_header_blocks] + assert isinstance(limit, RLSettings) - test_different_versions_results.append(total_tx_msg_count) - if len(test_different_versions_results) >= 4: - assert len(set(test_different_versions_results)) >= 2 + if Capability.RATE_LIMITS_V2 in a_con.local_capabilities and Capability.RATE_LIMITS_V2 in a_con.peer_capabilities: + assert limit.frequency == 5000 + else: + assert limit.frequency == 500 @pytest.mark.anyio diff --git a/chia/_tests/farmer_harvester/test_farmer.py b/chia/_tests/farmer_harvester/test_farmer.py index 252da1d14a48..fd639e8a772f 100644 --- a/chia/_tests/farmer_harvester/test_farmer.py +++ b/chia/_tests/farmer_harvester/test_farmer.py @@ -626,7 +626,14 @@ async def test_farmer_new_proof_of_space_for_pool_stats( } assert ( - verify_and_get_quality_string(pos, DEFAULT_CONSTANTS, case.challenge_hash, case.sp_hash, height=uint32(1)) + verify_and_get_quality_string( + pos, + DEFAULT_CONSTANTS, + case.challenge_hash, + case.sp_hash, + height=uint32(1), + prev_transaction_block_height=uint32(1), + ) is not None ) @@ -883,7 +890,12 @@ async def test_farmer_pool_response( assert ( verify_and_get_quality_string( - pos, DEFAULT_CONSTANTS, sp.challenge_hash, sp.challenge_chain_sp, height=uint32(1) + pos, + DEFAULT_CONSTANTS, + sp.challenge_hash, + sp.challenge_chain_sp, + height=uint32(1), + prev_transaction_block_height=uint32(1), ) is not None ) @@ -1251,7 +1263,12 @@ async def test_farmer_additional_headers_on_partial_submit( assert ( verify_and_get_quality_string( - pos, DEFAULT_CONSTANTS, sp.challenge_hash, sp.challenge_chain_sp, height=uint32(1) + pos, + DEFAULT_CONSTANTS, + sp.challenge_hash, + sp.challenge_chain_sp, + height=uint32(1), + prev_transaction_block_height=uint32(1), ) is not None ) diff --git a/chia/_tests/weight_proof/test_weight_proof.py b/chia/_tests/weight_proof/test_weight_proof.py index 45ef2406aa21..f33e0e6bf533 100644 --- a/chia/_tests/weight_proof/test_weight_proof.py +++ b/chia/_tests/weight_proof/test_weight_proof.py @@ -47,7 +47,6 @@ async def load_blocks_dont_validate( cc_sp, block.height, difficulty, - sub_slot_iters, uint32(0), # prev_tx_block(blocks, prev_b), todo need to get height of prev tx block somehow here ) assert required_iters is not None diff --git a/chia/consensus/block_header_validation.py b/chia/consensus/block_header_validation.py index 3ec214a04736..697836b696ed 100644 --- a/chia/consensus/block_header_validation.py +++ b/chia/consensus/block_header_validation.py @@ -502,7 +502,6 @@ def validate_unfinished_header_block( cc_sp_hash, height, expected_vs.difficulty, - expected_vs.ssi, prev_tx_block(blocks, prev_b), ) if required_iters is None: diff --git a/chia/consensus/default_constants.py b/chia/consensus/default_constants.py index 28f40deef24b..b730de006f71 100644 --- a/chia/consensus/default_constants.py +++ b/chia/consensus/default_constants.py @@ -84,7 +84,7 @@ HARD_FORK2_HEIGHT=uint32(0xFFFFFFFA), # starting at the hard fork 2 height, v1 plots will gradually be phased out, # and stop working entirely after this many blocks - PLOT_V1_PHASE_OUT=uint32(1179648), + PLOT_V1_PHASE_OUT=uint32(255 * 4608), # June 2027 PLOT_FILTER_128_HEIGHT=uint32(10542000), # June 2030 diff --git a/chia/consensus/multiprocess_validation.py b/chia/consensus/multiprocess_validation.py index 8a3e68ceb622..75f07cdeee34 100644 --- a/chia/consensus/multiprocess_validation.py +++ b/chia/consensus/multiprocess_validation.py @@ -220,7 +220,6 @@ async def return_error(error_code: Err) -> PreValidationResult: cc_sp_hash, block.height, vs.difficulty, - vs.ssi, prev_tx_block(blockchain, prev_b), ) if required_iters is None: diff --git a/chia/consensus/pot_iterations.py b/chia/consensus/pot_iterations.py index f61a5feca883..0ddeaf199575 100644 --- a/chia/consensus/pot_iterations.py +++ b/chia/consensus/pot_iterations.py @@ -28,25 +28,6 @@ def calculate_sp_iters(constants: ConsensusConstants, sub_slot_iters: uint64, si return uint64(calculate_sp_interval_iters(constants, sub_slot_iters) * signage_point_index) -def calculate_phase_out( - constants: ConsensusConstants, - sub_slot_iters: uint64, - prev_transaction_block_height: uint32, -) -> uint64: - if prev_transaction_block_height <= constants.HARD_FORK2_HEIGHT: - return uint64(0) - elif uint32(prev_transaction_block_height - constants.HARD_FORK2_HEIGHT) >= constants.PLOT_V1_PHASE_OUT: - return uint64(calculate_sp_interval_iters(constants, sub_slot_iters)) - - return uint64( - ( - uint32(prev_transaction_block_height - constants.HARD_FORK2_HEIGHT) - * calculate_sp_interval_iters(constants, sub_slot_iters) - ) - // constants.PLOT_V1_PHASE_OUT - ) - - def calculate_ip_iters( constants: ConsensusConstants, sub_slot_iters: uint64, @@ -75,11 +56,15 @@ def validate_pospace_and_get_required_iters( cc_sp_hash: bytes32, height: uint32, difficulty: uint64, - sub_slot_iters: uint64, prev_transaction_block_height: uint32, # this is the height of the last tx block before the current block SP ) -> Optional[uint64]: q_str: Optional[bytes32] = verify_and_get_quality_string( - proof_of_space, constants, challenge, cc_sp_hash, height=height + proof_of_space, + constants, + challenge, + cc_sp_hash, + height=height, + prev_transaction_block_height=prev_transaction_block_height, ) if q_str is None: return None @@ -90,8 +75,6 @@ def validate_pospace_and_get_required_iters( proof_of_space.size(), difficulty, cc_sp_hash, - sub_slot_iters, - prev_transaction_block_height, ) @@ -101,27 +84,17 @@ def calculate_iterations_quality( size: PlotSize, difficulty: uint64, cc_sp_output_hash: bytes32, - ssi: uint64, - prev_transaction_block_height: uint32, # this is the height of the last tx block before the current block SP ) -> uint64: """ Calculates the number of iterations from the quality. This is derives as the difficulty times the constant factor times a random number between 0 and 1 (based on quality string), divided by plot size. """ - if size.size_v1 is not None: - assert size.size_v2 is None - phase_out = calculate_phase_out(constants, ssi, prev_transaction_block_height) - else: - phase_out = uint64(0) sp_quality_string: bytes32 = std_hash(quality_string + cc_sp_output_hash) iters = uint64( - ( - int(difficulty) - * int(constants.DIFFICULTY_CONSTANT_FACTOR) - * int.from_bytes(sp_quality_string, "big", signed=False) - // (int(pow(2, 256)) * int(_expected_plot_size(size))) - ) - + phase_out + int(difficulty) + * int(constants.DIFFICULTY_CONSTANT_FACTOR) + * int.from_bytes(sp_quality_string, "big", signed=False) + // (int(pow(2, 256)) * int(_expected_plot_size(size))) ) return max(iters, uint64(1)) diff --git a/chia/farmer/farmer_api.py b/chia/farmer/farmer_api.py index db026304d47e..69f1a181b06a 100644 --- a/chia/farmer/farmer_api.py +++ b/chia/farmer/farmer_api.py @@ -111,6 +111,7 @@ async def new_proof_of_space( new_proof_of_space.challenge_hash, new_proof_of_space.sp_hash, height=sp.peak_height, + prev_transaction_block_height=sp.last_tx_height, ) if computed_quality_string is None: plotid: bytes32 = get_plot_id(new_proof_of_space.proof) @@ -125,8 +126,6 @@ async def new_proof_of_space( new_proof_of_space.proof.size(), sp.difficulty, new_proof_of_space.sp_hash, - sp.sub_slot_iters, - sp.last_tx_height, ) # If the iters are good enough to make a block, proceed with the block making flow @@ -234,8 +233,6 @@ async def new_proof_of_space( new_proof_of_space.proof.size(), pool_state_dict["current_difficulty"], new_proof_of_space.sp_hash, - sp.sub_slot_iters, - sp.last_tx_height, ) if required_iters >= calculate_sp_interval_iters( self.farmer.constants, self.farmer.constants.POOL_SUB_SLOT_ITERS @@ -813,6 +810,7 @@ def _process_respond_signatures( is_sp_signatures: bool = False sps = self.farmer.sps[response.sp_hash] peak_height = sps[0].peak_height + last_tx_height = sps[0].last_tx_height signage_point_index = sps[0].signage_point_index found_sp_hash_debug = False for sp_candidate in sps: @@ -831,7 +829,12 @@ def _process_respond_signatures( include_taproot: bool = pospace.pool_contract_puzzle_hash is not None computed_quality_string = verify_and_get_quality_string( - pospace, self.farmer.constants, response.challenge_hash, response.sp_hash, height=peak_height + pospace, + self.farmer.constants, + response.challenge_hash, + response.sp_hash, + height=peak_height, + prev_transaction_block_height=last_tx_height, ) if computed_quality_string is None: self.farmer.log.warning(f"Have invalid PoSpace {pospace}") diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index ae760f4ea333..2caa2e96f85e 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -877,21 +877,28 @@ async def declare_proof_of_space( new_block_gen: Optional[NewBlockGenerator] async with self.full_node.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.high): peak: Optional[BlockRecord] = self.full_node.blockchain.get_peak() + tx_peak: Optional[BlockRecord] = self.full_node.blockchain.get_tx_peak() # Checks that the proof of space is valid height: uint32 - if peak is None: + tx_height: uint32 + if peak is None or tx_peak is None: height = uint32(0) + tx_height = uint32(0) else: height = peak.height + tx_height = tx_peak.height quality_string: Optional[bytes32] = verify_and_get_quality_string( request.proof_of_space, self.full_node.constants, cc_challenge_hash, request.challenge_chain_sp, height=height, + prev_transaction_block_height=tx_height, ) - assert quality_string is not None and len(quality_string) == 32 + if quality_string is None: + self.log.warning("Received invalid proof of space in DeclareProofOfSpace from farmer") + return None if peak is not None: # Finds the last transaction block before this one @@ -1024,8 +1031,6 @@ def get_pool_sig(_1: PoolTarget, _2: Optional[G1Element]) -> Optional[G2Element] request.proof_of_space.size(), difficulty, request.challenge_chain_sp, - sub_slot_iters, - tx_peak.height if tx_peak is not None else uint32(0), ) sp_iters: uint64 = calculate_sp_iters(self.full_node.constants, sub_slot_iters, request.signage_point_index) ip_iters: uint64 = calculate_ip_iters( diff --git a/chia/full_node/weight_proof.py b/chia/full_node/weight_proof.py index 24b45b0694db..1e6eb298490c 100644 --- a/chia/full_node/weight_proof.py +++ b/chia/full_node/weight_proof.py @@ -1000,7 +1000,7 @@ def _validate_segment( if sampled and sub_slot_data.is_challenge(): after_challenge = True required_iters = __validate_pospace( - constants, segment, idx, curr_difficulty, curr_ssi, ses, first_segment_in_se, height + constants, segment, idx, curr_difficulty, ses, first_segment_in_se, height ) if required_iters is None: return False, uint64(0), uint64(0), uint64(0), [] @@ -1267,7 +1267,7 @@ def validate_recent_blocks( required_iters = caluclated_required_iters else: ret = _validate_pospace_recent_chain( - constants, sub_blocks, block, challenge, diff, ssi, overflow, prev_challenge + constants, sub_blocks, block, challenge, diff, overflow, prev_challenge ) if ret is None: return False, [] @@ -1310,7 +1310,6 @@ def _validate_pospace_recent_chain( block: HeaderBlock, challenge: bytes32, diff: uint64, - ssi: uint64, overflow: bool, prev_challenge: bytes32, ) -> Optional[uint64]: @@ -1328,7 +1327,6 @@ def _validate_pospace_recent_chain( cc_sp_hash, block.height, diff, - ssi, prev_tx_block(blocks, blocks.block_record(block.prev_header_hash)), ) if required_iters is None: @@ -1343,7 +1341,6 @@ def __validate_pospace( segment: SubEpochChallengeSegment, idx: int, curr_diff: uint64, - curr_sub_slot_iters: uint64, ses: Optional[SubEpochSummary], first_in_sub_epoch: bool, height: uint32, @@ -1370,6 +1367,9 @@ def __validate_pospace( # validate proof of space assert sub_slot_data.proof_of_space is not None + # when sampling blocks as part of weight proof validation, the previous + # transaction height is a conservative estimate, since we don't have direct + # access to it. required_iters = validate_pospace_and_get_required_iters( constants, sub_slot_data.proof_of_space, @@ -1377,8 +1377,7 @@ def __validate_pospace( cc_sp_hash, height, curr_diff, - curr_sub_slot_iters, - uint32(0), # prev_tx_block(blocks, prev_b), todo need to get height of prev tx block somehow here + uint32(max(0, height - constants.MAX_SUB_SLOT_BLOCKS)), ) if required_iters is None: log.error("could not verify proof of space") diff --git a/chia/harvester/harvester_api.py b/chia/harvester/harvester_api.py index 0d983cf7c7aa..6a3ddabe9462 100644 --- a/chia/harvester/harvester_api.py +++ b/chia/harvester/harvester_api.py @@ -29,6 +29,7 @@ calculate_pos_challenge, calculate_prefix_bits, generate_plot_public_key, + is_v1_phased_out, make_pos, passes_plot_filter, ) @@ -192,8 +193,6 @@ def blocking_lookup_v2_partial_proofs(filename: Path, plot_info: PlotInfo) -> Op plot_info.prover.get_size(), difficulty, new_challenge.sp_hash, - sub_slot_iters, - new_challenge.last_tx_height, ) if required_iters >= sp_interval_iters: @@ -287,8 +286,6 @@ def blocking_lookup(filename: Path, plot_info: PlotInfo) -> list[tuple[bytes32, plot_info.prover.get_size(), difficulty, new_challenge.sp_hash, - sub_slot_iters, - new_challenge.last_tx_height, ) sp_interval_iters = calculate_sp_interval_iters(self.harvester.constants, sub_slot_iters) if required_iters < sp_interval_iters: @@ -299,6 +296,17 @@ def blocking_lookup(filename: Path, plot_info: PlotInfo) -> list[tuple[bytes32, proof_xs = plot_info.prover.get_full_proof( sp_challenge_hash, index, self.harvester.parallel_read ) + + if is_v1_phased_out(proof_xs, new_challenge.last_tx_height, constants): + self.harvester.log.info( + f"Proof dropped due to hard fork phase-out of v1 plots: {filename}" + ) + self.harvester.log.info( + f"File: {filename} Plot ID: {plot_id.hex()}, challenge: {sp_challenge_hash}, " + f"plot_info: {plot_info}" + ) + continue + except RuntimeError as e: if str(e) == "GRResult_NoProof received": self.harvester.log.info( diff --git a/chia/simulator/block_tools.py b/chia/simulator/block_tools.py index 828ed0061b3f..51fd40a1a0e2 100644 --- a/chia/simulator/block_tools.py +++ b/chia/simulator/block_tools.py @@ -96,6 +96,7 @@ calculate_prefix_bits, generate_plot_public_key, generate_taproot_sk, + is_v1_phased_out, make_pos, passes_plot_filter, ) @@ -153,6 +154,7 @@ # Allows creating blockchains with timestamps up to 10 days in the future, for testing MAX_FUTURE_TIME2=uint32(3600 * 24 * 10), MEMPOOL_BLOCK_BUFFER=uint8(6), + PLOT_V1_PHASE_OUT=uint32(5 * 340), ) @@ -1550,6 +1552,10 @@ def get_pospaces_for_challenge( if not passes_plot_filter(prefix_bits, plot_id, challenge_hash, signage_point): continue + # TODO: todo_v2_plots change the existing PLOT_V1_PHASE_OUT constant to + # direclty specify the power-of-two epochs to phase-out over + phase_out_epochs = 1 << (constants.PLOT_V1_PHASE_OUT // constants.EPOCH_BLOCKS).bit_length() + if plot_info.prover.get_version() == PlotVersion.V2: # v2 plots aren't valid until after the hard fork if prev_tx_height < constants.HARD_FORK2_HEIGHT: @@ -1561,7 +1567,7 @@ def get_pospaces_for_challenge( f"cannot be used for farming: {plot_info.prover.get_filename()}" ) continue - elif prev_tx_height >= constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT: + elif prev_tx_height >= constants.HARD_FORK2_HEIGHT + phase_out_epochs * constants.EPOCH_BLOCKS: continue new_challenge: bytes32 = calculate_pos_challenge(plot_id, challenge_hash, signage_point) @@ -1577,8 +1583,6 @@ def get_pospaces_for_challenge( plot_info.prover.get_size(), difficulty, signage_point, - sub_slot_iters, - prev_tx_height, ) if required_iters >= calculate_sp_interval_iters(constants, sub_slot_iters): continue @@ -1586,6 +1590,8 @@ def get_pospaces_for_challenge( proof = b"" if isinstance(plot_info.prover, V1Prover): proof = plot_info.prover.get_full_proof(new_challenge, idx) + if is_v1_phased_out(proof, prev_tx_height, constants): + continue elif isinstance(plot_info.prover, V2Prover): assert isinstance(quality, V2Quality) partial_proof = plot_info.prover.get_partial_proof(quality) @@ -1852,7 +1858,6 @@ def load_block_list( sp_hash, full_block.height, uint64(difficulty), - sub_slot_iters, prev_transaction_b_height, ) assert required_iters is not None diff --git a/chia/timelord/iters_from_block.py b/chia/timelord/iters_from_block.py index edbd2e71a89b..e7e27f1e51bf 100644 --- a/chia/timelord/iters_from_block.py +++ b/chia/timelord/iters_from_block.py @@ -34,7 +34,6 @@ def iters_from_block( cc_sp, height, difficulty, - sub_slot_iters, prev_transaction_block_height, ) assert required_iters is not None diff --git a/chia/types/blockchain_format/proof_of_space.py b/chia/types/blockchain_format/proof_of_space.py index e734021e933e..a34bb6f43e87 100644 --- a/chia/types/blockchain_format/proof_of_space.py +++ b/chia/types/blockchain_format/proof_of_space.py @@ -75,6 +75,40 @@ def check_plot_size(constants: ConsensusConstants, ps: PlotSize) -> bool: return True +def is_v1_phased_out( + proof: bytes, + prev_transaction_block_height: uint32, # this is the height of the last tx block before the current block SP + constants: ConsensusConstants, +) -> bool: + if prev_transaction_block_height < constants.HARD_FORK2_HEIGHT: + return False + + # This is a v1 plot and the phase-out period has started + # The probability of having been phased out is proportional on the + # number of epochs since hard fork activation + + # TODO: todo_v2_plots change the existing PLOT_V1_PHASE_OUT constant to + # direclty specify the power-of-two epochs to phase-out over + phase_out_epoch_bits = (constants.PLOT_V1_PHASE_OUT // constants.EPOCH_BLOCKS).bit_length() + + phase_out_epoch_mask = (1 << phase_out_epoch_bits) - 1 + + # we just look at one byte so the mask can't be bigger than that + assert phase_out_epoch_mask < 256 + + # this counter is counting down to zero + epoch_counter = (1 << phase_out_epoch_bits) - ( + prev_transaction_block_height - constants.HARD_FORK2_HEIGHT + ) // constants.EPOCH_BLOCKS + + # if we're past the phase-out, v1 plots are unconditionally invalid + if epoch_counter <= 0: + return True + + proof_value = std_hash(proof + b"chia proof-of-space v1 phase-out")[0] & phase_out_epoch_mask + return proof_value > epoch_counter + + def verify_and_get_quality_string( pos: ProofOfSpace, constants: ConsensusConstants, @@ -82,7 +116,14 @@ def verify_and_get_quality_string( signage_point: bytes32, *, height: uint32, + prev_transaction_block_height: uint32, # this is the height of the last tx block before the current block SP ) -> Optional[bytes32]: + plot_size = pos.size() + + if plot_size.size_v1 is not None and is_v1_phased_out(pos.proof, prev_transaction_block_height, constants): + log.info("v1 proof has been phased-out and is no longer valid") + return None + # Exactly one of (pool_public_key, pool_contract_puzzle_hash) must not be None if (pos.pool_public_key is None) and (pos.pool_contract_puzzle_hash is None): log.error("Expected pool public key or pool contract puzzle hash but got neither") @@ -91,7 +132,6 @@ def verify_and_get_quality_string( log.error("Expected pool public key or pool contract puzzle hash but got both") return None - plot_size = pos.size() if not check_plot_size(constants, plot_size): return None