Skip to content

fullhistory: bootstrap frontfill-only from a history-archive tip (#833)#850

Draft
chowbao wants to merge 3 commits into
feature/full-historyfrom
feat/fullhistory-frontfill-tip-833
Draft

fullhistory: bootstrap frontfill-only from a history-archive tip (#833)#850
chowbao wants to merge 3 commits into
feature/full-historyfrom
feat/fullhistory-frontfill-tip-833

Conversation

@chowbao

@chowbao chowbao commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Closes #833.

A frontfill-only deployment ([backfill.datastore].type empty) had no tip source on first start: the only source was notConfiguredTip, which always errors, so no earliest_ledger form could pin a floor — even though captive core could ingest from the pinned floor once it exists.

One tipSampler, one binding site. All network-tip sampling now goes through a single tipSampler (Sample(ctx), retry defaults bound at construction), replacing the (NetworkTipBackend, TipBackoff, TipMaxAttempts) trio previously threaded through validateConfig, resolveEarliestFirstStart, StartConfig, and backfillToTip. The retry semantics are the old networkTip helper's, moved verbatim: cenkalti/backoff constant interval, count-bounded, sub-genesis tip is backoff.Permanent ("not ready"), ctx cancellation aborts the wait.

Lake first, archives as fallback. The sampler tries its sources in order: the bulk lake frontier (backend.Tip) when a datastore is configured, then the history archives' root HAS (GetRootHAS().CurrentLedger). For a frontfill-only daemon the archives are the sole source — and how it bootstraps its earliest_ledger pin. The archive URLs are the same [ingestion].history_archive_urls captive core already needs; no new config. The archives' one-checkpoint (64-ledger) lag is absorbed by the existing anchor = max(tip, lastCommitted) and the signed withinOneChunkOfTip, so no lag handling is added.

Synthetic tip deleted. Because the sampler falls back to the archives, an unavailable tip now means no source was reachable at all — so the tip = lastCommitted degraded mode in backfillToTip is gone. A tip failure errors the pass and the supervisor restarts (a transient outage self-heals on re-sample); the daemon never serves behind an unknown frontier.

Not a Backend. The issue sketched a captiveSource Backend, but a frontfill-only deployment never freezes bulk history, so it needs no LedgerStream — a Tip-only Backend would be a half-implemented interface. The archive tip is a plain tip source at the daemon layer instead; backfill.Backend keeps its one real implementation (bsbSource), and notConfiguredTip/backendTip are deleted.

Tests: frontfill bootstrap through buildTipSampler with a fake root-HAS archive, source ordering (lake preferred, archive fallback when the lake is down, all-down exhausts), the retry/sub-genesis/ctx-cancel suite carried over onto Sample, and TestBackfill_RestartTipUnreachableErrors replacing the deleted degraded-mode test. Full fullhistory -short suite and -race on the fullhistory package pass.

Deliberate skip: the archive pool is built without a network passphrase, so the SDK's HAS passphrase check is off. Wiring it would reorder daemon construction (the passphrase is peeked from the captive-core TOML after validateConfig, the sampler is needed before it), and a wrong-network archive URL already fails loudly at live ingestion — captive core consumes the same [ingestion].history_archive_urls — with recovery being a wipe of the still-empty first-start data dir.

@chowbao chowbao force-pushed the chore/fullhistory-hardening-840 branch 3 times, most recently from 8a10b76 to 604a021 Compare July 13, 2026 17:18
Base automatically changed from chore/fullhistory-hardening-840 to feature/full-history July 13, 2026 19:46
chowbao added 2 commits July 13, 2026 15:48
A frontfill-only deployment (no [backfill.datastore]) had no tip source on
first start: the only source was notConfiguredTip, which always errored, so no
earliest_ledger form could pin a floor even though captive core could ingest
from it once pinned.

Consolidate all network-tip sampling into one tipSampler (Sample(ctx), retry
defaults bound at construction), replacing the (NetworkTipBackend, TipBackoff,
TipMaxAttempts) trio threaded through validateConfig, resolveEarliestFirstStart,
StartConfig, and backfillToTip. The sampler queries its sources in order and
returns the first that answers:

  - the bulk lake frontier (backend.Tip) when a datastore is configured, and
  - the history archives' root HAS (GetRootHAS().CurrentLedger) as the fallback,
    and the sole source for a frontfill-only daemon.

The archive URLs are the same [ingestion].history_archive_urls captive core
already needs — no new config. Because the sampler now falls back to the
archives, an unavailable tip means no source was reachable at all, so the
synthetic tip = lastCommitted degraded mode in backfillToTip is deleted: on a
tip failure the pass errors and the supervisor restarts. The archives' one-
checkpoint lag is absorbed by the existing anchor = max(tip, lastCommitted) and
the signed withinOneChunkOfTip, so no lag handling is added.
@chowbao chowbao force-pushed the feat/fullhistory-frontfill-tip-833 branch from f707825 to 914cabb Compare July 13, 2026 19:49
…ured

A frontfill-only daemon with no [ingestion].history_archive_urls previously
built an empty sampler and only died later at captive-core open, with a less
on-point message; buildTipSampler now rejects the misconfiguration at startup.
Also note at the sub-genesis gate that it is source-order-blind: a source must
error (not return 0) when empty or it shadows a healthy fallback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant