Skip to content

Commit 639187e

Browse files
StephenButtolphceyonur
authored andcommitted
Move predicate header parsing (#824)
1 parent f12bff0 commit 639187e

File tree

8 files changed

+110
-76
lines changed

8 files changed

+110
-76
lines changed

core/evm.go

+21-39
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import (
3535
"github.com/ava-labs/subnet-evm/core/vm"
3636
"github.com/ava-labs/subnet-evm/predicate"
3737
"github.com/ethereum/go-ethereum/common"
38-
"github.com/ethereum/go-ethereum/log"
3938
"github.com/holiman/uint256"
4039
)
4140

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

5251
// NewEVMBlockContext creates a new context for use in the EVM.
5352
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
54-
predicateBytes, ok := predicate.GetPredicateResultBytes(header.Extra)
55-
if !ok {
56-
return newEVMBlockContext(header, chain, author, nil)
57-
}
58-
// Prior to Durango, the VM enforces the extra data is smaller than or
59-
// equal to this size. After Durango, the VM pre-verifies the extra
60-
// data past the dynamic fee rollup window is valid.
61-
predicateResults, err := predicate.ParseResults(predicateBytes)
62-
if err != nil {
63-
log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra)
64-
// As mentioned above, we pre-verify the extra data to ensure this never happens.
65-
// If we hit an error, construct a new block context rather than use a potentially half initialized value
66-
// as defense in depth.
67-
return newEVMBlockContext(header, chain, author, nil)
68-
}
69-
return newEVMBlockContext(header, chain, author, predicateResults)
70-
}
71-
72-
// NewEVMBlockContextWithPredicateResults creates a new context for use in the EVM with an override for the predicate results that is not present
73-
// in header.Extra.
74-
// 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
75-
// directly rather than re-encode the latest results when executing each individaul transaction.
76-
func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
77-
return newEVMBlockContext(header, chain, author, predicateResults)
78-
}
79-
80-
func newEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
8153
var (
8254
beneficiary common.Address
8355
baseFee *big.Int
@@ -97,20 +69,30 @@ func newEVMBlockContext(header *types.Header, chain ChainContext, author *common
9769
blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas)
9870
}
9971
return vm.BlockContext{
100-
CanTransfer: CanTransfer,
101-
Transfer: Transfer,
102-
GetHash: GetHashFn(header, chain),
103-
PredicateResults: predicateResults,
104-
Coinbase: beneficiary,
105-
BlockNumber: new(big.Int).Set(header.Number),
106-
Time: header.Time,
107-
Difficulty: new(big.Int).Set(header.Difficulty),
108-
BaseFee: baseFee,
109-
BlobBaseFee: blobBaseFee,
110-
GasLimit: header.GasLimit,
72+
CanTransfer: CanTransfer,
73+
Transfer: Transfer,
74+
GetHash: GetHashFn(header, chain),
75+
Extra: header.Extra,
76+
Coinbase: beneficiary,
77+
BlockNumber: new(big.Int).Set(header.Number),
78+
Time: header.Time,
79+
Difficulty: new(big.Int).Set(header.Difficulty),
80+
BaseFee: baseFee,
81+
BlobBaseFee: blobBaseFee,
82+
GasLimit: header.GasLimit,
11183
}
11284
}
11385

86+
// NewEVMBlockContextWithPredicateResults creates a new context for use in the EVM with an override for the predicate results that is not present
87+
// in header.Extra.
88+
// 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
89+
// directly rather than re-encode the latest results when executing each individaul transaction.
90+
func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext {
91+
blockContext := NewEVMBlockContext(header, chain, author)
92+
blockContext.PredicateResults = predicateResults
93+
return blockContext
94+
}
95+
11496
// NewEVMTxContext creates a new transaction context for a single transaction.
11597
func NewEVMTxContext(msg *Message) vm.TxContext {
11698
ctx := vm.TxContext{

core/vm/evm.go

+35-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/ava-labs/subnet-evm/constants"
3636
"github.com/ava-labs/subnet-evm/core/types"
3737
"github.com/ava-labs/subnet-evm/params"
38+
"github.com/ava-labs/subnet-evm/plugin/evm/header"
3839
"github.com/ava-labs/subnet-evm/precompile/contract"
3940
"github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist"
4041
"github.com/ava-labs/subnet-evm/precompile/modules"
@@ -43,6 +44,7 @@ import (
4344
"github.com/ava-labs/subnet-evm/vmerrs"
4445
"github.com/ethereum/go-ethereum/common"
4546
"github.com/ethereum/go-ethereum/crypto"
47+
"github.com/ethereum/go-ethereum/log"
4648
"github.com/holiman/uint256"
4749
)
4850

@@ -111,9 +113,14 @@ type BlockContext struct {
111113
Transfer TransferFunc
112114
// GetHash returns the hash corresponding to n
113115
GetHash GetHashFunc
114-
// PredicateResults are the results of predicate verification available throughout the EVM's execution.
115-
// PredicateResults may be nil if it is not encoded in the block's header.
116+
// PredicateResults are the results of predicate verification available
117+
// throughout the EVM's execution.
118+
//
119+
// PredicateResults may be nil if it is not encoded in the extra field of
120+
// the block's header or if the extra field has not been parsed yet.
116121
PredicateResults *predicate.Results
122+
// Extra is the extra field from the block header.
123+
Extra []byte
117124

118125
// Block information
119126
Coinbase common.Address // Provides information for COINBASE
@@ -209,6 +216,32 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
209216
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Time),
210217
}
211218
evm.interpreter = NewEVMInterpreter(evm)
219+
220+
// If the predicate results were set by the miner, use them.
221+
if blockCtx.PredicateResults != nil {
222+
return evm
223+
}
224+
225+
// Parse the predicate results from the extra field and store them in the
226+
// block context.
227+
predicateBytes := header.PredicateBytesFromExtra(blockCtx.Extra)
228+
if len(predicateBytes) == 0 {
229+
return evm
230+
}
231+
232+
// The VM has already verified the correctness of the results during header
233+
// validation.
234+
results, err := predicate.ParseResults(predicateBytes)
235+
if err != nil {
236+
log.Error("Unexpected error parsing predicate results",
237+
"err", err,
238+
)
239+
return evm
240+
}
241+
242+
// Because the BlockContext is pass-by-value, this does not cache the
243+
// results for future calls to NewEVM.
244+
evm.Context.PredicateResults = results
212245
return evm
213246
}
214247

plugin/evm/block.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/ava-labs/subnet-evm/core/rawdb"
1818
"github.com/ava-labs/subnet-evm/core/types"
1919
"github.com/ava-labs/subnet-evm/params"
20+
"github.com/ava-labs/subnet-evm/plugin/evm/header"
2021
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
2122
"github.com/ava-labs/subnet-evm/predicate"
2223

@@ -242,10 +243,7 @@ func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateCon
242243
return fmt.Errorf("failed to marshal predicate results: %w", err)
243244
}
244245
extraData := b.ethBlock.Extra()
245-
headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(extraData)
246-
if !ok {
247-
return fmt.Errorf("failed to find predicate results in extra data: %x", extraData)
248-
}
246+
headerPredicateResultsBytes := header.PredicateBytesFromExtra(extraData)
249247
if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) {
250248
return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes)
251249
}

plugin/evm/header/extra.go

+13
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,16 @@ func VerifyExtra(rules params.AvalancheRules, extra []byte) error {
6868
}
6969
return nil
7070
}
71+
72+
// PredicateBytesFromExtra returns the predicate result bytes from the header's
73+
// extra data. If the extra data is not long enough, an empty slice is returned.
74+
func PredicateBytesFromExtra(extra []byte) []byte {
75+
// Prior to Durango, the VM enforces the extra data is smaller than or equal
76+
// to this size.
77+
// After Durango, the VM pre-verifies the extra data past the dynamic fee
78+
// rollup window is valid.
79+
if len(extra) <= FeeWindowSize {
80+
return nil
81+
}
82+
return extra[FeeWindowSize:]
83+
}

plugin/evm/header/extra_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,40 @@ func TestVerifyExtra(t *testing.T) {
183183
})
184184
}
185185
}
186+
187+
func TestPredicateBytesFromExtra(t *testing.T) {
188+
tests := []struct {
189+
name string
190+
extra []byte
191+
expected []byte
192+
}{
193+
{
194+
name: "empty_extra",
195+
extra: nil,
196+
expected: nil,
197+
},
198+
{
199+
name: "too_short",
200+
extra: make([]byte, FeeWindowSize-1),
201+
expected: nil,
202+
},
203+
{
204+
name: "empty_predicate",
205+
extra: make([]byte, FeeWindowSize),
206+
expected: nil,
207+
},
208+
{
209+
name: "non_empty_predicate",
210+
extra: []byte{
211+
FeeWindowSize: 5,
212+
},
213+
expected: []byte{5},
214+
},
215+
}
216+
for _, test := range tests {
217+
t.Run(test.name, func(t *testing.T) {
218+
got := PredicateBytesFromExtra(test.extra)
219+
require.Equal(t, test.expected, got)
220+
})
221+
}
222+
}

plugin/evm/vm_warp_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/ava-labs/subnet-evm/core/types"
3333
"github.com/ava-labs/subnet-evm/eth/tracers"
3434
"github.com/ava-labs/subnet-evm/params"
35+
customheader "github.com/ava-labs/subnet-evm/plugin/evm/header"
3536
"github.com/ava-labs/subnet-evm/plugin/evm/message"
3637
"github.com/ava-labs/subnet-evm/precompile/contract"
3738
warpcontract "github.com/ava-labs/subnet-evm/precompile/contracts/warp"
@@ -668,8 +669,7 @@ func testReceiveWarpMessage(
668669

669670
// Require the block was built with a successful predicate result
670671
ethBlock := block2.(*chain.BlockWrapper).Block.(*Block).ethBlock
671-
headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(ethBlock.Extra())
672-
require.True(ok)
672+
headerPredicateResultsBytes := customheader.PredicateBytesFromExtra(ethBlock.Extra())
673673
results, err := predicate.ParseResults(headerPredicateResultsBytes)
674674
require.NoError(err)
675675

predicate/predicate_bytes.go

-13
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package predicate
66
import (
77
"fmt"
88

9-
"github.com/ava-labs/subnet-evm/plugin/evm/header"
109
"github.com/ethereum/go-ethereum/common"
1110
)
1211

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

5150
return trimmedPredicateBytes[:len(trimmedPredicateBytes)-1], nil
5251
}
53-
54-
// GetPredicateResultBytes returns the predicate result bytes from the extra data and
55-
// true iff the predicate results bytes have non-zero length.
56-
func GetPredicateResultBytes(extraData []byte) ([]byte, bool) {
57-
// Prior to Durango, the VM enforces the extra data is smaller than or equal to this size.
58-
// After Durango, the VM pre-verifies the extra data past the dynamic fee rollup window is
59-
// valid.
60-
if len(extraData) <= header.FeeWindowSize {
61-
return nil, false
62-
}
63-
return extraData[header.FeeWindowSize:], true
64-
}

predicate/predicate_bytes_test.go

-16
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"testing"
99

1010
"github.com/ava-labs/avalanchego/utils"
11-
"github.com/ava-labs/subnet-evm/plugin/evm/header"
1211
"github.com/stretchr/testify/require"
1312
)
1413

@@ -49,18 +48,3 @@ func TestUnpackInvalidPredicate(t *testing.T) {
4948
}
5049
}
5150
}
52-
53-
func TestPredicateResultsBytes(t *testing.T) {
54-
require := require.New(t)
55-
dataTooShort := utils.RandomBytes(header.FeeWindowSize - 1)
56-
_, ok := GetPredicateResultBytes(dataTooShort)
57-
require.False(ok)
58-
59-
preDurangoData := utils.RandomBytes(header.FeeWindowSize)
60-
_, ok = GetPredicateResultBytes(preDurangoData)
61-
require.False(ok)
62-
postDurangoData := utils.RandomBytes(header.FeeWindowSize + 2)
63-
resultBytes, ok := GetPredicateResultBytes(postDurangoData)
64-
require.True(ok)
65-
require.Equal(resultBytes, postDurangoData[header.FeeWindowSize:])
66-
}

0 commit comments

Comments
 (0)