Skip to content

Commit

Permalink
Supplement baseFee and gasLimit for pre gingerbread blocks (#302)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
Kourin1996 authored Jan 15, 2025
1 parent fb6e48c commit db006d0
Show file tree
Hide file tree
Showing 8 changed files with 771 additions and 22 deletions.
36 changes: 36 additions & 0 deletions core/rawdb/celo_accessors.go
Original file line number Diff line number Diff line change
@@ -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())
}
33 changes: 33 additions & 0 deletions core/rawdb/celo_accessors_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 6 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
153 changes: 153 additions & 0 deletions internal/ethapi/celo_block.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit db006d0

Please sign in to comment.