From ee806fc41feaa034ab9e17d648ff4a6315101f90 Mon Sep 17 00:00:00 2001 From: Daniel Burckhardt Date: Fri, 29 Jul 2022 12:35:07 +0200 Subject: [PATCH] ENG 119 json rpc unit tests (#1189) * 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 --- rpc/backend/backend.go | 2 +- rpc/backend/backend_suite_test.go | 120 ++-- rpc/backend/client_test.go | 147 +++++ rpc/backend/evm_backend.go | 29 +- rpc/backend/evm_backend_test.go | 975 ++++++++++++++++++++++++++++- rpc/backend/mocks/client.go | 841 +++++++++++++++++++++++++ rpc/backend/query_client_test.go | 160 +++++ rpc/backend/utils.go | 3 +- rpc/namespaces/ethereum/eth/api.go | 96 ++- rpc/types/utils.go | 21 + x/evm/keeper/grpc_query.go | 1 + 11 files changed, 2280 insertions(+), 115 deletions(-) create mode 100644 rpc/backend/client_test.go create mode 100644 rpc/backend/mocks/client.go create mode 100644 rpc/backend/query_client_test.go diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go index 67e815a368..550435c8ce 100644 --- a/rpc/backend/backend.go +++ b/rpc/backend/backend.go @@ -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) diff --git a/rpc/backend/backend_suite_test.go b/rpc/backend/backend_suite_test.go index 4839936c21..88186dfeab 100644 --- a/rpc/backend/backend_suite_test.go +++ b/rpc/backend/backend_suite_test.go @@ -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" ) @@ -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, + ) } diff --git a/rpc/backend/client_test.go b/rpc/backend/client_test.go new file mode 100644 index 0000000000..edf4efb410 --- /dev/null +++ b/rpc/backend/client_test.go @@ -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 +} diff --git a/rpc/backend/evm_backend.go b/rpc/backend/evm_backend.go index e8b87801a0..13e6d7b0f3 100644 --- a/rpc/backend/evm_backend.go +++ b/rpc/backend/evm_backend.go @@ -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. @@ -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 { @@ -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, @@ -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 diff --git a/rpc/backend/evm_backend_test.go b/rpc/backend/evm_backend_test.go index f05323d0aa..332ec3393f 100644 --- a/rpc/backend/evm_backend_test.go +++ b/rpc/backend/evm_backend_test.go @@ -1,30 +1,983 @@ package backend -import "github.com/ethereum/go-ethereum/common/hexutil" +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/tendermint/tendermint/abci/types" + tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" + "google.golang.org/grpc/metadata" + + "github.com/evmos/ethermint/rpc/backend/mocks" + ethrpc "github.com/evmos/ethermint/rpc/types" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" +) func (suite *BackendTestSuite) TestBlockNumber() { testCases := []struct { - mame string - malleate func() + name string + registerMock func() expBlockNumber hexutil.Uint64 expPass bool }{ { - "pass", + "fail - invalid block header height", + func() { + height := int64(1) + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterParamsInvalidHeight(queryClient, &header, int64(height)) + }, + 0x0, + false, + }, + { + "fail - invalid block header", func() { + height := int64(1) + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterParamsInvalidHeader(queryClient, &header, int64(height)) + }, + 0x0, + false, + }, + { + "pass - app state header height 1", + func() { + height := int64(1) + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterParams(queryClient, &header, int64(height)) }, 0x1, true, }, } for _, tc := range testCases { - blockNumber, err := suite.backend.BlockNumber() + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + blockNumber, err := suite.backend.BlockNumber() + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expBlockNumber, blockNumber) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetBlockByNumber() { + var ( + blockRes *tmrpctypes.ResultBlockResults + resBlock *tmrpctypes.ResultBlock + ) + msgEthereumTx, bz := suite.buildEthereumTx() + + testCases := []struct { + name string + blockNumber ethrpc.BlockNumber + fullTx bool + baseFee *big.Int + validator sdk.AccAddress + tx *evmtypes.MsgEthereumTx + txBz []byte + registerMock func(ethrpc.BlockNumber, sdk.Int, sdk.AccAddress, []byte) + expNoop bool + expPass bool + }{ + { + "fail - tendermint block error", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, _ sdk.Int, _ sdk.AccAddress, _ []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, height) + }, + false, + false, + }, + { + "pass - block not found (e.g. request block height that is greater than current one)", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, baseFee sdk.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockNotFound(client, height) + }, + true, + true, + }, + { + "pass - block results error", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, baseFee sdk.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + RegisterBlockResultsError(client, blockNum.Int64()) + }, + true, + true, + }, + { + "pass - without tx", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(blockNum ethrpc.BlockNumber, baseFee sdk.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + { + "pass - with tx", + ethrpc.BlockNumber(1), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + msgEthereumTx, + bz, + func(blockNum ethrpc.BlockNumber, baseFee sdk.Int, validator sdk.AccAddress, txBz []byte) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlock(client, height, txBz) + blockRes, _ = RegisterBlockResults(client, blockNum.Int64()) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.blockNumber, sdk.NewIntFromBigInt(tc.baseFee), tc.validator, tc.txBz) + + block, err := suite.backend.GetBlockByNumber(tc.blockNumber, tc.fullTx) + + if tc.expPass { + if tc.expNoop { + suite.Require().Nil(block) + } else { + expBlock := suite.buildFormattedBlock( + blockRes, + resBlock, + tc.fullTx, + tc.tx, + tc.validator, + tc.baseFee, + ) + suite.Require().Equal(expBlock, block) + } + suite.Require().NoError(err) + + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetBlockByHash() { + var ( + blockRes *tmrpctypes.ResultBlockResults + resBlock *tmrpctypes.ResultBlock + ) + msgEthereumTx, bz := suite.buildEthereumTx() + + block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) + + testCases := []struct { + name string + hash common.Hash + fullTx bool + baseFee *big.Int + validator sdk.AccAddress + tx *evmtypes.MsgEthereumTx + txBz []byte + registerMock func(common.Hash, sdk.Int, sdk.AccAddress, []byte) + expNoop bool + expPass bool + }{ + { + "pass - without tx", + common.BytesToHash(block.Hash()), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + nil, + nil, + func(hash common.Hash, baseFee sdk.Int, validator sdk.AccAddress, txBz []byte) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, txBz) + + blockRes, _ = RegisterBlockResults(client, height) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + { + "pass - with tx", + common.BytesToHash(block.Hash()), + true, + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + msgEthereumTx, + bz, + func(hash common.Hash, baseFee sdk.Int, validator sdk.AccAddress, txBz []byte) { + height := int64(1) + client := suite.backend.clientCtx.Client.(*mocks.Client) + resBlock, _ = RegisterBlockByHash(client, hash, txBz) + + blockRes, _ = RegisterBlockResults(client, height) + RegisterConsensusParams(client, height) + + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + }, + false, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.hash, sdk.NewIntFromBigInt(tc.baseFee), tc.validator, tc.txBz) + + block, err := suite.backend.GetBlockByHash(tc.hash, tc.fullTx) + + if tc.expPass { + if tc.expNoop { + suite.Require().Nil(block) + } else { + expBlock := suite.buildFormattedBlock( + blockRes, + resBlock, + tc.fullTx, + tc.tx, + tc.validator, + tc.baseFee, + ) + suite.Require().Equal(expBlock, block) + } + suite.Require().NoError(err) + + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTendermintBlockByNumber() { + var expResultBlock *tmrpctypes.ResultBlock + + testCases := []struct { + name string + blockNumber ethrpc.BlockNumber + registerMock func(ethrpc.BlockNumber) + found bool + expPass bool + }{ + { + "fail - client error", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockError(client, height) + }, + false, + false, + }, + { + "noop - block not found", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockNotFound(client, height) + }, + false, + true, + }, + { + "fail - blockNum < 0 with app state height error", + ethrpc.BlockNumber(-1), + func(_ ethrpc.BlockNumber) { + appHeight := int64(1) + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterParamsError(queryClient, &header, appHeight) + }, + false, + false, + }, + { + "pass - blockNum < 0 with app state height >= 1", + ethrpc.BlockNumber(-1), + func(blockNum ethrpc.BlockNumber) { + appHeight := int64(1) + var header metadata.MD + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterParams(queryClient, &header, appHeight) + + tmHeight := appHeight + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, tmHeight, nil) + }, + true, + true, + }, + { + "pass - blockNum = 0 (defaults to blockNum = 1 due to a difference between tendermint heights and geth heights", + ethrpc.BlockNumber(0), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, height, nil) + }, + true, + true, + }, + { + "pass - blockNum = 1", + ethrpc.BlockNumber(1), + func(blockNum ethrpc.BlockNumber) { + height := blockNum.Int64() + client := suite.backend.clientCtx.Client.(*mocks.Client) + expResultBlock, _ = RegisterBlock(client, height, nil) + }, + true, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + + tc.registerMock(tc.blockNumber) + resultBlock, err := suite.backend.GetTendermintBlockByNumber(tc.blockNumber) + + if tc.expPass { + suite.Require().NoError(err) + + if !tc.found { + suite.Require().Nil(resultBlock) + } else { + suite.Require().Equal(expResultBlock, resultBlock) + suite.Require().Equal(expResultBlock.Block.Header.Height, resultBlock.Block.Header.Height) + } + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetTendermintBlockResultByNumber() { + var expBlockRes *tmrpctypes.ResultBlockResults + + testCases := []struct { + name string + blockNumber int64 + registerMock func(int64) + expPass bool + }{ + { + "fail", + 1, + func(blockNum int64) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResultsError(client, blockNum) + }, + false, + }, + { + "pass", + 1, + func(blockNum int64) { + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterBlockResults(client, blockNum) + + expBlockRes = &tmrpctypes.ResultBlockResults{ + Height: blockNum, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + } + }, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(tc.blockNumber) + + blockRes, err := suite.backend.GetTendermintBlockResultByNumber(&tc.blockNumber) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(expBlockRes, blockRes) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestBlockBloom() { + testCases := []struct { + name string + blockRes *tmrpctypes.ResultBlockResults + expBlockBloom ethtypes.Bloom + expPass bool + }{ + { + "fail - empty block result", + &tmrpctypes.ResultBlockResults{}, + ethtypes.Bloom{}, + false, + }, + { + "fail - non block bloom event type", + &tmrpctypes.ResultBlockResults{ + EndBlockEvents: []types.Event{{Type: evmtypes.EventTypeEthereumTx}}, + }, + ethtypes.Bloom{}, + false, + }, + { + "fail - nonblock bloom attribute key", + &tmrpctypes.ResultBlockResults{ + EndBlockEvents: []types.Event{ + { + Type: evmtypes.EventTypeBlockBloom, + Attributes: []types.EventAttribute{ + {Key: []byte(evmtypes.AttributeKeyEthereumTxHash)}, + }, + }, + }, + }, + ethtypes.Bloom{}, + false, + }, + { + "pass - nonblock bloom attribute key", + &tmrpctypes.ResultBlockResults{ + EndBlockEvents: []types.Event{ + { + Type: evmtypes.EventTypeBlockBloom, + Attributes: []types.EventAttribute{ + {Key: []byte(bAttributeKeyEthereumBloom)}, + }, + }, + }, + }, + ethtypes.Bloom{}, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + blockBloom, err := suite.backend.BlockBloom(tc.blockRes) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expBlockBloom, blockBloom) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestEthBlockFromTendermint() { + msgEthereumTx, bz := suite.buildEthereumTx() + emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + + testCases := []struct { + name string + baseFee *big.Int + validator sdk.AccAddress + height int64 + resBlock *tmrpctypes.ResultBlock + blockRes *tmrpctypes.ResultBlockResults + fullTx bool + registerMock func(sdk.Int, sdk.AccAddress, int64) + expTxs bool + expPass bool + }{ + { + "pass - block without tx", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(common.Address{}.Bytes()), + int64(1), + &tmrpctypes.ResultBlock{Block: emptyBlock}, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + false, + func(baseFee sdk.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + false, + true, + }, + { + "pass - block with tx - with BaseFee error", + nil, + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdk.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFeeError(queryClient) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + { + "pass - block with tx - with ValidatorAccount error", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(common.Address{}.Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdk.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccountError(queryClient) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + { + "pass - block with tx - with ConsensusParams error - BlockMaxGas defaults to max uint32", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdk.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParamsError(client, height) + }, + true, + true, + }, + { + "pass - block with tx - with ShouldIgnoreGasUsed - empty txs", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 11, + GasUsed: 0, + Log: "no block gas left to run tx: out of gas", + }, + }, + }, + true, + func(baseFee sdk.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + false, + true, + }, + { + "pass - block with tx - non fullTx", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + false, + func(baseFee sdk.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + { + "pass - block with tx", + sdk.NewInt(1).BigInt(), + sdk.AccAddress(tests.GenerateAddress().Bytes()), + int64(1), + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + Height: 1, + TxsResults: []*types.ResponseDeliverTx{{Code: 0, GasUsed: 0}}, + }, + true, + func(baseFee sdk.Int, validator sdk.AccAddress, height int64) { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) + + client := suite.backend.clientCtx.Client.(*mocks.Client) + RegisterConsensusParams(client, height) + }, + true, + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock(sdk.NewIntFromBigInt(tc.baseFee), tc.validator, tc.height) + + block, err := suite.backend.EthBlockFromTendermint(tc.resBlock, tc.blockRes, tc.fullTx) + + var expBlock map[string]interface{} + header := tc.resBlock.Block.Header + gasLimit := int64(^uint32(0)) // for `MaxGas = -1` (DefaultConsensusParams) + gasUsed := new(big.Int).SetUint64(uint64(tc.blockRes.TxsResults[0].GasUsed)) + + root := common.Hash{}.Bytes() + receipt := ethtypes.NewReceipt(root, false, gasUsed.Uint64()) + bloom := ethtypes.CreateBloom(ethtypes.Receipts{receipt}) + + ethRPCTxs := []interface{}{} + + if tc.expTxs { + if tc.fullTx { + rpcTx, err := ethrpc.NewRPCTransaction( + msgEthereumTx.AsTransaction(), + common.BytesToHash(header.Hash()), + uint64(header.Height), + uint64(0), + tc.baseFee, + ) + suite.Require().NoError(err) + ethRPCTxs = []interface{}{rpcTx} + } else { + ethRPCTxs = []interface{}{common.HexToHash(msgEthereumTx.Hash)} + } + } + + expBlock = ethrpc.FormatBlock( + header, + tc.resBlock.Block.Size(), + gasLimit, + gasUsed, + ethRPCTxs, + bloom, + common.BytesToAddress(tc.validator.Bytes()), + tc.baseFee, + ) + + if tc.expPass { + suite.Require().Equal(expBlock, block) + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestBaseFee() { + baseFee := sdk.NewInt(1) + + testCases := []struct { + name string + blockRes *tmrpctypes.ResultBlockResults + registerMock func() + expBaseFee *big.Int + expPass bool + }{ + { + "fail - grpc BaseFee error", + &tmrpctypes.ResultBlockResults{Height: 1}, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc BaseFee error - with non feemarket block event", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: evmtypes.EventTypeBlockBloom, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc BaseFee error - with feemarket block event", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: feemarkettypes.EventTypeFeeMarket, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc BaseFee error - with feemarket block event with wrong attribute value", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: feemarkettypes.EventTypeFeeMarket, + Attributes: []types.EventAttribute{ + {Value: []byte{0x1}}, + }, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFeeError(queryClient) + }, + nil, + false, + }, + { + "fail - grpc baseFee error - with feemarket block event with baseFee attribute value", + &tmrpctypes.ResultBlockResults{ + Height: 1, + BeginBlockEvents: []types.Event{ + { + Type: feemarkettypes.EventTypeFeeMarket, + Attributes: []types.EventAttribute{ + {Value: []byte(baseFee.String())}, + }, + }, + }, + }, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFeeError(queryClient) + }, + baseFee.BigInt(), + true, + }, + { + "fail - base fee or london fork not enabled", + &tmrpctypes.ResultBlockResults{Height: 1}, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFeeDisabled(queryClient) + }, + nil, + true, + }, + { + "pass", + &tmrpctypes.ResultBlockResults{Height: 1}, + func() { + queryClient := suite.backend.queryClient.QueryClient.(*mocks.QueryClient) + RegisterBaseFee(queryClient, baseFee) + }, + baseFee.BigInt(), + true, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset test and queries + tc.registerMock() + + baseFee, err := suite.backend.BaseFee(tc.blockRes) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(tc.expBaseFee, baseFee) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() { + msgEthereumTx, bz := suite.buildEthereumTx() + + testCases := []struct { + name string + resBlock *tmrpctypes.ResultBlock + blockRes *tmrpctypes.ResultBlockResults + expMsgs []*evmtypes.MsgEthereumTx + }{ + { + "tx in not included in block - unsuccessful tx without ExceedBlockGasLimit error", + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 1, + }, + }, + }, + []*evmtypes.MsgEthereumTx(nil), + }, + { + "tx included in block - unsuccessful tx with ExceedBlockGasLimit error", + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 1, + Log: ExceedBlockGasLimitError, + }, + }, + }, + []*evmtypes.MsgEthereumTx{msgEthereumTx}, + }, + { + "pass", + &tmrpctypes.ResultBlock{ + Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil), + }, + &tmrpctypes.ResultBlockResults{ + TxsResults: []*types.ResponseDeliverTx{ + { + Code: 0, + Log: ExceedBlockGasLimitError, + }, + }, + }, + []*evmtypes.MsgEthereumTx{msgEthereumTx}, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + + suite.SetupTest() // reset test and queries - if tc.expPass { - suite.Require().NoError(err) - suite.Require().Equal(tc.expBlockNumber, blockNumber) - } else { - suite.Require().Error(err) - } + msgs := suite.backend.GetEthereumMsgsFromTendermintBlock(tc.resBlock, tc.blockRes) + suite.Require().Equal(tc.expMsgs, msgs) + }) } } diff --git a/rpc/backend/mocks/client.go b/rpc/backend/mocks/client.go new file mode 100644 index 0000000000..fa60f0e03c --- /dev/null +++ b/rpc/backend/mocks/client.go @@ -0,0 +1,841 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package mocks + +import ( + bytes "github.com/tendermint/tendermint/libs/bytes" + client "github.com/tendermint/tendermint/rpc/client" + + context "context" + + coretypes "github.com/tendermint/tendermint/rpc/core/types" + + log "github.com/tendermint/tendermint/libs/log" + + mock "github.com/stretchr/testify/mock" + + types "github.com/tendermint/tendermint/types" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +// ABCIInfo provides a mock function with given fields: _a0 +func (_m *Client) ABCIInfo(_a0 context.Context) (*coretypes.ResultABCIInfo, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultABCIInfo + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultABCIInfo); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultABCIInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ABCIQuery provides a mock function with given fields: ctx, path, data +func (_m *Client) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { + ret := _m.Called(ctx, path, data) + + var r0 *coretypes.ResultABCIQuery + if rf, ok := ret.Get(0).(func(context.Context, string, bytes.HexBytes) *coretypes.ResultABCIQuery); ok { + r0 = rf(ctx, path, data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultABCIQuery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, bytes.HexBytes) error); ok { + r1 = rf(ctx, path, data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ABCIQueryWithOptions provides a mock function with given fields: ctx, path, data, opts +func (_m *Client) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { + ret := _m.Called(ctx, path, data, opts) + + var r0 *coretypes.ResultABCIQuery + if rf, ok := ret.Get(0).(func(context.Context, string, bytes.HexBytes, client.ABCIQueryOptions) *coretypes.ResultABCIQuery); ok { + r0 = rf(ctx, path, data, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultABCIQuery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, bytes.HexBytes, client.ABCIQueryOptions) error); ok { + r1 = rf(ctx, path, data, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Block provides a mock function with given fields: ctx, height +func (_m *Client) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultBlock + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultBlock); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlock) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockByHash provides a mock function with given fields: ctx, hash +func (_m *Client) BlockByHash(ctx context.Context, hash []byte) (*coretypes.ResultBlock, error) { + ret := _m.Called(ctx, hash) + + var r0 *coretypes.ResultBlock + if rf, ok := ret.Get(0).(func(context.Context, []byte) *coretypes.ResultBlock); ok { + r0 = rf(ctx, hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlock) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + r1 = rf(ctx, hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockResults provides a mock function with given fields: ctx, height +func (_m *Client) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultBlockResults + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultBlockResults); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlockResults) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockSearch provides a mock function with given fields: ctx, query, page, perPage, orderBy +func (_m *Client) BlockSearch(ctx context.Context, query string, page *int, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) { + ret := _m.Called(ctx, query, page, perPage, orderBy) + + var r0 *coretypes.ResultBlockSearch + if rf, ok := ret.Get(0).(func(context.Context, string, *int, *int, string) *coretypes.ResultBlockSearch); ok { + r0 = rf(ctx, query, page, perPage, orderBy) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlockSearch) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, *int, *int, string) error); ok { + r1 = rf(ctx, query, page, perPage, orderBy) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BlockchainInfo provides a mock function with given fields: ctx, minHeight, maxHeight +func (_m *Client) BlockchainInfo(ctx context.Context, minHeight int64, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { + ret := _m.Called(ctx, minHeight, maxHeight) + + var r0 *coretypes.ResultBlockchainInfo + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *coretypes.ResultBlockchainInfo); ok { + r0 = rf(ctx, minHeight, maxHeight) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlockchainInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok { + r1 = rf(ctx, minHeight, maxHeight) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastEvidence provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastEvidence(_a0 context.Context, _a1 types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastEvidence + if rf, ok := ret.Get(0).(func(context.Context, types.Evidence) *coretypes.ResultBroadcastEvidence); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastEvidence) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Evidence) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastTxAsync provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastTxAsync(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultBroadcastTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastTx + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultBroadcastTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastTxCommit provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastTxCommit(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastTxCommit + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultBroadcastTxCommit); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastTxCommit) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BroadcastTxSync provides a mock function with given fields: _a0, _a1 +func (_m *Client) BroadcastTxSync(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultBroadcastTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultBroadcastTx + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultBroadcastTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBroadcastTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CheckTx provides a mock function with given fields: _a0, _a1 +func (_m *Client) CheckTx(_a0 context.Context, _a1 types.Tx) (*coretypes.ResultCheckTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultCheckTx + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) *coretypes.ResultCheckTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultCheckTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, types.Tx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Commit provides a mock function with given fields: ctx, height +func (_m *Client) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultCommit + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultCommit); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultCommit) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ConsensusParams provides a mock function with given fields: ctx, height +func (_m *Client) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { + ret := _m.Called(ctx, height) + + var r0 *coretypes.ResultConsensusParams + if rf, ok := ret.Get(0).(func(context.Context, *int64) *coretypes.ResultConsensusParams); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultConsensusParams) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ConsensusState provides a mock function with given fields: _a0 +func (_m *Client) ConsensusState(_a0 context.Context) (*coretypes.ResultConsensusState, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultConsensusState + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultConsensusState); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultConsensusState) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DumpConsensusState provides a mock function with given fields: _a0 +func (_m *Client) DumpConsensusState(_a0 context.Context) (*coretypes.ResultDumpConsensusState, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultDumpConsensusState + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultDumpConsensusState); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultDumpConsensusState) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Genesis provides a mock function with given fields: _a0 +func (_m *Client) Genesis(_a0 context.Context) (*coretypes.ResultGenesis, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultGenesis + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultGenesis); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultGenesis) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenesisChunked provides a mock function with given fields: _a0, _a1 +func (_m *Client) GenesisChunked(_a0 context.Context, _a1 uint) (*coretypes.ResultGenesisChunk, error) { + ret := _m.Called(_a0, _a1) + + var r0 *coretypes.ResultGenesisChunk + if rf, ok := ret.Get(0).(func(context.Context, uint) *coretypes.ResultGenesisChunk); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultGenesisChunk) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, uint) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Health provides a mock function with given fields: _a0 +func (_m *Client) Health(_a0 context.Context) (*coretypes.ResultHealth, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultHealth + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultHealth); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultHealth) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsRunning provides a mock function with given fields: +func (_m *Client) IsRunning() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// NetInfo provides a mock function with given fields: _a0 +func (_m *Client) NetInfo(_a0 context.Context) (*coretypes.ResultNetInfo, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultNetInfo + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultNetInfo); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultNetInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NumUnconfirmedTxs provides a mock function with given fields: _a0 +func (_m *Client) NumUnconfirmedTxs(_a0 context.Context) (*coretypes.ResultUnconfirmedTxs, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultUnconfirmedTxs + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultUnconfirmedTxs); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultUnconfirmedTxs) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OnReset provides a mock function with given fields: +func (_m *Client) OnReset() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OnStart provides a mock function with given fields: +func (_m *Client) OnStart() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OnStop provides a mock function with given fields: +func (_m *Client) OnStop() { + _m.Called() +} + +// Quit provides a mock function with given fields: +func (_m *Client) Quit() <-chan struct{} { + ret := _m.Called() + + var r0 <-chan struct{} + if rf, ok := ret.Get(0).(func() <-chan struct{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan struct{}) + } + } + + return r0 +} + +// Reset provides a mock function with given fields: +func (_m *Client) Reset() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetLogger provides a mock function with given fields: _a0 +func (_m *Client) SetLogger(_a0 log.Logger) { + _m.Called(_a0) +} + +// Start provides a mock function with given fields: +func (_m *Client) Start() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Status provides a mock function with given fields: _a0 +func (_m *Client) Status(_a0 context.Context) (*coretypes.ResultStatus, error) { + ret := _m.Called(_a0) + + var r0 *coretypes.ResultStatus + if rf, ok := ret.Get(0).(func(context.Context) *coretypes.ResultStatus); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultStatus) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Stop provides a mock function with given fields: +func (_m *Client) Stop() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// String provides a mock function with given fields: +func (_m *Client) String() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Subscribe provides a mock function with given fields: ctx, subscriber, query, outCapacity +func (_m *Client) Subscribe(ctx context.Context, subscriber string, query string, outCapacity ...int) (<-chan coretypes.ResultEvent, error) { + _va := make([]interface{}, len(outCapacity)) + for _i := range outCapacity { + _va[_i] = outCapacity[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, subscriber, query) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 <-chan coretypes.ResultEvent + if rf, ok := ret.Get(0).(func(context.Context, string, string, ...int) <-chan coretypes.ResultEvent); ok { + r0 = rf(ctx, subscriber, query, outCapacity...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan coretypes.ResultEvent) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string, ...int) error); ok { + r1 = rf(ctx, subscriber, query, outCapacity...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Tx provides a mock function with given fields: ctx, hash, prove +func (_m *Client) Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error) { + ret := _m.Called(ctx, hash, prove) + + var r0 *coretypes.ResultTx + if rf, ok := ret.Get(0).(func(context.Context, []byte, bool) *coretypes.ResultTx); ok { + r0 = rf(ctx, hash, prove) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, []byte, bool) error); ok { + r1 = rf(ctx, hash, prove) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TxSearch provides a mock function with given fields: ctx, query, prove, page, perPage, orderBy +func (_m *Client) TxSearch(ctx context.Context, query string, prove bool, page *int, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) { + ret := _m.Called(ctx, query, prove, page, perPage, orderBy) + + var r0 *coretypes.ResultTxSearch + if rf, ok := ret.Get(0).(func(context.Context, string, bool, *int, *int, string) *coretypes.ResultTxSearch); ok { + r0 = rf(ctx, query, prove, page, perPage, orderBy) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultTxSearch) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, bool, *int, *int, string) error); ok { + r1 = rf(ctx, query, prove, page, perPage, orderBy) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnconfirmedTxs provides a mock function with given fields: ctx, limit +func (_m *Client) UnconfirmedTxs(ctx context.Context, limit *int) (*coretypes.ResultUnconfirmedTxs, error) { + ret := _m.Called(ctx, limit) + + var r0 *coretypes.ResultUnconfirmedTxs + if rf, ok := ret.Get(0).(func(context.Context, *int) *coretypes.ResultUnconfirmedTxs); ok { + r0 = rf(ctx, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultUnconfirmedTxs) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int) error); ok { + r1 = rf(ctx, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Unsubscribe provides a mock function with given fields: ctx, subscriber, query +func (_m *Client) Unsubscribe(ctx context.Context, subscriber string, query string) error { + ret := _m.Called(ctx, subscriber, query) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, subscriber, query) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnsubscribeAll provides a mock function with given fields: ctx, subscriber +func (_m *Client) UnsubscribeAll(ctx context.Context, subscriber string) error { + ret := _m.Called(ctx, subscriber) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, subscriber) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Validators provides a mock function with given fields: ctx, height, page, perPage +func (_m *Client) Validators(ctx context.Context, height *int64, page *int, perPage *int) (*coretypes.ResultValidators, error) { + ret := _m.Called(ctx, height, page, perPage) + + var r0 *coretypes.ResultValidators + if rf, ok := ret.Get(0).(func(context.Context, *int64, *int, *int) *coretypes.ResultValidators); ok { + r0 = rf(ctx, height, page, perPage) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultValidators) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *int64, *int, *int) error); ok { + r1 = rf(ctx, height, page, perPage) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t mockConstructorTestingTNewClient) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/rpc/backend/query_client_test.go b/rpc/backend/query_client_test.go new file mode 100644 index 0000000000..f7e97dc48b --- /dev/null +++ b/rpc/backend/query_client_test.go @@ -0,0 +1,160 @@ +package backend + +import ( + "fmt" + "strconv" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + "github.com/evmos/ethermint/rpc/backend/mocks" + rpc "github.com/evmos/ethermint/rpc/types" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" + mock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// QueryClient defines a mocked object that implements the ethermint GRPC +// QueryClient interface. It allows for performing QueryClient queries without having +// to run a ethermint GRPC server. +// +// To use a mock method it has to be registered in a given test. +var _ evmtypes.QueryClient = &mocks.QueryClient{} + +// Params +func RegisterParams(queryClient *mocks.QueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &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, fmt.Sprint(height)) + *arg.HeaderAddr = h + }) +} + +func RegisterParamsInvalidHeader(queryClient *mocks.QueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &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{} + *arg.HeaderAddr = h + }) +} + +func RegisterParamsInvalidHeight(queryClient *mocks.QueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &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, "invalid") + *arg.HeaderAddr = h + }) +} + +// Params returns error +func RegisterParamsError(queryClient *mocks.QueryClient, header *metadata.MD, height int64) { + queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(header)). + Return(nil, sdkerrors.ErrInvalidRequest) +} + +func TestRegisterParams(t *testing.T) { + queryClient := mocks.NewQueryClient(t) + var header metadata.MD + height := int64(1) + RegisterParams(queryClient, &header, height) + + _, err := queryClient.Params(rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(&header)) + require.NoError(t, err) + blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader) + headerHeight, err := strconv.ParseInt(blockHeightHeader[0], 10, 64) + require.NoError(t, err) + require.Equal(t, height, headerHeight) +} + +func TestRegisterParamsError(t *testing.T) { + queryClient := mocks.NewQueryClient(t) + RegisterBaseFeeError(queryClient) + _, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Error(t, err) +} + +// BaseFee +func RegisterBaseFee(queryClient *mocks.QueryClient, baseFee sdk.Int) { + queryClient.On("BaseFee", rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}). + Return(&evmtypes.QueryBaseFeeResponse{BaseFee: &baseFee}, nil) +} + +// Base fee returns error +func RegisterBaseFeeError(queryClient *mocks.QueryClient) { + queryClient.On("BaseFee", rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}). + Return(&evmtypes.QueryBaseFeeResponse{}, evmtypes.ErrInvalidBaseFee) +} + +// Base fee not enabled +func RegisterBaseFeeDisabled(queryClient *mocks.QueryClient) { + queryClient.On("BaseFee", rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}). + Return(&evmtypes.QueryBaseFeeResponse{}, nil) +} + +func TestRegisterBaseFee(t *testing.T) { + baseFee := sdk.NewInt(1) + queryClient := mocks.NewQueryClient(t) + RegisterBaseFee(queryClient, baseFee) + res, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Equal(t, &evmtypes.QueryBaseFeeResponse{BaseFee: &baseFee}, res) + require.NoError(t, err) +} + +func TestRegisterBaseFeeError(t *testing.T) { + queryClient := mocks.NewQueryClient(t) + RegisterBaseFeeError(queryClient) + res, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Equal(t, &evmtypes.QueryBaseFeeResponse{}, res) + require.Error(t, err) +} + +func TestRegisterBaseFeeDisabled(t *testing.T) { + queryClient := mocks.NewQueryClient(t) + RegisterBaseFeeDisabled(queryClient) + res, err := queryClient.BaseFee(rpc.ContextWithHeight(1), &evmtypes.QueryBaseFeeRequest{}) + require.Equal(t, &evmtypes.QueryBaseFeeResponse{}, res) + require.NoError(t, err) +} + +// ValidatorAccount +func RegisterValidatorAccount(queryClient *mocks.QueryClient, validator sdk.AccAddress) { + queryClient.On("ValidatorAccount", rpc.ContextWithHeight(1), &evmtypes.QueryValidatorAccountRequest{}). + Return( + &evmtypes.QueryValidatorAccountResponse{ + AccountAddress: validator.String(), + }, + nil, + ) +} + +func RegisterValidatorAccountError(queryClient *mocks.QueryClient) { + queryClient.On("ValidatorAccount", rpc.ContextWithHeight(1), &evmtypes.QueryValidatorAccountRequest{}). + Return(nil, status.Error(codes.InvalidArgument, "empty request")) +} + +func TestRegisterValidatorAccount(t *testing.T) { + queryClient := mocks.NewQueryClient(t) + + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) + RegisterValidatorAccount(queryClient, validator) + res, err := queryClient.ValidatorAccount(rpc.ContextWithHeight(1), &evmtypes.QueryValidatorAccountRequest{}) + require.Equal(t, &evmtypes.QueryValidatorAccountResponse{AccountAddress: validator.String()}, res) + require.NoError(t, err) +} diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go index c9f878058c..1f4bafa85c 100644 --- a/rpc/backend/utils.go +++ b/rpc/backend/utils.go @@ -380,7 +380,8 @@ func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool { return strings.Contains(res.Log, ExceedBlockGasLimitError) } -// TxSuccessOrExceedsBlockGasLimit returns if the tx should be included in json-rpc responses +// TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful +// or if it failed with an ExceedBlockGasLimit error func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool { return res.Code == 0 || TxExceedBlockGasLimit(res) } diff --git a/rpc/namespaces/ethereum/eth/api.go b/rpc/namespaces/ethereum/eth/api.go index 607ea4811c..b72687b8c5 100644 --- a/rpc/namespaces/ethereum/eth/api.go +++ b/rpc/namespaces/ethereum/eth/api.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -38,6 +37,7 @@ import ( "github.com/evmos/ethermint/crypto/hd" "github.com/evmos/ethermint/rpc/backend" + rpctypes "github.com/evmos/ethermint/rpc/types" ethermint "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" @@ -583,26 +583,6 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) return txHash, nil } -// checkTxFee is an internal function used to check whether the fee of -// the given transaction is _reasonable_(under the cap). -func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { - // Short circuit if there is no cap for transaction fee at all. - if cap == 0 { - return nil - } - totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))) - // 1 photon in 10^18 aphoton - oneToken := new(big.Float).SetInt(big.NewInt(params.Ether)) - // quo = rounded(x/y) - feeEth := new(big.Float).Quo(totalfee, oneToken) - // no need to check error from parsing - feeFloat, _ := feeEth.Float64() - if feeFloat > cap { - return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap) - } - return nil -} - // Resend accepts an existing transaction and a new gas price and limit. It will remove // the given transaction from the pool and reinsert it with the new gas price and limit. func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { @@ -627,7 +607,7 @@ func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, g if gasLimit != nil { gas = uint64(*gasLimit) } - if err := checkTxFee(price, gas, e.backend.RPCTxFeeCap()); err != nil { + if err := rpctypes.CheckTxFee(price, gas, e.backend.RPCTxFeeCap()); err != nil { return common.Hash{}, err } @@ -757,6 +737,42 @@ func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransac return e.backend.GetTransactionByHash(hash) } +// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. +func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { + e.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx) + + block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes()) + if err != nil { + e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error()) + return nil, nil + } + + if block.Block == nil { + e.logger.Debug("block not found", "hash", hash.Hex()) + return nil, nil + } + + return e.getTransactionByBlockAndIndex(block, idx) +} + +// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. +func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { + e.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx) + + block, err := e.backend.GetTendermintBlockByNumber(blockNum) + if err != nil { + e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error()) + return nil, nil + } + + if block.Block == nil { + e.logger.Debug("block not found", "height", blockNum.Int64()) + return nil, nil + } + + return e.getTransactionByBlockAndIndex(block, idx) +} + // getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`. func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { blockRes, err := e.backend.GetTendermintBlockResultByNumber(&block.Block.Height) @@ -817,42 +833,6 @@ func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, ) } -// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. -func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { - e.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx) - - block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes()) - if err != nil { - e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error()) - return nil, nil - } - - if block.Block == nil { - e.logger.Debug("block not found", "hash", hash.Hex()) - return nil, nil - } - - return e.getTransactionByBlockAndIndex(block, idx) -} - -// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. -func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { - e.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx) - - block, err := e.backend.GetTendermintBlockByNumber(blockNum) - if err != nil { - e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error()) - return nil, nil - } - - if block.Block == nil { - e.logger.Debug("block not found", "height", blockNum.Int64()) - return nil, nil - } - - return e.getTransactionByBlockAndIndex(block, idx) -} - // GetTransactionReceipt returns the transaction receipt identified by hash. func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { hexTx := hash.Hex() diff --git a/rpc/types/utils.go b/rpc/types/utils.go index f8a0c7e11c..7e0aebd938 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -19,6 +19,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) // RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes. @@ -222,3 +223,23 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int { } return nil } + +// CheckTxFee is an internal function used to check whether the fee of +// the given transaction is _reasonable_(under the cap). +func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error { + // Short circuit if there is no cap for transaction fee at all. + if cap == 0 { + return nil + } + totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))) + // 1 photon in 10^18 aphoton + oneToken := new(big.Float).SetInt(big.NewInt(params.Ether)) + // quo = rounded(x/y) + feeEth := new(big.Float).Quo(totalfee, oneToken) + // no need to check error from parsing + feeFloat, _ := feeEth.Float64() + if feeFloat > cap { + return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap) + } + return nil +} diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index f192f1119b..9255a6c617 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -89,6 +89,7 @@ func (k Keeper) CosmosAccount(c context.Context, req *types.QueryCosmosAccountRe return &res, nil } +// ValidatorAccount implements the Query/Balance gRPC method func (k Keeper) ValidatorAccount(c context.Context, req *types.QueryValidatorAccountRequest) (*types.QueryValidatorAccountResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request")