Skip to content

feat: add configurable include set to gate captured tables#193

Merged
rishitesh-snt merged 9 commits into
mainfrom
feat/prover-db-5-include-filter
Jun 10, 2026
Merged

feat: add configurable include set to gate captured tables#193
rishitesh-snt merged 9 commits into
mainfrom
feat/prover-db-5-include-filter

Conversation

@rishitesh-snt

@rishitesh-snt rishitesh-snt commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a per-node include set the OCW consumer uses to gate which captured
events get forwarded to its prover-db indexer. The chain stays
unfiltered — every validator captures every Create/Drop/Insert event
into the offchain queue, exactly as before. Filtering happens only when
this node's OCW forwards to its indexer, so two indexer nodes can
subscribe to different subsets without affecting each other or the
chain.

Operator surface

Two flags on sxt-node, both env-var-friendly via clap:

  • --prover-db-url <URL> — toggles the consumer on; required.
  • --prover-db-include <patterns> — comma-separated wildcard list
    (also via PROVER_DB_INCLUDE or repeated flags). Grammar:
    • NAMESPACE.NAME — exact table
    • NAMESPACE.* — every table in that namespace
    • *.* — match everything (degenerate; same as omitting)
    • *.NAME — rejected
      Identifiers are uppercased before storage so matches are byte-exact
      against the on-chain canonical form. Empty list ⇒ "forward
      everything" (the default).

Misconfigurations fail at startup:

  • --prover-db-url without --enable-offchain-indexing — refused.
  • --prover-db-include without --prover-db-url — refused.
  • Malformed pattern — refused with the offending entry quoted.

Implementation

  • sxt-core: one new offchain key PROVER_DB_CONFIG_KEY holding a
    SCALE-encoded ProverDbConsumerConfig { url: String, include: Vec<IncludeRule> }.
    Single key so "consumer on" and "consumer off with stale filter" are
    unrepresentable.
  • Pallet OCW (run_consumer): one StorageValueRef decode per
    round, then the existing forward path with the include set threaded
    into forward_events. Each BlockEvent's table runs through
    table_matches_rules before the HTTP POST — Drops included,
    uniformly.
  • Producer (capture_events): unchanged. Every event still hits
    the offchain queue regardless of any node-local filter.
  • Node CLI: a clap::Args flattened ProverDbConsumerCli group;
    validated into the non-optional ProverDbConsumerConfig at service
    init by configure_prover_db_consumer.

Tests

  • 12 pallet tests pass, including two new ones for the consumer-side
    filter: ocw_forwards_only_events_matching_include_set (mixed
    matching/non-matching events → only matching ones POSTed) and
    ocw_with_empty_include_set_forwards_everything
    (absence ⇒ match-all).
  • End-to-end exercised via sxt-integration-harness --auto-start:
    9/9 scenarios, 89 actions green. The include_filter.toml scenario
    creates INCL_F_GOOD.T1 and INCL_F_BAD.T2 on chain, submits to
    both, and asserts only INCL_F_GOOD.T1 appears on prb-service.

Test plan

  • cargo test -p pallet-prover-db-indexer
  • cargo check --workspace
  • Manual: run a node with --prover-db-url http://… --prover-db-include "ETHEREUM_MT.*" and confirm only that namespace's events POST to prb-service.

…d tables

By default the producer hook captures every Create/Drop/Insert event
that reaches `T::EventCapture::capture_events`. This adds a sudo-only
extrinsic, `ProverDbIndexer::set_include_rules`, that replaces an
on-chain rule list against which every captured event is matched. An
empty rule list (the default) preserves the current "index everything"
behavior; a non-empty list gates the producer so only events for the
listed namespaces or fully-qualified tables reach the offchain DB —
saving offchain storage and OCW work for chains that index a strict
subset of their tables.

Shape:
- `sxt-core::prover_db_indexer::IncludeRule` (`Namespace(TableNamespace)`
  | `Table(TableIdentifier)`), stored as
  `BoundedVec<IncludeRule, ConstU32<MAX_INCLUDE_RULES = 256>>`.
- Pure helper `table_matches_rules(&TableIdentifier, &[IncludeRule])`
  with the "empty = match all" semantics.
- `pallet_prover_db_indexer::IncludeSet` value-query storage, with a
  matching `IncludeRulesSet { count }` event.
- `Pallet::capture_events` reads the rule set once per call and skips
  the offchain-DB write entirely when nothing passes the filter, so
  there's no per-event storage cost in the default case.

Tests cover the extrinsic (root succeeds + event deposited; non-root
rejected; storage unchanged on reject) and the producer side via
`offchain_index::set` → `persist_offchain_overlay()` →
`ext.offchain_db()`: empty set passes everything, namespace rules pass
only that namespace, table rules pass only the exact identifier, and a
non-matching set writes neither the payload nor the high-water-mark.
@rishitesh-snt rishitesh-snt requested review from a team as code owners June 1, 2026 07:52
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

1.69.0

Bug Fixes

  • add basic documentation to runtime crate (30c26e7
  • add missing docs for runtime module items (52a2905
  • add missing docs to chain-utils (c8235c6
  • address warnings in on-chain-table (095a856
  • address warnings in pallet-rewards mock module (886bc36
  • allow deprecated CurrencyAdapter usage (cfe0517
  • allow missing docs in private items in test_end_row_limits binary (7700cd6
  • allow missing_docs_in_private_items on event forwarder contract (b2d6f8b
  • allow unused private_key variables in pallet-keystore benchmarks (c218f0d
  • declare anonymous lifetime in commitment-sql proptest helper (63b3fc0
  • declare anonymous lifetimes in canaries parse functions (f81e9de
  • document all items in watcher main module (2fe60db
  • document memory_commitment_map module in commitment-map (32a38e3
  • document missing items in canaries (72c5f33
  • document missing items in event-forwarder (5408075
  • document modules in chain-utils (3eb6545
  • document modules in rpc crate (19cf1af
  • don't hide elided lifetimes in backwards compatibility test case generator (4e6968e
  • don't import frame_benchmarking if runtime-benchmarks is disabled in node (32cfca3
  • expect AttestationInfo::block_number to be dead code in canaries (7753c44
  • expect some dead code in NewFullBase in node (fe06f5e
  • ignore unused error variable in watcher main module (d330fe5
  • ignore unused variable in attestation-tree test (ce8adf3
  • ignore unused variable in pallet-attestation benchmarks (b0ef4a7
  • implement benchmark configurations for runtime outside runtime api implementation (d1a3db8
  • only define migrations with runtime-benchmarks disabled (26e8804
  • privatize deposit_event in pallet-rewards (6c1caaf
  • privatize deposit_event in pallet-smartcontracts (fe43a19
  • privatize deposit_event in pallet-system-tables (8bf7c21
  • privatize deposit_event in pallet-tables (da951a5
  • privatize pallet-keystore deposit_event (e878e37
  • privatize pallet-permissions deposit_event (099a7ba
  • privatize system-contracts deposit_event (305dfc9
  • privatize zkpay deposit_event (9d5993c
  • propagate tui error in update_ui in watcher (fe943eb
  • prover-db-indexer: clippy doc-formatting + result_large_err on parse_include_pattern (a64adb2
  • prover-db-indexer: collapse no_match vec to one line for cargo f (f0debea
  • remove dead code in chain-utils (0070600
  • remove dead code in node (6958ec6
  • remove unused block_number parameter from verify_attestations in watcher (6225426
  • remove unused block_number parameter from verify_signature in watcher (08f3bff
  • remove unused const in pallet-tables test (e7bf9f5
  • remove unused const in runtime test (c7e0add
  • remove unused dependencies from sxt-core (361f8a0
  • remove unused destructure variables in verify_attestation in watcher (dc04d24
  • remove unused import in memory_commitment_map test (4c2300a
  • remove unused import in pallet-attestation benchmarks (5fa662e
  • remove unused import in pallet-commitments migrations (ecf6a05
  • remove unused imports and format imports in chain-utils (5cd269b
  • remove unused imports in attestation-tree (91f5d61
  • remove unused imports in node (1f4c7a8
  • remove unused imports in pallet-smartcontracts (b7fa723
  • remove unused imports in pallet-tables (a08ccfd
  • remove unused imports in runtime (614a1e3
  • remove unused imports in test_end_row_limits (0328143
  • remove unused imports in watcher (340d27b
  • remove unused substrate key path from watcher client (2bd4d02
  • switch hex to being a dev dependency in rpc crate (7b01208
  • warn unused_crate_dependencies in sxt-core (62c85b7

Features

@rishitesh-snt rishitesh-snt requested a review from tlovell-sxt June 1, 2026 12:44
Comment thread pallets/prover_db_indexer/src/lib.rs Outdated
…de CLI config

Per review feedback: the include set is a node-local indexing policy
("which tables does *this* node's prb-service receive?"), not a
chain-wide one. Different indexer nodes can subscribe to different
subsets. Capture stays unfiltered on every validator so the offchain
queue mirrors the full block; the filter applies only when the OCW
forwards events.

Removed (on-chain):
- `IncludeSet` storage value
- `set_include_rules` extrinsic
- `IncludeRulesSet` event
- Producer-side filter in `EventCapture::capture_events`
- `IncludeRules` BoundedVec alias, `MAX_INCLUDE_RULES`,
  `MaxEncodedLen` / `TypeInfo` derives on `IncludeRule`

Added (per-node):
- `PROVER_DB_INCLUDE_KEY` offchain-storage key in `sxt-core`
- `--prover-db-include-file <path>` flag on the node binary, plus a
  `PROVER_DB_INCLUDE_FILE` env var. JSON schema:
    [ { "kind": "namespace", "value": "ETHEREUM_MT" },
      { "kind": "table", "namespace": "OTHER_NS", "name": "SPECIAL" } ]
  Identifiers get uppercased before storage so the byte-match against
  the on-chain canonical form works without per-operator vigilance.
- Service-side `configure_prover_db_include` parses the file and
  SCALE-encodes a `Vec<IncludeRule>` into offchain local storage at
  startup, before the first block. Mirrors the existing
  `configure_prover_db_url` shape exactly. Rejects the
  include-file-without-url misconfiguration up front.
- Consumer-side filter: `run_consumer` reads the rules once per OCW
  round and threads them through `forward_block` → `forward_events`,
  where each `BlockEvent` is matched against `table_matches_rules` and
  skipped (Drops included, uniformly) when it doesn't pass.

Tests dropped the four producer-filter tests and the two extrinsic
tests; added `ocw_forwards_only_events_matching_include_set` (mixed
events, only matchers POST) and `ocw_with_empty_include_set_forwards_everything`
(absent key ⇒ match-all). Kept the existing `capture_events_writes_all_events_unconditionally`
regression. 12/12 pass.
Comment thread node/src/cli.rs Outdated
Comment thread node/src/cli.rs Outdated
Comment thread node/src/service.rs Outdated
Comment thread node/src/cli.rs Outdated
…nfig; drop the file flag for a wildcard CLI flag

Two review points addressed together:

1. Make invalid states unrepresentable. The consumer used to live across
   two offchain keys — PROVER_DB_URL_KEY (`Vec<u8>`) and
   PROVER_DB_INCLUDE_KEY (`Vec<IncludeRule>`) — which let an operator
   accidentally have a stale include set with no URL. Replaced both
   with a single PROVER_DB_CONFIG_KEY holding a SCALE-encoded
   `ProverDbConsumerConfig { url: String, include: Vec<IncludeRule> }`
   in sxt-core. URL is non-optional in the type: writing the config
   *is* the act of enabling the consumer.

2. Replace `--prover-db-include-file` with `--prover-db-include`. A file
   path complicates deployment (volume mounts, ConfigMap + mountPath
   coordination) for what's now a tiny string list. The new flag is a
   clap `value_delimiter = ','` Vec<String> with env-var fallback
   (`PROVER_DB_INCLUDE`), and the grammar moves to a wildcard form:
       NAMESPACE.NAME — exact table
       NAMESPACE.*    — every table in NAMESPACE
       *.*            — match everything (degenerate; same as omitting)
       *.NAME         — rejected (runtime rule type can't express it)
   In K8s this is one ConfigMap key referenced by `envFrom` from every
   pod in the prover stack instead of a mounted file.

CLI shape uses clap::Args + #[clap(flatten)]: a new ProverDbConsumerCli
group holds both fields. The service-side resolver in
`configure_prover_db_consumer` is the single seam where the optional CLI
shape is validated into the non-optional `ProverDbConsumerConfig` —
fails loud if include patterns are set without a URL, or if any pattern
is malformed.

Pallet OCW: one StorageValueRef read replaces the previous two; URL
encoding error variant dropped (String can't be invalid UTF-8). Tests
seed the unified key via a new `setup_with_config(include)` helper;
existing `setup_with_url()` becomes a thin alias around an empty include
set. 12/12 pallet tests pass.

End-to-end: the harness orchestrator passes
`--prover-db-include <comma-joined patterns>` instead of rendering a
JSON file. All 9 example scenarios (89 actions) green under
`--auto-start --all`.
…arse_include_pattern

- cli.rs: blank-line separator before the '`*.NAME` is rejected' paragraph so it stops being parsed as a list-item continuation (doc_lazy_continuation).
- service.rs: dedent the wrap line under the '`"*.*"`' bullet from 23 spaces to 2 (doc_overindented_list_items).
- service.rs: `parse_include_pattern` returns `Result<_, ServiceError>`, which clippy now flags at 176 bytes (result_large_err). `ServiceError` is a substrate type we don't own, so apply the same `#[expect(..., reason = ...)]` already used on `configure_prover_db_consumer`.
Comment thread node/src/service.rs Outdated
Comment thread node/src/cli.rs Outdated
…> parsed via FromStr

Per review: split each side of the dot into IdentFilter
{Wildcard, Ident(String)} and combine into TableIdentifierFilter, both
with matches() + FromStr. Drops IncludeRule, table_matches_rules, and
service-side parse_include_pattern; clap parses --prover-db-include
values directly. *.NAME now allowed.
Per review: surface the match-all default in --help and let the include
vec stay non-empty. Cross-flag guard on URL-absent dropped since the
default now makes that branch unreachable; URL absence remains the
single dormancy signal.
Comment thread sxt-core/src/prover_db_indexer.rs
Comment thread sxt-core/src/prover_db_indexer.rs Outdated
…, and table_matches_filters

13 tests covering wildcard/literal matching, FromStr happy + error
paths (empty side, over-length, missing dot, multi-dot), and the
match-all-on-empty behaviour of table_matches_filters.
Per review: split "no filters configured" from "match-all configured"
so they're distinct, addressable states. table_matches_filters now
returns false on an empty list; callers wanting match-all must pass
an explicit *.* filter (the CLI default already does this).

Pallet tests: setup_with_url seeds *.* instead of an empty vec; a new
ocw_with_empty_include_set_forwards_nothing asserts the new
empty-list-rejects-everything behaviour.
@rishitesh-snt rishitesh-snt merged commit ab1cf7f into main Jun 10, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants