mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-09-05 02:24:21 -05:00
* plotting|tests: Extend the lifetime of plot data cache entries * plotting|tests: Add `prover` and `farmer_public_key` to the cache * plotting: Missing type hints in `Cache` and `CacheEntry` * setup: Bump `chiapos` to 1.0.10 * plotting: Move cache classes into `chia.plotting.cache` * plotting: Introduce `CacheEntry.from_disk_prover` * Make `Cache` a dataclass * `staticmethod` -> `classmethod` * Store cache data as `bytes` in `DiskCache`
177 lines
5.9 KiB
Python
177 lines
5.9 KiB
Python
import logging
|
|
import time
|
|
import traceback
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Dict, ItemsView, KeysView, List, Optional, Tuple, ValuesView
|
|
|
|
from blspy import G1Element
|
|
from chiapos import DiskProver
|
|
|
|
from chia.plotting.util import parse_plot_info
|
|
from chia.types.blockchain_format.proof_of_space import ProofOfSpace
|
|
from chia.types.blockchain_format.sized_bytes import bytes32
|
|
from chia.util.ints import uint16, uint64
|
|
from chia.util.path import mkdir
|
|
from chia.util.streamable import Streamable, streamable
|
|
from chia.wallet.derive_keys import master_sk_to_local_sk
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
CURRENT_VERSION: int = 1
|
|
|
|
|
|
@streamable
|
|
@dataclass(frozen=True)
|
|
class DiskCacheEntry(Streamable):
|
|
prover_data: bytes
|
|
farmer_public_key: G1Element
|
|
pool_public_key: Optional[G1Element]
|
|
pool_contract_puzzle_hash: Optional[bytes32]
|
|
plot_public_key: G1Element
|
|
last_use: uint64
|
|
|
|
|
|
@streamable
|
|
@dataclass(frozen=True)
|
|
class DiskCache(Streamable):
|
|
version: uint16
|
|
data: bytes
|
|
|
|
|
|
@streamable
|
|
@dataclass(frozen=True)
|
|
class CacheDataV1(Streamable):
|
|
entries: List[Tuple[str, DiskCacheEntry]]
|
|
|
|
|
|
@dataclass
|
|
class CacheEntry:
|
|
prover: DiskProver
|
|
farmer_public_key: G1Element
|
|
pool_public_key: Optional[G1Element]
|
|
pool_contract_puzzle_hash: Optional[bytes32]
|
|
plot_public_key: G1Element
|
|
last_use: float
|
|
|
|
@classmethod
|
|
def from_disk_prover(cls, prover: DiskProver) -> "CacheEntry":
|
|
(
|
|
pool_public_key_or_puzzle_hash,
|
|
farmer_public_key,
|
|
local_master_sk,
|
|
) = parse_plot_info(prover.get_memo())
|
|
|
|
pool_public_key: Optional[G1Element] = None
|
|
pool_contract_puzzle_hash: Optional[bytes32] = None
|
|
if isinstance(pool_public_key_or_puzzle_hash, G1Element):
|
|
pool_public_key = pool_public_key_or_puzzle_hash
|
|
else:
|
|
assert isinstance(pool_public_key_or_puzzle_hash, bytes32)
|
|
pool_contract_puzzle_hash = pool_public_key_or_puzzle_hash
|
|
|
|
local_sk = master_sk_to_local_sk(local_master_sk)
|
|
|
|
plot_public_key: G1Element = ProofOfSpace.generate_plot_public_key(
|
|
local_sk.get_g1(), farmer_public_key, pool_contract_puzzle_hash is not None
|
|
)
|
|
|
|
return cls(prover, farmer_public_key, pool_public_key, pool_contract_puzzle_hash, plot_public_key, time.time())
|
|
|
|
def bump_last_use(self) -> None:
|
|
self.last_use = time.time()
|
|
|
|
def expired(self, expiry_seconds: int) -> bool:
|
|
return time.time() - self.last_use > expiry_seconds
|
|
|
|
|
|
@dataclass
|
|
class Cache:
|
|
_path: Path
|
|
_changed: bool = False
|
|
_data: Dict[Path, CacheEntry] = field(default_factory=dict)
|
|
expiry_seconds: int = 7 * 24 * 60 * 60 # Keep the cache entries alive for 7 days after its last access
|
|
|
|
def __post_init__(self) -> None:
|
|
mkdir(self._path.parent)
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._data)
|
|
|
|
def update(self, path: Path, entry: CacheEntry) -> None:
|
|
self._data[path] = entry
|
|
self._changed = True
|
|
|
|
def remove(self, cache_keys: List[Path]) -> None:
|
|
for key in cache_keys:
|
|
if key in self._data:
|
|
del self._data[key]
|
|
self._changed = True
|
|
|
|
def save(self) -> None:
|
|
try:
|
|
disk_cache_entries: Dict[str, DiskCacheEntry] = {
|
|
str(path): DiskCacheEntry(
|
|
bytes(cache_entry.prover),
|
|
cache_entry.farmer_public_key,
|
|
cache_entry.pool_public_key,
|
|
cache_entry.pool_contract_puzzle_hash,
|
|
cache_entry.plot_public_key,
|
|
uint64(int(cache_entry.last_use)),
|
|
)
|
|
for path, cache_entry in self.items()
|
|
}
|
|
cache_data: CacheDataV1 = CacheDataV1(
|
|
[(plot_id, cache_entry) for plot_id, cache_entry in disk_cache_entries.items()]
|
|
)
|
|
disk_cache: DiskCache = DiskCache(uint16(CURRENT_VERSION), bytes(cache_data))
|
|
serialized: bytes = bytes(disk_cache)
|
|
self._path.write_bytes(serialized)
|
|
self._changed = False
|
|
log.info(f"Saved {len(serialized)} bytes of cached data")
|
|
except Exception as e:
|
|
log.error(f"Failed to save cache: {e}, {traceback.format_exc()}")
|
|
|
|
def load(self) -> None:
|
|
try:
|
|
serialized = self._path.read_bytes()
|
|
log.info(f"Loaded {len(serialized)} bytes of cached data")
|
|
stored_cache: DiskCache = DiskCache.from_bytes(serialized)
|
|
if stored_cache.version == CURRENT_VERSION:
|
|
cache_data: CacheDataV1 = CacheDataV1.from_bytes(stored_cache.data)
|
|
self._data = {
|
|
Path(path): CacheEntry(
|
|
DiskProver.from_bytes(cache_entry.prover_data),
|
|
cache_entry.farmer_public_key,
|
|
cache_entry.pool_public_key,
|
|
cache_entry.pool_contract_puzzle_hash,
|
|
cache_entry.plot_public_key,
|
|
float(cache_entry.last_use),
|
|
)
|
|
for path, cache_entry in cache_data.entries
|
|
}
|
|
else:
|
|
raise ValueError(f"Invalid cache version {stored_cache.version}. Expected version {CURRENT_VERSION}.")
|
|
except FileNotFoundError:
|
|
log.debug(f"Cache {self._path} not found")
|
|
except Exception as e:
|
|
log.error(f"Failed to load cache: {e}, {traceback.format_exc()}")
|
|
|
|
def keys(self) -> KeysView[Path]:
|
|
return self._data.keys()
|
|
|
|
def values(self) -> ValuesView[CacheEntry]:
|
|
return self._data.values()
|
|
|
|
def items(self) -> ItemsView[Path, CacheEntry]:
|
|
return self._data.items()
|
|
|
|
def get(self, path: Path) -> Optional[CacheEntry]:
|
|
return self._data.get(path)
|
|
|
|
def changed(self) -> bool:
|
|
return self._changed
|
|
|
|
def path(self) -> Path:
|
|
return self._path
|