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
12 changes: 12 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ Compare only the hash column: the `.sha256` file names the CI path (`dist/lanter

<!-- TODO-VERIFY: during the internal dry run the v1.10.0 linux-amd64 clean build did NOT match the published hash although the embedded build info was identical (friction F1, being fixed in a separate PR). Update this section once docs/REPRODUCIBLE-BUILDS.md and scripts/repro-build.sh land, and record the result in docs/VERIFICATION-M2.md. -->

## 7. First run

Every command takes `--network mainnet|calibration` (default mainnet).

```sh
LANTERN_PASS='' lantern init --network mainnet --no-wallet
lantern daemon --network mainnet
```

`init` writes the trust anchor only when at least 3 distinct operators agree on the current F3 finality and no source disagrees. **`lantern daemon` refuses to start without that anchor**; run `lantern init` (or `lantern repair`, which re-anchors from a fresh quorum) first. If `init` fails it prints which sources failed, how many operators answered, and the flags that help (`--peer <url>`, `--no-libp2p`, `--bootstrap-quorum N`); `lantern doctor --network <net>` re-runs the probe read-only. `--allow-rpc-anchor` lets the daemon boot from a weaker gateway+RPC head instead, with a warning. See [TRUST-MODEL.md](TRUST-MODEL.md) section 2.9.

## Expected timings

Measured on a clean `ubuntu:24.04` container on an 8-core / 16-thread Xeon E5-2667 v2 with a home fibre connection:
Expand Down Expand Up @@ -163,5 +174,6 @@ Useful variables: `LANTERN_VERSION=<tag>` (for example a release candidate; defa
| `go: downloading go1.26.4` then a network error | An older Go tried to fetch the pinned toolchain. Install 1.26.4 directly (step 2) or allow access to `proxy.golang.org`. |
| `lantern version` prints `dev` | You built without `-ldflags "... -X main.versionTag=$TAG"`. Rebuild with the exact command in step 4. |
| `go version -m` shows `vcs.modified=true` | The checkout has local changes. `git status`, then `git stash` or a fresh clone. |
| `lantern daemon` exits with `no trust anchor` | Run `lantern init --network <net>` (or `lantern repair --network <net>`) with the same `LANTERN_HOME`. If the quorum fails, follow its printed hints or run `lantern doctor --network <net>` (step 7). |
| `sha256sum -c` says "No such file or directory" | The published `.sha256` names `dist/lantern-<os>-<arch>`. Compare the hash column directly (step 6). |
| Local hash differs from the published hash | Check Go version, flags, tag and `vcs.modified` first, then follow [docs/REPRODUCIBLE-BUILDS.md](docs/REPRODUCIBLE-BUILDS.md). If it still differs, file an [operator report](https://github.com/Reiers/lantern/issues/new?template=operator-report.yml). |
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to Lantern.

## Unreleased

### Mainnet bootstrap works out of the box (F10, F11)
A fresh `lantern init --filecoin-network mainnet` (now `--network mainnet`) failed with "quorum not reached: no single tipset reached 5 sources (countable=9, counted=4)": most libp2p bootstrap peers were dead, and the 5-source default could not be met. The bar is now counted in distinct operators, which is both reachable and stronger.

- **Operator-aware quorum.** `chain/bootstrap` counts DISTINCT OPERATORS, not endpoints. Default: 3 operators must agree on the same F3 finality (mainnet and calibration), and **any conflicting answer for the same instance fails the probe** (`ErrConflict`, never retried, never outvoted). The probe keeps listening 3 s after the threshold so a late conflict is still seen. Different-instance answers are lag, not conflict.
- **Glif is chain.love.** api.node.glif.io and api.calibration.node.glif.io are served from chain.love infrastructure (same IP as api.chain.love, `x-chainlove-lb-*` headers, same Lotus build, HEAD redirects to filecoin.chain.love; observed 2026-10-26). `bootstrap.OperatorOf` now folds `glif.io` onto `chain.love` (and `chainsafe.dev` onto `chainsafe-fil.io`). The old 5-source default could be met by two operators; the `--bootstrap-quorum 4` workaround meant two. The #153 "independent RPC" for mainnet (boot anchor + headcheck) moves from chain.love to Filfox.
- **Bootstrap peers refreshed from lotus v1.38.3** (`build/bootstrap/*.pi` verbatim). `/dnsaddr` roots are now resolved (new `net/dnsaddr`, embedded snapshot as offline fallback) for the libp2p host, DHT, gossipsub direct peers, the #80 trusted floor and the quorum. Removed: the filincubator Venus peer (no DNS) and devtty.eu (TCP/QUIC time out). A peer listed with several addresses is one quorum source, not two.
- **More operators.** Mainnet RPC sources add Filfox and Ankr; calibration adds Filfox and Ankr testnet. Default sets now span 4 operators per network (chain.love, ChainSafe, Filfox, Ankr), 3 required.
- **HTTP User-Agent.** RPC clients send `lantern/<version>`. Filfox returns 403 to Go's default User-Agent, which silently dropped it from the quorum and the boot anchor.
- **Failure guidance.** init/doctor/repair print which sources failed (with operator), how many distinct operators answered vs required, and the flags that change the outcome (`--peer`, `--no-libp2p`, `--bootstrap-quorum N`). A conflict prints a separate explanation and does not suggest lowering the quorum. `lantern doctor` says when no anchor exists yet and what that means for the daemon.
- **F11: no anchor, no daemon.** `lantern daemon` no longer silently anchors via gateway+RPC agreement when init never wrote `bootstrap-anchor.json`; it refuses with instructions. `--allow-rpc-anchor` restores the old path with a loud WARN. Devnet and `--insecure-anchor` are unchanged. `lantern reset --chain-state` now points at `lantern repair` before restarting.
- **F11: stale F3 handled honestly.** Mainnet F3 is stuck at instance 466453 (epoch 5824156, ~231 days behind). The EC head-agreement fallback ignores a stale certificate instead of treating it as a cross-check (it made the single-operator "F3-consistent heaviest" rule reachable), re-samples on disagreement and compares independent operators at a common epoch when their heads straddle an epoch boundary.
- **Installer** passes no `--bootstrap-quorum` by default (uses lantern's default); `LANTERN_BOOTSTRAP_QUORUM` still overrides, now in operators.
- Reasoning and the source/operator table: TRUST-MODEL.md section 2.9.

### Bridge-off eth receipts for stock Curio-PDP
Stock Curio-PDP against a bridge-off Lantern registered and sent txs fine, but data-set creation never completed: Curio's `message_waits_eth` receipt carried `logs: []`, so `DataSetCreated event not found in receipt` / `failed to extract pieces from receipt`, and some receipts never resolved at all.

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,10 @@ cd lantern
git checkout <release tag>
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.versionTag=<release tag>" -o lantern ./cmd/lantern

# First-run wizard, including the multi-source bootstrap quorum.
# First-run wizard, including the multi-source bootstrap quorum
# (3 distinct operators must agree, no source may disagree).
# --network mainnet|calibration (default mainnet) is the same flag on every command.
# The daemon refuses to start until init (or repair) has written the trust anchor.
./lantern init --network mainnet

# Run the daemon (Lotus-compatible RPC on 127.0.0.1:1234)
Expand Down Expand Up @@ -426,6 +428,7 @@ header store), use:
```sh
lantern stop # if it's running as a service
lantern reset --chain-state # clears header store + trust anchor ONLY
lantern repair # writes a fresh quorum anchor
lantern daemon # re-syncs from live head
```

Expand Down
7 changes: 6 additions & 1 deletion RUN-PDP.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ lantern init --network calibration --no-wallet
lantern node-type pdp --network calibration
```

- `init` asks independent sources (libp2p peers and public RPC operators) for the current finalized head and refuses to write a trust anchor unless at least 3 agree. It takes about 15 s and ends with `Lantern initialised` and a list of next steps that all carry `--network calibration`.
- `init` asks independent sources (libp2p peers and public RPC operators) for the current finalized head and refuses to write a trust anchor unless at least **3 distinct operators** agree and none disagrees. Several endpoints of one operator count once (Glif and chain.love are one operator). It takes about 15 s and ends with `Lantern initialised` and a list of next steps that all carry `--network calibration`.
- If `init` fails, it prints which sources failed (and which operator runs each), how many operators answered against how many are required, and the flags that help: re-run `lantern doctor --network calibration`, add a source you trust with `--peer https://<your-node>/rpc/v1` (counts as its own operator), `--no-libp2p` if libp2p is blocked, or `--bootstrap-quorum N` to accept fewer operators. A conflict (two sources naming different finalized tipsets) is reported separately and is never fixed by lowering the quorum.
- **`lantern daemon` refuses to start until `init` (or `lantern repair --network calibration`) has written the trust anchor** (`$LANTERN_HOME/calibration/bootstrap-anchor.json`). `--allow-rpc-anchor` boots without it from a weaker gateway+RPC head agreement and prints a warning; do not use it for this setup.
- `--no-wallet`: Curio-PDP keeps its own PDP signing key in Yugabyte, so Lantern needs none.
- `node-type pdp` selects the PDP tier: a persistent 3 GiB block cache, so warm PDP / FWSS / registry contract state survives restarts.
- `init` mints API tokens under `$LANTERN_HOME/calibration/secrets/` (`token` is the admin token Curio uses).
Expand Down Expand Up @@ -389,6 +391,9 @@ Other problems you may run into:

| Symptom | Cause | Fix |
|---|---|---|
| `lantern daemon` exits with `no trust anchor at .../bootstrap-anchor.json` | `init` never completed for this network and `LANTERN_HOME`, or `lantern reset --chain-state` removed the anchor. The daemon no longer falls back silently to a gateway+RPC head. | `lantern init --network calibration --no-wallet` (or `lantern repair --network calibration`), with the same `LANTERN_HOME` as the service, then restart. If it fails, `lantern doctor --network calibration` shows which sources failed and which flags help. |
| `init` / `repair`: `quorum not reached` or `fewer countable operators than quorum` | Fewer than 3 distinct operators answered (public endpoints flake, or libp2p is blocked). | Re-run; follow the printed hints (`--peer`, `--no-libp2p`, `--bootstrap-quorum`). |
| `init` / `repair`: `conflicting finality answers` | Two sources returned different finalized tipsets for the same F3 instance: one is wrong, broken or on another network. | Do not lower the quorum. Compare against a node you run (`--peer`), re-run later, and file an operator report naming the sources. |
| `lantern daemon` syncs mainnet or `lantern info` says "not initialised" | A command was run without `--network calibration`. | Add `--network calibration` to every command and to the service. |
| Compose rejects `docker-compose.lantern.yaml` (unknown tag / invalid `depends_on`) | Compose older than v2.24.4 does not understand `!override`. | Update the Docker Compose plugin. |
| A `skiff-forest` container is running or downloading a snapshot | `up -d` was run without naming services. | `$C rm -sf forest`, then always `$C up -d --no-build yugabyte skiff`. |
Expand Down
110 changes: 108 additions & 2 deletions TRUST-MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,112 @@ headers and only checked for monotonicity, not recomputed (that needs
power state). The quorum is an eclipse and single-source defence on the
un-finalized tip; F3 finality (section 2.2) is what fully closes it.

### 2.9 Bootstrap quorum: distinct operators, zero conflicts (F10/F11)

`lantern init`, `repair`, `doctor`, the dashboard renew action and the
bridge-off auto-reset all run the same probe (`chain/bootstrap`): ask every
source for its latest F3 finality certificate in parallel, then accept a
finality only when

1. sources run by at least **3 distinct operators** (`--bootstrap-quorum`,
default 3 on mainnet and calibration) return the same (instance,
tipset key, power table), and
2. **no responder conflicts**: if any source, counted or not, names a
different tipset for an instance another source answered, the probe
fails with `ErrConflict`. It is not retried and no majority overrides it.
F3 finality is unique per instance (GPBFT safety), so a same-instance
disagreement means a source is lying, broken, or on another network.
An answer for a different instance is lag, not conflict. After the
threshold is met the probe keeps listening for 3 s so a slightly slower
conflicting answer is still seen.

**Why operators, not endpoints.** The pre-F10 rule was "5 sources agree",
counted per endpoint. On 2026-10-26 the reachable mainnet sources were the
Glif RPC, the chain.love RPC, the chain.love libp2p bootstrap peer and one
ChainSafe peer. Glif is served from chain.love infrastructure (same IP
46.229.170.7 as api.chain.love, `x-chainlove-lb-*` load-balancer headers, the
same non-release Lotus build, a bare HEAD redirects to filecoin.chain.love),
so three of those four "independent" votes were one operator, and
`--bootstrap-quorum 4` (the workaround users reached for) meant **two**
operators. Counting operators is strictly stronger even with a lower
number: today's default needs 3 different organisations, where the old
default of 5 could be met by 2.

**How a source is attributed** (`bootstrap.OperatorOf`,
`OperatorOfMultiaddr`):

* RPC endpoints: the registrable domain of the URL host, folded through a
small evidence-backed alias table (`glif.io -> chain.love`,
`chainsafe.dev -> chainsafe-fil.io`).
* libp2p bootstrap peers: the DNS domain in the bootstrap multiaddr. Peers
that come from a `/dnsaddr` root are attributed to the domain that
publishes the TXT records. A peer listed with several addresses is one
source. A bare IP counts as its own operator; a bare peer ID as itself.
* `--peer` URLs: the URL's domain, so a node you run yourself is its own
operator and adds a vote.
* The Lantern gateway is not counted unless `--count-gateway`; it votes as its
upstream (Glif, therefore chain.love).

This is a heuristic. It cannot prove two domains are different
organisations, only collapse the duplicates we know about. Every alias in
the table carries the evidence and the date it was observed.

**Default source set (verified 2026-10-26).** Bootstrap peers are Lotus
v1.38.3's `build/bootstrap/*.pi` verbatim; `/dnsaddr` roots are resolved at
runtime with an embedded snapshot as the offline fallback.

| Network | Operator | Sources |
|---|---|---|
| mainnet | chain.love | api.chain.love RPC, api.node.glif.io RPC, bootstrap.filecoin.chain.love peer |
| mainnet | chainsafe-fil.io | bootstrap-mainnet-{0,1,2} peers (only -1 serves F3 cert-exchange today) |
| mainnet | filfox.info | filfox.info RPC |
| mainnet | ankr.com | rpc.ankr.com/filecoin RPC |
| calibration | chain.love | api.calibration.node.glif.io RPC, bootstrap.calibration.filecoin.chain.love peer |
| calibration | chainsafe-fil.io | bootstrap-calibnet-{0,1,2} peers (only -1 serves F3 cert-exchange today) |
| calibration | filfox.info | calibration.filfox.info RPC |
| calibration | ankr.com | rpc.ankr.com/filecoin_testnet RPC |

Four operators per network, three required: one can be down. Dropped in
the refresh: the filincubator Venus peer (no DNS record) and the devtty.eu
peer (TCP and QUIC time out). Some public endpoints reject Go's default
HTTP User-Agent (filfox answered 403), so Lantern identifies itself as
`lantern/<version>`.

**Stale F3.** Mainnet F3 has been stuck at instance 466453 (epoch 5824156)
since spring 2026, about 231 days behind the live head on 2026-10-26. The
probe still reaches agreement on that certificate, and #167 then refuses to
anchor on it: init falls back to EC head agreement at the live head. In
that fallback a stale certificate is now ignored entirely (F11), because
"consistent with F3" is vacuous for a months-old finality: every live head
is a descendant. Without that, the old "F3-consistent heaviest" rule
accepted a single operator's head. The EC fallback requires 2 distinct
operators to agree on (state root, tipset key), compared at a common epoch
a few epochs below the lowest sampled head when the heads straddle an epoch
boundary. That is a lower bar than the 3-operator F3 quorum; it is the bar
the #54 boot anchor already used, and it is documented in
`bootstrap-anchor.json` as `"source": "ec-multi-source"`. Calibration F3 is live, so
calibration anchors on the F3 quorum directly.

**No anchor, no daemon (F11).** `lantern daemon` used to fall back silently
to a gateway+RPC head when init had not written an anchor. That path is
weaker than init's quorum, so the daemon now refuses to start without
`bootstrap-anchor.json` and tells the operator to run `lantern init` (or
`lantern doctor` to see what failed). `--allow-rpc-anchor` restores the old path
with a loud warning. Devnet and `--insecure-anchor` keep their existing explicit
single-endpoint behaviour.

**Tradeoffs, stated plainly.**

* Liveness vs safety: a single misbehaving default source can block
bootstrap (any conflict fails). It cannot steer it. Failing closed is the
intended behaviour; `--peer` lets an operator add a source they trust.
* 3 operators is the floor for a fresh install. `--bootstrap-quorum 2` or `1`
is possible and the failure hint offers it, but each step down is one
fewer organisation an attacker has to compromise.
* Independence of Ankr's Filecoin backend is not provable from outside. It
runs a different Lotus build from Glif/chain.love and Filfox, and it is
never needed alone: the other three operators already meet the default.

---

## 3. What Lantern does NOT trust
Expand All @@ -207,8 +313,8 @@ of the following as input that must clear local verification before use.

### 3.1 RPC providers (Glif, infura, etc.)

The default daemon uses `gateway.lantern.reiers.io` for the initial
trusted-head fetch and Glif as a fallback. Both responses are immediately
The daemon boots from the anchor written by `lantern init` (section 2.9) and
uses `gateway.lantern.reiers.io` and Glif for cold blocks and fallback reads. Both responses are immediately
CID-verified locally; if a provider lies about the state root, the
verifier rejects the response. **Lantern never returns unverified data
to its caller, no matter what an upstream RPC says.**
Expand Down
11 changes: 6 additions & 5 deletions build/bootstrap_calibnet.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// Calibration libp2p bootstrap peers. Copied verbatim from
// github.com/filecoin-project/lotus/build/bootstrap/calibnet.pi at tag
// v1.36.0 (2026-05-23 verified). See LICENSE.
// v1.38.3 (refreshed 2026-10-26, F10). See LICENSE.
//
// These are the seed peers Lantern dials on startup to enter the Filecoin
// calibration DHT + gossipsub mesh. The calibration network name is
// "calibrationnet" (confirmed live against api.calibration.node.glif.io
// 2026-05-23) — note this differs from mainnet's "testnetnet" wire-name.
// 2026-05-23), which differs from mainnet's "testnetnet" wire-name.
//
// The /dnsaddr root is expanded at runtime by net/dnsaddr, with
// BootstrapDNSAddrSnapshot (bootstrap_mainnet.go) as the offline fallback.

package build

// CalibnetBootstrapPeers is the canonical calibration bootstrap multiaddr list.
var CalibnetBootstrapPeers = []string{
"/dns/bootstrap.calibration.filecoin.chain.love/tcp/1237/p2p/12D3KooWQPYouEAsUQKzvFUA9sQ8tz4rfpqtTzh2eL6USd9bwg7x",
"/dns/bootstrap-calibnet-0.chainsafe-fil.io/tcp/34000/p2p/12D3KooWABQ5gTDHPWyvhJM7jPhtNwNJruzTEo32Lo4gcS5ABAMm",
"/dns/bootstrap-calibnet-1.chainsafe-fil.io/tcp/34000/p2p/12D3KooWS3ZRhMYL67b4bD5XQ6fcpTyVQXnDe8H89LvwrDqaSbiT",
"/dns/bootstrap-calibnet-2.chainsafe-fil.io/tcp/34000/p2p/12D3KooWEiBN8jBX8EBoM3M47pVRLRWV812gDRUJhMxgyVkUoR48",
"/dnsaddr/bootstrap-calibnet.chainsafe-fil.io",
}

// CalibnetGossipTopicMessages is the calibration message-pool gossipsub topic.
Expand Down
Loading
Loading