blockwatcher-types
blockwatcher-types is blockwatcher’s vocabulary crate: the
canonical value model
every decoder normalizes into, the
shapes of every configuration resource
(Network, Spec, Monitor, SinkDef, …), the schema types a
predicate type-checks against,
and the deterministic identifiers that let a downstream consumer deduplicate
under at-least-once delivery.
Nothing here runs anything: there is no pipeline, no async runtime, no
retry loop. This crate only defines what everything else in the workspace
passes around.
Its dependency list is the point, not an implementation detail: serde,
serde_json, num-bigint, and indexmap, and nothing else. Every other
crate in the core ring (blockwatcher-ports,
blockwatcher-core, blockwatcher-api) and every module
crate builds on this vocabulary without pulling in a chain SDK, an HTTP
client, or a storage driver by way of it, because there is nothing in this
crate’s own tree for them to pull in.
Key takeaways
blockwatcher-typesis blockwatcher’s vocabulary crate: the canonical value model, every resource shape, the schema types a predicate checks against, and the deterministic identifiers a consumer deduplicates by.- Nothing here runs anything: no pipeline, no async runtime, no retry loop; this crate only defines what everything else passes around.
- It depends on
serde,serde_json,num-bigint, andindexmap, and nothing else, which is what lets every other crate build on this vocabulary without pulling in a chain SDK, an HTTP client, or a storage driver by way of it.
Responsibilities
- Define
Value, the canonical value model every decoded field is expressed in: see Chain-agnosticism for the full discussion; this page does not repeat it. - Define the configuration resource shapes:
Network,Spec,Monitor,SinkDef,ModuleSel,RawSelector,DeliveryRetry,ResourceKind,VersionedRecord(the JSON-serializable structs a storage backend persists and the HTTP API accepts and returns).Monitor.gateisOption<ModuleSel>; config structs live with the modules (threshold/max_onceinblockwatcher-gates), not a closedMonitorGateenum in types.GateHitRecordandGateMetaare the persisted journal shapes (gate.rs). - Define the schema vocabulary a predicate type-checks against:
ValueType,EventSchema,FieldSchema,SchemaSet, and theFieldResolution/FieldOriginpairSchemaSet::resolvereturns. - Define the pipeline’s own data shapes:
RawEvent/RawPayload(what a source emits),DecodedEvent(what a decoder produces),Match(what a matcher accepts),SinkEvent(what a sink receives:MatchorRetracted), andDeadLetter(what a sink gives up on). - Define
CursorandCheckpoint, the position and resume-point types every source’s progress is tracked in, without interpreting either one. - Define the deterministic identifier types: the
string_id!-built newtypes (NetworkId,MonitorId,SinkId,SpecId,ChainKind) andMatchId, whosederivefunction is the one hashing algorithm in the crate. - Define
SecretRef, theenv:NAMEreference type, and note thatresolveis the one place this crate touches the outside world at all.
Not this crate’s job: deciding what a chain-specific decoder does with
any of these shapes (blockwatcher-evm and future chain modules own that);
defining the port traits that operate on them (blockwatcher-ports); running a
pipeline, retrying a delivery, or persisting a resource (blockwatcher-core,
blockwatcher-storage); the predicate language itself: this crate defines the
schema vocabulary a predicate checks against, not the grammar or evaluator
(blockwatcher-expr, see Predicates and the expression language).
Key types and traits
| Name | Kind | Role |
|---|---|---|
Value | enum | The canonical value model; see Chain-agnosticism |
Network, Spec, Monitor, SinkDef, ModuleSel | struct | Configuration resource shapes stored via the Storage port and exposed over the HTTP API |
RawSelector, DeliveryRetry, ResourceKind, VersionedRecord | struct/enum | Supporting resource vocabulary: a monitor’s selector entry, the engine’s delivery-retry policy, the four resource kinds, and storage’s optimistic-concurrency envelope |
ValueType | enum | The type each schema field is declared at, shaped after Value’s own variants |
EventKind | struct | An open, string_id!-built vocabulary of occurrence kinds (event/function_call/transaction, or a chain family’s own); carried by both EventSchema and DecodedEvent, and hashed directly in MatchId::derive |
EventSchema, FieldSchema | struct | One declared occurrence and one declared field within it |
SchemaSet | struct | The fields and namespaces a compiled monitor makes available for a predicate to check against; resolve is the field-path resolution algorithm every predicate compile runs against |
FieldResolution, FieldOrigin | enum | The three-way outcome of SchemaSet::resolve and whether a resolved field was declared or derived through an array |
Cursor, Checkpoint | struct | A source’s position in its own feed, and the resume point (cursor plus opaque source_state) a network’s pipeline persists |
RawEvent, RawPayload | struct/enum | What a source emits into the pipeline, before decoding |
DecodedEvent | struct | The normalized output of decoding: kind, name, canonical Value fields, cursor |
Match, SinkEvent, DeadLetter | struct/enum | One accepted occurrence (with its deterministic MatchId), the closed event a sink delivers (Match or Retracted { match_id }), and the record of one that exhausted its delivery budget |
MatchId, NetworkId, MonitorId, SinkId, SpecId, ChainKind | struct | Deterministic and open-vocabulary identifier newtypes; MatchId::derive is the crate’s one hashing algorithm |
SecretRef | struct | An env:NAME reference; resolve is the crate’s one I/O operation |
How data flows through it
This crate has no pipeline of its own: its “flow” is the sequence of types that hand off from one pipeline stage to the next, all defined here even though the stages themselves live in other crates:
flowchart LR
Config["Network / Spec / Monitor / SinkDef<br/>(write time)"] -.->|"read by"| Decoder
Config -.->|"read by"| Matcher
Source -->|"RawEvent / RawPayload"| Decoder
Decoder -->|"DecodedEvent<br/>(Value tree, via SchemaSet)"| Matcher
Matcher -->|"Match<br/>(MatchId::derive)"| SinkEvent
SinkEvent -->|"Sink::deliver"| Sink
Sink -.->|"on exhausted retries"| DeadLetter
Every arrow above is a type this crate defines; every box is a
port implementation that lives
elsewhere. SchemaSet::resolve runs once, at write time, when a monitor’s
predicate is compiled against its selectors’ schemas, and never again while
that pipeline is running: a decoded event at evaluation time is read
through the already-resolved Ast, not re-resolved against the schema.
Neighbours
blockwatcher-types depends on the following in production (nothing else in the workspace):
serde: (de)serialization derive supportserde_json: JSON wire formatnum-bigint: arbitrary-precision integersindexmap: ordered maps
In [dev-dependencies] only:
proptest: property tests inid.rsoverMatchId::derive
The following crates depend on it directly (per the dependency table):
blockwatcher-ports: trait boundary for all modulesblockwatcher-core: engine pipelineblockwatcher-api: HTTP routesblockwatcher-expr: predicate language and matcher moduleblockwatcher-evm: EVM chain moduleblockwatcher-evm-testkit: EVM module testsblockwatcher-testkit: workspace test fixturesblockwatcher-storage: storage moduleblockwatcher-sinks: sink modulesblockwatcher(binary): process binary
blockwatcher-rpc reaches it only transitively, through blockwatcher-ports.
Reading the source
- Start at
lib.rs: the module list, the re-exports, and the doc comment stating the crate’s one I/O exception (SecretRef::resolve). value.rs: theValueenum, its decimal/hex wire encoding, andfrom_json/get_path, the two general-purpose helpers built on it.schema.rs:ValueType,EventKind(an open vocabulary, not limited to the three constants it ships),EventSchema,SchemaSet, andresolve, the field-path resolution algorithm every predicate compile depends on.resource.rs: the configuration resource shapes:Network,Spec,Monitor,SinkDef,DeliveryRetry,RawSelector,ResourceKind,VersionedRecord.Monitor.gateisOption<ModuleSel>.gate.rs:GateHitRecord,GateMeta,GATE_HITS_CAP.id.rs: thestring_id!macro, the identifier newtypes it builds, andMatchId::derive’s hashing algorithm.event.rs: the pipeline’s own data shapes:RawEvent,RawPayload,DecodedEvent,Match,SinkEvent,DeadLetter.cursor.rs:CursorandCheckpoint.hex.rs: the internal0x-hex serde helpersValueandRawPayloadboth serialize through.secret.rs:SecretRef, and the crate’s one I/O exception.