From 1c4a5103d8efa2efe1ed9fe3b54db1ee18dab535 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 13 Nov 2024 10:53:12 -0500 Subject: [PATCH] ACP-77: Implement Warp message verification (#3423) --- snow/snowtest/context.go | 3 + vms/platformvm/block/builder/builder.go | 59 ++++- vms/platformvm/block/executor/block.go | 45 ++-- vms/platformvm/block/executor/manager.go | 16 ++ .../block/executor/verifier_test.go | 6 +- .../block/executor/warp_verifier.go | 36 +++ .../block/executor/warp_verifier_test.go | 155 +++++++++++++ vms/platformvm/txs/executor/warp_verifier.go | 132 +++++++++++ .../txs/executor/warp_verifier_test.go | 206 ++++++++++++++++++ vms/platformvm/vm.go | 7 +- 10 files changed, 641 insertions(+), 24 deletions(-) create mode 100644 vms/platformvm/block/executor/warp_verifier.go create mode 100644 vms/platformvm/block/executor/warp_verifier_test.go create mode 100644 vms/platformvm/txs/executor/warp_verifier.go create mode 100644 vms/platformvm/txs/executor/warp_verifier_test.go diff --git a/snow/snowtest/context.go b/snow/snowtest/context.go index edeefe89c8ec..b1338cd4b635 100644 --- a/snow/snowtest/context.go +++ b/snow/snowtest/context.go @@ -65,6 +65,9 @@ func Context(tb testing.TB, chainID ids.ID) *snow.Context { require.NoError(aliaser.Alias(CChainID, CChainID.String())) validatorState := &validatorstest.State{ + GetMinimumHeightF: func(context.Context) (uint64, error) { + return 0, nil + }, GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { subnetID, ok := map[ids.ID]ids.ID{ constants.PlatformChainID: constants.PrimaryNetworkID, diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index 40ce8a3633c7..410d3285a9e7 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -27,6 +27,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" + smblock "github.com/ava-labs/avalanchego/snow/engine/snowman/block" blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" ) @@ -51,6 +52,7 @@ var ( ) type Builder interface { + smblock.BuildBlockWithContextChainVM mempool.Mempool // StartBlockTimer starts to issue block creation requests to advance the @@ -208,10 +210,19 @@ func (b *builder) ShutdownBlockTimer() { }) } -// BuildBlock builds a block to be added to consensus. -// This method removes the transactions from the returned -// blocks from the mempool. -func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { +func (b *builder) BuildBlock(ctx context.Context) (snowman.Block, error) { + return b.BuildBlockWithContext( + ctx, + &smblock.Context{ + PChainHeight: 0, + }, + ) +} + +func (b *builder) BuildBlockWithContext( + ctx context.Context, + blockContext *smblock.Context, +) (snowman.Block, error) { // If there are still transactions in the mempool, then we need to // re-trigger block building. defer b.Mempool.RequestBuildBlock(false /*=emptyBlockPermitted*/) @@ -240,12 +251,14 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { } statelessBlk, err := buildBlock( + ctx, b, preferredID, nextHeight, timestamp, timeWasCapped, preferredState, + blockContext.PChainHeight, ) if err != nil { return nil, err @@ -270,36 +283,47 @@ func (b *builder) PackAllBlockTxs() ([]*txs.Tx, error) { return nil, fmt.Errorf("could not calculate next staker change time: %w", err) } + recommendedPChainHeight, err := b.txExecutorBackend.Ctx.ValidatorState.GetMinimumHeight(context.TODO()) + if err != nil { + return nil, err + } + if !b.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { return packDurangoBlockTxs( + context.TODO(), preferredID, preferredState, b.Mempool, b.txExecutorBackend, b.blkManager, timestamp, + recommendedPChainHeight, math.MaxInt, ) } return packEtnaBlockTxs( + context.TODO(), preferredID, preferredState, b.Mempool, b.txExecutorBackend, b.blkManager, timestamp, + recommendedPChainHeight, math.MaxUint64, ) } // [timestamp] is min(max(now, parent timestamp), next staker change time) func buildBlock( + ctx context.Context, builder *builder, parentID ids.ID, height uint64, timestamp time.Time, forceAdvanceTime bool, parentState state.Chain, + pChainHeight uint64, ) (block.Block, error) { var ( blockTxs []*txs.Tx @@ -307,22 +331,26 @@ func buildBlock( ) if builder.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { blockTxs, err = packEtnaBlockTxs( + ctx, parentID, parentState, builder.Mempool, builder.txExecutorBackend, builder.blkManager, timestamp, + pChainHeight, 0, // minCapacity is 0 as we want to honor the capacity in state. ) } else { blockTxs, err = packDurangoBlockTxs( + ctx, parentID, parentState, builder.Mempool, builder.txExecutorBackend, builder.blkManager, timestamp, + pChainHeight, targetBlockSize, ) } @@ -368,12 +396,14 @@ func buildBlock( } func packDurangoBlockTxs( + ctx context.Context, parentID ids.ID, parentState state.Chain, mempool mempool.Mempool, backend *txexecutor.Backend, manager blockexecutor.Manager, timestamp time.Time, + pChainHeight uint64, remainingSize int, ) ([]*txs.Tx, error) { stateDiff, err := state.NewDiffOn(parentState) @@ -401,11 +431,13 @@ func packDurangoBlockTxs( } shouldAdd, err := executeTx( + ctx, parentID, stateDiff, mempool, backend, manager, + pChainHeight, &inputs, feeCalculator, tx, @@ -425,12 +457,14 @@ func packDurangoBlockTxs( } func packEtnaBlockTxs( + ctx context.Context, parentID ids.ID, parentState state.Chain, mempool mempool.Mempool, backend *txexecutor.Backend, manager blockexecutor.Manager, timestamp time.Time, + pChainHeight uint64, minCapacity gas.Gas, ) ([]*txs.Tx, error) { stateDiff, err := state.NewDiffOn(parentState) @@ -474,11 +508,13 @@ func packEtnaBlockTxs( } shouldAdd, err := executeTx( + ctx, parentID, stateDiff, mempool, backend, manager, + pChainHeight, &inputs, feeCalculator, tx, @@ -498,11 +534,13 @@ func packEtnaBlockTxs( } func executeTx( + ctx context.Context, parentID ids.ID, stateDiff state.Diff, mempool mempool.Mempool, backend *txexecutor.Backend, manager blockexecutor.Manager, + pChainHeight uint64, inputs *set.Set[ids.ID], feeCalculator fee.Calculator, tx *txs.Tx, @@ -511,6 +549,19 @@ func executeTx( // Invariant: [tx] has already been syntactically verified. + err := txexecutor.VerifyWarpMessages( + ctx, + backend.Ctx.NetworkID, + backend.Ctx.ValidatorState, + pChainHeight, + tx.Unsigned, + ) + if err != nil { + txID := tx.ID() + mempool.MarkDropped(txID, err) + return false, nil + } + txDiff, err := state.NewDiffOn(stateDiff) if err != nil { return false, err diff --git a/vms/platformvm/block/executor/block.go b/vms/platformvm/block/executor/block.go index 76cf6ff9078f..b5f7e46308bf 100644 --- a/vms/platformvm/block/executor/block.go +++ b/vms/platformvm/block/executor/block.go @@ -29,34 +29,49 @@ func (*Block) ShouldVerifyWithContext(context.Context) (bool, error) { return true, nil } -func (b *Block) VerifyWithContext(_ context.Context, ctx *smblock.Context) error { - pChainHeight := uint64(0) - if ctx != nil { - pChainHeight = ctx.PChainHeight - } - +func (b *Block) VerifyWithContext(ctx context.Context, blockContext *smblock.Context) error { blkID := b.ID() - if blkState, ok := b.manager.blkIDToState[blkID]; ok { - if !blkState.verifiedHeights.Contains(pChainHeight) { - // PlatformVM blocks are currently valid regardless of the ProposerVM's - // PChainHeight. If this changes, those validity checks should be done prior - // to adding [pChainHeight] to [verifiedHeights]. - blkState.verifiedHeights.Add(pChainHeight) + blkState, previouslyExecuted := b.manager.blkIDToState[blkID] + warpAlreadyVerified := previouslyExecuted && blkState.verifiedHeights.Contains(blockContext.PChainHeight) + + // If the chain is bootstrapped and the warp messages haven't been verified, + // we must verify them. + if !warpAlreadyVerified && b.manager.txExecutorBackend.Bootstrapped.Get() { + err := VerifyWarpMessages( + ctx, + b.manager.ctx.NetworkID, + b.manager.ctx.ValidatorState, + blockContext.PChainHeight, + b, + ) + if err != nil { + return err } + } - // This block has already been verified. + // If the block was previously executed, we don't need to execute it again, + // we can just mark that the warp messages are valid at this height. + if previouslyExecuted { + blkState.verifiedHeights.Add(blockContext.PChainHeight) return nil } + // Since this is the first time we are verifying this block, we must execute + // the state transitions to generate the state diffs. return b.Visit(&verifier{ backend: b.manager.backend, txExecutorBackend: b.manager.txExecutorBackend, - pChainHeight: pChainHeight, + pChainHeight: blockContext.PChainHeight, }) } func (b *Block) Verify(ctx context.Context) error { - return b.VerifyWithContext(ctx, nil) + return b.VerifyWithContext( + ctx, + &smblock.Context{ + PChainHeight: 0, + }, + ) } func (b *Block) Accept(context.Context) error { diff --git a/vms/platformvm/block/executor/manager.go b/vms/platformvm/block/executor/manager.go index 7f153a83be3f..53fcbabbaf29 100644 --- a/vms/platformvm/block/executor/manager.go +++ b/vms/platformvm/block/executor/manager.go @@ -4,6 +4,7 @@ package executor import ( + "context" "errors" "github.com/ava-labs/avalanchego/ids" @@ -122,6 +123,21 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { return ErrChainNotSynced } + recommendedPChainHeight, err := m.ctx.ValidatorState.GetMinimumHeight(context.TODO()) + if err != nil { + return err + } + err = executor.VerifyWarpMessages( + context.TODO(), + m.ctx.NetworkID, + m.ctx.ValidatorState, + recommendedPChainHeight, + tx.Unsigned, + ) + if err != nil { + return err + } + stateDiff, err := state.NewDiff(m.preferred, m) if err != nil { return err diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index f1715842aedf..4060c6152803 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -465,7 +465,8 @@ func TestVerifierVisitCommitBlock(t *testing.T) { Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, - Clk: &mockable.Clock{}, + Clk: &mockable.Clock{}, + Bootstrapped: utils.NewAtomic(true), }, backend: backend, } @@ -532,7 +533,8 @@ func TestVerifierVisitAbortBlock(t *testing.T) { Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, - Clk: &mockable.Clock{}, + Clk: &mockable.Clock{}, + Bootstrapped: utils.NewAtomic(true), }, backend: backend, } diff --git a/vms/platformvm/block/executor/warp_verifier.go b/vms/platformvm/block/executor/warp_verifier.go new file mode 100644 index 000000000000..30e16c23e376 --- /dev/null +++ b/vms/platformvm/block/executor/warp_verifier.go @@ -0,0 +1,36 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/vms/platformvm/block" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" +) + +// VerifyWarpMessages verifies all warp messages in the block. If any of the +// warp messages are invalid, an error is returned. +func VerifyWarpMessages( + ctx context.Context, + networkID uint32, + validatorState validators.State, + pChainHeight uint64, + b block.Block, +) error { + for _, tx := range b.Txs() { + err := executor.VerifyWarpMessages( + ctx, + networkID, + validatorState, + pChainHeight, + tx.Unsigned, + ) + if err != nil { + return err + } + } + return nil +} diff --git a/vms/platformvm/block/executor/warp_verifier_test.go b/vms/platformvm/block/executor/warp_verifier_test.go new file mode 100644 index 000000000000..c05ffc9057eb --- /dev/null +++ b/vms/platformvm/block/executor/warp_verifier_test.go @@ -0,0 +1,155 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/vms/platformvm/block" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" +) + +func TestVerifyWarpMessages(t *testing.T) { + var ( + validTx = &txs.Tx{ + Unsigned: &txs.BaseTx{}, + } + invalidTx = &txs.Tx{ + Unsigned: &txs.RegisterSubnetValidatorTx{}, + } + ) + + tests := []struct { + name string + block block.Block + expectedErr error + }{ + { + name: "BanffAbortBlock", + block: &block.BanffAbortBlock{}, + }, + { + name: "BanffCommitBlock", + block: &block.BanffCommitBlock{}, + }, + { + name: "BanffProposalBlock with invalid standard tx", + block: &block.BanffProposalBlock{ + Transactions: []*txs.Tx{ + invalidTx, + }, + ApricotProposalBlock: block.ApricotProposalBlock{ + Tx: validTx, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "BanffProposalBlock with invalid proposal tx", + block: &block.BanffProposalBlock{ + ApricotProposalBlock: block.ApricotProposalBlock{ + Tx: invalidTx, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "BanffProposalBlock with valid proposal tx", + block: &block.BanffProposalBlock{ + ApricotProposalBlock: block.ApricotProposalBlock{ + Tx: validTx, + }, + }, + }, + { + name: "BanffStandardBlock with invalid tx", + block: &block.BanffStandardBlock{ + ApricotStandardBlock: block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + invalidTx, + }, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "BanffStandardBlock with valid tx", + block: &block.BanffStandardBlock{ + ApricotStandardBlock: block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + validTx, + }, + }, + }, + }, + { + name: "ApricotAbortBlock", + block: &block.ApricotAbortBlock{}, + }, + { + name: "ApricotCommitBlock", + block: &block.ApricotCommitBlock{}, + }, + { + name: "ApricotProposalBlock with invalid proposal tx", + block: &block.ApricotProposalBlock{ + Tx: invalidTx, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "ApricotProposalBlock with valid proposal tx", + block: &block.ApricotProposalBlock{ + Tx: validTx, + }, + }, + { + name: "ApricotStandardBlock with invalid tx", + block: &block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + invalidTx, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "ApricotStandardBlock with valid tx", + block: &block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + validTx, + }, + }, + }, + { + name: "ApricotAtomicBlock with invalid proposal tx", + block: &block.ApricotAtomicBlock{ + Tx: invalidTx, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "ApricotAtomicBlock with valid proposal tx", + block: &block.ApricotAtomicBlock{ + Tx: validTx, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := VerifyWarpMessages( + context.Background(), + constants.UnitTestID, + nil, + 0, + test.block, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go new file mode 100644 index 000000000000..731cac7b3827 --- /dev/null +++ b/vms/platformvm/txs/executor/warp_verifier.go @@ -0,0 +1,132 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +const ( + WarpQuorumNumerator = 67 + WarpQuorumDenominator = 100 +) + +var _ txs.Visitor = (*warpVerifier)(nil) + +// VerifyWarpMessages verifies all warp messages in the tx. If any of the warp +// messages are invalid, an error is returned. +func VerifyWarpMessages( + ctx context.Context, + networkID uint32, + validatorState validators.State, + pChainHeight uint64, + tx txs.UnsignedTx, +) error { + return tx.Visit(&warpVerifier{ + context: ctx, + networkID: networkID, + validatorState: validatorState, + pChainHeight: pChainHeight, + }) +} + +type warpVerifier struct { + context context.Context + networkID uint32 + validatorState validators.State + pChainHeight uint64 +} + +func (*warpVerifier) AddValidatorTx(*txs.AddValidatorTx) error { + return nil +} + +func (*warpVerifier) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { + return nil +} + +func (*warpVerifier) AddDelegatorTx(*txs.AddDelegatorTx) error { + return nil +} + +func (*warpVerifier) CreateChainTx(*txs.CreateChainTx) error { + return nil +} + +func (*warpVerifier) CreateSubnetTx(*txs.CreateSubnetTx) error { + return nil +} + +func (*warpVerifier) ImportTx(*txs.ImportTx) error { + return nil +} + +func (*warpVerifier) ExportTx(*txs.ExportTx) error { + return nil +} + +func (*warpVerifier) AdvanceTimeTx(*txs.AdvanceTimeTx) error { + return nil +} + +func (*warpVerifier) RewardValidatorTx(*txs.RewardValidatorTx) error { + return nil +} + +func (*warpVerifier) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { + return nil +} + +func (*warpVerifier) TransformSubnetTx(*txs.TransformSubnetTx) error { + return nil +} + +func (*warpVerifier) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { + return nil +} + +func (*warpVerifier) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { + return nil +} + +func (*warpVerifier) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { + return nil +} + +func (*warpVerifier) BaseTx(*txs.BaseTx) error { + return nil +} + +func (*warpVerifier) ConvertSubnetTx(*txs.ConvertSubnetTx) error { + return nil +} + +func (w *warpVerifier) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { + return w.verify(tx.Message) +} + +func (w *warpVerifier) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { + return w.verify(tx.Message) +} + +func (w *warpVerifier) verify(message []byte) error { + msg, err := warp.ParseMessage(message) + if err != nil { + return err + } + + return msg.Signature.Verify( + w.context, + &msg.UnsignedMessage, + w.networkID, + w.validatorState, + w.pChainHeight, + WarpQuorumNumerator, + WarpQuorumDenominator, + ) +} diff --git a/vms/platformvm/txs/executor/warp_verifier_test.go b/vms/platformvm/txs/executor/warp_verifier_test.go new file mode 100644 index 000000000000..fd925f4f7825 --- /dev/null +++ b/vms/platformvm/txs/executor/warp_verifier_test.go @@ -0,0 +1,206 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorstest" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +func TestVerifyWarpMessages(t *testing.T) { + var ( + subnetID = ids.GenerateTestID() + chainID = ids.GenerateTestID() + newValidator = func() (*bls.SecretKey, *validators.GetValidatorOutput) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + + return sk, &validators.GetValidatorOutput{ + NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicFromSecretKey(sk), + Weight: 1, + } + } + sk0, vdr0 = newValidator() + sk1, vdr1 = newValidator() + vdrs = map[ids.NodeID]*validators.GetValidatorOutput{ + vdr0.NodeID: vdr0, + vdr1.NodeID: vdr1, + } + state = &validatorstest.State{ + T: t, + GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { + return subnetID, nil + }, + GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + return vdrs, nil + }, + } + ) + + validUnsignedWarpMessage, err := warp.NewUnsignedMessage( + constants.UnitTestID, + chainID, + nil, + ) + require.NoError(t, err) + + var ( + sig0 = bls.Sign(sk0, validUnsignedWarpMessage.Bytes()) + sig1 = bls.Sign(sk1, validUnsignedWarpMessage.Bytes()) + ) + sig, err := bls.AggregateSignatures([]*bls.Signature{sig0, sig1}) + require.NoError(t, err) + + warpSignature := &warp.BitSetSignature{ + Signers: set.NewBits(0, 1).Bytes(), + Signature: [bls.SignatureLen]byte(bls.SignatureToBytes(sig)), + } + validWarpMessage, err := warp.NewMessage( + validUnsignedWarpMessage, + warpSignature, + ) + require.NoError(t, err) + + invalidWarpMessage, err := warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + constants.UnitTestID+1, + chainID, + nil, + )), + warpSignature, + ) + require.NoError(t, err) + + tests := []struct { + name string + tx txs.UnsignedTx + expectedErr error + }{ + { + name: "AddValidatorTx", + tx: &txs.AddValidatorTx{}, + }, + { + name: "AddSubnetValidatorTx", + tx: &txs.AddSubnetValidatorTx{}, + }, + { + name: "AddDelegatorTx", + tx: &txs.AddDelegatorTx{}, + }, + { + name: "CreateChainTx", + tx: &txs.CreateChainTx{}, + }, + { + name: "CreateSubnetTx", + tx: &txs.CreateSubnetTx{}, + }, + { + name: "ImportTx", + tx: &txs.ImportTx{}, + }, + { + name: "ExportTx", + tx: &txs.ExportTx{}, + }, + { + name: "AdvanceTimeTx", + tx: &txs.AdvanceTimeTx{}, + }, + { + name: "RewardValidatorTx", + tx: &txs.RewardValidatorTx{}, + }, + { + name: "RemoveSubnetValidatorTx", + tx: &txs.RemoveSubnetValidatorTx{}, + }, + { + name: "TransformSubnetTx", + tx: &txs.TransformSubnetTx{}, + }, + { + name: "AddPermissionlessValidatorTx", + tx: &txs.AddPermissionlessValidatorTx{}, + }, + { + name: "AddPermissionlessDelegatorTx", + tx: &txs.AddPermissionlessDelegatorTx{}, + }, + { + name: "TransferSubnetOwnershipTx", + tx: &txs.TransferSubnetOwnershipTx{}, + }, + { + name: "BaseTx", + tx: &txs.BaseTx{}, + }, + { + name: "ConvertSubnetTx", + tx: &txs.ConvertSubnetTx{}, + }, + { + name: "RegisterSubnetValidatorTx with unparsable message", + tx: &txs.RegisterSubnetValidatorTx{}, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "RegisterSubnetValidatorTx with invalid message", + tx: &txs.RegisterSubnetValidatorTx{ + Message: invalidWarpMessage.Bytes(), + }, + expectedErr: warp.ErrWrongNetworkID, + }, + { + name: "RegisterSubnetValidatorTx with valid message", + tx: &txs.RegisterSubnetValidatorTx{ + Message: validWarpMessage.Bytes(), + }, + }, + { + name: "SetSubnetValidatorWeightTx with unparsable message", + tx: &txs.SetSubnetValidatorWeightTx{}, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "SetSubnetValidatorWeightTx with invalid message", + tx: &txs.SetSubnetValidatorWeightTx{ + Message: invalidWarpMessage.Bytes(), + }, + expectedErr: warp.ErrWrongNetworkID, + }, + { + name: "SetSubnetValidatorWeightTx with valid message", + tx: &txs.SetSubnetValidatorWeightTx{ + Message: validWarpMessage.Bytes(), + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := VerifyWarpMessages( + context.Background(), + constants.UnitTestID, + state, + 0, + test.tx, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 7006c02d17ba..15ad3362d388 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -52,9 +52,10 @@ import ( ) var ( - _ snowmanblock.ChainVM = (*VM)(nil) - _ secp256k1fx.VM = (*VM)(nil) - _ validators.State = (*VM)(nil) + _ snowmanblock.ChainVM = (*VM)(nil) + _ snowmanblock.BuildBlockWithContextChainVM = (*VM)(nil) + _ secp256k1fx.VM = (*VM)(nil) + _ validators.State = (*VM)(nil) ) type VM struct {