Move predicate header parsing

See https://github.com/ava-labs/coreth/pull/824
This commit is contained in:
Stephen Buttolph
2025-03-13 12:43:57 +01:00
committed by Quentin Mc Gaw
parent b05cd69548
commit a414698c8f
8 changed files with 110 additions and 76 deletions
+21 -39
View File
@@ -35,7 +35,6 @@ import (
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/predicate"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
)
@@ -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
@@ -97,20 +69,30 @@ func newEVMBlockContext(header *types.Header, chain ChainContext, author *common
blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas)
}
return vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
PredicateResults: predicateResults,
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit,
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Extra: header.Extra,
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit,
}
}
// 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{
+35 -2
View File
@@ -35,6 +35,7 @@ import (
"github.com/ava-labs/subnet-evm/constants"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/plugin/evm/header"
"github.com/ava-labs/subnet-evm/precompile/contract"
"github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist"
"github.com/ava-labs/subnet-evm/precompile/modules"
@@ -43,6 +44,7 @@ import (
"github.com/ava-labs/subnet-evm/vmerrs"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
)
@@ -111,9 +113,14 @@ type BlockContext struct {
Transfer TransferFunc
// 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
@@ -209,6 +216,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
}
+2 -4
View File
@@ -17,6 +17,7 @@ import (
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/plugin/evm/header"
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
"github.com/ava-labs/subnet-evm/predicate"
@@ -242,10 +243,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)
}
+13
View File
@@ -68,3 +68,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
View File
@@ -183,3 +183,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)
})
}
}
+2 -2
View File
@@ -32,6 +32,7 @@ import (
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/eth/tracers"
"github.com/ava-labs/subnet-evm/params"
customheader "github.com/ava-labs/subnet-evm/plugin/evm/header"
"github.com/ava-labs/subnet-evm/plugin/evm/message"
"github.com/ava-labs/subnet-evm/precompile/contract"
warpcontract "github.com/ava-labs/subnet-evm/precompile/contracts/warp"
@@ -668,8 +669,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)
-13
View File
@@ -6,7 +6,6 @@ package predicate
import (
"fmt"
"github.com/ava-labs/subnet-evm/plugin/evm/header"
"github.com/ethereum/go-ethereum/common"
)
@@ -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
View File
@@ -8,7 +8,6 @@ import (
"testing"
"github.com/ava-labs/avalanchego/utils"
"github.com/ava-labs/subnet-evm/plugin/evm/header"
"github.com/stretchr/testify/require"
)
@@ -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:])
}