From db006d0c0c4e7d4257e06dcd92fda09ab248c19f Mon Sep 17 00:00:00 2001 From: kourin Date: Wed, 15 Jan 2025 16:43:43 +0900 Subject: [PATCH] Supplement baseFee and gasLimit for pre gingerbread blocks (#302) * [WIP] Retrieve baseFee and gasLimit for pre gingerbread block in RPC request * Add codes to store pre gingerbread block's base fee and gas limit into KVS * Add tests * Format code * Format code * Fix test * Fix test * Add retrievePreGingerbreadGasLimit to resolve broken tests * Add test comments * Add tests for PopulatePreGingerbreadHeaderFields and retrievePreGingerbreadBlockBaseFee" * Fix lint error * Fix lint error * Remove gas limit field from pre-Gingerbread additional fields record in local DB * Replace some assert with require in tests * Format codes based on review * Clean up codes * Remove unnecessary new line * Fix typo --- core/rawdb/celo_accessors.go | 36 +++ core/rawdb/celo_accessors_test.go | 33 ++ internal/ethapi/api.go | 6 + internal/ethapi/celo_block.go | 153 +++++++++ internal/ethapi/celo_block_test.go | 387 +++++++++++++++++++++++ internal/ethapi/transaction_args_test.go | 77 +++-- params/celo_config.go | 70 +++- params/celo_config_test.go | 31 ++ 8 files changed, 771 insertions(+), 22 deletions(-) create mode 100644 core/rawdb/celo_accessors.go create mode 100644 core/rawdb/celo_accessors_test.go create mode 100644 internal/ethapi/celo_block.go create mode 100644 internal/ethapi/celo_block_test.go create mode 100644 params/celo_config_test.go diff --git a/core/rawdb/celo_accessors.go b/core/rawdb/celo_accessors.go new file mode 100644 index 0000000000..37b0f21edc --- /dev/null +++ b/core/rawdb/celo_accessors.go @@ -0,0 +1,36 @@ +package rawdb + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +var ( + CeloPreGingerbreadBlockBaseFeePrefix = []byte("celoPgbBlockBaseFee-") // CeloPreGingerbreadBlockBaseFeePrefix + block hash -> BaseFee +) + +// preGingerbreadBlockBaseFeeKey calculates a database key of pre-Gingerbread block BaseFee for the given block hash +func preGingerbreadBlockBaseFeeKey(hash common.Hash) []byte { + return append(CeloPreGingerbreadBlockBaseFeePrefix, hash[:]...) +} + +// ReadPreGingerbreadBlockBaseFee reads BaseFee of pre-Gingerbread block from the given database for the given block hash +func ReadPreGingerbreadBlockBaseFee(db ethdb.KeyValueReader, blockHash common.Hash) (*big.Int, error) { + data, err := db.Get(preGingerbreadBlockBaseFeeKey(blockHash)) + if err != nil { + return nil, fmt.Errorf("error retrieving pre gingerbread base fee for block: %s, error: %w", blockHash, err) + } + if len(data) == 0 { + return nil, nil + } + + return new(big.Int).SetBytes(data), nil +} + +// WritePreGingerbreadBlockBaseFee writes BaseFee of pre-Gingerbread block to the given database at the given block hash +func WritePreGingerbreadBlockBaseFee(db ethdb.KeyValueWriter, blockHash common.Hash, baseFee *big.Int) error { + return db.Put(preGingerbreadBlockBaseFeeKey(blockHash), baseFee.Bytes()) +} diff --git a/core/rawdb/celo_accessors_test.go b/core/rawdb/celo_accessors_test.go new file mode 100644 index 0000000000..ed6ce14373 --- /dev/null +++ b/core/rawdb/celo_accessors_test.go @@ -0,0 +1,33 @@ +package rawdb + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestReadAndWritePreGingerbreadBlockBaseFee tests reading and writing pre-gingerbread block base fee to database +func TestReadAndWritePreGingerbreadBlockBaseFee(t *testing.T) { + db := NewMemoryDatabase() + + hash := common.HexToHash("0x1") + value := big.NewInt(1234) + + // Make sure it returns an error for a nonexistent record + record0, err := ReadPreGingerbreadBlockBaseFee(db, hash) + assert.ErrorContains(t, err, fmt.Sprintf("error retrieving pre gingerbread base fee for block: %s, error: not found", hash.String())) + require.Nil(t, record0) + + // Write data + err = WritePreGingerbreadBlockBaseFee(db, hash, value) + require.NoError(t, err) + + // Read data + record, err := ReadPreGingerbreadBlockBaseFee(db, hash) + require.NoError(t, err) + assert.Equal(t, value, record) +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 69f4fe16c9..cc68ef8f1a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -843,6 +843,7 @@ func decodeHash(s string) (h common.Hash, inputLength int, err error) { func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { header, err := api.b.HeaderByNumber(ctx, number) if header != nil && err == nil { + header := PopulatePreGingerbreadHeaderFields(ctx, api.b, header) response := RPCMarshalHeader(header) if number == rpc.PendingBlockNumber && api.b.ChainConfig().Optimism == nil { // Pending header need to nil out a few fields @@ -859,6 +860,7 @@ func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.Bloc func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} { header, _ := api.b.HeaderByHash(ctx, hash) if header != nil { + header := PopulatePreGingerbreadHeaderFields(ctx, api.b, header) return RPCMarshalHeader(header) } return nil @@ -874,6 +876,7 @@ func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := api.b.BlockByNumber(ctx, number) if block != nil && err == nil { + block := PopulatePreGingerbreadBlockFields(ctx, api.b, block) response, err := RPCMarshalBlock(ctx, block, true, fullTx, api.b.ChainConfig(), api.b) if err == nil && number == rpc.PendingBlockNumber && api.b.ChainConfig().Optimism == nil { // Pending blocks need to nil out a few fields @@ -891,6 +894,7 @@ func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.Block func (api *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) { block, err := api.b.BlockByHash(ctx, hash) if block != nil { + block := PopulatePreGingerbreadBlockFields(ctx, api.b, block) return RPCMarshalBlock(ctx, block, true, fullTx, api.b.ChainConfig(), api.b) } return nil, err @@ -906,6 +910,7 @@ func (api *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blo return nil, nil } block = types.NewBlockWithHeader(uncles[index]) + block = PopulatePreGingerbreadBlockFields(ctx, api.b, block) return RPCMarshalBlock(ctx, block, false, false, api.b.ChainConfig(), api.b) } return nil, err @@ -921,6 +926,7 @@ func (api *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, block return nil, nil } block = types.NewBlockWithHeader(uncles[index]) + block = PopulatePreGingerbreadBlockFields(ctx, api.b, block) return RPCMarshalBlock(ctx, block, false, false, api.b.ChainConfig(), api.b) } return nil, err diff --git a/internal/ethapi/celo_block.go b/internal/ethapi/celo_block.go new file mode 100644 index 0000000000..b817ed8916 --- /dev/null +++ b/internal/ethapi/celo_block.go @@ -0,0 +1,153 @@ +package ethapi + +import ( + "context" + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + gasPriceMinimumABIJson = `[{"inputs":[{"internalType":"uint256","name":"gasPriceMinimum","type":"uint256"}],"name":"GasPriceMinimumUpdated","outputs":[],"type":"event"}]` + gasPriceMinimumABI abi.ABI +) + +func init() { + parsedAbi, _ := abi.JSON(strings.NewReader(gasPriceMinimumABIJson)) + gasPriceMinimumABI = parsedAbi +} + +// PopulatePreGingerbreadBlockFields populates the baseFee and gasLimit fields of the block for pre-gingerbread blocks +func PopulatePreGingerbreadBlockFields(ctx context.Context, backend CeloBackend, block *types.Block) *types.Block { + return block.WithSeal( + PopulatePreGingerbreadHeaderFields(ctx, backend, block.Header()), + ) +} + +// PopulatePreGingerbreadHeaderFields populates the baseFee and gasLimit fields of the header for pre-gingerbread blocks +func PopulatePreGingerbreadHeaderFields(ctx context.Context, backend CeloBackend, header *types.Header) *types.Header { + // If the block is post-Gingerbread, return the header as is + if backend.ChainConfig().IsGingerbread(header.Number) { + return header + } + + var ( + gasLimit *big.Int + baseFee *big.Int + err error + ) + + if chainId := backend.ChainConfig().ChainID; chainId != nil { + gasLimit = retrievePreGingerbreadGasLimit(chainId.Uint64(), header.Number) + } + + baseFee, err = rawdb.ReadPreGingerbreadBlockBaseFee(backend.ChainDb(), header.Hash()) + if err != nil { + log.Debug("failed to load pre-Gingerbread block base fee from database", "block", header.Number.Uint64(), "err", err) + } + if baseFee == nil { + // If the record is not found, get the values and store them + baseFee, err = retrievePreGingerbreadBlockBaseFee(ctx, backend, header.Number) + if err != nil { + log.Debug("Not adding to RPC response, failed to retrieve pre-Gingerbread block base fee", "block", header.Number.Uint64(), "err", err) + } + + // Store the base fee for future use + if baseFee != nil { + err = rawdb.WritePreGingerbreadBlockBaseFee(backend.ChainDb(), header.Hash(), baseFee) + if err != nil { + log.Debug("failed to write pre-Gingerbread block base fee", "block", header.Number.Uint64(), "err", err) + } + } + } + + if baseFee != nil { + header.BaseFee = baseFee + } + if gasLimit != nil { + header.GasLimit = gasLimit.Uint64() + } + + return header +} + +// retrievePreGingerbreadGasLimit retrieves a gas limit at given height from hardcoded values +func retrievePreGingerbreadGasLimit(chainId uint64, height *big.Int) *big.Int { + limits, ok := params.PreGingerbreadNetworkGasLimits[chainId] + if !ok { + log.Debug("Not adding gasLimit to RPC response, unknown network", "chainID", chainId) + return nil + } + + return new(big.Int).SetUint64(limits.Limit(height)) +} + +// retrievePreGingerbreadBlockBaseFee retrieves a base fee at given height from the previous block +func retrievePreGingerbreadBlockBaseFee(ctx context.Context, backend CeloBackend, height *big.Int) (*big.Int, error) { + if height.Cmp(common.Big0) <= 0 { + return nil, nil + } + + prevHeight := height.Uint64() - 1 + prevBlock, err := backend.BlockByNumber(ctx, rpc.BlockNumber(prevHeight)) + if err != nil { + return nil, err + } + if prevBlock == nil { + return nil, fmt.Errorf("block #%d not found", prevHeight) + } + + prevReceipts, err := backend.GetReceipts(ctx, prevBlock.Hash()) + if err != nil { + return nil, err + } + + numTxs, numReceipts := len(prevBlock.Transactions()), len(prevReceipts) + if numReceipts <= numTxs { + return nil, fmt.Errorf("receipts of block #%d don't contain system logs", prevHeight) + } + + systemReceipt := prevReceipts[numTxs] + for _, logRecord := range systemReceipt.Logs { + if logRecord.Topics[0] != gasPriceMinimumABI.Events["GasPriceMinimumUpdated"].ID { + continue + } + + baseFee, err := parseGasPriceMinimumUpdated(logRecord.Data) + if err != nil { + return nil, fmt.Errorf("failed to extract GasPriceMinimumUpdated event from system logs: %w", err) + } + + return baseFee, nil + } + + return nil, fmt.Errorf("an event GasPriceMinimumUpdated is not included in receipts of block #%d", prevHeight) +} + +// parseGasPriceMinimumUpdated parses the data of GasPriceMinimumUpdated event +func parseGasPriceMinimumUpdated(data []byte) (*big.Int, error) { + values, err := gasPriceMinimumABI.Unpack("GasPriceMinimumUpdated", data) + if err != nil { + return nil, err + } + + // safe check, actually Unpack will parse first 32 bytes as a single value + if len(values) != 1 { + return nil, fmt.Errorf("unexpected format of values in GasPriceMinimumUpdated event") + } + + baseFee, ok := values[0].(*big.Int) + if !ok { + return nil, fmt.Errorf("unexpected base fee type in GasPriceMinimumUpdated event: expected *big.Int, got %T", values[0]) + } + + return baseFee, nil +} diff --git a/internal/ethapi/celo_block_test.go b/internal/ethapi/celo_block_test.go new file mode 100644 index 0000000000..51402340a5 --- /dev/null +++ b/internal/ethapi/celo_block_test.go @@ -0,0 +1,387 @@ +package ethapi + +import ( + "context" + "errors" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/blocktest" + "github.com/ethereum/go-ethereum/params" + "github.com/status-im/keycard-go/hexutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// encodeGasPriceMinimumUpdatedEvent encodes the given gas price minimum value into 32 bytes event data +func encodeGasPriceMinimumUpdatedEvent(gasPriceMinimum *big.Int) []byte { + gasPriceMinimumBytes := gasPriceMinimum.Bytes() + gasPriceMinimumEventData := make([]byte, 32) + copy(gasPriceMinimumEventData[32-len(gasPriceMinimumBytes):], gasPriceMinimumBytes) + return gasPriceMinimumEventData +} + +// TestPopulatePreGingerbreadHeaderFields tests the PopulatePreGingerbreadHeaderFields function +func TestPopulatePreGingerbreadHeaderFields(t *testing.T) { + t.Parallel() + + hasher := blocktest.NewHasher() + gingerBreadBeginsAt := big.NewInt(10e5) + + tests := []struct { + name string + beforeDbBaseFee *big.Int // BaseFee to be stored in the database before the test + afterDbBaseFee *big.Int // BaseFee to be stored in the database after the test + backendBaseFee *big.Int + header *types.Header + expected *types.Header + }{ + { + name: "should return the same header for post-gingerbread header", + header: &types.Header{ + Number: big.NewInt(10e5), + BaseFee: big.NewInt(10e2), + GasLimit: 10e3, + }, + expected: &types.Header{ + Number: big.NewInt(10e5), + BaseFee: big.NewInt(10e2), + GasLimit: 10e3, + }, + }, + { + name: "should return the header with baseFee and gasLimit retrieved from the database", + beforeDbBaseFee: big.NewInt(10e4), + afterDbBaseFee: big.NewInt(10e4), + header: &types.Header{ + Number: big.NewInt(10e3), + }, + expected: &types.Header{ + Number: big.NewInt(10e3), + BaseFee: big.NewInt(10e4), + GasLimit: 1e7, + }, + }, + { + name: "should return the header with baseFee and gasLimit retrieved from the backend", + beforeDbBaseFee: nil, + afterDbBaseFee: big.NewInt(10e8), + backendBaseFee: big.NewInt(10e8), + header: &types.Header{ + Number: big.NewInt(1000), + }, + expected: &types.Header{ + Number: big.NewInt(1000), + BaseFee: big.NewInt(10e8), + GasLimit: 20e6, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + headerHash := test.header.Hash() + backend := newCeloBackendMock(¶ms.ChainConfig{ + ChainID: big.NewInt(params.CeloMainnetChainID), + GingerbreadBlock: gingerBreadBeginsAt, + }) + + // set base fee into DB + if test.beforeDbBaseFee != nil { + err := rawdb.WritePreGingerbreadBlockBaseFee(backend.ChainDb(), headerHash, test.beforeDbBaseFee) + require.NoError(t, err) + } + + // set block & receipts for base fee + if test.backendBaseFee != nil { + prevHeader := &types.Header{ + Number: new(big.Int).Sub(test.header.Number, big.NewInt(1)), + } + prevBlock := types.NewBlock( + prevHeader, + nil, + nil, + hasher, + ) + backend.setBlock(prevBlock.Number().Int64(), prevBlock) + backend.setReceipts(prevBlock.Hash(), types.Receipts{ + { + Logs: []*types.Log{ + { + Topics: []common.Hash{ + gasPriceMinimumABI.Events["GasPriceMinimumUpdated"].ID, + }, + Data: encodeGasPriceMinimumUpdatedEvent(test.backendBaseFee), + }, + }, + }, + }) + } + + // retrieve baseFee and gasLimit + newHeader := PopulatePreGingerbreadHeaderFields(context.Background(), backend, test.header) + assert.Equal(t, test.expected, newHeader) + + // check db data after the test + dbData, err := rawdb.ReadPreGingerbreadBlockBaseFee(backend.ChainDb(), headerHash) + if test.afterDbBaseFee != nil { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, "error retrieving pre gingerbread base fee for block") + } + assert.Equal(t, test.afterDbBaseFee, dbData) + }) + } +} + +// Test_retrievePreGingerbreadGasLimit checks the gas limit retrieval for pre-gingerbread blocks +func Test_retrievePreGingerbreadGasLimit(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + chainId uint64 + height *big.Int + expected *big.Int + }{ + { + name: "should return latest gas limit value for Celo Mainnet", + chainId: params.CeloMainnetChainID, + height: big.NewInt(21355415), + expected: big.NewInt(32e6), + }, + { + name: "should return latest gas limit value for Celo Alfajores", + chainId: params.CeloAlfajoresChainID, + height: big.NewInt(11143973), + expected: big.NewInt(35e6), + }, + { + name: "should return latest gas limit value for Celo Baklava", + chainId: params.CeloBaklavaChainID, + height: big.NewInt(15158971), + expected: big.NewInt(20e6), + }, + { + name: "should return nil if chainId is unknown", + chainId: 12345, + height: big.NewInt(10), + expected: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gasLimit := retrievePreGingerbreadGasLimit(test.chainId, test.height) + + assert.Equal(t, test.expected, gasLimit) + }) + } +} + +// Test_retrievePreGingerbreadBlockBaseFee tests the base fee retrieval for pre-gingerbread blocks +func Test_retrievePreGingerbreadBlockBaseFee(t *testing.T) { + t.Parallel() + + prevHeader := &types.Header{Number: big.NewInt(999)} + hasher := blocktest.NewHasher() + + // encode GasPriceMinimumUpdated event body + baseFee := big.NewInt(1_000_000) + baseFeeEventData := encodeGasPriceMinimumUpdatedEvent(baseFee) + + tests := []struct { + name string + blocks map[int64]*types.Block + receipts types.Receipts + height *big.Int + expected *big.Int + err error + }{ + { + name: "should return an error if previous block is not found", + blocks: nil, + receipts: nil, + height: big.NewInt(1000), + expected: nil, + err: fmt.Errorf("block #999 not found"), + }, + { + name: "should return an error if block receipt is empty", + blocks: map[int64]*types.Block{ + 999: types.NewBlock( + prevHeader, + nil, + nil, + hasher, + ), + }, + receipts: nil, + height: big.NewInt(1000), + expected: nil, + err: fmt.Errorf("receipts of block #999 don't contain system logs"), + }, + { + name: "should return an error if block receipt doesn't contain system logs", + blocks: map[int64]*types.Block{ + 999: types.NewBlock( + prevHeader, + &types.Body{ + Transactions: []*types.Transaction{ + types.NewTx(&types.LegacyTx{ + Nonce: 0, + }), + }, + }, + nil, + hasher, + ), + }, + receipts: types.Receipts{ + { + TxHash: prevHeader.Hash(), + Logs: nil, + }, + }, + height: big.NewInt(1000), + expected: nil, + err: fmt.Errorf("receipts of block #999 don't contain system logs"), + }, + { + name: "should return an error if block receipt doesn't contain GasPriceMinimumUpdated event in system logs", + blocks: map[int64]*types.Block{ + 999: types.NewBlock( + prevHeader, + &types.Body{ + Transactions: []*types.Transaction{ + types.NewTx(&types.LegacyTx{ + Nonce: 0, + }), + }, + }, + nil, + hasher, + ), + }, + receipts: types.Receipts{ + { + TxHash: prevHeader.Hash(), + Logs: nil, + }, + { + Logs: []*types.Log{ + { + Topics: []common.Hash{ + common.HexToHash("0x123456"), // fake topic + }, + Data: baseFeeEventData, + }, + }, + }, + }, + height: big.NewInt(1000), + expected: nil, + err: fmt.Errorf("an event GasPriceMinimumUpdated is not included in receipts of block #999"), + }, + { + name: "should return base fee successfully", + blocks: map[int64]*types.Block{ + 999: types.NewBlock( + prevHeader, + &types.Body{ + Transactions: []*types.Transaction{ + types.NewTx(&types.LegacyTx{ + Nonce: 0, + }), + }, + }, + nil, + hasher, + ), + }, + receipts: types.Receipts{ + { + TxHash: prevHeader.Hash(), + Logs: nil, + }, + { + Logs: []*types.Log{ + { + Topics: []common.Hash{ + gasPriceMinimumABI.Events["GasPriceMinimumUpdated"].ID, + }, + Data: baseFeeEventData, + }, + }, + }, + }, + height: big.NewInt(1000), + expected: baseFee, + err: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // create a new backend mock with seed data + backend := newCeloBackendMock(¶ms.ChainConfig{}) + for number, block := range test.blocks { + backend.setBlock(number, block) + backend.setReceipts(block.Hash(), test.receipts) + } + + baseFee, err := retrievePreGingerbreadBlockBaseFee(context.Background(), backend, test.height) + + if test.err == nil { + require.NoError(t, err) + } else { + assert.EqualError(t, err, test.err.Error()) + } + + assert.Equal(t, test.expected, baseFee) + }) + } +} + +// Test_parseGasPriceMinimumUpdated checks the gas price minimum updated event parsing +func Test_parseGasPriceMinimumUpdated(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + data []byte + result *big.Int + err error + }{ + { + name: "should parse gas price successfully", + data: hexutils.HexToBytes("00000000000000000000000000000000000000000000000000000000000f4240"), + result: big.NewInt(1_000_000), + err: nil, + }, + { + name: "should return error if data is not in the expected format", + data: hexutils.HexToBytes("123456"), + result: nil, + err: errors.New("abi: cannot marshal in to go type: length insufficient 3 require 32"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := parseGasPriceMinimumUpdated(test.data) + + if test.err == nil { + require.NoError(t, err) + } else { + assert.EqualError(t, err, test.err.Error()) + } + + assert.Equal(t, test.result, result) + }) + } +} diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index c202ac2595..b7dd4d1fc2 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -40,6 +41,27 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +var ( + cancunTime uint64 = 600 + defaultBackendConfig = ¶ms.ChainConfig{ + ChainID: big.NewInt(42), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(1000), + CancunTime: &cancunTime, + } +) + // TestSetFeeDefaults tests the logic for filling in default fee values works as expected. func TestSetFeeDefaults(t *testing.T) { t.Parallel() @@ -53,7 +75,7 @@ func TestSetFeeDefaults(t *testing.T) { } var ( - b = newCeloBackendMock() + b = newCeloBackendMock(nil) zero = (*hexutil.Big)(big.NewInt(0)) fortytwo = (*hexutil.Big)(big.NewInt(42)) maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt())) @@ -292,11 +314,18 @@ func TestSetFeeDefaults(t *testing.T) { type celoBackendMock struct { *backendMock + + chainDb ethdb.Database + blockByNumber map[int64]*types.Block + receiptsByHash map[common.Hash]types.Receipts } -func newCeloBackendMock() *celoBackendMock { +func newCeloBackendMock(config *params.ChainConfig) *celoBackendMock { return &celoBackendMock{ - backendMock: newBackendMock(), + backendMock: newBackendMock(config), + chainDb: rawdb.NewMemoryDatabase(), + blockByNumber: make(map[int64]*types.Block), + receiptsByHash: make(map[common.Hash]types.Receipts), } } @@ -325,30 +354,36 @@ func (c *celoBackendMock) ConvertToCelo(ctx context.Context, blockNumOrHash rpc. return new(big.Int).Div(value, big.NewInt(2)), nil } +func (c *celoBackendMock) ChainDb() ethdb.Database { + return c.chainDb +} + +func (c *celoBackendMock) BlockByNumber(_ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + return c.blockByNumber[number.Int64()], nil +} + +func (c *celoBackendMock) GetReceipts(_ctx context.Context, hash common.Hash) (types.Receipts, error) { + return c.receiptsByHash[hash], nil +} + +func (c *celoBackendMock) setBlock(number int64, block *types.Block) { + c.blockByNumber[number] = block +} + +func (c *celoBackendMock) setReceipts(hash common.Hash, receipts types.Receipts) { + c.receiptsByHash[hash] = receipts +} + type backendMock struct { current *types.Header config *params.ChainConfig } -func newBackendMock() *backendMock { - var cancunTime uint64 = 600 - config := ¶ms.ChainConfig{ - ChainID: big.NewInt(42), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(1000), - CancunTime: &cancunTime, +func newBackendMock(config *params.ChainConfig) *backendMock { + if config == nil { + config = defaultBackendConfig } + return &backendMock{ current: &types.Header{ Difficulty: big.NewInt(10000000000), diff --git a/params/celo_config.go b/params/celo_config.go index c761cb4605..2f997f19cf 100644 --- a/params/celo_config.go +++ b/params/celo_config.go @@ -1,7 +1,75 @@ package params +import "math/big" + const ( CeloMainnetChainID = 42220 - CeloBaklavaChainID = 62320 CeloAlfajoresChainID = 44787 + CeloBaklavaChainID = 62320 +) + +// GasLimits holds the gas limit changes for a given chain +type GasLimits struct { + changes []LimitChange +} + +type LimitChange struct { + block *big.Int + gasLimit uint64 +} + +// Limit returns the gas limit at a given block number +func (g *GasLimits) Limit(block *big.Int) uint64 { + // Grab the gas limit at block 0 + curr := g.changes[0].gasLimit + for _, c := range g.changes[1:] { + if block.Cmp(c.block) < 0 { + return curr + } + curr = c.gasLimit + } + return curr +} + +var ( + // Hardcoded set of gas limit changes derived from historical state of Celo L1 chain + // Ported from celo-blockchain (https://github.com/celo-org/celo-blockchain/blob/master/params/config.go#L189-L220) + mainnetGasLimits = &GasLimits{ + changes: []LimitChange{ + {big.NewInt(0), 20e6}, + {big.NewInt(3317), 10e6}, + {big.NewInt(3251772), 13e6}, + {big.NewInt(6137285), 20e6}, + {big.NewInt(13562578), 50e6}, + {big.NewInt(14137511), 20e6}, + {big.NewInt(21355415), 32e6}, + }, + } + + alfajoresGasLimits = &GasLimits{ + changes: []LimitChange{ + {big.NewInt(0), 20e6}, + {big.NewInt(912), 10e6}, + {big.NewInt(1392355), 130e6}, + {big.NewInt(1507905), 13e6}, + {big.NewInt(4581182), 20e6}, + {big.NewInt(11143973), 35e6}, + }, + } + + baklavaGasLimits = &GasLimits{ + changes: []LimitChange{ + {big.NewInt(0), 20e6}, + {big.NewInt(1230), 10e6}, + {big.NewInt(1713181), 130e6}, + {big.NewInt(1945003), 13e6}, + {big.NewInt(15158971), 20e6}, + }, + } + + PreGingerbreadNetworkGasLimits = map[uint64]*GasLimits{ + CeloMainnetChainID: mainnetGasLimits, + CeloAlfajoresChainID: alfajoresGasLimits, + CeloBaklavaChainID: baklavaGasLimits, + } ) diff --git a/params/celo_config_test.go b/params/celo_config_test.go new file mode 100644 index 0000000000..f3aef3f241 --- /dev/null +++ b/params/celo_config_test.go @@ -0,0 +1,31 @@ +package params + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestGasLimits_Limit checks the gas limits at the beginning and end of each limit change of each chain +func TestGasLimits_Limit(t *testing.T) { + subTest := func(t *testing.T, name string, chainId uint64, limits []LimitChange) { + t.Run(name, func(t *testing.T) { + for i, l := range limits { + beginningHeight := l.block + beginningLimit := PreGingerbreadNetworkGasLimits[chainId].Limit(beginningHeight) + assert.Equal(t, l.gasLimit, beginningLimit, "gas limit at block %d (%s)", beginningHeight, name) + + if i < len(limits)-1 { + endHeight := new(big.Int).Sub(limits[i+1].block, big.NewInt(1)) + endLimit := PreGingerbreadNetworkGasLimits[chainId].Limit(endHeight) + assert.Equal(t, l.gasLimit, endLimit, "gas limit at block %d (%s)", endHeight, name) + } + } + }) + } + + subTest(t, "mainnet", CeloMainnetChainID, mainnetGasLimits.changes) + subTest(t, "alfajores", CeloAlfajoresChainID, alfajoresGasLimits.changes) + subTest(t, "baklava", CeloBaklavaChainID, baklavaGasLimits.changes) +}