From 2d01ded9bfb24886fe7dee4adfd6321a973ac9d4 Mon Sep 17 00:00:00 2001 From: kourin Date: Tue, 21 Jan 2025 23:08:13 +0900 Subject: [PATCH] Add totalDifficulty field to JSON-RPC schema of Celo1 block (#310) * Add totalDifficulty field to JSON-RPC response for Celo1 header & block * Fix test * Remove new line --------- Co-authored-by: piersy --- internal/ethapi/api.go | 17 +++++-- internal/ethapi/celo_api_test.go | 82 ++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 5eff40e9fa..d0dbfafadc 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -502,7 +502,7 @@ func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.Bloc header, err := api.b.HeaderByNumber(ctx, number) if header != nil && err == nil { header := PopulatePreGingerbreadHeaderFields(ctx, api.b, header) - response := RPCMarshalHeader(header) + response := RPCMarshalHeader(header, isCelo1Block(api.b.ChainConfig(), header.Time)) if number == rpc.PendingBlockNumber && api.b.ChainConfig().Optimism == nil { // Pending header need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { @@ -519,7 +519,7 @@ func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) header, _ := api.b.HeaderByHash(ctx, hash) if header != nil { header := PopulatePreGingerbreadHeaderFields(ctx, api.b, header) - return RPCMarshalHeader(header) + return RPCMarshalHeader(header, isCelo1Block(api.b.ChainConfig(), header.Time)) } return nil } @@ -1016,7 +1016,7 @@ func (api *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, } // RPCMarshalHeader converts the given header to the RPC output . -func RPCMarshalHeader(head *types.Header) map[string]interface{} { +func RPCMarshalHeader(head *types.Header, isCelo1 bool) map[string]interface{} { result := map[string]interface{}{ "number": (*hexutil.Big)(head.Number), "hash": head.Hash(), @@ -1053,14 +1053,23 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { if head.RequestsHash != nil { result["requestsHash"] = head.RequestsHash } + if isCelo1 { + result["totalDifficulty"] = (*hexutil.Big)(new(big.Int).Add(head.Number, common.Big1)) + } return result } +// isCelo1Block determines whether the given block is a Celo1 block +// based on the chain config and the provided block time +func isCelo1Block(config *params.ChainConfig, blockTime uint64) bool { + return config.Cel2Time != nil && !config.IsCel2(blockTime) +} + // RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are // returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain // transaction hashes. func RPCMarshalBlock(ctx context.Context, block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig, backend Backend) (map[string]interface{}, error) { - fields := RPCMarshalHeader(block.Header()) + fields := RPCMarshalHeader(block.Header(), isCelo1Block(config, block.Header().Time)) fields["size"] = hexutil.Uint64(block.Size()) if inclTx { diff --git a/internal/ethapi/celo_api_test.go b/internal/ethapi/celo_api_test.go index cf93b4f573..83ae6c2b17 100644 --- a/internal/ethapi/celo_api_test.go +++ b/internal/ethapi/celo_api_test.go @@ -1,6 +1,7 @@ package ethapi import ( + "context" "math/big" "testing" @@ -8,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -531,3 +533,83 @@ func checkTxFields( assert.Equal(t, (*hexutil.Big)(tx.GatewayFee()), rpcTx.GatewayFee) assert.Equal(t, tx.GatewayFeeRecipient(), rpcTx.GatewayFeeRecipient) } + +// Test_isCelo1Block tests isCelo1Block function to determine whether the given block time +// corresponds to Celo1 chain based on the provided chain configuration +func Test_isCelo1Block(t *testing.T) { + cel2Time := uint64(1000) + + t.Run("Non-Celo", func(t *testing.T) { + res := isCelo1Block(¶ms.ChainConfig{ + Cel2Time: nil, + }, 1000) + + assert.False(t, res) + }) + + t.Run("Celo1", func(t *testing.T) { + res := isCelo1Block(¶ms.ChainConfig{ + Cel2Time: &cel2Time, + }, 500) + + assert.True(t, res) + }) + + t.Run("Celo2", func(t *testing.T) { + res := isCelo1Block(¶ms.ChainConfig{ + Cel2Time: &cel2Time, + }, 1000) + + assert.False(t, res) + }) +} + +// TestRPCMarshalBlock_Celo1TotalDifficulty tests the RPCMarshalBlock function, specifically for totalDifficulty field +// It validates the result has `totalDifficulty` field only if it's Celo1 block +func TestRPCMarshalBlock_Celo1TotalDifficulty(t *testing.T) { + t.Parallel() + + blockTime := uint64(1000) + block := types.NewBlock(&types.Header{Number: big.NewInt(100), Time: blockTime}, &types.Body{Transactions: []*types.Transaction{}}, nil, blocktest.NewHasher(), types.DefaultBlockConfig) + + marshalBlock := func(t *testing.T, config *params.ChainConfig) map[string]interface{} { + t.Helper() + + resp, err := RPCMarshalBlock(context.Background(), block, false, false, config, testBackend{}) + if err != nil { + require.NoError(t, err) + } + + return resp + } + + t.Run("Non-Celo", func(t *testing.T) { + config := *params.MainnetChainConfig + + res := marshalBlock(t, &config) + + assert.Equal(t, nil, res["totalDifficulty"]) + }) + + t.Run("Celo1", func(t *testing.T) { + expected := (*hexutil.Big)(new(big.Int).Add(block.Number(), common.Big1)) + + cel2Time := blockTime + 500 + config := *params.MainnetChainConfig + config.Cel2Time = &cel2Time + + res := marshalBlock(t, &config) + + assert.Equal(t, expected, res["totalDifficulty"]) + }) + + t.Run("Celo2", func(t *testing.T) { + cel2Time := blockTime - 500 + config := *params.MainnetChainConfig + config.Cel2Time = &cel2Time + + res := marshalBlock(t, &config) + + assert.Equal(t, nil, res["totalDifficulty"]) + }) +}