feat: add LocalDataSource trait for block/bytecode fetching#135
Conversation
|
The PR currently has no labels, but based on the diff it should have the enhancement label. This PR introduces a new |
| Deadline(#[from] RpcDeadlineExceeded), | ||
|
|
||
| #[error(transparent)] | ||
| CodeVerification(CodeFetchError), |
There was a problem hiding this comment.
CodeVerification(CodeFetchError) couples the public error type to the RPC implementation. An embedded full-node impl that detects a bytecode hash mismatch has no natural CodeFetchError to construct — it must fall through to Provider(...) and lose the "verification failure" semantics. Consider replacing the inner type with a plain string or Box<dyn Error + Send + Sync>:
| CodeVerification(CodeFetchError), | |
| #[error("contract verification failed: {0}")] | |
| CodeVerification(Box<dyn std::error::Error + Send + Sync>), |
Then the From<CodeFetchError> conversion becomes:
CodeFetchError::VerificationFailure { .. } => {
Self::CodeVerification(Box::new(e))
}|
The overall design is sound — introducing Two issues to address before merging: 1. 2. nit: |
| /// Fetch the RPC-format block at `(number, hash)`. `hash` is authoritative; `number` is | ||
| /// for diagnostics. `deadline` is best-effort — local-DB impls may ignore it. | ||
| async fn block_by_hash( | ||
| &self, |
There was a problem hiding this comment.
This project targets nightly 2026-02-03, so async fn in traits is available natively — async-trait is unnecessary here. Using it adds a dependency and wraps every call in a Box<dyn Future> allocation.
| &self, | |
| pub trait LocalDataSource: Send + Sync + 'static { |
Dropping #[async_trait] on the trait and all impls (including the test mock) removes the dependency entirely.
|
|
||
| /// Fetch contract bytecode for a deduplicated list of codehashes. Each returned | ||
| /// bytecode's keccak must match the requested hash. | ||
| async fn codes_by_hashes( |
There was a problem hiding this comment.
The doc says "Each returned bytecode's keccak must match the requested hash" but this is a correctness invariant for stateless validation, not just a convention. Implementors that skip verification (e.g. a local DB impl that trusts its own storage) will silently break block validation without the pipeline detecting it. Consider asserting this at the call site or adding a default method that enforces it, rather than relying solely on the doc comment.
ReviewPR description vs. actual changesThe description says "ValidatorFetcher and ValidatorProcessor now hold
|
|
Three previous threads still need resolution before this is ready — and the PR description overstates what's in the diff. PR description vs actual diff The description says:
None of that is in the diff. Open threads
What's good
|
|
The three open threads and the PR description all reference Please update the title and description. The current diff does three things — none of which is "add LocalDataSource trait":
The three open inline threads ( Design question on The rest of the diff is clean. The by-hash fetch is a genuine correctness improvement and the test additions are correct. |
Summary
Introduces a
LocalDataSourcetrait so the validator can pull blocks and contract bytecode from a host process (a full node embedding the validator) instead of always round-tripping through RPC. The witness path stays on RPC — only replayers produce witnesses, so that source is inherently remote — but blocks and bytecode are now fetched through the trait.Motivated by embedding the validator into mega-reth, where both are already in-memory. Default behavior is unchanged:
RpcClientimplementsLocalDataSource, so existing deployments keep using RPC.Changes
New trait —
crates/stateless-common/src/local_source.rsLocalDataSource: object-safe async trait (Arc<dyn LocalDataSource>) withblock_by_hashandcodes_by_hashes.LocalDataError: typed errors —Deadline,CodeVerification,BlockMissing,Provider.From<CodeFetchError>flattensCodeFetchError::DeadlineintoLocalDataError::Deadlineso callers only match the deadline condition once.RpcClientpreserves the RPC-only deployment path.Validator wiring —
bin/stateless-validator/src/{chain_sync,workers}.rsValidatorFetcherandValidatorProcessornow holdArc<dyn LocalDataSource>for block/bytecode fetches.ValidatorFetcherstill keepsArc<RpcClient>for the witness call.get_block_hash), so a reorg between the hash lookup and the block fetch surfaces as a hash mismatch instead of silently swapping in a different block.ValidatorProcessormatches all fourLocalDataErrorvariants:CodeVerification→ non-retryable validation failure;BlockMissing/Provider→ retryable;Deadline→unreachable!(we passdeadline=None).Integration tests —
bin/stateless-validator/tests/integration.rseth_getBlockByHash(the new fetch path).shape_blockhelper deduplicates the full/hashes-only response shaping shared witheth_getBlockByNumber.Arc<dyn LocalDataSource>through both fetcher and processor.Workspace
async-trait = "0.1"(used by the new trait).Test plan
cargo check --workspacepassescargo test -p stateless-common local_source::tests— new unit tests passcargo test -p stateless-validator --test integration— by-hash block path + processor wiring exercised end-to-endLocalDataSource = RpcClientpath)