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.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty
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.util.errors import Err
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
if db_wrapper.db_version == 1:
@@ -20,7 +23,7 @@ async def check_block_store_invariant(bc: Blockchain) -> None:
in_chain = set()
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:
rows = await cursor.fetchall()
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_header_validation import validate_unfinished_header_block
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_mmr import BlockchainMMRManager
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_generator import get_block_generator
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.vdf import VDFInfo
from chia.types.generator_types import BlockGenerator
@@ -105,7 +105,7 @@ class Blockchain:
# Unspent Store
coin_store: CoinStoreProtocol
# Store
block_store: BlockStore
block_store: BlockStoreProtocol
mmr_manager: MMRManagerProtocol
# Used to verify blocks in parallel
pool: Executor
@@ -124,7 +124,7 @@ class Blockchain:
@staticmethod
async def create(
coin_store: CoinStoreProtocol,
block_store: BlockStore,
block_store: BlockStoreProtocol,
height_map: BlockHeightMap,
consensus_constants: ConsensusConstants,
pool: Executor,
+13 -4
View File
@@ -3,15 +3,17 @@ from __future__ import annotations
import dataclasses
import logging
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 zstd
from chia_rs import BlockRecord, FullBlock, SubEpochChallengeSegment, SubEpochSegments
from chia_rs.sized_bytes import bytes32
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.util.batches import to_batches
from chia.util.db_wrapper import DBWrapper2, execute_fetchone
@@ -38,6 +40,9 @@ def decompress_blob(block_bytes: bytes) -> bytes:
@typing_extensions.final
@dataclasses.dataclass
class BlockStore:
if TYPE_CHECKING:
_protocol_check: ClassVar[BlockStoreProtocol] = cast("BlockStore", None)
block_cache: LRUCache[bytes32, FullBlock]
db_wrapper: DBWrapper2
ses_challenge_cache: LRUCache[bytes32, list[SubEpochChallengeSegment]]
@@ -191,8 +196,12 @@ class BlockStore:
return challenge_segments
return None
def transaction(self) -> AbstractAsyncContextManager[aiosqlite.Connection]:
return self.db_wrapper.writer()
@asynccontextmanager
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:
return self.block_cache.get(header_hash)
-1
View File
@@ -22,7 +22,6 @@ path = "chia.consensus"
depends_on = [
"chia.types",
"chia.util",
{ path = "chia.full_node", deprecated = false },
]
[[modules]]