Skip to content

Commit 45bc771

Browse files
committed
add support for creating v2 plots in block_tools
1 parent 3d3a28d commit 45bc771

File tree

9 files changed

+197
-22
lines changed

9 files changed

+197
-22
lines changed

chia/_tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,7 @@ async def farmer_harvester_2_simulators_zero_bits_plot_filter(
12881288
num_og_plots=0,
12891289
num_pool_plots=0,
12901290
num_non_keychain_plots=0,
1291+
num_v2_plots=0,
12911292
config_overrides=config_overrides,
12921293
)
12931294
for _ in range(2)

chia/_tests/core/custom_types/test_proof_of_space.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,25 +112,23 @@ def b32(key: str) -> bytes32:
112112
id="v2 plot size 0",
113113
pos_challenge=bytes32(b"1" * 32),
114114
plot_size=PlotSize.make_v2(0),
115+
pool_contract_puzzle_hash=bytes32(b"1" * 32),
115116
plot_public_key=G1Element(),
116-
pool_public_key=G1Element(),
117117
expected_error="Plot size (0) is lower than the minimum (28)",
118118
),
119119
ProofOfSpaceCase(
120120
id="v2 plot size 34",
121121
pos_challenge=bytes32(b"1" * 32),
122122
plot_size=PlotSize.make_v2(34),
123+
pool_contract_puzzle_hash=bytes32(b"1" * 32),
123124
plot_public_key=G1Element(),
124-
pool_public_key=G1Element(),
125125
expected_error="Plot size (34) is higher than the maximum (32)",
126126
),
127127
ProofOfSpaceCase(
128128
id="Not passing the plot filter v2",
129-
pos_challenge=b32("3d29ea79d19b3f7e99ebf764ae53697cbe143603909873946af6ab1ece606861"),
129+
pos_challenge=b32("4cfaacbd2782db64d07cf490ca938534adb07dfbd2f92b0e479e2e5b196178db"),
130130
plot_size=PlotSize.make_v2(32),
131-
pool_public_key=g1(
132-
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
133-
),
131+
pool_contract_puzzle_hash=bytes32(b"1" * 32),
134132
plot_public_key=g1(
135133
"879526b4e7b616cfd64984d8ad140d0798b048392a6f11e2faf09054ef467ea44dc0dab5e5edb2afdfa850c5c8b629cc"
136134
),
@@ -160,16 +158,15 @@ def test_verify_and_get_quality_string(caplog: pytest.LogCaptureFixture, case: P
160158
@datacases(
161159
ProofOfSpaceCase(
162160
id="v2 plot are not implemented",
163-
plot_size=PlotSize.make_v2(30),
164-
pos_challenge=b32("47deb938e145d25d7b3b3c85ca9e3972b76c01aeeb78a02fe5d3b040d282317e"),
161+
plot_size=PlotSize.make_v2(28),
162+
pos_challenge=b32("6a85c4c5f21e5728c6668b01c0758e33bb7f3ae20d38703d4863ad151f983d9c"),
165163
plot_public_key=g1(
166164
"afa3aaf09c03885154be49216ee7fb2e4581b9c4a4d7e9cc402e27280bf0cfdbdf1b9ba674e301fd1d1450234b3b1868"
167165
),
168-
pool_public_key=g1(
169-
"b6449c2c68df97c19e884427e42ee7350982d4020571ead08732615ff39bd216bfd630b6460784982bec98b49fea79d0"
170-
),
171-
expected_error="NotImplementedError",
166+
pool_contract_puzzle_hash=bytes32(b"1" * 32),
167+
expected_error="Did not pass the plot filter",
172168
),
169+
# TODO: todo_v2_plots add test case that passes the plot filter
173170
)
174171
def test_verify_and_get_quality_string_v2(caplog: pytest.LogCaptureFixture, case: ProofOfSpaceCase) -> None:
175172
pos = make_pos(

chia/_tests/core/test_farmer_harvester_rpc.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ async def non_zero_plots() -> bool:
8787
res = await harvester_rpc_client.get_plots()
8888
nonlocal harvester_plots
8989
harvester_plots = res["plots"]
90-
return len(harvester_plots) > 0
90+
return len(harvester_plots) == 340
9191

9292
await time_out_assert(10, non_zero_plots)
9393

@@ -293,7 +293,7 @@ async def wait_for_plot_sync() -> bool:
293293
await farmer_api.farmer.update_pool_state()
294294

295295
pool_plot_count: int = (await farmer_rpc_client.get_pool_state())["pool_state"][0]["plot_count"]
296-
assert pool_plot_count == 5
296+
assert pool_plot_count == 325
297297

298298
# TODO: Maybe improve this to not remove from Receiver directly but instead from the harvester and then wait for
299299
# plot sync event.
@@ -333,8 +333,8 @@ def test_plot_matches_filter(filter_item: FilterItem, match: bool) -> None:
333333
@pytest.mark.parametrize(
334334
"endpoint, filtering, sort_key, reverse, expected_plot_count",
335335
[
336-
(FarmerRpcClient.get_harvester_plots_valid, [], "filename", False, 20),
337-
(FarmerRpcClient.get_harvester_plots_valid, [], "size", True, 20),
336+
(FarmerRpcClient.get_harvester_plots_valid, [], "filename", False, 340),
337+
(FarmerRpcClient.get_harvester_plots_valid, [], "size", True, 340),
338338
(
339339
FarmerRpcClient.get_harvester_plots_valid,
340340
[FilterItem("pool_contract_puzzle_hash", None)],
@@ -349,6 +349,20 @@ def test_plot_matches_filter(filter_item: FilterItem, match: bool) -> None:
349349
False,
350350
4,
351351
),
352+
(
353+
FarmerRpcClient.get_harvester_plots_valid,
354+
[FilterItem("strength", "2")],
355+
"size",
356+
True,
357+
320,
358+
),
359+
(
360+
FarmerRpcClient.get_harvester_plots_valid,
361+
[FilterItem("strength", "0")],
362+
"size",
363+
True,
364+
20,
365+
),
352366
(FarmerRpcClient.get_harvester_plots_invalid, [], None, True, 13),
353367
(FarmerRpcClient.get_harvester_plots_invalid, ["invalid_0"], None, False, 6),
354368
(FarmerRpcClient.get_harvester_plots_invalid, ["inval", "lid_1"], None, False, 2),

chia/_tests/farmer_harvester/test_filter_prefix_bits.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ async def have_connections() -> bool:
8484
await harvester_rpc_cl.await_closed()
8585

8686

87-
@pytest.mark.parametrize(argnames=["peak_height", "eligible_plots"], argvalues=[(5495999, 0), (5496000, 1)])
87+
@pytest.mark.parametrize(argnames=["peak_height", "eligible_plots"], argvalues=[(5495999, 9), (5496000, 10)])
8888
@pytest.mark.anyio
8989
async def test_filter_prefix_bits_with_farmer_harvester(
9090
farmer_harvester_with_filter_size_9: tuple[HarvesterService, FarmerAPI],

chia/plotting/create_plots.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
from pathlib import Path
66
from typing import Optional
77

8-
from chia_rs import AugSchemeMPL, G1Element, PrivateKey
8+
from chia_rs import AugSchemeMPL, G1Element, PrivateKey, create_v2_plot
99
from chia_rs.sized_bytes import bytes32
10+
from chia_rs.sized_ints import uint8
1011
from chiapos import DiskPlotter
1112

1213
from chia.daemon.keychain_proxy import KeychainProxy, connect_to_keychain_and_validate, wrap_local_keychain
1314
from chia.plotting.util import Params, stream_plot_info_ph, stream_plot_info_pk
1415
from chia.types.blockchain_format.proof_of_space import (
1516
calculate_plot_id_ph,
1617
calculate_plot_id_pk,
18+
calculate_plot_id_v2,
1719
generate_plot_public_key,
1820
)
1921
from chia.util.bech32m import decode_puzzle_hash
@@ -275,3 +277,63 @@ async def create_plots(
275277
log.info(created_path.name)
276278

277279
return created_plots, existing_plots
280+
281+
282+
async def create_v2_plots(
283+
final_dir: Path,
284+
*,
285+
pool_ph: bytes32,
286+
farmer_pk: G1Element,
287+
size: int = 28,
288+
strength: int = 2,
289+
num: int = 1,
290+
use_datetime: bool = True,
291+
test_private_keys: Optional[list[PrivateKey]] = None,
292+
) -> tuple[dict[bytes32, Path], dict[bytes32, Path]]:
293+
log.info(
294+
f"Creating {num} plots of size {size}, pool contract address: "
295+
f"{pool_ph} farmer public key: {bytes(farmer_pk).hex()}"
296+
)
297+
298+
final_dir.mkdir(parents=True, exist_ok=True)
299+
300+
created_plots: dict[bytes32, Path] = {}
301+
existing_plots: dict[bytes32, Path] = {}
302+
for i in range(num):
303+
# Generate a random master secret key
304+
if test_private_keys is not None:
305+
assert len(test_private_keys) == num
306+
sk: PrivateKey = test_private_keys[i]
307+
else:
308+
sk = AugSchemeMPL.key_gen(bytes32.secret())
309+
310+
# The plot public key is the combination of the harvester and farmer keys
311+
# New plots will also include a taproot of the keys, for extensibility
312+
plot_public_key = generate_plot_public_key(master_sk_to_local_sk(sk).get_g1(), farmer_pk, include_taproot=True)
313+
314+
# The plot id is based on the harvester, farmer, pool contract puzzle
315+
# hash and strength
316+
plot_id = calculate_plot_id_v2(pool_ph, plot_public_key, uint8(strength))
317+
plot_memo = stream_plot_info_ph(pool_ph, farmer_pk, sk)
318+
319+
dt_string = datetime.now().strftime("%Y-%m-%d-%H-%M")
320+
321+
if use_datetime:
322+
filename: str = f"plot-k{size}-{dt_string}-{plot_id}.plot2"
323+
else:
324+
filename = f"plot-k{size}-{plot_id}.plot2"
325+
full_path: Path = final_dir / filename
326+
327+
if not full_path.exists():
328+
log.info(f"Starting plot {i + 1}/{num}")
329+
create_v2_plot(str(full_path.absolute()), size, strength, plot_id, plot_memo)
330+
created_plots[plot_id] = full_path
331+
else:
332+
log.info(f"Plot {filename} already exists")
333+
existing_plots[plot_id] = full_path
334+
335+
log.info(f"Created a total of {len(created_plots)} new plots")
336+
for created_path in created_plots.values():
337+
log.info(created_path.name)
338+
339+
return created_plots, existing_plots

chia/plotting/manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ def _refresh_task(self, sleep_interval_ms: int):
278278
f"total_result.removed {len(total_result.removed)}, "
279279
f"total_duration {total_result.duration:.2f} seconds"
280280
)
281-
except Exception as e:
282-
log.error(f"_refresh_callback raised: {e} with the traceback: {traceback.format_exc()}")
281+
except Exception:
282+
log.exception("_refresh_callback raised")
283283
self.reset()
284284

285285
def refresh_batch(self, plot_paths: list[Path], plot_directories: set[Path]) -> PlotRefreshResult:

chia/simulator/block_tools.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
from chia.consensus.vdf_info_computation import get_signage_point_vdf_info
6464
from chia.daemon.keychain_proxy import KeychainProxy, connect_to_keychain_and_validate, wrap_local_keychain
6565
from chia.full_node.bundle_tools import simple_solution_generator, simple_solution_generator_backrefs
66-
from chia.plotting.create_plots import PlotKeys, create_plots
66+
from chia.plotting.create_plots import PlotKeys, create_plots, create_v2_plots
6767
from chia.plotting.manager import PlotManager
6868
from chia.plotting.prover import PlotVersion, QualityProtocol, V1Prover, V2Prover, V2Quality
6969
from chia.plotting.util import (
@@ -319,6 +319,7 @@ def __init__(
319319
self.temp_dir.mkdir(parents=True, exist_ok=True)
320320
self.expected_plots: dict[bytes32, Path] = {}
321321
self.created_plots: int = 0
322+
self.created_plots2: int = 0
322323
self.total_result = PlotRefreshResult()
323324

324325
def test_callback(event: PlotRefreshEvents, update_result: PlotRefreshResult) -> None:
@@ -338,6 +339,29 @@ def test_callback(event: PlotRefreshEvents, update_result: PlotRefreshResult) ->
338339
assert self.total_result.processed == update_result.processed
339340
assert self.total_result.duration == update_result.duration
340341
assert update_result.remaining == 0
342+
343+
expected_plots: set[str] = set()
344+
found_plots: set[str] = set()
345+
if len(self.plot_manager.plots) != len(self.expected_plots):
346+
for pid, filename in self.expected_plots.items():
347+
expected_plots.add(filename.name)
348+
for filename, _ in self.plot_manager.plots.items():
349+
found_plots.add(filename.name)
350+
print(f"directory: {self.plot_dir}")
351+
print(f"expected: {len(expected_plots)}")
352+
for f in expected_plots:
353+
print(f)
354+
print(f"plot manager: {len(found_plots)}")
355+
for f in found_plots:
356+
print(f)
357+
diff = found_plots.difference(expected_plots)
358+
print(f"found unexpected: {len(diff)}")
359+
for f in diff:
360+
print(f)
361+
diff = expected_plots.difference(found_plots)
362+
print(f"not found: {len(diff)}")
363+
for f in diff:
364+
print(f)
341365
assert len(self.plot_manager.plots) == len(self.expected_plots)
342366

343367
self.plot_manager: PlotManager = PlotManager(
@@ -498,11 +522,25 @@ async def setup_plots(
498522
num_og_plots: int = 15,
499523
num_pool_plots: int = 5,
500524
num_non_keychain_plots: int = 3,
525+
num_v2_plots: int = 320,
501526
plot_size: int = 20,
502527
bitfield: bool = True,
503528
) -> bool:
529+
# we have 20 v1 plots, each about 16.6 MB
530+
# in order to balance that out with v2 plots, we need approximately the
531+
# same size on disk. A v2 k-18 plot is expected to be about 1 MB, so we
532+
# need about 16 times more v2 plots, i.e. 320.
533+
# Additionally, the current reference plots are quite a bit larger than
534+
# the expected final plots. So until we have the optimized plot format
535+
# completed, the actual disk space for the v2 plots will be larger.
536+
print(
537+
f"setup_plots({num_og_plots}, {num_pool_plots}, "
538+
f"{num_non_keychain_plots}, {num_v2_plots}) "
539+
f"plot-dir: {self.plot_dir}"
540+
)
504541
self.add_plot_directory(self.plot_dir)
505542
assert self.created_plots == 0
543+
assert self.created_plots2 == 0
506544
existing_plots: bool = True
507545
# OG Plots
508546
for i in range(num_og_plots):
@@ -525,6 +563,11 @@ async def setup_plots(
525563
)
526564
if plot.new_plot:
527565
existing_plots = False
566+
# v2 plots
567+
for i in range(num_v2_plots):
568+
plot = await self.new_plot2(plot_size=18)
569+
if plot.new_plot:
570+
existing_plots = False
528571
await self.refresh_plots()
529572
assert len(self.plot_manager.plots) == len(self.expected_plots)
530573
return existing_plots
@@ -604,6 +647,48 @@ async def new_plot(
604647
shutil.rmtree(self.temp_dir, ignore_errors=True)
605648
sys.exit(1)
606649

650+
async def new_plot2(
651+
self,
652+
path: Optional[Path] = None,
653+
exclude_plots: bool = False,
654+
plot_size: int = 18,
655+
) -> BlockToolsNewPlotResult:
656+
final_dir = self.plot_dir
657+
if path is not None:
658+
final_dir = path
659+
final_dir.mkdir(parents=True, exist_ok=True)
660+
661+
# No datetime in the filename, to get deterministic filenames and not re-plot
662+
created, existed = await create_v2_plots(
663+
final_dir=Path(final_dir),
664+
size=plot_size,
665+
pool_ph=self.pool_ph,
666+
farmer_pk=self.farmer_pk,
667+
use_datetime=False,
668+
test_private_keys=[AugSchemeMPL.key_gen(std_hash(self.created_plots2.to_bytes(2, "big")))],
669+
)
670+
self.created_plots2 += 1
671+
672+
plot_id_new: Optional[bytes32] = None
673+
path_new: Optional[Path] = None
674+
new_plot: bool = True
675+
676+
if len(created):
677+
assert len(existed) == 0
678+
plot_id_new, path_new = next(iter(created.items()))
679+
680+
if len(existed):
681+
assert len(created) == 0
682+
plot_id_new, path_new = next(iter(existed.items()))
683+
new_plot = False
684+
assert plot_id_new is not None
685+
assert path_new is not None
686+
687+
if not exclude_plots:
688+
self.expected_plots[plot_id_new] = path_new
689+
690+
return BlockToolsNewPlotResult(plot_id_new, new_plot)
691+
607692
async def refresh_plots(self) -> None:
608693
self.plot_manager.refresh_parameter = replace(
609694
self.plot_manager.refresh_parameter, batch_size=uint32(4 if len(self.expected_plots) % 3 == 0 else 3)
@@ -2080,6 +2165,7 @@ async def create_block_tools_async(
20802165
num_og_plots: int = 15,
20812166
num_pool_plots: int = 5,
20822167
num_non_keychain_plots: int = 3,
2168+
num_v2_plots: int = 320,
20832169
) -> BlockTools:
20842170
global create_block_tools_async_count
20852171
create_block_tools_async_count += 1
@@ -2090,6 +2176,7 @@ async def create_block_tools_async(
20902176
num_og_plots=num_og_plots,
20912177
num_pool_plots=num_pool_plots,
20922178
num_non_keychain_plots=num_non_keychain_plots,
2179+
num_v2_plots=num_v2_plots,
20932180
)
20942181

20952182
return bt

chia/simulator/start_simulator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ async def async_main(
117117
plot_dir=plot_dir,
118118
)
119119
await bt.setup_keys(fingerprint=fingerprint, reward_ph=farming_puzzle_hash)
120-
await bt.setup_plots(num_og_plots=PLOTS, num_pool_plots=0, num_non_keychain_plots=0, plot_size=PLOT_SIZE)
120+
await bt.setup_plots(
121+
num_og_plots=PLOTS, num_pool_plots=0, num_non_keychain_plots=0, plot_size=PLOT_SIZE, num_v2_plots=PLOTS
122+
)
121123
# Everything after this is not simulator specific, excluding the if test_mode.
122124
initialize_logging(
123125
service_name=SERVICE_NAME,

chia/types/blockchain_format/proof_of_space.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ def make_pos(
5959

6060
def get_plot_id(pos: ProofOfSpace) -> bytes32:
6161
assert pos.pool_public_key is None or pos.pool_contract_puzzle_hash is None
62+
63+
plot_size = pos.size()
64+
if plot_size.size_v2 is not None:
65+
assert pos.pool_contract_puzzle_hash
66+
# v2 plots have a fixed k-size, so we store the *strength* in this field
67+
# instead
68+
return calculate_plot_id_v2(pos.pool_contract_puzzle_hash, pos.plot_public_key, uint8(plot_size.size_v2))
69+
6270
if pos.pool_public_key is None:
6371
assert pos.pool_contract_puzzle_hash is not None
6472
return calculate_plot_id_ph(pos.pool_contract_puzzle_hash, pos.plot_public_key)
@@ -203,6 +211,10 @@ def calculate_plot_id_ph(
203211
return std_hash(bytes(pool_contract_puzzle_hash) + bytes(plot_public_key))
204212

205213

214+
def calculate_plot_id_v2(pool_contract_puzzle_hash: bytes32, plot_public_key: G1Element, strength: uint8) -> bytes32:
215+
return std_hash(bytes(pool_contract_puzzle_hash) + bytes(plot_public_key) + strength.stream_to_bytes())
216+
217+
206218
def generate_taproot_sk(local_pk: G1Element, farmer_pk: G1Element) -> PrivateKey:
207219
taproot_message: bytes = bytes(local_pk + farmer_pk) + bytes(local_pk) + bytes(farmer_pk)
208220
taproot_hash: bytes32 = std_hash(taproot_message)

0 commit comments

Comments
 (0)