Skip to content

statement-store: failed propagation sends suppress redelivery to that peer #12764

Description

@DenzelPenzel

Summary

StatementHandler::send_statements_to_peer marks statements as known to a peer before the send is
attempted. If the send then fails, those statements are never re-offered to that peer for the
lifetime of the connection.

Where

substrate/client/network/statement/src/lib.rs:1468 — the mark happens inside the filter, before the
chunk is even queued:

let to_send: Vec<_> = statements
    .iter()
    .filter_map(|(hash, stmt)| {
        if peer.known_statements.contains(hash) {
            return None;
        }
        if peer.topic_affinity.as_ref().is_some_and(|a| !a.matches_statement(stmt)) {
            return None;
        }
        peer.known_statements.insert(*hash);   // <-- marked before the send is attempted
        Some(stmt)
    })
    .collect();

Three failure paths then drop those statements silently:

path location scope of loss
message_sink() returns None lib.rs:1501 early return abandons all remaining chunks of the batch
SendOutcome::NetworkError lib.rs:1544 the failed chunk
SendOutcome::TimedOut lib.rs:1551 the failed chunk

PendingSendResult (lib.rs:668) carries only statement_count: usize, not the hashes, so
handle_send_result has nothing to roll back with.

Why redelivery never happens

Two things compound:

  1. propagate_statements (lib.rs:1585) uses StatementStore::take_recent_statements(), which
    drains the recent index (client/statement-store/src/lib.rs:1724
    query_index.write().take_recent(); store_api.rs:389: "Return recent statements and clear the
    internal index"
    ). A statement gets exactly one propagation pass, ever.
  2. Initial sync — the only path that re-walks the full store — is scheduled only on peer connect
    (lib.rs:1186) and on explicit affinity change (lib.rs:1640). There is no periodic re-sync.

The statement itself is not lost: it stays in the store until expiry, and other peers are unaffected
(each peer gets its own send future). What is lost is delivery to this peer, until it reconnects or
changes affinity — at which point schedule_initial_sync_for_peer clears known_statements
(lib.rs:1608) and re-sends everything still unexpired.

Metadata

Metadata

Assignees

No one assigned

    Labels

    I2-bugThe node fails to follow expected behavior.T0-nodeThis PR/Issue is related to the topic “node”.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions