Add BlockStoreProtocol; drop consensus dependency on chia.full_node (#21169)

* Add BlockStoreProtocol; drop consensus dependency on chia.full_node

Co-authored-by: Cursor <cursoragent@cursor.com>

* Assert isinstance directly on bc.block_store

Review feedback: drop the intermediate block_store assignment; mypy
narrows the attribute expression after the isinstance assert.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Richard Kiss
2026-07-27 12:40:06 -07:00
committed by GitHub
co-authored by Cursor
parent 1d0bb18f9a
commit b966c5c96b
5 changed files with 87 additions and 9 deletions
@@ -8,11 +8,14 @@ from chia.consensus.block_body_validation import ForkInfo
from chia.consensus.blockchain import AddBlockResult, Blockchain from chia.consensus.blockchain import AddBlockResult, Blockchain
from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty
from chia.consensus.multiprocess_validation import PreValidationResult, pre_validate_block from chia.consensus.multiprocess_validation import PreValidationResult, pre_validate_block
from chia.full_node.block_store import BlockStore
from chia.types.validation_state import ValidationState from chia.types.validation_state import ValidationState
from chia.util.errors import Err from chia.util.errors import Err
async def check_block_store_invariant(bc: Blockchain) -> None: async def check_block_store_invariant(bc: Blockchain) -> None:
# this checks sqlite-level invariants, so it needs the concrete store
assert isinstance(bc.block_store, BlockStore)
db_wrapper = bc.block_store.db_wrapper db_wrapper = bc.block_store.db_wrapper
if db_wrapper.db_version == 1: if db_wrapper.db_version == 1:
@@ -20,7 +23,7 @@ async def check_block_store_invariant(bc: Blockchain) -> None:
in_chain = set() in_chain = set()
max_height = -1 max_height = -1
async with bc.block_store.transaction() as conn: async with db_wrapper.writer() as conn:
async with conn.execute("SELECT height, in_main_chain FROM full_blocks") as cursor: async with conn.execute("SELECT height, in_main_chain FROM full_blocks") as cursor:
rows = await cursor.fetchall() rows = await cursor.fetchall()
for row in rows: for row in rows:
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
from contextlib import AbstractAsyncContextManager
from typing import Protocol
from chia_rs import BlockRecord, FullBlock, SubEpochChallengeSegment
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32
class BlockStoreProtocol(Protocol):
"""
The block store interface used by `chia.consensus`.
This is a substitute for importing from chia.full_node.block_store directly.
The concrete `BlockStore` has a larger surface (block blobs by range,
compactification queries, etc.), but those methods serve peer sync and
RPCs, not consensus, so they are not part of this protocol.
"""
async def add_full_block(self, header_hash: bytes32, block: FullBlock, block_record: BlockRecord) -> None: ...
async def get_block_record(self, header_hash: bytes32) -> BlockRecord | None: ...
async def get_block_records_by_hash(self, header_hashes: list[bytes32]) -> list[BlockRecord]: ...
async def get_block_records_in_range(self, start: int, stop: int) -> dict[bytes32, BlockRecord]: ...
async def get_block_records_close_to_peak(
self, blocks_n: int
) -> tuple[dict[bytes32, BlockRecord], bytes32 | None]: ...
async def get_prev_hash(self, header_hash: bytes32) -> bytes32: ...
async def get_full_block(self, header_hash: bytes32) -> FullBlock | None: ...
async def get_blocks_by_hash(self, header_hashes: list[bytes32]) -> list[FullBlock]: ...
async def get_generator(self, header_hash: bytes32) -> bytes | None: ...
async def get_generators_at(self, heights: set[uint32]) -> dict[uint32, bytes]: ...
async def rollback(self, height: int) -> None: ...
async def set_in_chain(self, header_hashes: list[tuple[bytes32]]) -> None: ...
async def set_peak(self, header_hash: bytes32) -> None: ...
def transaction(self) -> AbstractAsyncContextManager[None]:
"""
A write transaction scope. Store methods called within the scope are
atomic. The context manager deliberately yields None: the underlying
database connection is an implementation detail of the store.
"""
...
def get_block_from_cache(self, header_hash: bytes32) -> FullBlock | None: ...
def rollback_cache_block(self, header_hash: bytes32) -> None: ...
async def persist_sub_epoch_challenge_segments(
self, ses_block_hash: bytes32, segments: list[SubEpochChallengeSegment]
) -> None: ...
async def get_sub_epoch_challenge_segments(
self, ses_block_hash: bytes32
) -> list[SubEpochChallengeSegment] | None: ...
+3 -3
View File
@@ -28,6 +28,7 @@ from chia_rs.sized_ints import uint16, uint32, uint64, uint128
from chia.consensus.block_body_validation import ForkInfo, validate_block_body from chia.consensus.block_body_validation import ForkInfo, validate_block_body
from chia.consensus.block_header_validation import validate_unfinished_header_block from chia.consensus.block_header_validation import validate_unfinished_header_block
from chia.consensus.block_height_map import BlockHeightMap from chia.consensus.block_height_map import BlockHeightMap
from chia.consensus.block_store_protocol import BlockStoreProtocol
from chia.consensus.blockchain_interface import MMRManagerProtocol from chia.consensus.blockchain_interface import MMRManagerProtocol
from chia.consensus.blockchain_mmr import BlockchainMMRManager from chia.consensus.blockchain_mmr import BlockchainMMRManager
from chia.consensus.coin_store_protocol import CoinStoreProtocol from chia.consensus.coin_store_protocol import CoinStoreProtocol
@@ -38,7 +39,6 @@ from chia.consensus.generator_tools import get_block_header
from chia.consensus.get_block_challenge import pre_sp_tx_block_height from chia.consensus.get_block_challenge import pre_sp_tx_block_height
from chia.consensus.get_block_generator import get_block_generator from chia.consensus.get_block_generator import get_block_generator
from chia.consensus.multiprocess_validation import PreValidationResult from chia.consensus.multiprocess_validation import PreValidationResult
from chia.full_node.block_store import BlockStore
from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.vdf import VDFInfo from chia.types.blockchain_format.vdf import VDFInfo
from chia.types.generator_types import BlockGenerator from chia.types.generator_types import BlockGenerator
@@ -105,7 +105,7 @@ class Blockchain:
# Unspent Store # Unspent Store
coin_store: CoinStoreProtocol coin_store: CoinStoreProtocol
# Store # Store
block_store: BlockStore block_store: BlockStoreProtocol
mmr_manager: MMRManagerProtocol mmr_manager: MMRManagerProtocol
# Used to verify blocks in parallel # Used to verify blocks in parallel
pool: Executor pool: Executor
@@ -124,7 +124,7 @@ class Blockchain:
@staticmethod @staticmethod
async def create( async def create(
coin_store: CoinStoreProtocol, coin_store: CoinStoreProtocol,
block_store: BlockStore, block_store: BlockStoreProtocol,
height_map: BlockHeightMap, height_map: BlockHeightMap,
consensus_constants: ConsensusConstants, consensus_constants: ConsensusConstants,
pool: Executor, pool: Executor,
+13 -4
View File
@@ -3,15 +3,17 @@ from __future__ import annotations
import dataclasses import dataclasses
import logging import logging
import sqlite3 import sqlite3
from contextlib import AbstractAsyncContextManager from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, ClassVar, cast
import aiosqlite
import typing_extensions import typing_extensions
import zstd import zstd
from chia_rs import BlockRecord, FullBlock, SubEpochChallengeSegment, SubEpochSegments from chia_rs import BlockRecord, FullBlock, SubEpochChallengeSegment, SubEpochSegments
from chia_rs.sized_bytes import bytes32 from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32 from chia_rs.sized_ints import uint32
from chia.consensus.block_store_protocol import BlockStoreProtocol
from chia.full_node.full_block_utils import GeneratorBlockInfo, block_info_from_block, generator_from_block from chia.full_node.full_block_utils import GeneratorBlockInfo, block_info_from_block, generator_from_block
from chia.util.batches import to_batches from chia.util.batches import to_batches
from chia.util.db_wrapper import DBWrapper2, execute_fetchone from chia.util.db_wrapper import DBWrapper2, execute_fetchone
@@ -38,6 +40,9 @@ def decompress_blob(block_bytes: bytes) -> bytes:
@typing_extensions.final @typing_extensions.final
@dataclasses.dataclass @dataclasses.dataclass
class BlockStore: class BlockStore:
if TYPE_CHECKING:
_protocol_check: ClassVar[BlockStoreProtocol] = cast("BlockStore", None)
block_cache: LRUCache[bytes32, FullBlock] block_cache: LRUCache[bytes32, FullBlock]
db_wrapper: DBWrapper2 db_wrapper: DBWrapper2
ses_challenge_cache: LRUCache[bytes32, list[SubEpochChallengeSegment]] ses_challenge_cache: LRUCache[bytes32, list[SubEpochChallengeSegment]]
@@ -191,8 +196,12 @@ class BlockStore:
return challenge_segments return challenge_segments
return None return None
def transaction(self) -> AbstractAsyncContextManager[aiosqlite.Connection]: @asynccontextmanager
return self.db_wrapper.writer() async def transaction(self) -> AsyncIterator[None]:
# the database connection is deliberately not exposed: callers only
# get an atomic scope, not access to the underlying database
async with self.db_wrapper.writer():
yield
def get_block_from_cache(self, header_hash: bytes32) -> FullBlock | None: def get_block_from_cache(self, header_hash: bytes32) -> FullBlock | None:
return self.block_cache.get(header_hash) return self.block_cache.get(header_hash)
-1
View File
@@ -22,7 +22,6 @@ path = "chia.consensus"
depends_on = [ depends_on = [
"chia.types", "chia.types",
"chia.util", "chia.util",
{ path = "chia.full_node", deprecated = false },
] ]
[[modules]] [[modules]]