diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index eaebc01728..1782186707 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -706,36 +706,39 @@ func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.H // only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) - if block != nil && err == nil { - response, err := s.rpcMarshalBlock(ctx, block, true, fullTx) - - if err == nil { + if block == nil || err != nil { + return nil, err + } + response, err := s.rpcMarshalBlock(ctx, block, true, fullTx) + if err == nil { + if s.b.RPCEthCompatibility() { addEthCompatibilityFields(ctx, response, s.b, block) - if number == rpc.PendingBlockNumber { - // Pending blocks need to nil out a few fields - for _, field := range []string{"hash", "nonce", "miner"} { - response[field] = nil - } + } + if number == rpc.PendingBlockNumber { + // Pending blocks need to nil out a few fields + for _, field := range []string{"hash", "nonce", "miner"} { + response[field] = nil } } - return response, err } - return nil, err + return response, err } // GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full // detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByHash(ctx, hash) - if block != nil { - result, err := s.rpcMarshalBlock(ctx, block, true, fullTx) - if err != nil { - return nil, err - } + if block == nil { + return nil, err + } + result, err := s.rpcMarshalBlock(ctx, block, true, fullTx) + if err != nil { + return nil, err + } + if s.b.RPCEthCompatibility() { addEthCompatibilityFields(ctx, result, s.b, block) - return result, nil } - return nil, err + return result, nil } // addEthCompatibilityFields seeks to work around the incompatibility of celo @@ -743,27 +746,29 @@ func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Ha // rpc response that ethers.js depends upon. // See https://github.com/celo-org/celo-blockchain/issues/1945 func addEthCompatibilityFields(ctx context.Context, response map[string]interface{}, b Backend, block *types.Block) { - isGingerbread := b.ChainConfig().IsGingerbread(block.Number()) - if !b.RPCEthCompatibility() { - if !isGingerbread { - delete(response, "gasLimit") - } - return - } - header := block.Header() - if !isGingerbread { - // Before Gingerbread, the header did not include the gasLimit, so we have to manually add it for eth-compatible RPC responses. - hash := header.Hash() - numhash := rpc.BlockNumberOrHash{ - BlockHash: &hash, - } - gasLimit, err := b.GetRealBlockGasLimit(ctx, numhash) - if err != nil { - log.Debug("Not adding gasLimit to RPC response, failed to retrieve it", "block", header.Number.Uint64(), "err", err) + if !b.ChainConfig().IsGingerbread(block.Number()) { + // Before Gingerbread, the header did not include the gasLimit, now we manually add it for old blocks. + var gasLimit uint64 + var err error + + // For mainnet, alfajores and baklava we have a set of hardcoded values derived from historical state that we can + // use, note ChainID might be unset, so we need to account for that. + chainId := b.ChainConfig().ChainID + if chainId != nil && params.PreGingerbreadNetworkGasLimits[chainId.Uint64()] != nil { + gasLimit = params.PreGingerbreadNetworkGasLimits[chainId.Uint64()].Limit(header.Number) } else { - response["gasLimit"] = hexutil.Uint64(gasLimit) + // If no hardcoded limits are available for this network then we will try to look up the gas limit in the state. + hash := header.Hash() + numhash := rpc.BlockNumberOrHash{ + BlockHash: &hash, + } + gasLimit, err = b.GetRealBlockGasLimit(ctx, numhash) + if err != nil { + log.Debug("Failed to retrieve gas limit for RPC block response, zero gas limit will be returned", "block", header.Number.Uint64(), "err", err) + } } + response["gasLimit"] = hexutil.Uint64(gasLimit) } if header.BaseFee != nil { @@ -1148,7 +1153,6 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { "miner": head.Coinbase, "extraData": hexutil.Bytes(head.Extra), "size": hexutil.Uint64(head.Size()), - "gasLimit": hexutil.Uint64(head.GasLimit), "gasUsed": hexutil.Uint64(head.GasUsed), "timestamp": hexutil.Uint64(head.Time), "transactionsRoot": head.TxHash, @@ -1164,6 +1168,11 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { result["mixHash"] = head.MixDigest } + // Before the gingerbread hardfork gas limit was not part of the block header. + if head.GasLimit > 0 { + result["gasLimit"] = hexutil.Uint64(head.GasLimit) + } + if head.BaseFee != nil { result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) } diff --git a/params/config.go b/params/config.go index f3845d7174..9ed7b50454 100644 --- a/params/config.go +++ b/params/config.go @@ -176,6 +176,51 @@ var ( }, ) TestRules = TestChainConfig.Rules(new(big.Int)) + + 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), 13e6}, + {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}, + }, + } + + // This is a hardcoded set of gas limit changes derived from historical + // state. They allow non archive nodes to return the gas limit for blocks + // before gingerbread (where we added gas limit to the block header). + // Additionally they ensure that archive nodes return the correct value at + // the beginning of the chain, before the blockchain parameters contract + // was deployed and activated. + PreGingerbreadNetworkGasLimits = map[uint64]*GasLimits{ + MainnetNetworkId: mainnetGasLimits, + AlfajoresNetworkId: alfajoresGasLimits, + BaklavaNetworkId: baklavaGasLimits, + } ) // TrustedCheckpoint represents a set of post-processed trie roots (CHT and diff --git a/params/gas_limts.go b/params/gas_limts.go new file mode 100644 index 0000000000..fd05ec0765 --- /dev/null +++ b/params/gas_limts.go @@ -0,0 +1,25 @@ +package params + +import "math/big" + +type GasLimits struct { + // changes holds all gas limit changes, it is assumed that the first change ocurrs at block 0. + changes []LimitChange +} + +type LimitChange struct { + block *big.Int + gasLimit uint64 +} + +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 +}