Skip to content

Commit

Permalink
Move predicate header parsing (#824)
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenButtolph authored Feb 25, 2025
1 parent 2663434 commit 94dca86
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 66 deletions.
40 changes: 11 additions & 29 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"github.com/ava-labs/coreth/core/vm"
"github.com/ava-labs/coreth/predicate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
)

Expand All @@ -51,33 +50,6 @@ type ChainContext interface {

// NewEVMBlockContext creates a new context for use in the EVM.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
predicateBytes, ok := predicate.GetPredicateResultBytes(header.Extra)
if !ok {
return newEVMBlockContext(header, chain, author, nil)
}
// Prior to Durango, the VM enforces the extra data is smaller than or
// equal to this size. After Durango, the VM pre-verifies the extra
// data past the dynamic fee rollup window is valid.
predicateResults, err := predicate.ParseResults(predicateBytes)
if err != nil {
log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra)
// As mentioned above, we pre-verify the extra data to ensure this never happens.
// If we hit an error, construct a new block context rather than use a potentially half initialized value
// as defense in depth.
return newEVMBlockContext(header, chain, author, nil)
}
return newEVMBlockContext(header, chain, author, predicateResults)
}

// NewEVMBlockContextWithPredicateResults creates a new context for use in the EVM with an override for the predicate results that is not present
// in header.Extra.
// This function is used to create a BlockContext when the header Extra data is not fully formed yet and it's more efficient to pass in predicateResults
// directly rather than re-encode the latest results when executing each individaul transaction.
func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
return newEVMBlockContext(header, chain, author, predicateResults)
}

func newEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
var (
beneficiary common.Address
baseFee *big.Int
Expand All @@ -102,7 +74,7 @@ func newEVMBlockContext(header *types.Header, chain ChainContext, author *common
Transfer: Transfer,
TransferMultiCoin: TransferMultiCoin,
GetHash: GetHashFn(header, chain),
PredicateResults: predicateResults,
Extra: header.Extra,
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Expand All @@ -113,6 +85,16 @@ func newEVMBlockContext(header *types.Header, chain ChainContext, author *common
}
}

// NewEVMBlockContextWithPredicateResults creates a new context for use in the EVM with an override for the predicate results that is not present
// in header.Extra.
// This function is used to create a BlockContext when the header Extra data is not fully formed yet and it's more efficient to pass in predicateResults
// directly rather than re-encode the latest results when executing each individaul transaction.
func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
blockContext := NewEVMBlockContext(header, chain, author)
blockContext.PredicateResults = predicateResults
return blockContext
}

// NewEVMTxContext creates a new transaction context for a single transaction.
func NewEVMTxContext(msg *Message) vm.TxContext {
ctx := vm.TxContext{
Expand Down
37 changes: 35 additions & 2 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ import (
"github.com/ava-labs/coreth/constants"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/plugin/evm/header"
"github.com/ava-labs/coreth/precompile/contract"
"github.com/ava-labs/coreth/precompile/modules"
"github.com/ava-labs/coreth/precompile/precompileconfig"
"github.com/ava-labs/coreth/predicate"
"github.com/ava-labs/coreth/vmerrs"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
)

Expand Down Expand Up @@ -122,9 +124,14 @@ type BlockContext struct {
TransferMultiCoin TransferMCFunc
// GetHash returns the hash corresponding to n
GetHash GetHashFunc
// PredicateResults are the results of predicate verification available throughout the EVM's execution.
// PredicateResults may be nil if it is not encoded in the block's header.
// PredicateResults are the results of predicate verification available
// throughout the EVM's execution.
//
// PredicateResults may be nil if it is not encoded in the extra field of
// the block's header or if the extra field has not been parsed yet.
PredicateResults *predicate.Results
// Extra is the extra field from the block header.
Extra []byte

// Block information
Coinbase common.Address // Provides information for COINBASE
Expand Down Expand Up @@ -220,6 +227,32 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Time),
}
evm.interpreter = NewEVMInterpreter(evm)

// If the predicate results were set by the miner, use them.
if blockCtx.PredicateResults != nil {
return evm
}

// Parse the predicate results from the extra field and store them in the
// block context.
predicateBytes := header.PredicateBytesFromExtra(blockCtx.Extra)
if len(predicateBytes) == 0 {
return evm
}

// The VM has already verified the correctness of the results during header
// validation.
results, err := predicate.ParseResults(predicateBytes)
if err != nil {
log.Error("Unexpected error parsing predicate results",
"err", err,
)
return evm
}

// Because the BlockContext is pass-by-value, this does not cache the
// results for future calls to NewEVM.
evm.Context.PredicateResults = results
return evm
}

Expand Down
6 changes: 2 additions & 4 deletions plugin/evm/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/plugin/evm/atomic"
"github.com/ava-labs/coreth/plugin/evm/header"
"github.com/ava-labs/coreth/precompile/precompileconfig"
"github.com/ava-labs/coreth/predicate"

Expand Down Expand Up @@ -381,10 +382,7 @@ func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateCon
return fmt.Errorf("failed to marshal predicate results: %w", err)
}
extraData := b.ethBlock.Extra()
headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(extraData)
if !ok {
return fmt.Errorf("failed to find predicate results in extra data: %x", extraData)
}
headerPredicateResultsBytes := header.PredicateBytesFromExtra(extraData)
if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) {
return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes)
}
Expand Down
13 changes: 13 additions & 0 deletions plugin/evm/header/extra.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,16 @@ func VerifyExtra(rules params.AvalancheRules, extra []byte) error {
}
return nil
}

// PredicateBytesFromExtra returns the predicate result bytes from the header's
// extra data. If the extra data is not long enough, an empty slice is returned.
func PredicateBytesFromExtra(extra []byte) []byte {
// Prior to Durango, the VM enforces the extra data is smaller than or equal
// to this size.
// After Durango, the VM pre-verifies the extra data past the dynamic fee
// rollup window is valid.
if len(extra) <= FeeWindowSize {
return nil
}
return extra[FeeWindowSize:]
}
37 changes: 37 additions & 0 deletions plugin/evm/header/extra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,40 @@ func TestVerifyExtra(t *testing.T) {
})
}
}

func TestPredicateBytesFromExtra(t *testing.T) {
tests := []struct {
name string
extra []byte
expected []byte
}{
{
name: "empty_extra",
extra: nil,
expected: nil,
},
{
name: "too_short",
extra: make([]byte, FeeWindowSize-1),
expected: nil,
},
{
name: "empty_predicate",
extra: make([]byte, FeeWindowSize),
expected: nil,
},
{
name: "non_empty_predicate",
extra: []byte{
FeeWindowSize: 5,
},
expected: []byte{5},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := PredicateBytesFromExtra(test.extra)
require.Equal(t, test.expected, got)
})
}
}
4 changes: 2 additions & 2 deletions plugin/evm/vm_warp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/eth/tracers"
"github.com/ava-labs/coreth/params"
customheader "github.com/ava-labs/coreth/plugin/evm/header"
"github.com/ava-labs/coreth/plugin/evm/message"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap0"
"github.com/ava-labs/coreth/precompile/contract"
Expand Down Expand Up @@ -652,8 +653,7 @@ func testReceiveWarpMessage(

// Require the block was built with a successful predicate result
ethBlock := block2.(*chain.BlockWrapper).Block.(*Block).ethBlock
headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(ethBlock.Extra())
require.True(ok)
headerPredicateResultsBytes := customheader.PredicateBytesFromExtra(ethBlock.Extra())
results, err := predicate.ParseResults(headerPredicateResultsBytes)
require.NoError(err)

Expand Down
13 changes: 0 additions & 13 deletions predicate/predicate_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package predicate
import (
"fmt"

"github.com/ava-labs/coreth/plugin/evm/header"
"github.com/ethereum/go-ethereum/common"
)

Expand Down Expand Up @@ -50,15 +49,3 @@ func UnpackPredicate(paddedPredicate []byte) ([]byte, error) {

return trimmedPredicateBytes[:len(trimmedPredicateBytes)-1], nil
}

// GetPredicateResultBytes returns the predicate result bytes from the extra data and
// true iff the predicate results bytes have non-zero length.
func GetPredicateResultBytes(extraData []byte) ([]byte, bool) {
// Prior to Durango, the VM enforces the extra data is smaller than or equal to this size.
// After Durango, the VM pre-verifies the extra data past the dynamic fee rollup window is
// valid.
if len(extraData) <= header.FeeWindowSize {
return nil, false
}
return extraData[header.FeeWindowSize:], true
}
16 changes: 0 additions & 16 deletions predicate/predicate_bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"testing"

"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/coreth/plugin/evm/header"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -49,18 +48,3 @@ func TestUnpackInvalidPredicate(t *testing.T) {
}
}
}

func TestPredicateResultsBytes(t *testing.T) {
require := require.New(t)
dataTooShort := utils.RandomBytes(header.FeeWindowSize - 1)
_, ok := GetPredicateResultBytes(dataTooShort)
require.False(ok)

preDurangoData := utils.RandomBytes(header.FeeWindowSize)
_, ok = GetPredicateResultBytes(preDurangoData)
require.False(ok)
postDurangoData := utils.RandomBytes(header.FeeWindowSize + 2)
resultBytes, ok := GetPredicateResultBytes(postDurangoData)
require.True(ok)
require.Equal(resultBytes, postDurangoData[header.FeeWindowSize:])
}

0 comments on commit 94dca86

Please sign in to comment.