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

ENG 119 json rpc unit tests #1189

Merged
merged 31 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f0e56f4
tests(json-rpc): wip evm_backend unit test setup
danburck Jul 18, 2022
1d8df0b
tests(json-rpc): wip evm_backend unit test setup
danburck Jul 18, 2022
34932a7
fix viper
danburck Jul 19, 2022
4761dc3
wip query client mock
danburck Jul 21, 2022
d1563e7
fix first backend test except error message
danburck Jul 21, 2022
9f1d6ee
clean up
danburck Jul 21, 2022
e38d5d8
wip Context with Height
danburck Jul 21, 2022
c78a4ca
fix JSON RPC backend test setup
danburck Jul 22, 2022
9dc9b6a
typo
danburck Jul 22, 2022
d9840af
refactor folder structure
danburck Jul 22, 2022
a5d65ab
tests(json-rpc):add BlockBloom tests
danburck Jul 22, 2022
a36c7cc
tests(json-rpc): remove unused malleate
danburck Jul 22, 2022
b8efec0
tests(json-rpc): add BaseFee tests
danburck Jul 22, 2022
ee05f99
Merge branch 'main' into ENG-119-json-rpc-unit-tests
danburck Jul 25, 2022
42263a8
refactor query tests
danburck Jul 25, 2022
87a9a4c
add client mock
danburck Jul 25, 2022
b9f4d18
add GetTendermintBlockByNumber tests
danburck Jul 25, 2022
422ff03
refactor mock tests
danburck Jul 25, 2022
b66ae7d
refactor
danburck Jul 25, 2022
51c1426
wip backend EthBlockFromTendermint test
danburck Jul 26, 2022
d5dbe1b
wip backend EthBlockFromTendermint test
danburck Jul 26, 2022
64bb30a
refactor backend EthBlockFromTendermint test
danburck Jul 27, 2022
b0c31f4
add TestGetTendermintBlockResultByNumber
danburck Jul 27, 2022
799fe8d
add GetBlockByNumber tests
danburck Jul 27, 2022
1274ab5
refactor mocks
danburck Jul 27, 2022
74ab452
Merge branch 'main' into ENG-119-json-rpc-unit-tests
danburck Jul 27, 2022
ff2229b
fix spelling
danburck Jul 27, 2022
16ed1b8
Merge branch 'ENG-119-json-rpc-unit-tests' of github.com:tharsis/ethe…
danburck Jul 27, 2022
c169a11
add more tests and address comments
danburck Jul 29, 2022
a2e79fe
Merge branch 'main' into ENG-119-json-rpc-unit-tests
danburck Jul 29, 2022
744093b
fix linter
danburck Jul 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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