forked from cerc-io/laconicd-deprecated
update fork
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetCode returns the contract code at the given address and block number.
|
||||
func (b *Backend) GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||||
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &evmtypes.QueryCodeRequest{
|
||||
Address: address.String(),
|
||||
}
|
||||
|
||||
res, err := b.queryClient.Code(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Code, nil
|
||||
}
|
||||
|
||||
// GetProof returns an account object with proof and any storage proofs
|
||||
func (b *Backend) GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error) {
|
||||
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
height := blockNum.Int64()
|
||||
_, err = b.TendermintBlockByNumber(blockNum)
|
||||
if err != nil {
|
||||
// the error message imitates geth behavior
|
||||
return nil, errors.New("header not found")
|
||||
}
|
||||
ctx := rpctypes.ContextWithHeight(height)
|
||||
|
||||
// if the height is equal to zero, meaning the query condition of the block is either "pending" or "latest"
|
||||
if height == 0 {
|
||||
bn, err := b.BlockNumber()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bn > math.MaxInt64 {
|
||||
return nil, fmt.Errorf("not able to query block number greater than MaxInt64")
|
||||
}
|
||||
|
||||
height = int64(bn)
|
||||
}
|
||||
|
||||
clientCtx := b.clientCtx.WithHeight(height)
|
||||
|
||||
// query storage proofs
|
||||
storageProofs := make([]rpctypes.StorageResult, len(storageKeys))
|
||||
|
||||
for i, key := range storageKeys {
|
||||
hexKey := common.HexToHash(key)
|
||||
valueBz, proof, err := b.queryClient.GetProof(clientCtx, evmtypes.StoreKey, evmtypes.StateKey(address, hexKey.Bytes()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for proof
|
||||
var proofStr string
|
||||
if proof != nil {
|
||||
proofStr = proof.String()
|
||||
}
|
||||
|
||||
storageProofs[i] = rpctypes.StorageResult{
|
||||
Key: key,
|
||||
Value: (*hexutil.Big)(new(big.Int).SetBytes(valueBz)),
|
||||
Proof: []string{proofStr},
|
||||
}
|
||||
}
|
||||
|
||||
// query EVM account
|
||||
req := &evmtypes.QueryAccountRequest{
|
||||
Address: address.String(),
|
||||
}
|
||||
|
||||
res, err := b.queryClient.Account(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// query account proofs
|
||||
accountKey := authtypes.AddressStoreKey(sdk.AccAddress(address.Bytes()))
|
||||
_, proof, err := b.queryClient.GetProof(clientCtx, authtypes.StoreKey, accountKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for proof
|
||||
var accProofStr string
|
||||
if proof != nil {
|
||||
accProofStr = proof.String()
|
||||
}
|
||||
|
||||
balance, ok := sdkmath.NewIntFromString(res.Balance)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid balance")
|
||||
}
|
||||
|
||||
return &rpctypes.AccountResult{
|
||||
Address: address,
|
||||
AccountProof: []string{accProofStr},
|
||||
Balance: (*hexutil.Big)(balance.BigInt()),
|
||||
CodeHash: common.HexToHash(res.CodeHash),
|
||||
Nonce: hexutil.Uint64(res.Nonce),
|
||||
StorageHash: common.Hash{}, // NOTE: Ethermint doesn't have a storage hash. TODO: implement?
|
||||
StorageProof: storageProofs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetStorageAt returns the contract storage at the given address, block number, and key.
|
||||
func (b *Backend) GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||||
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &evmtypes.QueryStorageRequest{
|
||||
Address: address.String(),
|
||||
Key: key,
|
||||
}
|
||||
|
||||
res, err := b.queryClient.Storage(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value := common.HexToHash(res.Value)
|
||||
return value.Bytes(), nil
|
||||
}
|
||||
|
||||
// GetBalance returns the provided account's balance up to the provided block number.
|
||||
func (b *Backend) GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error) {
|
||||
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &evmtypes.QueryBalanceRequest{
|
||||
Address: address.String(),
|
||||
}
|
||||
|
||||
_, err = b.TendermintBlockByNumber(blockNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := b.queryClient.Balance(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val, ok := sdkmath.NewIntFromString(res.Balance)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid balance")
|
||||
}
|
||||
|
||||
// balance can only be negative in case of pruned node
|
||||
if val.IsNegative() {
|
||||
return nil, errors.New("couldn't fetch balance. Node state is pruned")
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(val.BigInt()), nil
|
||||
}
|
||||
|
||||
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
|
||||
func (b *Backend) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) {
|
||||
// Get nonce (sequence) from account
|
||||
from := sdk.AccAddress(address.Bytes())
|
||||
accRet := b.clientCtx.AccountRetriever
|
||||
|
||||
err := accRet.EnsureExists(b.clientCtx, from)
|
||||
if err != nil {
|
||||
// account doesn't exist yet, return 0
|
||||
n := hexutil.Uint64(0)
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
includePending := blockNum == rpctypes.EthPendingBlockNumber
|
||||
nonce, err := b.getAccountNonce(address, includePending, blockNum.Int64(), b.logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n := hexutil.Uint64(nonce)
|
||||
return &n, nil
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/cerc-io/laconicd/crypto/hd"
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
"github.com/cerc-io/laconicd/server/config"
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
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/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// BackendI implements the Cosmos and EVM backend.
|
||||
type BackendI interface { //nolint: revive
|
||||
CosmosBackend
|
||||
EVMBackend
|
||||
}
|
||||
|
||||
// CosmosBackend implements the functionality shared within cosmos namespaces
|
||||
// as defined by Wallet Connect V2: https://docs.walletconnect.com/2.0/json-rpc/cosmos.
|
||||
// Implemented by Backend.
|
||||
type CosmosBackend interface { // TODO: define
|
||||
// GetAccounts()
|
||||
// SignDirect()
|
||||
// SignAmino()
|
||||
}
|
||||
|
||||
// EVMBackend implements the functionality shared within ethereum namespaces
|
||||
// as defined by EIP-1474: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md
|
||||
// Implemented by Backend.
|
||||
type EVMBackend interface {
|
||||
// Node specific queries
|
||||
Accounts() ([]common.Address, error)
|
||||
Syncing() (interface{}, error)
|
||||
SetEtherbase(etherbase common.Address) bool
|
||||
SetGasPrice(gasPrice hexutil.Big) bool
|
||||
ImportRawKey(privkey, password string) (common.Address, error)
|
||||
ListAccounts() ([]common.Address, error)
|
||||
NewMnemonic(uid string, language keyring.Language, hdPath, bip39Passphrase string, algo keyring.SignatureAlgo) (*keyring.Record, error)
|
||||
UnprotectedAllowed() bool
|
||||
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
|
||||
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
|
||||
RPCTxFeeCap() float64 // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for send-transaction variants. The unit is ether.
|
||||
RPCMinGasPrice() int64
|
||||
|
||||
// Sign Tx
|
||||
Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error)
|
||||
SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error)
|
||||
SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error)
|
||||
|
||||
// Blocks Info
|
||||
BlockNumber() (hexutil.Uint64, error)
|
||||
GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
||||
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
|
||||
GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint
|
||||
GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint
|
||||
TendermintBlockByNumber(blockNum rpctypes.BlockNumber) (*tmrpctypes.ResultBlock, error)
|
||||
TendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error)
|
||||
TendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error)
|
||||
BlockNumberFromTendermint(blockNrOrHash rpctypes.BlockNumberOrHash) (rpctypes.BlockNumber, error)
|
||||
BlockNumberFromTendermintByHash(blockHash common.Hash) (*big.Int, error)
|
||||
EthMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx
|
||||
BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (ethtypes.Bloom, error)
|
||||
HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error)
|
||||
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
|
||||
RPCBlockFromTendermintBlock(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults, fullTx bool) (map[string]interface{}, error)
|
||||
EthBlockByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Block, error)
|
||||
EthBlockFromTendermintBlock(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) (*ethtypes.Block, error)
|
||||
|
||||
// Account Info
|
||||
GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error)
|
||||
GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error)
|
||||
GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error)
|
||||
GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error)
|
||||
GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error)
|
||||
|
||||
// Chain Info
|
||||
ChainID() (*hexutil.Big, error)
|
||||
ChainConfig() *params.ChainConfig
|
||||
GlobalMinGasPrice() (sdk.Dec, error)
|
||||
BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error)
|
||||
CurrentHeader() *ethtypes.Header
|
||||
PendingTransactions() ([]*sdk.Tx, error)
|
||||
GetCoinbase() (sdk.AccAddress, error)
|
||||
FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error)
|
||||
SuggestGasTipCap(baseFee *big.Int) (*big.Int, error)
|
||||
|
||||
// Tx Info
|
||||
GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error)
|
||||
GetTxByEthHash(txHash common.Hash) (*ethermint.TxResult, error)
|
||||
GetTxByTxIndex(height int64, txIndex uint) (*ethermint.TxResult, error)
|
||||
GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||
GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error)
|
||||
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||
GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||
|
||||
// Send Transaction
|
||||
Resend(args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error)
|
||||
SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
|
||||
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
|
||||
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error)
|
||||
DoCall(args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber) (*evmtypes.MsgEthereumTxResponse, error)
|
||||
GasPrice() (*hexutil.Big, error)
|
||||
|
||||
// Filter API
|
||||
GetLogs(hash common.Hash) ([][]*ethtypes.Log, error)
|
||||
GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error)
|
||||
BloomStatus() (uint64, uint64)
|
||||
|
||||
// Tracing
|
||||
TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error)
|
||||
TraceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConfig, block *tmrpctypes.ResultBlock) ([]*evmtypes.TxTraceResult, error)
|
||||
}
|
||||
|
||||
var _ BackendI = (*Backend)(nil)
|
||||
|
||||
var bAttributeKeyEthereumBloom = []byte(evmtypes.AttributeKeyEthereumBloom)
|
||||
|
||||
// Backend implements the BackendI interface
|
||||
type Backend struct {
|
||||
ctx context.Context
|
||||
clientCtx client.Context
|
||||
queryClient *rpctypes.QueryClient // gRPC query client
|
||||
logger log.Logger
|
||||
chainID *big.Int
|
||||
cfg config.Config
|
||||
allowUnprotectedTxs bool
|
||||
indexer ethermint.EVMTxIndexer
|
||||
}
|
||||
|
||||
// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
|
||||
func NewBackend(
|
||||
ctx *server.Context,
|
||||
logger log.Logger,
|
||||
clientCtx client.Context,
|
||||
allowUnprotectedTxs bool,
|
||||
indexer ethermint.EVMTxIndexer,
|
||||
) *Backend {
|
||||
chainID, err := ethermint.ParseChainID(clientCtx.ChainID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
appConf, err := config.GetConfig(ctx.Viper)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
algos, _ := clientCtx.Keyring.SupportedAlgorithms()
|
||||
if !algos.Contains(hd.EthSecp256k1) {
|
||||
kr, err := keyring.New(
|
||||
sdk.KeyringServiceName(),
|
||||
viper.GetString(flags.FlagKeyringBackend),
|
||||
clientCtx.KeyringDir,
|
||||
clientCtx.Input,
|
||||
clientCtx.Codec,
|
||||
hd.EthSecp256k1Option(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
clientCtx = clientCtx.WithKeyring(kr)
|
||||
}
|
||||
|
||||
return &Backend{
|
||||
ctx: context.Background(),
|
||||
clientCtx: clientCtx,
|
||||
queryClient: rpctypes.NewQueryClient(clientCtx),
|
||||
logger: logger.With("module", "backend"),
|
||||
chainID: chainID,
|
||||
cfg: appConf,
|
||||
allowUnprotectedTxs: allowUnprotectedTxs,
|
||||
indexer: indexer,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
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"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/cerc-io/laconicd/app"
|
||||
"github.com/cerc-io/laconicd/crypto/hd"
|
||||
"github.com/cerc-io/laconicd/encoding"
|
||||
"github.com/cerc-io/laconicd/indexer"
|
||||
"github.com/cerc-io/laconicd/rpc/backend/mocks"
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
)
|
||||
|
||||
type BackendTestSuite struct {
|
||||
suite.Suite
|
||||
backend *Backend
|
||||
}
|
||||
|
||||
func TestBackendTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(BackendTestSuite))
|
||||
}
|
||||
|
||||
// SetupTest is executed before every BackendTestSuite test
|
||||
func (suite *BackendTestSuite) SetupTest() {
|
||||
ctx := server.NewDefaultContext()
|
||||
ctx.Viper.Set("telemetry.global-labels", []interface{}{})
|
||||
|
||||
baseDir := suite.T().TempDir()
|
||||
nodeDirName := fmt.Sprintf("node")
|
||||
clientDir := filepath.Join(baseDir, nodeDirName, "evmoscli")
|
||||
keyRing, err := suite.generateTestKeyring(clientDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
|
||||
clientCtx := client.Context{}.WithChainID("ethermint_9000-1").
|
||||
WithHeight(1).
|
||||
WithTxConfig(encodingConfig.TxConfig).
|
||||
WithKeyringDir(clientDir).
|
||||
WithKeyring(keyRing)
|
||||
|
||||
allowUnprotectedTxs := false
|
||||
|
||||
idxer := indexer.NewKVIndexer(dbm.NewMemDB(), ctx.Logger, clientCtx)
|
||||
|
||||
suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, idxer)
|
||||
suite.backend.queryClient.QueryClient = mocks.NewQueryClient(suite.T())
|
||||
suite.backend.clientCtx.Client = mocks.NewClient(suite.T())
|
||||
suite.backend.ctx = rpctypes.ContextWithHeight(1)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 := rpctypes.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)}
|
||||
}
|
||||
}
|
||||
|
||||
return rpctypes.FormatBlock(
|
||||
header,
|
||||
resBlock.Block.Size(),
|
||||
gasLimit,
|
||||
gasUsed,
|
||||
ethRPCTxs,
|
||||
bloom,
|
||||
common.BytesToAddress(validator.Bytes()),
|
||||
baseFee,
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *BackendTestSuite) generateTestKeyring(clientDir string) (keyring.Keyring, error) {
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
encCfg := encoding.MakeConfig(app.ModuleBasics)
|
||||
return keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, encCfg.Codec, []keyring.Option{hd.EthSecp256k1Option()}...)
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/pkg/errors"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// BlockNumber returns the current block number in abci app state. Because abci
|
||||
// app state could lag behind from tendermint latest block, it's more stable for
|
||||
// the client to use the latest block number in abci app state than tendermint
|
||||
// rpc.
|
||||
func (b *Backend) BlockNumber() (hexutil.Uint64, error) {
|
||||
// do any grpc query, ignore the response and use the returned block height
|
||||
var header metadata.MD
|
||||
_, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{}, grpc.Header(&header))
|
||||
if err != nil {
|
||||
return hexutil.Uint64(0), err
|
||||
}
|
||||
|
||||
blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader)
|
||||
if headerLen := len(blockHeightHeader); headerLen != 1 {
|
||||
return 0, fmt.Errorf("unexpected '%s' gRPC header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, headerLen, 1)
|
||||
}
|
||||
|
||||
height, err := strconv.ParseUint(blockHeightHeader[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to parse block height: %w", err)
|
||||
}
|
||||
|
||||
return hexutil.Uint64(height), nil
|
||||
}
|
||||
|
||||
// GetBlockByNumber returns the JSON-RPC compatible Ethereum block identified by
|
||||
// block number. Depending on fullTx it either returns the full transaction
|
||||
// objects or if false only the hashes of the transactions.
|
||||
func (b *Backend) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
|
||||
resBlock, err := b.TendermintBlockByNumber(blockNum)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// return if requested block height is greater than the current one
|
||||
if resBlock == nil || resBlock.Block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to fetch block result from Tendermint", "height", blockNum, "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res, err := b.RPCBlockFromTendermintBlock(resBlock, blockRes, fullTx)
|
||||
if err != nil {
|
||||
b.logger.Debug("GetEthBlockFromTendermint failed", "height", blockNum, "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetBlockByHash returns the JSON-RPC compatible Ethereum block identified by
|
||||
// hash.
|
||||
func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
||||
resBlock, err := b.TendermintBlockByHash(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBlock == nil {
|
||||
// block not found
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to fetch block result from Tendermint", "block-hash", hash.String(), "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res, err := b.RPCBlockFromTendermintBlock(resBlock, blockRes, fullTx)
|
||||
if err != nil {
|
||||
b.logger.Debug("GetEthBlockFromTendermint failed", "hash", hash, "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetBlockTransactionCountByHash returns the number of Ethereum transactions in
|
||||
// the block identified by hash.
|
||||
func (b *Backend) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
|
||||
block, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
|
||||
if err != nil {
|
||||
b.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
if block.Block == nil {
|
||||
b.logger.Debug("block not found", "hash", hash.Hex())
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.GetBlockTransactionCount(block)
|
||||
}
|
||||
|
||||
// GetBlockTransactionCountByNumber returns the number of Ethereum transactions
|
||||
// in the block identified by number.
|
||||
func (b *Backend) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
|
||||
block, err := b.TendermintBlockByNumber(blockNum)
|
||||
if err != nil {
|
||||
b.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
if block.Block == nil {
|
||||
b.logger.Debug("block not found", "height", blockNum.Int64())
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.GetBlockTransactionCount(block)
|
||||
}
|
||||
|
||||
// GetBlockTransactionCount returns the number of Ethereum transactions in a
|
||||
// given block.
|
||||
func (b *Backend) GetBlockTransactionCount(block *tmrpctypes.ResultBlock) *hexutil.Uint {
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&block.Block.Height)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ethMsgs := b.EthMsgsFromTendermintBlock(block, blockRes)
|
||||
n := hexutil.Uint(len(ethMsgs))
|
||||
return &n
|
||||
}
|
||||
|
||||
// TendermintBlockByNumber returns a Tendermint-formatted block for a given
|
||||
// block number
|
||||
func (b *Backend) TendermintBlockByNumber(blockNum rpctypes.BlockNumber) (*tmrpctypes.ResultBlock, error) {
|
||||
height := blockNum.Int64()
|
||||
if height <= 0 {
|
||||
// fetch the latest block number from the app state, more accurate than the tendermint block store state.
|
||||
n, err := b.BlockNumber()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
height = int64(n)
|
||||
}
|
||||
resBlock, err := b.clientCtx.Client.Block(b.ctx, &height)
|
||||
if err != nil {
|
||||
b.logger.Debug("tendermint client failed to get block", "height", height, "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBlock.Block == nil {
|
||||
b.logger.Debug("TendermintBlockByNumber block not found", "height", height)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return resBlock, nil
|
||||
}
|
||||
|
||||
// TendermintBlockResultByNumber returns a Tendermint-formatted block result
|
||||
// by block number
|
||||
func (b *Backend) TendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error) {
|
||||
return b.clientCtx.Client.BlockResults(b.ctx, height)
|
||||
}
|
||||
|
||||
// TendermintBlockByHash returns a Tendermint-formatted block by block number
|
||||
func (b *Backend) TendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) {
|
||||
resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, blockHash.Bytes())
|
||||
if err != nil {
|
||||
b.logger.Debug("tendermint client failed to get block", "blockHash", blockHash.Hex(), "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBlock == nil || resBlock.Block == nil {
|
||||
b.logger.Debug("TendermintBlockByHash block not found", "blockHash", blockHash.Hex())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return resBlock, nil
|
||||
}
|
||||
|
||||
// BlockNumberFromTendermint returns the BlockNumber from BlockNumberOrHash
|
||||
func (b *Backend) BlockNumberFromTendermint(blockNrOrHash rpctypes.BlockNumberOrHash) (rpctypes.BlockNumber, error) {
|
||||
switch {
|
||||
case blockNrOrHash.BlockHash == nil && blockNrOrHash.BlockNumber == nil:
|
||||
return rpctypes.EthEarliestBlockNumber, fmt.Errorf("types BlockHash and BlockNumber cannot be both nil")
|
||||
case blockNrOrHash.BlockHash != nil:
|
||||
blockNumber, err := b.BlockNumberFromTendermintByHash(*blockNrOrHash.BlockHash)
|
||||
if err != nil {
|
||||
return rpctypes.EthEarliestBlockNumber, err
|
||||
}
|
||||
return rpctypes.NewBlockNumber(blockNumber), nil
|
||||
case blockNrOrHash.BlockNumber != nil:
|
||||
return *blockNrOrHash.BlockNumber, nil
|
||||
default:
|
||||
return rpctypes.EthEarliestBlockNumber, nil
|
||||
}
|
||||
}
|
||||
|
||||
// BlockNumberFromTendermintByHash returns the block height of given block hash
|
||||
func (b *Backend) BlockNumberFromTendermintByHash(blockHash common.Hash) (*big.Int, error) {
|
||||
resBlock, err := b.TendermintBlockByHash(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resBlock == nil {
|
||||
return nil, errors.Errorf("block not found for hash %s", blockHash.Hex())
|
||||
}
|
||||
return big.NewInt(resBlock.Block.Height), nil
|
||||
}
|
||||
|
||||
// EthMsgsFromTendermintBlock returns all real MsgEthereumTxs from a
|
||||
// Tendermint block. It also ensures consistency over the correct txs indexes
|
||||
// across RPC endpoints
|
||||
func (b *Backend) EthMsgsFromTendermintBlock(
|
||||
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 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 !rpctypes.TxSuccessOrExceedsBlockGasLimit(txResults[i]) {
|
||||
b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
|
||||
continue
|
||||
}
|
||||
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(tx)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ethMsg.Hash = ethMsg.AsTransaction().Hash().Hex()
|
||||
result = append(result, ethMsg)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// HeaderByNumber returns the block header identified by height.
|
||||
func (b *Backend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error) {
|
||||
resBlock, err := b.TendermintBlockByNumber(blockNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBlock == nil {
|
||||
return nil, errors.Errorf("block not found for height %d", blockNum)
|
||||
}
|
||||
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("block result not found for height %d", resBlock.Block.Height)
|
||||
}
|
||||
|
||||
bloom, err := b.BlockBloom(blockRes)
|
||||
if err != nil {
|
||||
b.logger.Debug("HeaderByNumber BlockBloom failed", "height", resBlock.Block.Height)
|
||||
}
|
||||
|
||||
baseFee, err := b.BaseFee(blockRes)
|
||||
if err != nil {
|
||||
// handle the error for pruned node.
|
||||
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", resBlock.Block.Height, "error", err)
|
||||
}
|
||||
|
||||
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
|
||||
return ethHeader, nil
|
||||
}
|
||||
|
||||
// HeaderByHash returns the block header identified by hash.
|
||||
func (b *Backend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
|
||||
resBlock, err := b.TendermintBlockByHash(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resBlock == nil {
|
||||
return nil, errors.Errorf("block not found for hash %s", blockHash.Hex())
|
||||
}
|
||||
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("block result not found for height %d", resBlock.Block.Height)
|
||||
}
|
||||
|
||||
bloom, err := b.BlockBloom(blockRes)
|
||||
if err != nil {
|
||||
b.logger.Debug("HeaderByHash BlockBloom failed", "height", resBlock.Block.Height)
|
||||
}
|
||||
|
||||
baseFee, err := b.BaseFee(blockRes)
|
||||
if err != nil {
|
||||
// handle the error for pruned node.
|
||||
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", resBlock.Block.Height, "error", err)
|
||||
}
|
||||
|
||||
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
|
||||
return ethHeader, nil
|
||||
}
|
||||
|
||||
// BlockBloom query block bloom filter from block results
|
||||
func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (ethtypes.Bloom, error) {
|
||||
for _, event := range blockRes.EndBlockEvents {
|
||||
if event.Type != evmtypes.EventTypeBlockBloom {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, attr := range event.Attributes {
|
||||
if bytes.Equal(attr.Key, bAttributeKeyEthereumBloom) {
|
||||
return ethtypes.BytesToBloom(attr.Value), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return ethtypes.Bloom{}, errors.New("block bloom event is not found")
|
||||
}
|
||||
|
||||
// RPCBlockFromTendermintBlock returns a JSON-RPC compatible Ethereum block from a
|
||||
// given Tendermint block and its block result.
|
||||
func (b *Backend) RPCBlockFromTendermintBlock(
|
||||
resBlock *tmrpctypes.ResultBlock,
|
||||
blockRes *tmrpctypes.ResultBlockResults,
|
||||
fullTx bool,
|
||||
) (map[string]interface{}, error) {
|
||||
ethRPCTxs := []interface{}{}
|
||||
block := resBlock.Block
|
||||
|
||||
baseFee, err := b.BaseFee(blockRes)
|
||||
if err != nil {
|
||||
// handle the error for pruned node.
|
||||
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Height, "error", err)
|
||||
}
|
||||
|
||||
msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes)
|
||||
for txIndex, ethMsg := range msgs {
|
||||
if !fullTx {
|
||||
hash := common.HexToHash(ethMsg.Hash)
|
||||
ethRPCTxs = append(ethRPCTxs, hash)
|
||||
continue
|
||||
}
|
||||
|
||||
tx := ethMsg.AsTransaction()
|
||||
rpcTx, err := rpctypes.NewRPCTransaction(
|
||||
tx,
|
||||
common.BytesToHash(block.Hash()),
|
||||
uint64(block.Height),
|
||||
uint64(txIndex),
|
||||
baseFee,
|
||||
)
|
||||
if err != nil {
|
||||
b.logger.Debug("NewTransactionFromData for receipt failed", "hash", tx.Hash().Hex(), "error", err.Error())
|
||||
continue
|
||||
}
|
||||
ethRPCTxs = append(ethRPCTxs, rpcTx)
|
||||
}
|
||||
|
||||
bloom, err := b.BlockBloom(blockRes)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to query BlockBloom", "height", block.Height, "error", err.Error())
|
||||
}
|
||||
|
||||
req := &evmtypes.QueryValidatorAccountRequest{
|
||||
ConsAddress: sdk.ConsAddress(block.Header.ProposerAddress).String(),
|
||||
}
|
||||
|
||||
var validatorAccAddr sdk.AccAddress
|
||||
|
||||
ctx := rpctypes.ContextWithHeight(block.Height)
|
||||
res, err := b.queryClient.ValidatorAccount(ctx, req)
|
||||
if err != nil {
|
||||
b.logger.Debug(
|
||||
"failed to query validator operator address",
|
||||
"height", block.Height,
|
||||
"cons-address", req.ConsAddress,
|
||||
"error", err.Error(),
|
||||
)
|
||||
// use zero address as the validator operator address
|
||||
validatorAccAddr = sdk.AccAddress(common.Address{}.Bytes())
|
||||
} else {
|
||||
validatorAccAddr, err = sdk.AccAddressFromBech32(res.AccountAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
validatorAddr := common.BytesToAddress(validatorAccAddr)
|
||||
|
||||
gasLimit, err := rpctypes.BlockMaxGasFromConsensusParams(ctx, b.clientCtx, block.Height)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to query consensus params", "error", err.Error())
|
||||
}
|
||||
|
||||
gasUsed := uint64(0)
|
||||
|
||||
for _, txsResult := range blockRes.TxsResults {
|
||||
// workaround for cosmos-sdk bug. https://github.com/cosmos/cosmos-sdk/issues/10832
|
||||
if ShouldIgnoreGasUsed(txsResult) {
|
||||
// block gas limit has exceeded, other txs must have failed with same reason.
|
||||
break
|
||||
}
|
||||
gasUsed += uint64(txsResult.GetGasUsed())
|
||||
}
|
||||
|
||||
formattedBlock := rpctypes.FormatBlock(
|
||||
block.Header, block.Size(),
|
||||
gasLimit, new(big.Int).SetUint64(gasUsed),
|
||||
ethRPCTxs, bloom, validatorAddr, baseFee,
|
||||
)
|
||||
return formattedBlock, nil
|
||||
}
|
||||
|
||||
// EthBlockByNumber returns the Ethereum Block identified by number.
|
||||
func (b *Backend) EthBlockByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Block, error) {
|
||||
resBlock, err := b.TendermintBlockByNumber(blockNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resBlock == nil {
|
||||
// block not found
|
||||
return nil, fmt.Errorf("block not found for height %d", blockNum)
|
||||
}
|
||||
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("block result not found for height %d", resBlock.Block.Height)
|
||||
}
|
||||
|
||||
return b.EthBlockFromTendermintBlock(resBlock, blockRes)
|
||||
}
|
||||
|
||||
// EthBlockFromTendermintBlock returns an Ethereum Block type from Tendermint block
|
||||
// EthBlockFromTendermintBlock
|
||||
func (b *Backend) EthBlockFromTendermintBlock(
|
||||
resBlock *tmrpctypes.ResultBlock,
|
||||
blockRes *tmrpctypes.ResultBlockResults,
|
||||
) (*ethtypes.Block, error) {
|
||||
block := resBlock.Block
|
||||
height := block.Height
|
||||
bloom, err := b.BlockBloom(blockRes)
|
||||
if err != nil {
|
||||
b.logger.Debug("HeaderByNumber BlockBloom failed", "height", height)
|
||||
}
|
||||
|
||||
baseFee, err := b.BaseFee(blockRes)
|
||||
if err != nil {
|
||||
// handle error for pruned node and log
|
||||
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", height, "error", err)
|
||||
}
|
||||
|
||||
ethHeader := rpctypes.EthHeaderFromTendermint(block.Header, bloom, baseFee)
|
||||
msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes)
|
||||
|
||||
txs := make([]*ethtypes.Transaction, len(msgs))
|
||||
for i, ethMsg := range msgs {
|
||||
txs[i] = ethMsg.AsTransaction()
|
||||
}
|
||||
|
||||
// TODO: add tx receipts
|
||||
ethBlock := ethtypes.NewBlock(ethHeader, txs, nil, nil, trie.NewStackTrie(nil))
|
||||
return ethBlock, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,398 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// 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 (b *Backend) Resend(args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
|
||||
if args.Nonce == nil {
|
||||
return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec")
|
||||
}
|
||||
|
||||
args, err := b.SetTxDefaults(args)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// The signer used should always be the 'latest' known one because we expect
|
||||
// signers to be backwards-compatible with old transactions.
|
||||
eip155ChainID, err := ethermint.ParseChainID(b.clientCtx.ChainID)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
cfg := b.ChainConfig()
|
||||
if cfg == nil {
|
||||
cfg = evmtypes.DefaultChainConfig().EthereumConfig(eip155ChainID)
|
||||
}
|
||||
|
||||
signer := ethtypes.LatestSigner(cfg)
|
||||
|
||||
matchTx := args.ToTransaction().AsTransaction()
|
||||
|
||||
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
|
||||
price := matchTx.GasPrice()
|
||||
if gasPrice != nil {
|
||||
price = gasPrice.ToInt()
|
||||
}
|
||||
gas := matchTx.Gas()
|
||||
if gasLimit != nil {
|
||||
gas = uint64(*gasLimit)
|
||||
}
|
||||
if err := rpctypes.CheckTxFee(price, gas, b.RPCTxFeeCap()); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
pending, err := b.PendingTransactions()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
for _, tx := range pending {
|
||||
// FIXME does Resend api possible at all? https://github.com/cerc-io/laconicd/issues/905
|
||||
p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{})
|
||||
if err != nil {
|
||||
// not valid ethereum tx
|
||||
continue
|
||||
}
|
||||
|
||||
pTx := p.AsTransaction()
|
||||
|
||||
wantSigHash := signer.Hash(matchTx)
|
||||
pFrom, err := ethtypes.Sender(signer, pTx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if pFrom == *args.From && signer.Hash(pTx) == wantSigHash {
|
||||
// Match. Re-sign and send the transaction.
|
||||
if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
|
||||
args.GasPrice = gasPrice
|
||||
}
|
||||
if gasLimit != nil && *gasLimit != 0 {
|
||||
args.Gas = gasLimit
|
||||
}
|
||||
|
||||
return b.SendTransaction(args) // TODO: this calls SetTxDefaults again, refactor to avoid calling it twice
|
||||
}
|
||||
}
|
||||
|
||||
return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())
|
||||
}
|
||||
|
||||
// SendRawTransaction send a raw Ethereum transaction.
|
||||
func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
|
||||
// RLP decode raw transaction bytes
|
||||
tx := ðtypes.Transaction{}
|
||||
if err := tx.UnmarshalBinary(data); err != nil {
|
||||
b.logger.Error("transaction decoding failed", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// check the local node config in case unprotected txs are disabled
|
||||
if !b.UnprotectedAllowed() && !tx.Protected() {
|
||||
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
|
||||
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
|
||||
}
|
||||
|
||||
ethereumTx := &evmtypes.MsgEthereumTx{}
|
||||
if err := ethereumTx.FromEthereumTx(tx); err != nil {
|
||||
b.logger.Error("transaction converting failed", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
if err := ethereumTx.ValidateBasic(); err != nil {
|
||||
b.logger.Debug("tx failed basic validation", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// Query params to use the EVM denomination
|
||||
res, err := b.queryClient.QueryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
b.logger.Error("failed to query evm params", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
cosmosTx, err := ethereumTx.BuildTx(b.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to build cosmos tx", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// Encode transaction by default Tx encoder
|
||||
txBytes, err := b.clientCtx.TxConfig.TxEncoder()(cosmosTx)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
txHash := ethereumTx.AsTransaction().Hash()
|
||||
|
||||
syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync)
|
||||
rsp, err := syncCtx.BroadcastTx(txBytes)
|
||||
if rsp != nil && rsp.Code != 0 {
|
||||
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
|
||||
}
|
||||
if err != nil {
|
||||
b.logger.Error("failed to broadcast tx", "error", err.Error())
|
||||
return txHash, err
|
||||
}
|
||||
|
||||
return txHash, nil
|
||||
}
|
||||
|
||||
// SetTxDefaults populates tx message with default values in case they are not
|
||||
// provided on the args
|
||||
func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error) {
|
||||
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
||||
return args, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||
}
|
||||
|
||||
head := b.CurrentHeader()
|
||||
if head == nil {
|
||||
return args, errors.New("latest header is nil")
|
||||
}
|
||||
|
||||
// If user specifies both maxPriorityfee and maxFee, then we do not
|
||||
// need to consult the chain for defaults. It's definitely a London tx.
|
||||
if args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil {
|
||||
// In this clause, user left some fields unspecified.
|
||||
if head.BaseFee != nil && args.GasPrice == nil {
|
||||
if args.MaxPriorityFeePerGas == nil {
|
||||
tip, err := b.SuggestGasTipCap(head.BaseFee)
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
args.MaxPriorityFeePerGas = (*hexutil.Big)(tip)
|
||||
}
|
||||
|
||||
if args.MaxFeePerGas == nil {
|
||||
gasFeeCap := new(big.Int).Add(
|
||||
(*big.Int)(args.MaxPriorityFeePerGas),
|
||||
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
|
||||
)
|
||||
args.MaxFeePerGas = (*hexutil.Big)(gasFeeCap)
|
||||
}
|
||||
|
||||
if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
|
||||
return args, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
|
||||
}
|
||||
} else {
|
||||
if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil {
|
||||
return args, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet")
|
||||
}
|
||||
|
||||
if args.GasPrice == nil {
|
||||
price, err := b.SuggestGasTipCap(head.BaseFee)
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
if head.BaseFee != nil {
|
||||
// The legacy tx gas price suggestion should not add 2x base fee
|
||||
// because all fees are consumed, so it would result in a spiral
|
||||
// upwards.
|
||||
price.Add(price, head.BaseFee)
|
||||
}
|
||||
args.GasPrice = (*hexutil.Big)(price)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Both maxPriorityfee and maxFee set by caller. Sanity-check their internal relation
|
||||
if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
|
||||
return args, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
|
||||
}
|
||||
}
|
||||
|
||||
if args.Value == nil {
|
||||
args.Value = new(hexutil.Big)
|
||||
}
|
||||
if args.Nonce == nil {
|
||||
// get the nonce from the account retriever
|
||||
// ignore error in case tge account doesn't exist yet
|
||||
nonce, _ := b.getAccountNonce(*args.From, true, 0, b.logger)
|
||||
args.Nonce = (*hexutil.Uint64)(&nonce)
|
||||
}
|
||||
|
||||
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
|
||||
return args, errors.New("both 'data' and 'input' are set and not equal. Please use 'input' to pass transaction call data")
|
||||
}
|
||||
|
||||
if args.To == nil {
|
||||
// Contract creation
|
||||
var input []byte
|
||||
if args.Data != nil {
|
||||
input = *args.Data
|
||||
} else if args.Input != nil {
|
||||
input = *args.Input
|
||||
}
|
||||
|
||||
if len(input) == 0 {
|
||||
return args, errors.New("contract creation without any data provided")
|
||||
}
|
||||
}
|
||||
|
||||
if args.Gas == nil {
|
||||
// For backwards-compatibility reason, we try both input and data
|
||||
// but input is preferred.
|
||||
input := args.Input
|
||||
if input == nil {
|
||||
input = args.Data
|
||||
}
|
||||
|
||||
callArgs := evmtypes.TransactionArgs{
|
||||
From: args.From,
|
||||
To: args.To,
|
||||
Gas: args.Gas,
|
||||
GasPrice: args.GasPrice,
|
||||
MaxFeePerGas: args.MaxFeePerGas,
|
||||
MaxPriorityFeePerGas: args.MaxPriorityFeePerGas,
|
||||
Value: args.Value,
|
||||
Data: input,
|
||||
AccessList: args.AccessList,
|
||||
}
|
||||
|
||||
blockNr := rpctypes.NewBlockNumber(big.NewInt(0))
|
||||
estimated, err := b.EstimateGas(callArgs, &blockNr)
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
args.Gas = &estimated
|
||||
b.logger.Debug("estimate gas usage automatically", "gas", args.Gas)
|
||||
}
|
||||
|
||||
if args.ChainID == nil {
|
||||
args.ChainID = (*hexutil.Big)(b.chainID)
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// EstimateGas returns an estimate of gas usage for the given smart contract call.
|
||||
func (b *Backend) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) {
|
||||
blockNr := rpctypes.EthPendingBlockNumber
|
||||
if blockNrOptional != nil {
|
||||
blockNr = *blockNrOptional
|
||||
}
|
||||
|
||||
bz, err := json.Marshal(&args)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
req := evmtypes.EthCallRequest{
|
||||
Args: bz,
|
||||
GasCap: b.RPCGasCap(),
|
||||
}
|
||||
|
||||
_, err = b.TendermintBlockByNumber(blockNr)
|
||||
if err != nil {
|
||||
// the error message imitates geth behavior
|
||||
return 0, errors.New("header not found")
|
||||
}
|
||||
|
||||
// From ContextWithHeight: if the provided height is 0,
|
||||
// it will return an empty context and the gRPC query will use
|
||||
// the latest block height for querying.
|
||||
res, err := b.queryClient.EstimateGas(rpctypes.ContextWithHeight(blockNr.Int64()), &req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return hexutil.Uint64(res.Gas), nil
|
||||
}
|
||||
|
||||
// DoCall performs a simulated call operation through the evmtypes. It returns the
|
||||
// estimated gas used on the operation or an error if fails.
|
||||
func (b *Backend) DoCall(
|
||||
args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber,
|
||||
) (*evmtypes.MsgEthereumTxResponse, error) {
|
||||
bz, err := json.Marshal(&args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := evmtypes.EthCallRequest{
|
||||
Args: bz,
|
||||
GasCap: b.RPCGasCap(),
|
||||
}
|
||||
|
||||
// From ContextWithHeight: if the provided height is 0,
|
||||
// it will return an empty context and the gRPC query will use
|
||||
// the latest block height for querying.
|
||||
ctx := rpctypes.ContextWithHeight(blockNr.Int64())
|
||||
timeout := b.RPCEVMTimeout()
|
||||
|
||||
// Setup context so it may be canceled the call has completed
|
||||
// or, in case of unmetered gas, setup a context with a timeout.
|
||||
var cancel context.CancelFunc
|
||||
if timeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||
} else {
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
}
|
||||
|
||||
// Make sure the context is canceled when the call has completed
|
||||
// this makes sure resources are cleaned up.
|
||||
defer cancel()
|
||||
|
||||
res, err := b.queryClient.EthCall(ctx, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.Failed() {
|
||||
if res.VmError != vm.ErrExecutionReverted.Error() {
|
||||
return nil, status.Error(codes.Internal, res.VmError)
|
||||
}
|
||||
return nil, evmtypes.NewExecErrorWithReason(res.Ret)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GasPrice returns the current gas price based on Ethermint's gas price oracle.
|
||||
func (b *Backend) GasPrice() (*hexutil.Big, error) {
|
||||
var (
|
||||
result *big.Int
|
||||
err error
|
||||
)
|
||||
if head := b.CurrentHeader(); head.BaseFee != nil {
|
||||
result, err = b.SuggestGasTipCap(head.BaseFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = result.Add(result, head.BaseFee)
|
||||
} else {
|
||||
result = big.NewInt(b.RPCMinGasPrice())
|
||||
}
|
||||
|
||||
// return at least GlobalMinGasPrice from FeeMarket module
|
||||
minGasPrice, err := b.GlobalMinGasPrice()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minGasPriceInt := minGasPrice.TruncateInt().BigInt()
|
||||
if result.Cmp(minGasPriceInt) < 0 {
|
||||
result = minGasPriceInt
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(result), nil
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
feemarkettypes "github.com/cerc-io/laconicd/x/feemarket/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// ChainID is the EIP-155 replay-protection chain id for the current ethereum chain config.
|
||||
func (b *Backend) ChainID() (*hexutil.Big, error) {
|
||||
eip155ChainID, err := ethermint.ParseChainID(b.clientCtx.ChainID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
|
||||
bn, err := b.BlockNumber()
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to fetch latest block number", "error", err.Error())
|
||||
return (*hexutil.Big)(eip155ChainID), nil
|
||||
}
|
||||
|
||||
if config := b.ChainConfig(); config.IsEIP155(new(big.Int).SetUint64(uint64(bn))) {
|
||||
return (*hexutil.Big)(config.ChainID), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block")
|
||||
}
|
||||
|
||||
// ChainConfig returns the latest ethereum chain configuration
|
||||
func (b *Backend) ChainConfig() *params.ChainConfig {
|
||||
params, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return params.Params.ChainConfig.EthereumConfig(b.chainID)
|
||||
}
|
||||
|
||||
// GlobalMinGasPrice returns MinGasPrice param from FeeMarket
|
||||
func (b *Backend) GlobalMinGasPrice() (sdk.Dec, error) {
|
||||
res, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
return sdk.ZeroDec(), err
|
||||
}
|
||||
return res.Params.MinGasPrice, nil
|
||||
}
|
||||
|
||||
// BaseFee returns the base fee tracked by the Fee Market module.
|
||||
// If the base fee is not enabled globally, the query returns nil.
|
||||
// If the London hard fork is not activated at the current height, the query will
|
||||
// return nil.
|
||||
func (b *Backend) BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error) {
|
||||
// return BaseFee if London hard fork is activated and feemarket is enabled
|
||||
res, err := b.queryClient.BaseFee(rpctypes.ContextWithHeight(blockRes.Height), &evmtypes.QueryBaseFeeRequest{})
|
||||
if err != nil || res.BaseFee == nil {
|
||||
// we can't tell if it's london HF not enabled or the state is pruned,
|
||||
// in either case, we'll fallback to parsing from begin blocker event,
|
||||
// faster to iterate reversely
|
||||
for i := len(blockRes.BeginBlockEvents) - 1; i >= 0; i-- {
|
||||
evt := blockRes.BeginBlockEvents[i]
|
||||
if evt.Type == feemarkettypes.EventTypeFeeMarket && len(evt.Attributes) > 0 {
|
||||
baseFee, err := strconv.ParseInt(string(evt.Attributes[0].Value), 10, 64)
|
||||
if err == nil {
|
||||
return big.NewInt(baseFee), nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.BaseFee == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return res.BaseFee.BigInt(), nil
|
||||
}
|
||||
|
||||
// CurrentHeader returns the latest block header
|
||||
func (b *Backend) CurrentHeader() *ethtypes.Header {
|
||||
header, _ := b.HeaderByNumber(rpctypes.EthLatestBlockNumber)
|
||||
return header
|
||||
}
|
||||
|
||||
// PendingTransactions returns the transactions that are in the transaction pool
|
||||
// and have a from address that is one of the accounts this node manages.
|
||||
func (b *Backend) PendingTransactions() ([]*sdk.Tx, error) {
|
||||
res, err := b.clientCtx.Client.UnconfirmedTxs(b.ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*sdk.Tx, 0, len(res.Txs))
|
||||
for _, txBz := range res.Txs {
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, &tx)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetCoinbase is the address that staking rewards will be send to (alias for Etherbase).
|
||||
func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
|
||||
node, err := b.clientCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, err := node.Status(b.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &evmtypes.QueryValidatorAccountRequest{
|
||||
ConsAddress: sdk.ConsAddress(status.ValidatorInfo.Address).String(),
|
||||
}
|
||||
|
||||
res, err := b.queryClient.ValidatorAccount(b.ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
address, _ := sdk.AccAddressFromBech32(res.AccountAddress)
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
|
||||
func (b *Backend) FeeHistory(
|
||||
userBlockCount rpc.DecimalOrHex, // number blocks to fetch, maximum is 100
|
||||
lastBlock rpc.BlockNumber, // the block to start search , to oldest
|
||||
rewardPercentiles []float64, // percentiles to fetch reward
|
||||
) (*rpctypes.FeeHistoryResult, error) {
|
||||
blockEnd := int64(lastBlock)
|
||||
|
||||
if blockEnd <= 0 {
|
||||
blockNumber, err := b.BlockNumber()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockEnd = int64(blockNumber)
|
||||
}
|
||||
userBlockCountInt := int64(userBlockCount)
|
||||
maxBlockCount := int64(b.cfg.JSONRPC.FeeHistoryCap)
|
||||
if userBlockCountInt > maxBlockCount {
|
||||
return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", userBlockCountInt, maxBlockCount)
|
||||
}
|
||||
blockStart := blockEnd - userBlockCountInt
|
||||
if blockStart < 0 {
|
||||
blockStart = 0
|
||||
}
|
||||
|
||||
blockCount := blockEnd - blockStart
|
||||
|
||||
oldestBlock := (*hexutil.Big)(big.NewInt(blockStart))
|
||||
|
||||
// prepare space
|
||||
reward := make([][]*hexutil.Big, blockCount)
|
||||
rewardCount := len(rewardPercentiles)
|
||||
for i := 0; i < int(blockCount); i++ {
|
||||
reward[i] = make([]*hexutil.Big, rewardCount)
|
||||
}
|
||||
thisBaseFee := make([]*hexutil.Big, blockCount)
|
||||
thisGasUsedRatio := make([]float64, blockCount)
|
||||
|
||||
// rewards should only be calculated if reward percentiles were included
|
||||
calculateRewards := rewardCount != 0
|
||||
|
||||
// fetch block
|
||||
for blockID := blockStart; blockID < blockEnd; blockID++ {
|
||||
index := int32(blockID - blockStart)
|
||||
// tendermint block
|
||||
tendermintblock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(blockID))
|
||||
if tendermintblock == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// eth block
|
||||
ethBlock, err := b.GetBlockByNumber(rpctypes.BlockNumber(blockID), true)
|
||||
if ethBlock == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// tendermint block result
|
||||
tendermintBlockResult, err := b.TendermintBlockResultByNumber(&tendermintblock.Block.Height)
|
||||
if tendermintBlockResult == nil {
|
||||
b.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oneFeeHistory := rpctypes.OneFeeHistory{}
|
||||
err = b.processBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// copy
|
||||
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
|
||||
thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio
|
||||
if calculateRewards {
|
||||
for j := 0; j < rewardCount; j++ {
|
||||
reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j])
|
||||
if reward[index][j] == nil {
|
||||
reward[index][j] = (*hexutil.Big)(big.NewInt(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feeHistory := rpctypes.FeeHistoryResult{
|
||||
OldestBlock: oldestBlock,
|
||||
BaseFee: thisBaseFee,
|
||||
GasUsedRatio: thisGasUsedRatio,
|
||||
}
|
||||
|
||||
if calculateRewards {
|
||||
feeHistory.Reward = reward
|
||||
}
|
||||
|
||||
return &feeHistory, nil
|
||||
}
|
||||
|
||||
// SuggestGasTipCap returns the suggested tip cap
|
||||
// Although we don't support tx prioritization yet, but we return a positive value to help client to
|
||||
// mitigate the base fee changes.
|
||||
func (b *Backend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) {
|
||||
if baseFee == nil {
|
||||
// london hardfork not enabled or feemarket not enabled
|
||||
return big.NewInt(0), nil
|
||||
}
|
||||
|
||||
params, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// calculate the maximum base fee delta in current block, assuming all block gas limit is consumed
|
||||
// ```
|
||||
// GasTarget = GasLimit / ElasticityMultiplier
|
||||
// Delta = BaseFee * (GasUsed - GasTarget) / GasTarget / Denominator
|
||||
// ```
|
||||
// The delta is at maximum when `GasUsed` is equal to `GasLimit`, which is:
|
||||
// ```
|
||||
// MaxDelta = BaseFee * (GasLimit - GasLimit / ElasticityMultiplier) / (GasLimit / ElasticityMultiplier) / Denominator
|
||||
// = BaseFee * (ElasticityMultiplier - 1) / Denominator
|
||||
// ```
|
||||
maxDelta := baseFee.Int64() * (int64(params.Params.ElasticityMultiplier) - 1) / int64(params.Params.BaseFeeChangeDenominator)
|
||||
if maxDelta < 0 {
|
||||
// impossible if the parameter validation passed.
|
||||
maxDelta = 0
|
||||
}
|
||||
return big.NewInt(maxDelta), nil
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/tendermint/tendermint/abci/types"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/cerc-io/laconicd/rpc/backend/mocks"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
feemarkettypes "github.com/cerc-io/laconicd/x/feemarket/types"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cerc-io/laconicd/rpc/backend/mocks"
|
||||
rpc "github.com/cerc-io/laconicd/rpc/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
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
|
||||
}
|
||||
|
||||
func RegisterBlockByHashError(client *mocks.Client, hash common.Hash, tx []byte) {
|
||||
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(nil, sdkerrors.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
func RegisterBlockByHashNotFound(client *mocks.Client, hash common.Hash, tx []byte) {
|
||||
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(nil, nil)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetLogs returns all the logs from all the ethereum transactions in a block.
|
||||
func (b *Backend) GetLogs(hash common.Hash) ([][]*ethtypes.Log, error) {
|
||||
resBlock, err := b.TendermintBlockByHash(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resBlock == nil {
|
||||
return nil, errors.Errorf("block not found for hash %s", hash)
|
||||
}
|
||||
|
||||
return b.GetLogsByHeight(&resBlock.Block.Header.Height)
|
||||
}
|
||||
|
||||
// GetLogsByHeight returns all the logs from all the ethereum transactions in a block.
|
||||
func (b *Backend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) {
|
||||
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
|
||||
blockRes, err := b.TendermintBlockResultByNumber(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetLogsFromBlockResults(blockRes)
|
||||
}
|
||||
|
||||
// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
|
||||
// by the chain indexer.
|
||||
func (b *Backend) BloomStatus() (uint64, uint64) {
|
||||
return 4096, 0
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// Code generated by mockery v2.14.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
types "github.com/cerc-io/laconicd/x/evm/types"
|
||||
)
|
||||
|
||||
// QueryClient is an autogenerated mock type for the QueryClient type
|
||||
type QueryClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Account provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) Account(ctx context.Context, in *types.QueryAccountRequest, opts ...grpc.CallOption) (*types.QueryAccountResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryAccountResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryAccountRequest, ...grpc.CallOption) *types.QueryAccountResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryAccountResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryAccountRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Balance provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) Balance(ctx context.Context, in *types.QueryBalanceRequest, opts ...grpc.CallOption) (*types.QueryBalanceResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryBalanceResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryBalanceRequest, ...grpc.CallOption) *types.QueryBalanceResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryBalanceResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryBalanceRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// BaseFee provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) BaseFee(ctx context.Context, in *types.QueryBaseFeeRequest, opts ...grpc.CallOption) (*types.QueryBaseFeeResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryBaseFeeResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryBaseFeeRequest, ...grpc.CallOption) *types.QueryBaseFeeResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryBaseFeeResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryBaseFeeRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Code provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) Code(ctx context.Context, in *types.QueryCodeRequest, opts ...grpc.CallOption) (*types.QueryCodeResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryCodeResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryCodeRequest, ...grpc.CallOption) *types.QueryCodeResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryCodeResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryCodeRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CosmosAccount provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) CosmosAccount(ctx context.Context, in *types.QueryCosmosAccountRequest, opts ...grpc.CallOption) (*types.QueryCosmosAccountResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryCosmosAccountResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryCosmosAccountRequest, ...grpc.CallOption) *types.QueryCosmosAccountResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryCosmosAccountResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryCosmosAccountRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// EstimateGas provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) EstimateGas(ctx context.Context, in *types.EthCallRequest, opts ...grpc.CallOption) (*types.EstimateGasResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.EstimateGasResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) *types.EstimateGasResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.EstimateGasResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// EthCall provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) EthCall(ctx context.Context, in *types.EthCallRequest, opts ...grpc.CallOption) (*types.MsgEthereumTxResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.MsgEthereumTxResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) *types.MsgEthereumTxResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.MsgEthereumTxResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.EthCallRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Params provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) Params(ctx context.Context, in *types.QueryParamsRequest, opts ...grpc.CallOption) (*types.QueryParamsResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryParamsResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryParamsRequest, ...grpc.CallOption) *types.QueryParamsResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryParamsResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryParamsRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Storage provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) Storage(ctx context.Context, in *types.QueryStorageRequest, opts ...grpc.CallOption) (*types.QueryStorageResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryStorageResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryStorageRequest, ...grpc.CallOption) *types.QueryStorageResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryStorageResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryStorageRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// TraceBlock provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) TraceBlock(ctx context.Context, in *types.QueryTraceBlockRequest, opts ...grpc.CallOption) (*types.QueryTraceBlockResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryTraceBlockResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryTraceBlockRequest, ...grpc.CallOption) *types.QueryTraceBlockResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryTraceBlockResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryTraceBlockRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// TraceTx provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) TraceTx(ctx context.Context, in *types.QueryTraceTxRequest, opts ...grpc.CallOption) (*types.QueryTraceTxResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryTraceTxResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryTraceTxRequest, ...grpc.CallOption) *types.QueryTraceTxResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryTraceTxResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryTraceTxRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ValidatorAccount provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *QueryClient) ValidatorAccount(ctx context.Context, in *types.QueryValidatorAccountRequest, opts ...grpc.CallOption) (*types.QueryValidatorAccountResponse, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 *types.QueryValidatorAccountResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *types.QueryValidatorAccountRequest, ...grpc.CallOption) *types.QueryValidatorAccountResponse); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.QueryValidatorAccountResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *types.QueryValidatorAccountRequest, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewQueryClient interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewQueryClient creates a new instance of QueryClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewQueryClient(t mockConstructorTestingTNewQueryClient) *QueryClient {
|
||||
mock := &QueryClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
"github.com/cerc-io/laconicd/crypto/ethsecp256k1"
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
"github.com/cerc-io/laconicd/server/config"
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdkcrypto "github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
sdkconfig "github.com/cosmos/cosmos-sdk/server/config"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Accounts returns the list of accounts available to this node.
|
||||
func (b *Backend) Accounts() ([]common.Address, error) {
|
||||
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||
|
||||
infos, err := b.clientCtx.Keyring.List()
|
||||
if err != nil {
|
||||
return addresses, err
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
pubKey, err := info.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addressBytes := pubKey.Address().Bytes()
|
||||
addresses = append(addresses, common.BytesToAddress(addressBytes))
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
|
||||
// yet received the latest block headers from its pears. In case it is synchronizing:
|
||||
// - startingBlock: block number this node started to synchronize from
|
||||
// - currentBlock: block number this node is currently importing
|
||||
// - highestBlock: block number of the highest block header this node has received from peers
|
||||
// - pulledStates: number of state entries processed until now
|
||||
// - knownStates: number of known state entries that still need to be pulled
|
||||
func (b *Backend) Syncing() (interface{}, error) {
|
||||
status, err := b.clientCtx.Client.Status(b.ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !status.SyncInfo.CatchingUp {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"startingBlock": hexutil.Uint64(status.SyncInfo.EarliestBlockHeight),
|
||||
"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
|
||||
// "highestBlock": nil, // NA
|
||||
// "pulledStates": nil, // NA
|
||||
// "knownStates": nil, // NA
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetEtherbase sets the etherbase of the miner
|
||||
func (b *Backend) SetEtherbase(etherbase common.Address) bool {
|
||||
delAddr, err := b.GetCoinbase()
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to get coinbase address", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
withdrawAddr := sdk.AccAddress(etherbase.Bytes())
|
||||
msg := distributiontypes.NewMsgSetWithdrawAddress(delAddr, withdrawAddr)
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
b.logger.Debug("tx failed basic validation", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// Assemble transaction from fields
|
||||
builder, ok := b.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
|
||||
if !ok {
|
||||
b.logger.Debug("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
err = builder.SetMsgs(msg)
|
||||
if err != nil {
|
||||
b.logger.Error("builder.SetMsgs failed", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// Fetch minimun gas price to calculate fees using the configuration.
|
||||
minGasPrices := b.cfg.GetMinGasPrices()
|
||||
if len(minGasPrices) == 0 || minGasPrices.Empty() {
|
||||
b.logger.Debug("the minimun fee is not set")
|
||||
return false
|
||||
}
|
||||
minGasPriceValue := minGasPrices[0].Amount
|
||||
denom := minGasPrices[0].Denom
|
||||
|
||||
delCommonAddr := common.BytesToAddress(delAddr.Bytes())
|
||||
nonce, err := b.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to get nonce", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
txFactory := tx.Factory{}
|
||||
txFactory = txFactory.
|
||||
WithChainID(b.clientCtx.ChainID).
|
||||
WithKeybase(b.clientCtx.Keyring).
|
||||
WithTxConfig(b.clientCtx.TxConfig).
|
||||
WithSequence(uint64(*nonce)).
|
||||
WithGasAdjustment(1.25)
|
||||
|
||||
_, gas, err := tx.CalculateGas(b.clientCtx, txFactory, msg)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to calculate gas", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
txFactory = txFactory.WithGas(gas)
|
||||
|
||||
value := new(big.Int).SetUint64(gas * minGasPriceValue.Ceil().TruncateInt().Uint64())
|
||||
fees := sdk.Coins{sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(value))}
|
||||
builder.SetFeeAmount(fees)
|
||||
builder.SetGasLimit(gas)
|
||||
|
||||
keyInfo, err := b.clientCtx.Keyring.KeyByAddress(delAddr)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to get the wallet address using the keyring", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
if err := tx.Sign(txFactory, keyInfo.Name, builder, false); err != nil {
|
||||
b.logger.Debug("failed to sign tx", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// Encode transaction by default Tx encoder
|
||||
txEncoder := b.clientCtx.TxConfig.TxEncoder()
|
||||
txBytes, err := txEncoder(builder.GetTx())
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to encode eth tx using default encoder", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
tmHash := common.BytesToHash(tmtypes.Tx(txBytes).Hash())
|
||||
|
||||
// Broadcast transaction in sync mode (default)
|
||||
// NOTE: If error is encountered on the node, the broadcast will not return an error
|
||||
syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync)
|
||||
rsp, err := syncCtx.BroadcastTx(txBytes)
|
||||
if rsp != nil && rsp.Code != 0 {
|
||||
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
|
||||
}
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to broadcast tx", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
b.logger.Debug("broadcasted tx to set miner withdraw address (etherbase)", "hash", tmHash.String())
|
||||
return true
|
||||
}
|
||||
|
||||
// ImportRawKey armors and encrypts a given raw hex encoded ECDSA key and stores it into the key directory.
|
||||
// The name of the key will have the format "personal_<length-keys>", where <length-keys> is the total number of
|
||||
// keys stored on the keyring.
|
||||
//
|
||||
// NOTE: The key will be both armored and encrypted using the same passphrase.
|
||||
func (b *Backend) ImportRawKey(privkey, password string) (common.Address, error) {
|
||||
priv, err := crypto.HexToECDSA(privkey)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
||||
privKey := ðsecp256k1.PrivKey{Key: crypto.FromECDSA(priv)}
|
||||
|
||||
addr := sdk.AccAddress(privKey.PubKey().Address().Bytes())
|
||||
ethereumAddr := common.BytesToAddress(addr)
|
||||
|
||||
// return if the key has already been imported
|
||||
if _, err := b.clientCtx.Keyring.KeyByAddress(addr); err == nil {
|
||||
return ethereumAddr, nil
|
||||
}
|
||||
|
||||
// ignore error as we only care about the length of the list
|
||||
list, _ := b.clientCtx.Keyring.List()
|
||||
privKeyName := fmt.Sprintf("personal_%d", len(list))
|
||||
|
||||
armor := sdkcrypto.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
|
||||
|
||||
if err := b.clientCtx.Keyring.ImportPrivKey(privKeyName, armor, password); err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
||||
b.logger.Info("key successfully imported", "name", privKeyName, "address", ethereumAddr.String())
|
||||
|
||||
return ethereumAddr, nil
|
||||
}
|
||||
|
||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||
func (b *Backend) ListAccounts() ([]common.Address, error) {
|
||||
addrs := []common.Address{}
|
||||
|
||||
list, err := b.clientCtx.Keyring.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, info := range list {
|
||||
pubKey, err := info.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs = append(addrs, common.BytesToAddress(pubKey.Address()))
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// NewAccount will create a new account and returns the address for the new account.
|
||||
func (b *Backend) NewMnemonic(uid string,
|
||||
language keyring.Language,
|
||||
hdPath,
|
||||
bip39Passphrase string,
|
||||
algo keyring.SignatureAlgo,
|
||||
) (*keyring.Record, error) {
|
||||
info, _, err := b.clientCtx.Keyring.NewMnemonic(uid, keyring.English, bip39Passphrase, bip39Passphrase, algo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info, err
|
||||
}
|
||||
|
||||
// SetGasPrice sets the minimum accepted gas price for the miner.
|
||||
// NOTE: this function accepts only integers to have the same interface than go-eth
|
||||
// to use float values, the gas prices must be configured using the configuration file
|
||||
func (b *Backend) SetGasPrice(gasPrice hexutil.Big) bool {
|
||||
appConf, err := config.GetConfig(b.clientCtx.Viper)
|
||||
if err != nil {
|
||||
b.logger.Debug("could not get the server config", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
var unit string
|
||||
minGasPrices := appConf.GetMinGasPrices()
|
||||
|
||||
// fetch the base denom from the sdk Config in case it's not currently defined on the node config
|
||||
if len(minGasPrices) == 0 || minGasPrices.Empty() {
|
||||
var err error
|
||||
unit, err = sdk.GetBaseDenom()
|
||||
if err != nil {
|
||||
b.logger.Debug("could not get the denom of smallest unit registered", "error", err.Error())
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
unit = minGasPrices[0].Denom
|
||||
}
|
||||
|
||||
c := sdk.NewDecCoin(unit, sdk.NewIntFromBigInt(gasPrice.ToInt()))
|
||||
|
||||
appConf.SetMinGasPrices(sdk.DecCoins{c})
|
||||
sdkconfig.WriteConfigFile(b.clientCtx.Viper.ConfigFileUsed(), appConf)
|
||||
b.logger.Info("Your configuration file was modified. Please RESTART your node.", "gas-price", c.String())
|
||||
return true
|
||||
}
|
||||
|
||||
// UnprotectedAllowed returns the node configuration value for allowing
|
||||
// unprotected transactions (i.e not replay-protected)
|
||||
func (b Backend) UnprotectedAllowed() bool {
|
||||
return b.allowUnprotectedTxs
|
||||
}
|
||||
|
||||
// RPCGasCap is the global gas cap for eth-call variants.
|
||||
func (b *Backend) RPCGasCap() uint64 {
|
||||
return b.cfg.JSONRPC.GasCap
|
||||
}
|
||||
|
||||
// RPCEVMTimeout is the global evm timeout for eth-call variants.
|
||||
func (b *Backend) RPCEVMTimeout() time.Duration {
|
||||
return b.cfg.JSONRPC.EVMTimeout
|
||||
}
|
||||
|
||||
// RPCGasCap is the global gas cap for eth-call variants.
|
||||
func (b *Backend) RPCTxFeeCap() float64 {
|
||||
return b.cfg.JSONRPC.TxFeeCap
|
||||
}
|
||||
|
||||
// RPCFilterCap is the limit for total number of filters that can be created
|
||||
func (b *Backend) RPCFilterCap() int32 {
|
||||
return b.cfg.JSONRPC.FilterCap
|
||||
}
|
||||
|
||||
// RPCFeeHistoryCap is the limit for total number of blocks that can be fetched
|
||||
func (b *Backend) RPCFeeHistoryCap() int32 {
|
||||
return b.cfg.JSONRPC.FeeHistoryCap
|
||||
}
|
||||
|
||||
// RPCLogsCap defines the max number of results can be returned from single `eth_getLogs` query.
|
||||
func (b *Backend) RPCLogsCap() int32 {
|
||||
return b.cfg.JSONRPC.LogsCap
|
||||
}
|
||||
|
||||
// RPCBlockRangeCap defines the max block range allowed for `eth_getLogs` query.
|
||||
func (b *Backend) RPCBlockRangeCap() int32 {
|
||||
return b.cfg.JSONRPC.BlockRangeCap
|
||||
}
|
||||
|
||||
// RPCMinGasPrice returns the minimum gas price for a transaction obtained from
|
||||
// the node config. If set value is 0, it will default to 20.
|
||||
|
||||
func (b *Backend) RPCMinGasPrice() int64 {
|
||||
evmParams, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
return ethermint.DefaultGasPrice
|
||||
}
|
||||
|
||||
minGasPrice := b.cfg.GetMinGasPrices()
|
||||
amt := minGasPrice.AmountOf(evmParams.Params.EvmDenom).TruncateInt64()
|
||||
if amt == 0 {
|
||||
return ethermint.DefaultGasPrice
|
||||
}
|
||||
|
||||
return amt
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/cerc-io/laconicd/rpc/backend/mocks"
|
||||
rpc "github.com/cerc-io/laconicd/rpc/types"
|
||||
"github.com/cerc-io/laconicd/tests"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/cerc-io/laconicd/ethereum/eip712"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SendTransaction sends transaction based on received args using Node's key to sign it
|
||||
func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) {
|
||||
// Look up the wallet containing the requested signer
|
||||
_, err := b.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.GetFrom().Bytes()))
|
||||
if err != nil {
|
||||
b.logger.Error("failed to find key in keyring", "address", args.GetFrom(), "error", err.Error())
|
||||
return common.Hash{}, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
|
||||
}
|
||||
|
||||
args, err = b.SetTxDefaults(args)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
msg := args.ToTransaction()
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
b.logger.Debug("tx failed basic validation", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
bn, err := b.BlockNumber()
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to fetch latest block number", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
signer := ethtypes.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(uint64(bn)))
|
||||
|
||||
// Sign transaction
|
||||
if err := msg.Sign(signer, b.clientCtx.Keyring); err != nil {
|
||||
b.logger.Debug("failed to sign tx", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// Query params to use the EVM denomination
|
||||
res, err := b.queryClient.QueryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
b.logger.Error("failed to query evm params", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// Assemble transaction from fields
|
||||
tx, err := msg.BuildTx(b.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
|
||||
if err != nil {
|
||||
b.logger.Error("build cosmos tx failed", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// Encode transaction by default Tx encoder
|
||||
txEncoder := b.clientCtx.TxConfig.TxEncoder()
|
||||
txBytes, err := txEncoder(tx)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
ethTx := msg.AsTransaction()
|
||||
|
||||
// check the local node config in case unprotected txs are disabled
|
||||
if !b.UnprotectedAllowed() && !ethTx.Protected() {
|
||||
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
|
||||
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
|
||||
}
|
||||
|
||||
txHash := ethTx.Hash()
|
||||
|
||||
// Broadcast transaction in sync mode (default)
|
||||
// NOTE: If error is encountered on the node, the broadcast will not return an error
|
||||
syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync)
|
||||
rsp, err := syncCtx.BroadcastTx(txBytes)
|
||||
if rsp != nil && rsp.Code != 0 {
|
||||
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
|
||||
}
|
||||
if err != nil {
|
||||
b.logger.Error("failed to broadcast tx", "error", err.Error())
|
||||
return txHash, err
|
||||
}
|
||||
|
||||
// Return transaction hash
|
||||
return txHash, nil
|
||||
}
|
||||
|
||||
// Sign signs the provided data using the private key of address via Geth's signature standard.
|
||||
func (b *Backend) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||
from := sdk.AccAddress(address.Bytes())
|
||||
|
||||
_, err := b.clientCtx.Keyring.KeyByAddress(from)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to find key in keyring", "address", address.String())
|
||||
return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
|
||||
}
|
||||
|
||||
// Sign the requested hash with the wallet
|
||||
signature, _, err := b.clientCtx.Keyring.SignByAddress(from, data)
|
||||
if err != nil {
|
||||
b.logger.Error("keyring.SignByAddress failed", "address", address.Hex())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// SignTypedData signs EIP-712 conformant typed data
|
||||
func (b *Backend) SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error) {
|
||||
from := sdk.AccAddress(address.Bytes())
|
||||
|
||||
_, err := b.clientCtx.Keyring.KeyByAddress(from)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to find key in keyring", "address", address.String())
|
||||
return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
|
||||
}
|
||||
|
||||
sigHash, err := eip712.ComputeTypedDataHash(typedData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sign the requested hash with the wallet
|
||||
signature, _, err := b.clientCtx.Keyring.SignByAddress(from, sigHash)
|
||||
if err != nil {
|
||||
b.logger.Error("keyring.SignByAddress failed", "address", address.Hex())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||
return signature, nil
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// TraceTransaction returns the structured logs created during the execution of EVM
|
||||
// and returns them as a JSON object.
|
||||
func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
|
||||
// Get transaction by hash
|
||||
transaction, err := b.GetTxByEthHash(hash)
|
||||
if err != nil {
|
||||
b.logger.Debug("tx not found", "hash", hash)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if block number is 0
|
||||
if transaction.Height == 0 {
|
||||
return nil, errors.New("genesis is not traceable")
|
||||
}
|
||||
|
||||
blk, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(transaction.Height))
|
||||
if err != nil {
|
||||
b.logger.Debug("block not found", "height", transaction.Height)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check tx index is not out of bound
|
||||
if uint32(len(blk.Block.Txs)) < transaction.TxIndex {
|
||||
b.logger.Debug("tx index out of bounds", "index", transaction.TxIndex, "hash", hash.String(), "height", blk.Block.Height)
|
||||
return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height)
|
||||
}
|
||||
|
||||
var predecessors []*evmtypes.MsgEthereumTx
|
||||
for _, txBz := range blk.Block.Txs[:transaction.TxIndex] {
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
|
||||
continue
|
||||
}
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
predecessors = append(predecessors, ethMsg)
|
||||
}
|
||||
}
|
||||
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[transaction.TxIndex])
|
||||
if err != nil {
|
||||
b.logger.Debug("tx not found", "hash", hash)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add predecessor messages in current cosmos tx
|
||||
for i := 0; i < int(transaction.MsgIndex); i++ {
|
||||
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
predecessors = append(predecessors, ethMsg)
|
||||
}
|
||||
|
||||
ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
|
||||
return nil, fmt.Errorf("invalid transaction type %T", tx)
|
||||
}
|
||||
|
||||
traceTxRequest := evmtypes.QueryTraceTxRequest{
|
||||
Msg: ethMessage,
|
||||
Predecessors: predecessors,
|
||||
BlockNumber: blk.Block.Height,
|
||||
BlockTime: blk.Block.Time,
|
||||
BlockHash: common.Bytes2Hex(blk.BlockID.Hash),
|
||||
}
|
||||
|
||||
if config != nil {
|
||||
traceTxRequest.TraceConfig = config
|
||||
}
|
||||
|
||||
// minus one to get the context of block beginning
|
||||
contextHeight := transaction.Height - 1
|
||||
if contextHeight < 1 {
|
||||
// 0 is a special value in `ContextWithHeight`
|
||||
contextHeight = 1
|
||||
}
|
||||
traceResult, err := b.queryClient.TraceTx(rpctypes.ContextWithHeight(contextHeight), &traceTxRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Response format is unknown due to custom tracer config param
|
||||
// More information can be found here https://geth.ethereum.org/docs/dapp/tracing-filtered
|
||||
var decodedResult interface{}
|
||||
err = json.Unmarshal(traceResult.Data, &decodedResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodedResult, nil
|
||||
}
|
||||
|
||||
// traceBlock configures a new tracer according to the provided configuration, and
|
||||
// executes all the transactions contained within. The return value will be one item
|
||||
// per transaction, dependent on the requested tracer.
|
||||
func (b *Backend) TraceBlock(height rpctypes.BlockNumber,
|
||||
config *evmtypes.TraceConfig,
|
||||
block *tmrpctypes.ResultBlock,
|
||||
) ([]*evmtypes.TxTraceResult, error) {
|
||||
txs := block.Block.Txs
|
||||
txsLength := len(txs)
|
||||
|
||||
if txsLength == 0 {
|
||||
// If there are no transactions return empty array
|
||||
return []*evmtypes.TxTraceResult{}, nil
|
||||
}
|
||||
|
||||
txDecoder := b.clientCtx.TxConfig.TxDecoder()
|
||||
|
||||
var txsMessages []*evmtypes.MsgEthereumTx
|
||||
for i, tx := range txs {
|
||||
decodedTx, err := txDecoder(tx)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to decode transaction", "hash", txs[i].Hash(), "error", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
for _, msg := range decodedTx.GetMsgs() {
|
||||
ethMessage, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
// Just considers Ethereum transactions
|
||||
continue
|
||||
}
|
||||
txsMessages = append(txsMessages, ethMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// minus one to get the context at the beginning of the block
|
||||
contextHeight := height - 1
|
||||
if contextHeight < 1 {
|
||||
// 0 is a special value for `ContextWithHeight`.
|
||||
contextHeight = 1
|
||||
}
|
||||
ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight))
|
||||
|
||||
traceBlockRequest := &evmtypes.QueryTraceBlockRequest{
|
||||
Txs: txsMessages,
|
||||
TraceConfig: config,
|
||||
BlockNumber: block.Block.Height,
|
||||
BlockTime: block.Block.Time,
|
||||
BlockHash: common.Bytes2Hex(block.BlockID.Hash),
|
||||
}
|
||||
|
||||
res, err := b.queryClient.TraceBlock(ctxWithHeight, traceBlockRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodedResults := make([]*evmtypes.TxTraceResult, txsLength)
|
||||
if err := json.Unmarshal(res.Data, &decodedResults); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodedResults, nil
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/pkg/errors"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// GetTransactionByHash returns the Ethereum format transaction identified by Ethereum transaction hash
|
||||
func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error) {
|
||||
res, err := b.GetTxByEthHash(txHash)
|
||||
hexTx := txHash.Hex()
|
||||
|
||||
if err != nil {
|
||||
return b.getTransactionByHashPending(txHash)
|
||||
}
|
||||
|
||||
block, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// the `res.MsgIndex` is inferred from tx index, should be within the bound.
|
||||
msg, ok := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid ethereum tx")
|
||||
}
|
||||
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&block.Block.Height)
|
||||
if err != nil {
|
||||
b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if res.EthTxIndex == -1 {
|
||||
// Fallback to find tx index by iterating all valid eth transactions
|
||||
msgs := b.EthMsgsFromTendermintBlock(block, blockRes)
|
||||
for i := range msgs {
|
||||
if msgs[i].Hash == hexTx {
|
||||
res.EthTxIndex = int32(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we still unable to find the eth tx index, return error, shouldn't happen.
|
||||
if res.EthTxIndex == -1 {
|
||||
return nil, errors.New("can't find index of ethereum tx")
|
||||
}
|
||||
|
||||
baseFee, err := b.BaseFee(blockRes)
|
||||
if err != nil {
|
||||
// handle the error for pruned node.
|
||||
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", blockRes.Height, "error", err)
|
||||
}
|
||||
|
||||
return rpctypes.NewTransactionFromMsg(
|
||||
msg,
|
||||
common.BytesToHash(block.BlockID.Hash.Bytes()),
|
||||
uint64(res.Height),
|
||||
uint64(res.EthTxIndex),
|
||||
baseFee,
|
||||
)
|
||||
}
|
||||
|
||||
// getTransactionByHashPending find pending tx from mempool
|
||||
func (b *Backend) getTransactionByHashPending(txHash common.Hash) (*rpctypes.RPCTransaction, error) {
|
||||
hexTx := txHash.Hex()
|
||||
// try to find tx in mempool
|
||||
txs, err := b.PendingTransactions()
|
||||
if err != nil {
|
||||
b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, tx := range txs {
|
||||
msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash)
|
||||
if err != nil {
|
||||
// not ethereum tx
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Hash == hexTx {
|
||||
// use zero block values since it's not included in a block yet
|
||||
rpctx, err := rpctypes.NewTransactionFromMsg(
|
||||
msg,
|
||||
common.Hash{},
|
||||
uint64(0),
|
||||
uint64(0),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rpctx, nil
|
||||
}
|
||||
}
|
||||
|
||||
b.logger.Debug("tx not found", "hash", hexTx)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetTransactionReceipt returns the transaction receipt identified by hash.
|
||||
func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
|
||||
hexTx := hash.Hex()
|
||||
b.logger.Debug("eth_getTransactionReceipt", "hash", hexTx)
|
||||
|
||||
res, err := b.GetTxByEthHash(hash)
|
||||
if err != nil {
|
||||
b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
resBlock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height))
|
||||
if err != nil {
|
||||
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex])
|
||||
if err != nil {
|
||||
b.logger.Debug("decoding failed", "error", err.Error())
|
||||
return nil, fmt.Errorf("failed to decode tx: %w", err)
|
||||
}
|
||||
ethMsg := tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(ethMsg.Data)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to unpack tx data", "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cumulativeGasUsed := uint64(0)
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&res.Height)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
for _, txResult := range blockRes.TxsResults[0:res.TxIndex] {
|
||||
cumulativeGasUsed += uint64(txResult.GasUsed)
|
||||
}
|
||||
cumulativeGasUsed += res.CumulativeGasUsed
|
||||
|
||||
var status hexutil.Uint
|
||||
if res.Failed {
|
||||
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
|
||||
} else {
|
||||
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
|
||||
}
|
||||
|
||||
chainID, err := b.ChainID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
from, err := ethMsg.GetSender(chainID.ToInt())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parse tx logs from events
|
||||
logs, err := TxLogsFromEvents(blockRes.TxsResults[res.TxIndex].Events, int(res.MsgIndex))
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error())
|
||||
}
|
||||
|
||||
if res.EthTxIndex == -1 {
|
||||
// Fallback to find tx index by iterating all valid eth transactions
|
||||
msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes)
|
||||
for i := range msgs {
|
||||
if msgs[i].Hash == hexTx {
|
||||
res.EthTxIndex = int32(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// return error if still unable to find the eth tx index
|
||||
if res.EthTxIndex == -1 {
|
||||
return nil, errors.New("can't find index of ethereum tx")
|
||||
}
|
||||
|
||||
receipt := map[string]interface{}{
|
||||
// Consensus fields: These fields are defined by the Yellow Paper
|
||||
"status": status,
|
||||
"cumulativeGasUsed": hexutil.Uint64(cumulativeGasUsed),
|
||||
"logsBloom": ethtypes.BytesToBloom(ethtypes.LogsBloom(logs)),
|
||||
"logs": logs,
|
||||
|
||||
// Implementation fields: These fields are added by geth when processing a transaction.
|
||||
// They are stored in the chain database.
|
||||
"transactionHash": hash,
|
||||
"contractAddress": nil,
|
||||
"gasUsed": hexutil.Uint64(res.GasUsed),
|
||||
|
||||
// Inclusion information: These fields provide information about the inclusion of the
|
||||
// transaction corresponding to this receipt.
|
||||
"blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(),
|
||||
"blockNumber": hexutil.Uint64(res.Height),
|
||||
"transactionIndex": hexutil.Uint64(res.EthTxIndex),
|
||||
|
||||
// sender and receiver (contract or EOA) addreses
|
||||
"from": from,
|
||||
"to": txData.GetTo(),
|
||||
}
|
||||
|
||||
if logs == nil {
|
||||
receipt["logs"] = [][]*ethtypes.Log{}
|
||||
}
|
||||
|
||||
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
|
||||
if txData.GetTo() == nil {
|
||||
receipt["contractAddress"] = crypto.CreateAddress(from, txData.GetNonce())
|
||||
}
|
||||
|
||||
if dynamicTx, ok := txData.(*evmtypes.DynamicFeeTx); ok {
|
||||
baseFee, err := b.BaseFee(blockRes)
|
||||
if err != nil {
|
||||
// tolerate the error for pruned node.
|
||||
b.logger.Error("fetch basefee failed, node is pruned?", "height", res.Height, "error", err)
|
||||
} else {
|
||||
receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.EffectiveGasPrice(baseFee))
|
||||
}
|
||||
}
|
||||
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
|
||||
func (b *Backend) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||||
b.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx)
|
||||
|
||||
block, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
|
||||
if err != nil {
|
||||
b.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if block.Block == nil {
|
||||
b.logger.Debug("block not found", "hash", hash.Hex())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return b.GetTransactionByBlockAndIndex(block, idx)
|
||||
}
|
||||
|
||||
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
|
||||
func (b *Backend) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||||
b.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
|
||||
|
||||
block, err := b.TendermintBlockByNumber(blockNum)
|
||||
if err != nil {
|
||||
b.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if block.Block == nil {
|
||||
b.logger.Debug("block not found", "height", blockNum.Int64())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return b.GetTransactionByBlockAndIndex(block, idx)
|
||||
}
|
||||
|
||||
// GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash
|
||||
// TODO: Don't need to convert once hashing is fixed on Tendermint
|
||||
// https://github.com/tendermint/tendermint/issues/6539
|
||||
func (b *Backend) GetTxByEthHash(hash common.Hash) (*ethermint.TxResult, error) {
|
||||
if b.indexer != nil {
|
||||
return b.indexer.GetByTxHash(hash)
|
||||
}
|
||||
|
||||
// fallback to tendermint tx indexer
|
||||
query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
|
||||
txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx {
|
||||
return txs.GetTxByHash(hash)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(err, "GetTxByEthHash %s", hash.Hex())
|
||||
}
|
||||
return txResult, nil
|
||||
}
|
||||
|
||||
// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
|
||||
func (b *Backend) GetTxByTxIndex(height int64, index uint) (*ethermint.TxResult, error) {
|
||||
if b.indexer != nil {
|
||||
return b.indexer.GetByBlockAndIndex(height, int32(index))
|
||||
}
|
||||
|
||||
// fallback to tendermint tx indexer
|
||||
query := fmt.Sprintf("tx.height=%d AND %s.%s=%d",
|
||||
height, evmtypes.TypeMsgEthereumTx,
|
||||
evmtypes.AttributeKeyTxIndex, index,
|
||||
)
|
||||
txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx {
|
||||
return txs.GetTxByTxIndex(int(index))
|
||||
})
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(err, "GetTxByTxIndex %d %d", height, index)
|
||||
}
|
||||
return txResult, nil
|
||||
}
|
||||
|
||||
// queryTendermintTxIndexer query tx in tendermint tx indexer
|
||||
func (b *Backend) queryTendermintTxIndexer(query string, txGetter func(*rpctypes.ParsedTxs) *rpctypes.ParsedTx) (*ethermint.TxResult, error) {
|
||||
resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resTxs.Txs) == 0 {
|
||||
return nil, errors.New("ethereum tx not found")
|
||||
}
|
||||
txResult := resTxs.Txs[0]
|
||||
if !rpctypes.TxSuccessOrExceedsBlockGasLimit(&txResult.TxResult) {
|
||||
return nil, errors.New("invalid ethereum tx")
|
||||
}
|
||||
|
||||
var tx sdk.Tx
|
||||
if txResult.TxResult.Code != 0 {
|
||||
// it's only needed when the tx exceeds block gas limit
|
||||
tx, err = b.clientCtx.TxConfig.TxDecoder()(txResult.Tx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ethereum tx")
|
||||
}
|
||||
}
|
||||
|
||||
return rpctypes.ParseTxIndexerResult(txResult, tx, txGetter)
|
||||
}
|
||||
|
||||
// getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`.
|
||||
func (b *Backend) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||||
blockRes, err := b.TendermintBlockResultByNumber(&block.Block.Height)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var msg *evmtypes.MsgEthereumTx
|
||||
// find in tx indexer
|
||||
res, err := b.GetTxByTxIndex(block.Block.Height, uint(idx))
|
||||
if err == nil {
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(block.Block.Txs[res.TxIndex])
|
||||
if err != nil {
|
||||
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ok bool
|
||||
// msgIndex is inferred from tx events, should be within bound.
|
||||
msg, ok = tx.GetMsgs()[res.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
i := int(idx)
|
||||
ethMsgs := b.EthMsgsFromTendermintBlock(block, blockRes)
|
||||
if i >= len(ethMsgs) {
|
||||
b.logger.Debug("block txs index out of bound", "index", i)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
msg = ethMsgs[i]
|
||||
}
|
||||
|
||||
baseFee, err := b.BaseFee(blockRes)
|
||||
if err != nil {
|
||||
// handle the error for pruned node.
|
||||
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Block.Height, "error", err)
|
||||
}
|
||||
|
||||
return rpctypes.NewTransactionFromMsg(
|
||||
msg,
|
||||
common.BytesToHash(block.Block.Hash()),
|
||||
uint64(block.Block.Height),
|
||||
uint64(idx),
|
||||
baseFee,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/cerc-io/laconicd/rpc/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
)
|
||||
|
||||
type txGasAndReward struct {
|
||||
gasUsed uint64
|
||||
reward *big.Int
|
||||
}
|
||||
|
||||
type sortGasAndReward []txGasAndReward
|
||||
|
||||
func (s sortGasAndReward) Len() int { return len(s) }
|
||||
func (s sortGasAndReward) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s sortGasAndReward) Less(i, j int) bool {
|
||||
return s[i].reward.Cmp(s[j].reward) < 0
|
||||
}
|
||||
|
||||
// getAccountNonce returns the account nonce for the given account address.
|
||||
// If the pending value is true, it will iterate over the mempool (pending)
|
||||
// txs in order to compute and return the pending tx sequence.
|
||||
// Todo: include the ability to specify a blockNumber
|
||||
func (b *Backend) getAccountNonce(accAddr common.Address, pending bool, height int64, logger log.Logger) (uint64, error) {
|
||||
queryClient := authtypes.NewQueryClient(b.clientCtx)
|
||||
res, err := queryClient.Account(types.ContextWithHeight(height), &authtypes.QueryAccountRequest{Address: sdk.AccAddress(accAddr.Bytes()).String()})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var acc authtypes.AccountI
|
||||
if err := b.clientCtx.InterfaceRegistry.UnpackAny(res.Account, &acc); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
nonce := acc.GetSequence()
|
||||
|
||||
if !pending {
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// the account retriever doesn't include the uncommitted transactions on the nonce so we need to
|
||||
// to manually add them.
|
||||
pendingTxs, err := b.PendingTransactions()
|
||||
if err != nil {
|
||||
logger.Error("failed to fetch pending transactions", "error", err.Error())
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// add the uncommitted txs to the nonce counter
|
||||
// only supports `MsgEthereumTx` style tx
|
||||
for _, tx := range pendingTxs {
|
||||
for _, msg := range (*tx).GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
// not ethereum tx
|
||||
break
|
||||
}
|
||||
|
||||
sender, err := ethMsg.GetSender(b.chainID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if sender == accAddr {
|
||||
nonce++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// output: targetOneFeeHistory
|
||||
func (b *Backend) processBlock(
|
||||
tendermintBlock *tmrpctypes.ResultBlock,
|
||||
ethBlock *map[string]interface{},
|
||||
rewardPercentiles []float64,
|
||||
tendermintBlockResult *tmrpctypes.ResultBlockResults,
|
||||
targetOneFeeHistory *types.OneFeeHistory,
|
||||
) error {
|
||||
blockHeight := tendermintBlock.Block.Height
|
||||
blockBaseFee, err := b.BaseFee(tendermintBlockResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set basefee
|
||||
targetOneFeeHistory.BaseFee = blockBaseFee
|
||||
|
||||
// set gas used ratio
|
||||
gasLimitUint64, ok := (*ethBlock)["gasLimit"].(hexutil.Uint64)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid gas limit type: %T", (*ethBlock)["gasLimit"])
|
||||
}
|
||||
|
||||
gasUsedBig, ok := (*ethBlock)["gasUsed"].(*hexutil.Big)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid gas used type: %T", (*ethBlock)["gasUsed"])
|
||||
}
|
||||
|
||||
gasusedfloat, _ := new(big.Float).SetInt(gasUsedBig.ToInt()).Float64()
|
||||
|
||||
if gasLimitUint64 <= 0 {
|
||||
return fmt.Errorf("gasLimit of block height %d should be bigger than 0 , current gaslimit %d", blockHeight, gasLimitUint64)
|
||||
}
|
||||
|
||||
gasUsedRatio := gasusedfloat / float64(gasLimitUint64)
|
||||
blockGasUsed := gasusedfloat
|
||||
targetOneFeeHistory.GasUsedRatio = gasUsedRatio
|
||||
|
||||
rewardCount := len(rewardPercentiles)
|
||||
targetOneFeeHistory.Reward = make([]*big.Int, rewardCount)
|
||||
for i := 0; i < rewardCount; i++ {
|
||||
targetOneFeeHistory.Reward[i] = big.NewInt(0)
|
||||
}
|
||||
|
||||
// check tendermintTxs
|
||||
tendermintTxs := tendermintBlock.Block.Txs
|
||||
tendermintTxResults := tendermintBlockResult.TxsResults
|
||||
tendermintTxCount := len(tendermintTxs)
|
||||
|
||||
var sorter sortGasAndReward
|
||||
|
||||
for i := 0; i < tendermintTxCount; i++ {
|
||||
eachTendermintTx := tendermintTxs[i]
|
||||
eachTendermintTxResult := tendermintTxResults[i]
|
||||
|
||||
tx, err := b.clientCtx.TxConfig.TxDecoder()(eachTendermintTx)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to decode transaction in block", "height", blockHeight, "error", err.Error())
|
||||
continue
|
||||
}
|
||||
txGasUsed := uint64(eachTendermintTxResult.GasUsed)
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tx := ethMsg.AsTransaction()
|
||||
reward := tx.EffectiveGasTipValue(blockBaseFee)
|
||||
if reward == nil {
|
||||
reward = big.NewInt(0)
|
||||
}
|
||||
sorter = append(sorter, txGasAndReward{gasUsed: txGasUsed, reward: reward})
|
||||
}
|
||||
}
|
||||
|
||||
// return an all zero row if there are no transactions to gather data from
|
||||
ethTxCount := len(sorter)
|
||||
if ethTxCount == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Sort(sorter)
|
||||
|
||||
var txIndex int
|
||||
sumGasUsed := sorter[0].gasUsed
|
||||
|
||||
for i, p := range rewardPercentiles {
|
||||
thresholdGasUsed := uint64(blockGasUsed * p / 100)
|
||||
for sumGasUsed < thresholdGasUsed && txIndex < ethTxCount-1 {
|
||||
txIndex++
|
||||
sumGasUsed += sorter[txIndex].gasUsed
|
||||
}
|
||||
targetOneFeeHistory.Reward[i] = sorter[txIndex].reward
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllTxLogsFromEvents parses all ethereum logs from cosmos events
|
||||
func AllTxLogsFromEvents(events []abci.Event) ([][]*ethtypes.Log, error) {
|
||||
allLogs := make([][]*ethtypes.Log, 0, 4)
|
||||
for _, event := range events {
|
||||
if event.Type != evmtypes.EventTypeTxLog {
|
||||
continue
|
||||
}
|
||||
|
||||
logs, err := ParseTxLogsFromEvent(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allLogs = append(allLogs, logs)
|
||||
}
|
||||
return allLogs, nil
|
||||
}
|
||||
|
||||
// TxLogsFromEvents parses ethereum logs from cosmos events for specific msg index
|
||||
func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*ethtypes.Log, error) {
|
||||
for _, event := range events {
|
||||
if event.Type != evmtypes.EventTypeTxLog {
|
||||
continue
|
||||
}
|
||||
|
||||
if msgIndex > 0 {
|
||||
// not the eth tx we want
|
||||
msgIndex--
|
||||
continue
|
||||
}
|
||||
|
||||
return ParseTxLogsFromEvent(event)
|
||||
}
|
||||
return nil, fmt.Errorf("eth tx logs not found for message index %d", msgIndex)
|
||||
}
|
||||
|
||||
// ParseTxLogsFromEvent parse tx logs from one event
|
||||
func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) {
|
||||
logs := make([]*evmtypes.Log, 0, len(event.Attributes))
|
||||
for _, attr := range event.Attributes {
|
||||
if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) {
|
||||
continue
|
||||
}
|
||||
|
||||
var log evmtypes.Log
|
||||
if err := json.Unmarshal(attr.Value, &log); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logs = append(logs, &log)
|
||||
}
|
||||
return evmtypes.LogsToEthereum(logs), nil
|
||||
}
|
||||
|
||||
// ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored
|
||||
// workaround for issue: https://github.com/cosmos/cosmos-sdk/issues/10832
|
||||
func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool {
|
||||
return res.GetCode() == 11 && strings.Contains(res.GetLog(), "no block gas left to run tx: out of gas")
|
||||
}
|
||||
|
||||
// GetLogsFromBlockResults returns the list of event logs from the tendermint block result response
|
||||
func GetLogsFromBlockResults(blockRes *tmrpctypes.ResultBlockResults) ([][]*ethtypes.Log, error) {
|
||||
blockLogs := [][]*ethtypes.Log{}
|
||||
for _, txResult := range blockRes.TxsResults {
|
||||
logs, err := AllTxLogsFromEvents(txResult.Events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockLogs = append(blockLogs, logs...)
|
||||
}
|
||||
|
||||
return blockLogs, nil
|
||||
}
|
||||
Reference in New Issue
Block a user