Files
zeekayandHanzo Dev 9d13e82725 Depend on the renamed utility package
github.com/luxfi/utils is now github.com/luxfi/util. Import paths and the
module requirement follow the rename, and the luxutils alias becomes luxutil.
The json types cross the evm client boundary, so this also takes
luxfi/vm v1.3.18, where those API structs already speak the renamed types.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 04:29:33 -07:00

696 lines
21 KiB
Go

//go:build skip
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package warp
import (
"bytes"
"context"
"errors"
"fmt"
"testing"
consensusctx "github.com/luxfi/consensus/context"
"github.com/luxfi/consensus/engine/chain/block"
consensustest "github.com/luxfi/consensus/test/helpers"
validators "github.com/luxfi/consensus/validator"
"github.com/luxfi/consensus/validator/validatorstest"
"github.com/luxfi/constants"
"github.com/luxfi/coreth/precompile/precompileconfig"
"github.com/luxfi/coreth/precompile/precompiletest"
"github.com/luxfi/coreth/predicate"
"github.com/luxfi/coreth/utils"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/crypto/bls/signer/localsigner"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
agoUtils "github.com/luxfi/util"
"github.com/luxfi/warp"
"github.com/luxfi/warp/payload"
"github.com/stretchr/testify/require"
)
const pChainHeight uint64 = 1337
var (
_ agoUtils.Sortable[*testValidator] = (*testValidator)(nil)
errTest = errors.New("non-nil error")
sourceChainID = ids.GenerateTestID()
sourceChainID = ids.GenerateTestID()
// valid unsigned warp message used throughout testing
unsignedMsg *warp.Message
// valid addressed payload
addressedPayload *payload.AddressedCall
addressedPayloadBytes []byte
// blsSignatures of [unsignedMsg] from each of [testVdrs]
blsSignatures []*bls.Signature
numTestVdrs = 10_000
testVdrs []*testValidator
vdrs map[ids.NodeID]*validators.GetValidatorOutput
)
func init() {
testVdrs = make([]*testValidator, 0, numTestVdrs)
for i := 0; i < numTestVdrs; i++ {
testVdrs = append(testVdrs, newTestValidator())
}
agoUtils.Sort(testVdrs)
vdrs = map[ids.NodeID]*validators.GetValidatorOutput{
testVdrs[0].nodeID: {
NodeID: testVdrs[0].nodeID,
PublicKey: testVdrs[0].vdr.PublicKeyBytes,
Weight: testVdrs[0].vdr.Weight,
},
testVdrs[1].nodeID: {
NodeID: testVdrs[1].nodeID,
PublicKey: testVdrs[1].vdr.PublicKeyBytes,
Weight: testVdrs[1].vdr.Weight,
},
testVdrs[2].nodeID: {
NodeID: testVdrs[2].nodeID,
PublicKey: testVdrs[2].vdr.PublicKeyBytes,
Weight: testVdrs[2].vdr.Weight,
},
}
var err error
addr := ids.GenerateTestShortID()
addressedPayload, err = payload.NewAddressedCall(
addr[:],
[]byte{1, 2, 3},
)
if err != nil {
panic(err)
}
addressedPayloadBytes = addressedPayload.Bytes()
unsignedMsg, err = warp.NewMessage(constants.UnitTestID, sourceChainID, addressedPayload.Bytes())
if err != nil {
panic(err)
}
for _, testVdr := range testVdrs {
blsSignature, err := testVdr.sk.Sign(unsignedMsg.Bytes())
if err != nil {
panic(err)
}
blsSignatures = append(blsSignatures, blsSignature)
}
}
type testValidator struct {
nodeID ids.NodeID
sk bls.Signer
vdr *warp.Validator
}
func (v *testValidator) Compare(o *testValidator) int {
return bytes.Compare(v.vdr.PublicKeyBytes, o.vdr.PublicKeyBytes)
}
func newTestValidator() *testValidator {
sk, err := localsigner.New()
if err != nil {
panic(err)
}
nodeID := ids.GenerateTestNodeID()
pk := sk.PublicKey()
return &testValidator{
nodeID: nodeID,
sk: sk,
vdr: &warp.Validator{
PublicKey: pk,
PublicKeyBytes: bls.PublicKeyToCompressedBytes(pk),
Weight: 3,
NodeID: nodeID,
},
}
}
// createWarpMessage constructs a signed warp message using the global variable [unsignedMsg]
// and the first [numKeys] signatures from [blsSignatures]
func createWarpMessage(numKeys int) *warp.Envelope {
aggregateSignature, err := bls.AggregateSignatures(blsSignatures[0:numKeys])
if err != nil {
panic(err)
}
bitSet := set.NewBits()
for i := 0; i < numKeys; i++ {
bitSet.Add(i)
}
warpSignature := &warp.BitSetSignature{
Signers: bitSet.Bytes(),
}
copy(warpSignature.Signature[:], bls.SignatureToBytes(aggregateSignature))
warpMsg, err := warp.NewEnvelope(unsignedMsg, warpSignature)
if err != nil {
panic(err)
}
return warpMsg
}
// createPredicate constructs a warp message using createWarpMessage with numKeys signers
// and packs it into predicate encoding.
func createPredicate(numKeys int) []byte {
warpMsg := createWarpMessage(numKeys)
predicateBytes := predicate.PackPredicate(warpMsg.Bytes())
return predicateBytes
}
// validatorRange specifies a range of validators to include from [start, end), a staking weight
// to specify for each validator in that range, and whether or not to include the public key.
type validatorRange struct {
start int
end int
weight uint64
publicKey bool
}
// createConsensusCtx creates a consensusctx.Context instance with a validator state specified by the given validatorRanges
func createConsensusCtx(tb testing.TB, validatorRanges []validatorRange) *consensusctx.Context {
getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput)
for _, validatorRange := range validatorRanges {
for i := validatorRange.start; i < validatorRange.end; i++ {
validatorOutput := &validators.GetValidatorOutput{
NodeID: testVdrs[i].nodeID,
Weight: validatorRange.weight,
}
if validatorRange.publicKey {
validatorOutput.PublicKey = testVdrs[i].vdr.PublicKeyBytes
}
getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput
}
}
quasarCtx := consensustest.Context(tb, consensustest.CChainID)
state := &validatorstest.State{
GetChainIDF: func(ctx context.Context, chainID ids.ID) (ids.ID, error) {
return sourceChainID, nil
},
GetValidatorSetF: func(ctx context.Context, height uint64, chainID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
return getValidatorsOutput, nil
},
}
quasarCtx.ValidatorState = state
return quasarCtx
}
func createValidPredicateTest(quasarCtx *consensusctx.Context, numKeys uint64, predicateBytes []byte) precompiletest.PredicateTest {
return precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + numKeys*GasCostPerWarpSigner,
GasErr: nil,
ExpectedErr: nil,
}
}
func TestWarpMessageFromPrimaryNetwork(t *testing.T) {
for _, requirePrimaryNetworkSigners := range []bool{true, false} {
testWarpMessageFromPrimaryNetwork(t, requirePrimaryNetworkSigners)
}
}
func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigners bool) {
require := require.New(t)
numKeys := 10
cChainID := ids.GenerateTestID()
addressedCall, err := payload.NewAddressedCall(agoUtils.RandomBytes(20), agoUtils.RandomBytes(100))
require.NoError(err)
unsignedMsg, err := warp.NewMessage(constants.UnitTestID, cChainID, addressedCall.Bytes())
require.NoError(err)
getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput)
blsSignatures := make([]*bls.Signature, 0, numKeys)
for i := 0; i < numKeys; i++ {
sig, err := testVdrs[i].sk.Sign(unsignedMsg.Bytes())
require.NoError(err)
validatorOutput := &validators.GetValidatorOutput{
NodeID: testVdrs[i].nodeID,
Weight: 20,
PublicKey: testVdrs[i].vdr.PublicKeyBytes,
}
getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput
blsSignatures = append(blsSignatures, sig)
}
aggregateSignature, err := bls.AggregateSignatures(blsSignatures)
require.NoError(err)
bitSet := set.NewBits()
for i := 0; i < numKeys; i++ {
bitSet.Add(i)
}
warpSignature := &warp.BitSetSignature{
Signers: bitSet.Bytes(),
}
copy(warpSignature.Signature[:], bls.SignatureToBytes(aggregateSignature))
warpMsg, err := warp.NewEnvelope(unsignedMsg, warpSignature)
require.NoError(err)
predicateBytes := predicate.PackPredicate(warpMsg.Bytes())
quasarCtx := consensustest.Context(t, ids.GenerateTestID())
quasarCtx.ChainID = ids.GenerateTestID()
quasarCtx.CChainID = cChainID
quasarCtx.ValidatorState = &validatorstest.State{
GetChainIDF: func(ctx context.Context, chainID ids.ID) (ids.ID, error) {
require.Equal(chainID, cChainID)
return constants.PrimaryNetworkID, nil // Return Primary Network ChainID
},
GetValidatorSetF: func(ctx context.Context, height uint64, chainID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
expectedChainID := quasarCtx.ChainID
if requirePrimaryNetworkSigners {
expectedChainID = constants.PrimaryNetworkID
}
require.Equal(expectedChainID, chainID)
return getValidatorsOutput, nil
},
}
test := precompiletest.PredicateTest{
Config: NewConfig(utils.NewUint64(0), 0, requirePrimaryNetworkSigners),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner,
GasErr: nil,
ExpectedErr: nil,
}
test.Run(t)
}
func TestInvalidPredicatePacking(t *testing.T) {
numKeys := 1
quasarCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
predicateBytes := createPredicate(numKeys)
predicateBytes = append(predicateBytes, byte(0x01)) // Invalidate the predicate byte packing
test := precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner,
GasErr: errInvalidPredicateBytes,
}
test.Run(t)
}
func TestInvalidWarpMessage(t *testing.T) {
numKeys := 1
quasarCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
warpMsg := createWarpMessage(1)
warpMsgBytes := warpMsg.Bytes()
warpMsgBytes = append(warpMsgBytes, byte(0x01)) // Invalidate warp message packing
predicateBytes := predicate.PackPredicate(warpMsgBytes)
test := precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner,
GasErr: errInvalidWarpMsg,
}
test.Run(t)
}
func TestInvalidAddressedPayload(t *testing.T) {
numKeys := 1
quasarCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
aggregateSignature, err := bls.AggregateSignatures(blsSignatures[0:numKeys])
require.NoError(t, err)
bitSet := set.NewBits()
for i := 0; i < numKeys; i++ {
bitSet.Add(i)
}
warpSignature := &warp.BitSetSignature{
Signers: bitSet.Bytes(),
}
copy(warpSignature.Signature[:], bls.SignatureToBytes(aggregateSignature))
// Create an unsigned message with an invalid addressed payload
unsignedMsg, err := warp.NewMessage(constants.UnitTestID, sourceChainID, []byte{1, 2, 3})
require.NoError(t, err)
warpMsg, err := warp.NewEnvelope(unsignedMsg, warpSignature)
require.NoError(t, err)
warpMsgBytes := warpMsg.Bytes()
predicateBytes := predicate.PackPredicate(warpMsgBytes)
test := precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner,
GasErr: errInvalidWarpMsgPayload,
}
test.Run(t)
}
func TestInvalidBitSet(t *testing.T) {
addressedCall, err := payload.NewAddressedCall(agoUtils.RandomBytes(20), agoUtils.RandomBytes(100))
require.NoError(t, err)
unsignedMsg, err := warp.NewMessage(
constants.UnitTestID,
sourceChainID,
addressedCall.Bytes(),
)
require.NoError(t, err)
msg, err := warp.NewEnvelope(
unsignedMsg,
&warp.BitSetSignature{
Signers: make([]byte, 1),
Signature: [bls.SignatureLen]byte{},
},
)
require.NoError(t, err)
numKeys := 1
quasarCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
predicateBytes := predicate.PackPredicate(msg.Bytes())
test := precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner,
GasErr: errCannotGetNumSigners,
}
test.Run(t)
}
func TestWarpSignatureWeightsDefaultQuorumNumerator(t *testing.T) {
quasarCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: 100,
weight: 20,
publicKey: true,
},
})
tests := make(map[string]precompiletest.PredicateTest)
for _, numSigners := range []int{
1,
int(WarpDefaultQuorumNumerator) - 1,
int(WarpDefaultQuorumNumerator),
int(WarpDefaultQuorumNumerator) + 1,
int(WarpQuorumDenominator) - 1,
int(WarpQuorumDenominator),
int(WarpQuorumDenominator) + 1,
} {
predicateBytes := createPredicate(numSigners)
// The predicate is valid iff the number of signers is >= the required numerator and does not exceed the denominator.
var expectedErr error
if numSigners >= int(WarpDefaultQuorumNumerator) && numSigners <= int(WarpQuorumDenominator) {
expectedErr = nil
} else {
expectedErr = errFailedVerification
}
tests[fmt.Sprintf("default quorum %d signature(s)", numSigners)] = precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numSigners)*GasCostPerWarpSigner,
GasErr: nil,
ExpectedErr: expectedErr,
}
}
precompiletest.RunPredicateTests(t, tests)
}
// multiple messages all correct, multiple messages all incorrect, mixed bag
func TestWarpMultiplePredicates(t *testing.T) {
quasarCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: 100,
weight: 20,
publicKey: true,
},
})
tests := make(map[string]precompiletest.PredicateTest)
for _, validMessageIndices := range [][]bool{
{},
{true, false},
{false, true},
{false, false},
{true, true},
} {
var (
numSigners = int(WarpQuorumDenominator)
invalidPredicateBytes = createPredicate(1)
validPredicateBytes = createPredicate(numSigners)
)
for _, valid := range validMessageIndices {
var (
predicate []byte
expectedGas uint64
expectedErr error
)
if valid {
predicate = validPredicateBytes
expectedGas = GasCostPerSignatureVerification + uint64(len(validPredicateBytes))*GasCostPerWarpMessageBytes + uint64(numSigners)*GasCostPerWarpSigner
expectedErr = nil
} else {
expectedGas = GasCostPerSignatureVerification + uint64(len(invalidPredicateBytes))*GasCostPerWarpMessageBytes + uint64(1)*GasCostPerWarpSigner
predicate = invalidPredicateBytes
expectedErr = errFailedVerification
}
tests[fmt.Sprintf("multiple predicates %v", validMessageIndices)] = precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicate,
Gas: expectedGas,
GasErr: nil,
ExpectedErr: expectedErr,
}
}
}
precompiletest.RunPredicateTests(t, tests)
}
func TestWarpSignatureWeightsNonDefaultQuorumNumerator(t *testing.T) {
quasarCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: 100,
weight: 20,
publicKey: true,
},
})
tests := make(map[string]precompiletest.PredicateTest)
nonDefaultQuorumNumerator := 50
// Ensure this test fails if the DefaultQuroumNumerator is changed to an unexpected value during development
require.NotEqual(t, nonDefaultQuorumNumerator, int(WarpDefaultQuorumNumerator))
// Add cases with default quorum
for _, numSigners := range []int{nonDefaultQuorumNumerator, nonDefaultQuorumNumerator + 1, 99, 100, 101} {
predicateBytes := createPredicate(numSigners)
// The predicate is valid iff the number of signers is >= the required numerator and does not exceed the denominator.
var expectedErr error
if numSigners >= nonDefaultQuorumNumerator && numSigners <= int(WarpQuorumDenominator) {
expectedErr = nil
} else {
expectedErr = errFailedVerification
}
name := fmt.Sprintf("non-default quorum %d signature(s)", numSigners)
tests[name] = precompiletest.PredicateTest{
Config: NewConfig(utils.NewUint64(0), uint64(nonDefaultQuorumNumerator), false),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: quasarCtx,
ProposerVMBlockCtx: &block.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numSigners)*GasCostPerWarpSigner,
GasErr: nil,
ExpectedErr: expectedErr,
}
}
precompiletest.RunPredicateTests(t, tests)
}
func makeWarpPredicateTests(tb testing.TB) map[string]precompiletest.PredicateTest {
predicateTests := make(map[string]precompiletest.PredicateTest)
for _, totalNodes := range []int{10, 100, 1_000, 10_000} {
testName := fmt.Sprintf("%d signers/%d validators", totalNodes, totalNodes)
predicateBytes := createPredicate(totalNodes)
quasarCtx := createConsensusCtx(tb, []validatorRange{
{
start: 0,
end: totalNodes,
weight: 20,
publicKey: true,
},
})
predicateTests[testName] = createValidPredicateTest(quasarCtx, uint64(totalNodes), predicateBytes)
}
numSigners := 10
for _, totalNodes := range []int{100, 1_000, 10_000} {
testName := fmt.Sprintf("%d signers (heavily weighted)/%d validators", numSigners, totalNodes)
predicateBytes := createPredicate(numSigners)
quasarCtx := createConsensusCtx(tb, []validatorRange{
{
start: 0,
end: numSigners,
weight: 10_000_000,
publicKey: true,
},
{
start: numSigners,
end: totalNodes,
weight: 20,
publicKey: true,
},
})
predicateTests[testName] = createValidPredicateTest(quasarCtx, uint64(numSigners), predicateBytes)
}
for _, totalNodes := range []int{100, 1_000, 10_000} {
testName := fmt.Sprintf("%d signers (heavily weighted)/%d validators (non-signers without registered PublicKey)", numSigners, totalNodes)
predicateBytes := createPredicate(numSigners)
quasarCtx := createConsensusCtx(tb, []validatorRange{
{
start: 0,
end: numSigners,
weight: 10_000_000,
publicKey: true,
},
{
start: numSigners,
end: totalNodes,
weight: 20,
publicKey: false,
},
})
predicateTests[testName] = createValidPredicateTest(quasarCtx, uint64(numSigners), predicateBytes)
}
for _, totalNodes := range []int{100, 1_000, 10_000} {
testName := fmt.Sprintf("%d validators w/ %d signers/repeated PublicKeys", totalNodes, numSigners)
predicateBytes := createPredicate(numSigners)
getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput, totalNodes)
for i := 0; i < totalNodes; i++ {
getValidatorsOutput[testVdrs[i].nodeID] = &validators.GetValidatorOutput{
NodeID: testVdrs[i].nodeID,
Weight: 20,
PublicKey: testVdrs[i%numSigners].vdr.PublicKeyBytes,
}
}
quasarCtx := consensustest.Context(tb, consensustest.CChainID)
state := &validatorstest.State{
GetChainIDF: func(ctx context.Context, chainID ids.ID) (ids.ID, error) {
return sourceChainID, nil
},
GetValidatorSetF: func(ctx context.Context, height uint64, chainID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
return getValidatorsOutput, nil
},
}
quasarCtx.ValidatorState = state
predicateTests[testName] = createValidPredicateTest(quasarCtx, uint64(numSigners), predicateBytes)
}
return predicateTests
}
func TestWarpPredicate(t *testing.T) {
predicateTests := makeWarpPredicateTests(t)
precompiletest.RunPredicateTests(t, predicateTests)
}
func BenchmarkWarpPredicate(b *testing.B) {
predicateTests := makeWarpPredicateTests(b)
precompiletest.RunPredicateBenchmarks(b, predicateTests)
}