From 31b4b6fb113183d6471ead93abe4c3d39a8ae5e4 Mon Sep 17 00:00:00 2001 From: arvidn Date: Thu, 14 Aug 2025 10:30:33 +0200 Subject: [PATCH 1/3] add v2 support to calculate_iterations_quality() and update the corresponding test --- .../core/consensus/test_pot_iterations.py | 23 +++++++++------- chia/consensus/pot_iterations.py | 26 +++++++++---------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/chia/_tests/core/consensus/test_pot_iterations.py b/chia/_tests/core/consensus/test_pot_iterations.py index 187213263bce..f7df7683f32e 100644 --- a/chia/_tests/core/consensus/test_pot_iterations.py +++ b/chia/_tests/core/consensus/test_pot_iterations.py @@ -83,20 +83,22 @@ 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 - # TODO: todo_v2_plots test this for v2 plots as well 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. """ farmer_ks = { - uint8(32): 100, - uint8(33): 100, - uint8(34): 100, - uint8(35): 100, - uint8(36): 100, + PlotSize.make_v1(32): 100, + PlotSize.make_v1(33): 100, + PlotSize.make_v1(34): 100, + PlotSize.make_v1(35): 100, + PlotSize.make_v1(36): 100, + PlotSize.make_v2(28): 100, + PlotSize.make_v2(30): 100, + PlotSize.make_v2(32): 100, } - farmer_space = {k: _expected_plot_size(PlotSize.make_v1(k)) * count for k, count in farmer_ks.items()} + farmer_space = {k: _expected_plot_size(k) * count for k, count in farmer_ks.items()} total_space = sum(farmer_space.values()) percentage_space = {k: float(sp / total_space) for k, sp in farmer_space.items()} wins = {k: 0 for k in farmer_ks.keys()} @@ -111,9 +113,12 @@ def test_win_percentage(self): sp_hash = std_hash(slot_index.to_bytes(4, "big") + sp_index.to_bytes(4, "big")) for k, count in farmer_ks.items(): for farmer_index in range(count): - quality = std_hash(slot_index.to_bytes(4, "big") + k.to_bytes(1, "big") + bytes(farmer_index)) + plot_k_val = k.size_v1 if k.size_v2 is None else k.size_v2 + 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, PlotSize.make_v1(k), difficulty, sp_hash, uint64(100000000), uint32(0) + constants, quality, k, difficulty, sp_hash, uint64(100000000), uint32(0) ) if required_iters < sp_interval_iters: wins[k] += 1 diff --git a/chia/consensus/pot_iterations.py b/chia/consensus/pot_iterations.py index 513adab77581..f61a5feca883 100644 --- a/chia/consensus/pot_iterations.py +++ b/chia/consensus/pot_iterations.py @@ -110,18 +110,18 @@ def calculate_iterations_quality( """ if size.size_v1 is not None: assert size.size_v2 is None - sp_quality_string: bytes32 = std_hash(quality_string + cc_sp_output_hash) phase_out = calculate_phase_out(constants, ssi, prev_transaction_block_height) - 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 - ) - return max(iters, uint64(1)) else: - # TODO: todo_v2_plots support v2 plots - raise NotImplementedError + 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 + ) + return max(iters, uint64(1)) From 446bfa841e5c71b6fcc453f52367658502c1bc42 Mon Sep 17 00:00:00 2001 From: arvidn Date: Thu, 14 Aug 2025 10:35:03 +0200 Subject: [PATCH 2/3] update _expected_plot_size() for v2 plots --- chia/_tests/core/consensus/test_pot_iterations.py | 15 +++++---------- chia/consensus/pos_quality.py | 15 +++++++++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/chia/_tests/core/consensus/test_pot_iterations.py b/chia/_tests/core/consensus/test_pot_iterations.py index f7df7683f32e..70e3ab9622d5 100644 --- a/chia/_tests/core/consensus/test_pot_iterations.py +++ b/chia/_tests/core/consensus/test_pot_iterations.py @@ -159,21 +159,16 @@ def test_calculate_phase_out(self): def test_expected_plot_size_v1() -> None: - last_size = 4800000 + last_size = 2_400_000 for k in range(18, 50): plot_size = _expected_plot_size(PlotSize.make_v1(k)) - assert plot_size > last_size + assert plot_size > last_size * 2 last_size = plot_size def test_expected_plot_size_v2() -> None: - last_size = 1700000000 - for k in range(18, 32, 2): + last_size = 100_000 + for k in range(16, 32, 2): plot_size = _expected_plot_size(PlotSize.make_v2(k)) - # TODO: todo_v2_plots remove this special case once we support smaller k-sizes - if k < 28: - assert plot_size == 0 - continue - - assert plot_size > last_size + assert plot_size > last_size * 2 last_size = plot_size diff --git a/chia/consensus/pos_quality.py b/chia/consensus/pos_quality.py index 5d7e78db7e49..c6f3bc403fb4 100644 --- a/chia/consensus/pos_quality.py +++ b/chia/consensus/pos_quality.py @@ -7,11 +7,18 @@ # This is not used in consensus, only for display purposes UI_ACTUAL_SPACE_CONSTANT_FACTOR = 0.78 -# these values are from CHIP-48 +# TODO: todo_v2_plots these values prelimenary. When the plotter is complete, +# replace this table with a closed form formula v2_plot_sizes: dict[int, uint64] = { - 28: uint64(1717986918), - 30: uint64(4509715660), - 32: uint64(11381663334), + 16: uint64(222_863), + 18: uint64(1_048_737), + 20: uint64(4_824_084), + 22: uint64(21_812_958), + 24: uint64(97_318_160), + 26: uint64(429_539_960), + 28: uint64(1_879_213_114), + 30: uint64(8_161_097_549), + 32: uint64(35_221_370_574), } From 16e7c9b61c4cdd203b8ec926fd31cb1cb462db23 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Mon, 18 Aug 2025 11:23:33 +0200 Subject: [PATCH 3/3] address review comments --- .../core/consensus/test_pot_iterations.py | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/chia/_tests/core/consensus/test_pot_iterations.py b/chia/_tests/core/consensus/test_pot_iterations.py index 70e3ab9622d5..7c96604b5947 100644 --- a/chia/_tests/core/consensus/test_pot_iterations.py +++ b/chia/_tests/core/consensus/test_pot_iterations.py @@ -1,5 +1,6 @@ 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 @@ -83,7 +84,17 @@ 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 - def test_win_percentage(self): + @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): """ 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. @@ -94,19 +105,19 @@ def test_win_percentage(self): PlotSize.make_v1(34): 100, PlotSize.make_v1(35): 100, PlotSize.make_v1(36): 100, - PlotSize.make_v2(28): 100, - PlotSize.make_v2(30): 100, - PlotSize.make_v2(32): 100, + PlotSize.make_v2(28): 200, + PlotSize.make_v2(30): 200, + PlotSize.make_v2(32): 200, } farmer_space = {k: _expected_plot_size(k) * count for k, count in farmer_ks.items()} - total_space = sum(farmer_space.values()) - percentage_space = {k: float(sp / total_space) for k, sp in farmer_space.items()} wins = {k: 0 for k in farmer_ks.keys()} + + constants = test_constants.replace(DIFFICULTY_CONSTANT_FACTOR=uint128(2**25)) total_slots = 50 num_sps = 16 - sp_interval_iters = uint64(100000000 // 32) + sub_slot_iters = uint64(100000000) + sp_interval_iters = calculate_sp_interval_iters(constants, sub_slot_iters) difficulty = uint64(500000000000) - constants = test_constants.replace(DIFFICULTY_CONSTANT_FACTOR=uint128(2**25)) for slot_index in range(total_slots): total_wins_in_slot = 0 for sp_index in range(num_sps): @@ -114,41 +125,55 @@ def test_win_percentage(self): for k, count in farmer_ks.items(): for farmer_index in range(count): plot_k_val = k.size_v1 if k.size_v2 is None else k.size_v2 + assert plot_k_val is not None 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, uint64(100000000), uint32(0) + constants, quality, k, difficulty, sp_hash, sub_slot_iters, height ) 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() + } + 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 - def test_calculate_phase_out(self): + @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(100000000000) - sp_interval = calculate_sp_interval_iters(constants, sub_slot_iters) + 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, constants.HARD_FORK2_HEIGHT - 1) == 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, constants.HARD_FORK2_HEIGHT + 1) + 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, constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT // 2 + 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, constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT) + calculate_phase_out( + constants, sub_slot_iters, uint32(constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT) + ) == sp_interval )