Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
ENG 119 json rpc unit tests (#1189)
Browse files Browse the repository at this point in the history
* tests(json-rpc): wip evm_backend unit test setup

* tests(json-rpc): wip evm_backend unit test setup

* fix viper

* wip query client mock

* fix first backend test except error message

* clean up

* wip Context with Height

* fix JSON RPC backend test setup

* typo

* refactor folder structure

* tests(json-rpc):add BlockBloom tests

* tests(json-rpc): remove unused malleate

* tests(json-rpc): add BaseFee tests

* refactor query tests

* add client mock

* add GetTendermintBlockByNumber tests

* refactor mock tests

* refactor

* wip backend EthBlockFromTendermint test

* wip backend EthBlockFromTendermint test

* refactor backend EthBlockFromTendermint test

* add TestGetTendermintBlockResultByNumber

* add GetBlockByNumber tests

* refactor mocks

* fix spelling

* add more tests and address comments
  • Loading branch information
danburck committed Jul 29, 2022
1 parent ebbffc6 commit ee806fc
Show file tree
Hide file tree
Showing 11 changed files with 2,280 additions and 115 deletions.
2 changes: 1 addition & 1 deletion rpc/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ type EVMBackend interface {

// Blockchain API
BlockNumber() (hexutil.Uint64, error)
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error)
GetTendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error)
GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, 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)
BlockByHash(blockHash common.Hash) (*ethtypes.Block, error)
Expand Down
120 changes: 84 additions & 36 deletions rpc/backend/backend_suite_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package backend

import (
"math/big"
"testing"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/suite"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"

"github.com/evmos/ethermint/app"
"github.com/evmos/ethermint/encoding"
"github.com/evmos/ethermint/rpc/backend/mocks"
ethrpc "github.com/evmos/ethermint/rpc/types"
rpc "github.com/evmos/ethermint/rpc/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
)
Expand All @@ -30,44 +33,89 @@ func TestBackendTestSuite(t *testing.T) {
func (suite *BackendTestSuite) SetupTest() {
ctx := server.NewDefaultContext()
ctx.Viper.Set("telemetry.global-labels", []interface{}{})
clientCtx := client.Context{}.WithChainID("ethermint_9000-1").WithHeight(1)
allowUnprotectedTxs := false

suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
clientCtx := client.Context{}.WithChainID("ethermint_9000-1").
WithHeight(1).
WithTxConfig(encodingConfig.TxConfig)

queryClient := mocks.NewQueryClient(suite.T())
var header metadata.MD
RegisterMockQueries(queryClient, &header)
allowUnprotectedTxs := false

suite.backend.queryClient.QueryClient = queryClient
suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
suite.backend.queryClient.QueryClient = mocks.NewQueryClient(suite.T())
suite.backend.clientCtx.Client = mocks.NewClient(suite.T())
suite.backend.ctx = rpc.ContextWithHeight(1)
}

// QueryClient defines a mocked object that implements the grpc QueryCLient
// interface. It's used on tests to test the JSON-RPC without running a grpc
// client server. E.g. JSON-PRC-CLIENT -> BACKEND -> Mock GRPC CLIENT -> APP
var _ evmtypes.QueryClient = &mocks.QueryClient{}

// RegisterMockQueries registers the queries and their respective responses,
// so that they can be called in tests using the queryClient
func RegisterMockQueries(queryClient *mocks.QueryClient, header *metadata.MD) {
queryClient.On("Params", rpc.ContextWithHeight(1), &evmtypes.QueryParamsRequest{}, grpc.Header(header)).
Return(&evmtypes.QueryParamsResponse{}, nil).
Run(func(args mock.Arguments) {
// If Params call is successful, also update the header height
arg := args.Get(2).(grpc.HeaderCallOption)
h := metadata.MD{}
h.Set(grpctypes.GRPCBlockHeightHeader, "1")
*arg.HeaderAddr = h
})
// buildEthereumTx returns an example legacy Ethereum transaction
func (suite *BackendTestSuite) buildEthereumTx() (*evmtypes.MsgEthereumTx, []byte) {
msgEthereumTx := evmtypes.NewTx(
big.NewInt(1),
uint64(0),
&common.Address{},
big.NewInt(0),
100000,
big.NewInt(1),
nil,
nil,
nil,
nil,
)

// A valid msg should have empty `From`
msgEthereumTx.From = ""

txBuilder := suite.backend.clientCtx.TxConfig.NewTxBuilder()
err := txBuilder.SetMsgs(msgEthereumTx)
suite.Require().NoError(err)

bz, err := suite.backend.clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
suite.Require().NoError(err)
return msgEthereumTx, bz
}

func TestQueryClient(t *testing.T) {
queryClient := mocks.NewQueryClient(t)
var header metadata.MD
RegisterMockQueries(queryClient, &header)
// buildFormattedBlock returns a formatted block for testing
func (suite *BackendTestSuite) buildFormattedBlock(
blockRes *tmrpctypes.ResultBlockResults,
resBlock *tmrpctypes.ResultBlock,
fullTx bool,
tx *evmtypes.MsgEthereumTx,
validator sdk.AccAddress,
baseFee *big.Int,
) map[string]interface{} {
header := resBlock.Block.Header
gasLimit := int64(^uint32(0)) // for `MaxGas = -1` (DefaultConsensusParams)
gasUsed := new(big.Int).SetUint64(uint64(blockRes.TxsResults[0].GasUsed))

root := common.Hash{}.Bytes()
receipt := ethtypes.NewReceipt(root, false, gasUsed.Uint64())
bloom := ethtypes.CreateBloom(ethtypes.Receipts{receipt})

ethRPCTxs := []interface{}{}
if tx != nil {
if fullTx {
rpcTx, err := ethrpc.NewRPCTransaction(
tx.AsTransaction(),
common.BytesToHash(header.Hash()),
uint64(header.Height),
uint64(0),
baseFee,
)
suite.Require().NoError(err)
ethRPCTxs = []interface{}{rpcTx}
} else {
ethRPCTxs = []interface{}{common.HexToHash(tx.Hash)}
}
}

// mock calls for abstraction
_, err := queryClient.Params(rpc.ContextWithHeight(1), &evmtypes.QueryParamsRequest{}, grpc.Header(&header))
require.NoError(t, err)
return ethrpc.FormatBlock(
header,
resBlock.Block.Size(),
gasLimit,
gasUsed,
ethRPCTxs,
bloom,
common.BytesToAddress(validator.Bytes()),
baseFee,
)
}
147 changes: 147 additions & 0 deletions rpc/backend/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package backend

import (
"testing"

sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common"
"github.com/evmos/ethermint/rpc/backend/mocks"
rpc "github.com/evmos/ethermint/rpc/types"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
tmrpcclient "github.com/tendermint/tendermint/rpc/client"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/types"
)

// Client defines a mocked object that implements the Tendermint JSON-RPC Client
// interface. It allows for performing Client queries without having to run a
// Tendermint RPC Client server.
//
// To use a mock method it has to be registered in a given test.
var _ tmrpcclient.Client = &mocks.Client{}

// Block
func RegisterBlock(
client *mocks.Client,
height int64,
tx []byte,
) (*tmrpctypes.ResultBlock, error) {
// without tx
if tx == nil {
emptyBlock := types.MakeBlock(height, []types.Tx{}, nil, nil)
resBlock := &tmrpctypes.ResultBlock{Block: emptyBlock}
client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(resBlock, nil)
return resBlock, nil
}

// with tx
block := types.MakeBlock(height, []types.Tx{tx}, nil, nil)
res := &tmrpctypes.ResultBlock{Block: block}
client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(res, nil)
return res, nil
}

// Block returns error
func RegisterBlockError(client *mocks.Client, height int64) {
client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(nil, sdkerrors.ErrInvalidRequest)
}

// Block not found
func RegisterBlockNotFound(
client *mocks.Client,
height int64,
) (*tmrpctypes.ResultBlock, error) {
client.On("Block", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(&tmrpctypes.ResultBlock{Block: nil}, nil)

return &tmrpctypes.ResultBlock{Block: nil}, nil
}

func TestRegisterBlock(t *testing.T) {
client := mocks.NewClient(t)
height := rpc.BlockNumber(1).Int64()
RegisterBlock(client, height, nil)

res, err := client.Block(rpc.ContextWithHeight(height), &height)

emptyBlock := types.MakeBlock(height, []types.Tx{}, nil, nil)
resBlock := &tmrpctypes.ResultBlock{Block: emptyBlock}
require.Equal(t, resBlock, res)
require.NoError(t, err)
}

// ConsensusParams
func RegisterConsensusParams(client *mocks.Client, height int64) {
consensusParams := types.DefaultConsensusParams()
client.On("ConsensusParams", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(&tmrpctypes.ResultConsensusParams{ConsensusParams: *consensusParams}, nil)
}

func RegisterConsensusParamsError(client *mocks.Client, height int64) {
client.On("ConsensusParams", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(nil, sdkerrors.ErrInvalidRequest)
}

func TestRegisterConsensusParams(t *testing.T) {
client := mocks.NewClient(t)
height := int64(1)
RegisterConsensusParams(client, height)

res, err := client.ConsensusParams(rpc.ContextWithHeight(height), &height)
consensusParams := types.DefaultConsensusParams()
require.Equal(t, &tmrpctypes.ResultConsensusParams{ConsensusParams: *consensusParams}, res)
require.NoError(t, err)
}

// BlockResults
func RegisterBlockResults(
client *mocks.Client,
height int64,
) (*tmrpctypes.ResultBlockResults, error) {
res := &tmrpctypes.ResultBlockResults{
Height: height,
TxsResults: []*abci.ResponseDeliverTx{{Code: 0, GasUsed: 0}},
}

client.On("BlockResults", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(res, nil)
return res, nil
}

func RegisterBlockResultsError(client *mocks.Client, height int64) {
client.On("BlockResults", rpc.ContextWithHeight(height), mock.AnythingOfType("*int64")).
Return(nil, sdkerrors.ErrInvalidRequest)
}

func TestRegisterBlockResults(t *testing.T) {
client := mocks.NewClient(t)
height := int64(1)
RegisterBlockResults(client, height)

res, err := client.BlockResults(rpc.ContextWithHeight(height), &height)
expRes := &tmrpctypes.ResultBlockResults{
Height: height,
TxsResults: []*abci.ResponseDeliverTx{{Code: 0, GasUsed: 0}},
}
require.Equal(t, expRes, res)
require.NoError(t, err)
}

// BlockByHash
func RegisterBlockByHash(
client *mocks.Client,
hash common.Hash,
tx []byte,
) (*tmrpctypes.ResultBlock, error) {
block := types.MakeBlock(1, []types.Tx{tx}, nil, nil)
resBlock := &tmrpctypes.ResultBlock{Block: block}

client.On("BlockByHash", rpc.ContextWithHeight(1), []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}).
Return(resBlock, nil)
return resBlock, nil
}
29 changes: 21 additions & 8 deletions rpc/backend/evm_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]inte
return nil, nil
}

return b.EthBlockFromTendermint(resBlock, blockRes, fullTx)
res, err := b.EthBlockFromTendermint(resBlock, blockRes, fullTx)
if err != nil {
b.logger.Debug("EthBlockFromTendermint failed", "hash", hash, "error", err.Error())
return nil, err
}

return res, nil
}

// BlockByNumber returns the block identified by number.
Expand Down Expand Up @@ -177,7 +183,8 @@ func (b *Backend) EthBlockFromTm(resBlock *tmrpctypes.ResultBlock, blockRes *tmr
return ethBlock, nil
}

// GetTendermintBlockByNumber returns a Tendermint format block by block number
// GetTendermintBlockByNumber returns a Tendermint formatted block for a given
// block number
func (b *Backend) GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error) {
height := blockNum.Int64()
if height <= 0 {
Expand Down Expand Up @@ -239,7 +246,8 @@ func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (ethtypes.
return ethtypes.Bloom{}, errors.New("block bloom event is not found")
}

// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a given Tendermint block and its block result.
// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a
// given Tendermint block and its block result.
func (b *Backend) EthBlockFromTendermint(
resBlock *tmrpctypes.ResultBlock,
blockRes *tmrpctypes.ResultBlockResults,
Expand Down Expand Up @@ -981,17 +989,22 @@ func (b *Backend) FeeHistory(
return &feeHistory, nil
}

// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block.
// It also ensures consistency over the correct txs indexes across RPC endpoints
func (b *Backend) GetEthereumMsgsFromTendermintBlock(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx {
// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a
// Tendermint block. It also ensures consistency over the correct txs indexes
// across RPC endpoints
func (b *Backend) GetEthereumMsgsFromTendermintBlock(
resBlock *tmrpctypes.ResultBlock,
blockRes *tmrpctypes.ResultBlockResults,
) []*evmtypes.MsgEthereumTx {
var result []*evmtypes.MsgEthereumTx
block := resBlock.Block

txResults := blockRes.TxsResults

for i, tx := range block.Txs {
// check tx exists on EVM by cross checking with blockResults
// include the tx that exceeds block gas limit
// Check if tx exists on EVM by cross checking with blockResults:
// - Include unsuccessful tx that exceeds block gas limit
// - Exclude unsuccessful tx with any other error but ExceedBlockGasLimit
if !TxSuccessOrExceedsBlockGasLimit(txResults[i]) {
b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
continue
Expand Down
Loading

0 comments on commit ee806fc

Please sign in to comment.