mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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]>
835 lines
26 KiB
Go
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)
|
|
}
|