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.
91 lines
3.8 KiB
Go
91 lines
3.8 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// red_unsolicited_ancestors_test.go — the reply lane answers to a number, not to a peer.
|
|
//
|
|
// A bootstrap fetch goes out to a sampled set of staked beacons. The reply comes back
|
|
// through handleContext, which knows who sent it, and is handed to
|
|
// deliverBootstrapAncestors, which does not take a sender at all. So the batch the
|
|
// descent works on is whichever framed payload carries the right requestID first —
|
|
// beacon or bystander. Past initial sync there is not even that much: pendingContext
|
|
// records the {nodeID, requestID} of every fetch this node sends, keyed by the block it
|
|
// wants, and nothing reads it back — the TTL reaper, the cap and the no-peer release
|
|
// clear it, a reply never does.
|
|
//
|
|
// A bootstrapper that indexes by id alone cannot express this:
|
|
// Ancestors() looks up {NodeID, RequestID} in outstandingRequests and drops anything
|
|
// that is not a pair it created.
|
|
//
|
|
// The consequence lives one layer up. consensus/engine/chain/bootstrap/bootstrapper.go
|
|
// abandons a pass whose batch does not contain the block it asked for, and 60 abandoned
|
|
// passes in a row is ErrStalled — which chains/bootstrap_sync.go
|
|
// isRetryableBootstrapFailure declines to retry, so the manager stops the engine.
|
|
// TestRED_ForgedAncestryUnderHonestFrontierRejected already pins that half: off-path
|
|
// ancestry finalizes nothing and stalls. This pins the half that lets an arbitrary peer
|
|
// supply it.
|
|
package chains
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
)
|
|
|
|
// frameAncestors wraps block payloads the way the Ancestors responder does, so the
|
|
// bootstrap lane decodes them as a real batch.
|
|
func frameAncestors(blocks ...[]byte) []byte {
|
|
var out []byte
|
|
for _, b := range blocks {
|
|
entry := encodeCatchupEntry(b, nil)
|
|
var length [4]byte
|
|
binary.BigEndian.PutUint32(length[:], uint32(len(entry)))
|
|
out = append(out, length[:]...)
|
|
out = append(out, entry...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// TestRED_UnsolicitedAncestorsFeedTheBootstrapDescent: a peer that was never sampled
|
|
// answers a bootstrap fetch, and the descent consumes its batch.
|
|
func TestRED_UnsolicitedAncestorsFeedTheBootstrapDescent(t *testing.T) {
|
|
chain, _ := buildBSChain(4, -1)
|
|
bh := &blockHandler{
|
|
logger: log.NewNoOpLogger(),
|
|
vm: newBSVMAt(chain, 0),
|
|
bsAncestorCh: make(map[uint32]chan []bootstrapFetchedBlock),
|
|
}
|
|
bh.bsActive.Store(true)
|
|
|
|
// What Ancestors() registers before sending GetAncestors to its beacon sample.
|
|
const requestID = 7
|
|
waiter := make(chan []bootstrapFetchedBlock, bootstrapAncestorSample)
|
|
bh.bsAncestorCh[requestID] = waiter
|
|
|
|
// Nobody asked this node for anything. It is not a beacon, not in the sample, and
|
|
// on a default fleet (DefaultNetworkRequireValidatorToConnect is false) it does not
|
|
// need stake to be connected and routed here.
|
|
bystander := ids.GenerateTestNodeID()
|
|
junk := frameAncestors([]byte("not a block"))
|
|
|
|
// Ask the lane directly: whether an unasked peer's reply reaches the descent is a
|
|
// property of the lane, and routing through handleContext would also drive the live
|
|
// cert path, which this handler is not built for.
|
|
if bh.deliverBootstrapAncestors(bystander, requestID, junk) {
|
|
t.Fatalf("the bootstrap lane CLAIMED a reply from a peer it never asked")
|
|
}
|
|
|
|
select {
|
|
case batch := <-waiter:
|
|
t.Logf("the bootstrap descent is holding %d entr(ies) from a peer it never asked: %q",
|
|
len(batch), batch[0].Bytes)
|
|
t.Fatalf("an Ancestors reply from an unsampled peer reached the bootstrap descent — " +
|
|
"the lane correlates on requestID alone, so any connected peer can answer a fetch " +
|
|
"and abandon the pass (60 in a row is the terminal ErrStalled)")
|
|
default:
|
|
// Correct behavior: the reply names a peer this fetch never asked, so it is not
|
|
// this lane's batch.
|
|
}
|
|
}
|