Skip to content

Commit 3491f1a

Browse files
authored
Plot v2 quality string (#19769)
* add shortcuts for bytes32 and G1Element to test_proof_of_space.py to make it a bit easier to read * introduce calculate_plot_difficulty(), for incrementing plot difficulty over time. Tweak the placeholder cut-over block heights to make it possible to test * update verify_and_get_quality_string() to support more aspects of v2 plots. update the v2 proof verification stub to take plot difficulty. add plot filter test cases for v2 plots * move some tests from weight-proof to proof-of-space
1 parent 0864c34 commit 3491f1a

File tree

7 files changed

+209
-140
lines changed

7 files changed

+209
-140
lines changed

chia/_tests/core/custom_types/test_proof_of_space.py

Lines changed: 120 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@
55
from typing import Optional
66

77
import pytest
8-
from chia_rs import G1Element, ProofOfSpace
8+
from chia_rs import G1Element, PlotSize, ProofOfSpace
99
from chia_rs.sized_bytes import bytes32, bytes48
1010
from chia_rs.sized_ints import uint8, uint32
1111

1212
from chia._tests.util.misc import Marks, datacases
1313
from chia.consensus.default_constants import DEFAULT_CONSTANTS
14-
from chia.types.blockchain_format.proof_of_space import passes_plot_filter, verify_and_get_quality_string
14+
from chia.types.blockchain_format.proof_of_space import (
15+
calculate_plot_difficulty,
16+
calculate_prefix_bits,
17+
passes_plot_filter,
18+
verify_and_get_quality_string,
19+
)
1520

1621

1722
@dataclass
@@ -27,6 +32,15 @@ class ProofOfSpaceCase:
2732
marks: Marks = ()
2833

2934

35+
def g1(key: str) -> G1Element:
36+
return G1Element.from_bytes_unchecked(bytes48.from_hexstr(key))
37+
38+
39+
def b32(key: str) -> bytes32:
40+
return bytes32.from_hexstr(key)
41+
42+
43+
# TODO: todo_v2_plots more test cases
3044
@datacases(
3145
ProofOfSpaceCase(
3246
id="Neither pool public key nor pool contract puzzle hash",
@@ -70,37 +84,57 @@ class ProofOfSpaceCase:
7084
),
7185
ProofOfSpaceCase(
7286
id="Not passing the plot filter with size 9",
73-
pos_challenge=bytes32.from_hexstr("08b23cc2844dfb92d2eedaa705a1ce665d571ee753bd81cbb67b92caa6d34722"),
87+
pos_challenge=b32("08b23cc2844dfb92d2eedaa705a1ce665d571ee753bd81cbb67b92caa6d34722"),
7488
plot_size=uint8(42),
75-
pool_public_key=G1Element.from_bytes_unchecked(
76-
bytes48.from_hexstr(
77-
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
78-
)
89+
pool_public_key=g1(
90+
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
7991
),
80-
plot_public_key=G1Element.from_bytes_unchecked(
81-
bytes48.from_hexstr(
82-
"b17d368f5400230b2b01464807825bf4163c5c159bd7d4465f935912e538ac9fb996dd9a9c479bd8aa6256bdca1fed96"
83-
)
92+
plot_public_key=g1(
93+
"b17d368f5400230b2b01464807825bf4163c5c159bd7d4465f935912e538ac9fb996dd9a9c479bd8aa6256bdca1fed96"
8494
),
8595
height=uint32(5495999),
8696
expected_error="Did not pass the plot filter",
8797
),
8898
ProofOfSpaceCase(
8999
id="Passing the plot filter with size 8",
90-
pos_challenge=bytes32.from_hexstr("08b23cc2844dfb92d2eedaa705a1ce665d571ee753bd81cbb67b92caa6d34722"),
100+
pos_challenge=b32("08b23cc2844dfb92d2eedaa705a1ce665d571ee753bd81cbb67b92caa6d34722"),
91101
plot_size=uint8(42),
92-
pool_public_key=G1Element.from_bytes_unchecked(
93-
bytes48.from_hexstr(
94-
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
95-
)
102+
pool_public_key=g1(
103+
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
96104
),
97-
plot_public_key=G1Element.from_bytes_unchecked(
98-
bytes48.from_hexstr(
99-
"b17d368f5400230b2b01464807825bf4163c5c159bd7d4465f935912e538ac9fb996dd9a9c479bd8aa6256bdca1fed96"
100-
)
105+
plot_public_key=g1(
106+
"b17d368f5400230b2b01464807825bf4163c5c159bd7d4465f935912e538ac9fb996dd9a9c479bd8aa6256bdca1fed96"
101107
),
102108
height=uint32(5496000),
103109
),
110+
ProofOfSpaceCase(
111+
id="v2 plot size 0",
112+
pos_challenge=bytes32(b"1" * 32),
113+
plot_size=uint8(0x80),
114+
plot_public_key=G1Element(),
115+
pool_public_key=G1Element(),
116+
expected_error="Plot size is lower than the minimum",
117+
),
118+
ProofOfSpaceCase(
119+
id="v2 plot size 34",
120+
pos_challenge=bytes32(b"1" * 32),
121+
plot_size=uint8(0x80 | 34),
122+
plot_public_key=G1Element(),
123+
pool_public_key=G1Element(),
124+
expected_error="Plot size is higher than the maximum",
125+
),
126+
ProofOfSpaceCase(
127+
id="Not passing the plot filter v2",
128+
pos_challenge=b32("3d29ea79d19b3f7e99ebf764ae53697cbe143603909873946af6ab1ece606861"),
129+
plot_size=uint8(0x80 | 32),
130+
pool_public_key=g1(
131+
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
132+
),
133+
plot_public_key=g1(
134+
"879526b4e7b616cfd64984d8ad140d0798b048392a6f11e2faf09054ef467ea44dc0dab5e5edb2afdfa850c5c8b629cc"
135+
),
136+
expected_error="Did not pass the plot filter",
137+
),
104138
)
105139
def test_verify_and_get_quality_string(caplog: pytest.LogCaptureFixture, case: ProofOfSpaceCase) -> None:
106140
pos = ProofOfSpace(
@@ -114,41 +148,25 @@ def test_verify_and_get_quality_string(caplog: pytest.LogCaptureFixture, case: P
114148
quality_string = verify_and_get_quality_string(
115149
pos=pos,
116150
constants=DEFAULT_CONSTANTS,
117-
original_challenge_hash=bytes32.from_hexstr(
118-
"0x73490e166d0b88347c37d921660b216c27316aae9a3450933d3ff3b854e5831a"
119-
),
120-
signage_point=bytes32.from_hexstr("0x7b3e23dbd438f9aceefa9827e2c5538898189987f49b06eceb7a43067e77b531"),
151+
original_challenge_hash=b32("0x73490e166d0b88347c37d921660b216c27316aae9a3450933d3ff3b854e5831a"),
152+
signage_point=b32("0x7b3e23dbd438f9aceefa9827e2c5538898189987f49b06eceb7a43067e77b531"),
121153
height=case.height,
122154
)
123155
assert quality_string is None
124156
assert len(caplog.text) == 0 if case.expected_error is None else case.expected_error in caplog.text
125157

126158

127-
# TODO: todo_v2_plots more test cases
128159
@datacases(
129-
ProofOfSpaceCase(
130-
id="v2 plot size 0",
131-
pos_challenge=bytes32(b"1" * 32),
132-
plot_size=uint8(0x80),
133-
plot_public_key=G1Element(),
134-
pool_public_key=G1Element(),
135-
pool_contract_puzzle_hash=bytes32(b"1" * 32),
136-
expected_error="Plot size is lower than the minimum",
137-
),
138-
ProofOfSpaceCase(
139-
id="v2 plot size 34",
140-
pos_challenge=bytes32(b"1" * 32),
141-
plot_size=uint8(0x80 | 34),
142-
plot_public_key=G1Element(),
143-
pool_public_key=G1Element(),
144-
expected_error="Plot size is higher than the maximum",
145-
),
146160
ProofOfSpaceCase(
147161
id="v2 plot are not implemented",
148-
pos_challenge=bytes32(b"1" * 32),
149162
plot_size=uint8(0x80 | 30),
150-
plot_public_key=G1Element(),
151-
pool_public_key=G1Element(),
163+
pos_challenge=b32("47deb938e145d25d7b3b3c85ca9e3972b76c01aeeb78a02fe5d3b040d282317e"),
164+
plot_public_key=g1(
165+
"afa3aaf09c03885154be49216ee7fb2e4581b9c4a4d7e9cc402e27280bf0cfdbdf1b9ba674e301fd1d1450234b3b1868"
166+
),
167+
pool_public_key=g1(
168+
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
169+
),
152170
expected_error="NotImplementedError",
153171
),
154172
)
@@ -168,20 +186,41 @@ def test_verify_and_get_quality_string_v2(caplog: pytest.LogCaptureFixture, case
168186
quality_string = verify_and_get_quality_string(
169187
pos=pos,
170188
constants=DEFAULT_CONSTANTS,
171-
original_challenge_hash=bytes32.from_hexstr(
172-
"0x73490e166d0b88347c37d921660b216c27316aae9a3450933d3ff3b854e5831a"
173-
),
174-
signage_point=bytes32.from_hexstr("0x7b3e23dbd438f9aceefa9827e2c5538898189987f49b06eceb7a43067e77b531"),
189+
original_challenge_hash=b32("0x73490e166d0b88347c37d921660b216c27316aae9a3450933d3ff3b854e5831a"),
190+
signage_point=b32("0x7b3e23dbd438f9aceefa9827e2c5538898189987f49b06eceb7a43067e77b531"),
175191
height=case.height,
176192
)
177-
except Exception as e:
193+
except NotImplementedError as e:
178194
assert case.expected_error is not None
179195
assert case.expected_error in repr(e)
180196
else:
181197
assert quality_string is None
182198
assert len(caplog.text) == 0 if case.expected_error is None else case.expected_error in caplog.text
183199

184200

201+
@pytest.mark.parametrize(
202+
"height, difficulty",
203+
[
204+
(0, 2),
205+
(DEFAULT_CONSTANTS.HARD_FORK_HEIGHT, 2),
206+
(DEFAULT_CONSTANTS.HARD_FORK2_HEIGHT, 2),
207+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_4_HEIGHT - 1, 2),
208+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_4_HEIGHT, 4),
209+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_5_HEIGHT - 1, 4),
210+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_5_HEIGHT, 5),
211+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_6_HEIGHT - 1, 5),
212+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_6_HEIGHT, 6),
213+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_7_HEIGHT - 1, 6),
214+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_7_HEIGHT, 7),
215+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_8_HEIGHT - 1, 7),
216+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_8_HEIGHT, 8),
217+
(DEFAULT_CONSTANTS.PLOT_DIFFICULTY_8_HEIGHT + 1000000, 8),
218+
],
219+
)
220+
def test_calculate_plot_difficulty(height: uint32, difficulty: uint8) -> None:
221+
assert calculate_plot_difficulty(DEFAULT_CONSTANTS, height) == difficulty
222+
223+
185224
class TestProofOfSpace:
186225
@pytest.mark.parametrize("prefix_bits", [DEFAULT_CONSTANTS.NUMBER_ZERO_BITS_PLOT_FILTER_V1, 8, 7, 6, 5, 1, 0])
187226
def test_can_create_proof(self, prefix_bits: int, seeded_random: random.Random) -> None:
@@ -200,3 +239,34 @@ def test_can_create_proof(self, prefix_bits: int, seeded_random: random.Random)
200239
success_count += 1
201240

202241
assert abs((success_count * target_filter / num_trials) - 1) < 0.35
242+
243+
244+
@pytest.mark.parametrize("height,expected", [(0, 3), (5496000, 2), (10542000, 1), (15592000, 0), (20643000, 0)])
245+
@pytest.mark.parametrize("plot_size", [PlotSize.make_v1(32), PlotSize.make_v2(28)])
246+
def test_calculate_prefix_bits_clamp_zero(height: uint32, expected: int, plot_size: PlotSize) -> None:
247+
constants = DEFAULT_CONSTANTS.replace(NUMBER_ZERO_BITS_PLOT_FILTER_V1=uint8(3))
248+
if plot_size.size_v2 is not None:
249+
expected = constants.NUMBER_ZERO_BITS_PLOT_FILTER_V2
250+
assert calculate_prefix_bits(constants, height, plot_size) == expected
251+
252+
253+
@pytest.mark.parametrize(
254+
argnames=["height", "expected"],
255+
argvalues=[
256+
(0, 9),
257+
(5495999, 9),
258+
(5496000, 8),
259+
(10541999, 8),
260+
(10542000, 7),
261+
(15591999, 7),
262+
(15592000, 6),
263+
(20642999, 6),
264+
(20643000, 5),
265+
],
266+
)
267+
@pytest.mark.parametrize("plot_size", [PlotSize.make_v1(32), PlotSize.make_v2(28)])
268+
def test_calculate_prefix_bits_default(height: uint32, expected: int, plot_size: PlotSize) -> None:
269+
constants = DEFAULT_CONSTANTS
270+
if plot_size.size_v2 is not None:
271+
expected = DEFAULT_CONSTANTS.NUMBER_ZERO_BITS_PLOT_FILTER_V2
272+
assert calculate_prefix_bits(constants, height, plot_size) == expected

chia/_tests/weight_proof/test_weight_proof.py

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33
import pytest
44
from chia_rs import BlockRecord, ConsensusConstants, FullBlock, HeaderBlock, SubEpochSummary
55
from chia_rs.sized_bytes import bytes32
6-
from chia_rs.sized_ints import uint8, uint32
6+
from chia_rs.sized_ints import uint32
77

88
from chia._tests.util.blockchain_mock import BlockchainMock
9-
from chia.consensus.default_constants import DEFAULT_CONSTANTS
109
from chia.consensus.full_block_to_block_record import block_to_block_record
1110
from chia.consensus.generator_tools import get_block_header
1211
from chia.consensus.pot_iterations import validate_pospace_and_get_required_iters
1312
from chia.full_node.weight_proof import WeightProofHandler, _map_sub_epoch_summaries, _validate_summaries_weight
1413
from chia.simulator.block_tools import BlockTools
15-
from chia.types.blockchain_format.proof_of_space import calculate_prefix_bits
1614

1715

1816
async def load_blocks_dont_validate(
@@ -490,28 +488,3 @@ async def test_weight_proof_extend_multiple_ses(
490488
valid, fork_point, _ = await wpf.validate_weight_proof(new_wp)
491489
assert valid
492490
assert fork_point != 0
493-
494-
495-
@pytest.mark.parametrize("height,expected", [(0, 3), (5496000, 2), (10542000, 1), (15592000, 0), (20643000, 0)])
496-
def test_calculate_prefix_bits_clamp_zero(height: uint32, expected: int) -> None:
497-
constants = DEFAULT_CONSTANTS.replace(NUMBER_ZERO_BITS_PLOT_FILTER_V1=uint8(3))
498-
assert calculate_prefix_bits(constants, height) == expected
499-
500-
501-
@pytest.mark.parametrize(
502-
argnames=["height", "expected"],
503-
argvalues=[
504-
(0, 9),
505-
(5495999, 9),
506-
(5496000, 8),
507-
(10541999, 8),
508-
(10542000, 7),
509-
(15591999, 7),
510-
(15592000, 6),
511-
(20642999, 6),
512-
(20643000, 5),
513-
],
514-
)
515-
def test_calculate_prefix_bits_default(height: uint32, expected: int) -> None:
516-
constants = DEFAULT_CONSTANTS
517-
assert calculate_prefix_bits(constants, height) == expected

chia/consensus/default_constants.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
9
3131
), # H(plot signature of the challenge) must start with these many zeroes, for v1 plots
3232
NUMBER_ZERO_BITS_PLOT_FILTER_V2=uint8(
33-
9
33+
5
3434
), # H(plot signature of the challenge) must start with these many zeroes. for v2 plots
3535
MIN_PLOT_SIZE_V1=uint8(32), # 32 for mainnet
3636
MAX_PLOT_SIZE_V1=uint8(50),
@@ -81,18 +81,18 @@
8181
POOL_SUB_SLOT_ITERS=uint64(37600000000), # iters limit * NUM_SPS
8282
# June 2024
8383
HARD_FORK_HEIGHT=uint32(5496000),
84-
HARD_FORK2_HEIGHT=uint32(0xFFFFFFFF),
84+
HARD_FORK2_HEIGHT=uint32(0xFFFFFFFA),
8585
# June 2027
8686
PLOT_FILTER_128_HEIGHT=uint32(10542000),
8787
# June 2030
8888
PLOT_FILTER_64_HEIGHT=uint32(15592000),
8989
# June 2033
9090
PLOT_FILTER_32_HEIGHT=uint32(20643000),
9191
PLOT_DIFFICULTY_INITIAL=uint8(2),
92-
PLOT_DIFFICULTY_4_HEIGHT=uint32(0xFFFFFFFF),
93-
PLOT_DIFFICULTY_5_HEIGHT=uint32(0xFFFFFFFF),
94-
PLOT_DIFFICULTY_6_HEIGHT=uint32(0xFFFFFFFF),
95-
PLOT_DIFFICULTY_7_HEIGHT=uint32(0xFFFFFFFF),
92+
PLOT_DIFFICULTY_4_HEIGHT=uint32(0xFFFFFFFB),
93+
PLOT_DIFFICULTY_5_HEIGHT=uint32(0xFFFFFFFC),
94+
PLOT_DIFFICULTY_6_HEIGHT=uint32(0xFFFFFFFD),
95+
PLOT_DIFFICULTY_7_HEIGHT=uint32(0xFFFFFFFE),
9696
PLOT_DIFFICULTY_8_HEIGHT=uint32(0xFFFFFFFF),
9797
)
9898

chia/full_node/full_node_rpc_api.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
BlockRecord,
1111
CoinSpend,
1212
FullBlock,
13+
PlotSize,
1314
SpendBundle,
1415
SpendBundleConditions,
1516
run_block_generator2,
@@ -599,7 +600,9 @@ async def get_network_space(self, request: dict[str, Any]) -> EndpointResult:
599600
raise ValueError(f"Older block {older_block_hex} not found")
600601
delta_weight = newer_block.weight - older_block.weight
601602

602-
plot_filter_size = calculate_prefix_bits(self.service.constants, newer_block.height)
603+
# TODO: todo_v2_plots Update this calculation to take v2 plots into
604+
# account
605+
plot_filter_size = calculate_prefix_bits(self.service.constants, newer_block.height, PlotSize.make_v1(32))
603606
delta_iters = newer_block.total_iters - older_block.total_iters
604607
weight_div_iters = delta_weight / delta_iters
605608
additional_difficulty_constant = self.service.constants.DIFFICULTY_CONSTANT_FACTOR

chia/harvester/harvester_api.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,15 @@ async def lookup_challenge(
249249
# Passes the plot filter (does not check sp filter yet though, since we have not reached sp)
250250
# This is being executed at the beginning of the slot
251251
total += 1
252-
filter_prefix_bits = uint8(calculate_prefix_bits(self.harvester.constants, new_challenge.peak_height))
252+
253+
# TODO: todo_v2_plots support v2 plots in PlotManager
254+
filter_prefix_bits = uint8(
255+
calculate_prefix_bits(
256+
self.harvester.constants,
257+
new_challenge.peak_height,
258+
PlotSize.make_v1(try_plot_info.prover.get_size()),
259+
)
260+
)
253261
if passes_plot_filter(
254262
filter_prefix_bits,
255263
try_plot_info.prover.get_id(),

chia/simulator/block_tools.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1506,7 +1506,8 @@ def get_pospaces_for_challenge(
15061506
plot_id: bytes32 = plot_info.prover.get_id()
15071507
if force_plot_id is not None and plot_id != force_plot_id:
15081508
continue
1509-
prefix_bits = calculate_prefix_bits(constants, height)
1509+
# TODO: todo_v2_plots support v2 plots in the plot manager
1510+
prefix_bits = calculate_prefix_bits(constants, height, PlotSize.make_v1(plot_info.prover.get_size()))
15101511
if passes_plot_filter(prefix_bits, plot_id, challenge_hash, signage_point):
15111512
new_challenge: bytes32 = calculate_pos_challenge(plot_id, challenge_hash, signage_point)
15121513
qualities = plot_info.prover.get_qualities_for_challenge(new_challenge)
@@ -1515,6 +1516,7 @@ def get_pospaces_for_challenge(
15151516
required_iters = calculate_iterations_quality(
15161517
constants,
15171518
quality_str,
1519+
# TODO: todo_v2_plots support v2 plots in the plot manager
15181520
PlotSize.make_v1(plot_info.prover.get_size()),
15191521
difficulty,
15201522
signage_point,

0 commit comments

Comments
 (0)