Files
zeekayandHanzo Dev 37e2940ead mesh is the peer layer, network is the validator set
Two packages were named for the same word and meant different things. network/
dialed peers, exchanged keys, throttled and tracked them; nets/ tracked which
chains share a validator set and when they have all bootstrapped. One letter
apart, and neither name said which was which.

The peer layer is mesh — the word this estate already uses for it, in
lux-rs/node's own mesh.rs. That frees network for the thing that is actually a
network: a set of chains sharing a validator set. A network holds chains; a
chain belongs to one network.

github.com/luxfi/p2p stays what it is, the gossip library the VMs use. Three
concerns, three names, none of them borrowed from another.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 21:17:09 -07:00

163 lines
4.6 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package network
import (
"sync"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
)
type chain struct {
lock sync.RWMutex
bootstrapping set.Set[ids.ID]
bootstrapped set.Set[ids.ID]
once sync.Once
bootstrappedSema chan struct{}
config Config
myNodeID ids.NodeID
}
var _ Net = (*chain)(nil)
type Allower interface {
// IsAllowed filters out nodes that are not allowed to connect to this chain
IsAllowed(nodeID ids.NodeID, isValidator bool) bool
}
// Net is a set of chains that share a validator set and bootstrap together. It
// is bootstrapped when none of its chains still is.
//
// A network holds chains; a chain belongs to one network. The two are not the
// same shape and neither contains the other recursively.
type Net interface {
// IsBootstrapped returns true if the chains in this chain are done bootstrapping
IsBootstrapped() bool
// Bootstrapping returns the IDs of the chains in this net that have NOT yet
// finished initial sync. The net owns the bootstrapping set, so it is the
// net — not its caller — that can name those chains.
Bootstrapping() []ids.ID
// IsChainBootstrapped reports whether a SPECIFIC chain in this net has finished
// initial sync — i.e. Bootstrapped(chainID) was called for it (the chain reached
// the network frontier and its VM went to normal operation). This is the per-chain
// truth that info.isBootstrapped keys on, distinct from IsBootstrapped() which is
// the net-wide "no chain still bootstrapping" aggregate. A chain that is merely
// tracked (added, sync goroutine launched) but has NOT converged is still in the
// bootstrapping set and reads false here — closing the premature-true masking bug.
IsChainBootstrapped(chainID ids.ID) bool
// Bootstrapped marks the chain as done bootstrapping
Bootstrapped(chainID ids.ID)
// OnBootstrapCompleted is called when bootstrapping completes
OnBootstrapCompleted() error
// AddChain adds a chain to this Net
AddChain(chainID ids.ID) bool
// Config returns config of this Net
Config() Config
Allower
}
func New(myNodeID ids.NodeID, config Config) Net {
return &chain{
bootstrapping: make(set.Set[ids.ID]),
bootstrapped: make(set.Set[ids.ID]),
bootstrappedSema: make(chan struct{}),
config: config,
myNodeID: myNodeID,
}
}
func (s *chain) IsBootstrapped() bool {
s.lock.RLock()
defer s.lock.RUnlock()
return s.bootstrapping.Len() == 0
}
func (s *chain) Bootstrapping() []ids.ID {
s.lock.RLock()
defer s.lock.RUnlock()
return s.bootstrapping.List()
}
// IsChainBootstrapped assumes MONOTONIC per-process bootstrapped state: a chain
// only ever moves bootstrapping→bootstrapped (Bootstrapped is forward-only and
// AddChain refuses to re-add a chain already in either set), so this signal — and
// the Bootstrapping() health check that shares the set — never report stale-true.
// INVARIANT: if a future path ever moves a live chain BACK to bootstrapping (e.g.
// SetState(Bootstrapping) on a running chain) it MUST remove the chainID from
// bootstrapped, or both this signal and the readiness health check will report a
// re-syncing chain as still bootstrapped.
func (s *chain) IsChainBootstrapped(chainID ids.ID) bool {
s.lock.RLock()
defer s.lock.RUnlock()
return s.bootstrapped.Contains(chainID)
}
func (s *chain) Bootstrapped(chainID ids.ID) {
s.lock.Lock()
defer s.lock.Unlock()
s.bootstrapping.Remove(chainID)
s.bootstrapped.Add(chainID)
if s.bootstrapping.Len() > 0 {
return
}
s.once.Do(func() {
close(s.bootstrappedSema)
})
}
func (s *chain) AllBootstrapped() <-chan struct{} {
return s.bootstrappedSema
}
func (s *chain) OnBootstrapCompleted() error {
// Mark net as having completed bootstrap
// This is called when all chains in the net have bootstrapped
return nil
}
func (s *chain) OnBootstrapStarted() error {
// Mark net as starting bootstrap
return nil
}
func (s *chain) AddChain(chainID ids.ID) bool {
s.lock.Lock()
defer s.lock.Unlock()
if s.bootstrapping.Contains(chainID) || s.bootstrapped.Contains(chainID) {
return false
}
s.bootstrapping.Add(chainID)
return true
}
func (s *chain) Config() Config {
return s.config
}
func (s *chain) IsAllowed(nodeID ids.NodeID, isValidator bool) bool {
// Case 1: NodeID is this node
// Case 2: This net is not validator-only chain
// Case 3: NodeID is a validator for this chain
// Case 4: NodeID is explicitly allowed whether it's net validator or not
return nodeID == s.myNodeID ||
!s.config.ValidatorOnly ||
isValidator ||
s.config.AllowedNodes.Contains(nodeID)
}