Skip to content

Commit 19a8329

Browse files
authored
Plotmanager v2 (#19832)
* prover protocol and v2Prover * format name * format * refactor filename * tests/raise unimplemented * add get_filename_str to mock * rename methods * rename * refactor * improve coverage * test from bytes
1 parent a8d7d32 commit 19a8329

File tree

9 files changed

+303
-29
lines changed

9 files changed

+303
-29
lines changed

chia/_tests/plot_sync/test_sync_simulated.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from chia.plot_sync.sender import Sender
2626
from chia.plot_sync.util import Constants
2727
from chia.plotting.manager import PlotManager
28+
from chia.plotting.prover import V1Prover
2829
from chia.plotting.util import PlotInfo
2930
from chia.protocols.harvester_protocol import PlotSyncError, PlotSyncResponse
3031
from chia.protocols.outbound_message import make_msg
@@ -68,17 +69,17 @@ async def run(
6869
initial: bool,
6970
) -> None:
7071
for plot_info in loaded:
71-
assert plot_info.prover.get_filename() not in self.plots
72+
assert Path(plot_info.prover.get_filename()) not in self.plots
7273
for plot_info in removed:
73-
assert plot_info.prover.get_filename() in self.plots
74+
assert Path(plot_info.prover.get_filename()) in self.plots
7475

7576
self.invalid = invalid
7677
self.keys_missing = keys_missing
7778
self.duplicates = duplicates
7879

79-
removed_paths: list[Path] = [p.prover.get_filename() for p in removed] if removed is not None else []
80-
invalid_dict: dict[Path, int] = {p.prover.get_filename(): 0 for p in self.invalid}
81-
keys_missing_set: set[Path] = {p.prover.get_filename() for p in self.keys_missing}
80+
removed_paths: list[Path] = [Path(p.prover.get_filename()) for p in removed] if removed is not None else []
81+
invalid_dict: dict[Path, int] = {Path(p.prover.get_filename()): 0 for p in self.invalid}
82+
keys_missing_set: set[Path] = {Path(p.prover.get_filename()) for p in self.keys_missing}
8283
duplicates_set: set[str] = {p.prover.get_filename() for p in self.duplicates}
8384

8485
# Inject invalid plots into `PlotManager` of the harvester so that the callback calls below can use them
@@ -122,9 +123,9 @@ async def sync_done() -> bool:
122123
await time_out_assert(60, sync_done)
123124

124125
for plot_info in loaded:
125-
self.plots[plot_info.prover.get_filename()] = plot_info
126+
self.plots[Path(plot_info.prover.get_filename())] = plot_info
126127
for plot_info in removed:
127-
del self.plots[plot_info.prover.get_filename()]
128+
del self.plots[Path(plot_info.prover.get_filename())]
128129

129130
def validate_plot_sync(self) -> None:
130131
assert len(self.plots) == len(self.plot_sync_receiver.plots())
@@ -284,7 +285,7 @@ def get_compression_level(self) -> uint8:
284285

285286
return [
286287
PlotInfo(
287-
prover=DiskProver(f"{x}", bytes32.random(seeded_random), 25 + x % 26),
288+
prover=V1Prover(DiskProver(f"{x}", bytes32.random(seeded_random), 25 + x % 26)),
288289
pool_public_key=None,
289290
pool_contract_puzzle_hash=None,
290291
plot_public_key=G1Element(),
@@ -416,16 +417,16 @@ async def test_sync_reset_cases(
416417
# Inject some data into `PlotManager` of the harvester so that we can validate the reset worked and triggered a
417418
# fresh sync of all available data of the plot manager
418419
for plot_info in plots[0:10]:
419-
test_data.plots[plot_info.prover.get_filename()] = plot_info
420+
test_data.plots[Path(plot_info.prover.get_filename())] = plot_info
420421
plot_manager.plots = test_data.plots
421422
test_data.invalid = plots[10:20]
422423
test_data.keys_missing = plots[20:30]
423424
test_data.plot_sync_receiver.simulate_error = simulate_error # type: ignore[attr-defined]
424425
sender: Sender = test_runner.test_data[0].plot_sync_sender
425426
started_sync_id: uint64 = uint64(0)
426427

427-
plot_manager.failed_to_open_filenames = {p.prover.get_filename(): 0 for p in test_data.invalid}
428-
plot_manager.no_key_filenames = {p.prover.get_filename() for p in test_data.keys_missing}
428+
plot_manager.failed_to_open_filenames = {Path(p.prover.get_filename()): 0 for p in test_data.invalid}
429+
plot_manager.no_key_filenames = {Path(p.prover.get_filename()) for p in test_data.keys_missing}
429430

430431
async def wait_for_reset() -> bool:
431432
assert started_sync_id != 0

chia/_tests/plotting/test_plot_manager.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
import pytest
1414
from chia_rs import G1Element
1515
from chia_rs.sized_ints import uint16, uint32
16+
from chiapos import DiskProver
1617

1718
from chia._tests.plotting.util import get_test_plots
1819
from chia._tests.util.misc import boolean_datacases
1920
from chia._tests.util.time_out_assert import time_out_assert
2021
from chia.plotting.cache import CURRENT_VERSION, CacheDataV1
2122
from chia.plotting.manager import Cache, PlotManager
23+
from chia.plotting.prover import V1Prover
2224
from chia.plotting.util import (
2325
PlotInfo,
2426
PlotRefreshEvents,
@@ -743,6 +745,20 @@ async def test_recursive_plot_scan(environment: Environment) -> None:
743745
await env.refresh_tester.run(expected_result)
744746

745747

748+
@pytest.mark.anyio
749+
async def test_disk_prover_from_bytes(environment: Environment):
750+
env: Environment = environment
751+
expected_result = PlotRefreshResult()
752+
expected_result.loaded = env.dir_1.plot_info_list() # type: ignore[assignment]
753+
expected_result.processed = len(env.dir_1)
754+
add_plot_directory(env.root_path, str(env.dir_1.path))
755+
await env.refresh_tester.run(expected_result)
756+
_, plot_info = next(iter(env.refresh_tester.plot_manager.plots.items()))
757+
recreated_prover = V1Prover(DiskProver.from_bytes(bytes(plot_info.prover)))
758+
assert recreated_prover.get_id() == plot_info.prover.get_id()
759+
assert recreated_prover.get_filename() == plot_info.prover.get_filename()
760+
761+
746762
@boolean_datacases(name="follow_links", false="no_follow", true="follow")
747763
@pytest.mark.anyio
748764
async def test_recursive_plot_scan_symlinks(environment: Environment, follow_links: bool) -> None:
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from __future__ import annotations
2+
3+
import tempfile
4+
from pathlib import Path
5+
from unittest.mock import MagicMock, patch
6+
7+
import pytest
8+
9+
from chia.plotting.prover import PlotVersion, V1Prover, V2Prover, get_prover_from_bytes, get_prover_from_file
10+
11+
12+
class TestProver:
13+
def test_v2_prover_init_with_nonexistent_file(self) -> None:
14+
prover = V2Prover("/nonexistent/path/test.plot2")
15+
assert prover.get_version() == PlotVersion.V2
16+
assert prover.get_filename() == "/nonexistent/path/test.plot2"
17+
18+
def test_v2_prover_get_size_raises_error(self) -> None:
19+
prover = V2Prover("/nonexistent/path/test.plot2")
20+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
21+
prover.get_size()
22+
23+
def test_v2_prover_get_memo_raises_error(self) -> None:
24+
prover = V2Prover("/nonexistent/path/test.plot2")
25+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
26+
prover.get_memo()
27+
28+
def test_v2_prover_get_compression_level_raises_error(self) -> None:
29+
prover = V2Prover("/nonexistent/path/test.plot2")
30+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
31+
prover.get_compression_level()
32+
33+
def test_v2_prover_get_id_raises_error(self) -> None:
34+
prover = V2Prover("/nonexistent/path/test.plot2")
35+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
36+
prover.get_id()
37+
38+
def test_v2_prover_get_qualities_for_challenge_raises_error(self) -> None:
39+
prover = V2Prover("/nonexistent/path/test.plot2")
40+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
41+
prover.get_qualities_for_challenge(b"challenge")
42+
43+
def test_v2_prover_get_full_proof_raises_error(self) -> None:
44+
prover = V2Prover("/nonexistent/path/test.plot2")
45+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
46+
prover.get_full_proof(b"challenge", 0)
47+
48+
def test_v2_prover_bytes_raises_error(self) -> None:
49+
prover = V2Prover("/nonexistent/path/test.plot2")
50+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
51+
bytes(prover)
52+
53+
def test_v2_prover_from_bytes_raises_error(self) -> None:
54+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
55+
V2Prover.from_bytes(b"test_data")
56+
57+
def test_get_prover_from_file(self) -> None:
58+
prover = get_prover_from_file("/nonexistent/path/test.plot2")
59+
assert prover.get_version() == PlotVersion.V2
60+
with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"):
61+
prover.get_size()
62+
63+
def test_get_prover_from_file_with_plot1_still_works(self) -> None:
64+
with tempfile.NamedTemporaryFile(suffix=".plot", delete=False) as f:
65+
temp_path = f.name
66+
try:
67+
with pytest.raises(Exception) as exc_info:
68+
get_prover_from_file(temp_path)
69+
assert not isinstance(exc_info.value, NotImplementedError)
70+
finally:
71+
Path(temp_path).unlink()
72+
73+
def test_unsupported_file_extension_raises_value_error(self) -> None:
74+
with pytest.raises(ValueError, match="Unsupported plot file"):
75+
get_prover_from_file("/nonexistent/path/test.txt")
76+
77+
78+
class TestV1Prover:
79+
def test_v1_prover_get_version(self) -> None:
80+
"""Test that V1Prover.get_version() returns PlotVersion.V1"""
81+
mock_disk_prover = MagicMock()
82+
prover = V1Prover(mock_disk_prover)
83+
assert prover.get_version() == PlotVersion.V1
84+
85+
86+
class TestGetProverFromBytes:
87+
def test_get_prover_from_bytes_v2_plot(self) -> None:
88+
with patch("chia.plotting.prover.V2Prover.from_bytes") as mock_v2_from_bytes:
89+
mock_prover = MagicMock()
90+
mock_v2_from_bytes.return_value = mock_prover
91+
result = get_prover_from_bytes("test.plot2", b"test_data")
92+
assert result == mock_prover
93+
94+
def test_get_prover_from_bytes_v1_plot(self) -> None:
95+
with patch("chia.plotting.prover.DiskProver") as mock_disk_prover_class:
96+
mock_disk_prover = MagicMock()
97+
mock_disk_prover_class.from_bytes.return_value = mock_disk_prover
98+
result = get_prover_from_bytes("test.plot", b"test_data")
99+
assert isinstance(result, V1Prover)
100+
101+
def test_get_prover_from_bytes_unsupported_extension(self) -> None:
102+
with pytest.raises(ValueError, match="Unsupported plot file"):
103+
get_prover_from_bytes("test.txt", b"test_data")

chia/harvester/harvester_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async def new_signage_point_harvester(
9797
loop = asyncio.get_running_loop()
9898

9999
def blocking_lookup(filename: Path, plot_info: PlotInfo) -> list[tuple[bytes32, ProofOfSpace]]:
100-
# Uses the DiskProver object to lookup qualities. This is a blocking call,
100+
# Uses the Prover object to lookup qualities. This is a blocking call,
101101
# so it should be run in a thread pool.
102102
try:
103103
plot_id = plot_info.prover.get_id()
@@ -218,7 +218,7 @@ def blocking_lookup(filename: Path, plot_info: PlotInfo) -> list[tuple[bytes32,
218218
async def lookup_challenge(
219219
filename: Path, plot_info: PlotInfo
220220
) -> tuple[Path, list[harvester_protocol.NewProofOfSpace]]:
221-
# Executes a DiskProverLookup in a thread pool, and returns responses
221+
# Executes a ProverLookup in a thread pool, and returns responses
222222
all_responses: list[harvester_protocol.NewProofOfSpace] = []
223223
if self.harvester._shut_down:
224224
return filename, []

chia/plotting/cache.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
from dataclasses import dataclass, field
88
from math import ceil
99
from pathlib import Path
10-
from typing import Optional
10+
from typing import TYPE_CHECKING, Optional
11+
12+
if TYPE_CHECKING:
13+
from chia.plotting.prover import ProverProtocol
1114

1215
from chia_rs import G1Element
1316
from chia_rs.sized_bytes import bytes32
1417
from chia_rs.sized_ints import uint16, uint64
15-
from chiapos import DiskProver
1618

19+
from chia.plotting.prover import get_prover_from_bytes
1720
from chia.plotting.util import parse_plot_info
1821
from chia.types.blockchain_format.proof_of_space import generate_plot_public_key
1922
from chia.util.streamable import Streamable, VersionedBlob, streamable
@@ -43,15 +46,15 @@ class CacheDataV1(Streamable):
4346

4447
@dataclass
4548
class CacheEntry:
46-
prover: DiskProver
49+
prover: ProverProtocol
4750
farmer_public_key: G1Element
4851
pool_public_key: Optional[G1Element]
4952
pool_contract_puzzle_hash: Optional[bytes32]
5053
plot_public_key: G1Element
5154
last_use: float
5255

5356
@classmethod
54-
def from_disk_prover(cls, prover: DiskProver) -> CacheEntry:
57+
def from_prover(cls, prover: ProverProtocol) -> CacheEntry:
5558
(
5659
pool_public_key_or_puzzle_hash,
5760
farmer_public_key,
@@ -149,8 +152,9 @@ def load(self) -> None:
149152
39: 44367,
150153
}
151154
for path, cache_entry in cache_data.entries:
155+
prover: ProverProtocol = get_prover_from_bytes(path, cache_entry.prover_data)
152156
new_entry = CacheEntry(
153-
DiskProver.from_bytes(cache_entry.prover_data),
157+
prover,
154158
cache_entry.farmer_public_key,
155159
cache_entry.pool_public_key,
156160
cache_entry.pool_contract_puzzle_hash,

chia/plotting/check_plots.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import Optional
1010

1111
from chia_rs import G1Element
12-
from chia_rs.sized_ints import uint32
12+
from chia_rs.sized_ints import uint8, uint32
1313
from chiapos import Verifier
1414

1515
from chia.plotting.manager import PlotManager
@@ -133,7 +133,7 @@ def check_plots(
133133
log.info("")
134134
log.info("")
135135
log.info(f"Starting to test each plot with {num} challenges each\n")
136-
total_good_plots: Counter[str] = Counter()
136+
total_good_plots: Counter[uint8] = Counter()
137137
total_size = 0
138138
bad_plots_list: list[Path] = []
139139

chia/plotting/manager.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
from typing import Any, Callable, Optional
1010

1111
from chia_rs import G1Element
12-
from chiapos import DiskProver, decompressor_context_queue
12+
from chiapos import decompressor_context_queue
1313

1414
from chia.consensus.pos_quality import UI_ACTUAL_SPACE_CONSTANT_FACTOR, _expected_plot_size
1515
from chia.plotting.cache import Cache, CacheEntry
16+
from chia.plotting.prover import get_prover_from_file
1617
from chia.plotting.util import (
1718
HarvestingMode,
1819
PlotInfo,
@@ -323,7 +324,7 @@ def process_file(file_path: Path) -> Optional[PlotInfo]:
323324
cache_entry = self.cache.get(file_path)
324325
cache_hit = cache_entry is not None
325326
if not cache_hit:
326-
prover = DiskProver(str(file_path))
327+
prover = get_prover_from_file(str(file_path))
327328

328329
log.debug(f"process_file {file_path!s}")
329330

@@ -343,7 +344,7 @@ def process_file(file_path: Path) -> Optional[PlotInfo]:
343344
)
344345
return None
345346

346-
cache_entry = CacheEntry.from_disk_prover(prover)
347+
cache_entry = CacheEntry.from_prover(prover)
347348
self.cache.update(file_path, cache_entry)
348349

349350
assert cache_entry is not None

0 commit comments

Comments
 (0)