mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
67cdfcda41
* build(deps): bump ruff from 0.15.21 to 0.16.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.21 to 0.16.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.21...0.16.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.16.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * affect changes * Restrict formtting to chia/tools/benchmarks * Fix generated protocol files * more noqa * network protocol files --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matt <quexington@gmail.com>
170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
"""
|
|
Persistent cache for expensive plot operations: get_qualities_for_challenge(),
|
|
get_full_proof(), and solve_proof().
|
|
|
|
The cache is stored in the plots directory as `.plot_cache.pickle` and is keyed
|
|
by (plot_id, challenge) for qualities and full proofs, and by
|
|
(partial_proof_bytes, plot_id) for solve_proof.
|
|
|
|
Call install() to monkeypatch the prover classes and solve_proof. The cache is
|
|
loaded from disk on first install and saved on process exit.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import atexit
|
|
import logging
|
|
import pickle # ruff: ignore[suspicious-pickle-import]
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from chia_rs import PartialProof
|
|
from chia_rs import solve_proof as _orig_solve_proof
|
|
from chia_rs.sized_bytes import bytes32
|
|
from filelock import FileLock
|
|
|
|
from chia.plotting.prover import QualityProtocol, V1Prover, V1Quality, V2Prover, V2Quality
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
CACHE_FILENAME = ".plot_cache.pickle"
|
|
|
|
# (plot_id, challenge) -> list of serialized quality data
|
|
# V1: each entry is 32 bytes (quality bytes32)
|
|
# V2: each entry is serialized PartialProof bytes
|
|
_qualities: dict[tuple[bytes, bytes], list[bytes]] = {}
|
|
|
|
# (plot_id, challenge, index) -> proof bytes
|
|
_full_proofs: dict[tuple[bytes, bytes, int], bytes] = {}
|
|
|
|
# (partial_proof_bytes, plot_id) -> proof bytes
|
|
_solve_proofs: dict[tuple[bytes, bytes], bytes] = {}
|
|
|
|
_installed: bool = False
|
|
_cache_path: Path | None = None
|
|
_hits: int = 0
|
|
_misses: int = 0
|
|
|
|
|
|
def _load_from_disk(path: Path) -> tuple[dict[Any, Any], dict[Any, Any], dict[Any, Any]]:
|
|
if not path.exists():
|
|
return {}, {}, {}
|
|
try:
|
|
data = pickle.loads(path.read_bytes()) # ruff: ignore[suspicious-pickle-usage]
|
|
if isinstance(data, tuple) and len(data) == 3:
|
|
return data[0], data[1], data[2]
|
|
except Exception:
|
|
log.exception("plot_cache: failed to read existing cache for merge")
|
|
return {}, {}, {}
|
|
|
|
|
|
def _save() -> None:
|
|
total_lookups = _hits + _misses
|
|
hit_pct = f"{_hits * 100 / total_lookups:.1f}%" if total_lookups > 0 else "n/a"
|
|
if _misses > 0 and _cache_path is not None:
|
|
try:
|
|
with FileLock(str(_cache_path) + ".lock"):
|
|
disk_q, disk_fp, disk_sp = _load_from_disk(_cache_path)
|
|
disk_q.update(_qualities)
|
|
disk_fp.update(_full_proofs)
|
|
disk_sp.update(_solve_proofs)
|
|
data = (disk_q, disk_fp, disk_sp)
|
|
_cache_path.write_bytes(pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL))
|
|
total = len(disk_q) + len(disk_fp) + len(disk_sp)
|
|
print(f"plot_cache: saved {total} entries to {_cache_path}")
|
|
except Exception:
|
|
log.exception("plot_cache: failed to save")
|
|
if total_lookups > 0:
|
|
print(f"plot_cache: hits={_hits} misses={_misses} hit_rate={hit_pct}")
|
|
|
|
|
|
def load(plot_dir: Path) -> None:
|
|
global _cache_path
|
|
cache_path = plot_dir / CACHE_FILENAME
|
|
if not plot_dir.exists():
|
|
return
|
|
_cache_path = cache_path
|
|
try:
|
|
with FileLock(str(_cache_path) + ".lock"):
|
|
disk_q, disk_fp, disk_sp = _load_from_disk(_cache_path)
|
|
except Exception:
|
|
log.exception("plot_cache: failed to acquire lock for loading")
|
|
return
|
|
_qualities.update(disk_q)
|
|
_full_proofs.update(disk_fp)
|
|
_solve_proofs.update(disk_sp)
|
|
total = len(_qualities) + len(_full_proofs) + len(_solve_proofs)
|
|
if total > 0:
|
|
print(f"plot_cache: loaded {total} entries from {_cache_path}")
|
|
|
|
|
|
def cached_solve_proof(partial_proof: PartialProof, plot_id: bytes32, strength: int, k: int, testnet: bool) -> bytes:
|
|
global _hits, _misses
|
|
key = (partial_proof.to_bytes(), bytes(plot_id))
|
|
cached = _solve_proofs.get(key)
|
|
if cached is not None:
|
|
_hits += 1
|
|
return cached
|
|
result = _orig_solve_proof(partial_proof, plot_id, strength, k, testnet)
|
|
_solve_proofs[key] = result
|
|
_misses += 1
|
|
return result
|
|
|
|
|
|
def install(plot_dir: Path) -> None:
|
|
global _installed
|
|
if _installed:
|
|
return
|
|
_installed = True
|
|
|
|
load(plot_dir)
|
|
|
|
orig_v1_quals = V1Prover.get_qualities_for_challenge
|
|
orig_v2_quals = V2Prover.get_qualities_for_challenge
|
|
orig_v1_proof = V1Prover.get_full_proof
|
|
|
|
def v1_qualities(self: V1Prover, challenge: bytes32) -> list[QualityProtocol]:
|
|
global _hits, _misses
|
|
key = (bytes(self.get_id()), bytes(challenge))
|
|
cached = _qualities.get(key)
|
|
if cached is not None:
|
|
_hits += 1
|
|
return [V1Quality(bytes32(q)) for q in cached]
|
|
result = orig_v1_quals(self, challenge)
|
|
_qualities[key] = [bytes(q.get_string()) for q in result]
|
|
_misses += 1
|
|
return result
|
|
|
|
def v2_qualities(self: V2Prover, challenge: bytes32) -> list[QualityProtocol]:
|
|
global _hits, _misses
|
|
key = (bytes(self.get_id()), bytes(challenge))
|
|
cached = _qualities.get(key)
|
|
if cached is not None:
|
|
_hits += 1
|
|
return [V2Quality(PartialProof.from_bytes(q), self.get_strength()) for q in cached]
|
|
result = orig_v2_quals(self, challenge)
|
|
_qualities[key] = [q.get_partial_proof().to_bytes() for q in result] # type: ignore[attr-defined]
|
|
_misses += 1
|
|
return result
|
|
|
|
def v1_full_proof(self: V1Prover, challenge: bytes32, index: int, parallel_read: bool = True) -> bytes:
|
|
global _hits, _misses
|
|
key = (bytes(self.get_id()), bytes(challenge), index)
|
|
cached = _full_proofs.get(key)
|
|
if cached is not None:
|
|
_hits += 1
|
|
return cached
|
|
result = orig_v1_proof(self, challenge, index, parallel_read)
|
|
_full_proofs[key] = result
|
|
_misses += 1
|
|
return result
|
|
|
|
V1Prover.get_qualities_for_challenge = v1_qualities # type: ignore[method-assign]
|
|
V2Prover.get_qualities_for_challenge = v2_qualities # type: ignore[method-assign]
|
|
V1Prover.get_full_proof = v1_full_proof # type: ignore[method-assign]
|
|
|
|
setattr(__import__("chia.simulator.block_tools", fromlist=["solve_proof"]), "solve_proof", cached_solve_proof)
|
|
setattr(__import__("chia.solver.solver", fromlist=["solve_proof"]), "solve_proof", cached_solve_proof)
|
|
|
|
atexit.register(_save)
|