mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
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]>
163 lines
4.6 KiB
Go
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)
|
|
}
|