mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
chains: complete proposervm wrap — define shouldWrapInProposerVM + test
The prior commit (9d9d2a8785) captured only manager.go, which CALLS
shouldWrapInProposerVM but did not include its definition, so HEAD did not
compile (chains/manager.go:1205: undefined: shouldWrapInProposerVM). This adds:
- chains/quorum.go: shouldWrapInProposerVM(k, chainID, innerIsDAGNative) —
the single pure policy gate (K>1 AND not P-Chain AND not DAG-native) that
decides whether a linear chain.ChainVM is wrapped in proposervm for
single-proposer-per-height block production.
- chains/proposervm_wrap_test.go: table tests pinning the gate (C-Chain
devnet/mainnet wrapped; P-Chain, X-Chain, K==1 excluded) and that the K
flows from selectConsensusParams.
- chains/manager.go: gofmt import ordering.
Restores a green build. No push, no tag.
This commit is contained in:
+1
-1
@@ -76,8 +76,8 @@ import (
|
||||
"github.com/luxfi/utxo/mldsafx"
|
||||
"github.com/luxfi/utxo/nftfx"
|
||||
|
||||
"github.com/luxfi/utxo/propertyfx"
|
||||
"github.com/luxfi/node/vms/proposervm"
|
||||
"github.com/luxfi/utxo/propertyfx"
|
||||
"github.com/luxfi/utxo/schnorrfx"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
"github.com/luxfi/utxo/secp256r1fx"
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// proposervm_wrap_test.go — pins the single policy gate that decides whether a
|
||||
// linear chain.ChainVM is re-wrapped in proposervm for single-proposer-per-height
|
||||
// block production (the consensus-safety fix for the equivocation crash). The
|
||||
// gate is a pure function so the policy is verifiable without standing up a chain.
|
||||
package chains
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
func TestShouldWrapInProposerVM(t *testing.T) {
|
||||
// A native chain ID that is NOT the P-Chain (e.g. the C-Chain): first 31
|
||||
// bytes zero, last byte the chain letter. Any non-platform ID exercises the
|
||||
// chainID condition; this mirrors how native chain IDs are shaped.
|
||||
cChainID := ids.ID{}
|
||||
cChainID[ids.IDLen-1] = 'C'
|
||||
xChainID := ids.ID{}
|
||||
xChainID[ids.IDLen-1] = 'X'
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
k int
|
||||
chainID ids.ID
|
||||
innerIsDAGNative bool
|
||||
want bool
|
||||
why string
|
||||
}{
|
||||
{
|
||||
name: "C-Chain devnet K=4",
|
||||
k: 4, // LocalBFTParams
|
||||
chainID: cChainID,
|
||||
innerIsDAGNative: false,
|
||||
want: true,
|
||||
why: "multi-validator EVM, not the P-Chain, not DAG — the chain that crashed; must be wrapped",
|
||||
},
|
||||
{
|
||||
name: "C-Chain mainnet K=21",
|
||||
k: 21, // MainnetParams
|
||||
chainID: cChainID,
|
||||
innerIsDAGNative: false,
|
||||
want: true,
|
||||
why: "large multi-validator EVM is wrapped exactly as avalanchego wraps it",
|
||||
},
|
||||
{
|
||||
name: "P-Chain is excluded even at K>1",
|
||||
k: 4,
|
||||
chainID: constants.PlatformChainID,
|
||||
innerIsDAGNative: false,
|
||||
want: false,
|
||||
why: "P-Chain validator state is published mid-create; its windower would be empty — keep newPChainHeightVM",
|
||||
},
|
||||
{
|
||||
name: "X-Chain (DAG-native) is excluded",
|
||||
k: 4,
|
||||
chainID: xChainID,
|
||||
innerIsDAGNative: true,
|
||||
want: false,
|
||||
why: "linearized DAG VM uses a push-notification bridge that does not compose with proposervm's window",
|
||||
},
|
||||
{
|
||||
name: "single-node K=1 is not wrapped",
|
||||
k: 1, // SingleValidatorParams
|
||||
chainID: cChainID,
|
||||
innerIsDAGNative: false,
|
||||
want: false,
|
||||
why: "one validator is trivially the sole proposer; no equivocation to prevent",
|
||||
},
|
||||
{
|
||||
name: "K=1 P-Chain is not wrapped",
|
||||
k: 1,
|
||||
chainID: constants.PlatformChainID,
|
||||
innerIsDAGNative: false,
|
||||
want: false,
|
||||
why: "K==1 short-circuits regardless of chain",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := shouldWrapInProposerVM(tt.k, tt.chainID, tt.innerIsDAGNative)
|
||||
if got != tt.want {
|
||||
t.Fatalf("shouldWrapInProposerVM(k=%d, chainID=%s, dag=%v) = %v, want %v — %s",
|
||||
tt.k, tt.chainID, tt.innerIsDAGNative, got, tt.want, tt.why)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestShouldWrapInProposerVM_FlowsFromSelectedParams proves the K the gate reads
|
||||
// is the one selectConsensusParams produces per network, so the wrap decision is
|
||||
// consistent with the BFT committee actually chosen: every sybil-protected
|
||||
// network yields K>1 (C-Chain wrapped), and single-node yields K==1 (not wrapped).
|
||||
func TestShouldWrapInProposerVM_FlowsFromSelectedParams(t *testing.T) {
|
||||
cChainID := ids.ID{}
|
||||
cChainID[ids.IDLen-1] = 'C'
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
sybilProtection bool
|
||||
networkID uint32
|
||||
wantWrapCChain bool
|
||||
}{
|
||||
{"single-node dev", false, constants.LocalID, false},
|
||||
{"devnet sybil", true, constants.DevnetID, true},
|
||||
{"localnet sybil", true, constants.LocalID, true},
|
||||
{"mainnet", true, constants.MainnetID, true},
|
||||
{"testnet", true, constants.TestnetID, true},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
params := selectConsensusParams(c.sybilProtection, c.networkID)
|
||||
got := shouldWrapInProposerVM(params.K, cChainID, false)
|
||||
if got != c.wantWrapCChain {
|
||||
t.Fatalf("network %s (sybil=%v): K=%d → wrap=%v, want %v",
|
||||
c.name, c.sybilProtection, params.K, got, c.wantWrapCChain)
|
||||
}
|
||||
// The P-Chain is never wrapped, whatever the committee.
|
||||
if shouldWrapInProposerVM(params.K, constants.PlatformChainID, false) {
|
||||
t.Fatalf("network %s: P-Chain must never be wrapped (K=%d)", c.name, params.K)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,32 @@ func selectConsensusParams(sybilProtection bool, networkID uint32) consensusconf
|
||||
}
|
||||
}
|
||||
|
||||
// shouldWrapInProposerVM decides whether a linear chain.ChainVM is wrapped in
|
||||
// proposervm to enforce single-proposer-per-height block production (the
|
||||
// Snowman++ window). It is the SINGLE policy gate (the manager calls it once);
|
||||
// keeping it a pure function makes the policy unit-testable without standing up
|
||||
// a whole chain. All three conditions must hold:
|
||||
//
|
||||
// - k > 1: a multi-validator quorum. K==1 (single-node --dev) has exactly one
|
||||
// proposer already, so there is no equivocation to prevent and no schedule
|
||||
// to compute (proposervm would only add a wrapper with no safety value).
|
||||
// - chainID is NOT the P-Chain: the P-Chain publishes its OWN height-indexed
|
||||
// validators.State DURING its createChain, AFTER the chainRuntime snapshot
|
||||
// proposervm's windower reads — so its windower would see an empty set and
|
||||
// fall back to anyone-can-propose with a P-chain-height-0 stamp. The P-Chain
|
||||
// keeps the existing newPChainHeightVM path (which DOES get the live state).
|
||||
// - inner is NOT DAG-native (no Linearize): a linearized DAG VM (X-Chain) is
|
||||
// driven by a push-notification bridge that does not compose with
|
||||
// proposervm's pull/window model without avalanchego's initializeOnLinearizeVM
|
||||
// machinery. It keeps the existing path.
|
||||
//
|
||||
// The C-Chain and sovereign-L1 EVM chains satisfy all three (multi-validator,
|
||||
// not the P-Chain, not DAG) — they are exactly the chains that exhibited the
|
||||
// equivocation crash, and exactly the chains avalanchego wraps in proposervm.
|
||||
func shouldWrapInProposerVM(k int, chainID ids.ID, innerIsDAGNative bool) bool {
|
||||
return k > 1 && chainID != constants.PlatformChainID && !innerIsDAGNative
|
||||
}
|
||||
|
||||
// --- BLS vote verifier -------------------------------------------------------
|
||||
|
||||
// blsVoteVerifier verifies a validator's BLS signature over the canonical vote
|
||||
|
||||
Reference in New Issue
Block a user