[CHIA-3795] change v1 plot phase-out (#20216)

* change v1 plot phase-out to not affect required iters, and to be quantized to epoch blocks

* disable hard-fork 2 tests until we have a new test cache

* fix rate limits test

* address review comments
This commit is contained in:
Arvid Norberg
2025-11-06 09:36:29 -07:00
committed by GitHub
parent cb7e51df82
commit ece6de2af5
18 changed files with 162 additions and 132 deletions
+2 -1
View File
@@ -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
@@ -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.pos_quality import _expected_plot_size
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 @@ class TestPotIterations:
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 @@ class TestPotIterations:
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
@@ -14,6 +14,7 @@ from chia.consensus.default_constants import DEFAULT_CONSTANTS
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
@@ -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
+7 -5
View File
@@ -24,7 +24,6 @@ from chia.types.peer_info import PeerInfo
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
+20 -3
View File
@@ -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
)
@@ -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
@@ -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:
+1 -1
View File
@@ -84,7 +84,7 @@ DEFAULT_CONSTANTS = ConsensusConstants(
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
@@ -220,7 +220,6 @@ async def pre_validate_block(
cc_sp_hash,
block.height,
vs.difficulty,
vs.ssi,
prev_tx_block(blockchain, prev_b),
)
if required_iters is None:
+10 -37
View File
@@ -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))
+8 -5
View File
@@ -111,6 +111,7 @@ class FarmerAPI:
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 @@ class FarmerAPI:
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 @@ class FarmerAPI:
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 @@ class FarmerAPI:
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 @@ class FarmerAPI:
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}")
+9 -4
View File
@@ -877,21 +877,28 @@ class FullNodeAPI:
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 @@ class FullNodeAPI:
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(
+6 -7
View File
@@ -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")
+12 -4
View File
@@ -29,6 +29,7 @@ from chia.types.blockchain_format.proof_of_space import (
calculate_pos_challenge,
calculate_prefix_bits,
generate_plot_public_key,
is_v1_phased_out,
make_pos,
passes_plot_filter,
)
@@ -192,8 +193,6 @@ class HarvesterAPI:
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 @@ class HarvesterAPI:
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 @@ class HarvesterAPI:
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(
+9 -4
View File
@@ -96,6 +96,7 @@ from chia.types.blockchain_format.proof_of_space import (
calculate_prefix_bits,
generate_plot_public_key,
generate_taproot_sk,
is_v1_phased_out,
make_pos,
passes_plot_filter,
)
@@ -153,6 +154,7 @@ test_constants = DEFAULT_CONSTANTS.replace(
# 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 @@ class BlockTools:
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 @@ class BlockTools:
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 @@ class BlockTools:
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 @@ class BlockTools:
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
-1
View File
@@ -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
+41 -1
View File
@@ -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