Skip to content

Commit

Permalink
ACP-77: Implement Warp message verification (#3423)
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenButtolph authored Nov 13, 2024
1 parent 3696d68 commit 1c4a510
Show file tree
Hide file tree
Showing 10 changed files with 641 additions and 24 deletions.
3 changes: 3 additions & 0 deletions snow/snowtest/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
59 changes: 55 additions & 4 deletions vms/platformvm/block/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -51,6 +52,7 @@ var (
)

type Builder interface {
smblock.BuildBlockWithContextChainVM
mempool.Mempool

// StartBlockTimer starts to issue block creation requests to advance the
Expand Down Expand Up @@ -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*/)
Expand Down Expand Up @@ -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
Expand All @@ -270,59 +283,74 @@ 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
err error
)
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,
)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -401,11 +431,13 @@ func packDurangoBlockTxs(
}

shouldAdd, err := executeTx(
ctx,
parentID,
stateDiff,
mempool,
backend,
manager,
pChainHeight,
&inputs,
feeCalculator,
tx,
Expand All @@ -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)
Expand Down Expand Up @@ -474,11 +508,13 @@ func packEtnaBlockTxs(
}

shouldAdd, err := executeTx(
ctx,
parentID,
stateDiff,
mempool,
backend,
manager,
pChainHeight,
&inputs,
feeCalculator,
tx,
Expand All @@ -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,
Expand All @@ -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
Expand Down
45 changes: 30 additions & 15 deletions vms/platformvm/block/executor/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions vms/platformvm/block/executor/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package executor

import (
"context"
"errors"

"github.com/ava-labs/avalanchego/ids"
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions vms/platformvm/block/executor/verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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,
}
Expand Down
36 changes: 36 additions & 0 deletions vms/platformvm/block/executor/warp_verifier.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 1c4a510

Please sign in to comment.