diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index d2d957875d..e4656a0937 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -34,13 +34,13 @@ import ( "os" "path" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/eth/tracers" "github.com/ava-labs/subnet-evm/eth/tracers/logger" "github.com/ava-labs/subnet-evm/params" + customheader "github.com/ava-labs/subnet-evm/plugin/evm/header" "github.com/ava-labs/subnet-evm/tests" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -90,7 +90,7 @@ type input struct { } func Transition(ctx *cli.Context) error { - var getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil } + getTracer := func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil } baseDir, err := createBasedir(ctx) if err != nil { @@ -222,7 +222,7 @@ func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { BaseFee: env.ParentBaseFee, GasUsed: env.ParentGasUsed, GasLimit: env.ParentGasLimit, - Extra: make([]byte, params.DynamicFeeExtraDataSize), // TODO: consider passing extra through env + Extra: make([]byte, customheader.FeeWindowSize), // TODO: consider passing extra through env } feeConfig := params.DefaultFeeConfig if env.MinBaseFee != nil { @@ -230,7 +230,7 @@ func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { feeConfig.MinBaseFee = env.MinBaseFee } var err error - _, env.BaseFee, err = dummy.CalcBaseFee(chainConfig, feeConfig, parent, env.Timestamp) + env.BaseFee, err = customheader.BaseFee(chainConfig, feeConfig, parent, env.Timestamp) if err != nil { return NewError(ErrorConfig, fmt.Errorf("failed calculating base fee: %v", err)) } @@ -282,7 +282,7 @@ func saveFile(baseDir, filename string, data interface{}) error { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) } location := path.Join(baseDir, filename) - if err = os.WriteFile(location, b, 0644); err != nil { + if err = os.WriteFile(location, b, 0o644); err != nil { return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) } log.Info("Wrote file", "file", location) diff --git a/commontype/fee_config.go b/commontype/fee_config.go index 3089df5d9c..2ca8c53570 100644 --- a/commontype/fee_config.go +++ b/commontype/fee_config.go @@ -95,6 +95,8 @@ func (f *FeeConfig) Verify() error { return fmt.Errorf("minBlockGasCost = %d cannot be greater than maxBlockGasCost = %d", f.MinBlockGasCost, f.MaxBlockGasCost) case f.BlockGasCostStep.Cmp(common.Big0) == -1: return fmt.Errorf("blockGasCostStep = %d cannot be less than 0", f.BlockGasCostStep) + case !f.MaxBlockGasCost.IsUint64(): + return fmt.Errorf("maxBlockGasCost = %d is not a valid uint64", f.MaxBlockGasCost) } return f.checkByteLens() } diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 3cf65644b4..5a4c0dc272 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/utils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -26,11 +27,8 @@ import ( var ( allowedFutureBlockTime = 10 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks - errInvalidBlockTime = errors.New("timestamp less than parent's") - errUnclesUnsupported = errors.New("uncles unsupported") - errBlockGasCostNil = errors.New("block gas cost is nil") - errBlockGasCostTooLarge = errors.New("block gas cost is not uint64") - errBaseFeeNil = errors.New("base fee is nil") + errInvalidBlockTime = errors.New("timestamp less than parent's") + errUnclesUnsupported = errors.New("uncles unsupported") ) type Mode struct { @@ -117,14 +115,6 @@ func (eng *DummyEngine) verifyCoinbase(config *params.ChainConfig, header *types } func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header *types.Header, parent *types.Header, chain consensus.ChainHeaderReader) error { - // Verify that the gas limit is <= 2^63-1 - if header.GasLimit > params.MaxGasLimit { - return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) - } - // Verify that the gasUsed is <= gasLimit - if header.GasUsed > header.GasLimit { - return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) - } // We verify the current block by checking the parent fee config // this is because the current block cannot set the fee config for itself // Fee config might depend on the state when precompile is activated @@ -134,67 +124,45 @@ func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header if err != nil { return err } - if config.IsSubnetEVM(header.Time) { - expectedGasLimit := feeConfig.GasLimit.Uint64() - if header.GasLimit != expectedGasLimit { - return fmt.Errorf("expected gas limit to be %d, but found %d", expectedGasLimit, header.GasLimit) - } - } else { - // Verify that the gas limit remains within allowed bounds - diff := int64(parent.GasLimit) - int64(header.GasLimit) - if diff < 0 { - diff *= -1 - } - limit := parent.GasLimit / params.GasLimitBoundDivisor - - if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { - return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) - } - // Verify BaseFee is not present before Subnet EVM - if header.BaseFee != nil { - return fmt.Errorf("invalid baseFee before fork: have %d, want ", header.BaseFee) - } - if header.BlockGasCost != nil { - return fmt.Errorf("invalid blockGasCost before fork: have %d, want ", header.BlockGasCost) - } - return nil + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } - - // Verify baseFee and rollupWindow encoding as part of header verification - // starting in Subnet EVM - expectedRollupWindowBytes, expectedBaseFee, err := CalcBaseFee(config, feeConfig, parent, header.Time) - if err != nil { - return fmt.Errorf("failed to calculate base fee: %w", err) + if err := customheader.VerifyGasLimit(config, feeConfig, parent, header); err != nil { + return err } - if len(header.Extra) < len(expectedRollupWindowBytes) || !bytes.Equal(expectedRollupWindowBytes, header.Extra[:len(expectedRollupWindowBytes)]) { - return fmt.Errorf("expected rollup window bytes: %x, found %x", expectedRollupWindowBytes, header.Extra) + // Verify header.Extra matches the expected value. + expectedExtraPrefix, err := customheader.ExtraPrefix(config, parent, header.Time) + if err != nil { + return fmt.Errorf("failed to calculate extra prefix: %w", err) } - if header.BaseFee == nil { - return errors.New("expected baseFee to be non-nil") + if !bytes.HasPrefix(header.Extra, expectedExtraPrefix) { + return fmt.Errorf("expected header.Extra to have prefix: %x, found %x", expectedExtraPrefix, header.Extra) } - if bfLen := header.BaseFee.BitLen(); bfLen > 256 { - return fmt.Errorf("too large base fee: bitlen %d", bfLen) + + // Verify header.BaseFee matches the expected value. + expectedBaseFee, err := customheader.BaseFee(config, feeConfig, parent, header.Time) + if err != nil { + return fmt.Errorf("failed to calculate base fee: %w", err) } - if header.BaseFee.Cmp(expectedBaseFee) != 0 { + if !utils.BigEqual(header.BaseFee, expectedBaseFee) { return fmt.Errorf("expected base fee (%d), found (%d)", expectedBaseFee, header.BaseFee) } + if !config.IsSubnetEVM(header.Time) { + if header.BlockGasCost != nil { + return fmt.Errorf("invalid blockGasCost before fork: have %d, want ", header.BlockGasCost) + } + return nil + } + // Enforce BlockGasCost constraints - expectedBlockGasCost := calcBlockGasCost( - feeConfig.TargetBlockRate, - feeConfig.MinBlockGasCost, - feeConfig.MaxBlockGasCost, - feeConfig.BlockGasCostStep, - parent.BlockGasCost, - parent.Time, header.Time, + expectedBlockGasCost := customheader.BlockGasCost( + feeConfig, + parent, + header.Time, ) - if header.BlockGasCost == nil { - return errBlockGasCostNil - } - if !header.BlockGasCost.IsUint64() { - return errBlockGasCostTooLarge - } - if header.BlockGasCost.Cmp(expectedBlockGasCost) != 0 { + if !utils.BigEqualUint64(header.BlockGasCost, expectedBlockGasCost) { return fmt.Errorf("invalid block gas cost: have %d, want %d", header.BlockGasCost, expectedBlockGasCost) } return nil @@ -358,7 +326,9 @@ func (eng *DummyEngine) verifyBlockFee( } func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types.Block, parent *types.Header, state *state.StateDB, receipts []*types.Receipt) error { - if chain.Config().IsSubnetEVM(block.Time()) { + config := chain.Config() + timestamp := block.Time() + if config.IsSubnetEVM(block.Time()) { // we use the parent to determine the fee config // since the current block has not been finalized yet. feeConfig, _, err := chain.GetFeeConfigAt(parent) @@ -366,24 +336,21 @@ func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types return err } - // Calculate the expected blockGasCost for this block. - // Note: this is a deterministic transtion that defines an exact block fee for this block. - blockGasCost := calcBlockGasCost( - feeConfig.TargetBlockRate, - feeConfig.MinBlockGasCost, - feeConfig.MaxBlockGasCost, - feeConfig.BlockGasCostStep, - parent.BlockGasCost, - parent.Time, block.Time(), + // Verify the BlockGasCost set in the header matches the expected value. + blockGasCost := block.BlockGasCost() + expectedBlockGasCost := customheader.BlockGasCost( + feeConfig, + parent, + timestamp, ) - // Verify the BlockGasCost set in the header matches the calculated value. - if blockBlockGasCost := block.BlockGasCost(); blockBlockGasCost == nil || !blockBlockGasCost.IsUint64() || blockBlockGasCost.Cmp(blockGasCost) != 0 { - return fmt.Errorf("invalid blockGasCost: have %d, want %d", blockBlockGasCost, blockGasCost) + if !utils.BigEqualUint64(blockGasCost, expectedBlockGasCost) { + return fmt.Errorf("invalid blockGasCost: have %d, want %d", blockGasCost, expectedBlockGasCost) } + // Verify the block fee was paid. if err := eng.verifyBlockFee( block.BaseFee(), - block.BlockGasCost(), + blockGasCost, block.Transactions(), receipts, ); err != nil { @@ -397,7 +364,8 @@ func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, ) (*types.Block, error) { - if chain.Config().IsSubnetEVM(header.Time) { + config := chain.Config() + if config.IsSubnetEVM(header.Time) { // we use the parent to determine the fee config // since the current block has not been finalized yet. feeConfig, _, err := chain.GetFeeConfigAt(parent) @@ -405,14 +373,13 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h return nil, err } // Calculate the required block gas cost for this block. - header.BlockGasCost = calcBlockGasCost( - feeConfig.TargetBlockRate, - feeConfig.MinBlockGasCost, - feeConfig.MaxBlockGasCost, - feeConfig.BlockGasCostStep, - parent.BlockGasCost, - parent.Time, header.Time, + blockGasCost := customheader.BlockGasCost( + feeConfig, + parent, + header.Time, ) + header.BlockGasCost = new(big.Int).SetUint64(blockGasCost) + // Verify that this block covers the block fee. if err := eng.verifyBlockFee( header.BaseFee, @@ -423,8 +390,16 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h return nil, err } } + + // finalize the header.Extra + extraPrefix, err := customheader.ExtraPrefix(config, parent, header.Time) + if err != nil { + return nil, fmt.Errorf("failed to calculate new header.Extra: %w", err) + } + header.Extra = append(extraPrefix, header.Extra...) + // commit the final state root - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.Root = state.IntermediateRoot(config.IsEIP158(header.Number)) // Header seems complete, assemble into a block and return return types.NewBlock( diff --git a/consensus/dummy/consensus_test.go b/consensus/dummy/consensus_test.go index 12fc21a499..a961083174 100644 --- a/consensus/dummy/consensus_test.go +++ b/consensus/dummy/consensus_test.go @@ -8,27 +8,34 @@ import ( "math/big" "testing" + "github.com/ava-labs/subnet-evm/commontype" "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/ethereum/go-ethereum/common" ) -var testBlockGasCostStep = big.NewInt(50_000) +var testFeeConfig = commontype.FeeConfig{ + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + TargetBlockRate: 2, + BlockGasCostStep: big.NewInt(50_000), + TargetGas: big.NewInt(10_000_000), + BaseFeeChangeDenominator: big.NewInt(12), +} func TestVerifyBlockFee(t *testing.T) { tests := map[string]struct { - baseFee *big.Int - parentBlockGasCost *big.Int - parentTime, currentTime uint64 - txs []*types.Transaction - receipts []*types.Receipt - shouldErr bool + baseFee *big.Int + parentBlockGasCost *big.Int + timeElapsed uint64 + txs []*types.Transaction + receipts []*types.Receipt + shouldErr bool }{ "tx only base fee": { baseFee: big.NewInt(100), parentBlockGasCost: big.NewInt(0), - parentTime: 10, - currentTime: 10, + timeElapsed: 0, txs: []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100, big.NewInt(100), nil), }, @@ -40,8 +47,7 @@ func TestVerifyBlockFee(t *testing.T) { "tx covers exactly block fee": { baseFee: big.NewInt(100), parentBlockGasCost: big.NewInt(0), - parentTime: 10, - currentTime: 10, + timeElapsed: 0, txs: []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(200), nil), }, @@ -53,8 +59,7 @@ func TestVerifyBlockFee(t *testing.T) { "txs share block fee": { baseFee: big.NewInt(100), parentBlockGasCost: big.NewInt(0), - parentTime: 10, - currentTime: 10, + timeElapsed: 0, txs: []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(200), nil), types.NewTransaction(1, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(100), nil), @@ -68,8 +73,7 @@ func TestVerifyBlockFee(t *testing.T) { "txs split block fee": { baseFee: big.NewInt(100), parentBlockGasCost: big.NewInt(0), - parentTime: 10, - currentTime: 10, + timeElapsed: 0, txs: []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(150), nil), types.NewTransaction(1, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(150), nil), @@ -83,8 +87,7 @@ func TestVerifyBlockFee(t *testing.T) { "tx only base fee after full time window": { baseFee: big.NewInt(100), parentBlockGasCost: big.NewInt(500_000), - parentTime: 10, - currentTime: 22, // 2s target + 10 + timeElapsed: testFeeConfig.TargetBlockRate + 10, txs: []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100, big.NewInt(100), nil), }, @@ -96,8 +99,7 @@ func TestVerifyBlockFee(t *testing.T) { "tx only base fee after large time window": { baseFee: big.NewInt(100), parentBlockGasCost: big.NewInt(100_000), - parentTime: 0, - currentTime: math.MaxUint64, + timeElapsed: math.MaxUint64, txs: []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100, big.NewInt(100), nil), }, @@ -106,29 +108,20 @@ func TestVerifyBlockFee(t *testing.T) { }, shouldErr: false, }, - "parent time > current time": { - baseFee: big.NewInt(100), - parentBlockGasCost: big.NewInt(0), - parentTime: 11, - currentTime: 10, - txs: nil, - receipts: nil, - shouldErr: true, - }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - blockGasCost := calcBlockGasCost( - params.DefaultFeeConfig.TargetBlockRate, - params.DefaultFeeConfig.MinBlockGasCost, - params.DefaultFeeConfig.MaxBlockGasCost, - testBlockGasCostStep, + blockGasCost := header.BlockGasCostWithStep( + testFeeConfig, test.parentBlockGasCost, - test.parentTime, test.currentTime, + testFeeConfig.BlockGasCostStep.Uint64(), + test.timeElapsed, ) + bigBlockGasCost := new(big.Int).SetUint64(blockGasCost) + engine := NewFaker() - if err := engine.verifyBlockFee(test.baseFee, blockGasCost, test.txs, test.receipts); err != nil { + if err := engine.verifyBlockFee(test.baseFee, bigBlockGasCost, test.txs, test.receipts); err != nil { if !test.shouldErr { t.Fatalf("Unexpected error: %s", err) } diff --git a/consensus/dummy/dynamic_fee_window.go b/consensus/dummy/dynamic_fee_window.go deleted file mode 100644 index 8e9cb1c4de..0000000000 --- a/consensus/dummy/dynamic_fee_window.go +++ /dev/null @@ -1,88 +0,0 @@ -// (c) 2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package dummy - -import ( - "encoding/binary" - "errors" - "fmt" - - "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common/math" -) - -var ErrDynamicFeeWindowInsufficientLength = errors.New("insufficient length for dynamic fee window") - -// DynamicFeeWindow is a window of the last [params.RollupWindow] seconds of gas -// usage. -// -// Index 0 is the oldest entry, and [params.RollupWindow]-1 is the current -// entry. -type DynamicFeeWindow [params.RollupWindow]uint64 - -func ParseDynamicFeeWindow(bytes []byte) (DynamicFeeWindow, error) { - if len(bytes) < params.DynamicFeeExtraDataSize { - return DynamicFeeWindow{}, fmt.Errorf("%w: expected at least %d bytes but got %d bytes", - ErrDynamicFeeWindowInsufficientLength, - params.DynamicFeeExtraDataSize, - len(bytes), - ) - } - - var window DynamicFeeWindow - for i := range window { - offset := i * wrappers.LongLen - window[i] = binary.BigEndian.Uint64(bytes[offset:]) - } - return window, nil -} - -// Add adds the amounts to the most recent entry in the window. -// -// If the most recent entry overflows, it is set to [math.MaxUint64]. -func (w *DynamicFeeWindow) Add(amounts ...uint64) { - const lastIndex uint = params.RollupWindow - 1 - w[lastIndex] = add(w[lastIndex], amounts...) -} - -// Shift removes the oldest n entries from the window and adds n new empty -// entries. -func (w *DynamicFeeWindow) Shift(n uint64) { - if n >= params.RollupWindow { - *w = DynamicFeeWindow{} - return - } - - var newWindow DynamicFeeWindow - copy(newWindow[:], w[n:]) - *w = newWindow -} - -// Sum returns the sum of all the entries in the window. -// -// If the sum overflows, [math.MaxUint64] is returned. -func (w *DynamicFeeWindow) Sum() uint64 { - return add(0, w[:]...) -} - -func (w *DynamicFeeWindow) Bytes() []byte { - bytes := make([]byte, params.DynamicFeeExtraDataSize) - for i, v := range w { - offset := i * wrappers.LongLen - binary.BigEndian.PutUint64(bytes[offset:], v) - } - return bytes -} - -func add(sum uint64, values ...uint64) uint64 { - var overflow bool - for _, v := range values { - sum, overflow = math.SafeAdd(sum, v) - if overflow { - return math.MaxUint64 - } - } - return sum -} diff --git a/consensus/dummy/dynamic_fee_window_test.go b/consensus/dummy/dynamic_fee_window_test.go deleted file mode 100644 index f1ac6e1aa0..0000000000 --- a/consensus/dummy/dynamic_fee_window_test.go +++ /dev/null @@ -1,246 +0,0 @@ -// (c) 2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package dummy - -import ( - "strconv" - "testing" - - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/require" -) - -func TestDynamicFeeWindow_Add(t *testing.T) { - tests := []struct { - name string - window DynamicFeeWindow - amount uint64 - expected DynamicFeeWindow - }{ - { - name: "normal_addition", - window: DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - }, - amount: 5, - expected: DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, - }, - }, - { - name: "amount_overflow", - window: DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - }, - amount: math.MaxUint64, - expected: DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, math.MaxUint64, - }, - }, - { - name: "window_overflow", - window: DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, math.MaxUint64, - }, - amount: 5, - expected: DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, math.MaxUint64, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - test.window.Add(test.amount) - require.Equal(t, test.expected, test.window) - }) - } -} - -func TestDynamicFeeWindow_Shift(t *testing.T) { - window := DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - } - tests := []struct { - n uint64 - expected DynamicFeeWindow - }{ - { - n: 0, - expected: window, - }, - { - n: 1, - expected: DynamicFeeWindow{ - 2, 3, 4, 5, 6, 7, 8, 9, 10, - }, - }, - { - n: 2, - expected: DynamicFeeWindow{ - 3, 4, 5, 6, 7, 8, 9, 10, - }, - }, - { - n: 3, - expected: DynamicFeeWindow{ - 4, 5, 6, 7, 8, 9, 10, - }, - }, - { - n: 4, - expected: DynamicFeeWindow{ - 5, 6, 7, 8, 9, 10, - }, - }, - { - n: 5, - expected: DynamicFeeWindow{ - 6, 7, 8, 9, 10, - }, - }, - { - n: 6, - expected: DynamicFeeWindow{ - 7, 8, 9, 10, - }, - }, - { - n: 7, - expected: DynamicFeeWindow{ - 8, 9, 10, - }, - }, - { - n: 8, - expected: DynamicFeeWindow{ - 9, 10, - }, - }, - { - n: 9, - expected: DynamicFeeWindow{ - 10, - }, - }, - { - n: 10, - expected: DynamicFeeWindow{}, - }, - { - n: 100, - expected: DynamicFeeWindow{}, - }, - } - for _, test := range tests { - t.Run(strconv.FormatUint(test.n, 10), func(t *testing.T) { - window := window - window.Shift(test.n) - require.Equal(t, test.expected, window) - }) - } -} - -func TestDynamicFeeWindow_Sum(t *testing.T) { - tests := []struct { - name string - window DynamicFeeWindow - expected uint64 - }{ - { - name: "empty", - window: DynamicFeeWindow{}, - expected: 0, - }, - { - name: "full", - window: DynamicFeeWindow{ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - }, - expected: 55, - }, - { - name: "overflow", - window: DynamicFeeWindow{ - math.MaxUint64, 2, 3, 4, 5, 6, 7, 8, 9, 10, - }, - expected: math.MaxUint64, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require.Equal(t, test.expected, test.window.Sum()) - }) - } -} - -func TestDynamicFeeWindow_Bytes(t *testing.T) { - tests := []struct { - name string - bytes []byte - window DynamicFeeWindow - parseErr error - }{ - { - name: "insufficient_length", - bytes: make([]byte, params.DynamicFeeExtraDataSize-1), - parseErr: ErrDynamicFeeWindowInsufficientLength, - }, - { - name: "zero_window", - bytes: make([]byte, params.DynamicFeeExtraDataSize), - window: DynamicFeeWindow{}, - }, - { - name: "truncate_bytes", - bytes: []byte{ - params.DynamicFeeExtraDataSize: 1, - }, - window: DynamicFeeWindow{}, - }, - { - name: "endianess", - bytes: []byte{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, - 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - }, - window: DynamicFeeWindow{ - 0x0102030405060708, - 0x1112131415161718, - 0x2122232425262728, - 0x3132333435363738, - 0x4142434445464748, - 0x5152535455565758, - 0x6162636465666768, - 0x7172737475767778, - 0x8182838485868788, - 0x9192939495969798, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - window, err := ParseDynamicFeeWindow(test.bytes) - require.Equal(test.window, window) - require.ErrorIs(err, test.parseErr) - if test.parseErr != nil { - return - } - - expectedBytes := test.bytes[:params.DynamicFeeExtraDataSize] - bytes := window.Bytes() - require.Equal(expectedBytes, bytes) - }) - } -} diff --git a/consensus/dummy/dynamic_fees.go b/consensus/dummy/dynamic_fees.go deleted file mode 100644 index 9898e8f27b..0000000000 --- a/consensus/dummy/dynamic_fees.go +++ /dev/null @@ -1,185 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package dummy - -import ( - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" -) - -// CalcBaseFee takes the previous header and the timestamp of its child block -// and calculates the expected base fee as well as the encoding of the past -// pricing information for the child block. -func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) { - // If the current block is the first EIP-1559 block, or it is the genesis block - // return the initial slice and initial base fee. - isSubnetEVM := config.IsSubnetEVM(parent.Time) - if !isSubnetEVM || parent.Number.Cmp(common.Big0) == 0 { - initialSlice := (&DynamicFeeWindow{}).Bytes() - return initialSlice, feeConfig.MinBaseFee, nil - } - - dynamicFeeWindow, err := ParseDynamicFeeWindow(parent.Extra) - if err != nil { - return nil, nil, err - } - - // start off with parent's base fee - baseFee := new(big.Int).Set(parent.BaseFee) - baseFeeChangeDenominator := feeConfig.BaseFeeChangeDenominator - - parentGasTargetBig := feeConfig.TargetGas - parentGasTarget := parentGasTargetBig.Uint64() - - // Compute the new state of the gas rolling window. - dynamicFeeWindow.Add(parent.GasUsed) - - if timestamp < parent.Time { - return nil, nil, fmt.Errorf("cannot calculate base fee for timestamp %d prior to parent timestamp %d", timestamp, parent.Time) - } - roll := timestamp - parent.Time - - // roll the window over by the difference between the timestamps to generate - // the new rollup window. - dynamicFeeWindow.Shift(roll) - dynamicFeeWindowBytes := dynamicFeeWindow.Bytes() - - // Calculate the amount of gas consumed within the rollup window. - totalGas := dynamicFeeWindow.Sum() - if totalGas == parentGasTarget { - return dynamicFeeWindowBytes, baseFee, nil - } - - num := new(big.Int) - - if totalGas > parentGasTarget { - // If the parent block used more gas than its target, the baseFee should increase. - num.SetUint64(totalGas - parentGasTarget) - num.Mul(num, parent.BaseFee) - num.Div(num, parentGasTargetBig) - num.Div(num, baseFeeChangeDenominator) - baseFeeDelta := math.BigMax(num, common.Big1) - - baseFee.Add(baseFee, baseFeeDelta) - } else { - // Otherwise if the parent block used less gas than its target, the baseFee should decrease. - num.SetUint64(parentGasTarget - totalGas) - num.Mul(num, parent.BaseFee) - num.Div(num, parentGasTargetBig) - num.Div(num, baseFeeChangeDenominator) - baseFeeDelta := math.BigMax(num, common.Big1) - - // If [roll] is greater than [rollupWindow], apply the state transition to the base fee to account - // for the interval during which no blocks were produced. - // We use roll/rollupWindow, so that the transition is applied for every [rollupWindow] seconds - // that has elapsed between the parent and this block. - if roll > params.RollupWindow { - // Note: roll/params.RollupWindow must be greater than 1 since we've checked that roll > params.RollupWindow - baseFeeDelta = new(big.Int).Mul(baseFeeDelta, new(big.Int).SetUint64(roll/params.RollupWindow)) - } - baseFee.Sub(baseFee, baseFeeDelta) - } - - baseFee = selectBigWithinBounds(feeConfig.MinBaseFee, baseFee, nil) - - return dynamicFeeWindowBytes, baseFee, nil -} - -// EstimateNextBaseFee attempts to estimate the next base fee based on a block with [parent] being built at -// [timestamp]. -// If [timestamp] is less than the timestamp of [parent], then it uses the same timestamp as parent. -// Warning: This function should only be used in estimation and should not be used when calculating the canonical -// base fee for a subsequent block. -func EstimateNextBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) (*big.Int, error) { - if timestamp < parent.Time { - timestamp = parent.Time - } - _, baseFee, err := CalcBaseFee(config, feeConfig, parent, timestamp) - return baseFee, err -} - -// selectBigWithinBounds returns [value] if it is within the bounds: -// lowerBound <= value <= upperBound or the bound at either end if [value] -// is outside of the defined boundaries. -func selectBigWithinBounds(lowerBound, value, upperBound *big.Int) *big.Int { - switch { - case lowerBound != nil && value.Cmp(lowerBound) < 0: - return new(big.Int).Set(lowerBound) - case upperBound != nil && value.Cmp(upperBound) > 0: - return new(big.Int).Set(upperBound) - default: - return value - } -} - -// calcBlockGasCost calculates the required block gas cost. If [parentTime] -// > [currentTime], the timeElapsed will be treated as 0. -func calcBlockGasCost( - targetBlockRate uint64, - minBlockGasCost *big.Int, - maxBlockGasCost *big.Int, - blockGasCostStep *big.Int, - parentBlockGasCost *big.Int, - parentTime, currentTime uint64, -) *big.Int { - // Handle Subnet EVM boundary by returning the minimum value as the boundary. - if parentBlockGasCost == nil { - return new(big.Int).Set(minBlockGasCost) - } - - // Treat an invalid parent/current time combination as 0 elapsed time. - var timeElapsed uint64 - if parentTime <= currentTime { - timeElapsed = currentTime - parentTime - } - - var blockGasCost *big.Int - if timeElapsed < targetBlockRate { - blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(targetBlockRate-timeElapsed)) - blockGasCost = new(big.Int).Add(parentBlockGasCost, blockGasCostDelta) - } else { - blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(timeElapsed-targetBlockRate)) - blockGasCost = new(big.Int).Sub(parentBlockGasCost, blockGasCostDelta) - } - - blockGasCost = selectBigWithinBounds(minBlockGasCost, blockGasCost, maxBlockGasCost) - if !blockGasCost.IsUint64() { - blockGasCost = new(big.Int).SetUint64(math.MaxUint64) - } - return blockGasCost -} - -// MinRequiredTip is the estimated minimum tip a transaction would have -// needed to pay to be included in a given block (assuming it paid a tip -// proportional to its gas usage). In reality, there is no minimum tip that -// is enforced by the consensus engine and high tip paying transactions can -// subsidize the inclusion of low tip paying transactions. The only -// correctness check performed is that the sum of all tips is >= the -// required block fee. -// -// This function will return nil for all return values prior to Subnet EVM. -func MinRequiredTip(config *params.ChainConfig, header *types.Header) (*big.Int, error) { - if !config.IsSubnetEVM(header.Time) { - return nil, nil - } - if header.BaseFee == nil { - return nil, errBaseFeeNil - } - if header.BlockGasCost == nil { - return nil, errBlockGasCostNil - } - - // minTip = requiredBlockFee/blockGasUsage - requiredBlockFee := new(big.Int).Mul( - header.BlockGasCost, - header.BaseFee, - ) - return new(big.Int).Div(requiredBlockFee, new(big.Int).SetUint64(header.GasUsed)), nil -} diff --git a/consensus/dummy/dynamic_fees_test.go b/consensus/dummy/dynamic_fees_test.go deleted file mode 100644 index b2db96456e..0000000000 --- a/consensus/dummy/dynamic_fees_test.go +++ /dev/null @@ -1,348 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package dummy - -import ( - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var testMinBaseFee = big.NewInt(75_000_000_000) - -type blockDefinition struct { - timestamp uint64 - gasUsed uint64 -} - -type test struct { - baseFee *big.Int - genBlocks func() []blockDefinition - minFee *big.Int -} - -func TestDynamicFees(t *testing.T) { - spacedTimestamps := []uint64{1, 1, 2, 5, 15, 120} - - var tests []test = []test{ - // Test minimal gas usage - { - baseFee: nil, - minFee: testMinBaseFee, - genBlocks: func() []blockDefinition { - blocks := make([]blockDefinition, 0, len(spacedTimestamps)) - for _, timestamp := range spacedTimestamps { - blocks = append(blocks, blockDefinition{ - timestamp: timestamp, - gasUsed: 21000, - }) - } - return blocks - }, - }, - // Test overflow handling - { - baseFee: nil, - minFee: testMinBaseFee, - genBlocks: func() []blockDefinition { - blocks := make([]blockDefinition, 0, len(spacedTimestamps)) - for _, timestamp := range spacedTimestamps { - blocks = append(blocks, blockDefinition{ - timestamp: timestamp, - gasUsed: math.MaxUint64, - }) - } - return blocks - }, - }, - // Test update increase handling - { - baseFee: big.NewInt(50_000_000_000), - minFee: testMinBaseFee, - genBlocks: func() []blockDefinition { - blocks := make([]blockDefinition, 0, len(spacedTimestamps)) - for _, timestamp := range spacedTimestamps { - blocks = append(blocks, blockDefinition{ - timestamp: timestamp, - gasUsed: math.MaxUint64, - }) - } - return blocks - }, - }, - { - baseFee: nil, - minFee: testMinBaseFee, - genBlocks: func() []blockDefinition { - return []blockDefinition{ - { - timestamp: 1, - gasUsed: 1_000_000, - }, - { - timestamp: 3, - gasUsed: 1_000_000, - }, - { - timestamp: 5, - gasUsed: 2_000_000, - }, - { - timestamp: 5, - gasUsed: 6_000_000, - }, - { - timestamp: 7, - gasUsed: 6_000_000, - }, - { - timestamp: 1000, - gasUsed: 6_000_000, - }, - { - timestamp: 1001, - gasUsed: 6_000_000, - }, - { - timestamp: 1002, - gasUsed: 6_000_000, - }, - } - }, - }, - } - - for _, test := range tests { - testDynamicFeesStaysWithinRange(t, test) - } -} - -func testDynamicFeesStaysWithinRange(t *testing.T, test test) { - blocks := test.genBlocks() - initialBlock := blocks[0] - header := &types.Header{ - Time: initialBlock.timestamp, - GasUsed: initialBlock.gasUsed, - Number: big.NewInt(0), - BaseFee: test.baseFee, - } - - for index, block := range blocks[1:] { - testFeeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: test.minFee, - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - } - - nextExtraData, nextBaseFee, err := CalcBaseFee(params.TestChainConfig, testFeeConfig, header, block.timestamp) - if err != nil { - t.Fatalf("Failed to calculate base fee at index %d: %s", index, err) - } - if nextBaseFee.Cmp(test.minFee) < 0 { - t.Fatalf("Expected fee to stay greater than %d, but found %d", test.minFee, nextBaseFee) - } - log.Info("Update", "baseFee", nextBaseFee) - header = &types.Header{ - Time: block.timestamp, - GasUsed: block.gasUsed, - Number: big.NewInt(int64(index) + 1), - BaseFee: nextBaseFee, - Extra: nextExtraData, - } - } -} - -func TestSelectBigWithinBounds(t *testing.T) { - type test struct { - lower, value, upper, expected *big.Int - } - - tests := map[string]test{ - "value within bounds": { - lower: big.NewInt(0), - value: big.NewInt(5), - upper: big.NewInt(10), - expected: big.NewInt(5), - }, - "value below lower bound": { - lower: big.NewInt(0), - value: big.NewInt(-1), - upper: big.NewInt(10), - expected: big.NewInt(0), - }, - "value above upper bound": { - lower: big.NewInt(0), - value: big.NewInt(11), - upper: big.NewInt(10), - expected: big.NewInt(10), - }, - "value matches lower bound": { - lower: big.NewInt(0), - value: big.NewInt(0), - upper: big.NewInt(10), - expected: big.NewInt(0), - }, - "value matches upper bound": { - lower: big.NewInt(0), - value: big.NewInt(10), - upper: big.NewInt(10), - expected: big.NewInt(10), - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - v := selectBigWithinBounds(test.lower, test.value, test.upper) - if v.Cmp(test.expected) != 0 { - t.Fatalf("Expected (%d), found (%d)", test.expected, v) - } - }) - } -} - -func TestCalcBlockGasCost(t *testing.T) { - tests := map[string]struct { - parentBlockGasCost *big.Int - parentTime, currentTime uint64 - - expected *big.Int - }{ - "Nil parentBlockGasCost": { - parentBlockGasCost: nil, - parentTime: 1, - currentTime: 1, - expected: params.DefaultFeeConfig.MinBlockGasCost, - }, - "Same timestamp from 0": { - parentBlockGasCost: big.NewInt(0), - parentTime: 1, - currentTime: 1, - expected: big.NewInt(100_000), - }, - "1s from 0": { - parentBlockGasCost: big.NewInt(0), - parentTime: 1, - currentTime: 2, - expected: big.NewInt(50_000), - }, - "Same timestamp from non-zero": { - parentBlockGasCost: big.NewInt(50_000), - parentTime: 1, - currentTime: 1, - expected: big.NewInt(150_000), - }, - "0s Difference (MAX)": { - parentBlockGasCost: big.NewInt(1_000_000), - parentTime: 1, - currentTime: 1, - expected: big.NewInt(1_000_000), - }, - "1s Difference (MAX)": { - parentBlockGasCost: big.NewInt(1_000_000), - parentTime: 1, - currentTime: 2, - expected: big.NewInt(1_000_000), - }, - "2s Difference": { - parentBlockGasCost: big.NewInt(900_000), - parentTime: 1, - currentTime: 3, - expected: big.NewInt(900_000), - }, - "3s Difference": { - parentBlockGasCost: big.NewInt(1_000_000), - parentTime: 1, - currentTime: 4, - expected: big.NewInt(950_000), - }, - "10s Difference": { - parentBlockGasCost: big.NewInt(1_000_000), - parentTime: 1, - currentTime: 11, - expected: big.NewInt(600_000), - }, - "20s Difference": { - parentBlockGasCost: big.NewInt(1_000_000), - parentTime: 1, - currentTime: 21, - expected: big.NewInt(100_000), - }, - "22s Difference": { - parentBlockGasCost: big.NewInt(1_000_000), - parentTime: 1, - currentTime: 23, - expected: big.NewInt(0), - }, - "23s Difference": { - parentBlockGasCost: big.NewInt(1_000_000), - parentTime: 1, - currentTime: 24, - expected: big.NewInt(0), - }, - "-1s Difference": { - parentBlockGasCost: big.NewInt(50_000), - parentTime: 1, - currentTime: 0, - expected: big.NewInt(150_000), - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - assert.Zero(t, test.expected.Cmp(calcBlockGasCost( - params.DefaultFeeConfig.TargetBlockRate, - params.DefaultFeeConfig.MinBlockGasCost, - params.DefaultFeeConfig.MaxBlockGasCost, - testBlockGasCostStep, - test.parentBlockGasCost, - test.parentTime, - test.currentTime, - ))) - }) - } -} - -func TestCalcBaseFeeRegression(t *testing.T) { - parentTimestamp := uint64(1) - timestamp := parentTimestamp + params.RollupWindow + 1000 - - parentHeader := &types.Header{ - Time: parentTimestamp, - GasUsed: 1_000_000, - Number: big.NewInt(1), - BaseFee: big.NewInt(1), - Extra: make([]byte, params.DynamicFeeExtraDataSize), - } - - testFeeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(1 * params.GWei), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(100000), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - } - _, _, err := CalcBaseFee(params.TestChainConfig, testFeeConfig, parentHeader, timestamp) - require.NoError(t, err) - require.Equalf(t, 0, common.Big1.Cmp(big.NewInt(1)), "big1 should be 1, got %s", common.Big1) -} diff --git a/core/blockchain_log_test.go b/core/blockchain_log_test.go index fa16293634..d05c42faae 100644 --- a/core/blockchain_log_test.go +++ b/core/blockchain_log_test.go @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -43,7 +44,7 @@ func TestAcceptedLogsSubscription(t *testing.T) { gspec = &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: funds}}, - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(legacy.BaseFee), } contractAddress = crypto.CreateAddress(addr1, 0) signer = types.LatestSigner(gspec.Config) @@ -59,13 +60,13 @@ func TestAcceptedLogsSubscription(t *testing.T) { switch i { case 0: // First, we deploy the contract - contractTx := types.NewContractCreation(0, common.Big0, 200000, big.NewInt(params.TestInitialBaseFee), common.FromHex(callableBin)) + contractTx := types.NewContractCreation(0, common.Big0, 200000, big.NewInt(legacy.BaseFee), common.FromHex(callableBin)) contractSignedTx, err := types.SignTx(contractTx, signer, key1) require.NoError(err) b.AddTx(contractSignedTx) case 1: // In the next block, we call the contract function - tx := types.NewTransaction(1, contractAddress, common.Big0, 23000, big.NewInt(params.TestInitialBaseFee), packedFunction) + tx := types.NewTransaction(1, contractAddress, common.Big0, 23000, big.NewInt(legacy.BaseFee), packedFunction) tx, err := types.SignTx(tx, signer, key1) require.NoError(err) b.AddTx(tx) diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index eba02b712c..d1f242d94c 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -361,11 +361,15 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e } // GetFeeConfigAt returns the fee configuration and the last changed block number at [parent]. +// If Subnet-EVM is not activated, returns default fee config and nil block number. // If FeeManager is activated at [parent], returns the fee config in the precompile contract state. // Otherwise returns the fee config in the chain config. // Assumes that a valid configuration is stored when the precompile is activated. func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { config := bc.Config() + if !config.IsSubnetEVM(parent.Time) { + return params.DefaultFeeConfig, nil, nil + } if !config.IsPrecompileEnabled(feemanager.ContractAddress, parent.Time) { return config.FeeConfig, common.Big0, nil } diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 3772aa8da9..222437b4ca 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -281,6 +281,7 @@ func testLongDeepRepair(t *testing.T, snapshots bool) { func TestLongOldForkedShallowRepair(t *testing.T) { testLongOldForkedShallowRepair(t, false) } + func TestLongOldForkedShallowRepairWithSnapshots(t *testing.T) { testLongOldForkedShallowRepair(t, true) } @@ -359,6 +360,7 @@ func testLongOldForkedDeepRepair(t *testing.T, snapshots bool) { func TestLongNewerForkedShallowRepair(t *testing.T) { testLongNewerForkedShallowRepair(t, false) } + func TestLongNewerForkedShallowRepairWithSnapshots(t *testing.T) { testLongNewerForkedShallowRepair(t, true) } @@ -511,7 +513,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) { // It's hard to follow the test case, visualize the input - //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // fmt.Println(tt.dump(true)) // Create a temporary persistent database @@ -567,7 +569,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s gspec.MustCommit(genDb, triedb.NewDatabase(genDb, nil)) sideblocks, _, err = GenerateChain(gspec.Config, gspec.ToBlock(), engine, genDb, tt.sidechainBlocks, 10, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x01}) - tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x01}, big.NewInt(10000), params.TxGas, common.Big1, nil), signer, key1) + tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x01}, big.NewInt(10000), params.TxGas, chainConfig.FeeConfig.MinBaseFee, nil), signer, key1) require.NoError(err) b.AddTx(tx) }) @@ -581,7 +583,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s canonblocks, _, err := GenerateChain(gspec.Config, gspec.ToBlock(), engine, genDb, tt.canonicalBlocks, 10, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x02}) b.SetDifficulty(big.NewInt(1000000)) - tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x02}, big.NewInt(10000), params.TxGas, common.Big1, nil), signer, key1) + tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x02}, big.NewInt(10000), params.TxGas, chainConfig.FeeConfig.MinBaseFee, nil), signer, key1) require.NoError(err) b.AddTx(tx) }) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 9b5cea4aa1..87b5931941 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -44,6 +44,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" ) @@ -84,7 +85,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo // Initialize a fresh chain var ( gspec = &Genesis{ - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(legacy.BaseFee), Config: params.TestChainConfig, } engine = dummy.NewFullFaker() @@ -306,7 +307,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { gappedBlocks, _, _ := GenerateChain(snaptest.gspec.Config, blocks[len(blocks)-1], snaptest.engine, snaptest.genDb, snaptest.gapped, 10, func(i int, b *BlockGen) {}) // Insert a few more blocks without enabling snapshot - var cacheConfig = &CacheConfig{ + cacheConfig := &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, SnapshotLimit: 0, diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 4b9534996a..ed420f0f85 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/eth/tracers/logger" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -546,7 +547,7 @@ func TestCanonicalHashMarker(t *testing.T) { } func testCanonicalHashMarker(t *testing.T, scheme string) { - var cases = []struct { + cases := []struct { forkA int forkB int }{ @@ -585,7 +586,7 @@ func testCanonicalHashMarker(t *testing.T, scheme string) { gspec = &Genesis{ Config: params.TestChainConfig, Alloc: types.GenesisAlloc{}, - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(legacy.BaseFee), } engine = dummy.NewCoinbaseFaker() ) @@ -715,6 +716,7 @@ func TestCreateThenDeletePreByzantium(t *testing.T) { testCreateThenDelete(t, &config) } + func TestCreateThenDeletePostByzantium(t *testing.T) { testCreateThenDelete(t, params.TestChainConfig) } @@ -739,7 +741,8 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { byte(vm.PUSH1), 0x1, byte(vm.SSTORE), // Get the runtime-code on the stack - byte(vm.PUSH32)} + byte(vm.PUSH32), + } initCode = append(initCode, code...) initCode = append(initCode, []byte{ byte(vm.PUSH1), 0x0, // offset @@ -781,8 +784,8 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { }) // Import the canonical chain chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfig, gspec, engine, vm.Config{ - //Debug: true, - //Tracer: logger.NewJSONLogger(nil, os.Stdout), + // Debug: true, + // Tracer: logger.NewJSONLogger(nil, os.Stdout), }, common.Hash{}, false) if err != nil { t.Fatalf("failed to create tester chain: %v", err) @@ -941,7 +944,8 @@ func TestTransientStorageReset(t *testing.T) { byte(vm.TSTORE), // Get the runtime-code on the stack - byte(vm.PUSH32)} + byte(vm.PUSH32), + } initCode = append(initCode, code...) initCode = append(initCode, []byte{ byte(vm.PUSH1), 0x0, // offset diff --git a/core/chain_makers.go b/core/chain_makers.go index 6b8467dc3c..ad0b6b7f86 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -32,7 +32,6 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/rawdb" @@ -40,6 +39,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/header" "github.com/ava-labs/subnet-evm/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -374,29 +374,26 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, func (cm *chainMaker) makeHeader(parent *types.Block, gap uint64, state *state.StateDB, engine consensus.Engine) *types.Header { time := parent.Time() + gap // block time is fixed at [gap] seconds + feeConfig, _, err := cm.GetFeeConfigAt(parent.Header()) + if err != nil { + panic(err) + } + gasLimit := header.GasLimit(cm.config, feeConfig, parent.Header(), CalcGasLimit, time) + baseFee, err := header.BaseFee(cm.config, feeConfig, parent.Header(), time) + if err != nil { + panic(err) + } header := &types.Header{ Root: state.IntermediateRoot(cm.config.IsEIP158(parent.Number())), ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), Difficulty: engine.CalcDifficulty(cm, time, parent.Header()), - GasLimit: parent.GasLimit(), + GasLimit: gasLimit, Number: new(big.Int).Add(parent.Number(), common.Big1), Time: time, + BaseFee: baseFee, } - if cm.config.IsSubnetEVM(time) { - feeConfig, _, err := cm.GetFeeConfigAt(parent.Header()) - if err != nil { - panic(err) - } - header.GasLimit = feeConfig.GasLimit.Uint64() - header.Extra, header.BaseFee, err = dummy.CalcBaseFee(cm.config, feeConfig, parent.Header(), time) - if err != nil { - panic(err) - } - } else { - header.GasLimit = CalcGasLimit(parent.GasUsed(), parent.GasLimit(), parent.GasLimit(), parent.GasLimit()) - } if cm.config.IsCancun(header.Number, header.Time) { var ( parentExcessBlobGas uint64 diff --git a/core/genesis.go b/core/genesis.go index e08f186981..4b05cdda58 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -37,6 +37,7 @@ import ( "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/triedb" "github.com/ava-labs/subnet-evm/triedb/pathdb" @@ -397,7 +398,7 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big g := Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{addr: {Balance: balance}}, - BaseFee: big.NewInt(params.TestMaxBaseFee), + BaseFee: big.NewInt(legacy.BaseFee), } return g.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) } diff --git a/core/genesis_test.go b/core/genesis_test.go index ec56473e2d..cc170af1b0 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -39,6 +39,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/ava-labs/subnet-evm/trie" @@ -375,7 +376,7 @@ func TestVerkleGenesisCommit(t *testing.T) { } genesis := &Genesis{ - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(legacy.BaseFee), Config: verkleConfig, Timestamp: verkleTime, Difficulty: big.NewInt(0), diff --git a/core/headerchain_test.go b/core/headerchain_test.go index a5e65e81a8..6a6e652bef 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -38,6 +38,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ethereum/go-ethereum/common" ) @@ -75,7 +76,7 @@ func TestHeaderInsertion(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() gspec = &Genesis{ - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(legacy.BaseFee), Config: params.TestChainConfig, } ) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 2780684de3..dd715edd5e 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -39,6 +39,8 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "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/upgrade/legacy" "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/utils" @@ -59,17 +61,17 @@ func TestStateProcessorErrors(t *testing.T) { config := &cpcfg config.ShanghaiTime = u64(0) config.CancunTime = u64(0) - config.FeeConfig.MinBaseFee = big.NewInt(params.TestMaxBaseFee) + config.FeeConfig.MinBaseFee = big.NewInt(legacy.BaseFee) var ( signer = types.LatestSigner(config) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") ) - var makeTx = func(key *ecdsa.PrivateKey, nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { + makeTx := func(key *ecdsa.PrivateKey, nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, key) return tx } - var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { + mkDynamicTx := func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ Nonce: nonce, GasTipCap: gasTipCap, @@ -80,7 +82,7 @@ func TestStateProcessorErrors(t *testing.T) { }), signer, key1) return tx } - var mkDynamicCreationTx = func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction { + mkDynamicCreationTx := func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction { tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ Nonce: nonce, GasTipCap: gasTipCap, @@ -91,7 +93,7 @@ func TestStateProcessorErrors(t *testing.T) { }), signer, key1) return tx } - var mkBlobTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int, hashes []common.Hash) *types.Transaction { + mkBlobTx := func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int, hashes []common.Hash) *types.Transaction { tx, err := types.SignTx(types.NewTx(&types.BlobTx{ Nonce: nonce, GasTipCap: uint256.MustFromBig(gasTipCap), @@ -225,13 +227,13 @@ func TestStateProcessorErrors(t *testing.T) { }, { // ErrMaxInitCodeSizeExceeded txs: []*types.Transaction{ - mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.TestInitialBaseFee), tooBigInitCode[:]), + mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(legacy.BaseFee), tooBigInitCode[:]), }, want: "could not apply tx 0 [0x18a05f40f29ff16d5287f6f88b21c9f3c7fbc268f707251144996294552c4cd6]: max initcode size exceeded: code size 49153 limit 49152", }, { // ErrIntrinsicGas: Not enough gas to cover init code txs: []*types.Transaction{ - mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.TestInitialBaseFee), make([]byte, 320)), + mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(legacy.BaseFee), make([]byte, 320)), }, want: "could not apply tx 0 [0x849278f616d51ab56bba399551317213ce7a10e4d9cbc3d14bb663e50cb7ab99]: intrinsic gas too low: have 54299, want 54300", }, @@ -430,6 +432,12 @@ func TestBadTxAllowListBlock(t *testing.T) { // - valid pow (fake), ancestry, difficulty, gaslimit etc func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block { fakeChainReader := newChainMaker(nil, config, engine) + time := parent.Time() + 10 + feeConfig, _, err := fakeChainReader.GetFeeConfigAt(parent.Header()) + if err != nil { + panic(err) + } + baseFee, _ := customheader.BaseFee(config, feeConfig, parent.Header(), time) header := &types.Header{ ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), @@ -441,12 +449,11 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr }), GasLimit: parent.GasLimit(), Number: new(big.Int).Add(parent.Number(), common.Big1), - Time: parent.Time() + 10, + Time: time, UncleHash: types.EmptyUncleHash, + BaseFee: baseFee, } - if config.IsSubnetEVM(header.Time) { - header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, config.FeeConfig, parent.Header(), header.Time) header.BlockGasCost = big.NewInt(0) } var receipts []*types.Receipt @@ -466,9 +473,10 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr cumulativeGas += tx.Gas() nBlobs += len(tx.BlobHashes()) } + header.Extra, _ = customheader.ExtraPrefix(config, parent.Header(), time) header.Root = common.BytesToHash(hasher.Sum(nil)) if config.IsCancun(header.Number, header.Time) { - var pExcess, pUsed = uint64(0), uint64(0) + pExcess, pUsed := uint64(0), uint64(0) if parent.ExcessBlobGas() != nil { pExcess = *parent.ExcessBlobGas() pUsed = *parent.BlobGasUsed() diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 3dc6ab8439..791275b4aa 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -39,7 +39,6 @@ import ( "sync" "time" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/state" @@ -47,6 +46,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/header" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -361,11 +361,11 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserve txpool.Addres ) if p.config.Datadir != "" { queuedir = filepath.Join(p.config.Datadir, pendingTransactionStore) - if err := os.MkdirAll(queuedir, 0700); err != nil { + if err := os.MkdirAll(queuedir, 0o700); err != nil { return err } limbodir = filepath.Join(p.config.Datadir, limboedTransactionStore) - if err := os.MkdirAll(limbodir, 0700); err != nil { + if err := os.MkdirAll(limbodir, 0o700); err != nil { return err } } @@ -415,7 +415,7 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserve txpool.Addres p.Close() return err } - baseFee, err := dummy.EstimateNextBaseFee( + baseFee, err := header.EstimateNextBaseFee( p.chain.Config(), feeConfig, p.head, @@ -851,7 +851,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { log.Error("Failed to get fee config to reset blobpool fees", "err", err) return } - baseFeeBig, err := dummy.EstimateNextBaseFee( + baseFeeBig, err := header.EstimateNextBaseFee( p.chain.Config(), feeConfig, p.head, diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index b7d0c06c2b..6f729f1901 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -40,7 +40,6 @@ import ( "time" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" @@ -48,6 +47,8 @@ import ( "github.com/ava-labs/subnet-evm/core/txpool" "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/plugin/evm/upgrade/legacy" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/kzg4844" @@ -114,9 +115,9 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { GasLimit: gasLimit, GasUsed: 0, BaseFee: mid, - Extra: make([]byte, params.DynamicFeeExtraDataSize), + Extra: make([]byte, header.FeeWindowSize), } - _, baseFee, err := dummy.CalcBaseFee( + baseFee, err := header.BaseFee( bc.config, bc.config.FeeConfig, parent, blockTime, ) if err != nil { @@ -153,7 +154,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { GasLimit: gasLimit, BaseFee: baseFee, ExcessBlobGas: &excessBlobGas, - Extra: make([]byte, params.DynamicFeeExtraDataSize), + Extra: make([]byte, header.FeeWindowSize), } } @@ -347,7 +348,7 @@ func TestOpenDrops(t *testing.T) { storage, _ := os.MkdirTemp("", "blobpool-") defer os.RemoveAll(storage) - os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0o700) store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) // Insert a malformed transaction to verify that decoding errors (or format @@ -587,7 +588,7 @@ func TestOpenDrops(t *testing.T) { chain := &testBlockChain{ config: testChainConfig, - basefee: uint256.NewInt(uint64(params.TestInitialBaseFee)), + basefee: uint256.NewInt(uint64(legacy.BaseFee)), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), statedb: statedb, } @@ -670,7 +671,7 @@ func TestOpenIndex(t *testing.T) { storage, _ := os.MkdirTemp("", "blobpool-") defer os.RemoveAll(storage) - os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0o700) store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) // Insert a sequence of transactions with varying price points to check that @@ -683,8 +684,8 @@ func TestOpenIndex(t *testing.T) { txExecFeeCaps = []uint64{100, 90, 200, 10, 80, 300} txBlobFeeCaps = []uint64{55, 66, 77, 33, 22, 11} - //basefeeJumps = []float64{39.098, 38.204, 44.983, 19.549, 37.204, 48.426} // log 1.125 (exec fee cap) - //blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap) + // basefeeJumps = []float64{39.098, 38.204, 44.983, 19.549, 37.204, 48.426} // log 1.125 (exec fee cap) + // blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap) evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1} evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap)) @@ -706,7 +707,7 @@ func TestOpenIndex(t *testing.T) { chain := &testBlockChain{ config: testChainConfig, - basefee: uint256.NewInt(uint64(params.TestInitialBaseFee)), + basefee: uint256.NewInt(uint64(legacy.BaseFee)), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), statedb: statedb, } @@ -759,7 +760,7 @@ func TestOpenHeap(t *testing.T) { storage, _ := os.MkdirTemp("", "blobpool-") defer os.RemoveAll(storage) - os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0o700) store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) // Insert a few transactions from a few accounts. To remove randomness from @@ -846,7 +847,7 @@ func TestOpenCap(t *testing.T) { storage, _ := os.MkdirTemp("", "blobpool-") defer os.RemoveAll(storage) - os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0o700) store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) // Insert a few transactions from a few accounts @@ -1277,7 +1278,7 @@ func TestAdd(t *testing.T) { storage, _ := os.MkdirTemp("", "blobpool-") defer os.RemoveAll(storage) // late defer, still ok - os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0o700) store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) // Insert the seed transactions for the pool startup diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 47ddf2632b..e20b6309fa 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -37,13 +37,13 @@ import ( "time" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/txpool" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/header" "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" @@ -69,11 +69,9 @@ const ( txMaxSize = 4 * txSlotSize // 128KB ) -var ( - // ErrTxPoolOverflow is returned if the transaction pool is full and can't accept - // another remote transaction. - ErrTxPoolOverflow = errors.New("txpool is full") -) +// ErrTxPoolOverflow is returned if the transaction pool is full and can't accept +// another remote transaction. +var ErrTxPoolOverflow = errors.New("txpool is full") var ( evictionInterval = time.Minute // Time interval to check for evictable transactions @@ -1075,7 +1073,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error newErrs, dirtyAddrs := pool.addTxsLocked(news, local) pool.mu.Unlock() - var nilSlot = 0 + nilSlot := 0 for _, err := range newErrs { for errs[nilSlot] != nil { nilSlot++ @@ -1818,6 +1816,8 @@ func (pool *LegacyPool) periodicBaseFeeUpdate() { } } +// updateBaseFee updates the base fee in the tx pool based on the current head block. +// should only be called when the chain is in Subnet EVM. func (pool *LegacyPool) updateBaseFee() { pool.mu.Lock() defer pool.mu.Unlock() @@ -1829,12 +1829,13 @@ func (pool *LegacyPool) updateBaseFee() { } // assumes lock is already held +// should only be called when the chain is in Subnet EVM. func (pool *LegacyPool) updateBaseFeeAt(head *types.Header) error { feeConfig, _, err := pool.chain.GetFeeConfigAt(head) if err != nil { return err } - baseFeeEstimate, err := dummy.EstimateNextBaseFee(pool.chainconfig, feeConfig, head, uint64(time.Now().Unix())) + baseFeeEstimate, err := header.EstimateNextBaseFee(pool.chainconfig, feeConfig, head, uint64(time.Now().Unix())) if err != nil { return err } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 52a3b04b01..aefbc27689 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" @@ -105,7 +106,7 @@ func setDefaults(cfg *Config) { } } if cfg.BaseFee == nil { - cfg.BaseFee = new(big.Int).Set(params.DefaultFeeConfig.MinBaseFee) + cfg.BaseFee = big.NewInt(legacy.BaseFee) } if cfg.BlobBaseFee == nil { cfg.BlobBaseFee = big.NewInt(params.BlobTxMinBlobGasprice) diff --git a/eth/api_backend.go b/eth/api_backend.go index e3cba7bf92..f542229cc4 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -35,7 +35,6 @@ import ( "github.com/ava-labs/subnet-evm/accounts" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/bloombits" "github.com/ava-labs/subnet-evm/core/state" @@ -45,6 +44,7 @@ import ( "github.com/ava-labs/subnet-evm/eth/gasprice" "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/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -512,7 +512,7 @@ func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Blo } func (b *EthAPIBackend) MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error) { - return dummy.MinRequiredTip(b.ChainConfig(), header) + return customheader.EstimateRequiredTip(b.ChainConfig(), header) } func (b *EthAPIBackend) isLatestAndAllowed(number rpc.BlockNumber) bool { diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 818c4623f1..2642b2c063 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -34,10 +34,11 @@ import ( "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/types" "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/upgrade/legacy" "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" @@ -67,7 +68,7 @@ const ( var ( DefaultMaxPrice = big.NewInt(150 * params.GWei) DefaultMinPrice = big.NewInt(0 * params.GWei) - DefaultMinBaseFee = big.NewInt(params.TestInitialBaseFee) + DefaultMinBaseFee = big.NewInt(legacy.BaseFee) DefaultMinGasUsed = big.NewInt(6_000_000) // block gas limit is 8,000,000 DefaultMaxLookbackSeconds = uint64(80) ) @@ -192,12 +193,8 @@ func NewOracle(backend OracleBackend, config Config) (*Oracle, error) { } }() feeConfig, _, err := backend.GetFeeConfigAt(backend.LastAcceptedBlock().Header()) - var minBaseFee *big.Int if err != nil { - // resort back to chain config return nil, fmt.Errorf("failed getting fee config in the oracle: %w", err) - } else { - minBaseFee = feeConfig.MinBaseFee } feeInfoProvider, err := newFeeInfoProvider(backend, minGasUsed.Uint64(), config.Blocks) if err != nil { @@ -206,7 +203,7 @@ func NewOracle(backend OracleBackend, config Config) (*Oracle, error) { return &Oracle{ backend: backend, lastPrice: minPrice, - lastBaseFee: new(big.Int).Set(minBaseFee), + lastBaseFee: new(big.Int).Set(feeConfig.MinBaseFee), minPrice: minPrice, maxPrice: maxPrice, checkBlocks: blocks, @@ -268,7 +265,7 @@ func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) // If the block does have a baseFee, calculate the next base fee // based on the current time and add it to the tip to estimate the // total gas price estimate. - return dummy.EstimateNextBaseFee(oracle.backend.ChainConfig(), feeConfig, header, oracle.clock.Unix()) + return customheader.EstimateNextBaseFee(oracle.backend.ChainConfig(), feeConfig, header, oracle.clock.Unix()) } // SuggestPrice returns an estimated price for legacy transactions. diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 769c968a5a..9a6b7ed7e6 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -39,6 +39,8 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "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/upgrade/legacy" "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/rpc" "github.com/ava-labs/subnet-evm/utils" @@ -99,7 +101,7 @@ func (b *testBackend) teardown() { } func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { - var gspec = &core.Genesis{ + gspec := &core.Genesis{ Config: config, Alloc: types.GenesisAlloc{addr: {Balance: bal}}, } @@ -126,7 +128,7 @@ func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBloc // newTestBackend creates a test backend. OBS: don't forget to invoke tearDown // after use, otherwise the blockchain instance will mem-leak via goroutines. func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { - var gspec = &core.Genesis{ + gspec := &core.Genesis{ Config: config, Alloc: types.GenesisAlloc{addr: {Balance: bal}}, } @@ -150,7 +152,7 @@ func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, gen } func (b *testBackend) MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error) { - return dummy.MinRequiredTip(b.chain.Config(), header) + return customheader.EstimateRequiredTip(b.chain.Config(), header) } func (b *testBackend) CurrentHeader() *types.Header { @@ -339,7 +341,7 @@ func TestSuggestGasPriceSubnetEVM(t *testing.T) { b.SetCoinbase(common.Address{1}) signer := types.LatestSigner(params.TestChainConfig) - gasPrice := big.NewInt(params.MinGasPrice) + gasPrice := big.NewInt(legacy.BaseFee) for j := 0; j < 50; j++ { tx := types.NewTx(&types.LegacyTx{ Nonce: b.TxNonce(addr), diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index d5a931ebc4..058899052b 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -55,6 +55,7 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/internal/blocktest" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -158,7 +159,8 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []txDa "r": "0xac639f4319e9268898e29444b97101f1225e2a0837151626da23e73dda2443fc", "s": "0x4fcc3f4c3a75f70ee45bb42d4b0aad432cc8c0140efb3e2611d6a6dda8460907" }`, - }, { + }, + { Tx: &types.LegacyTx{ Nonce: 5, GasPrice: big.NewInt(6), @@ -235,7 +237,8 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []txDa "s": "0x2dc134b6bc43abbdfebef0b2d62c175459fc1e8ddff60c8e740c461d7ea1522f", "yParity": "0x1" }`, - }, { + }, + { Tx: &types.AccessListTx{ ChainID: config.ChainID, Nonce: 5, @@ -281,7 +284,8 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []txDa "s": "0x32a908d1bc2db0229354f4dd392ffc37934e24ae8b18a620c6588c72660b6238", "yParity": "0x1" }`, - }, { + }, + { Tx: &types.DynamicFeeTx{ ChainID: config.ChainID, Nonce: 5, @@ -330,7 +334,8 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []txDa "s": "0x31da567e2b3a83e58e42f7902c3706926c926625f6978c24fdaa21b9d143bbf7", "yParity": "0x0" }`, - }, { + }, + { Tx: &types.DynamicFeeTx{ ChainID: config.ChainID, Nonce: 5, @@ -443,14 +448,12 @@ type testBackend struct { } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { - var ( - cacheConfig = &core.CacheConfig{ - TrieCleanLimit: 256, - TrieDirtyLimit: 256, - SnapshotLimit: 0, - Pruning: false, // Archive mode - } - ) + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + SnapshotLimit: 0, + Pruning: false, // Archive mode + } accman, acc := newTestAccountManager(t) gspec.Alloc[acc.Address] = types.Account{Balance: big.NewInt(params.Ether)} // Generate blocks for testing @@ -476,6 +479,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return big.NewInt(0), nil } + func (b testBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { return nil, nil, nil, nil, nil } @@ -493,9 +497,11 @@ func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) } return b.chain.GetHeaderByNumber(uint64(number)), nil } + func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { return b.chain.GetHeaderByHash(hash), nil } + func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.HeaderByNumber(ctx, blockNr) @@ -514,9 +520,11 @@ func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) } return b.chain.GetBlockByNumber(uint64(number)), nil } + func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { return b.chain.GetBlockByHash(hash), nil } + func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.BlockByNumber(ctx, blockNr) @@ -526,9 +534,11 @@ func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc. } panic("unknown type rpc.BlockNumberOrHash") } + func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil } + func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { if number == rpc.PendingBlockNumber { panic("pending state not implemented") @@ -543,6 +553,7 @@ func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.Bloc stateDb, err := b.chain.StateAt(header.Root) return stateDb, header, err } + func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.StateAndHeaderByNumber(ctx, blockNr) @@ -558,6 +569,7 @@ func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.R receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), header.Time, b.chain.Config()) return receipts, nil } + func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) *vm.EVM { if vmConfig == nil { vmConfig = b.chain.GetVMConfig() @@ -569,21 +581,27 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state } return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig) } + func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { panic("implement me") } + func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { panic("implement me") } + func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { panic("implement me") } + func (b testBackend) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { panic("implement me") } + func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { panic("implement me") } + func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) return true, tx, blockHash, blockNumber, index, nil @@ -597,9 +615,11 @@ func (b testBackend) Stats() (pending int, queued int) { panic("implement me") } func (b testBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { panic("implement me") } + func (b testBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { panic("implement me") } + func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription { panic("implement me") } @@ -608,12 +628,15 @@ func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { panic("implement me") } + func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { panic("implement me") } + func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { panic("implement me") } + func (b testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { panic("implement me") } @@ -654,7 +677,7 @@ func TestEstimateGas(t *testing.T) { b.AddTx(tx) // b.SetPoS() })) - var testSuite = []struct { + testSuite := []struct { blockNumber rpc.BlockNumber call TransactionArgs overrides StateOverride @@ -726,7 +749,7 @@ func TestEstimateGas(t *testing.T) { call: TransactionArgs{ From: &accounts[0].addr, Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), - GasPrice: (*hexutil.Big)(big.NewInt(params.TestInitialBaseFee)), // Legacy as pricing + GasPrice: (*hexutil.Big)(big.NewInt(legacy.BaseFee)), // Legacy as pricing }, expectErr: nil, want: 67617, @@ -736,7 +759,7 @@ func TestEstimateGas(t *testing.T) { call: TransactionArgs{ From: &accounts[0].addr, Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), - MaxFeePerGas: (*hexutil.Big)(big.NewInt(params.TestInitialBaseFee)), // 1559 gas pricing + MaxFeePerGas: (*hexutil.Big)(big.NewInt(legacy.BaseFee)), // 1559 gas pricing }, expectErr: nil, want: 67617, @@ -759,7 +782,7 @@ func TestEstimateGas(t *testing.T) { From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, + BlobHashes: []common.Hash{{0x01, 0x22}}, BlobFeeCap: (*hexutil.Big)(big.NewInt(1)), }, want: 21000, @@ -813,7 +836,7 @@ func TestCall(t *testing.T) { // b.SetPoS() })) randomAccounts := newAccounts(3) - var testSuite = []struct { + testSuite := []struct { blockNumber rpc.BlockNumber overrides StateOverride call TransactionArgs @@ -948,7 +971,7 @@ func TestCall(t *testing.T) { call: TransactionArgs{ From: &accounts[1].addr, To: &randomAccounts[2].addr, - BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, + BlobHashes: []common.Hash{{0x01, 0x22}}, BlobFeeCap: (*hexutil.Big)(big.NewInt(1)), }, overrides: StateOverride{ @@ -1075,7 +1098,7 @@ func TestSendBlobTransaction(t *testing.T) { From: &b.acc.Address, To: &to, Value: (*hexutil.Big)(big.NewInt(1)), - BlobHashes: []common.Hash{common.Hash{0x01, 0x22}}, + BlobHashes: []common.Hash{{0x01, 0x22}}, }) if err != nil { t.Fatalf("failed to fill tx defaults: %v\n", err) @@ -1295,7 +1318,7 @@ func argsFromTransaction(tx *types.Transaction, from common.Address) Transaction Input: (*hexutil.Bytes)(&input), ChainID: (*hexutil.Big)(tx.ChainId()), // TODO: impl accessList conversion - //AccessList: tx.AccessList(), + // AccessList: tx.AccessList(), BlobFeeCap: (*hexutil.Big)(tx.BlobGasFeeCap()), BlobHashes: tx.BlobHashes(), } @@ -1358,7 +1381,7 @@ func TestRPCMarshalBlock(t *testing.T) { } block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, txs, nil, nil, blocktest.NewHasher()) - var testSuite = []struct { + testSuite := []struct { inclTx bool fullTx bool want string @@ -1582,7 +1605,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) { } pendingHash := pending.Hash() - var testSuite = []struct { + testSuite := []struct { blockNumber rpc.BlockNumber blockHash *common.Hash fullTx bool @@ -1883,7 +1906,7 @@ func TestRPCGetTransactionReceipt(t *testing.T) { api = NewTransactionAPI(backend, new(AddrLocker)) ) - var testSuite = []struct { + testSuite := []struct { txHash common.Hash file string }{ @@ -1961,7 +1984,7 @@ func TestRPCGetBlockReceipts(t *testing.T) { blockHashes[i] = header.Hash() } - var testSuite = []struct { + testSuite := []struct { test rpc.BlockNumberOrHash file string }{ @@ -2049,7 +2072,7 @@ func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc s } outputFile := filepath.Join("testdata", fmt.Sprintf("%s-%s.json", rpc, file)) if os.Getenv("WRITE_TEST_FILES") != "" { - os.WriteFile(outputFile, data, 0644) + os.WriteFile(outputFile, data, 0o644) } want, err := os.ReadFile(outputFile) if err != nil { diff --git a/miner/worker.go b/miner/worker.go index b7b5e7b9b8..230b51d26c 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -39,7 +39,6 @@ import ( "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/subnet-evm/consensus" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/state" @@ -47,6 +46,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "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" "github.com/ethereum/go-ethereum/common" @@ -144,36 +144,26 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateConte timestamp = parent.Time } - var gasLimit uint64 // The fee manager relies on the state of the parent block to set the fee config // because the fee config may be changed by the current block. feeConfig, _, err := w.chain.GetFeeConfigAt(parent) if err != nil { return nil, err } - configuredGasLimit := feeConfig.GasLimit.Uint64() - if w.chainConfig.IsSubnetEVM(timestamp) { - gasLimit = configuredGasLimit - } else { - // The gas limit is set in SubnetEVMGasLimit because the ceiling and floor were set to the same value - // such that the gas limit converged to it. Since this is hardbaked now, we remove the ability to configure it. - gasLimit = core.CalcGasLimit(parent.GasUsed, parent.GasLimit, configuredGasLimit, configuredGasLimit) + gasLimit := header.GasLimit(w.chainConfig, feeConfig, parent, core.CalcGasLimit, timestamp) + baseFee, err := header.BaseFee(w.chainConfig, feeConfig, parent, timestamp) + if err != nil { + return nil, fmt.Errorf("failed to calculate new base fee: %w", err) } + header := &types.Header{ ParentHash: parent.Hash(), Number: new(big.Int).Add(parent.Number, common.Big1), GasLimit: gasLimit, - Extra: nil, Time: timestamp, + BaseFee: baseFee, } - if w.chainConfig.IsSubnetEVM(timestamp) { - var err error - header.Extra, header.BaseFee, err = dummy.CalcBaseFee(w.chainConfig, feeConfig, parent, timestamp) - if err != nil { - return nil, fmt.Errorf("failed to calculate new base fee: %w", err) - } - } // Apply EIP-4844, EIP-4788. if w.chainConfig.IsCancun(header.Number, header.Time) { var excessBlobGas uint64 diff --git a/params/config_extra.go b/params/config_extra.go index c8f292d914..cf30d64454 100644 --- a/params/config_extra.go +++ b/params/config_extra.go @@ -10,7 +10,6 @@ import ( "math/big" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" @@ -18,15 +17,6 @@ import ( const ( maxJSONLen = 64 * 1024 * 1024 // 64MB - - // Consensus Params - RollupWindow = 10 // in seconds - DynamicFeeExtraDataSize = wrappers.LongLen * RollupWindow - - // For legacy tests - MinGasPrice int64 = 225_000_000_000 - TestInitialBaseFee int64 = 225_000_000_000 - TestMaxBaseFee int64 = 225_000_000_000 ) var ( diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 4ca35c1555..2b55482c5f 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -13,10 +13,11 @@ import ( "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/plugin/evm/upgrade/legacy" "github.com/ava-labs/subnet-evm/trie" ) -var legacyMinGasPrice = big.NewInt(params.MinGasPrice) +var legacyMinGasPrice = big.NewInt(legacy.BaseFee) type BlockValidator interface { SyntacticVerify(b *Block, rules params.Rules) error diff --git a/plugin/evm/blockgascost/cost.go b/plugin/evm/blockgascost/cost.go new file mode 100644 index 0000000000..56293691cf --- /dev/null +++ b/plugin/evm/blockgascost/cost.go @@ -0,0 +1,57 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// blockgascost implements the block gas cost logic +package blockgascost + +import ( + "math" + + safemath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/subnet-evm/commontype" +) + +// BlockGasCost calculates the required block gas cost. +// +// cost = parentCost + step * (TargetBlockRate - timeElapsed) +// +// The returned cost is clamped to [MinBlockGasCost, MaxBlockGasCost]. +func BlockGasCost( + feeConfig commontype.FeeConfig, + parentCost uint64, + step uint64, + timeElapsed uint64, +) uint64 { + deviation := safemath.AbsDiff(feeConfig.TargetBlockRate, timeElapsed) + change, err := safemath.Mul(step, deviation) + if err != nil { + change = math.MaxUint64 + } + + var ( + minBlockGasCost uint64 = feeConfig.MinBlockGasCost.Uint64() + maxBlockGasCost uint64 = feeConfig.MaxBlockGasCost.Uint64() + op = safemath.Add[uint64] + defaultCost uint64 = feeConfig.MaxBlockGasCost.Uint64() + ) + if timeElapsed > feeConfig.TargetBlockRate { + op = safemath.Sub + defaultCost = minBlockGasCost + } + + cost, err := op(parentCost, change) + if err != nil { + cost = defaultCost + } + + switch { + case cost < minBlockGasCost: + // This is technically dead code because [MinBlockGasCost] is 0, but it + // makes the code more clear. + return minBlockGasCost + case cost > maxBlockGasCost: + return maxBlockGasCost + default: + return cost + } +} diff --git a/plugin/evm/blockgascost/cost_test.go b/plugin/evm/blockgascost/cost_test.go new file mode 100644 index 0000000000..acc3938181 --- /dev/null +++ b/plugin/evm/blockgascost/cost_test.go @@ -0,0 +1,100 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blockgascost + +import ( + "math" + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/stretchr/testify/require" +) + +func TestBlockGasCost(t *testing.T) { + testFeeConfig := commontype.FeeConfig{ + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + TargetBlockRate: 2, + BlockGasCostStep: big.NewInt(50_000), + } + BlockGasCostTest(t, testFeeConfig) + + testFeeConfigDouble := commontype.FeeConfig{ + MinBlockGasCost: big.NewInt(2), + MaxBlockGasCost: big.NewInt(2_000_000), + TargetBlockRate: 4, + BlockGasCostStep: big.NewInt(100_000), + } + BlockGasCostTest(t, testFeeConfigDouble) +} + +func BlockGasCostTest(t *testing.T, testFeeConfig commontype.FeeConfig) { + targetBlockRate := testFeeConfig.TargetBlockRate + maxBlockGasCost := testFeeConfig.MaxBlockGasCost.Uint64() + tests := []struct { + name string + parentCost uint64 + step uint64 + timeElapsed uint64 + want uint64 + }{ + { + name: "timeElapsed_under_target", + parentCost: 500, + step: 100, + timeElapsed: 0, + want: 500 + 100*targetBlockRate, + }, + { + name: "timeElapsed_at_target", + parentCost: 3, + step: 100, + timeElapsed: targetBlockRate, + want: 3, + }, + { + name: "timeElapsed_over_target", + parentCost: 500, + step: 100, + timeElapsed: 2 * targetBlockRate, + want: 500 - 100*targetBlockRate, + }, + { + name: "change_overflow", + parentCost: 500, + step: math.MaxUint64, + timeElapsed: 0, + want: maxBlockGasCost, + }, + { + name: "cost_overflow", + parentCost: math.MaxUint64, + step: 1, + timeElapsed: 0, + want: maxBlockGasCost, + }, + { + name: "clamp_to_max", + parentCost: maxBlockGasCost, + step: 100, + timeElapsed: targetBlockRate - 1, + want: maxBlockGasCost, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal( + t, + test.want, + BlockGasCost( + testFeeConfig, + test.parentCost, + test.step, + test.timeElapsed, + ), + ) + }) + } +} diff --git a/plugin/evm/gossip_test.go b/plugin/evm/gossip_test.go index 8e0b82ec0f..36a46a7b79 100644 --- a/plugin/evm/gossip_test.go +++ b/plugin/evm/gossip_test.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/prometheus/client_golang/prometheus" @@ -67,7 +68,7 @@ func TestGossipSubscribe(t *testing.T) { }, 10*time.Second, 500*time.Millisecond, "expected gossipTxPool to be subscribed") // create eth txs - ethTxs := getValidEthTxs(key, 10, big.NewInt(226*params.GWei)) + ethTxs := getValidEthTxs(key, 10, big.NewInt(226*utils.GWei)) // Notify mempool about txs errs := txPool.AddRemotesSync(ethTxs) diff --git a/plugin/evm/header/base_fee.go b/plugin/evm/header/base_fee.go new file mode 100644 index 0000000000..2098ac68d6 --- /dev/null +++ b/plugin/evm/header/base_fee.go @@ -0,0 +1,56 @@ +// (c) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "errors" + "math/big" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" +) + +var errEstimateBaseFeeWithoutActivation = errors.New("cannot estimate base fee for chain without activation scheduled") + +// BaseFee takes the previous header and the timestamp of its child block and +// calculates the expected base fee for the child block. +// +// Prior to SubnetEVM, the returned base fee will be nil. +func BaseFee( + config *params.ChainConfig, + feeConfig commontype.FeeConfig, + parent *types.Header, + timestamp uint64, +) (*big.Int, error) { + switch { + case config.IsSubnetEVM(timestamp): + return baseFeeFromWindow(config, feeConfig, parent, timestamp) + default: + // Prior to SubnetEVM the expected base fee is nil. + return nil, nil + } +} + +// EstimateNextBaseFee attempts to estimate the base fee of a block built at +// `timestamp` on top of `parent`. +// +// If timestamp is before parent.Time or the SubnetEVM activation time, then timestamp +// is set to the maximum of parent.Time and the SubnetEVM activation time. +// +// Warning: This function should only be used in estimation and should not be +// used when calculating the canonical base fee for a block. +func EstimateNextBaseFee( + config *params.ChainConfig, + feeConfig commontype.FeeConfig, + parent *types.Header, + timestamp uint64, +) (*big.Int, error) { + if config.SubnetEVMTimestamp == nil { + return nil, errEstimateBaseFeeWithoutActivation + } + + timestamp = max(timestamp, parent.Time, *config.SubnetEVMTimestamp) + return BaseFee(config, feeConfig, parent, timestamp) +} diff --git a/plugin/evm/header/base_fee_test.go b/plugin/evm/header/base_fee_test.go new file mode 100644 index 0000000000..6bd740d0fa --- /dev/null +++ b/plugin/evm/header/base_fee_test.go @@ -0,0 +1,260 @@ +// (c) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/subnetevm" + "github.com/ava-labs/subnet-evm/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +const ( + maxBaseFee = 225 * utils.GWei +) + +func TestBaseFee(t *testing.T) { + t.Run("normal", func(t *testing.T) { + BaseFeeTest(t, testFeeConfig) + }) + t.Run("double", func(t *testing.T) { + BaseFeeTest(t, testFeeConfigDouble) + }) +} + +func BaseFeeTest(t *testing.T, feeConfig commontype.FeeConfig) { + tests := []struct { + name string + upgrades params.NetworkUpgrades + parent *types.Header + timestamp uint64 + want *big.Int + wantErr error + }{ + { + name: "pre_subnet_evm", + upgrades: params.TestPreSubnetEVMChainConfig.NetworkUpgrades, + want: nil, + wantErr: nil, + }, + { + name: "subnet_evm_first_block", + upgrades: params.NetworkUpgrades{ + SubnetEVMTimestamp: utils.NewUint64(1), + }, + parent: &types.Header{ + Number: big.NewInt(1), + }, + timestamp: 1, + want: big.NewInt(feeConfig.MinBaseFee.Int64()), + }, + { + name: "subnet_evm_genesis_block", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(0), + }, + want: big.NewInt(feeConfig.MinBaseFee.Int64()), + }, + { + name: "subnet_evm_invalid_fee_window", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + }, + wantErr: errDynamicFeeWindowInsufficientLength, + }, + { + name: "subnet_evm_invalid_timestamp", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + Time: 1, + Extra: feeWindowBytes(subnetevm.Window{}), + }, + timestamp: 0, + wantErr: errInvalidTimestamp, + }, + { + name: "subnet_evm_no_change", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + GasUsed: feeConfig.TargetGas.Uint64(), + Time: 1, + Extra: feeWindowBytes(subnetevm.Window{}), + BaseFee: big.NewInt(feeConfig.MinBaseFee.Int64() + 1), + }, + timestamp: 1, + want: big.NewInt(feeConfig.MinBaseFee.Int64() + 1), + }, + { + name: "subnet_evm_small_decrease", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + Extra: feeWindowBytes(subnetevm.Window{}), + BaseFee: big.NewInt(maxBaseFee), + }, + timestamp: 1, + want: func() *big.Int { + var ( + gasTarget = feeConfig.TargetGas.Int64() + gasUsed = int64(0) + amountUnderTarget = gasTarget - gasUsed + parentBaseFee = int64(maxBaseFee) + smoothingFactor = feeConfig.BaseFeeChangeDenominator.Int64() + baseFeeFractionUnderTarget = amountUnderTarget * parentBaseFee / gasTarget + delta = baseFeeFractionUnderTarget / smoothingFactor + baseFee = parentBaseFee - delta + ) + return big.NewInt(baseFee) + }(), + }, + { + name: "subnet_evm_large_decrease", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + Extra: feeWindowBytes(subnetevm.Window{}), + BaseFee: big.NewInt(maxBaseFee), + }, + timestamp: 2 * subnetevm.WindowLen, + want: func() *big.Int { + var ( + gasTarget = feeConfig.TargetGas.Int64() + gasUsed = int64(0) + amountUnderTarget = gasTarget - gasUsed + parentBaseFee = int64(maxBaseFee) + smoothingFactor = feeConfig.BaseFeeChangeDenominator.Int64() + baseFeeFractionUnderTarget = amountUnderTarget * parentBaseFee / gasTarget + windowsElapsed = int64(2) + delta = windowsElapsed * baseFeeFractionUnderTarget / smoothingFactor + baseFee = parentBaseFee - delta + ) + return big.NewInt(baseFee) + }(), + }, + { + name: "subnet_evm_increase", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + GasUsed: 2 * feeConfig.TargetGas.Uint64(), + Extra: feeWindowBytes(subnetevm.Window{}), + BaseFee: big.NewInt(feeConfig.MinBaseFee.Int64()), + }, + timestamp: 1, + want: func() *big.Int { + var ( + gasTarget = feeConfig.TargetGas.Int64() + gasUsed = 2 * gasTarget + amountOverTarget = gasUsed - gasTarget + parentBaseFee = feeConfig.MinBaseFee.Int64() + smoothingFactor = feeConfig.BaseFeeChangeDenominator.Int64() + baseFeeFractionOverTarget = amountOverTarget * parentBaseFee / gasTarget + delta = baseFeeFractionOverTarget / smoothingFactor + baseFee = parentBaseFee + delta + ) + return big.NewInt(baseFee) + }(), + }, + { + name: "subnet_evm_big_1_not_modified", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + GasUsed: 1, + Extra: feeWindowBytes(subnetevm.Window{}), + BaseFee: big.NewInt(1), + }, + timestamp: 2 * subnetevm.WindowLen, + want: big.NewInt(feeConfig.MinBaseFee.Int64()), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + config := ¶ms.ChainConfig{ + NetworkUpgrades: test.upgrades, + } + got, err := BaseFee(config, feeConfig, test.parent, test.timestamp) + require.ErrorIs(err, test.wantErr) + require.Equal(test.want, got) + + // Verify that [common.Big1] is not modified by [BaseFee]. + require.Equal(big.NewInt(1), common.Big1) + }) + } +} + +func TestEstimateNextBaseFee(t *testing.T) { + t.Run("normal", func(t *testing.T) { + BlockGasCostTest(t, testFeeConfig) + }) + t.Run("double", func(t *testing.T) { + BlockGasCostTest(t, testFeeConfigDouble) + }) +} + +func EstimateNextBaseFeeTest(t *testing.T, feeConfig commontype.FeeConfig) { + testBaseFee := uint64(225 * utils.GWei) + nilUpgrade := params.NetworkUpgrades{} + tests := []struct { + name string + upgrades params.NetworkUpgrades + parent *types.Header + timestamp uint64 + want *big.Int + wantErr error + }{ + { + name: "activated", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + Extra: feeWindowBytes(subnetevm.Window{}), + BaseFee: new(big.Int).SetUint64(testBaseFee), + }, + timestamp: 1, + want: func() *big.Int { + var ( + gasTarget = feeConfig.TargetGas.Uint64() + gasUsed = uint64(0) + amountUnderTarget = gasTarget - gasUsed + parentBaseFee = testBaseFee + smoothingFactor = feeConfig.BaseFeeChangeDenominator.Uint64() + baseFeeFractionUnderTarget = amountUnderTarget * parentBaseFee / gasTarget + delta = baseFeeFractionUnderTarget / smoothingFactor + baseFee = parentBaseFee - delta + ) + return new(big.Int).SetUint64(baseFee) + }(), + }, + { + name: "not_scheduled", + upgrades: nilUpgrade, + wantErr: errEstimateBaseFeeWithoutActivation, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + config := ¶ms.ChainConfig{ + NetworkUpgrades: test.upgrades, + } + got, err := EstimateNextBaseFee(config, feeConfig, test.parent, test.timestamp) + require.ErrorIs(err, test.wantErr) + require.Equal(test.want, got) + }) + } +} diff --git a/plugin/evm/header/block_gas_cost.go b/plugin/evm/header/block_gas_cost.go new file mode 100644 index 0000000000..e9b9453826 --- /dev/null +++ b/plugin/evm/header/block_gas_cost.go @@ -0,0 +1,101 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "errors" + "math/big" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/blockgascost" +) + +var ( + errBaseFeeNil = errors.New("base fee is nil") + errBlockGasCostNil = errors.New("block gas cost is nil") +) + +// BlockGasCost calculates the required block gas cost based on the parent +// header and the timestamp of the new block. +func BlockGasCost( + feeConfig commontype.FeeConfig, + parent *types.Header, + timestamp uint64, +) uint64 { + step := feeConfig.BlockGasCostStep.Uint64() + // Treat an invalid parent/current time combination as 0 elapsed time. + // + // TODO: Does it even make sense to handle this? The timestamp should be + // verified to ensure this never happens. + var timeElapsed uint64 + if parent.Time <= timestamp { + timeElapsed = timestamp - parent.Time + } + return BlockGasCostWithStep( + feeConfig, + parent.BlockGasCost, + step, + timeElapsed, + ) +} + +// BlockGasCostWithStep calculates the required block gas cost based on the +// parent cost and the time difference between the parent block and new block. +// +// This is a helper function that allows the caller to manually specify the step +// value to use. +func BlockGasCostWithStep( + feeConfig commontype.FeeConfig, + parentCost *big.Int, + step uint64, + timeElapsed uint64, +) uint64 { + if parentCost == nil { + return feeConfig.MinBlockGasCost.Uint64() + } + + // [feeConfig.MaxBlockGasCost] is <= MaxUint64, so we know that parentCost is + // always going to be a valid uint64. + return blockgascost.BlockGasCost( + feeConfig, + parentCost.Uint64(), + step, + timeElapsed, + ) +} + +// EstimateRequiredTip is the estimated tip a transaction would have needed to +// pay to be included in a given block (assuming it paid a tip proportional to +// its gas usage). +// +// In reality, the consensus engine does not enforce a minimum tip on individual +// transactions. The only correctness check performed is that the sum of all +// tips is >= the required block fee. +// +// This function will return nil for all return values prior to Apricot Phase 4. +func EstimateRequiredTip( + config *params.ChainConfig, + header *types.Header, +) (*big.Int, error) { + switch { + case !config.IsSubnetEVM(header.Time): + return nil, nil + case header.BaseFee == nil: + return nil, errBaseFeeNil + case header.BlockGasCost == nil: + return nil, errBlockGasCostNil + } + + totalGasUsed := new(big.Int).SetUint64(header.GasUsed) + + // totalRequiredTips = blockGasCost * baseFee + totalRequiredTips := new(big.Int) + totalRequiredTips.Mul(header.BlockGasCost, header.BaseFee) + + // estimatedTip = totalRequiredTips / totalGasUsed + estimatedTip := totalRequiredTips.Div(totalRequiredTips, totalGasUsed) + return estimatedTip, nil +} diff --git a/plugin/evm/header/block_gas_cost_test.go b/plugin/evm/header/block_gas_cost_test.go new file mode 100644 index 0000000000..73ee342db9 --- /dev/null +++ b/plugin/evm/header/block_gas_cost_test.go @@ -0,0 +1,254 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + testFeeConfig = commontype.FeeConfig{ + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + TargetBlockRate: 2, + BlockGasCostStep: big.NewInt(50_000), + TargetGas: big.NewInt(10_000_000), + BaseFeeChangeDenominator: big.NewInt(12), + MinBaseFee: big.NewInt(25 * utils.GWei), + GasLimit: big.NewInt(12_000_000), + } + + testFeeConfigDouble = commontype.FeeConfig{ + MinBlockGasCost: big.NewInt(2), + MaxBlockGasCost: big.NewInt(2_000_000), + TargetBlockRate: 4, + BlockGasCostStep: big.NewInt(100_000), + TargetGas: big.NewInt(20_000_000), + BaseFeeChangeDenominator: big.NewInt(24), + MinBaseFee: big.NewInt(50 * utils.GWei), + GasLimit: big.NewInt(24_000_000), + } +) + +func TestBlockGasCost(t *testing.T) { + t.Run("normal", func(t *testing.T) { + BlockGasCostTest(t, testFeeConfig) + }) + t.Run("double", func(t *testing.T) { + BlockGasCostTest(t, testFeeConfigDouble) + }) +} + +func BlockGasCostTest(t *testing.T, feeConfig commontype.FeeConfig) { + maxBlockGasCostBig := feeConfig.MaxBlockGasCost + maxBlockGasCost := feeConfig.MaxBlockGasCost.Uint64() + blockGasCostStep := feeConfig.BlockGasCostStep.Uint64() + minBlockGasCost := feeConfig.MinBlockGasCost.Uint64() + targetBlockRate := feeConfig.TargetBlockRate + + tests := []struct { + name string + parentTime uint64 + parentCost *big.Int + timestamp uint64 + expected uint64 + }{ + { + name: "normal", + parentTime: 10, + parentCost: maxBlockGasCostBig, + timestamp: 10 + targetBlockRate + 1, + expected: maxBlockGasCost - blockGasCostStep, + }, + { + name: "negative_time_elapsed", + parentTime: 10, + parentCost: feeConfig.MinBlockGasCost, + timestamp: 9, + expected: minBlockGasCost + blockGasCostStep*targetBlockRate, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parent := &types.Header{ + Time: test.parentTime, + BlockGasCost: test.parentCost, + } + + assert.Equal(t, test.expected, BlockGasCost( + feeConfig, + parent, + test.timestamp, + )) + }) + } +} + +func TestBlockGasCostWithStep(t *testing.T) { + t.Run("normal", func(t *testing.T) { + BlockGasCostWithStepTest(t, testFeeConfig) + }) + t.Run("double", func(t *testing.T) { + BlockGasCostWithStepTest(t, testFeeConfigDouble) + }) +} + +func BlockGasCostWithStepTest(t *testing.T, feeConfig commontype.FeeConfig) { + minBlockGasCost := feeConfig.MinBlockGasCost.Uint64() + blockGasCostStep := feeConfig.BlockGasCostStep.Uint64() + targetBlockRate := feeConfig.TargetBlockRate + bigMaxBlockGasCost := feeConfig.MaxBlockGasCost + maxBlockGasCost := bigMaxBlockGasCost.Uint64() + tests := []struct { + name string + parentCost *big.Int + timeElapsed uint64 + expected uint64 + }{ + { + name: "nil_parentCost", + parentCost: nil, + timeElapsed: 0, + expected: minBlockGasCost, + }, + { + name: "timeElapsed_0", + parentCost: big.NewInt(0), + timeElapsed: 0, + expected: targetBlockRate * blockGasCostStep, + }, + { + name: "timeElapsed_1", + parentCost: big.NewInt(0), + timeElapsed: 1, + expected: (targetBlockRate - 1) * blockGasCostStep, + }, + { + name: "timeElapsed_0_with_parentCost", + parentCost: big.NewInt(50_000), + timeElapsed: 0, + expected: 50_000 + targetBlockRate*blockGasCostStep, + }, + { + name: "timeElapsed_0_with_max_parentCost", + parentCost: bigMaxBlockGasCost, + timeElapsed: 0, + expected: maxBlockGasCost, + }, + { + name: "timeElapsed_1_with_max_parentCost", + parentCost: bigMaxBlockGasCost, + timeElapsed: 1, + expected: maxBlockGasCost, + }, + { + name: "timeElapsed_at_target", + parentCost: big.NewInt(900_000), + timeElapsed: targetBlockRate, + expected: 900_000, + }, + { + name: "timeElapsed_over_target_1", + parentCost: bigMaxBlockGasCost, + timeElapsed: targetBlockRate + 1, + expected: maxBlockGasCost - blockGasCostStep, + }, + { + name: "timeElapsed_over_target_10", + parentCost: bigMaxBlockGasCost, + timeElapsed: targetBlockRate + 10, + expected: maxBlockGasCost - 10*blockGasCostStep, + }, + { + name: "timeElapsed_over_target_15", + parentCost: bigMaxBlockGasCost, + timeElapsed: targetBlockRate + 15, + expected: maxBlockGasCost - 15*blockGasCostStep, + }, + { + name: "timeElapsed_large_clamped_to_0", + parentCost: bigMaxBlockGasCost, + timeElapsed: targetBlockRate + 100, + expected: minBlockGasCost, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expected, BlockGasCostWithStep( + feeConfig, + test.parentCost, + blockGasCostStep, + test.timeElapsed, + )) + }) + } +} + +func TestEstimateRequiredTip(t *testing.T) { + tests := []struct { + name string + subnetEVMTimestamp *uint64 + header *types.Header + want *big.Int + wantErr error + }{ + { + name: "not_subnet_evm", + subnetEVMTimestamp: utils.NewUint64(1), + header: &types.Header{}, + }, + { + name: "nil_base_fee", + subnetEVMTimestamp: utils.NewUint64(0), + header: &types.Header{ + BlockGasCost: big.NewInt(1), + }, + wantErr: errBaseFeeNil, + }, + { + name: "nil_block_gas_cost", + subnetEVMTimestamp: utils.NewUint64(0), + header: &types.Header{ + BaseFee: big.NewInt(1), + }, + wantErr: errBlockGasCostNil, + }, + { + name: "success", + subnetEVMTimestamp: utils.NewUint64(0), + header: &types.Header{ + GasUsed: 123, + BaseFee: big.NewInt(456), + BlockGasCost: big.NewInt(101112), + }, + // totalRequiredTips = BlockGasCost * BaseFee + // estimatedTip = totalRequiredTips / GasUsed + want: big.NewInt((101112 * 456) / (123)), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + config := ¶ms.ChainConfig{ + NetworkUpgrades: params.NetworkUpgrades{ + SubnetEVMTimestamp: test.subnetEVMTimestamp, + }, + } + requiredTip, err := EstimateRequiredTip(config, test.header) + require.ErrorIs(err, test.wantErr) + require.Equal(test.want, requiredTip) + }) + } +} diff --git a/plugin/evm/header/dynamic_fee_windower.go b/plugin/evm/header/dynamic_fee_windower.go new file mode 100644 index 0000000000..393537b636 --- /dev/null +++ b/plugin/evm/header/dynamic_fee_windower.go @@ -0,0 +1,196 @@ +// (c) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/subnetevm" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +// FeeWindowSize is the number of bytes that are used to encode the dynamic fee +// window in the header's Extra field after the SubnetEVM activation. +const FeeWindowSize = wrappers.LongLen * subnetevm.WindowLen + +var ( + maxUint256Plus1 = new(big.Int).Lsh(common.Big1, 256) + maxUint256 = new(big.Int).Sub(maxUint256Plus1, common.Big1) + + errInvalidTimestamp = errors.New("invalid timestamp") + errDynamicFeeWindowInsufficientLength = errors.New("insufficient length for dynamic fee window") +) + +// baseFeeFromWindow should only be called if `timestamp` >= `config.SubnetEVMTimestamp` +func baseFeeFromWindow(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) (*big.Int, error) { + // If the current block is the first EIP-1559 block, or it is the genesis block + // return the initial slice and initial base fee. + if !config.IsSubnetEVM(parent.Time) || parent.Number.Cmp(common.Big0) == 0 { + return big.NewInt(feeConfig.MinBaseFee.Int64()), nil + } + + dynamicFeeWindow, err := feeWindow(config, parent, timestamp) + if err != nil { + return nil, err + } + + // Calculate the amount of gas consumed within the rollup window. + var ( + baseFee = new(big.Int).Set(parent.BaseFee) + baseFeeChangeDenominator = feeConfig.BaseFeeChangeDenominator + parentGasTarget = feeConfig.TargetGas.Uint64() + totalGas = dynamicFeeWindow.Sum() + ) + + if totalGas == parentGasTarget { + // If the parent block used exactly its target gas, the baseFee stays + // the same. + // + // For legacy reasons, this is true even if the baseFee would have + // otherwise been clamped to a different range. + return baseFee, nil + } + + var ( + num = new(big.Int) + parentGasTargetBig = new(big.Int).SetUint64(parentGasTarget) + ) + if totalGas > parentGasTarget { + // If the parent block used more gas than its target, the baseFee should increase. + num.SetUint64(totalGas - parentGasTarget) + num.Mul(num, parent.BaseFee) + num.Div(num, parentGasTargetBig) + num.Div(num, baseFeeChangeDenominator) + baseFeeDelta := math.BigMax(num, common.Big1) + + baseFee.Add(baseFee, baseFeeDelta) + } else { + // Otherwise if the parent block used less gas than its target, the baseFee should decrease. + num.SetUint64(parentGasTarget - totalGas) + num.Mul(num, parent.BaseFee) + num.Div(num, parentGasTargetBig) + num.Div(num, baseFeeChangeDenominator) + baseFeeDelta := math.BigMax(num, common.Big1) + + if timestamp < parent.Time { + // This should never happen as the fee window calculations should + // have already failed, but it is kept for clarity. + return nil, fmt.Errorf("cannot calculate base fee for timestamp %d prior to parent timestamp %d", + timestamp, + parent.Time, + ) + } + + // If timeElapsed is greater than [subnetevm.WindowLen], apply the state + // transition to the base fee to account for the interval during which + // no blocks were produced. + // + // We use timeElapsed/[subnetevm.WindowLen], so that the transition is applied + // for every [subnetevm.WindowLen] seconds that has elapsed between the parent + // and this block. + var ( + timeElapsed = timestamp - parent.Time + windowsElapsed = timeElapsed / subnetevm.WindowLen + ) + if windowsElapsed > 1 { + bigWindowsElapsed := new(big.Int).SetUint64(windowsElapsed) + // Because baseFeeDelta could actually be [common.Big1], we must not + // modify the existing value of `baseFeeDelta` but instead allocate + // a new one. + baseFeeDelta = new(big.Int).Mul(baseFeeDelta, bigWindowsElapsed) + } + baseFee.Sub(baseFee, baseFeeDelta) + } + + // Ensure that the base fee does not increase/decrease outside of the bounds + baseFee = selectBigWithinBounds(feeConfig.MinBaseFee, baseFee, maxUint256) + + return baseFee, nil +} + +// feeWindow takes the previous header and the timestamp of its child block and +// calculates the expected fee window. +// +// feeWindow should only be called if timestamp >= config.SubnetEVMTimestamp +func feeWindow( + config *params.ChainConfig, + parent *types.Header, + timestamp uint64, +) (subnetevm.Window, error) { + // If the current block is the first EIP-1559 block, or it is the genesis block + // return the initial window. + if !config.IsSubnetEVM(parent.Time) || parent.Number.Cmp(common.Big0) == 0 { + return subnetevm.Window{}, nil + } + + dynamicFeeWindow, err := parseFeeWindow(parent.Extra) + if err != nil { + return subnetevm.Window{}, err + } + + if timestamp < parent.Time { + return subnetevm.Window{}, fmt.Errorf("%w: timestamp %d prior to parent timestamp %d", + errInvalidTimestamp, + timestamp, + parent.Time, + ) + } + timeElapsed := timestamp - parent.Time + + // Compute the new state of the gas rolling window. + dynamicFeeWindow.Add(parent.GasUsed) + + // roll the window over by the timeElapsed to generate the new rollup + // window. + dynamicFeeWindow.Shift(timeElapsed) + return dynamicFeeWindow, nil +} + +// selectBigWithinBounds returns [value] if it is within the bounds: +// lowerBound <= value <= upperBound or the bound at either end if [value] +// is outside of the defined boundaries. +func selectBigWithinBounds(lowerBound, value, upperBound *big.Int) *big.Int { + switch { + case lowerBound != nil && value.Cmp(lowerBound) < 0: + return new(big.Int).Set(lowerBound) + case upperBound != nil && value.Cmp(upperBound) > 0: + return new(big.Int).Set(upperBound) + default: + return value + } +} + +func parseFeeWindow(bytes []byte) (subnetevm.Window, error) { + if len(bytes) < FeeWindowSize { + return subnetevm.Window{}, fmt.Errorf("%w: expected at least %d bytes but got %d bytes", + errDynamicFeeWindowInsufficientLength, + FeeWindowSize, + len(bytes), + ) + } + + var window subnetevm.Window + for i := range window { + offset := i * wrappers.LongLen + window[i] = binary.BigEndian.Uint64(bytes[offset:]) + } + return window, nil +} + +func feeWindowBytes(w subnetevm.Window) []byte { + bytes := make([]byte, FeeWindowSize) + for i, v := range w { + offset := i * wrappers.LongLen + binary.BigEndian.PutUint64(bytes[offset:], v) + } + return bytes +} diff --git a/plugin/evm/header/dynamic_fee_windower_test.go b/plugin/evm/header/dynamic_fee_windower_test.go new file mode 100644 index 0000000000..bf502d7f36 --- /dev/null +++ b/plugin/evm/header/dynamic_fee_windower_test.go @@ -0,0 +1,130 @@ +// (c) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/subnetevm" + "github.com/stretchr/testify/require" +) + +func TestSelectBigWithinBounds(t *testing.T) { + type test struct { + lower, value, upper, expected *big.Int + } + + tests := map[string]test{ + "value within bounds": { + lower: big.NewInt(0), + value: big.NewInt(5), + upper: big.NewInt(10), + expected: big.NewInt(5), + }, + "value below lower bound": { + lower: big.NewInt(0), + value: big.NewInt(-1), + upper: big.NewInt(10), + expected: big.NewInt(0), + }, + "value above upper bound": { + lower: big.NewInt(0), + value: big.NewInt(11), + upper: big.NewInt(10), + expected: big.NewInt(10), + }, + "value matches lower bound": { + lower: big.NewInt(0), + value: big.NewInt(0), + upper: big.NewInt(10), + expected: big.NewInt(0), + }, + "value matches upper bound": { + lower: big.NewInt(0), + value: big.NewInt(10), + upper: big.NewInt(10), + expected: big.NewInt(10), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + v := selectBigWithinBounds(test.lower, test.value, test.upper) + if v.Cmp(test.expected) != 0 { + t.Fatalf("Expected (%d), found (%d)", test.expected, v) + } + }) + } +} + +func TestParseDynamicFeeWindow(t *testing.T) { + tests := []struct { + name string + bytes []byte + window subnetevm.Window + parseErr error + }{ + { + name: "insufficient_length", + bytes: make([]byte, FeeWindowSize-1), + parseErr: errDynamicFeeWindowInsufficientLength, + }, + { + name: "zero_window", + bytes: make([]byte, FeeWindowSize), + window: subnetevm.Window{}, + }, + { + name: "truncate_bytes", + bytes: []byte{ + FeeWindowSize: 1, + }, + window: subnetevm.Window{}, + }, + { + name: "endianess", + bytes: []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + }, + window: subnetevm.Window{ + 0x0102030405060708, + 0x1112131415161718, + 0x2122232425262728, + 0x3132333435363738, + 0x4142434445464748, + 0x5152535455565758, + 0x6162636465666768, + 0x7172737475767778, + 0x8182838485868788, + 0x9192939495969798, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + window, err := parseFeeWindow(test.bytes) + require.Equal(test.window, window) + require.ErrorIs(err, test.parseErr) + if test.parseErr != nil { + return + } + + expectedBytes := test.bytes[:FeeWindowSize] + bytes := feeWindowBytes(window) + require.Equal(expectedBytes, bytes) + }) + } +} diff --git a/plugin/evm/header/extra.go b/plugin/evm/header/extra.go index f8c7ab3299..4dc5414845 100644 --- a/plugin/evm/header/extra.go +++ b/plugin/evm/header/extra.go @@ -7,31 +7,52 @@ import ( "errors" "fmt" + "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" ) var errInvalidExtraLength = errors.New("invalid header.Extra length") +// ExtraPrefix takes the previous header and the timestamp of its child +// block and calculates the expected extra prefix for the child block. +func ExtraPrefix( + config *params.ChainConfig, + parent *types.Header, + timestamp uint64, +) ([]byte, error) { + switch { + case config.IsSubnetEVM(timestamp): + window, err := feeWindow(config, parent, timestamp) + if err != nil { + return nil, fmt.Errorf("failed to calculate fee window: %w", err) + } + return feeWindowBytes(window), nil + default: + // Prior to SubnetEVM there was no expected extra prefix. + return nil, nil + } +} + // VerifyExtra verifies that the header's Extra field is correctly formatted for // [rules]. func VerifyExtra(rules params.AvalancheRules, extra []byte) error { extraLen := len(extra) switch { case rules.IsDurango: - if extraLen < params.DynamicFeeExtraDataSize { + if extraLen < FeeWindowSize { return fmt.Errorf( "%w: expected >= %d but got %d", errInvalidExtraLength, - params.DynamicFeeExtraDataSize, + FeeWindowSize, extraLen, ) } case rules.IsSubnetEVM: - if extraLen != params.DynamicFeeExtraDataSize { + if extraLen != FeeWindowSize { return fmt.Errorf( "%w: expected %d but got %d", errInvalidExtraLength, - params.DynamicFeeExtraDataSize, + FeeWindowSize, extraLen, ) } diff --git a/plugin/evm/header/extra_test.go b/plugin/evm/header/extra_test.go index 41253f9d62..56f43e4557 100644 --- a/plugin/evm/header/extra_test.go +++ b/plugin/evm/header/extra_test.go @@ -4,12 +4,110 @@ package header import ( + "math/big" "testing" + "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/subnetevm" + "github.com/ava-labs/subnet-evm/utils" "github.com/stretchr/testify/require" ) +const ( + targetGas = 10_000_000 + blockGas = 1_000_000 +) + +func TestExtraPrefix(t *testing.T) { + tests := []struct { + name string + upgrades params.NetworkUpgrades + parent *types.Header + timestamp uint64 + want []byte + wantErr error + }{ + { + name: "pre_subnet_evm", + upgrades: params.TestPreSubnetEVMChainConfig.NetworkUpgrades, + want: nil, + wantErr: nil, + }, + { + name: "subnet_evm_first_block", + upgrades: params.NetworkUpgrades{ + SubnetEVMTimestamp: utils.NewUint64(1), + }, + parent: &types.Header{ + Number: big.NewInt(1), + }, + timestamp: 1, + want: feeWindowBytes(subnetevm.Window{}), + }, + { + name: "subnet_evm_genesis_block", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(0), + }, + want: feeWindowBytes(subnetevm.Window{}), + }, + { + name: "subnet_evm_invalid_fee_window", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + }, + wantErr: errDynamicFeeWindowInsufficientLength, + }, + { + name: "subnet_evm_invalid_timestamp", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + Time: 1, + Extra: feeWindowBytes(subnetevm.Window{}), + }, + timestamp: 0, + wantErr: errInvalidTimestamp, + }, + { + name: "subnet_evm_normal", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + Number: big.NewInt(1), + GasUsed: targetGas, + Extra: feeWindowBytes(subnetevm.Window{ + 1, 2, 3, 4, + }), + BlockGasCost: big.NewInt(blockGas), + }, + timestamp: 1, + want: func() []byte { + window := subnetevm.Window{ + 1, 2, 3, 4, + } + window.Add(targetGas) + window.Shift(1) + return feeWindowBytes(window) + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + config := ¶ms.ChainConfig{ + NetworkUpgrades: test.upgrades, + } + got, err := ExtraPrefix(config, test.parent, test.timestamp) + require.ErrorIs(err, test.wantErr) + require.Equal(test.want, got) + }) + } +} + func TestVerifyExtra(t *testing.T) { tests := []struct { name string @@ -34,7 +132,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsSubnetEVM: true, }, - extra: make([]byte, params.DynamicFeeExtraDataSize), + extra: make([]byte, FeeWindowSize), expected: nil, }, { @@ -42,7 +140,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsSubnetEVM: true, }, - extra: make([]byte, params.DynamicFeeExtraDataSize-1), + extra: make([]byte, FeeWindowSize-1), expected: errInvalidExtraLength, }, { @@ -50,7 +148,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsSubnetEVM: true, }, - extra: make([]byte, params.DynamicFeeExtraDataSize+1), + extra: make([]byte, FeeWindowSize+1), expected: errInvalidExtraLength, }, { @@ -58,7 +156,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsDurango: true, }, - extra: make([]byte, params.DynamicFeeExtraDataSize), + extra: make([]byte, FeeWindowSize), expected: nil, }, { @@ -66,7 +164,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsDurango: true, }, - extra: make([]byte, params.DynamicFeeExtraDataSize+1), + extra: make([]byte, FeeWindowSize+1), expected: nil, }, { @@ -74,7 +172,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsDurango: true, }, - extra: make([]byte, params.DynamicFeeExtraDataSize-1), + extra: make([]byte, FeeWindowSize-1), expected: errInvalidExtraLength, }, } diff --git a/plugin/evm/header/gas_limit.go b/plugin/evm/header/gas_limit.go new file mode 100644 index 0000000000..4449f97bb7 --- /dev/null +++ b/plugin/evm/header/gas_limit.go @@ -0,0 +1,81 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "errors" + "fmt" + + "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" +) + +var errInvalidGasLimit = errors.New("invalid gas limit") + +type CalculateGasLimitFunc func(parentGasUsed, parentGasLimit, gasFloor, gasCeil uint64) uint64 + +// GasLimit takes the previous header and the timestamp of its child block and +// calculates the gas limit for the child block. +func GasLimit( + config *params.ChainConfig, + feeConfig commontype.FeeConfig, + parent *types.Header, + calcGasLimit CalculateGasLimitFunc, + timestamp uint64, +) uint64 { + switch { + case config.IsSubnetEVM(timestamp): + return feeConfig.GasLimit.Uint64() + default: + // This is calculated with core.CalcGasLimit(parent.GasUsed(), parent.GasLimit(), parent.GasLimit(), parent.GasLimit()) + // however importing core package here would create a circular dependency. + // This can be fixed once we transition to libevm. + // return calcGasLimit(parent.GasUsed, parent.GasLimit, parent.GasLimit, parent.GasLimit) + return parent.GasLimit + } +} + +// VerifyGasLimit verifies that the gas limit for the header is valid. +func VerifyGasLimit( + config *params.ChainConfig, + feeConfig commontype.FeeConfig, + parent *types.Header, + header *types.Header, +) error { + switch { + case config.IsSubnetEVM(header.Time): + expectedGasLimit := feeConfig.GasLimit.Uint64() + if header.GasLimit != expectedGasLimit { + return fmt.Errorf("%w: expected to be %d in Subnet-EVM, but found %d", + errInvalidGasLimit, + expectedGasLimit, + header.GasLimit, + ) + } + default: + if header.GasLimit < params.MinGasLimit || header.GasLimit > params.MaxGasLimit { + return fmt.Errorf("%w: %d not in range [%d, %d]", + errInvalidGasLimit, + header.GasLimit, + params.MinGasLimit, + params.MaxGasLimit, + ) + } + + // Verify that the gas limit remains within allowed bounds + diff := math.AbsDiff(parent.GasLimit, header.GasLimit) + limit := parent.GasLimit / params.GasLimitBoundDivisor + if diff >= limit { + return fmt.Errorf("%w: have %d, want %d += %d", + errInvalidGasLimit, + header.GasLimit, + parent.GasLimit, + limit, + ) + } + } + return nil +} diff --git a/plugin/evm/header/gas_limit_test.go b/plugin/evm/header/gas_limit_test.go new file mode 100644 index 0000000000..6ad68b42e1 --- /dev/null +++ b/plugin/evm/header/gas_limit_test.go @@ -0,0 +1,146 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package header + +import ( + "testing" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" + "github.com/stretchr/testify/require" +) + +func TestGasLimit(t *testing.T) { + t.Run("normal", func(t *testing.T) { + GasLimitTest(t, testFeeConfig) + }) + t.Run("double", func(t *testing.T) { + GasLimitTest(t, testFeeConfigDouble) + }) +} + +func GasLimitTest(t *testing.T, feeConfig commontype.FeeConfig) { + tests := []struct { + name string + upgrades params.NetworkUpgrades + calcGas CalculateGasLimitFunc + parent *types.Header + timestamp uint64 + want uint64 + }{ + { + name: "subnet_evm", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + want: feeConfig.GasLimit.Uint64(), + }, + { + name: "pre_subnet_evm", + upgrades: params.TestPreSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + GasLimit: 1, + }, + calcGas: func(parentGasUsed, parentGasLimit, gasFloor, gasCeil uint64) uint64 { + return 1 + }, + want: 1, // Same as parent + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := ¶ms.ChainConfig{ + NetworkUpgrades: test.upgrades, + } + got := GasLimit(config, feeConfig, test.parent, test.calcGas, test.timestamp) + require.Equal(t, test.want, got) + }) + } +} + +func TestVerifyGasLimit(t *testing.T) { + t.Run("normal", func(t *testing.T) { + VerifyGasLimitTest(t, testFeeConfig) + }) + t.Run("double", func(t *testing.T) { + VerifyGasLimitTest(t, testFeeConfigDouble) + }) +} + +func VerifyGasLimitTest(t *testing.T, feeConfig commontype.FeeConfig) { + tests := []struct { + name string + upgrades params.NetworkUpgrades + parent *types.Header + header *types.Header + want error + }{ + { + name: "subnet_evm_valid", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + header: &types.Header{ + GasLimit: feeConfig.GasLimit.Uint64(), + }, + }, + { + name: "subnet_evm_invalid", + upgrades: params.TestSubnetEVMChainConfig.NetworkUpgrades, + header: &types.Header{ + GasLimit: feeConfig.GasLimit.Uint64() + 1, + }, + want: errInvalidGasLimit, + }, + { + name: "pre_subnet_evm_valid", + upgrades: params.TestPreSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + GasLimit: 50_000, + }, + header: &types.Header{ + GasLimit: 50_001, // Gas limit is allowed to change by 1/1024 + }, + }, + { + name: "pre_subnet_evm_too_low", + upgrades: params.TestPreSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + GasLimit: params.MinGasLimit, + }, + header: &types.Header{ + GasLimit: params.MinGasLimit - 1, + }, + want: errInvalidGasLimit, + }, + { + name: "pre_subnet_evm_too_high", + upgrades: params.TestPreSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + GasLimit: params.MaxGasLimit, + }, + header: &types.Header{ + GasLimit: params.MaxGasLimit + 1, + }, + want: errInvalidGasLimit, + }, + { + name: "pre_subnet_evm_too_large", + upgrades: params.TestPreSubnetEVMChainConfig.NetworkUpgrades, + parent: &types.Header{ + GasLimit: params.MinGasLimit, + }, + header: &types.Header{ + GasLimit: params.MaxGasLimit, + }, + want: errInvalidGasLimit, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := ¶ms.ChainConfig{ + NetworkUpgrades: test.upgrades, + } + err := VerifyGasLimit(config, feeConfig, test.parent, test.header) + require.ErrorIs(t, err, test.want) + }) + } +} diff --git a/plugin/evm/upgrade/legacy/params.go b/plugin/evm/upgrade/legacy/params.go new file mode 100644 index 0000000000..0a4a985ced --- /dev/null +++ b/plugin/evm/upgrade/legacy/params.go @@ -0,0 +1,7 @@ +package legacy + +const ( + BaseFee = 225_000_000_000 + + GasLimit = 8_000_000 +) diff --git a/plugin/evm/upgrade/subnetevm/window.go b/plugin/evm/upgrade/subnetevm/window.go new file mode 100644 index 0000000000..eaaa97d70e --- /dev/null +++ b/plugin/evm/upgrade/subnetevm/window.go @@ -0,0 +1,60 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// subnetevm defines the dynamic fee window used after subnetevm upgrade. +package subnetevm + +import ( + "math" + + safemath "github.com/ethereum/go-ethereum/common/math" +) + +const ( + // WindowLen is the number of seconds of gas consumption to track. + WindowLen = 10 +) + +// Window is a window of the last [WindowLen] seconds of gas usage. +// +// Index 0 is the oldest entry, and [WindowLen]-1 is the current entry. +type Window [WindowLen]uint64 + +// Add adds the amounts to the most recent entry in the window. +// +// If the most recent entry overflows, it is set to [math.MaxUint64]. +func (w *Window) Add(amounts ...uint64) { + const lastIndex uint = WindowLen - 1 + w[lastIndex] = add(w[lastIndex], amounts...) +} + +// Shift removes the oldest n entries from the window and adds n new empty +// entries. +func (w *Window) Shift(n uint64) { + if n >= WindowLen { + *w = Window{} + return + } + + var newWindow Window + copy(newWindow[:], w[n:]) + *w = newWindow +} + +// Sum returns the sum of all the entries in the window. +// +// If the sum overflows, [math.MaxUint64] is returned. +func (w *Window) Sum() uint64 { + return add(0, w[:]...) +} + +func add(sum uint64, values ...uint64) uint64 { + var overflow bool + for _, v := range values { + sum, overflow = safemath.SafeAdd(sum, v) + if overflow { + return math.MaxUint64 + } + } + return sum +} diff --git a/plugin/evm/upgrade/subnetevm/window_test.go b/plugin/evm/upgrade/subnetevm/window_test.go new file mode 100644 index 0000000000..839010c21b --- /dev/null +++ b/plugin/evm/upgrade/subnetevm/window_test.go @@ -0,0 +1,175 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package subnetevm + +import ( + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/stretchr/testify/require" +) + +func TestWindow_Add(t *testing.T) { + tests := []struct { + name string + window Window + amount uint64 + expected Window + }{ + { + name: "normal_addition", + window: Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + }, + amount: 5, + expected: Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, + }, + }, + { + name: "amount_overflow", + window: Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + }, + amount: math.MaxUint64, + expected: Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, math.MaxUint64, + }, + }, + { + name: "window_overflow", + window: Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, math.MaxUint64, + }, + amount: 5, + expected: Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, math.MaxUint64, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.window.Add(test.amount) + require.Equal(t, test.expected, test.window) + }) + } +} + +func TestWindow_Shift(t *testing.T) { + window := Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + } + tests := []struct { + n uint64 + expected Window + }{ + { + n: 0, + expected: window, + }, + { + n: 1, + expected: Window{ + 2, 3, 4, 5, 6, 7, 8, 9, 10, + }, + }, + { + n: 2, + expected: Window{ + 3, 4, 5, 6, 7, 8, 9, 10, + }, + }, + { + n: 3, + expected: Window{ + 4, 5, 6, 7, 8, 9, 10, + }, + }, + { + n: 4, + expected: Window{ + 5, 6, 7, 8, 9, 10, + }, + }, + { + n: 5, + expected: Window{ + 6, 7, 8, 9, 10, + }, + }, + { + n: 6, + expected: Window{ + 7, 8, 9, 10, + }, + }, + { + n: 7, + expected: Window{ + 8, 9, 10, + }, + }, + { + n: 8, + expected: Window{ + 9, 10, + }, + }, + { + n: 9, + expected: Window{ + 10, + }, + }, + { + n: 10, + expected: Window{}, + }, + { + n: 100, + expected: Window{}, + }, + } + for _, test := range tests { + t.Run(strconv.FormatUint(test.n, 10), func(t *testing.T) { + window := window + window.Shift(test.n) + require.Equal(t, test.expected, window) + }) + } +} + +func TestWindow_Sum(t *testing.T) { + tests := []struct { + name string + window Window + expected uint64 + }{ + { + name: "empty", + window: Window{}, + expected: 0, + }, + { + name: "full", + window: Window{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + }, + expected: 55, + }, + { + name: "overflow", + window: Window{ + math.MaxUint64, 2, 3, 4, 5, 6, 7, 8, 9, 10, + }, + expected: math.MaxUint64, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expected, test.window.Sum()) + }) + } +} diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 8815e50ce1..53488ded64 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -39,7 +39,6 @@ import ( accountKeystore "github.com/ava-labs/subnet-evm/accounts/keystore" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/txpool" @@ -48,6 +47,7 @@ import ( "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/plugin/evm/config" + "github.com/ava-labs/subnet-evm/plugin/evm/header" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" @@ -98,7 +98,7 @@ var ( genesisJSONDurango = genesisJSON(params.TestDurangoChainConfig) genesisJSONEtna = genesisJSON(params.TestEtnaChainConfig) genesisJSONFUpgrade = genesisJSON(params.TestFUpgradeChainConfig) - genesisJSONLatest = genesisJSONEtna + genesisJSONLatest = genesisJSONFUpgrade ) func init() { @@ -1991,11 +1991,11 @@ func TestBuildSubnetEVMBlock(t *testing.T) { if ethBlk.BlockGasCost() == nil || ethBlk.BlockGasCost().Cmp(big.NewInt(100)) < 0 { t.Fatalf("expected blockGasCost to be at least 100 but got %d", ethBlk.BlockGasCost()) } - minRequiredTip, err := dummy.MinRequiredTip(vm.chainConfig, ethBlk.Header()) + minRequiredTip, err := header.EstimateRequiredTip(vm.chainConfig, ethBlk.Header()) if err != nil { t.Fatal(err) } - if minRequiredTip == nil || minRequiredTip.Cmp(big.NewInt(0.05*params.GWei)) < 0 { + if minRequiredTip == nil || minRequiredTip.Cmp(big.NewInt(0.05*utils.GWei)) < 0 { t.Fatalf("expected minRequiredTip to be at least 0.05 gwei but got %d", minRequiredTip) } diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 03e30932a9..8431a87ac9 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -340,7 +340,7 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned require.NoError(err) createTx, err := types.SignTx( - types.NewContractCreation(0, common.Big0, 7_000_000, big.NewInt(225*params.GWei), common.Hex2Bytes(exampleWarpBin)), + types.NewContractCreation(0, common.Big0, 7_000_000, big.NewInt(225*utils.GWei), common.Hex2Bytes(exampleWarpBin)), types.LatestSignerForChainID(vm.chainConfig.ChainID), testKeys[0], ) @@ -353,8 +353,8 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned 1, &exampleWarpAddress, 1_000_000, - big.NewInt(225*params.GWei), - big.NewInt(params.GWei), + big.NewInt(225*utils.GWei), + big.NewInt(utils.GWei), common.Big0, txPayload, types.AccessList{}, @@ -639,8 +639,8 @@ func testReceiveWarpMessage( vm.txPool.Nonce(testEthAddrs[0]), &warpcontract.Module.Address, 1_000_000, - big.NewInt(225*params.GWei), - big.NewInt(params.GWei), + big.NewInt(225*utils.GWei), + big.NewInt(utils.GWei), common.Big0, getWarpMsgInput, types.AccessList{}, diff --git a/predicate/predicate_bytes.go b/predicate/predicate_bytes.go index c31cc1f507..138b33ad2a 100644 --- a/predicate/predicate_bytes.go +++ b/predicate/predicate_bytes.go @@ -6,7 +6,7 @@ package predicate import ( "fmt" - "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/header" "github.com/ethereum/go-ethereum/common" ) @@ -57,8 +57,8 @@ 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) <= params.DynamicFeeExtraDataSize { + if len(extraData) <= header.FeeWindowSize { return nil, false } - return extraData[params.DynamicFeeExtraDataSize:], true + return extraData[header.FeeWindowSize:], true } diff --git a/predicate/predicate_bytes_test.go b/predicate/predicate_bytes_test.go index af19a1ac96..47c6f0bfff 100644 --- a/predicate/predicate_bytes_test.go +++ b/predicate/predicate_bytes_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/header" "github.com/stretchr/testify/require" ) @@ -52,15 +52,15 @@ func TestUnpackInvalidPredicate(t *testing.T) { func TestPredicateResultsBytes(t *testing.T) { require := require.New(t) - dataTooShort := utils.RandomBytes(params.DynamicFeeExtraDataSize - 1) + dataTooShort := utils.RandomBytes(header.FeeWindowSize - 1) _, ok := GetPredicateResultBytes(dataTooShort) require.False(ok) - preDurangoData := utils.RandomBytes(params.DynamicFeeExtraDataSize) + preDurangoData := utils.RandomBytes(header.FeeWindowSize) _, ok = GetPredicateResultBytes(preDurangoData) require.False(ok) - postDurangoData := utils.RandomBytes(params.DynamicFeeExtraDataSize + 2) + postDurangoData := utils.RandomBytes(header.FeeWindowSize + 2) resultBytes, ok := GetPredicateResultBytes(postDurangoData) require.True(ok) - require.Equal(resultBytes, postDurangoData[params.DynamicFeeExtraDataSize:]) + require.Equal(resultBytes, postDurangoData[header.FeeWindowSize:]) } diff --git a/tests/utils/proposervm.go b/tests/utils/proposervm.go index d7f2a37530..2b8d9071a1 100644 --- a/tests/utils/proposervm.go +++ b/tests/utils/proposervm.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -41,7 +42,7 @@ func IssueTxsToActivateProposerVMFork( } defer sub.Unsubscribe() - gasPrice := big.NewInt(params.MinGasPrice) + gasPrice := big.NewInt(legacy.BaseFee) txSigner := types.LatestSignerForChainID(chainID) for i := 0; i < numTriggerTxs; i++ { tx := types.NewTransaction( diff --git a/utils/denomination.go b/utils/denomination.go new file mode 100644 index 0000000000..9bf3de605a --- /dev/null +++ b/utils/denomination.go @@ -0,0 +1,10 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package utils + +const ( + Wei = 1 + GWei = 1_000_000_000 * Wei + Ether = 1_000_000_000 * GWei +) diff --git a/utils/numbers.go b/utils/numbers.go index d665b2f8e8..f8761b0658 100644 --- a/utils/numbers.go +++ b/utils/numbers.go @@ -37,3 +37,28 @@ func Uint64PtrEqual(x, y *uint64) bool { } return *x == *y } + +// BigEqual returns true if a is equal to b. If a and b are nil, it returns +// true. +func BigEqual(a, b *big.Int) bool { + if a == nil || b == nil { + return a == b + } + return a.Cmp(b) == 0 +} + +// BigEqualUint64 returns true if a is equal to b. If a is nil or not a uint64, +// it returns false. +func BigEqualUint64(a *big.Int, b uint64) bool { + return a != nil && + a.IsUint64() && + a.Uint64() == b +} + +// BigLessOrEqualUint64 returns true if a is less than or equal to b. If a is +// nil or not a uint64, it returns false. +func BigLessOrEqualUint64(a *big.Int, b uint64) bool { + return a != nil && + a.IsUint64() && + a.Uint64() <= b +} diff --git a/utils/numbers_test.go b/utils/numbers_test.go new file mode 100644 index 0000000000..b50713072f --- /dev/null +++ b/utils/numbers_test.go @@ -0,0 +1,139 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package utils + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBigEqual(t *testing.T) { + tests := []struct { + name string + a *big.Int + b *big.Int + want bool + }{ + { + name: "nil_nil", + a: nil, + b: nil, + want: true, + }, + { + name: "0_nil", + a: big.NewInt(0), + b: nil, + want: false, + }, + { + name: "0_1", + a: big.NewInt(0), + b: big.NewInt(1), + want: false, + }, + { + name: "1_1", + a: big.NewInt(1), + b: big.NewInt(1), + want: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert := assert.New(t) + + assert.Equal(test.want, BigEqual(test.a, test.b)) + assert.Equal(test.want, BigEqual(test.b, test.a)) + }) + } +} + +func TestBigEqualUint64(t *testing.T) { + tests := []struct { + name string + a *big.Int + b uint64 + want bool + }{ + { + name: "nil", + a: nil, + b: 0, + want: false, + }, + { + name: "not_uint64", + a: big.NewInt(-1), + b: 0, + want: false, + }, + { + name: "equal", + a: big.NewInt(1), + b: 1, + want: true, + }, + { + name: "not_equal", + a: big.NewInt(1), + b: 2, + want: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := BigEqualUint64(test.a, test.b) + assert.Equal(t, test.want, got) + }) + } +} + +func TestLessOrEqualUint64(t *testing.T) { + tests := []struct { + name string + a *big.Int + b uint64 + want bool + }{ + { + name: "nil", + a: nil, + b: 0, + want: false, + }, + { + name: "not_uint64", + a: big.NewInt(-1), + b: 0, + want: false, + }, + { + name: "less", + a: big.NewInt(1), + b: 2, + want: true, + }, + { + name: "equal", + a: big.NewInt(1), + b: 1, + want: true, + }, + { + name: "greater", + a: big.NewInt(2), + b: 1, + want: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := BigLessOrEqualUint64(test.a, test.b) + assert.Equal(t, test.want, got) + }) + } +}