Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Network

A network names one feed to watch: an id, a chain tag, and a source module selection (Network, crates/blockwatcher-types/src/resource.rs). It is written through PUT /networks/{id} on the HTTP API or as one JSON file under a seed directory’s networks/ subdirectory. Writing a network always restarts that network’s pipeline; the restart is cheap because the checkpoint survives it, per Resources § Lifecycle.

A complete network resource. The source.config object is registry_examples/evm_rpc.json from crates/blockwatcher-evm/src/, copied verbatim from the same file the crate’s family-completeness test constructs (the resource envelope around it precludes a literal include); any change to that file updates this example in the same change, per the wiki-parity rule:

{
  "id": "eth-mainnet",
  "chain": "evm",
  "source": {
    "module": "evm-rpc",
    "config": {
      "start_block": 18000000,
      "endpoints": [
        {
          "name": "alchemy",
          "url_secret": "env:BLOCKWATCHER_EXAMPLE_EVM_RPC_URL",
          "priority": "high",
          "rate_limit": { "rps": 25 }
        },
        {
          "name": "public",
          "url_secret": "env:BLOCKWATCHER_EXAMPLE_EVM_RPC_URL_FALLBACK",
          "priority": "low"
        }
      ],
      "confirmations": 12
    }
  }
}

Fields

Network rejects an unrecognized field (#[serde(deny_unknown_fields)]), so a typo’d key fails the write rather than being silently ignored.

KeyTypeDefaultMeaning
idstringnone (required)The network’s name, referenced by every monitor’s network field.
chainstringnone (required)The chain family tag. The write refuses a chain with no loaded decoder; shipped builds load evm.
sourceobjectnone (required)A ModuleSel: which source module feeds this network, plus that module’s own config.

source is the universal module envelope (ModuleSel, resource.rs):

KeyTypeDefaultMeaning
modulestringnone (required)The source module’s name: evm-rpc or evm-mempool in shipped builds.
configobjectnone (required)The named module’s own config; its shape depends on module and is documented per module below.

Write-time validation constructs the named module with the given config, so every refusal quoted below is raised at the write (as a 422) or at seed validation, never discovered later at runtime. Updating an existing network additionally recompiles every monitor already stored for it against the incoming chain, refusing a chain reassignment that would strand them, per put_network (crates/blockwatcher-core/src/control/writes.rs).

Endpoints: the shared pool vocabulary

Both source modules name a pool of HTTP JSON-RPC endpoints with the same shape (EndpointDef, crates/blockwatcher-evm/src/source/endpoint.rs). Each endpoint object:

KeyTypeDefaultMeaning
namestringnone (required)Labels every metric and log line for this endpoint. A repeat refuses: endpoint name 'primary' is used by more than one endpoint; endpoint names must be unique.
url_secretstringnone (required)An env:NAME reference to where the URL lives, never the URL itself. A value that is not a reference refuses with endpoint '<name>' url_secret is not a secret reference: ..., deliberately never echoing what was written: a provider URL routinely carries an API key.
prioritystring: high or low"high"The selection tier; high endpoints are always tried before low.
rate_limitobject { "rps": u32 }noneA preemptive request rate enforced before a call leaves the pool. A zero refuses: endpoint '<name>' rate_limit.rps is 0; it must be at least 1.
weightu321Ring slots in the tier’s rotation; meaningful only under round_robin selection, inert under ordered (and therefore always inert on evm-mempool, whose pool is fixed to ordered and exposes no selection knob). A zero refuses: endpoint '<name>' weight is 0; it must be at least 1. Above the pool’s cap of 100 (blockwatcher_rpc::MAX_WEIGHT) refuses: endpoint '<name>' weight (101) exceeds the maximum (100).

evm-rpc

Polls confirmed blocks over HTTP JSON-RPC: events via eth_getLogs, and, only while some monitor watches functions, full transaction bodies too. Its config (EvmRpcConfig, crates/blockwatcher-evm/src/source/rpc/config.rs) rejects unrecognized fields, including a network key: which network a source’s events belong to is the engine’s to supply, never the config’s.

The annotated example above is the module’s complete example config. The tunables:

KeyTypeDefaultMeaning
endpointsarraynone (required)The pool, per the table above. An empty array refuses: evm-rpc requires at least one endpoint.
selectionstring: ordered or round_robin"ordered"How the pool orders a tier’s endpoints when no window pin dictates the choice: ordered concentrates calls on the first admissible endpoint in configuration order; round_robin spreads windows across the tier, weighted by each endpoint’s weight. A misspelling is refused by serde naming both accepted spellings.
start_blocku64none (required)Where a run with no persisted checkpoint begins scanning. Deliberately absolute and without a default: a head-relative start would re-derive a different block on every restart and silently skip the gap.
confirmationsu6412How deep a block must age before it is emitted; trades delivery latency for reorg safety.
max_lag_blocksu643How far behind the pool’s most current head an endpoint may report before it is excluded from serving.
poll_interval_msu643000How often, while caught up, one eth_blockNumber head check advances the emission barrier.
logs_windowobject{ "initial": 512, "max": 2048 }How wide an eth_getLogs scan window starts and how far it may grow, per the table below.
full_block_windowu648Ceiling on a range fetched with full transaction bodies, applied only while some monitor watches functions. A zero refuses: full_block_window (0) must be at least 1.
probe_interval_msu6410000How often every non-open endpoint is probed for health changes, bypassing rate limits.
retry_backoff_max_msu6430000Cap on the doubling backoff for a window the run loop cannot fetch; the schedule starts at one poll interval. A cap below poll_interval_ms refuses: retry_backoff_max_ms (2999) is below poll_interval_ms (3000); the retry schedule starts at one poll interval, so a smaller cap silently disables the backoff.
receiptsstring: always or when_read"always"When a matching transaction spends an eth_getTransactionReceipt, which buys exactly tx.status. always keeps the derived match id a function of chain content alone; when_read saves the round trip when no predicate reads tx.status, at the cost of a payload (and match id) that changes with the monitor set. A misspelling is refused by serde naming both accepted values.
receipt_concurrencyu644How many eth_getTransactionReceipt calls may be in flight at once for one leaf’s matching transactions. Receipts are unpinned consensus reads, so concurrency changes pacing alone and never consistency; the per-endpoint rate limiter and breaker still gate every call. Raising it shortens function-heavy leaves at the cost of burstier provider load. A zero refuses: receipt_concurrency (0) must be at least 1.
header_batchu6420How many eth_getBlockByNumber requests ride one JSON-RPC batch, so a window of w blocks costs ceil(w / header_batch) header round trips. A batch is one HTTP request against one endpoint, so it inherits the window pin exactly as single calls do. 1 sends classic single calls, which is the setting for a provider that rejects batch arrays: such a provider fails its first window visibly and the run loop retries it under Degraded. A zero refuses: header_batch (0) must be at least 1.
bloom_screenbooltrueWhether a window may skip its eth_getLogs call when every fetched header’s logsBloom proves no monitored address or topic0 can be present. Against a protocol-conforming node the skip is lossless, since a bloom is a superset of its own block’s logs. Enabling it nonetheless adds a dependency the unscreened path does not carry: correctness rests on the bloom as well as on the logs response, so an endpoint or caching proxy that serves correct logs behind a zeroed or otherwise-inaccurate bloom loses those logs silently. Disable it for endpoints whose blooms are not trusted. A broad filter, carrying neither addresses nor topic0s, is never screened, and a header without a readable bloom is never screened regardless of this setting.

logs_window (LogsWindow, same file):

KeyTypeDefaultMeaning
initialu64512Starting scan width. A zero refuses: logs_window.initial (0) must be at least 1. A value above max refuses: logs_window.initial (2048) exceeds logs_window.max (512).
maxu642048How far the width may grow back after a provider forces it narrower. At runtime the first width a provider refuses drops the run’s effective ceiling pool-wide, so one narrow-limited endpoint caps every endpoint until the source next starts.

evm-mempool

Subscribes to pending transaction hashes over WebSocket and hydrates each against an HTTP pool: a notification carries a hash and nothing else, so every candidate costs one eth_getTransactionByHash. Its config (EvmMempoolConfig, crates/blockwatcher-evm/src/source/mempool/config.rs) has no start_block and no confirmations: a pending stream has no history to begin from and no reorg barrier to honor, and a start_block copied over from an evm-rpc network is refused by name as an unknown field rather than silently ignored. What that difference means for delivery guarantees is on Selectors § The position problem.

The module’s complete example config, registry_examples/evm_mempool.json from crates/blockwatcher-evm/src/, verbatim, the same file the crate’s family-completeness test constructs:

{
  "ws_url_secret": "env:BLOCKWATCHER_EXAMPLE_EVM_MEMPOOL_WS_URL",
  "endpoints": [
    {
      "name": "primary",
      "url_secret": "env:BLOCKWATCHER_EXAMPLE_EVM_RPC_URL"
    }
  ]
}
KeyTypeDefaultMeaning
ws_url_secretstringnone (required)An env:NAME reference naming where the ws:// or wss:// subscription endpoint lives. A value that is not a reference refuses with ws_url_secret is not a secret reference: ..., never echoing what was written.
endpointsarraynone (required)The HTTP hydration pool, per the shared endpoint table above. An empty array refuses: evm-mempool requires at least one endpoint.
reconnect_msu641000How long to wait after a dropped subscription before dialing again; a fixed interval with no jitter and no backoff. A zero refuses: reconnect_ms is 0; it must be at least 1.
idle_policyobject{ "ping_after_ms": 30000, "pong_deadline_ms": 10000 }When to suspect the subscription half-open, and how long to wait for proof before redialing, per the table below.

idle_policy (IdlePolicyDef, same file, the wire form of ws::IdlePolicy):

KeyTypeDefaultMeaning
ping_after_msu6430000How long a subscription may sit silent before an idle WebSocket ping goes out. A zero refuses: idle_policy.ping_after_ms is 0; it must be at least 1.
pong_deadline_msu6410000How long to wait for any frame after an idle ping before presuming the connection half-open and redialing. A zero refuses: idle_policy.pong_deadline_ms is 0; it must be at least 1.