mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
76 lines
3.2 KiB
Go
76 lines
3.2 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// bootstrap_retry_test.go — which failures end initial sync, and which are just a bad round.
|
|
//
|
|
// Initial sync is the ONLY path that can close a gap wider than one served window;
|
|
// the live catch-up path cannot, by construction. So a failure that ends initial
|
|
// sync permanently ends the node's ability to rejoin, and the distinction between
|
|
// "this round found nobody" and "this chain cannot be reached from here" is the
|
|
// difference between a pause and a brick.
|
|
|
|
package chains
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
chainbootstrap "github.com/luxfi/consensus/engine/chain/bootstrap"
|
|
)
|
|
|
|
// TestAStalledRoundIsRetried is the failure this file exists for.
|
|
//
|
|
// ErrStalled says no peer served the ancestry THIS ROUND — a statement about one
|
|
// pass over a sampled set, not about the chain. The peers that could serve may be
|
|
// busy, mid-restart, or simply not the ones this round drew. Ending initial sync
|
|
// on it leaves the node with only the live catch-up path, which cannot close a
|
|
// deep gap, so it re-asks forever and never rejoins.
|
|
func TestAStalledRoundIsRetried(t *testing.T) {
|
|
if !isRetryableBootstrapFailure(chainbootstrap.ErrStalled) {
|
|
t.Fatal("a stalled round ended initial sync — the node keeps only the live catch-up path, " +
|
|
"which cannot close a gap wider than one served window, so it can never rejoin")
|
|
}
|
|
}
|
|
|
|
// TestAStalledRoundIsRetriedWhenWrapped: the caller sees whatever the bootstrapper
|
|
// wrapped it in, so the predicate must match on the sentinel rather than the value.
|
|
func TestAStalledRoundIsRetriedWhenWrapped(t *testing.T) {
|
|
wrapped := fmt.Errorf("attempt 3: %w", chainbootstrap.ErrStalled)
|
|
if !isRetryableBootstrapFailure(wrapped) {
|
|
t.Fatal("a wrapped stalled round was not recognised — errors.Is must see through the wrap")
|
|
}
|
|
}
|
|
|
|
// TestConnectivityFailuresStayRetryable: the two that were already retried, so a
|
|
// change to the predicate cannot quietly drop them.
|
|
func TestConnectivityFailuresStayRetryable(t *testing.T) {
|
|
for _, err := range []error{
|
|
chainbootstrap.ErrBeaconsUnreachable,
|
|
chainbootstrap.ErrNoBeaconQuorum,
|
|
} {
|
|
if !isRetryableBootstrapFailure(err) {
|
|
t.Fatalf("%v must stay retryable — it is a transient quorum condition", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAGapTooDeepIsNotRetried is the other half, and it is the one that keeps this
|
|
// honest: retrying must not become "retry everything". A gap wider than the
|
|
// in-memory window needs a different mechanism, and re-running the same pass
|
|
// cannot produce one — so it is surfaced for the operator rather than looped on.
|
|
func TestAGapTooDeepIsNotRetried(t *testing.T) {
|
|
if isRetryableBootstrapFailure(chainbootstrap.ErrGapTooLarge) {
|
|
t.Fatal("a gap too deep for the window was retried — a retry cannot fix it, " +
|
|
"and looping hides the one failure an operator has to act on")
|
|
}
|
|
}
|
|
|
|
// TestAnUnknownFailureIsNotRetried: the predicate lists what it knows. An error it
|
|
// has never seen is surfaced, not swallowed into an infinite loop.
|
|
func TestAnUnknownFailureIsNotRetried(t *testing.T) {
|
|
if isRetryableBootstrapFailure(errors.New("something nobody has classified")) {
|
|
t.Fatal("an unclassified failure was retried forever instead of being surfaced")
|
|
}
|
|
}
|