From daf8dbd1725e2afee21d83541785c67a14701e96 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 6 Oct 2025 11:27:48 -0400 Subject: [PATCH 01/10] Replace warp.GetCanonicalValidatorSetFromSubnetID --- warp/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp/service.go b/warp/service.go index ed2f438c94..a56f451c88 100644 --- a/warp/service.go +++ b/warp/service.go @@ -105,7 +105,7 @@ func (a *API) aggregateSignatures(ctx context.Context, unsignedMessage *warp.Uns return nil, err } - validatorSet, err := warp.GetCanonicalValidatorSetFromSubnetID(ctx, validatorState, pChainHeight, subnetID) + validatorSet, err := validatorState.GetWarpValidatorSet(ctx, pChainHeight, subnetID) if err != nil { return nil, fmt.Errorf("failed to get validator set: %w", err) } From a144654194327e3193cef9666b4d65ecb9c10b70 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 6 Oct 2025 12:06:13 -0400 Subject: [PATCH 02/10] Inline warp subnetID override logic --- precompile/contracts/warp/config.go | 57 ++++++++++++++++++++++------- warp/validators/state.go | 55 ---------------------------- warp/validators/state_test.go | 48 ------------------------ 3 files changed, 43 insertions(+), 117 deletions(-) delete mode 100644 warp/validators/state.go delete mode 100644 warp/validators/state_test.go diff --git a/precompile/contracts/warp/config.go b/precompile/contracts/warp/config.go index 843a3b7dcc..7b3effdd35 100644 --- a/precompile/contracts/warp/config.go +++ b/precompile/contracts/warp/config.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/evm/predicate" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -16,8 +17,6 @@ import ( "github.com/ava-labs/libevm/log" "github.com/ava-labs/coreth/precompile/precompileconfig" - - warpValidators "github.com/ava-labs/coreth/warp/validators" ) const ( @@ -202,24 +201,51 @@ func (c *Config) VerifyPredicate(predicateContext *precompileconfig.PredicateCon quorumNumerator = c.QuorumNumerator } - log.Debug("verifying warp message", "warpMsg", warpMsg, "quorumNum", quorumNumerator, "quorumDenom", WarpQuorumDenominator) + log.Debug("verifying warp message", + "warpMsg", warpMsg, + "quorumNum", quorumNumerator, + "quorumDenom", WarpQuorumDenominator, + ) - // Wrap validators.State on the chain snow context to special case the Primary Network - state := warpValidators.NewState( - predicateContext.SnowCtx.ValidatorState, - predicateContext.SnowCtx.SubnetID, + sourceSubnetID, err := predicateContext.SnowCtx.ValidatorState.GetSubnetID( + context.TODO(), warpMsg.SourceChainID, - c.RequirePrimaryNetworkSigners, ) + if err != nil { + log.Debug("failed to retrieve subnetID for chain", + "msgID", warpMsg.ID(), + "chainID", warpMsg.SourceChainID, + "err", err, + ) + return fmt.Errorf("%w: %w", errCannotRetrieveValidatorSet, err) + } + + // The primary network validator set is never required when verifying + // messages from the P-chain. + // + // For the X-chain and the C-chain, chains can be configured not to require + // the primary network validators to have signed the warp message and to use + // the, likely smaller, local subnet's validator set. + canOptimizePrimaryNetwork := !c.RequirePrimaryNetworkSigners || warpMsg.SourceChainID == constants.PlatformChainID + // If the chain is in the primary network and we don't require verifying + // against the primary network validator set, then we override the source + // subnet ID to the local chain's validator set. + if canOptimizePrimaryNetwork && sourceSubnetID == constants.PrimaryNetworkID { + sourceSubnetID = predicateContext.SnowCtx.SubnetID + } - validatorSet, err := warp.GetCanonicalValidatorSetFromChainID( - context.Background(), - state, + validatorSet, err := warp.GetCanonicalValidatorSetFromSubnetID( + context.TODO(), + predicateContext.SnowCtx.ValidatorState, predicateContext.ProposerVMBlockCtx.PChainHeight, - warpMsg.UnsignedMessage.SourceChainID, + sourceSubnetID, ) if err != nil { - log.Debug("failed to retrieve canonical validator set", "msgID", warpMsg.ID(), "err", err) + log.Debug("failed to retrieve canonical validator set", + "msgID", warpMsg.ID(), + "subnetID", sourceSubnetID, + "err", err, + ) return fmt.Errorf("%w: %w", errCannotRetrieveValidatorSet, err) } @@ -231,7 +257,10 @@ func (c *Config) VerifyPredicate(predicateContext *precompileconfig.PredicateCon WarpQuorumDenominator, ) if err != nil { - log.Debug("failed to verify warp signature", "msgID", warpMsg.ID(), "err", err) + log.Debug("failed to verify warp signature", + "msgID", warpMsg.ID(), + "err", err, + ) return fmt.Errorf("%w: %w", errFailedVerification, err) } diff --git a/warp/validators/state.go b/warp/validators/state.go deleted file mode 100644 index 5853075069..0000000000 --- a/warp/validators/state.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package validators - -import ( - "context" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/utils/constants" -) - -var _ validators.State = (*State)(nil) - -// State provides a special case used to handle Avalanche Warp Message verification for messages sent -// from the Primary Network. Subnets have strictly fewer validators than the Primary Network, so we require -// signatures from a threshold of the RECEIVING subnet validator set rather than the full Primary Network -// since the receiving subnet already relies on a majority of its validators being correct. -type State struct { - validators.State - mySubnetID ids.ID - sourceChainID ids.ID - requirePrimaryNetworkSigners bool -} - -// NewState returns a wrapper of [validators.State] which special cases the handling of the Primary Network. -// -// The wrapped state will return the [mySubnetID's] validator set instead of the Primary Network when -// the Primary Network SubnetID is passed in. -func NewState(state validators.State, mySubnetID ids.ID, sourceChainID ids.ID, requirePrimaryNetworkSigners bool) *State { - return &State{ - State: state, - mySubnetID: mySubnetID, - sourceChainID: sourceChainID, - requirePrimaryNetworkSigners: requirePrimaryNetworkSigners, - } -} - -func (s *State) GetValidatorSet( - ctx context.Context, - height uint64, - subnetID ids.ID, -) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - // If the subnetID is anything other than the Primary Network, or Primary - // Network signers are required (except P-Chain), this is a direct passthrough. - usePrimary := s.requirePrimaryNetworkSigners && s.sourceChainID != constants.PlatformChainID - if usePrimary || subnetID != constants.PrimaryNetworkID { - return s.State.GetValidatorSet(ctx, height, subnetID) - } - - // If the requested subnet is the primary network, then we return the validator - // set for the Subnet that is receiving the message instead. - return s.State.GetValidatorSet(ctx, height, s.mySubnetID) -} diff --git a/warp/validators/state_test.go b/warp/validators/state_test.go deleted file mode 100644 index 4ed8fe9610..0000000000 --- a/warp/validators/state_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package validators - -import ( - "context" - "testing" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/snowtest" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/snow/validators/validatorsmock" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetValidatorSetPrimaryNetwork(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - mySubnetID := ids.GenerateTestID() - otherSubnetID := ids.GenerateTestID() - - mockState := validatorsmock.NewState(ctrl) - snowCtx := snowtest.Context(t, snowtest.CChainID) - snowCtx.SubnetID = mySubnetID - snowCtx.ValidatorState = mockState - state := NewState(snowCtx.ValidatorState, snowCtx.SubnetID, snowCtx.ChainID, false) - // Expect that requesting my validator set returns my validator set - mockState.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), mySubnetID).Return(make(map[ids.NodeID]*validators.GetValidatorOutput), nil) - output, err := state.GetValidatorSet(context.Background(), 10, mySubnetID) - require.NoError(err) - require.Empty(output) - - // Expect that requesting the Primary Network validator set overrides and returns my validator set - mockState.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), mySubnetID).Return(make(map[ids.NodeID]*validators.GetValidatorOutput), nil) - output, err = state.GetValidatorSet(context.Background(), 10, constants.PrimaryNetworkID) - require.NoError(err) - require.Empty(output) - - // Expect that requesting other validator set returns that validator set - mockState.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), otherSubnetID).Return(make(map[ids.NodeID]*validators.GetValidatorOutput), nil) - output, err = state.GetValidatorSet(context.Background(), 10, otherSubnetID) - require.NoError(err) - require.Empty(output) -} From daeba8160c6384ba6e73b93a9ac9f89297846ae3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 6 Oct 2025 12:29:37 -0400 Subject: [PATCH 03/10] Replace warp.GetCanonicalValidatorSetFromSubnetID --- plugin/evm/vm_warp_test.go | 20 ++++++++++++++++++-- precompile/contracts/warp/config.go | 3 +-- precompile/contracts/warp/predicate_test.go | 16 +++++++++++++++- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 6fc43dab5d..704a2f4731 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -338,7 +338,7 @@ func testWarpVMTransaction(t *testing.T, scheme string, unsignedMessage *avalanc minimumValidPChainHeight := uint64(10) getValidatorSetTestErr := errors.New("can't get validator set test error") - vm.ctx.ValidatorState = &validatorstest.State{ + validatorState := &validatorstest.State{ // TODO: test both Primary Network / C-Chain and non-Primary Network GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { return ids.Empty, nil @@ -361,6 +361,14 @@ func testWarpVMTransaction(t *testing.T, scheme string, unsignedMessage *avalanc }, nil }, } + validatorState.GetWarpValidatorSetF = func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { + vdrs, err := validatorState.GetValidatorSet(ctx, height, subnetID) + if err != nil { + return validators.WarpSet{}, err + } + return validators.FlattenValidatorSet(vdrs) + } + vm.ctx.ValidatorState = validatorState signersBitSet := set.NewBits() signersBitSet.Add(0) @@ -638,7 +646,7 @@ func testReceiveWarpMessage( minimumValidPChainHeight := uint64(10) getValidatorSetTestErr := errors.New("can't get validator set test error") - vm.ctx.ValidatorState = &validatorstest.State{ + validatorState := &validatorstest.State{ GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { if msgFrom == fromPrimary { return constants.PrimaryNetworkID, nil @@ -665,6 +673,14 @@ func testReceiveWarpMessage( return vdrOutput, nil }, } + validatorState.GetWarpValidatorSetF = func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { + vdrs, err := validatorState.GetValidatorSet(ctx, height, subnetID) + if err != nil { + return validators.WarpSet{}, err + } + return validators.FlattenValidatorSet(vdrs) + } + vm.ctx.ValidatorState = validatorState signersBitSet := set.NewBits() for i := range signers { diff --git a/precompile/contracts/warp/config.go b/precompile/contracts/warp/config.go index 7b3effdd35..4bb7fde263 100644 --- a/precompile/contracts/warp/config.go +++ b/precompile/contracts/warp/config.go @@ -234,9 +234,8 @@ func (c *Config) VerifyPredicate(predicateContext *precompileconfig.PredicateCon sourceSubnetID = predicateContext.SnowCtx.SubnetID } - validatorSet, err := warp.GetCanonicalValidatorSetFromSubnetID( + validatorSet, err := predicateContext.SnowCtx.ValidatorState.GetWarpValidatorSet( context.TODO(), - predicateContext.SnowCtx.ValidatorState, predicateContext.ProposerVMBlockCtx.PChainHeight, sourceSubnetID, ) diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index 93bc288c3d..7e43a57c00 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -215,6 +215,9 @@ func createSnowCtx(tb testing.TB, validatorRanges []validatorRange) *snow.Contex GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { return getValidatorsOutput, nil }, + GetWarpValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { + return validators.FlattenValidatorSet(getValidatorsOutput) + }, } snowCtx.ValidatorState = state return snowCtx @@ -283,7 +286,7 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner snowCtx := snowtest.Context(t, ids.GenerateTestID()) snowCtx.SubnetID = ids.GenerateTestID() snowCtx.CChainID = cChainID - snowCtx.ValidatorState = &validatorstest.State{ + validatorState := &validatorstest.State{ GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { require.Equal(chainID, cChainID) return constants.PrimaryNetworkID, nil // Return Primary Network SubnetID @@ -297,6 +300,14 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner return getValidatorsOutput, nil }, } + validatorState.GetWarpValidatorSetF = func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { + vdrs, err := validatorState.GetValidatorSet(ctx, height, subnetID) + if err != nil { + return validators.WarpSet{}, err + } + return validators.FlattenValidatorSet(vdrs) + } + snowCtx.ValidatorState = validatorState test := precompiletest.PredicateTest{ Config: NewConfig(utils.NewUint64(0), 0, requirePrimaryNetworkSigners), @@ -738,6 +749,9 @@ func makeWarpPredicateTests(tb testing.TB) map[string]precompiletest.PredicateTe GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { return getValidatorsOutput, nil }, + GetWarpValidatorSetF: func(context.Context, uint64, ids.ID) (validators.WarpSet, error) { + return validators.FlattenValidatorSet(getValidatorsOutput) + }, } snowCtx.ValidatorState = state From 2095797954617bf39f154153f7a7e91d65f6e0d6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 7 Oct 2025 09:20:35 -0400 Subject: [PATCH 04/10] lint --- precompile/contracts/warp/predicate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index 7e43a57c00..6c288a529d 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -215,7 +215,7 @@ func createSnowCtx(tb testing.TB, validatorRanges []validatorRange) *snow.Contex GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { return getValidatorsOutput, nil }, - GetWarpValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { + GetWarpValidatorSetF: func(context.Context, uint64, ids.ID) (validators.WarpSet, error) { return validators.FlattenValidatorSet(getValidatorsOutput) }, } From 829be11679acae4bbbc4a7cacea99e8fefb8a66b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 7 Oct 2025 10:05:34 -0400 Subject: [PATCH 05/10] reduce diff --- plugin/evm/vm_warp_test.go | 43 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 704a2f4731..8611c05a91 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -338,37 +338,36 @@ func testWarpVMTransaction(t *testing.T, scheme string, unsignedMessage *avalanc minimumValidPChainHeight := uint64(10) getValidatorSetTestErr := errors.New("can't get validator set test error") - validatorState := &validatorstest.State{ + vm.ctx.ValidatorState = &validatorstest.State{ // TODO: test both Primary Network / C-Chain and non-Primary Network GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { return ids.Empty, nil }, - GetValidatorSetF: func(_ context.Context, height uint64, _ ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + GetWarpValidatorSetF: func(_ context.Context, height uint64, _ ids.ID) (validators.WarpSet, error) { if height < minimumValidPChainHeight { - return nil, getValidatorSetTestErr + return validators.WarpSet{}, getValidatorSetTestErr } - return map[ids.NodeID]*validators.GetValidatorOutput{ - nodeID1: { - NodeID: nodeID1, - PublicKey: blsPublicKey1, - Weight: 50, - }, - nodeID2: { - NodeID: nodeID2, - PublicKey: blsPublicKey2, - Weight: 50, + vdrs := validators.WarpSet{ + Validators: []*validators.Warp{ + { + PublicKey: blsPublicKey1, + PublicKeyBytes: bls.PublicKeyToUncompressedBytes(blsPublicKey1), + Weight: 50, + NodeIDs: []ids.NodeID{nodeID1}, + }, + { + PublicKey: blsPublicKey2, + PublicKeyBytes: bls.PublicKeyToUncompressedBytes(blsPublicKey2), + Weight: 50, + NodeIDs: []ids.NodeID{nodeID2}, + }, }, - }, nil + TotalWeight: 100, + } + avagoUtils.Sort(vdrs.Validators) + return vdrs, nil }, } - validatorState.GetWarpValidatorSetF = func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { - vdrs, err := validatorState.GetValidatorSet(ctx, height, subnetID) - if err != nil { - return validators.WarpSet{}, err - } - return validators.FlattenValidatorSet(vdrs) - } - vm.ctx.ValidatorState = validatorState signersBitSet := set.NewBits() signersBitSet.Add(0) From 39e37e8d0dec1c4c7e01141527193c0c6c331393 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 7 Oct 2025 10:10:42 -0400 Subject: [PATCH 06/10] reduce diff --- plugin/evm/vm_warp_test.go | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 8611c05a91..c4aa7fdc39 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -645,41 +645,37 @@ func testReceiveWarpMessage( minimumValidPChainHeight := uint64(10) getValidatorSetTestErr := errors.New("can't get validator set test error") - validatorState := &validatorstest.State{ + vm.ctx.ValidatorState = &validatorstest.State{ GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { if msgFrom == fromPrimary { return constants.PrimaryNetworkID, nil } return vm.ctx.SubnetID, nil }, - GetValidatorSetF: func(_ context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + GetWarpValidatorSetF: func(_ context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { if height < minimumValidPChainHeight { - return nil, getValidatorSetTestErr + return validators.WarpSet{}, getValidatorSetTestErr } signers := subnetSigners if subnetID == constants.PrimaryNetworkID { signers = primarySigners } - vdrOutput := make(map[ids.NodeID]*validators.GetValidatorOutput) + vdrs := validators.WarpSet{} for _, s := range signers { - vdrOutput[s.nodeID] = &validators.GetValidatorOutput{ - NodeID: s.nodeID, - PublicKey: s.secret.PublicKey(), - Weight: s.weight, - } + pk := s.secret.PublicKey() + vdrs.Validators = append(vdrs.Validators, &validators.Warp{ + PublicKey: pk, + PublicKeyBytes: bls.PublicKeyToUncompressedBytes(pk), + Weight: s.weight, + NodeIDs: []ids.NodeID{s.nodeID}, + }) + vdrs.TotalWeight += s.weight } - return vdrOutput, nil + avagoUtils.Sort(vdrs.Validators) + return vdrs, nil }, } - validatorState.GetWarpValidatorSetF = func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { - vdrs, err := validatorState.GetValidatorSet(ctx, height, subnetID) - if err != nil { - return validators.WarpSet{}, err - } - return validators.FlattenValidatorSet(vdrs) - } - vm.ctx.ValidatorState = validatorState signersBitSet := set.NewBits() for i := range signers { From 1429e706d81426a95c2d65e228d58ba1c7919b0d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 7 Oct 2025 10:24:58 -0400 Subject: [PATCH 07/10] reduce diff --- precompile/contracts/warp/predicate_test.go | 72 ++++++++++----------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index 6c288a529d..d171c0e94c 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -192,8 +192,7 @@ type validatorRange struct { // createSnowCtx creates a snow.Context instance with a validator state specified by the given validatorRanges func createSnowCtx(tb testing.TB, validatorRanges []validatorRange) *snow.Context { - getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput) - + validatorSet := make(map[ids.NodeID]*validators.GetValidatorOutput) for _, validatorRange := range validatorRanges { for i := validatorRange.start; i < validatorRange.end; i++ { validatorOutput := &validators.GetValidatorOutput{ @@ -203,23 +202,21 @@ func createSnowCtx(tb testing.TB, validatorRanges []validatorRange) *snow.Contex if validatorRange.publicKey { validatorOutput.PublicKey = testVdrs[i].vdr.PublicKey } - getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput + validatorSet[testVdrs[i].nodeID] = validatorOutput } } + warpValidators, err := validators.FlattenValidatorSet(validatorSet) + require.NoError(tb, err) snowCtx := snowtest.Context(tb, snowtest.CChainID) - state := &validatorstest.State{ + snowCtx.ValidatorState = &validatorstest.State{ GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { return sourceSubnetID, nil }, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return getValidatorsOutput, nil - }, GetWarpValidatorSetF: func(context.Context, uint64, ids.ID) (validators.WarpSet, error) { - return validators.FlattenValidatorSet(getValidatorsOutput) + return warpValidators, nil }, } - snowCtx.ValidatorState = state return snowCtx } @@ -254,20 +251,29 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner unsignedMsg, err := avalancheWarp.NewUnsignedMessage(constants.UnitTestID, cChainID, addressedCall.Bytes()) require.NoError(err) - getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput) - blsSignatures := make([]*bls.Signature, 0, numKeys) + var ( + warpValidators = validators.WarpSet{ + Validators: make([]*validators.Warp, 0, numKeys), + TotalWeight: 20 * uint64(numKeys), + } + blsSignatures = make([]*bls.Signature, 0, numKeys) + ) for i := 0; i < numKeys; i++ { - sig, err := testVdrs[i].sk.Sign(unsignedMsg.Bytes()) + vdr := testVdrs[i] + sig, err := vdr.sk.Sign(unsignedMsg.Bytes()) require.NoError(err) - - validatorOutput := &validators.GetValidatorOutput{ - NodeID: testVdrs[i].nodeID, - Weight: 20, - PublicKey: testVdrs[i].vdr.PublicKey, - } - getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput blsSignatures = append(blsSignatures, sig) + + pk := vdr.sk.PublicKey() + warpValidators.Validators = append(warpValidators.Validators, &validators.Warp{ + PublicKey: pk, + PublicKeyBytes: bls.PublicKeyToUncompressedBytes(pk), + Weight: 20, + NodeIDs: []ids.NodeID{vdr.nodeID}, + }) } + agoUtils.Sort(warpValidators.Validators) + aggregateSignature, err := bls.AggregateSignatures(blsSignatures) require.NoError(err) bitSet := set.NewBits() @@ -286,28 +292,20 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner snowCtx := snowtest.Context(t, ids.GenerateTestID()) snowCtx.SubnetID = ids.GenerateTestID() snowCtx.CChainID = cChainID - validatorState := &validatorstest.State{ + snowCtx.ValidatorState = &validatorstest.State{ GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { require.Equal(chainID, cChainID) return constants.PrimaryNetworkID, nil // Return Primary Network SubnetID }, - GetValidatorSetF: func(_ context.Context, _ uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + GetWarpValidatorSetF: func(_ context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { expectedSubnetID := snowCtx.SubnetID if requirePrimaryNetworkSigners { expectedSubnetID = constants.PrimaryNetworkID } require.Equal(expectedSubnetID, subnetID) - return getValidatorsOutput, nil + return warpValidators, nil }, } - validatorState.GetWarpValidatorSetF = func(ctx context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { - vdrs, err := validatorState.GetValidatorSet(ctx, height, subnetID) - if err != nil { - return validators.WarpSet{}, err - } - return validators.FlattenValidatorSet(vdrs) - } - snowCtx.ValidatorState = validatorState test := precompiletest.PredicateTest{ Config: NewConfig(utils.NewUint64(0), 0, requirePrimaryNetworkSigners), @@ -732,28 +730,26 @@ func makeWarpPredicateTests(tb testing.TB) map[string]precompiletest.PredicateTe testName := fmt.Sprintf("%d validators w/ %d signers/repeated PublicKeys", totalNodes, numSigners) pred := createPredicate(numSigners) - getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput, totalNodes) + validatorSet := make(map[ids.NodeID]*validators.GetValidatorOutput, totalNodes) for i := 0; i < totalNodes; i++ { - getValidatorsOutput[testVdrs[i].nodeID] = &validators.GetValidatorOutput{ + validatorSet[testVdrs[i].nodeID] = &validators.GetValidatorOutput{ NodeID: testVdrs[i].nodeID, Weight: 20, PublicKey: testVdrs[i%numSigners].vdr.PublicKey, } } + warpValidators, err := validators.FlattenValidatorSet(validatorSet) + require.NoError(tb, err) snowCtx := snowtest.Context(tb, snowtest.CChainID) - state := &validatorstest.State{ + snowCtx.ValidatorState = &validatorstest.State{ GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { return sourceSubnetID, nil }, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return getValidatorsOutput, nil - }, GetWarpValidatorSetF: func(context.Context, uint64, ids.ID) (validators.WarpSet, error) { - return validators.FlattenValidatorSet(getValidatorsOutput) + return warpValidators, nil }, } - snowCtx.ValidatorState = state predicateTests[testName] = createValidPredicateTest(snowCtx, uint64(numSigners), pred) } From 245ad81fe16a1baec0475e2c31b8e18acfe1a25b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 7 Oct 2025 10:29:21 -0400 Subject: [PATCH 08/10] lint --- precompile/contracts/warp/predicate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index d171c0e94c..adeb8d47ba 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -297,7 +297,7 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner require.Equal(chainID, cChainID) return constants.PrimaryNetworkID, nil // Return Primary Network SubnetID }, - GetWarpValidatorSetF: func(_ context.Context, height uint64, subnetID ids.ID) (validators.WarpSet, error) { + GetWarpValidatorSetF: func(_ context.Context, _ uint64, subnetID ids.ID) (validators.WarpSet, error) { expectedSubnetID := snowCtx.SubnetID if requirePrimaryNetworkSigners { expectedSubnetID = constants.PrimaryNetworkID From e3e4083cc79f4f2d94649f1511180dfb234a6012 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 7 Oct 2025 10:39:34 -0400 Subject: [PATCH 09/10] fix test --- precompile/contracts/warp/predicate_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index adeb8d47ba..3ad9c5dc0a 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -205,8 +205,6 @@ func createSnowCtx(tb testing.TB, validatorRanges []validatorRange) *snow.Contex validatorSet[testVdrs[i].nodeID] = validatorOutput } } - warpValidators, err := validators.FlattenValidatorSet(validatorSet) - require.NoError(tb, err) snowCtx := snowtest.Context(tb, snowtest.CChainID) snowCtx.ValidatorState = &validatorstest.State{ @@ -214,7 +212,7 @@ func createSnowCtx(tb testing.TB, validatorRanges []validatorRange) *snow.Contex return sourceSubnetID, nil }, GetWarpValidatorSetF: func(context.Context, uint64, ids.ID) (validators.WarpSet, error) { - return warpValidators, nil + return validators.FlattenValidatorSet(validatorSet) }, } return snowCtx From 905cab4d2ee611ce056b6a4cd0c1d179f7c69f00 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 7 Oct 2025 16:18:17 -0400 Subject: [PATCH 10/10] address comment --- precompile/contracts/warp/config.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/precompile/contracts/warp/config.go b/precompile/contracts/warp/config.go index 4bb7fde263..66d25a8a94 100644 --- a/precompile/contracts/warp/config.go +++ b/precompile/contracts/warp/config.go @@ -220,18 +220,16 @@ func (c *Config) VerifyPredicate(predicateContext *precompileconfig.PredicateCon return fmt.Errorf("%w: %w", errCannotRetrieveValidatorSet, err) } - // The primary network validator set is never required when verifying - // messages from the P-chain. - // - // For the X-chain and the C-chain, chains can be configured not to require - // the primary network validators to have signed the warp message and to use - // the, likely smaller, local subnet's validator set. - canOptimizePrimaryNetwork := !c.RequirePrimaryNetworkSigners || warpMsg.SourceChainID == constants.PlatformChainID - // If the chain is in the primary network and we don't require verifying - // against the primary network validator set, then we override the source - // subnet ID to the local chain's validator set. - if canOptimizePrimaryNetwork && sourceSubnetID == constants.PrimaryNetworkID { - sourceSubnetID = predicateContext.SnowCtx.SubnetID + if sourceSubnetID == constants.PrimaryNetworkID { + // For the X-chain and the C-chain, chains can be configured not to + // require the primary network validators to have signed the warp + // message and to use the, likely smaller, local subnet's validator set. + // + // The primary network validator set is never required when verifying + // messages from the P-chain because the P-chain is always synced. + if !c.RequirePrimaryNetworkSigners || warpMsg.SourceChainID == constants.PlatformChainID { + sourceSubnetID = predicateContext.SnowCtx.SubnetID + } } validatorSet, err := predicateContext.SnowCtx.ValidatorState.GetWarpValidatorSet(