Files
zeekayandHanzo Dev 9f54b4984a 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 old module stays as an indirect requirement that published dependencies
still pull in.

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

835 lines
26 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package warp
import (
"context"
"errors"
"fmt"
"testing"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/crypto/bls/signer/localsigner"
evmconsensus "github.com/luxfi/evm/consensus"
"github.com/luxfi/evm/precompile/precompileconfig"
"github.com/luxfi/evm/precompile/precompiletest"
"github.com/luxfi/evm/predicate"
"github.com/luxfi/evm/utils"
"github.com/luxfi/evm/utils/utilstest"
"github.com/luxfi/ids"
consensuscontext "github.com/luxfi/runtime"
agoUtils "github.com/luxfi/util"
validators "github.com/luxfi/validators"
"github.com/luxfi/validators/validatorstest"
"github.com/luxfi/vm/chain"
"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()
sourceNetworkID = ids.GenerateTestID()
// valid signed core used throughout testing
msg *warp.Message
// valid addressed payload
addressedPayload *payload.AddressedCall
addressedPayloadBytes []byte
// blsSignatures over BeamSigningBytes(core.ID()) 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: bls.PublicKeyToCompressedBytes(testVdrs[0].cryptoPK),
Weight: testVdrs[0].vdr.Weight,
},
testVdrs[1].nodeID: {
NodeID: testVdrs[1].nodeID,
PublicKey: bls.PublicKeyToCompressedBytes(testVdrs[1].cryptoPK),
Weight: testVdrs[1].vdr.Weight,
},
testVdrs[2].nodeID: {
NodeID: testVdrs[2].nodeID,
PublicKey: bls.PublicKeyToCompressedBytes(testVdrs[2].cryptoPK),
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()
msg, err = warp.NewMessage(constants.UnitTestID, sourceChainID, addressedPayload.Bytes())
if err != nil {
panic(err)
}
// Post-ZAP the BLS Beam signs BeamSigningBytes(core.ID()), not raw bytes.
beamMsg := warp.BeamSigningBytes(msg.ID())
for _, testVdr := range testVdrs {
blsSignature, err := testVdr.sk.Sign(beamMsg)
if err != nil {
panic(err)
}
blsSignatures = append(blsSignatures, blsSignature)
}
}
type testValidator struct {
nodeID ids.NodeID
sk bls.Signer
vdr *warp.Validator
cryptoPK *bls.PublicKey // Cached crypto/bls public key for consensus validation
}
func (v *testValidator) Compare(o *testValidator) int {
// Compare by public key bytes since warp.Validator doesn't have Compare method
if v.vdr.Less(o.vdr) {
return -1
}
if o.vdr.Less(v.vdr) {
return 1
}
return 0
}
func newTestValidator() *testValidator {
sk, err := localsigner.New()
if err != nil {
panic(err)
}
nodeID := ids.GenerateTestNodeID()
cryptoPK := sk.PublicKey()
cryptoPKBytes := bls.PublicKeyToCompressedBytes(cryptoPK)
return &testValidator{
nodeID: nodeID,
sk: sk,
cryptoPK: cryptoPK,
vdr: &warp.Validator{
PublicKey: cryptoPK, // warp now uses crypto/bls directly
PublicKeyBytes: cryptoPKBytes,
Weight: 3,
NodeID: nodeID,
},
}
}
// createWarpMessage constructs a signed warp envelope using the global [core]
// 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 := warp.NewBitSet()
for i := 0; i < numKeys; i++ {
bitSet.Add(i)
}
beam := warp.BitSetSignature{
Signers: bitSet,
}
copy(beam.Signature[:], bls.SignatureToBytes(aggregateSignature))
env, err := warp.NewEnvelope(msg, beam, nil, nil)
if err != nil {
panic(err)
}
return env
}
// createPredicate constructs a warp envelope using createWarpMessage with numKeys signers
// and packs it into predicate encoding.
func createPredicate(numKeys int) []byte {
warpMsg := createWarpMessage(numKeys)
envBytes, err := warpMsg.Bytes()
if err != nil {
panic(err)
}
predicateBytes := predicate.PackPredicate(envBytes)
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
}
// testValidatorStateWrapper wraps validatorstest.State to implement consensus.ValidatorState
type testValidatorStateWrapper struct {
*validatorstest.State
GetMinimumHeightF func(context.Context) (uint64, error)
GetChainIDF func(ids.ID) (ids.ID, error)
GetNetworkIDF func(ids.ID) (ids.ID, error)
}
func (t *testValidatorStateWrapper) GetCurrentHeight(ctx context.Context) (uint64, error) {
return t.State.GetCurrentHeight(ctx)
}
func (t *testValidatorStateWrapper) GetMinimumHeight(ctx context.Context) (uint64, error) {
if t.GetMinimumHeightF != nil {
return t.GetMinimumHeightF(ctx)
}
return 0, nil
}
// GetValidatorSetWithOutput returns the full validator output including public keys
// This implements the ValidatorOutputGetter interface used by VerifyPredicate
func (t *testValidatorStateWrapper) GetValidatorSetWithOutput(ctx context.Context, height uint64, chainID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
return t.State.GetValidatorSet(ctx, height, chainID)
}
func (t *testValidatorStateWrapper) GetChainID(id ids.ID) (ids.ID, error) {
if t.GetChainIDF != nil {
return t.GetChainIDF(id)
}
return ids.Empty, nil
}
func (t *testValidatorStateWrapper) GetNetworkID(id ids.ID) (ids.ID, error) {
if t.GetNetworkIDF != nil {
return t.GetNetworkIDF(id)
}
return ids.Empty, nil
}
// newPredicateConsensusCtx builds a consensus context whose Runtime carries the
// given validator state. VerifyPredicate reads the validator set via the
// production path runtime.GetValidatorState (Runtime.ValidatorState), so tests
// must inject through the Runtime, not the legacy WithValidatorState key.
func newPredicateConsensusCtx(tb testing.TB, state consensuscontext.ValidatorState) context.Context {
rt := utilstest.NewTestRuntime(tb, ids.GenerateTestID())
rt.ValidatorState = state
// Align the node's network ID with the cores the tests build via
// warp.NewMessage(constants.UnitTestID, ...) so VerifyPredicate's network-ID
// check (parity with warp.VerifyEnvelope) accepts them.
rt.NetworkID = constants.UnitTestID
return consensuscontext.WithContext(context.Background(), rt)
}
// createConsensusCtx creates a context.Context instance with a validator state specified by the given validatorRanges
func createConsensusCtx(tb testing.TB, validatorRanges []validatorRange) context.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 = bls.PublicKeyToCompressedBytes(testVdrs[i].cryptoPK)
}
getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput
}
}
state := &validatorstest.State{
GetValidatorSetF: func(ctx context.Context, height uint64, chainID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
return getValidatorsOutput, nil
},
}
wrappedState := &testValidatorStateWrapper{
State: state,
GetNetworkIDF: func(id ids.ID) (ids.ID, error) {
return sourceNetworkID, nil
},
}
return newPredicateConsensusCtx(tb, wrappedState)
}
func createValidPredicateTest(consensusCtx context.Context, numKeys uint64, predicateBytes []byte) precompiletest.PredicateTest {
return precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: consensusCtx,
ProposerVMBlockCtx: &chain.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)
msg, err := warp.NewMessage(constants.UnitTestID, cChainID, addressedCall.Bytes())
require.NoError(err)
beamMsg := warp.BeamSigningBytes(msg.ID())
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(beamMsg)
require.NoError(err)
validatorOutput := &validators.GetValidatorOutput{
NodeID: testVdrs[i].nodeID,
Weight: 20,
PublicKey: bls.PublicKeyToCompressedBytes(testVdrs[i].cryptoPK),
}
getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput
blsSignatures = append(blsSignatures, sig)
}
aggregateSignature, err := bls.AggregateSignatures(blsSignatures)
require.NoError(err)
bitSet := warp.NewBitSet()
for i := 0; i < numKeys; i++ {
bitSet.Add(i)
}
beam := warp.BitSetSignature{
Signers: bitSet,
}
copy(beam.Signature[:], bls.SignatureToBytes(aggregateSignature))
warpMsg, err := warp.NewEnvelope(msg, beam, nil, nil)
require.NoError(err)
envBytes, err := warpMsg.Bytes()
require.NoError(err)
predicateBytes := predicate.PackPredicate(envBytes)
chainID := ids.GenerateTestID()
state := &validatorstest.State{
GetValidatorSetF: func(ctx context.Context, height uint64, requestedChainID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
expectedChainID := chainID
if requirePrimaryNetworkSigners {
expectedChainID = constants.PrimaryNetworkID
}
require.Equal(expectedChainID, requestedChainID)
return getValidatorsOutput, nil
},
}
// Wrap the state; C-Chain (cChainID) is on the primary network.
wrappedState := &testValidatorStateWrapper{
State: state,
GetChainIDF: func(cID ids.ID) (ids.ID, error) {
require.Equal(cID, cChainID)
// C-Chain is on the primary network, so return PrimaryNetworkID (ids.Empty)
return constants.PrimaryNetworkID, nil
},
}
// Inject the validator state via the Runtime (production path) and set the
// receiving chainID via evmconsensus for GetChainID.
consensusCtx := newPredicateConsensusCtx(t, wrappedState)
consensusCtx = evmconsensus.WithChainID(consensusCtx, chainID)
test := precompiletest.PredicateTest{
Config: NewConfig(utils.NewUint64(0), 0, requirePrimaryNetworkSigners),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: consensusCtx,
ProposerVMBlockCtx: &chain.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
consensusCtx := 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: consensusCtx,
ProposerVMBlockCtx: &chain.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
consensusCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
warpMsg := createWarpMessage(1)
warpMsgBytes, err := warpMsg.Bytes()
require.NoError(t, err)
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: consensusCtx,
ProposerVMBlockCtx: &chain.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
consensusCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
aggregateSignature, err := bls.AggregateSignatures(blsSignatures[0:numKeys])
require.NoError(t, err)
bitSet := warp.NewBitSet()
for i := 0; i < numKeys; i++ {
bitSet.Add(i)
}
beam := warp.BitSetSignature{
Signers: bitSet,
}
copy(beam.Signature[:], bls.SignatureToBytes(aggregateSignature))
// Create a signed core with an invalid addressed payload
msg, err := warp.NewMessage(constants.UnitTestID, sourceChainID, []byte{1, 2, 3})
require.NoError(t, err)
warpMsg, err := warp.NewEnvelope(msg, beam, nil, nil)
require.NoError(t, err)
warpMsgBytes, err := warpMsg.Bytes()
require.NoError(t, err)
predicateBytes := predicate.PackPredicate(warpMsgBytes)
test := precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: consensusCtx,
ProposerVMBlockCtx: &chain.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)
msg, err := warp.NewMessage(
constants.UnitTestID,
sourceChainID,
addressedCall.Bytes(),
)
require.NoError(t, err)
env, err := warp.NewEnvelope(msg, warp.BitSetSignature{
Signers: make([]byte, 1),
Signature: [bls.SignatureLen]byte{},
}, nil, nil)
require.NoError(t, err)
numKeys := 1
consensusCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
msgBytes, err := env.Bytes()
require.NoError(t, err)
predicateBytes := predicate.PackPredicate(msgBytes)
test := precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: consensusCtx,
ProposerVMBlockCtx: &chain.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner,
GasErr: errCannotGetNumSigners,
}
test.Run(t)
}
// TestWarpWrongNetworkID proves VerifyPredicate rejects a message whose Message
// is stamped with a network ID different from the verifying node's — parity
// with warp.VerifyEnvelope. The rejection happens before any signature work,
// so the (unsigned) message still passes PredicateGas.
func TestWarpWrongNetworkID(t *testing.T) {
numKeys := 10
consensusCtx := createConsensusCtx(t, []validatorRange{
{
start: 0,
end: numKeys,
weight: 20,
publicKey: true,
},
})
addressedCall, err := payload.NewAddressedCall(agoUtils.RandomBytes(20), agoUtils.RandomBytes(100))
require.NoError(t, err)
// constants.UnitTestID+1 != the test runtime's UnitTestID.
msg, err := warp.NewMessage(constants.UnitTestID+1, sourceChainID, addressedCall.Bytes())
require.NoError(t, err)
bitSet := warp.NewBitSet()
for i := 0; i < numKeys; i++ {
bitSet.Add(i)
}
warpMsg, err := warp.NewEnvelope(msg, warp.BitSetSignature{Signers: bitSet}, nil, nil)
require.NoError(t, err)
warpMsgBytes, err := warpMsg.Bytes()
require.NoError(t, err)
predicateBytes := predicate.PackPredicate(warpMsgBytes)
test := precompiletest.PredicateTest{
Config: NewDefaultConfig(utils.NewUint64(0)),
PredicateContext: &precompileconfig.PredicateContext{
ConsensusCtx: consensusCtx,
ProposerVMBlockCtx: &chain.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicateBytes,
Gas: GasCostPerSignatureVerification + uint64(len(predicateBytes))*GasCostPerWarpMessageBytes + uint64(numKeys)*GasCostPerWarpSigner,
ExpectedErr: errFailedVerification,
}
test.Run(t)
}
func TestWarpSignatureWeightsDefaultQuorumNumerator(t *testing.T) {
consensusCtx := 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: consensusCtx,
ProposerVMBlockCtx: &chain.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) {
consensusCtx := 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: consensusCtx,
ProposerVMBlockCtx: &chain.Context{
PChainHeight: 1,
},
},
PredicateBytes: predicate,
Gas: expectedGas,
GasErr: nil,
ExpectedErr: expectedErr,
}
}
}
precompiletest.RunPredicateTests(t, tests)
}
func TestWarpSignatureWeightsNonDefaultQuorumNumerator(t *testing.T) {
consensusCtx := 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: consensusCtx,
ProposerVMBlockCtx: &chain.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)
consensusCtx := createConsensusCtx(tb, []validatorRange{
{
start: 0,
end: totalNodes,
weight: 20,
publicKey: true,
},
})
predicateTests[testName] = createValidPredicateTest(consensusCtx, 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)
consensusCtx := createConsensusCtx(tb, []validatorRange{
{
start: 0,
end: numSigners,
weight: 10_000_000,
publicKey: true,
},
{
start: numSigners,
end: totalNodes,
weight: 20,
publicKey: true,
},
})
predicateTests[testName] = createValidPredicateTest(consensusCtx, 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)
consensusCtx := createConsensusCtx(tb, []validatorRange{
{
start: 0,
end: numSigners,
weight: 10_000_000,
publicKey: true,
},
{
start: numSigners,
end: totalNodes,
weight: 20,
publicKey: false,
},
})
predicateTests[testName] = createValidPredicateTest(consensusCtx, 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: bls.PublicKeyToCompressedBytes(testVdrs[i%numSigners].cryptoPK),
}
}
state := &validatorstest.State{
GetValidatorSetF: func(ctx context.Context, height uint64, chainID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
return getValidatorsOutput, nil
},
}
wrappedState := &testValidatorStateWrapper{
State: state,
GetNetworkIDF: func(id ids.ID) (ids.ID, error) {
return sourceNetworkID, nil
},
}
consensusCtx := newPredicateConsensusCtx(tb, wrappedState)
predicateTests[testName] = createValidPredicateTest(consensusCtx, uint64(numSigners), predicateBytes)
}
return predicateTests
}
func TestWarpPredicate(t *testing.T) {
// Handle potential RLP serialization issues gracefully
defer func() {
if r := recover(); r != nil {
t.Logf("Recovered from RLP serialization issue: %v", r)
// Convert panic to test failure with useful information
t.Fatalf("Warp predicate test failed due to RLP serialization: %v", r)
}
}()
predicateTests := makeWarpPredicateTests(t)
precompiletest.RunPredicateTests(t, predicateTests)
}
func BenchmarkWarpPredicate(b *testing.B) {
predicateTests := makeWarpPredicateTests(b)
precompiletest.RunPredicateBenchmarks(b, predicateTests)
}