-
Notifications
You must be signed in to change notification settings - Fork 120
Description
Is there an existing issue for this?
- I have searched the existing issues
What happened?
Error handling on eth_getLogs rpc call
Description
We found in the rpc package that some calls made to Tendermint RPC are not handled properly. In specific, the eth_getLogs rpc call when providing a blockHash.
TendermintBlockResultByNumber
Starting on the Logs function in the filters package:
// Logs searches the blockchain for matching log entries, returning all from the
// first block that contains matches, updating the start of the filter accordingly.
func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
var err error
// If we're doing singleton block filtering, execute and return
if f.criteria.BlockHash != nil && *f.criteria.BlockHash != (common.Hash{}) {
resBlock, err := f.backend.TendermintBlockByHash(*f.criteria.BlockHash)
if err != nil {
return nil, fmt.Errorf("failed to fetch header by hash %s: %w", f.criteria.BlockHash, err)
}
blockRes, err := f.backend.TendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
f.logger.Debug("failed to fetch block result from Tendermint", "height", resBlock.Block.Height, "error", err.Error())
return nil, nil
}
bloom, err := f.backend.BlockBloom(blockRes)
if err != nil {
return nil, err
}
return f.blockLogs(blockRes, bloom)
}
// ...
}When fetching the block result, the TendermintBlockResultByNumber function returns the result and an error. However, the error is not handled and the function returns nil, nil, only debugging the error.
By not handling the error, the Logs function will return nil, nil, which will cause the GetLogs to call the returnLogs function with a nil argument, returning an empty array.
// GetLogs returns logs matching the given argument that are stored within the state.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) {
var filter *Filter
if crit.BlockHash != nil {
// Block filter requested, construct a single-shot filter
filter = NewBlockFilter(api.logger, api.backend, crit)
} else {
// Convert the RPC block numbers into internal representations
begin := rpc.LatestBlockNumber.Int64()
if crit.FromBlock != nil {
begin = crit.FromBlock.Int64()
}
end := rpc.LatestBlockNumber.Int64()
if crit.ToBlock != nil {
end = crit.ToBlock.Int64()
}
// Construct the range filter
filter = NewRangeFilter(api.logger, api.backend, begin, end, crit.Addresses, crit.Topics)
}
// Run the filter and return all the logs
logs, err := filter.Logs(ctx, int(api.backend.RPCLogsCap()), int64(api.backend.RPCBlockRangeCap()))
if err != nil {
return nil, err
}
return returnLogs(logs), err
}// returnLogs is a helper that will return an empty log array in case the given logs array is nil,
// otherwise the given logs array is returned.
func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log {
if logs == nil {
return []*ethtypes.Log{}
}
return logs
}Solution
The solution is to handle the returned error in the TendermintBlockResultByNumber function.
// TendermintBlockResultByNumber returns a Tendermint-formatted block result
// by block number
func (b *Backend) TendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error) {
res, err := b.rpcClient.BlockResults(b.ctx, height)
if err != nil {
return nil, fmt.Errorf("failed to fetch block result from Tendermint %d: %w", *height, err)
}
return res, nil
}And properly handle the error in the Logs function.
// Logs searches the blockchain for matching log entries, returning all from the
// first block that contains matches, updating the start of the filter accordingly.
func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
var err error
// If we're doing singleton block filtering, execute and return
if f.criteria.BlockHash != nil && *f.criteria.BlockHash != (common.Hash{}) {
resBlock, err := f.backend.TendermintBlockByHash(*f.criteria.BlockHash)
if err != nil {
return nil, fmt.Errorf("failed to fetch header by hash %s: %w", f.criteria.BlockHash, err)
}
blockRes, err := f.backend.TendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
f.logger.Debug("failed to fetch block result from Tendermint", "height", resBlock.Block.Height, "error", err.Error())
return nil, err
}
bloom, err := f.backend.BlockBloom(blockRes)
if err != nil {
return nil, err
}
return f.blockLogs(blockRes, bloom)
}
// ...
}TendermintBlockByHash
Also, TendermintBlockByHash returns a pair of nil, nil when the resBlock or resBlock.Block is nil.
// TendermintBlockByHash returns a Tendermint-formatted block by block number
func (b *Backend) TendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) {
resBlock, err := b.rpcClient.BlockByHash(b.ctx, blockHash.Bytes())
if err != nil {
b.logger.Debug("tendermint client failed to get block", "blockHash", blockHash.Hex(), "error", err.Error())
return nil, err
}
if resBlock == nil || resBlock.Block == nil {
b.logger.Debug("TendermintBlockByHash block not found", "blockHash", blockHash.Hex())
return nil, nil
}
return resBlock, nil
}This will cause the TendermintBlockResultByNumber in the Logs function to panic since it will receive a nil resBlock and will try to access the Block.Height field.
// Logs searches the blockchain for matching log entries, returning all from the
// first block that contains matches, updating the start of the filter accordingly.
func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
var err error
// If we're doing singleton block filtering, execute and return
if f.criteria.BlockHash != nil && *f.criteria.BlockHash != (common.Hash{}) {
resBlock, err := f.backend.TendermintBlockByHash(*f.criteria.BlockHash)
if err != nil {
return nil, fmt.Errorf("failed to fetch header by hash %s: %w", f.criteria.BlockHash, err)
}
blockRes, err := f.backend.TendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
f.logger.Debug("failed to fetch block result from Tendermint", "height", resBlock.Block.Height, "error", err.Error())
return nil, nil
}
// ...
}
// ...
}Solution
The solution is to return an error when checking that resBlock or resBlock.Block is nil.
// TendermintBlockByHash returns a Tendermint-formatted block by block number
func (b *Backend) TendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) {
resBlock, err := b.rpcClient.BlockByHash(b.ctx, blockHash.Bytes())
if err != nil {
b.logger.Debug("tendermint client failed to get block", "blockHash", blockHash.Hex(), "error", err.Error())
return nil, err
}
if resBlock == nil || resBlock.Block == nil {
b.logger.Error("tendermint block not found", "blockHash", blockHash.Hex())
return nil, fmt.Errorf("block not found for hash %s", blockHash.Hex())
}
return resBlock, nil
}Affected functions in cosmos/evm package
This issue affects the following functions in the cosmos/evm package:
It is possible that other rpc calls are affected by this issue.
Cosmos EVM Version
main
How to reproduce?
No response