Documentation
¶
Overview ¶
Package headcheck is Lantern's running-head divergence monitor.
Background (#85, community discussion, #80): Lantern follows the *running* (unfinalized) chain head over gossipsub (net/blockingest). The boot anchor is a strong multi-source 5-of-N quorum on an F3-finalized tipset (chain/bootstrap), and #79 added heaviest-ParentWeight fork choice so a competing *lighter* fork on the running head is rejected. What neither of those closes is the case where an attacker eclipses the gossip peer table and feeds a self-consistent, parent-linked, *heavier-looking* fork: the node would happily follow it because every individual header hashes fine and the weight arithmetic only sees the attacker's numbers.
headcheck is the defense-in-depth layer #85 item 2 asks for: it periodically asks several INDEPENDENT head sources (gossip-observed tip, one or more Lotus-compatible RPC endpoints, user --peer endpoints) "what epoch is the head at?" and checks that they agree within a small look-back tolerance (default 3 blocks, matching the #85 ask). When the local gossip head drifts outside that tolerance from the diversity of external sources, headcheck raises a divergence signal so the daemon can log loudly / surface it on the dashboard / (optionally) refuse to serve a head it can't corroborate.
It is explicitly NOT a trusted-RPC head oracle. Lantern never *takes* its head from an RPC (that is the whole point of the project, see TRUST-MODEL.md §3.1). headcheck only uses external sources to CORROBORATE or DISPUTE the head Lantern already derived from gossip. A single RPC saying something different does not move our head; a diverse quorum of independent sources disagreeing with us is an eclipse alarm.
Source diversity matters: N sources that are really the same upstream (e.g. three Glif URLs) are one source for eclipse purposes. headcheck counts agreement by source Kind so a quorum requires genuinely independent observers, mirroring chain/bootstrap's Kind policy.
This package is pure logic over a HeadSource interface; the libp2p / HTTP source adapters live with the daemon wiring so headcheck stays unit-testable with mocks.
Index ¶
Constants ¶
const DefaultInterval = 30 * time.Second
DefaultInterval is how often the monitor polls its sources.
const DefaultLookback abi.ChainEpoch = 3
DefaultLookback is the head-agreement tolerance in epochs. #85 asks for a 3-block look-back: a source up to 3 epochs behind our head (or ahead of it) still counts as "agreeing", since gossip propagation and null rounds routinely put honest observers a couple epochs apart.
const DefaultMinAgree = 2
DefaultMinAgree is the minimum number of distinct-Kind external sources that must report a head within Lookback of ours before we treat the local head as corroborated. With fewer than this many *reachable* sources headcheck reports StatusInsufficient rather than a false-clean or a false-alarm.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Local reports Lantern's own (gossip-derived) head epoch. Required.
Local func() abi.ChainEpoch
// Sources are the external observers. Polled in parallel each round.
Sources []HeadSource
// Lookback tolerance in epochs (default DefaultLookback).
Lookback abi.ChainEpoch
// Interval between rounds (default DefaultInterval).
Interval time.Duration
// MinAgree distinct-Kind sources required to corroborate
// (default DefaultMinAgree).
MinAgree int
// PerSourceTimeout caps each source's HeadEpoch call (default 15s).
PerSourceTimeout time.Duration
// OnResult, if set, fires after each round with the Result. Used by
// the daemon to log alarms and update the dashboard. May be nil.
OnResult func(Result)
}
Config configures a Monitor.
type GatewayHeadSource ¶
type GatewayHeadSource struct {
// contains filtered or unexported fields
}
GatewayHeadSource is a HeadSource backed by the Lantern gateway's HTTP /state/root endpoint (net/hsync shape), which returns an Epoch field. The gateway speaks HTTP, not Filecoin.ChainHead JSON-RPC, so it needs its own adapter. Tagged KindLanternGateway so diversity counting treats it as its own kind (and, per bootstrap policy, does not let the project gateway alone satisfy a quorum). Purely corroborative, never the head oracle - same contract as RPCHeadSource.
func NewGatewayHeadSource ¶
func NewGatewayHeadSource(url string, timeout time.Duration) *GatewayHeadSource
NewGatewayHeadSource builds a gateway-backed head source. url is the gateway base (e.g. https://gateway.golantern.io); zero timeout => 15s.
func (*GatewayHeadSource) HeadEpoch ¶
func (s *GatewayHeadSource) HeadEpoch(ctx context.Context) (abi.ChainEpoch, error)
func (*GatewayHeadSource) Kind ¶
func (s *GatewayHeadSource) Kind() bootstrap.Kind
func (*GatewayHeadSource) Name ¶
func (s *GatewayHeadSource) Name() string
type HeadSource ¶
type HeadSource interface {
// Name is a stable human label for logs/dashboard.
Name() string
// Kind classifies the source for diversity counting.
Kind() bootstrap.Kind
// HeadEpoch returns the source's current head epoch.
HeadEpoch(ctx context.Context) (abi.ChainEpoch, error)
}
HeadSource is a single independent observer of the chain head epoch. Implementations: the gossip-observed tip (local), a Lotus-compatible RPC endpoint, a user --peer endpoint, a libp2p peer. Kind is used to measure genuine independence (see package doc).
type Monitor ¶
type Monitor struct {
// contains filtered or unexported fields
}
Monitor periodically checks local-vs-external head agreement.
func New ¶
New builds a Monitor. Local is required; with no Sources every round is StatusInsufficient (the monitor is then a no-op alarm-wise, which is the correct behaviour for a node the operator hasn't given any corroborating endpoints).
func (*Monitor) CheckOnce ¶
CheckOnce runs a single round synchronously and returns the Result. Exported for tests and for an on-demand dashboard refresh.
func (*Monitor) Start ¶
Start runs the monitor loop until ctx is cancelled or Stop is called. Non-blocking: spawns a goroutine.
type RPCHeadSource ¶
type RPCHeadSource struct {
// contains filtered or unexported fields
}
RPCHeadSource is a HeadSource backed by a Lotus-compatible JSON-RPC endpoint's Filecoin.ChainHead. It is used ONLY to corroborate or dispute the head Lantern already derived from gossip (see package doc); it is never the source of truth for the head.
Kind defaults to bootstrap.KindForest for an operator-supplied node; pass bootstrap.KindLanternGateway / KindUser explicitly to tag the project gateway or a user --peer so diversity counting is honest.
func NewRPCHeadSource ¶
func NewRPCHeadSource(name string, kind bootstrap.Kind, url, token string, timeout time.Duration) *RPCHeadSource
NewRPCHeadSource builds an RPC-backed head source. Empty name derives one from the URL; zero timeout defaults to 15s.
func (*RPCHeadSource) HeadEpoch ¶
func (s *RPCHeadSource) HeadEpoch(ctx context.Context) (abi.ChainEpoch, error)
func (*RPCHeadSource) Kind ¶
func (s *RPCHeadSource) Kind() bootstrap.Kind
func (*RPCHeadSource) Name ¶
func (s *RPCHeadSource) Name() string
type Result ¶
type Result struct {
Status Status
LocalHead abi.ChainEpoch
Agreeing int // distinct-Kind sources within Lookback
Disagreeing int // distinct-Kind sources outside Lookback
Reachable int // sources that answered at all
Total int // sources configured
MedianExtHead abi.ChainEpoch // median external head (−1 if none)
At time.Time // when this round completed
PerKind map[string]bool // Kind -> agreed (for dashboard)
}
Result is a snapshot of the most recent check round.
type Status ¶
type Status int
Status is the outcome of one check round.
const ( // StatusUnknown: no round has run yet. StatusUnknown Status = iota // StatusAgree: enough independent sources are within Lookback of our // local head. The head is corroborated. StatusAgree // StatusDiverge: a quorum of independent sources reports a head // outside Lookback of ours. Possible eclipse / fork-follow. ALARM. StatusDiverge // StatusInsufficient: too few sources were reachable to make a call. // Not an alarm by itself, but means the head is uncorroborated. StatusInsufficient )