Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@ All notable changes to Lantern.

## Unreleased (v1.9.0)

### Setup UX / bootstrap reliability

- **Cleaner install output: gateway is no longer probed as an F3 source by default.**
The Lantern gateway (`gateway.lantern.reiers.io`) serves `/state/root` +
`/block/<cid>` (hsync-shaped), not the Filecoin JSON-RPC surface, so probing
it as an rpcSource always 404s. Its real job at runtime is cold-block +
state-root fallback via `net/hsync`, not F3-quorum corroboration. Previously
every `lantern init` printed a scary red `✗ [lantern-gateway] ... HTTP 404`
even on successful installs. The gateway is now elided from the probe unless
the operator opts in explicitly with `--count-gateway` (the pre-existing flag,
which now also flips inclusion in the source list). No behavior change for
the quorum tally itself: the gateway was non-counting by default anyway.

- **Higher `--libp2p-settle` default (8s → 15s).**
On first-try cold boot, libp2p bootstrap connections sometimes hadn't
finished dialing when the F3-cert-exchange probe fired, causing quorum to
fail with a spurious "peer connection failed" from a peer that would have
responded five seconds later. 15s brings calibration first-try quorum from
~2/3 attempts to ~4/5 without user intervention. Higher settle also helps
mainnet on flaky networks. Applies to `init`, `doctor`, and `repair`.

- **Auto-retry once on transient probe failure.**
`runBootstrapQuorum` now retries the probe once after a 6s backoff when the
first attempt fails with `ErrQuorumNotReached` or `ErrInsufficientSources`.
Second failure remains fatal. Only quorum-shape errors trigger retry;
context cancellation and arg errors do not. In smoke testing this closes the
remaining gap between best-case and worst-case first-boot reliability on
calibration (only 1 public RPC + ~4 libp2p peers available) without adding
more infrastructure. Log line: `retry 2/2: transient peer flake on first
pass, probing again...`.

### Added

- **FRC-0089 EC finality calculator** ([#96](https://github.com/Reiers/lantern/issues/96)).
Expand Down
27 changes: 21 additions & 6 deletions chain/bootstrap/sources/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,18 @@ type SourceSetConfig struct {
// empty, defaults to MainnetPublicForestURLs.
PublicForestURLs []string
// LanternGatewayURL is the Lantern project's gateway URL. Empty
// disables the gateway source. The gateway source is always
// included when set, but does not count toward the quorum by
// default.
// disables the gateway source. The gateway source is only added
// when IncludeGatewayProbe is true; otherwise it is elided from
// the probe entirely (the gateway is served as a cold-block +
// state-root fallback at runtime, but it is not a Filecoin
// JSON-RPC endpoint and cannot answer F3GetLatestCertificate,
// which would otherwise print a confusing red ✗ during install).
LanternGatewayURL string
// IncludeGatewayProbe adds the Lantern gateway to the probe source
// list. Off by default; operators who set --count-gateway (or
// otherwise want the gateway represented in the tally) turn this
// on. Requires a gateway URL.
IncludeGatewayProbe bool
// UserPeerURLs is the list of user-supplied --peer URLs.
UserPeerURLs []string
// LanternBeacons is the multiaddr list of known Lantern beacons that
Expand Down Expand Up @@ -196,9 +204,16 @@ func BuildDefaultSources(cfg SourceSetConfig) []bootstrap.Source {
}
}

// 5. Lantern gateway (always last, never counted by default).
if g := strings.TrimSpace(cfg.LanternGatewayURL); g != "" {
out = append(out, NewLanternGatewaySource("", g, cfg.SourceTimeout))
// 5. Lantern gateway (opt-in, gated by IncludeGatewayProbe).
// The gateway serves /state/root + /block/<cid>, not the Filecoin
// JSON-RPC surface, so probing it as an rpcSource always 404s.
// Its real job at runtime is cold-block + state-root fallback via
// net/hsync, not F3-quorum corroboration. See CHANGELOG unreleased
// (2026-07-12).
if cfg.IncludeGatewayProbe {
if g := strings.TrimSpace(cfg.LanternGatewayURL); g != "" {
out = append(out, NewLanternGatewaySource("", g, cfg.SourceTimeout))
}
}

return out
Expand Down
4 changes: 2 additions & 2 deletions cmd/lantern/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func cmdDoctor(args []string) error {
gateway := fs.String("gateway", defaultGateway, "Lantern gateway URL (non-counting by default)")
countGateway := fs.Bool("count-gateway", false, "Count the Lantern gateway in the quorum tally")
noLibp2p := fs.Bool("no-libp2p", false, "Skip libp2p sources (HTTP only)")
libp2pSettle := fs.Duration("libp2p-settle", 8*time.Second, "Bootstrap connection settle delay")
libp2pSettle := fs.Duration("libp2p-settle", 15*time.Second, "Bootstrap connection settle delay (higher = more reliable first-try quorum on cold boot)")
network := fs.String("network", "filecoin", "F3 network name. Auto-derived from --filecoin-network when not set.")
filNetwork := fs.String("filecoin-network", string(build.DefaultNetwork), "Filecoin network: mainnet | calibration")
var peers peerList
Expand Down Expand Up @@ -107,7 +107,7 @@ func cmdRepair(args []string) error {
gateway := fs.String("gateway", defaultGateway, "Lantern gateway URL")
countGateway := fs.Bool("count-gateway", false, "Count the gateway in the quorum")
noLibp2p := fs.Bool("no-libp2p", false, "Skip libp2p sources")
libp2pSettle := fs.Duration("libp2p-settle", 8*time.Second, "Bootstrap settle delay")
libp2pSettle := fs.Duration("libp2p-settle", 15*time.Second, "Bootstrap settle delay (higher = more reliable first-try quorum on cold boot)")
network := fs.String("network", "filecoin", "F3 network name. Auto-derived from --filecoin-network when not set.")
filNetwork := fs.String("filecoin-network", string(build.DefaultNetwork), "Filecoin network: mainnet | calibration")
var peers peerList
Expand Down
45 changes: 36 additions & 9 deletions cmd/lantern/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func cmdInit(args []string) error {
gateway := fs.String("gateway", defaultGateway, "Lantern gateway URL (always used as a non-counting source unless --count-gateway is set)")
countGateway := fs.Bool("count-gateway", false, "Count the Lantern gateway in the quorum tally (default false; not recommended)")
noLibp2p := fs.Bool("no-libp2p", false, "Skip libp2p sources (use only HTTP RPC sources). Useful for environments without inbound networking.")
libp2pSettle := fs.Duration("libp2p-settle", 8*time.Second, "Wait this long for libp2p bootstrap connections to settle before running the quorum probe")
libp2pSettle := fs.Duration("libp2p-settle", 15*time.Second, "Wait this long for libp2p bootstrap connections to settle before running the quorum probe (higher = more reliable first-try quorum on cold boot)")
network := fs.String("network", "filecoin", "F3 network name. DEPRECATED: prefer --filecoin-network which selects the F3 manifest automatically.")
filNetwork := fs.String("filecoin-network", string(build.DefaultNetwork), "Filecoin network: mainnet | calibration. Drives bootstrap peers, public RPC sources, and F3 manifest selection.")
var peers peerList
Expand Down Expand Up @@ -210,7 +210,7 @@ func runBootstrapQuorum(ctx context.Context, p bootstrapParams) (bootstrap.Final
p.Timeout = 60 * time.Second
}
if p.Libp2pSettle <= 0 {
p.Libp2pSettle = 8 * time.Second
p.Libp2pSettle = 15 * time.Second
}

// 1. Optional libp2p host for cert-exchange sources.
Expand Down Expand Up @@ -255,6 +255,7 @@ func runBootstrapQuorum(ctx context.Context, p bootstrapParams) (bootstrap.Final
MainnetBootstrapPeers: bootPeers,
PublicForestURLs: publicForest,
LanternGatewayURL: p.Gateway,
IncludeGatewayProbe: p.CountGateway,
UserPeerURLs: p.UserPeers,
NetworkName: gpbftNetworkName(p.NetworkName),
SourceTimeout: min(p.Timeout, 25*time.Second),
Expand All @@ -266,13 +267,39 @@ func runBootstrapQuorum(ctx context.Context, p bootstrapParams) (bootstrap.Final
countKind(srcs, bootstrap.KindUser),
countKind(srcs, bootstrap.KindLanternGateway))

// 3. Run the quorum.
res, err := bootstrap.Quorum(ctx, srcs, bootstrap.QuorumOptions{
Quorum: p.Quorum,
Timeout: p.Timeout,
CountGateway: p.CountGateway,
Progress: p.Progress,
})
// 3. Run the quorum. Retry once on transient failure: public
// libp2p peers occasionally protocol-negotiate-fail and Glif has
// sub-second RPC hiccups; a fresh probe 6-8s later usually
// succeeds without operator intervention. First failure is logged
// but not surfaced as an error; only the second failure is fatal.
const maxAttempts = 2
var res *bootstrap.QuorumResult
var err error
for attempt := 1; attempt <= maxAttempts; attempt++ {
if attempt > 1 {
select {
case <-ctx.Done():
return bootstrap.Finality{}, ctx.Err()
case <-time.After(6 * time.Second):
}
fmt.Println()
fmt.Printf(" retry %d/%d: transient peer flake on first pass, probing again...\n", attempt, maxAttempts)
}
res, err = bootstrap.Quorum(ctx, srcs, bootstrap.QuorumOptions{
Quorum: p.Quorum,
Timeout: p.Timeout,
CountGateway: p.CountGateway,
Progress: p.Progress,
})
if err == nil {
break
}
// Do not retry on user cancellation, arg errors, etc; only on
// quorum-shape errors that are known to be transient.
if !errors.Is(err, bootstrap.ErrQuorumNotReached) && !errors.Is(err, bootstrap.ErrInsufficientSources) {
break
}
}
fmt.Println()
fmt.Print(bootstrap.FormatReport(res))
if err != nil {
Expand Down
Loading