Skip to content

Commit

Permalink
fix!: fix json-rpc failures for pruned nodes (backport evmos#1126)
Browse files Browse the repository at this point in the history
Closes: evmos#1123
Solution:
- try to parse base fee from events if grpc query failed
- use a `nil` base fee if failed to parse base fee from events
- use zero address if query validator address failed
- optimize some json-rpc apis by the way.

* changelog

* fix lint

* use GetTendermintBlockResultByNumber

* refactor

Co-authored-by: Federico Kunze Küllmer <[email protected]>
  • Loading branch information
yihuang and fedekunze committed Jun 30, 2022
1 parent 39ab2dd commit 4a4fd60
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 97 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

* (rpc) [tharsis#1068](https://github.com/tharsis/ethermint/pull/1068) Fix london hardfork check logic in json-rpc apis.
- (rpc) [tharsis#1081](https://github.com/tharsis/ethermint/pull/1081) Deduplicate some json-rpc logic codes, cleanup several dead functions.
* (rpc) [\#1126](https://github.com/evmos/ethermint/pull/1126) Make some JSON-RPC APIS work for pruned nodes.

### Improvements

Expand Down
171 changes: 105 additions & 66 deletions rpc/ethereum/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Backend interface {
BlockNumber() (hexutil.Uint64, error)
GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error)
GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error)
GetTendermintBlockResultByNumber(*int64) (*tmrpctypes.ResultBlockResults, error)
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
BlockByNumber(blockNum types.BlockNumber) (*ethtypes.Block, error)
Expand All @@ -70,7 +71,7 @@ type Backend interface {
GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error)
BaseFee(height int64) (*big.Int, error)
BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error)

// Filter API
BloomStatus() (uint64, uint64)
Expand Down Expand Up @@ -150,7 +151,13 @@ func (e *EVMBackend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (
return nil, nil
}

res, err := e.EthBlockFromTendermint(resBlock, fullTx)
blockRes, err := e.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
e.logger.Debug("failed to fetch block result from Tendermint", "height", blockNum, "error", err.Error())
return nil, nil
}

res, err := e.EthBlockFromTendermint(resBlock, blockRes, fullTx)
if err != nil {
e.logger.Debug("EthBlockFromTendermint failed", "height", blockNum, "error", err.Error())
return nil, err
Expand All @@ -165,12 +172,19 @@ func (e *EVMBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]i
if err != nil {
return nil, err
}

if resBlock == nil {
// block not found
return nil, nil
}

return e.EthBlockFromTendermint(resBlock, fullTx)
blockRes, err := e.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
e.logger.Debug("failed to fetch block result from Tendermint", "block-hash", hash.String(), "error", err.Error())
return nil, nil
}

return e.EthBlockFromTendermint(resBlock, blockRes, fullTx)
}

// BlockByNumber returns the block identified by number.
Expand All @@ -181,10 +195,15 @@ func (e *EVMBackend) BlockByNumber(blockNum types.BlockNumber) (*ethtypes.Block,
}
if resBlock == nil {
// block not found
return nil, errors.Errorf("block not found for height %d", blockNum)
return nil, fmt.Errorf("block not found for height %d", blockNum)
}

blockRes, err := e.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
return nil, fmt.Errorf("block result not found for height %d", resBlock.Block.Height)
}

return e.EthBlockFromTm(resBlock)
return e.EthBlockFromTm(resBlock, blockRes)
}

// BlockByHash returns the block identified by hash.
Expand All @@ -195,29 +214,34 @@ func (e *EVMBackend) BlockByHash(hash common.Hash) (*ethtypes.Block, error) {
}

if resBlock == nil || resBlock.Block == nil {
return nil, errors.Errorf("block not found for hash %s", hash)
return nil, fmt.Errorf("block not found for hash %s", hash)
}

blockRes, err := e.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
return nil, fmt.Errorf("block result not found for hash %s", hash)
}

return e.EthBlockFromTm(resBlock)
return e.EthBlockFromTm(resBlock, blockRes)
}

func (e *EVMBackend) EthBlockFromTm(resBlock *tmrpctypes.ResultBlock) (*ethtypes.Block, error) {
func (e *EVMBackend) EthBlockFromTm(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) (*ethtypes.Block, error) {
block := resBlock.Block
height := block.Height
bloom, err := e.BlockBloom(&height)
bloom, err := e.BlockBloom(blockRes)
if err != nil {
e.logger.Debug("EthBlockFromTm BlockBloom failed", "height", height)
}

baseFee, err := e.BaseFee(height)
baseFee, err := e.BaseFee(blockRes)
if err != nil {
e.logger.Debug("EthBlockFromTm BaseFee failed", "height", height, "error", err.Error())
return nil, err
// handle error for pruned node and log
e.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", height, "error", err)
}

ethHeader := types.EthHeaderFromTendermint(block.Header, bloom, baseFee)

resBlockResult, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Height)
resBlockResult, err := e.GetTendermintBlockResultByNumber(&block.Height)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -259,6 +283,11 @@ func (e *EVMBackend) GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tm
return resBlock, nil
}

// GetTendermintBlockResultByNumber returns a Tendermint-formatted block result by block number
func (e *EVMBackend) GetTendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error) {
return e.clientCtx.Client.BlockResults(e.ctx, height)
}

// GetTendermintBlockByHash returns a Tendermint format block by block number
func (e *EVMBackend) GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) {
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, blockHash.Bytes())
Expand All @@ -276,12 +305,8 @@ func (e *EVMBackend) GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctype
}

// BlockBloom query block bloom filter from block results
func (e *EVMBackend) BlockBloom(height *int64) (ethtypes.Bloom, error) {
result, err := e.clientCtx.Client.BlockResults(e.ctx, height)
if err != nil {
return ethtypes.Bloom{}, err
}
for _, event := range result.EndBlockEvents {
func (e *EVMBackend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (ethtypes.Bloom, error) {
for _, event := range blockRes.EndBlockEvents {
if event.Type != evmtypes.EventTypeBlockBloom {
continue
}
Expand All @@ -298,22 +323,19 @@ func (e *EVMBackend) BlockBloom(height *int64) (ethtypes.Bloom, error) {
// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a given Tendermint block and its block result.
func (e *EVMBackend) EthBlockFromTendermint(
resBlock *tmrpctypes.ResultBlock,
blockRes *tmrpctypes.ResultBlockResults,
fullTx bool,
) (map[string]interface{}, error) {
ethRPCTxs := []interface{}{}
block := resBlock.Block

baseFee, err := e.BaseFee(block.Height)
if err != nil {
return nil, err
}

resBlockResult, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Height)
baseFee, err := e.BaseFee(blockRes)
if err != nil {
return nil, err
// handle the error for pruned node.
e.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Height, "error", err)
}

msgs := e.GetEthereumMsgsFromTendermintBlock(resBlock, resBlockResult)
msgs := e.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
for txIndex, ethMsg := range msgs {
if !fullTx {
hash := common.HexToHash(ethMsg.Hash)
Expand All @@ -336,7 +358,7 @@ func (e *EVMBackend) EthBlockFromTendermint(
ethRPCTxs = append(ethRPCTxs, rpcTx)
}

bloom, err := e.BlockBloom(&block.Height)
bloom, err := e.BlockBloom(blockRes)
if err != nil {
e.logger.Debug("failed to query BlockBloom", "height", block.Height, "error", err.Error())
}
Expand All @@ -345,6 +367,8 @@ func (e *EVMBackend) EthBlockFromTendermint(
ConsAddress: sdk.ConsAddress(block.Header.ProposerAddress).String(),
}

var validatorAccAddr sdk.AccAddress

ctx := types.ContextWithHeight(block.Height)
res, err := e.queryClient.ValidatorAccount(ctx, req)
if err != nil {
Expand All @@ -354,15 +378,16 @@ func (e *EVMBackend) EthBlockFromTendermint(
"cons-address", req.ConsAddress,
"error", err.Error(),
)
return nil, err
}

addr, err := sdk.AccAddressFromBech32(res.AccountAddress)
if err != nil {
return nil, err
// use zero address as the validator operator address
validatorAccAddr = sdk.AccAddress(common.Address{}.Bytes())
} else {
validatorAccAddr, err = sdk.AccAddressFromBech32(res.AccountAddress)
if err != nil {
return nil, err
}
}

validatorAddr := common.BytesToAddress(addr)
validatorAddr := common.BytesToAddress(validatorAccAddr)

gasLimit, err := types.BlockMaxGasFromConsensusParams(ctx, e.clientCtx, block.Height)
if err != nil {
Expand All @@ -371,7 +396,7 @@ func (e *EVMBackend) EthBlockFromTendermint(

gasUsed := uint64(0)

for _, txsResult := range resBlockResult.TxsResults {
for _, txsResult := range blockRes.TxsResults {
// workaround for cosmos-sdk bug. https://github.com/cosmos/cosmos-sdk/issues/10832
if ShouldIgnoreGasUsed(txsResult) {
// block gas limit has exceeded, other txs must have failed with same reason.
Expand Down Expand Up @@ -405,15 +430,20 @@ func (e *EVMBackend) HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Heade
return nil, errors.Errorf("block not found for height %d", blockNum)
}

bloom, err := e.BlockBloom(&resBlock.Block.Height)
blockRes, err := e.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
return nil, fmt.Errorf("block result not found for height %d", resBlock.Block.Height)
}

bloom, err := e.BlockBloom(blockRes)
if err != nil {
e.logger.Debug("HeaderByNumber BlockBloom failed", "height", resBlock.Block.Height)
}

baseFee, err := e.BaseFee(resBlock.Block.Height)
baseFee, err := e.BaseFee(blockRes)
if err != nil {
e.logger.Debug("HeaderByNumber BaseFee failed", "height", resBlock.Block.Height, "error", err.Error())
return nil, err
// handle the error for pruned node.
e.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", resBlock.Block.Height, "error", err)
}

ethHeader := types.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
Expand All @@ -430,15 +460,20 @@ func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, erro
return nil, errors.Errorf("block not found for hash %s", blockHash.Hex())
}

bloom, err := e.BlockBloom(&resBlock.Block.Height)
blockRes, err := e.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
return nil, errors.Errorf("block result not found for height %d", resBlock.Block.Height)
}

bloom, err := e.BlockBloom(blockRes)
if err != nil {
e.logger.Debug("HeaderByHash BlockBloom failed", "height", resBlock.Block.Height)
}

baseFee, err := e.BaseFee(resBlock.Block.Height)
baseFee, err := e.BaseFee(blockRes)
if err != nil {
e.logger.Debug("HeaderByHash BaseFee failed", "height", resBlock.Block.Height, "error", err.Error())
return nil, err
// handle the error for pruned node.
e.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", resBlock.Block.Height, "error", err)
}

ethHeader := types.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
Expand Down Expand Up @@ -468,22 +503,12 @@ func (e *EVMBackend) PendingTransactions() ([]*sdk.Tx, error) {
// GetLogsByHeight returns all the logs from all the ethereum transactions in a block.
func (e *EVMBackend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) {
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, height)
blockRes, err := e.GetTendermintBlockResultByNumber(height)
if err != nil {
return nil, err
}

blockLogs := [][]*ethtypes.Log{}
for _, txResult := range blockRes.TxsResults {
logs, err := AllTxLogsFromEvents(txResult.Events)
if err != nil {
return nil, err
}

blockLogs = append(blockLogs, logs...)
}

return blockLogs, nil
return GetLogsFromBlockResults(blockRes)
}

// GetLogs returns all the logs from all the ethereum transactions in a block.
Expand Down Expand Up @@ -600,12 +625,14 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
return nil, err
}

blockRes, err := e.GetTendermintBlockResultByNumber(&block.Block.Height)
if err != nil {
e.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error())
return nil, nil
}

if parsedTx.EthTxIndex == -1 {
// Fallback to find tx index by iterating all valid eth transactions
blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
if err != nil {
return nil, nil
}
msgs := e.GetEthereumMsgsFromTendermintBlock(block, blockRes)
for i := range msgs {
if msgs[i].Hash == hexTx {
Expand All @@ -618,10 +645,10 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
return nil, errors.New("can't find index of ethereum tx")
}

baseFee, err := e.BaseFee(block.Block.Height)
baseFee, err := e.BaseFee(blockRes)
if err != nil {
e.logger.Debug("HeaderByHash BaseFee failed", "height", block.Block.Height, "error", err.Error())
return nil, err
// handle the error for pruned node.
e.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", blockRes.Height, "error", err)
}

return types.NewTransactionFromMsg(
Expand Down Expand Up @@ -885,10 +912,22 @@ func (e *EVMBackend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) {
// If the base fee is not enabled globally, the query returns nil.
// If the London hard fork is not activated at the current height, the query will
// return nil.
func (e *EVMBackend) BaseFee(height int64) (*big.Int, error) {
func (e *EVMBackend) BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error) {
// return BaseFee if London hard fork is activated and feemarket is enabled
res, err := e.queryClient.BaseFee(types.ContextWithHeight(height), &evmtypes.QueryBaseFeeRequest{})
if err != nil {
res, err := e.queryClient.BaseFee(types.ContextWithHeight(blockRes.Height), &evmtypes.QueryBaseFeeRequest{})
if err != nil {
// fallback to parsing from begin blocker event, could happen on pruned nodes.
// faster to iterate reversely
for i := len(blockRes.BeginBlockEvents) - 1; i >= 0; i-- {
evt := blockRes.BeginBlockEvents[i]
if evt.Type == feemarkettypes.EventTypeFeeMarket && len(evt.Attributes) > 0 {
baseFee, err := strconv.ParseInt(string(evt.Attributes[0].Value), 10, 64)
if err == nil {
return big.NewInt(baseFee), nil
}
break
}
}
return nil, err
}

Expand Down
2 changes: 1 addition & 1 deletion rpc/ethereum/backend/feebackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (e *EVMBackend) processBlock(
tendermintBlockResult *tmrpctypes.ResultBlockResults,
targetOneFeeHistory *rpctypes.OneFeeHistory) error {
blockHeight := tendermintBlock.Block.Height
blockBaseFee, err := e.BaseFee(blockHeight)
blockBaseFee, err := e.BaseFee(tendermintBlockResult)
if err != nil {
return err
}
Expand Down
16 changes: 16 additions & 0 deletions rpc/ethereum/backend/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/tharsis/ethermint/rpc/ethereum/types"
Expand Down Expand Up @@ -272,3 +273,18 @@ func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool {
func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool {
return res.GetCode() == 11 && strings.Contains(res.GetLog(), "no block gas left to run tx: out of gas")
}

// GetLogsFromBlockResults returns the list of event logs from the tendermint block result response
func GetLogsFromBlockResults(blockRes *tmrpctypes.ResultBlockResults) ([][]*ethtypes.Log, error) {
blockLogs := [][]*ethtypes.Log{}
for _, txResult := range blockRes.TxsResults {
logs, err := AllTxLogsFromEvents(txResult.Events)
if err != nil {
return nil, err
}

blockLogs = append(blockLogs, logs...)
}

return blockLogs, nil
}
Loading

0 comments on commit 4a4fd60

Please sign in to comment.