blockingest

package
v1.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: Apache-2.0, MIT Imports: 13 Imported by: 0

Documentation

Overview

Package blockingest consumes Filecoin block announcements from gossipsub (/fil/blocks/<network>) and installs them into the header store as new heads, giving Lantern 0-1 epoch head-tracking latency without polling an upstream RPC.

This logic previously lived in cmd/lantern (package main) and could not be reused by pkg/daemon (the embedded path used by curio-core). It is extracted here unchanged so both the standalone daemon (cmd/lantern) and the embedded daemon (pkg/daemon) mount the same gossipsub head-tracker. See lantern#40.

Trust model: validation here is intentionally narrow. blockpub already did the CBOR shape + signature-presence check and the gossipsub topic validator; we add a CID re-derive (defense in depth), dedupe, a height fence, and parent-linkage. We do NOT do full Lotus-style block validation (BLS sig / election proof / beacon). Lantern is an F3-anchored light client: the trust path is F3 anchor -> SetHead -> content-addressed state queries, not full block re-execution. The polling Sync agent (chain/header/store) remains the catch-up fallback for anything gossipsub misses.

Concurrency: blockpub's OnBlock callback fires from its own read goroutine. We funnel into a single processor goroutine via a bounded channel so head installs are serialized cleanly against the polling Sync and a runaway peer can't blow our memory.

Index

Constants

View Source
const DefaultBackfillCap abi.ChainEpoch = 3

DefaultBackfillCap bounds RPC-based inline backfill depth (in epochs) per gossipsub event before deferring to the polling Sync agent. The RPC path (glif) does one round-trip per epoch, so this stays small to avoid hammering the upstream on a single stale gossip arrival.

View Source
const DefaultParentWalkCap abi.ChainEpoch = 2880

DefaultParentWalkCap bounds parent-walk (bridge-off, #76) inline backfill depth in epochs. In bridge-off mode there is NO polling Sync catch-up (the polling src is a no-op without an upstream RPC), so this cap is the only path that stitches a cold-booted head from the seeded anchor to the live tip. It has to accommodate the natural anchor→live gap that accrues during init + daemon startup + peer discovery (easily 30-120 epochs on calibration; more if the anchor sat on disk overnight). The chainxchg path handles this efficiently (one iterative request per 900-tipset chunk, capped internally at maxBackfillTipsets); the per-CID bitswap fallback pays a linear FetchBlock cost, still bounded.

Variables

This section is empty.

Functions

func StatsLogger

func StatsLogger(ctx context.Context, ing *Ingestor, pub *blockpub.Publisher, interval time.Duration, logf func(format string, args ...any))

StatsLogger runs a periodic one-line stats summary every interval until ctx is cancelled. Optional; standalone cmd/lantern uses it so operators can confirm gossipsub is carrying the load. logf is the sink (e.g. fmt.Fprintf to stderr or a logger).

Types

type BackfillSource

type BackfillSource interface {
	TipsetCIDsByHeight(ctx context.Context, h abi.ChainEpoch) ([]cid.Cid, error)
	FetchBlock(ctx context.Context, k cid.Cid) (*ltypes.BlockHeader, error)
}

BackfillSource is the minimal RPC surface the ingestor uses for inline backfill when a gossipsub block arrives at head+N with N>1. The Lantern glif client, gateway client, and combined fetcher all satisfy it.

type ChainFetcher

type ChainFetcher interface {
	FetchTipsetChain(ctx context.Context, head []cid.Cid, length int) ([][]*ltypes.BlockHeader, error)
}

ChainFetcher pulls a verified header chain (newest->oldest, level 0 = the tipset whose block CIDs are head) from the p2p swarm. Implemented by net/chainxchg.Client (#91).

type Ingestor

type Ingestor struct {
	// contains filtered or unexported fields
}

Ingestor consumes block announcements from gossipsub and installs them into the header store as new heads. One ingestor per daemon; owns the dedup state. Build with New, then call Run once and feed it via Enqueue (which is the blockpub OnBlock callback).

func New

func New(store *hstore.Store, src BackfillSource) *Ingestor

New builds an ingestor wired to the header store. src may be nil; when nil, blocks at head+N>1 are skipped and the polling Sync's backfill path handles them on its next cycle.

func Start

func Start(ctx context.Context, ps *pubsub.PubSub, store *hstore.Store, src BackfillSource, topic string) (*Ingestor, *blockpub.Publisher, error)

Start brings up the blockpub subscription + ingestor goroutine on the given gossipsub topic. Returns the ingestor (for stats/shutdown) and the blockpub publisher, or an error.

Side effects:

  • Joins /fil/blocks/<network> on gossipsub
  • Starts the ingestor goroutine

The caller owns ctx cancellation for shutdown and may start its own stats-logging loop using Ingestor.Stats() + Publisher.Stats().

func (*Ingestor) Enqueue

func (g *Ingestor) Enqueue(blk *ltypes.BlockMsg)

Enqueue is the OnBlock callback handed to blockpub. Non-blocking: drops when the processor is behind so the gossipsub read loop never stalls (the polling Sync picks up any dropped head within its cycle).

func (*Ingestor) Fresh

func (g *Ingestor) Fresh(within time.Duration) bool

Fresh reports whether the ingestor installed a block within the last `within` duration. Used by the polling Sync (#71) to decide whether gossip is currently keeping the store head live, in which case the Sync skips its upstream-RPC HeadEpoch() poll for that tick. Returns false before the first install (zero timestamp).

func (*Ingestor) ObservedHead

func (g *Ingestor) ObservedHead() abi.ChainEpoch

ObservedHead returns the highest block height the ingestor has successfully installed into the store. This is the gossip layer's view of the chain tip: it tracks the live head (>= the canonical store head, since individual high-epoch blocks can be installed before the canonical head advances contiguously to them). Returns -1 if nothing installed yet.

The polling Sync uses this to make its #71 gossip-fresh skip lag-aware (#83): gossip being "fresh" only means head moved recently, not that head is at the tip. Comparing the store head against ObservedHead lets Sync skip the catch-up poll only when actually at the tip, and run it when gossip is fresh-but-lagging - without paying an upstream HeadEpoch RPC call.

func (*Ingestor) Run

func (g *Ingestor) Run(ctx context.Context)

Run is the processor loop. Blocks until ctx is cancelled.

func (*Ingestor) SetChainFetcher

func (g *Ingestor) SetChainFetcher(f ChainFetcher)

SetChainFetcher wires the ChainExchange client as the preferred parent-walk gap resolver (#91). Call before Run.

func (*Ingestor) SetHeadAdoptionGate

func (g *Ingestor) SetHeadAdoptionGate(fn func() bool)

SetHeadAdoptionGate wires a predicate consulted before each head adoption. While it returns false (e.g. headcheck reports the running head DIVERGES from the independent-source quorum), the ingestor holds head where it is: incoming blocks are still received and backfilled, but head is not advanced onto an uncorroborated tip. Passing nil re-opens the gate (always adopt). Safe to call before or after Run. (#79 item 2)

func (*Ingestor) SetHeadCorroboration

func (g *Ingestor) SetHeadCorroboration(fn func(cid.Cid) bool)

SetHeadCorroboration wires the per-candidate corroboration predicate (#80 part 2): before adopting a block as the new head, the ingestor asks whether the block has been forwarded by enough distinct peers (see blockpub.CorroborationGate). While the predicate returns false the ingestor holds head where it is and schedules bounded retries (corroborating duplicates arrive within ~1s of first delivery on a healthy mesh). After the retries are exhausted the block is dropped; the polling Sync remains the safety net for head progress, so an uncorroboratable block can delay adoption but never wedge the node. Passing nil disables the check. Safe to call before or after Run.

func (*Ingestor) SetParentWalkBackfill

func (g *Ingestor) SetParentWalkBackfill(on bool)

SetParentWalkBackfill enables the #76 bridge-off backfill strategy, where a gap is filled by CID-walking the gossip block's Parents via bitswap FetchBlock instead of the RPC-shaped height->CID lookup. Call once at construction time before Run.

func (*Ingestor) Stats

func (g *Ingestor) Stats() Stats

Stats returns a snapshot of counters.

type Stats

type Stats struct {
	Received         uint64
	Dedup            uint64
	Installed        uint64
	Skipped          uint64
	Rejected         uint64
	RejectedLighter  uint64 // #79: rejected by heaviest-ParentWeight fork choice
	HeldDiverged     uint64 // #79: head adoptions held while divergence gate closed
	HeldUncorrob     uint64 // #80: head adoptions held awaiting multi-peer corroboration
	Backfilled       uint64
	BackfillFailed   uint64
	LastInstallEpoch abi.ChainEpoch
}

Stats is a snapshot of ingestor counters for observability.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL