github.com/MetalBlockchain/subnet-evm@v0.4.9/eth/gasprice/gasprice_test.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2020 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  package gasprice
    28  
    29  import (
    30  	"context"
    31  	"math/big"
    32  	"testing"
    33  	"time"
    34  
    35  	"github.com/MetalBlockchain/subnet-evm/commontype"
    36  	"github.com/MetalBlockchain/subnet-evm/consensus/dummy"
    37  	"github.com/MetalBlockchain/subnet-evm/core"
    38  	"github.com/MetalBlockchain/subnet-evm/core/rawdb"
    39  	"github.com/MetalBlockchain/subnet-evm/core/types"
    40  	"github.com/MetalBlockchain/subnet-evm/core/vm"
    41  	"github.com/MetalBlockchain/subnet-evm/ethdb"
    42  	"github.com/MetalBlockchain/subnet-evm/params"
    43  	"github.com/MetalBlockchain/subnet-evm/precompile"
    44  	"github.com/MetalBlockchain/subnet-evm/rpc"
    45  	"github.com/ethereum/go-ethereum/common"
    46  	"github.com/ethereum/go-ethereum/crypto"
    47  	"github.com/ethereum/go-ethereum/event"
    48  	"github.com/stretchr/testify/require"
    49  )
    50  
    51  var (
    52  	key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
    53  	addr   = crypto.PubkeyToAddress(key.PublicKey)
    54  	bal, _ = new(big.Int).SetString("100000000000000000000000", 10)
    55  )
    56  
    57  type testBackend struct {
    58  	db            ethdb.Database
    59  	chain         *core.BlockChain
    60  	acceptedEvent chan<- core.ChainEvent
    61  }
    62  
    63  func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
    64  	if number == rpc.LatestBlockNumber {
    65  		return b.chain.CurrentBlock().Header(), nil
    66  	}
    67  	return b.chain.GetHeaderByNumber(uint64(number)), nil
    68  }
    69  
    70  func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
    71  	if number == rpc.LatestBlockNumber {
    72  		return b.chain.CurrentBlock(), nil
    73  	}
    74  	return b.chain.GetBlockByNumber(uint64(number)), nil
    75  }
    76  
    77  func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
    78  	return b.chain.GetReceiptsByHash(hash), nil
    79  }
    80  
    81  func (b *testBackend) ChainConfig() *params.ChainConfig {
    82  	return b.chain.Config()
    83  }
    84  
    85  func (b *testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
    86  	return nil
    87  }
    88  
    89  func (b *testBackend) SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription {
    90  	b.acceptedEvent = ch
    91  	return nil
    92  }
    93  
    94  func (b *testBackend) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) {
    95  	return b.chain.GetFeeConfigAt(parent)
    96  }
    97  
    98  func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend {
    99  	var gspec = &core.Genesis{
   100  		Config: config,
   101  		Alloc:  core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}},
   102  	}
   103  
   104  	engine := dummy.NewETHFaker()
   105  	db := rawdb.NewMemoryDatabase()
   106  	genesis := gspec.MustCommit(db)
   107  
   108  	// Generate testing blocks
   109  	blocks, _, err := core.GenerateChain(gspec.Config, genesis, engine, db, numBlocks, 0, genBlocks)
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	// Construct testing chain
   114  	diskdb := rawdb.NewMemoryDatabase()
   115  	gspec.Commit(diskdb)
   116  	chain, err := core.NewBlockChain(diskdb, core.DefaultCacheConfig, gspec.Config, engine, vm.Config{}, common.Hash{})
   117  	if err != nil {
   118  		t.Fatalf("Failed to create local chain, %v", err)
   119  	}
   120  	if _, err := chain.InsertChain(blocks); err != nil {
   121  		t.Fatalf("Failed to insert chain, %v", err)
   122  	}
   123  	return &testBackend{chain: chain}
   124  }
   125  
   126  func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend {
   127  	var gspec = &core.Genesis{
   128  		Config: config,
   129  		Alloc:  core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}},
   130  	}
   131  
   132  	engine := dummy.NewFaker()
   133  	db := rawdb.NewMemoryDatabase()
   134  	genesis := gspec.MustCommit(db)
   135  
   136  	// Generate testing blocks
   137  	blocks, _, err := core.GenerateChain(gspec.Config, genesis, engine, db, numBlocks, 1, genBlocks)
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  	// Construct testing chain
   142  	diskdb := rawdb.NewMemoryDatabase()
   143  	gspec.Commit(diskdb)
   144  	chain, err := core.NewBlockChain(diskdb, core.DefaultCacheConfig, gspec.Config, engine, vm.Config{}, common.Hash{})
   145  	if err != nil {
   146  		t.Fatalf("Failed to create local chain, %v", err)
   147  	}
   148  	if _, err := chain.InsertChain(blocks); err != nil {
   149  		t.Fatalf("Failed to insert chain, %v", err)
   150  	}
   151  	return &testBackend{chain: chain, db: db}
   152  }
   153  
   154  func (b *testBackend) MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error) {
   155  	return dummy.MinRequiredTip(b.chain.Config(), header)
   156  }
   157  
   158  func (b *testBackend) CurrentHeader() *types.Header {
   159  	return b.chain.CurrentHeader()
   160  }
   161  
   162  func (b *testBackend) LastAcceptedBlock() *types.Block {
   163  	return b.chain.CurrentBlock()
   164  }
   165  
   166  func (b *testBackend) GetBlockByNumber(number uint64) *types.Block {
   167  	return b.chain.GetBlockByNumber(number)
   168  }
   169  
   170  type suggestTipCapTest struct {
   171  	chainConfig *params.ChainConfig
   172  	numBlocks   int
   173  	genBlock    func(i int, b *core.BlockGen)
   174  	expectedTip *big.Int
   175  }
   176  
   177  func defaultOracleConfig() Config {
   178  	return Config{
   179  		Blocks:             20,
   180  		Percentile:         60,
   181  		MaxLookbackSeconds: 80,
   182  	}
   183  }
   184  
   185  // timeCrunchOracleConfig returns a config with [MaxLookbackSeconds] set to 5
   186  // to ensure that during gas price estimation, we will hit the time based look back limit
   187  func timeCrunchOracleConfig() Config {
   188  	return Config{
   189  		Blocks:             20,
   190  		Percentile:         60,
   191  		MaxLookbackSeconds: 5,
   192  	}
   193  }
   194  
   195  func applyGasPriceTest(t *testing.T, test suggestTipCapTest, config Config) {
   196  	if test.genBlock == nil {
   197  		test.genBlock = func(i int, b *core.BlockGen) {}
   198  	}
   199  	backend := newTestBackend(t, test.chainConfig, test.numBlocks, test.genBlock)
   200  	oracle, err := NewOracle(backend, config)
   201  	require.NoError(t, err)
   202  
   203  	// mock time to be consistent across different CI runs
   204  	// sets currentTime to be 20 seconds
   205  	oracle.clock.Set(time.Unix(20, 0))
   206  
   207  	got, err := oracle.SuggestTipCap(context.Background())
   208  	require.NoError(t, err)
   209  
   210  	if got.Cmp(test.expectedTip) != 0 {
   211  		t.Fatalf("Expected tip (%d), got tip (%d)", test.expectedTip, got)
   212  	}
   213  }
   214  
   215  func testGenBlock(t *testing.T, tip int64, numTx int) func(int, *core.BlockGen) {
   216  	return func(i int, b *core.BlockGen) {
   217  		b.SetCoinbase(common.Address{1})
   218  
   219  		txTip := big.NewInt(tip * params.GWei)
   220  		signer := types.LatestSigner(params.TestChainConfig)
   221  		baseFee := b.BaseFee()
   222  		feeCap := new(big.Int).Add(baseFee, txTip)
   223  		for j := 0; j < numTx; j++ {
   224  			tx := types.NewTx(&types.DynamicFeeTx{
   225  				ChainID:   params.TestChainConfig.ChainID,
   226  				Nonce:     b.TxNonce(addr),
   227  				To:        &common.Address{},
   228  				Gas:       params.TxGas,
   229  				GasFeeCap: feeCap,
   230  				GasTipCap: txTip,
   231  				Data:      []byte{},
   232  			})
   233  			tx, err := types.SignTx(tx, signer, key)
   234  			require.NoError(t, err, "failed to create tx")
   235  			b.AddTx(tx)
   236  		}
   237  	}
   238  }
   239  
   240  func TestSuggestTipCapNetworkUpgrades(t *testing.T) {
   241  	tests := map[string]suggestTipCapTest{
   242  		"subnet evm": {
   243  			chainConfig: params.TestChainConfig,
   244  			expectedTip: DefaultMinPrice,
   245  		},
   246  	}
   247  
   248  	for name, test := range tests {
   249  		t.Run(name, func(t *testing.T) {
   250  			applyGasPriceTest(t, test, defaultOracleConfig())
   251  		})
   252  	}
   253  }
   254  
   255  func TestSuggestTipCap(t *testing.T) {
   256  	applyGasPriceTest(t, suggestTipCapTest{
   257  		chainConfig: params.TestChainConfig,
   258  		numBlocks:   3,
   259  		genBlock:    testGenBlock(t, 55, 370),
   260  		expectedTip: big.NewInt(643_500_643),
   261  	}, defaultOracleConfig())
   262  }
   263  
   264  func TestSuggestTipCapSimpleFloor(t *testing.T) {
   265  	applyGasPriceTest(t, suggestTipCapTest{
   266  		chainConfig: params.TestChainConfig,
   267  		numBlocks:   1,
   268  		genBlock:    testGenBlock(t, 55, 370),
   269  		expectedTip: common.Big0,
   270  	}, defaultOracleConfig())
   271  }
   272  
   273  func TestSuggestTipCapSmallTips(t *testing.T) {
   274  	tip := big.NewInt(550 * params.GWei)
   275  	applyGasPriceTest(t, suggestTipCapTest{
   276  		chainConfig: params.TestChainConfig,
   277  		numBlocks:   3,
   278  		genBlock: func(i int, b *core.BlockGen) {
   279  			b.SetCoinbase(common.Address{1})
   280  
   281  			signer := types.LatestSigner(params.TestChainConfig)
   282  			baseFee := b.BaseFee()
   283  			feeCap := new(big.Int).Add(baseFee, tip)
   284  			for j := 0; j < 185; j++ {
   285  				tx := types.NewTx(&types.DynamicFeeTx{
   286  					ChainID:   params.TestChainConfig.ChainID,
   287  					Nonce:     b.TxNonce(addr),
   288  					To:        &common.Address{},
   289  					Gas:       params.TxGas,
   290  					GasFeeCap: feeCap,
   291  					GasTipCap: tip,
   292  					Data:      []byte{},
   293  				})
   294  				tx, err := types.SignTx(tx, signer, key)
   295  				if err != nil {
   296  					t.Fatalf("failed to create tx: %s", err)
   297  				}
   298  				b.AddTx(tx)
   299  				tx = types.NewTx(&types.DynamicFeeTx{
   300  					ChainID:   params.TestChainConfig.ChainID,
   301  					Nonce:     b.TxNonce(addr),
   302  					To:        &common.Address{},
   303  					Gas:       params.TxGas,
   304  					GasFeeCap: feeCap,
   305  					GasTipCap: common.Big1,
   306  					Data:      []byte{},
   307  				})
   308  				tx, err = types.SignTx(tx, signer, key)
   309  				if err != nil {
   310  					t.Fatalf("failed to create tx: %s", err)
   311  				}
   312  				b.AddTx(tx)
   313  			}
   314  		},
   315  		expectedTip: big.NewInt(643_500_643),
   316  	}, defaultOracleConfig())
   317  }
   318  
   319  func TestSuggestTipCapMinGas(t *testing.T) {
   320  	applyGasPriceTest(t, suggestTipCapTest{
   321  		chainConfig: params.TestChainConfig,
   322  		numBlocks:   3,
   323  		genBlock:    testGenBlock(t, 500, 50),
   324  		expectedTip: big.NewInt(0),
   325  	}, defaultOracleConfig())
   326  }
   327  
   328  // Regression test to ensure that SuggestPrice does not panic prior to activation of Subnet EVM
   329  // Note: support for gas estimation without activated hard forks has been deprecated, but we still
   330  // ensure that the call does not panic.
   331  func TestSuggestGasPricePreSubnetEVM(t *testing.T) {
   332  	config := Config{
   333  		Blocks:     20,
   334  		Percentile: 60,
   335  	}
   336  
   337  	backend := newTestBackend(t, params.TestPreSubnetEVMConfig, 3, func(i int, b *core.BlockGen) {
   338  		b.SetCoinbase(common.Address{1})
   339  
   340  		signer := types.LatestSigner(params.TestPreSubnetEVMConfig)
   341  		gasPrice := big.NewInt(params.MinGasPrice)
   342  		for j := 0; j < 50; j++ {
   343  			tx := types.NewTx(&types.LegacyTx{
   344  				Nonce:    b.TxNonce(addr),
   345  				To:       &common.Address{},
   346  				Gas:      params.TxGas,
   347  				GasPrice: gasPrice,
   348  				Data:     []byte{},
   349  			})
   350  			tx, err := types.SignTx(tx, signer, key)
   351  			if err != nil {
   352  				t.Fatalf("failed to create tx: %s", err)
   353  			}
   354  			b.AddTx(tx)
   355  		}
   356  	})
   357  	oracle, err := NewOracle(backend, config)
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  
   362  	_, err = oracle.SuggestPrice(context.Background())
   363  	if err != nil {
   364  		t.Fatal(err)
   365  	}
   366  }
   367  
   368  // Regression test to ensure that SuggestPrice does not panic prior to activation of SubnetEVM
   369  // Note: support for gas estimation without activated hard forks has been deprecated, but we still
   370  // ensure that the call does not panic.
   371  func TestSuggestGasPricePreAP3(t *testing.T) {
   372  	config := Config{
   373  		Blocks:     20,
   374  		Percentile: 60,
   375  	}
   376  
   377  	backend := newTestBackend(t, params.TestChainConfig, 3, func(i int, b *core.BlockGen) {
   378  		b.SetCoinbase(common.Address{1})
   379  
   380  		signer := types.LatestSigner(params.TestChainConfig)
   381  		gasPrice := big.NewInt(params.MinGasPrice)
   382  		for j := 0; j < 50; j++ {
   383  			tx := types.NewTx(&types.LegacyTx{
   384  				Nonce:    b.TxNonce(addr),
   385  				To:       &common.Address{},
   386  				Gas:      params.TxGas,
   387  				GasPrice: gasPrice,
   388  				Data:     []byte{},
   389  			})
   390  			tx, err := types.SignTx(tx, signer, key)
   391  			if err != nil {
   392  				t.Fatalf("failed to create tx: %s", err)
   393  			}
   394  			b.AddTx(tx)
   395  		}
   396  	})
   397  	oracle, err := NewOracle(backend, config)
   398  	if err != nil {
   399  		t.Fatal(err)
   400  	}
   401  
   402  	_, err = oracle.SuggestPrice(context.Background())
   403  	if err != nil {
   404  		t.Fatal(err)
   405  	}
   406  }
   407  
   408  func TestSuggestTipCapMaxBlocksLookback(t *testing.T) {
   409  	applyGasPriceTest(t, suggestTipCapTest{
   410  		chainConfig: params.TestChainConfig,
   411  		numBlocks:   20,
   412  		genBlock:    testGenBlock(t, 550, 370),
   413  		expectedTip: big.NewInt(5_807_226_110),
   414  	}, defaultOracleConfig())
   415  }
   416  
   417  func TestSuggestTipCapMaxBlocksSecondsLookback(t *testing.T) {
   418  	applyGasPriceTest(t, suggestTipCapTest{
   419  		chainConfig: params.TestChainConfig,
   420  		numBlocks:   20,
   421  		genBlock:    testGenBlock(t, 550, 370),
   422  		expectedTip: big.NewInt(10_384_877_851),
   423  	}, timeCrunchOracleConfig())
   424  }
   425  
   426  // Regression test to ensure the last estimation of base fee is not used
   427  // for the block immediately following a fee configuration update.
   428  func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) {
   429  	require := require.New(t)
   430  	config := Config{
   431  		Blocks:     20,
   432  		Percentile: 60,
   433  	}
   434  
   435  	// create a chain config with fee manager enabled at genesis with [addr] as the admin
   436  	chainConfig := *params.TestChainConfig
   437  	chainConfig.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr}, nil, nil)
   438  
   439  	// create a fee config with higher MinBaseFee and prepare it for inclusion in a tx
   440  	signer := types.LatestSigner(params.TestChainConfig)
   441  	highFeeConfig := chainConfig.FeeConfig
   442  	highFeeConfig.MinBaseFee = big.NewInt(28_000_000_000)
   443  	data, err := precompile.PackSetFeeConfig(highFeeConfig)
   444  	require.NoError(err)
   445  
   446  	// before issuing the block changing the fee into the chain, the fee estimation should
   447  	// follow the fee config in genesis.
   448  	backend := newTestBackend(t, &chainConfig, 0, func(i int, b *core.BlockGen) {})
   449  	oracle, err := NewOracle(backend, config)
   450  	require.NoError(err)
   451  	got, err := oracle.SuggestPrice(context.Background())
   452  	require.NoError(err)
   453  	require.Equal(chainConfig.FeeConfig.MinBaseFee, got)
   454  
   455  	// issue the block with tx that changes the fee
   456  	genesis := backend.chain.Genesis()
   457  	engine := backend.chain.Engine()
   458  	blocks, _, err := core.GenerateChain(&chainConfig, genesis, engine, backend.db, 1, 0, func(i int, b *core.BlockGen) {
   459  		b.SetCoinbase(common.Address{1})
   460  
   461  		// admin issues tx to change fee config to higher MinBaseFee
   462  		tx := types.NewTx(&types.DynamicFeeTx{
   463  			ChainID:   chainConfig.ChainID,
   464  			Nonce:     b.TxNonce(addr),
   465  			To:        &precompile.FeeConfigManagerAddress,
   466  			Gas:       chainConfig.FeeConfig.GasLimit.Uint64(),
   467  			Value:     common.Big0,
   468  			GasFeeCap: chainConfig.FeeConfig.MinBaseFee, // give low fee, it should work since we still haven't applied high fees
   469  			GasTipCap: common.Big0,
   470  			Data:      data,
   471  		})
   472  		tx, err = types.SignTx(tx, signer, key)
   473  		require.NoError(err, "failed to create tx")
   474  		b.AddTx(tx)
   475  	})
   476  	require.NoError(err)
   477  	_, err = backend.chain.InsertChain(blocks)
   478  	require.NoError(err)
   479  
   480  	// verify the suggested price follows the new fee config.
   481  	got, err = oracle.SuggestPrice(context.Background())
   482  	require.NoError(err)
   483  	require.Equal(highFeeConfig.MinBaseFee, got)
   484  }