Expand description
Statement handling to plug on top of the network service.
This crate implements gossip-based propagation of statements between nodes, layered on the
substrate notifications protocol. Two protocol versions are negotiated per peer: statement/2
(preferred) with statement/1 as a fallback.
§Propagation
- During major chain synchronization, statement gossip is paused so peers prioritize downloading blocks; it resumes automatically once the node is fully synced (peers are reconnected to recover statements missed while syncing).
- A propagation loop runs every second (
config::PROPAGATE_TIMEOUT): it takes all statements added since the previous round and queues their hashes to a per-peer outbox. Each peer has at most one propagation chunk in flight at a time. When its send slot is free, statements are fetched from the store, filtered and encoded up to the maximum notification size (config::MAX_STATEMENT_NOTIFICATION_SIZE, ~1 MiB). - Incoming statements are pushed onto a bounded validation queue
(
config::MAX_PENDING_STATEMENTS); if the queue is full, incoming statements are dropped. - Peer reputation is adjusted based on statement quality (good, duplicate, invalid, flooding).
§Tracking received statements
There is no per-peer record of delivered statements. A statement is propagated once, on the tick after its import: the propagation pass drains the store’s recent set, so no later pass can pick it up again. The only duplicates worth preventing are statements sent back to the peers they came from.
While a statement waits for validation, the peers it came from are recorded in
pending_statements_peers. On import they move to recently_received_statements, and a
peer that resends the statement before its tick is added there too. Propagation and
initial-sync chunks both skip the recorded peers, and the propagation pass clears
recently_received_statements when done.
§Initial sync
A peer joining (or changing its topic affinity) receives the store’s existing statements through a cursor over the store’s admission journal: scheduling captures the journal’s watermark, and bursts walk the admissions below it chunk by chunk, advancing the cursor as each send is confirmed — a failed chunk is resent from the same position. The watermark also splits delivery ownership between the two paths: admissions below the peer’s highest watermark are the sync cursor’s job, and propagation skips them, so within one sync a statement reaches the peer through at most one path. Duplicates stay possible at the edges — an affinity-change re-sync restarts the cursor from zero and redelivers earlier admissions, and a chunk whose send timed out may have arrived regardless, so its resend repeats it.
§Send scheduling
Every peer has one send slot, shared by propagation and initial sync, so at most one chunk per connection is in flight. Each chunk carries a fresh id, so the result of a send left over from a previous connection cannot free the current one’s slot. A completed chunk frees the slot at once and the next propagation chunk follows, a failed send included: a failed propagation chunk is not retried, but the rest of the backlog keeps draining, while a failed initial-sync chunk is resent from the sync’s cursor.
Propagation queues hashes in a per-peer outbox and fetches, filters and encodes them only when
the slot is free, so a slow peer holds one encoded chunk rather than its whole backlog. An
outbox past config::MAX_PROPAGATION_OUTBOX_LEN drops its oldest hashes, since the freshest
statements are the ones still worth delivering. Dropped hashes are counted in
undelivered_statements.
In-flight bytes of both kinds are held against the shared
config::MAX_SEND_IN_FLIGHT_BYTES budget. A peer that finds the budget full, or whose store
fetch fails, is parked once and refilled in parking order as completed sends free bytes and on
propagation ticks. While initial syncs are pending, propagation parks
config::INITIAL_SYNC_RESERVED_BYTES early: refills reclaim freed bytes synchronously,
while the timer-driven sync bursts would otherwise always find the budget full.
§Topic affinity and light nodes
The statement/2 protocol lets a peer advertise which topics it cares about as a bloom filter
(“topic affinity”). Once a peer has an active affinity filter, only matching statements are
forwarded to it; when its affinity changes, newly relevant statements are re-sent. Affinity
advertisements are rate-limited. See the affinity module.
Light-client peers on statement/2 must advertise an affinity before receiving any statements:
a light V2 peer pulls only the topics it cares about instead of the full feed, and is synced
those statements in an initial burst on connect. Full nodes receive all statements unless they
opt into an affinity.
§Usage
- Use
StatementHandlerPrototype::newto create a prototype. - Pass the
NonDefaultSetConfigreturned fromStatementHandlerPrototype::newto the network configuration as an extra peers set. - Use
StatementHandlerPrototype::buildthenStatementHandler::runto obtain aFuturethat processes statements.
Modules§
- config
- Configuration of the statement protocol
Structs§
- Statement
Handler - Handler for statements. Call
StatementHandler::runto start the processing. - Statement
Handler Prototype - Prototype for a
StatementHandler.
Type Aliases§
- Statement
Import Future - Future resolving to statement import result.
- Statements
- A set of statements.