diff --git a/core/evm.go b/core/evm.go index bc33411714..6e537fd334 100644 --- a/core/evm.go +++ b/core/evm.go @@ -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" ) @@ -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 @@ -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, @@ -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{ diff --git a/core/vm/evm.go b/core/vm/evm.go index 0eb7ec12df..eac394b362 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -34,6 +34,7 @@ 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" @@ -41,6 +42,7 @@ import ( "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" ) @@ -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 @@ -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 } diff --git a/plugin/evm/block.go b/plugin/evm/block.go index f94f3ebd81..d30a943fa8 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -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" @@ -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) } diff --git a/plugin/evm/header/extra.go b/plugin/evm/header/extra.go index a8652d9a03..e4db0b6c6f 100644 --- a/plugin/evm/header/extra.go +++ b/plugin/evm/header/extra.go @@ -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:] +} diff --git a/plugin/evm/header/extra_test.go b/plugin/evm/header/extra_test.go index 654b71792d..c00fe31430 100644 --- a/plugin/evm/header/extra_test.go +++ b/plugin/evm/header/extra_test.go @@ -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) + }) + } +} diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 169731232d..26696986bd 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -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" @@ -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) diff --git a/predicate/predicate_bytes.go b/predicate/predicate_bytes.go index bc9fb25756..8947741b3c 100644 --- a/predicate/predicate_bytes.go +++ b/predicate/predicate_bytes.go @@ -6,7 +6,6 @@ package predicate import ( "fmt" - "github.com/ava-labs/coreth/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 -} diff --git a/predicate/predicate_bytes_test.go b/predicate/predicate_bytes_test.go index 52431b55b3..9742ed2858 100644 --- a/predicate/predicate_bytes_test.go +++ b/predicate/predicate_bytes_test.go @@ -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" ) @@ -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:]) -}