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]>
131 lines
5.4 KiB
Go
131 lines
5.4 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// scheme_gate.go — single cross-axis gate between an incoming peer's
|
|
// NodeIDScheme byte and the chain's pinned ValidatorSchemeID. The gate
|
|
// is the primitive; callers that turn a wire NodeID into a "this peer
|
|
// is admissible on this chain" decision call Classify and pass the
|
|
// site name so the refused-by log line names which boundary refused.
|
|
//
|
|
// Sites that funnel through this gate:
|
|
//
|
|
// "handshake" — peer TLS upgrade (network/peer/upgrader.go)
|
|
// "proposer" — proposervm block proposer attribution
|
|
// "validator" — platformvm validator registration
|
|
// "mempool-sender" — mempool signed-tx submission
|
|
//
|
|
// Forward-only PQ: there is no transition window and no classical
|
|
// fallback. Every site funnels through Classify which admits only the
|
|
// pinned PQ NodeIDScheme byte (ML-DSA-65 today); anything else — a
|
|
// classical secp256k1 byte, a cross-PQ scheme like ML-DSA-87 on a chain
|
|
// pinning ML-DSA-65, or an unknown byte — is refused at the gate.
|
|
|
|
package peer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
consensusconfig "github.com/luxfi/consensus/config"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// SchemeGate is the chain-scoped policy object that decides whether a
|
|
// peer's NodeIDScheme is admissible under the chain's pinned profile.
|
|
// One SchemeGate per chain; created at chain-bootstrap from the chain's
|
|
// ChainSecurityProfile and pinned for the chain's lifetime (no
|
|
// re-derivation in the hot path).
|
|
//
|
|
// Forward-only PQ: the gate has no transition window and no operator
|
|
// classical-compat escape hatch. Every Classify call admits only the
|
|
// pinned PQ scheme byte; classical secp256k1 NodeIDs and cross-PQ
|
|
// bytes are refused at every height.
|
|
type SchemeGate struct {
|
|
// Profile is the chain's locked ChainSecurityProfile. The pinned
|
|
// ValidatorSchemeID is read through profile.ValidatorSchemeID().
|
|
Profile *consensusconfig.ChainSecurityProfile
|
|
|
|
// ActivationHeight is preserved for symmetry with the per-chain
|
|
// fork-height bookkeeping; the gate's policy is the same on both
|
|
// sides of it (PQ-only). Reserved for downstream callers that want
|
|
// a single field to log "where strict-PQ took effect" alongside
|
|
// other hardfork heights — the gate itself does not branch on it.
|
|
ActivationHeight uint64
|
|
}
|
|
|
|
// NewSchemeGate constructs a SchemeGate. The profile pointer MUST be
|
|
// non-nil and MUST have already passed Validate at chain-bootstrap;
|
|
// the gate does not re-validate the profile on each call.
|
|
func NewSchemeGate(
|
|
profile *consensusconfig.ChainSecurityProfile,
|
|
activationHeight uint64,
|
|
) (*SchemeGate, error) {
|
|
if profile == nil {
|
|
return nil, fmt.Errorf("%w: profile is nil", ErrSchemeGateConfig)
|
|
}
|
|
return &SchemeGate{
|
|
Profile: profile,
|
|
ActivationHeight: activationHeight,
|
|
}, nil
|
|
}
|
|
|
|
// Classify is the entry point for every cross-axis check. It takes the
|
|
// bare NodeID, the scheme byte derived by the caller (handshake / proposer
|
|
// / validator / mempool), the block height we are mounting against, and
|
|
// a site tag for the log line; it returns a TypedNodeID stamped with the
|
|
// scheme byte once the chain's policy admits the pair.
|
|
//
|
|
// Forward-only PQ: only the pinned PQ scheme byte is admitted. A
|
|
// classical secp256k1 byte, a cross-PQ byte (ML-DSA-87 on a chain
|
|
// pinning ML-DSA-65), or an unknown byte is refused at every height.
|
|
//
|
|
// site is a free-form tag ("handshake", "proposer", "validator",
|
|
// "mempool-sender" are the conventional values) included verbatim in
|
|
// the refused-by error so a log reader knows which boundary refused.
|
|
func (g *SchemeGate) Classify(
|
|
nodeID ids.NodeID,
|
|
derivedScheme ids.NodeIDScheme,
|
|
height uint64,
|
|
site string,
|
|
) (ids.TypedNodeID, error) {
|
|
if !derivedScheme.IsKnown() {
|
|
return ids.TypedNodeID{}, fmt.Errorf("%w: site=%s scheme=0x%02x",
|
|
ErrSchemeGateUnknownScheme, site, byte(derivedScheme))
|
|
}
|
|
|
|
// PQ-only: defer to the profile's cross-axis check with the
|
|
// classical-compat escape hatch pinned off. The profile refuses
|
|
// classical schemes; this gate refuses them at every height.
|
|
presentedSig := consensusconfig.SigSchemeID(derivedScheme)
|
|
if err := g.Profile.AcceptsValidatorScheme(presentedSig, false); err != nil {
|
|
return ids.TypedNodeID{}, fmt.Errorf("%w: site=%s height=%d: %w",
|
|
ErrSchemeGateMismatch, site, height, err)
|
|
}
|
|
|
|
typed, err := ids.NewTypedNodeID(derivedScheme, nodeID)
|
|
if err != nil {
|
|
return ids.TypedNodeID{}, fmt.Errorf("%w: site=%s: %w",
|
|
ErrSchemeGateMismatch, site, err)
|
|
}
|
|
return typed, nil
|
|
}
|
|
|
|
// Typed validation errors. Distinct from the consensus / ids errors so
|
|
// a log reader can tell exactly which boundary refused.
|
|
var (
|
|
// ErrSchemeGateConfig — the gate could not be constructed
|
|
// (nil profile, invalid configuration).
|
|
ErrSchemeGateConfig = errors.New("peer: SchemeGate misconfigured")
|
|
|
|
// ErrSchemeGateMismatch — a peer / proposer / validator /
|
|
// mempool sender presented a NodeIDScheme byte the chain's
|
|
// SchemeGate refuses. Wraps the underlying mismatch reason from
|
|
// consensus/config.AcceptsValidatorScheme or ids.NewTypedNodeID.
|
|
ErrSchemeGateMismatch = errors.New("peer: NodeIDScheme refused by SchemeGate")
|
|
|
|
// ErrSchemeGateUnknownScheme — a wire input named a NodeIDScheme
|
|
// byte this build does not understand. Always refused, regardless
|
|
// of profile class or activation height.
|
|
ErrSchemeGateUnknownScheme = errors.New("peer: NodeIDScheme byte is unknown")
|
|
)
|