update fork

This commit is contained in:
0xmuralik
2022-10-10 16:08:33 +05:30
parent 632d53e34a
commit 56d59feaa0
383 changed files with 36784 additions and 21462 deletions
+68 -43
View File
@@ -10,22 +10,28 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/cerc-io/laconicd/rpc/ethereum/backend"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/debug"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/eth"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/eth/filters"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/miner"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/net"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/personal"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/txpool"
"github.com/cerc-io/laconicd/rpc/ethereum/namespaces/web3"
"github.com/cerc-io/laconicd/rpc/ethereum/types"
"github.com/cerc-io/laconicd/rpc/backend"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/debug"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/eth"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/eth/filters"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/miner"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/net"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/personal"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/txpool"
"github.com/cerc-io/laconicd/rpc/namespaces/ethereum/web3"
ethermint "github.com/cerc-io/laconicd/types"
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
)
// RPC namespaces and API version
const (
// Cosmos namespaces
CosmosNamespace = "cosmos"
// Ethereum namespaces
Web3Namespace = "web3"
EthNamespace = "eth"
PersonalNamespace = "personal"
@@ -37,22 +43,32 @@ const (
apiVersion = "1.0"
)
// APICreator creates the json-rpc api implementations.
type APICreator = func(*server.Context, client.Context, *rpcclient.WSClient) []rpc.API
// APICreator creates the JSON-RPC API implementations.
type APICreator = func(
ctx *server.Context,
clientCtx client.Context,
tendermintWebsocketClient *rpcclient.WSClient,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
) []rpc.API
// apiCreators defines the json-rpc api namespaces.
// apiCreators defines the JSON-RPC API namespaces.
var apiCreators map[string]APICreator
func init() {
apiCreators = map[string]APICreator{
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient) []rpc.API {
nonceLock := new(types.AddrLocker)
evmBackend := backend.NewEVMBackend(ctx, ctx.Logger, clientCtx)
EthNamespace: func(ctx *server.Context,
clientCtx client.Context,
tmWSClient *rpcclient.WSClient,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: EthNamespace,
Version: apiVersion,
Service: eth.NewPublicAPI(ctx.Logger, clientCtx, evmBackend, nonceLock),
Service: eth.NewPublicAPI(ctx.Logger, evmBackend),
Public: true,
},
{
@@ -63,7 +79,7 @@ func init() {
},
}
},
Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient) []rpc.API {
Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient, bool, ethermint.EVMTxIndexer) []rpc.API {
return []rpc.API{
{
Namespace: Web3Namespace,
@@ -73,7 +89,7 @@ func init() {
},
}
},
NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool, _ ethermint.EVMTxIndexer) []rpc.API {
return []rpc.API{
{
Namespace: NetNamespace,
@@ -83,18 +99,23 @@ func init() {
},
}
},
PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
evmBackend := backend.NewEVMBackend(ctx, ctx.Logger, clientCtx)
PersonalNamespace: func(ctx *server.Context,
clientCtx client.Context,
_ *rpcclient.WSClient,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: PersonalNamespace,
Version: apiVersion,
Service: personal.NewAPI(ctx.Logger, clientCtx, evmBackend),
Service: personal.NewAPI(ctx.Logger, evmBackend),
Public: false,
},
}
},
TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient) []rpc.API {
TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient, _ bool, _ ethermint.EVMTxIndexer) []rpc.API {
return []rpc.API{
{
Namespace: TxPoolNamespace,
@@ -104,24 +125,34 @@ func init() {
},
}
},
DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
evmBackend := backend.NewEVMBackend(ctx, ctx.Logger, clientCtx)
DebugNamespace: func(ctx *server.Context,
clientCtx client.Context,
_ *rpcclient.WSClient,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: DebugNamespace,
Version: apiVersion,
Service: debug.NewAPI(ctx, evmBackend, clientCtx),
Service: debug.NewAPI(ctx, evmBackend),
Public: true,
},
}
},
MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
evmBackend := backend.NewEVMBackend(ctx, ctx.Logger, clientCtx)
MinerNamespace: func(ctx *server.Context,
clientCtx client.Context,
_ *rpcclient.WSClient,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
) []rpc.API {
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs, indexer)
return []rpc.API{
{
Namespace: MinerNamespace,
Version: apiVersion,
Service: miner.NewPrivateAPI(ctx, clientCtx, evmBackend),
Service: miner.NewPrivateAPI(ctx, evmBackend),
Public: false,
},
}
@@ -129,25 +160,19 @@ func init() {
}
}
func unique(intSlice []string) []string {
keys := make(map[string]bool)
var list []string
for _, entry := range intSlice {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}
// GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, selectedAPIs []string) []rpc.API {
func GetRPCAPIs(ctx *server.Context,
clientCtx client.Context,
tmWSClient *rpcclient.WSClient,
allowUnprotectedTxs bool,
indexer ethermint.EVMTxIndexer,
selectedAPIs []string,
) []rpc.API {
var apis []rpc.API
for _, ns := range selectedAPIs {
if creator, ok := apiCreators[ns]; ok {
apis = append(apis, creator(ctx, clientCtx, tmWSClient)...)
apis = append(apis, creator(ctx, clientCtx, tmWSClient, allowUnprotectedTxs, indexer)...)
} else {
ctx.Logger.Error("invalid namespace value", "namespace", ns)
}
+206
View File
@@ -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
}
+192
View File
@@ -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,
}
}
+147
View File
@@ -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()}...)
}
+491
View File
@@ -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
+398
View File
@@ -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 := &ethtypes.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
}
+265
View File
@@ -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, &ethBlock, 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
}
+146
View File
@@ -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)
}
})
}
}
+157
View File
@@ -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)
}
+37
View File
@@ -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
}
+841
View File
@@ -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
}
+393
View File
@@ -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
}
+339
View File
@@ -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 := &ethsecp256k1.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
}
+160
View File
@@ -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)
}
+148
View File
@@ -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
}
+175
View File
@@ -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
}
+390
View File
@@ -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,
)
}
+265
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
-215
View File
@@ -1,215 +0,0 @@
package backend
import (
"fmt"
"math/big"
"sort"
rpctypes "github.com/cerc-io/laconicd/rpc/ethereum/types"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
)
type (
txGasAndReward struct {
gasUsed uint64
reward *big.Int
}
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
}
// output: targetOneFeeHistory
func (e *EVMBackend) processBlock(
tendermintBlock *tmrpctypes.ResultBlock,
ethBlock *map[string]interface{},
rewardPercentiles []float64,
tendermintBlockResult *tmrpctypes.ResultBlockResults,
targetOneFeeHistory *rpctypes.OneFeeHistory,
) error {
blockHeight := tendermintBlock.Block.Height
blockBaseFee, err := e.BaseFee(blockHeight)
if err != nil {
return err
}
// set basefee
targetOneFeeHistory.BaseFee = blockBaseFee
// set gasused ratio
gasLimitUint64 := (*ethBlock)["gasLimit"].(hexutil.Uint64)
gasUsedBig := (*ethBlock)["gasUsed"].(*hexutil.Big)
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 := e.clientCtx.TxConfig.TxDecoder()(eachTendermintTx)
if err != nil {
e.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
}
// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
func (e *EVMBackend) 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 := e.BlockNumber()
if err != nil {
return nil, err
}
blockEnd = int64(blockNumber)
}
userBlockCountInt := int64(userBlockCount)
maxBlockCount := int64(e.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)
// eth block
ethBlock, err := e.GetBlockByNumber(rpctypes.BlockNumber(blockID), true)
if ethBlock == nil {
return nil, err
}
// tendermint block
tendermintblock, err := e.GetTendermintBlockByNumber(rpctypes.BlockNumber(blockID))
if tendermintblock == nil {
return nil, err
}
// tendermint block result
tendermintBlockResult, err := e.clientCtx.Client.BlockResults(e.ctx, &tendermintblock.Block.Height)
if tendermintBlockResult == nil {
e.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
return nil, err
}
oneFeeHistory := rpctypes.OneFeeHistory{}
err = e.processBlock(tendermintblock, &ethBlock, 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
}
-253
View File
@@ -1,253 +0,0 @@
package backend
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
"github.com/cerc-io/laconicd/rpc/ethereum/types"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// SetTxDefaults populates tx message with default values in case they are not
// provided on the args
func (e *EVMBackend) 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 := e.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 := e.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 := e.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, _ := e.getAccountNonce(*args.From, true, 0, e.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 := types.NewBlockNumber(big.NewInt(0))
estimated, err := e.EstimateGas(callArgs, &blockNr)
if err != nil {
return args, err
}
args.Gas = &estimated
e.logger.Debug("estimate gas usage automatically", "gas", args.Gas)
}
if args.ChainID == nil {
args.ChainID = (*hexutil.Big)(e.chainID)
}
return args, nil
}
// 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 (e *EVMBackend) getAccountNonce(accAddr common.Address, pending bool, height int64, logger log.Logger) (uint64, error) {
queryClient := authtypes.NewQueryClient(e.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 := e.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 := e.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(e.chainID)
if err != nil {
continue
}
if sender == accAddr {
nonce++
}
}
}
return nonce, 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([]byte(attr.Key), []byte(evmtypes.AttributeKeyTxLog)) {
continue
}
var log evmtypes.Log
if err := json.Unmarshal([]byte(attr.Value), &log); err != nil {
return nil, err
}
logs = append(logs, &log)
}
return evmtypes.LogsToEthereum(logs), nil
}
File diff suppressed because it is too large Load Diff
-192
View File
@@ -1,192 +0,0 @@
package miner
import (
"math/big"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/server"
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/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cerc-io/laconicd/rpc/ethereum/backend"
rpctypes "github.com/cerc-io/laconicd/rpc/ethereum/types"
"github.com/cerc-io/laconicd/server/config"
)
// API is the private miner prefixed set of APIs in the Miner JSON-RPC spec.
type API struct {
ctx *server.Context
logger log.Logger
clientCtx client.Context
backend backend.Backend
}
// NewPrivateAPI creates an instance of the Miner API.
func NewPrivateAPI(
ctx *server.Context,
clientCtx client.Context,
backend backend.Backend,
) *API {
return &API{
ctx: ctx,
clientCtx: clientCtx,
logger: ctx.Logger.With("api", "miner"),
backend: backend,
}
}
// SetEtherbase sets the etherbase of the miner
func (api *API) SetEtherbase(etherbase common.Address) bool {
api.logger.Debug("miner_setEtherbase")
delAddr, err := api.backend.GetCoinbase()
if err != nil {
api.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 {
api.logger.Debug("tx failed basic validation", "error", err.Error())
return false
}
// Assemble transaction from fields
builder, ok := api.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok {
api.logger.Debug("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error())
return false
}
err = builder.SetMsgs(msg)
if err != nil {
api.logger.Error("builder.SetMsgs failed", "error", err.Error())
return false
}
// Fetch minimun gas price to calculate fees using the configuration.
appConf, err := config.GetConfig(api.ctx.Viper)
if err != nil {
panic(err)
}
minGasPrices := appConf.GetMinGasPrices()
if len(minGasPrices) == 0 || minGasPrices.Empty() {
api.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 := api.backend.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber)
if err != nil {
api.logger.Debug("failed to get nonce", "error", err.Error())
return false
}
txFactory := tx.Factory{}
txFactory = txFactory.
WithChainID(api.clientCtx.ChainID).
WithKeybase(api.clientCtx.Keyring).
WithTxConfig(api.clientCtx.TxConfig).
WithSequence(uint64(*nonce)).
WithGasAdjustment(1.25)
_, gas, err := tx.CalculateGas(api.clientCtx, txFactory, msg)
if err != nil {
api.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, sdk.NewIntFromBigInt(value))}
builder.SetFeeAmount(fees)
builder.SetGasLimit(gas)
keyInfo, err := api.clientCtx.Keyring.KeyByAddress(delAddr)
if err != nil {
api.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 {
api.logger.Debug("failed to sign tx", "error", err.Error())
return false
}
// Encode transaction by default Tx encoder
txEncoder := api.clientCtx.TxConfig.TxEncoder()
txBytes, err := txEncoder(builder.GetTx())
if err != nil {
api.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 := api.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 {
api.logger.Debug("failed to broadcast tx", "error", err.Error())
return false
}
api.logger.Debug("broadcasted tx to set miner withdraw address (etherbase)", "hash", tmHash.String())
return true
}
// 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 (api *API) SetGasPrice(gasPrice hexutil.Big) bool {
api.logger.Info(api.ctx.Viper.ConfigFileUsed())
appConf, err := config.GetConfig(api.ctx.Viper)
if err != nil {
panic(err)
}
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 {
api.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(api.ctx.Viper.ConfigFileUsed(), appConf)
api.logger.Info("Your configuration file was modified. Please RESTART your node.", "gas-price", c.String())
return true
}
@@ -2,7 +2,6 @@ package debug
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@@ -14,17 +13,15 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
"github.com/cosmos/cosmos-sdk/client"
stderrors "github.com/pkg/errors"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cerc-io/laconicd/rpc/ethereum/backend"
rpctypes "github.com/cerc-io/laconicd/rpc/ethereum/types"
"github.com/cerc-io/laconicd/rpc/backend"
rpctypes "github.com/cerc-io/laconicd/rpc/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash"
@@ -43,27 +40,22 @@ type HandlerT struct {
// API is the collection of tracing APIs exposed over the private debugging endpoint.
type API struct {
ctx *server.Context
logger log.Logger
backend backend.Backend
clientCtx client.Context
queryClient *rpctypes.QueryClient
handler *HandlerT
ctx *server.Context
logger log.Logger
backend backend.EVMBackend
handler *HandlerT
}
// NewAPI creates a new API definition for the tracing methods of the Ethereum service.
func NewAPI(
ctx *server.Context,
backend backend.Backend,
clientCtx client.Context,
backend backend.EVMBackend,
) *API {
return &API{
ctx: ctx,
logger: ctx.Logger.With("module", "debug"),
backend: backend,
clientCtx: clientCtx,
queryClient: rpctypes.NewQueryClient(clientCtx),
handler: new(HandlerT),
ctx: ctx,
logger: ctx.Logger.With("module", "debug"),
backend: backend,
handler: new(HandlerT),
}
}
@@ -71,106 +63,7 @@ func NewAPI(
// and returns them as a JSON object.
func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
a.logger.Debug("debug_traceTransaction", "hash", hash)
// Get transaction by hash
transaction, err := a.backend.GetTxByEthHash(hash)
if err != nil {
a.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 := a.backend.GetTendermintBlockByNumber(rpctypes.BlockNumber(transaction.Height))
if err != nil {
a.logger.Debug("block not found", "height", transaction.Height)
return nil, err
}
msgIndex, _ := rpctypes.FindTxAttributes(transaction.TxResult.Events, hash.Hex())
if msgIndex < 0 {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex())
}
// check tx index is not out of bound
if uint32(len(blk.Block.Txs)) < transaction.Index {
a.logger.Debug("tx index out of bounds", "index", transaction.Index, "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.Index] {
tx, err := a.clientCtx.TxConfig.TxDecoder()(txBz)
if err != nil {
a.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 := a.clientCtx.TxConfig.TxDecoder()(transaction.Tx)
if err != nil {
a.logger.Debug("tx not found", "hash", hash)
return nil, err
}
// add predecessor messages in current cosmos tx
for i := 0; i < msgIndex; i++ {
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
if !ok {
continue
}
predecessors = append(predecessors, ethMsg)
}
ethMessage, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
a.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
return nil, fmt.Errorf("invalid transaction type %T", tx)
}
traceTxRequest := evmtypes.QueryTraceTxRequest{
Msg: ethMessage,
TxIndex: uint64(transaction.Index),
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 := a.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
return a.backend.TraceTransaction(hash, config)
}
// TraceBlockByNumber returns the structured logs created during the execution of
@@ -181,13 +74,13 @@ func (a *API) TraceBlockByNumber(height rpctypes.BlockNumber, config *evmtypes.T
return nil, errors.New("genesis is not traceable")
}
// Get Tendermint Block
resBlock, err := a.backend.GetTendermintBlockByNumber(height)
resBlock, err := a.backend.TendermintBlockByNumber(height)
if err != nil {
a.logger.Debug("get block failed", "height", height, "error", err.Error())
return nil, err
}
return a.traceBlock(height, config, resBlock)
return a.backend.TraceBlock(rpctypes.BlockNumber(resBlock.Block.Height), config, resBlock)
}
// TraceBlockByHash returns the structured logs created during the execution of
@@ -195,7 +88,7 @@ func (a *API) TraceBlockByNumber(height rpctypes.BlockNumber, config *evmtypes.T
func (a *API) TraceBlockByHash(hash common.Hash, config *evmtypes.TraceConfig) ([]*evmtypes.TxTraceResult, error) {
a.logger.Debug("debug_traceBlockByHash", "hash", hash)
// Get Tendermint Block
resBlock, err := a.backend.GetTendermintBlockByHash(hash)
resBlock, err := a.backend.TendermintBlockByHash(hash)
if err != nil {
a.logger.Debug("get block failed", "hash", hash.Hex(), "error", err.Error())
return nil, err
@@ -206,68 +99,7 @@ func (a *API) TraceBlockByHash(hash common.Hash, config *evmtypes.TraceConfig) (
return nil, errors.New("block not found")
}
return a.traceBlock(rpctypes.BlockNumber(resBlock.Block.Height), config, resBlock)
}
// 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 (a *API) 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 := a.clientCtx.TxConfig.TxDecoder()
var txsMessages []*evmtypes.MsgEthereumTx
for i, tx := range txs {
decodedTx, err := txDecoder(tx)
if err != nil {
a.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 := a.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
return a.backend.TraceBlock(rpctypes.BlockNumber(resBlock.Block.Height), config, resBlock)
}
// BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
@@ -284,7 +116,7 @@ func (a *API) BlockProfile(file string, nsec uint) error {
// CpuProfile turns on CPU profiling for nsec seconds and writes
// profile data to file.
func (a *API) CpuProfile(file string, nsec uint) error { // nolint: golint, stylecheck, revive
func (a *API) CpuProfile(file string, nsec uint) error { //nolint: golint, stylecheck, revive
a.logger.Debug("debug_cpuProfile", "file", file, "nsec", nsec)
if err := a.StartCPUProfile(file); err != nil {
return err
@@ -466,7 +298,7 @@ func (a *API) GetHeaderRlp(number uint64) (hexutil.Bytes, error) {
// GetBlockRlp retrieves the RLP encoded for of a single block.
func (a *API) GetBlockRlp(number uint64) (hexutil.Bytes, error) {
block, err := a.backend.BlockByNumber(rpctypes.BlockNumber(number))
block, err := a.backend.EthBlockByNumber(rpctypes.BlockNumber(number))
if err != nil {
return nil, err
}
@@ -476,7 +308,7 @@ func (a *API) GetBlockRlp(number uint64) (hexutil.Bytes, error) {
// PrintBlock retrieves a block and returns its pretty printed form.
func (a *API) PrintBlock(number uint64) (string, error) {
block, err := a.backend.BlockByNumber(rpctypes.BlockNumber(number))
block, err := a.backend.EthBlockByNumber(rpctypes.BlockNumber(number))
if err != nil {
return "", err
}
+512
View File
@@ -0,0 +1,512 @@
package eth
import (
"context"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/ethereum/go-ethereum/rpc"
"github.com/tendermint/tendermint/libs/log"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/cerc-io/laconicd/rpc/backend"
rpctypes "github.com/cerc-io/laconicd/rpc/types"
ethermint "github.com/cerc-io/laconicd/types"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
)
// The Ethereum API allows applications to connect to an Evmos node that is
// part of the Evmos blockchain. Developers can interact with on-chain EVM data
// and send different types of transactions to the network by utilizing the
// endpoints provided by the API. The API follows a JSON-RPC standard. If not
// otherwise specified, the interface is derived from the Alchemy Ethereum API:
// https://docs.alchemy.com/alchemy/apis/ethereum
type EthereumAPI interface {
// Getting Blocks
//
// Retrieves information from a particular block in the blockchain.
BlockNumber() (hexutil.Uint64, error)
GetBlockByNumber(ethBlockNum 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
// Reading Transactions
//
// Retrieves information on the state data for addresses regardless of whether
// it is a user or a smart contract.
GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error)
GetTransactionCount(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Uint64, 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)
// eth_getBlockReceipts
// Writing Transactions
//
// Allows developers to both send ETH from one address to another, write data
// on-chain, and interact with smart contracts.
SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error)
// eth_sendPrivateTransaction
// eth_cancel PrivateTransaction
// Account Information
//
// Returns information regarding an address's stored on-chain data.
Accounts() ([]common.Address, error)
GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error)
GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error)
GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error)
GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error)
// EVM/Smart Contract Execution
//
// Allows developers to read data from the blockchain which includes executing
// smart contracts. However, no data is published to the Ethereum network.
Call(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash, _ *rpctypes.StateOverride) (hexutil.Bytes, error)
// Chain Information
//
// Returns information on the Ethereum network and internal settings.
ProtocolVersion() hexutil.Uint
GasPrice() (*hexutil.Big, error)
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error)
FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error)
MaxPriorityFeePerGas() (*hexutil.Big, error)
ChainId() (*hexutil.Big, error)
// Getting Uncles
//
// Returns information on uncle blocks are which are network rejected blocks and replaced by a canonical block instead.
GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{}
GetUncleByBlockNumberAndIndex(number, idx hexutil.Uint) map[string]interface{}
GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint
GetUncleCountByBlockNumber(blockNum rpctypes.BlockNumber) hexutil.Uint
// Proof of Work
Hashrate() hexutil.Uint64
Mining() bool
// Other
Syncing() (interface{}, error)
Coinbase() (string, error)
Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error)
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error)
FillTransaction(args evmtypes.TransactionArgs) (*rpctypes.SignTransactionResult, error)
Resend(ctx context.Context, args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error)
GetPendingTransactions() ([]*rpctypes.RPCTransaction, error)
// eth_signTransaction (on Ethereum.org)
// eth_getCompilers (on Ethereum.org)
// eth_compileSolidity (on Ethereum.org)
// eth_compileLLL (on Ethereum.org)
// eth_compileSerpent (on Ethereum.org)
// eth_getWork (on Ethereum.org)
// eth_submitWork (on Ethereum.org)
// eth_submitHashrate (on Ethereum.org)
}
var _ EthereumAPI = (*PublicAPI)(nil)
// PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicAPI struct {
ctx context.Context
logger log.Logger
backend backend.EVMBackend
}
// NewPublicAPI creates an instance of the public ETH Web3 API.
func NewPublicAPI(logger log.Logger, backend backend.EVMBackend) *PublicAPI {
api := &PublicAPI{
ctx: context.Background(),
logger: logger.With("client", "json-rpc"),
backend: backend,
}
return api
}
///////////////////////////////////////////////////////////////////////////////
/// Blocks ///
///////////////////////////////////////////////////////////////////////////////
// BlockNumber returns the current block number.
func (e *PublicAPI) BlockNumber() (hexutil.Uint64, error) {
e.logger.Debug("eth_blockNumber")
return e.backend.BlockNumber()
}
// GetBlockByNumber returns the block identified by number.
func (e *PublicAPI) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
e.logger.Debug("eth_getBlockByNumber", "number", ethBlockNum, "full", fullTx)
return e.backend.GetBlockByNumber(ethBlockNum, fullTx)
}
// GetBlockByHash returns the block identified by hash.
func (e *PublicAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
e.logger.Debug("eth_getBlockByHash", "hash", hash.Hex(), "full", fullTx)
return e.backend.GetBlockByHash(hash, fullTx)
}
///////////////////////////////////////////////////////////////////////////////
/// Read Txs ///
///////////////////////////////////////////////////////////////////////////////
// GetTransactionByHash returns the transaction identified by hash.
func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByHash", "hash", hash.Hex())
return e.backend.GetTransactionByHash(hash)
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Uint64, error) {
e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number or hash", blockNrOrHash)
blockNum, err := e.backend.BlockNumberFromTendermint(blockNrOrHash)
if err != nil {
return nil, err
}
return e.backend.GetTransactionCount(address, blockNum)
}
// GetTransactionReceipt returns the transaction receipt identified by hash.
func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
hexTx := hash.Hex()
e.logger.Debug("eth_getTransactionReceipt", "hash", hexTx)
return e.backend.GetTransactionReceipt(hash)
}
// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
func (e *PublicAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash.Hex())
return e.backend.GetBlockTransactionCountByHash(hash)
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
func (e *PublicAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByNumber", "height", blockNum.Int64())
return e.backend.GetBlockTransactionCountByNumber(blockNum)
}
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx)
return e.backend.GetTransactionByBlockHashAndIndex(hash, idx)
}
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
return e.backend.GetTransactionByBlockNumberAndIndex(blockNum, idx)
}
///////////////////////////////////////////////////////////////////////////////
/// Write Txs ///
///////////////////////////////////////////////////////////////////////////////
// SendRawTransaction send a raw Ethereum transaction.
func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
e.logger.Debug("eth_sendRawTransaction", "length", len(data))
return e.backend.SendRawTransaction(data)
}
// SendTransaction sends an Ethereum transaction.
func (e *PublicAPI) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) {
e.logger.Debug("eth_sendTransaction", "args", args.String())
return e.backend.SendTransaction(args)
}
///////////////////////////////////////////////////////////////////////////////
/// Account Information ///
///////////////////////////////////////////////////////////////////////////////
// Accounts returns the list of accounts available to this node.
func (e *PublicAPI) Accounts() ([]common.Address, error) {
e.logger.Debug("eth_accounts")
return e.backend.Accounts()
}
// GetBalance returns the provided account's balance up to the provided block number.
func (e *PublicAPI) GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error) {
e.logger.Debug("eth_getBalance", "address", address.String(), "block number or hash", blockNrOrHash)
return e.backend.GetBalance(address, blockNrOrHash)
}
// GetStorageAt returns the contract storage at the given address, block number, and key.
func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
e.logger.Debug("eth_getStorageAt", "address", address.Hex(), "key", key, "block number or hash", blockNrOrHash)
return e.backend.GetStorageAt(address, key, blockNrOrHash)
}
// GetCode returns the contract code at the given address and block number.
func (e *PublicAPI) GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
e.logger.Debug("eth_getCode", "address", address.Hex(), "block number or hash", blockNrOrHash)
return e.backend.GetCode(address, blockNrOrHash)
}
// GetProof returns an account object with proof and any storage proofs
func (e *PublicAPI) GetProof(address common.Address,
storageKeys []string,
blockNrOrHash rpctypes.BlockNumberOrHash,
) (*rpctypes.AccountResult, error) {
e.logger.Debug("eth_getProof", "address", address.Hex(), "keys", storageKeys, "block number or hash", blockNrOrHash)
return e.backend.GetProof(address, storageKeys, blockNrOrHash)
}
///////////////////////////////////////////////////////////////////////////////
/// EVM/Smart Contract Execution ///
///////////////////////////////////////////////////////////////////////////////
// Call performs a raw contract call.
func (e *PublicAPI) Call(args evmtypes.TransactionArgs,
blockNrOrHash rpctypes.BlockNumberOrHash,
_ *rpctypes.StateOverride,
) (hexutil.Bytes, error) {
e.logger.Debug("eth_call", "args", args.String(), "block number or hash", blockNrOrHash)
blockNum, err := e.backend.BlockNumberFromTendermint(blockNrOrHash)
if err != nil {
return nil, err
}
data, err := e.backend.DoCall(args, blockNum)
if err != nil {
return []byte{}, err
}
return (hexutil.Bytes)(data.Ret), nil
}
///////////////////////////////////////////////////////////////////////////////
/// Event Logs ///
///////////////////////////////////////////////////////////////////////////////
// FILTER API at ./filters/api.go
///////////////////////////////////////////////////////////////////////////////
/// Chain Information ///
///////////////////////////////////////////////////////////////////////////////
// ProtocolVersion returns the supported Ethereum protocol version.
func (e *PublicAPI) ProtocolVersion() hexutil.Uint {
e.logger.Debug("eth_protocolVersion")
return hexutil.Uint(ethermint.ProtocolVersion)
}
// GasPrice returns the current gas price based on Ethermint's gas price oracle.
func (e *PublicAPI) GasPrice() (*hexutil.Big, error) {
e.logger.Debug("eth_gasPrice")
return e.backend.GasPrice()
}
// EstimateGas returns an estimate of gas usage for the given smart contract call.
func (e *PublicAPI) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) {
e.logger.Debug("eth_estimateGas")
return e.backend.EstimateGas(args, blockNrOptional)
}
func (e *PublicAPI) FeeHistory(blockCount rpc.DecimalOrHex,
lastBlock rpc.BlockNumber,
rewardPercentiles []float64,
) (*rpctypes.FeeHistoryResult, error) {
e.logger.Debug("eth_feeHistory")
return e.backend.FeeHistory(blockCount, lastBlock, rewardPercentiles)
}
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
func (e *PublicAPI) MaxPriorityFeePerGas() (*hexutil.Big, error) {
e.logger.Debug("eth_maxPriorityFeePerGas")
head := e.backend.CurrentHeader()
tipcap, err := e.backend.SuggestGasTipCap(head.BaseFee)
if err != nil {
return nil, err
}
return (*hexutil.Big)(tipcap), nil
}
// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { //nolint
e.logger.Debug("eth_chainId")
return e.backend.ChainID()
}
///////////////////////////////////////////////////////////////////////////////
/// Uncles ///
///////////////////////////////////////////////////////////////////////////////
// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil.
func (e *PublicAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} {
return nil
}
// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil.
func (e *PublicAPI) GetUncleByBlockNumberAndIndex(number, idx hexutil.Uint) map[string]interface{} {
return nil
}
// GetUncleCountByBlockHash returns the number of uncles in the block identified by hash. Always zero.
func (e *PublicAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint {
return 0
}
// GetUncleCountByBlockNumber returns the number of uncles in the block identified by number. Always zero.
func (e *PublicAPI) GetUncleCountByBlockNumber(blockNum rpctypes.BlockNumber) hexutil.Uint {
return 0
}
///////////////////////////////////////////////////////////////////////////////
/// Proof of Work ///
///////////////////////////////////////////////////////////////////////////////
// Hashrate returns the current node's hashrate. Always 0.
func (e *PublicAPI) Hashrate() hexutil.Uint64 {
e.logger.Debug("eth_hashrate")
return 0
}
// Mining returns whether or not this node is currently mining. Always false.
func (e *PublicAPI) Mining() bool {
e.logger.Debug("eth_mining")
return false
}
///////////////////////////////////////////////////////////////////////////////
/// Other ///
///////////////////////////////////////////////////////////////////////////////
// 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 (e *PublicAPI) Syncing() (interface{}, error) {
e.logger.Debug("eth_syncing")
return e.backend.Syncing()
}
// Coinbase is the address that staking rewards will be send to (alias for Etherbase).
func (e *PublicAPI) Coinbase() (string, error) {
e.logger.Debug("eth_coinbase")
coinbase, err := e.backend.GetCoinbase()
if err != nil {
return "", err
}
ethAddr := common.BytesToAddress(coinbase.Bytes())
return ethAddr.Hex(), nil
}
// Sign signs the provided data using the private key of address via Geth's signature standard.
func (e *PublicAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
e.logger.Debug("eth_sign", "address", address.Hex(), "data", common.Bytes2Hex(data))
return e.backend.Sign(address, data)
}
// GetTransactionLogs returns the logs given a transaction hash.
func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
e.logger.Debug("eth_getTransactionLogs", "hash", txHash)
hexTx := txHash.Hex()
res, err := e.backend.GetTxByEthHash(txHash)
if err != nil {
e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
return nil, nil
}
if res.Failed {
// failed, return empty logs
return nil, nil
}
resBlockResult, err := e.backend.TendermintBlockResultByNumber(&res.Height)
if err != nil {
e.logger.Debug("block result not found", "number", res.Height, "error", err.Error())
return nil, nil
}
// parse tx logs from events
return backend.TxLogsFromEvents(resBlockResult.TxsResults[res.TxIndex].Events, int(res.MsgIndex))
}
// SignTypedData signs EIP-712 conformant typed data
func (e *PublicAPI) SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error) {
e.logger.Debug("eth_signTypedData", "address", address.Hex(), "data", typedData)
return e.backend.SignTypedData(address, typedData)
}
// FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields)
// on a given unsigned transaction, and returns it to the caller for further
// processing (signing + broadcast).
func (e *PublicAPI) FillTransaction(args evmtypes.TransactionArgs) (*rpctypes.SignTransactionResult, error) {
// Set some sanity defaults and terminate on failure
args, err := e.backend.SetTxDefaults(args)
if err != nil {
return nil, err
}
// Assemble the transaction and obtain rlp
tx := args.ToTransaction().AsTransaction()
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
return &rpctypes.SignTransactionResult{
Raw: data,
Tx: tx,
}, nil
}
// Resend accepts an existing transaction and a new gas price and limit. It will remove
// the given transaction from the pool and reinsert it with the new gas price and limit.
func (e *PublicAPI) Resend(_ context.Context,
args evmtypes.TransactionArgs,
gasPrice *hexutil.Big,
gasLimit *hexutil.Uint64,
) (common.Hash, error) {
e.logger.Debug("eth_resend", "args", args.String())
return e.backend.Resend(args, gasPrice, gasLimit)
}
// GetPendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
func (e *PublicAPI) GetPendingTransactions() ([]*rpctypes.RPCTransaction, error) {
e.logger.Debug("eth_getPendingTransactions")
txs, err := e.backend.PendingTransactions()
if err != nil {
return nil, err
}
result := make([]*rpctypes.RPCTransaction, 0, len(txs))
for _, tx := range txs {
for _, msg := range (*tx).GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
// not valid ethereum tx
break
}
rpctx, err := rpctypes.NewTransactionFromMsg(
ethMsg,
common.Hash{},
uint64(0),
uint64(0),
nil,
)
if err != nil {
return nil, err
}
result = append(result, rpctx)
}
}
return result, nil
}
@@ -6,7 +6,7 @@ import (
"sync"
"time"
"github.com/cerc-io/laconicd/rpc/ethereum/types"
"github.com/cerc-io/laconicd/rpc/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/tendermint/tendermint/libs/log"
@@ -23,14 +23,27 @@ import (
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
)
// FilterAPI gathers
type FilterAPI interface {
NewPendingTransactionFilter() rpc.ID
NewBlockFilter() rpc.ID
NewFilter(criteria filters.FilterCriteria) (rpc.ID, error)
GetFilterChanges(id rpc.ID) (interface{}, error)
GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ethtypes.Log, error)
UninstallFilter(id rpc.ID) bool
GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error)
}
// Backend defines the methods requided by the PublicFilterAPI backend
type Backend interface {
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
TendermintBlockByHash(hash common.Hash) (*coretypes.ResultBlock, error)
TendermintBlockResultByNumber(height *int64) (*coretypes.ResultBlockResults, error)
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error)
BlockBloom(height *int64) (ethtypes.Bloom, error)
GetLogsByHeight(*int64) ([][]*ethtypes.Log, error)
BlockBloom(blockRes *coretypes.ResultBlockResults) (ethtypes.Bloom, error)
BloomStatus() (uint64, uint64)
@@ -123,7 +136,12 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error()))
}
api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
api.filters[pendingTxSub.ID()] = &filter{
typ: filters.PendingTransactionsSubscription,
deadline: time.NewTimer(deadline),
hashes: make([]common.Hash, 0),
s: pendingTxSub,
}
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
defer cancelSubs()
@@ -155,7 +173,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
for _, msg := range tx.GetMsgs() {
ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
if ok {
f.hashes = append(f.hashes, common.HexToHash(ethTx.Hash))
f.hashes = append(f.hashes, ethTx.AsTransaction().Hash())
}
}
}
@@ -219,7 +237,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su
for _, msg := range tx.GetMsgs() {
ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
if ok {
_ = notifier.Notify(rpcSub.ID, common.HexToHash(ethTx.Hash))
_ = notifier.Notify(rpcSub.ID, ethTx.AsTransaction().Hash())
}
}
case <-rpcSub.Err():
@@ -385,8 +403,9 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
continue
}
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data, api.clientCtx.Codec)
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
api.logger.Error("fail to decode tx response", "error", err)
return
}
@@ -441,7 +460,13 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
filterID = logsSub.ID()
api.filters[filterID] = &filter{typ: filters.LogsSubscription, crit: criteria, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: logsSub}
api.filters[filterID] = &filter{
typ: filters.LogsSubscription,
crit: criteria,
deadline: time.NewTimer(deadline),
hashes: []common.Hash{},
s: logsSub,
}
go func(eventCh <-chan coretypes.ResultEvent) {
defer cancelSubs()
@@ -461,8 +486,9 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
continue
}
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data, api.clientCtx.Codec)
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
api.logger.Error("fail to decode tx response", "error", err)
return
}
@@ -27,8 +27,12 @@ import (
)
var (
txEvents = tmtypes.QueryForEvent(tmtypes.EventTx).String()
evmEvents = tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s.%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName)).String()
txEvents = tmtypes.QueryForEvent(tmtypes.EventTx).String()
evmEvents = tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s.%s='%s'",
tmtypes.EventTypeKey,
tmtypes.EventTx,
sdk.EventTypeMessage,
sdk.AttributeKeyModule, evmtypes.ModuleName)).String()
headerEvents = tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String()
)
@@ -3,12 +3,15 @@ package filters
import (
"context"
"encoding/binary"
"fmt"
"math/big"
"github.com/cerc-io/laconicd/rpc/ethereum/types"
"github.com/cerc-io/laconicd/rpc/backend"
"github.com/cerc-io/laconicd/rpc/types"
"github.com/pkg/errors"
"github.com/tendermint/tendermint/libs/log"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
@@ -45,7 +48,7 @@ func NewRangeFilter(logger log.Logger, backend Backend, begin, end int64, addres
// Flatten the address and topic filter clauses into a single bloombits filter
// system. Since the bloombits are not positional, nil topics are permitted,
// which get flattened into a nil byte slice.
var filtersBz [][][]byte // nolint: prealloc
var filtersBz [][][]byte //nolint: prealloc
if len(addresses) > 0 {
filter := make([][]byte, len(addresses))
for i, address := range addresses {
@@ -89,28 +92,35 @@ const (
// Logs searches the blockchain for matching log entries, returning all from the
// first block that contains matches, updating the start of the filter accordingly.
func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*ethtypes.Log, error) {
func (f *Filter) Logs(ctx context.Context, logLimit int, blockLimit int64) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
var err error
// If we're doing singleton block filtering, execute and return
if f.criteria.BlockHash != nil && *f.criteria.BlockHash != (common.Hash{}) {
header, err := f.backend.HeaderByHash(*f.criteria.BlockHash)
resBlock, err := f.backend.TendermintBlockByHash(*f.criteria.BlockHash)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch header by hash")
return nil, fmt.Errorf("failed to fetch header by hash %s: %w", f.criteria.BlockHash, err)
}
if header == nil {
return nil, errors.Errorf("unknown block header %s", f.criteria.BlockHash.String())
blockRes, err := f.backend.TendermintBlockResultByNumber(&resBlock.Block.Height)
if err != nil {
f.logger.Debug("failed to fetch block result from Tendermint", "height", resBlock.Block.Height, "error", err.Error())
return nil, nil
}
return f.blockLogs(header.Number.Int64(), header.Bloom)
bloom, err := f.backend.BlockBloom(blockRes)
if err != nil {
return nil, err
}
return f.blockLogs(blockRes, bloom)
}
// Figure out the limits of the filter range
header, err := f.backend.HeaderByNumber(types.EthLatestBlockNumber)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch header by number (latest)")
return nil, fmt.Errorf("failed to fetch header by number (latest): %w", err)
}
if header == nil || header.Number == nil {
@@ -131,7 +141,7 @@ func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*eth
}
if f.criteria.ToBlock.Int64()-f.criteria.FromBlock.Int64() > blockLimit {
return nil, errors.Errorf("maximum [from, to] blocks distance: %d", blockLimit)
return nil, fmt.Errorf("maximum [from, to] blocks distance: %d", blockLimit)
}
// check bounds
@@ -145,19 +155,25 @@ func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*eth
to := f.criteria.ToBlock.Int64()
for height := from; height <= to; height++ {
bloom, err := f.backend.BlockBloom(&height)
blockRes, err := f.backend.TendermintBlockResultByNumber(&height)
if err != nil {
f.logger.Debug("failed to fetch block result from Tendermint", "height", height, "error", err.Error())
return nil, nil
}
bloom, err := f.backend.BlockBloom(blockRes)
if err != nil {
return nil, err
}
filtered, err := f.blockLogs(height, bloom)
filtered, err := f.blockLogs(blockRes, bloom)
if err != nil {
return nil, errors.Wrapf(err, "failed to fetch block by number %d", height)
}
// check logs limit
if len(logs)+len(filtered) > logLimit {
return nil, errors.Errorf("query returned more than %d results", logLimit)
return nil, fmt.Errorf("query returned more than %d results", logLimit)
}
logs = append(logs, filtered...)
}
@@ -165,16 +181,14 @@ func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*eth
}
// blockLogs returns the logs matching the filter criteria within a single block.
func (f *Filter) blockLogs(height int64, bloom ethtypes.Bloom) ([]*ethtypes.Log, error) {
func (f *Filter) blockLogs(blockRes *tmrpctypes.ResultBlockResults, bloom ethtypes.Bloom) ([]*ethtypes.Log, error) {
if !bloomFilter(bloom, f.criteria.Addresses, f.criteria.Topics) {
return []*ethtypes.Log{}, nil
}
// DANGER: do not call GetLogs(header.Hash())
// eth header's hash doesn't match tm block hash
logsList, err := f.backend.GetLogsByNumber(types.BlockNumber(height))
logsList, err := backend.GetLogsFromBlockResults(blockRes)
if err != nil {
return []*ethtypes.Log{}, errors.Wrapf(err, "failed to fetch logs block number %d", height)
return []*ethtypes.Log{}, errors.Wrapf(err, "failed to fetch logs block number %d", blockRes.Height)
}
unfiltered := make([]*ethtypes.Log, 0)
@@ -31,7 +31,7 @@ func (s Subscription) ID() rpc.ID {
}
// Unsubscribe from the current subscription to Tendermint Websocket. It sends an error to the
// subscription error channel if unsubscription fails.
// subscription error channel if unsubscribe fails.
func (s *Subscription) Unsubscribe(es *EventSystem) {
go func() {
uninstallLoop:
+43
View File
@@ -0,0 +1,43 @@
package miner
import (
"github.com/cosmos/cosmos-sdk/server"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/tendermint/tendermint/libs/log"
"github.com/cerc-io/laconicd/rpc/backend"
)
// API is the private miner prefixed set of APIs in the Miner JSON-RPC spec.
type API struct {
ctx *server.Context
logger log.Logger
backend backend.EVMBackend
}
// NewPrivateAPI creates an instance of the Miner API.
func NewPrivateAPI(
ctx *server.Context,
backend backend.EVMBackend,
) *API {
return &API{
ctx: ctx,
logger: ctx.Logger.With("api", "miner"),
backend: backend,
}
}
// SetEtherbase sets the etherbase of the miner
func (api *API) SetEtherbase(etherbase common.Address) bool {
api.logger.Debug("miner_setEtherbase")
return api.backend.SetEtherbase(etherbase)
}
// SetGasPrice sets the minimum accepted gas price for the miner.
func (api *API) SetGasPrice(gasPrice hexutil.Big) bool {
api.logger.Info(api.ctx.Viper.ConfigFileUsed())
return api.backend.SetGasPrice(gasPrice)
}
@@ -6,16 +6,13 @@ import (
"os"
"time"
"github.com/cerc-io/laconicd/rpc/ethereum/backend"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cerc-io/laconicd/rpc/backend"
"github.com/cerc-io/laconicd/crypto/hd"
ethermint "github.com/cerc-io/laconicd/types"
"github.com/tendermint/tendermint/libs/log"
sdkcrypto "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -24,20 +21,21 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/cerc-io/laconicd/crypto/ethsecp256k1"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
)
// PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PrivateAccountAPI struct {
clientCtx client.Context
backend backend.Backend
backend backend.EVMBackend
logger log.Logger
hdPathIter ethermint.HDPathIterator
}
// NewAPI creates an instance of the public Personal Eth API.
func NewAPI(logger log.Logger, clientCtx client.Context, backend backend.Backend) *PrivateAccountAPI {
func NewAPI(
logger log.Logger,
backend backend.EVMBackend,
) *PrivateAccountAPI {
cfg := sdk.GetConfig()
basePath := cfg.GetFullBIP44Path()
@@ -47,7 +45,6 @@ func NewAPI(logger log.Logger, clientCtx client.Context, backend backend.Backend
}
return &PrivateAccountAPI{
clientCtx: clientCtx,
logger: logger.With("api", "personal"),
hdPathIter: iterator,
backend: backend,
@@ -61,55 +58,13 @@ func NewAPI(logger log.Logger, clientCtx client.Context, backend backend.Backend
// NOTE: The key will be both armored and encrypted using the same passphrase.
func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Address, error) {
api.logger.Debug("personal_importRawKey")
priv, err := crypto.HexToECDSA(privkey)
if err != nil {
return common.Address{}, err
}
privKey := &ethsecp256k1.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 := api.clientCtx.Keyring.KeyByAddress(addr); err == nil {
return ethereumAddr, nil
}
// ignore error as we only care about the length of the list
list, _ := api.clientCtx.Keyring.List()
privKeyName := fmt.Sprintf("personal_%d", len(list))
armor := sdkcrypto.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
if err := api.clientCtx.Keyring.ImportPrivKey(privKeyName, armor, password); err != nil {
return common.Address{}, err
}
api.logger.Info("key successfully imported", "name", privKeyName, "address", ethereumAddr.String())
return ethereumAddr, nil
return api.backend.ImportRawKey(privkey, password)
}
// ListAccounts will return a list of addresses for accounts this node manages.
func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
api.logger.Debug("personal_listAccounts")
addrs := []common.Address{}
list, err := api.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
return api.backend.ListAccounts()
}
// LockAccount will lock the account associated with the given address when it's unlocked.
@@ -130,13 +85,16 @@ func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error
// create the mnemonic and save the account
hdPath := api.hdPathIter()
info, _, err := api.clientCtx.Keyring.NewMnemonic(name, keyring.English, hdPath.String(), password, hd.EthSecp256k1)
info, err := api.backend.NewMnemonic(name, keyring.English, hdPath.String(), password, hd.EthSecp256k1)
if err != nil {
return common.Address{}, err
}
pubKeyAddr, err := info.GetAddress()
addr := common.BytesToAddress(pubKeyAddr.Bytes())
pubKey, err := info.GetPubKey()
if err != nil {
return common.Address{}, err
}
addr := common.BytesToAddress(pubKey.Address().Bytes())
api.logger.Info("Your new key was generated", "address", addr.String())
api.logger.Info("Please backup your key file!", "path", os.Getenv("HOME")+"/.ethermint/"+name) // TODO: pass the correct binary
api.logger.Info("Please remember your password!")
@@ -157,19 +115,6 @@ func (api *PrivateAccountAPI) UnlockAccount(_ context.Context, addr common.Addre
// able to decrypt the key it fails.
func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args evmtypes.TransactionArgs, _ string) (common.Hash, error) {
api.logger.Debug("personal_sendTransaction", "address", args.To.String())
if args.From == nil {
return common.Hash{}, fmt.Errorf("from address cannot be nil in send transaction")
}
addr := sdk.AccAddress(args.From.Bytes())
// check if the key is on the keyring
_, err := api.clientCtx.Keyring.KeyByAddress(addr)
if err != nil {
return common.Hash{}, err
}
return api.backend.SendTransaction(args)
}
@@ -184,17 +129,7 @@ func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args evmtypes.T
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
api.logger.Debug("personal_sign", "data", data, "address", addr.String())
cosmosAddr := sdk.AccAddress(addr.Bytes())
sig, _, err := api.clientCtx.Keyring.SignByAddress(cosmosAddr, accounts.TextHash(data))
if err != nil {
api.logger.Error("failed to sign with key", "data", data, "address", addr.String(), "error", err.Error())
return nil, err
}
sig[crypto.RecoveryIDOffset] += 27 // transform V from 0/1 to 27/28
return sig, nil
return api.backend.Sign(addr, data)
}
// EcRecover returns the address for the account that was used to create the signature.
@@ -5,7 +5,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/cerc-io/laconicd/rpc/ethereum/types"
"github.com/cerc-io/laconicd/rpc/types"
)
// PublicAPI offers and API for the transaction pool. It only operates on data that is non-confidential.
@@ -30,9 +30,10 @@ const (
)
const (
BlockParamEarliest = "earliest"
BlockParamLatest = "latest"
BlockParamPending = "pending"
BlockParamEarliest = "earliest"
BlockParamLatest = "latest"
BlockParamFinalized = "finalized"
BlockParamPending = "pending"
)
// NewBlockNumber creates a new BlockNumber instance.
@@ -57,7 +58,7 @@ func ContextWithHeight(height int64) context.Context {
}
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
// - "latest", "earliest" or "pending" as string arguments
// - "latest", "finalized", "earliest" or "pending" as string arguments
// - the block number
// Returned errors:
// - an invalid block number error when the given argument isn't a known strings
@@ -72,7 +73,7 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
case BlockParamEarliest:
*bn = EthEarliestBlockNumber
return nil
case BlockParamLatest:
case BlockParamLatest, BlockParamFinalized:
*bn = EthLatestBlockNumber
return nil
case BlockParamPending:
+259
View File
@@ -0,0 +1,259 @@
package types
import (
"fmt"
"strconv"
ethermint "github.com/cerc-io/laconicd/types"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
abci "github.com/tendermint/tendermint/abci/types"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
)
// EventFormat is the format version of the events.
//
// To fix the issue of tx exceeds block gas limit, we changed the event format in a breaking way.
// But to avoid forcing clients to re-sync from scatch, we make json-rpc logic to be compatible with both formats.
type EventFormat int
const (
eventFormatUnknown EventFormat = iota
// Event Format 1 (the format used before PR #1062):
// ```
// ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ...
// ```
eventFormat1
// Event Format 2 (the format used after PR #1062):
// ```
// ethereum_tx(ethereumTxHash, txIndex)
// ethereum_tx(ethereumTxHash, txIndex)
// ...
// ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed)
// tx_log(txLog, txLog, ...)
// ...
// ```
// If the transaction exceeds block gas limit, it only emits the first part.
eventFormat2
)
// ParsedTx is the tx infos parsed from events.
type ParsedTx struct {
MsgIndex int
// the following fields are parsed from events
Hash common.Hash
// -1 means uninitialized
EthTxIndex int32
GasUsed uint64
Failed bool
}
// NewParsedTx initialize a ParsedTx
func NewParsedTx(msgIndex int) ParsedTx {
return ParsedTx{MsgIndex: msgIndex, EthTxIndex: -1}
}
// ParsedTxs is the tx infos parsed from eth tx events.
type ParsedTxs struct {
// one item per message
Txs []ParsedTx
// map tx hash to msg index
TxHashes map[common.Hash]int
}
// ParseTxResult parse eth tx infos from cosmos-sdk events.
// It supports two event formats, the formats are described in the comments of the format constants.
func ParseTxResult(result *abci.ResponseDeliverTx, tx sdk.Tx) (*ParsedTxs, error) {
format := eventFormatUnknown
// the index of current ethereum_tx event in format 1 or the second part of format 2
eventIndex := -1
p := &ParsedTxs{
TxHashes: make(map[common.Hash]int),
}
for _, event := range result.Events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
if format == eventFormatUnknown {
// discover the format version by inspect the first ethereum_tx event.
if len(event.Attributes) > 2 {
format = eventFormat1
} else {
format = eventFormat2
}
}
if len(event.Attributes) == 2 {
// the first part of format 2
if err := p.newTx(event.Attributes); err != nil {
return nil, err
}
} else {
// format 1 or second part of format 2
eventIndex++
if format == eventFormat1 {
// append tx
if err := p.newTx(event.Attributes); err != nil {
return nil, err
}
} else {
// the second part of format 2, update tx fields
if err := p.updateTx(eventIndex, event.Attributes); err != nil {
return nil, err
}
}
}
}
// some old versions miss some events, fill it with tx result
if len(p.Txs) == 1 {
p.Txs[0].GasUsed = uint64(result.GasUsed)
}
// this could only happen if tx exceeds block gas limit
if result.Code != 0 && tx != nil {
for i := 0; i < len(p.Txs); i++ {
p.Txs[i].Failed = true
// replace gasUsed with gasLimit because that's what's actually deducted.
gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas()
p.Txs[i].GasUsed = gasLimit
}
}
return p, nil
}
// ParseTxIndexerResult parse tm tx result to a format compatible with the custom tx indexer.
func ParseTxIndexerResult(txResult *tmrpctypes.ResultTx, tx sdk.Tx, getter func(*ParsedTxs) *ParsedTx) (*ethermint.TxResult, error) {
txs, err := ParseTxResult(&txResult.TxResult, tx)
if err != nil {
return nil, fmt.Errorf("failed to parse tx events: block %d, index %d, %v", txResult.Height, txResult.Index, err)
}
parsedTx := getter(txs)
if parsedTx == nil {
return nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", txResult.Height, txResult.Index)
}
return &ethermint.TxResult{
Height: txResult.Height,
TxIndex: txResult.Index,
MsgIndex: uint32(parsedTx.MsgIndex),
EthTxIndex: parsedTx.EthTxIndex,
Failed: parsedTx.Failed,
GasUsed: parsedTx.GasUsed,
CumulativeGasUsed: txs.AccumulativeGasUsed(parsedTx.MsgIndex),
}, nil
}
// newTx parse a new tx from events, called during parsing.
func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error {
msgIndex := len(p.Txs)
tx := NewParsedTx(msgIndex)
if err := fillTxAttributes(&tx, attrs); err != nil {
return err
}
p.Txs = append(p.Txs, tx)
p.TxHashes[tx.Hash] = msgIndex
return nil
}
// updateTx updates an exiting tx from events, called during parsing.
// In event format 2, we update the tx with the attributes of the second `ethereum_tx` event,
// Due to bug https://github.com/cerc-io/laconicd/issues/1175, the first `ethereum_tx` event may emit incorrect tx hash,
// so we prefer the second event and override the first one.
func (p *ParsedTxs) updateTx(eventIndex int, attrs []abci.EventAttribute) error {
tx := NewParsedTx(eventIndex)
if err := fillTxAttributes(&tx, attrs); err != nil {
return err
}
if tx.Hash != p.Txs[eventIndex].Hash {
// if hash is different, index the new one too
p.TxHashes[tx.Hash] = eventIndex
}
// override the tx because the second event is more trustworthy
p.Txs[eventIndex] = tx
return nil
}
// GetTxByHash find ParsedTx by tx hash, returns nil if not exists.
func (p *ParsedTxs) GetTxByHash(hash common.Hash) *ParsedTx {
if idx, ok := p.TxHashes[hash]; ok {
return &p.Txs[idx]
}
return nil
}
// GetTxByMsgIndex returns ParsedTx by msg index
func (p *ParsedTxs) GetTxByMsgIndex(i int) *ParsedTx {
if i < 0 || i >= len(p.Txs) {
return nil
}
return &p.Txs[i]
}
// GetTxByTxIndex returns ParsedTx by tx index
func (p *ParsedTxs) GetTxByTxIndex(txIndex int) *ParsedTx {
if len(p.Txs) == 0 {
return nil
}
// assuming the `EthTxIndex` increase continuously,
// convert TxIndex to MsgIndex by subtract the begin TxIndex.
msgIndex := txIndex - int(p.Txs[0].EthTxIndex)
// GetTxByMsgIndex will check the bound
return p.GetTxByMsgIndex(msgIndex)
}
// AccumulativeGasUsed calculates the accumulated gas used within the batch of txs
func (p *ParsedTxs) AccumulativeGasUsed(msgIndex int) (result uint64) {
for i := 0; i <= msgIndex; i++ {
result += p.Txs[i].GasUsed
}
return result
}
// fillTxAttribute parse attributes by name, less efficient than hardcode the index, but more stable against event
// format changes.
func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error {
switch string(key) {
case evmtypes.AttributeKeyEthereumTxHash:
tx.Hash = common.HexToHash(string(value))
case evmtypes.AttributeKeyTxIndex:
txIndex, err := strconv.ParseUint(string(value), 10, 31)
if err != nil {
return err
}
tx.EthTxIndex = int32(txIndex)
case evmtypes.AttributeKeyTxGasUsed:
gasUsed, err := strconv.ParseUint(string(value), 10, 64)
if err != nil {
return err
}
tx.GasUsed = gasUsed
case evmtypes.AttributeKeyEthereumTxFailed:
tx.Failed = len(value) > 0
}
return nil
}
func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error {
for _, attr := range attrs {
if err := fillTxAttribute(tx, attr.Key, attr.Value); err != nil {
return err
}
}
return nil
}
+236
View File
@@ -0,0 +1,236 @@
package types
import (
"math/big"
"testing"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
)
func TestParseTxResult(t *testing.T) {
address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2"
txHash := common.BigToHash(big.NewInt(1))
txHash2 := common.BigToHash(big.NewInt(2))
testCases := []struct {
name string
response abci.ResponseDeliverTx
expTxs []*ParsedTx // expected parse result, nil means expect error.
}{
{
"format 1 events",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: "coin_received", Attributes: []abci.EventAttribute{
{Key: []byte("receiver"), Value: []byte("ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa")},
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
}},
{Type: "coin_spent", Attributes: []abci.EventAttribute{
{Key: []byte("spender"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: "message", Attributes: []abci.EventAttribute{
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
{Key: []byte("module"), Value: []byte("evm")},
{Key: []byte("sender"), Value: []byte(address)},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("11")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
{Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}},
},
},
[]*ParsedTx{
{
MsgIndex: 0,
Hash: txHash,
EthTxIndex: 10,
GasUsed: 21000,
Failed: false,
},
{
MsgIndex: 1,
Hash: txHash2,
EthTxIndex: 11,
GasUsed: 21000,
Failed: true,
},
},
},
{
"format 2 events",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: "coin_received", Attributes: []abci.EventAttribute{
{Key: []byte("receiver"), Value: []byte("ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa")},
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
}},
{Type: "coin_spent", Attributes: []abci.EventAttribute{
{Key: []byte("spender"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("0")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("0")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: "message", Attributes: []abci.EventAttribute{
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
{Key: []byte("module"), Value: []byte("evm")},
{Key: []byte("sender"), Value: []byte(address)},
}},
},
},
[]*ParsedTx{
{
MsgIndex: 0,
Hash: txHash,
EthTxIndex: 0,
GasUsed: 21000,
Failed: false,
},
},
},
{
"format 1 events, failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("0x01")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
{Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}},
},
},
nil,
},
{
"format 1 events, failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("0x01")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
{Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")},
}},
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}},
},
},
nil,
},
{
"format 2 events failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("0x01")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("21000")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
},
},
nil,
},
{
"format 2 events failed",
abci.ResponseDeliverTx{
GasUsed: 21000,
Events: []abci.Event{
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
{Key: []byte("txIndex"), Value: []byte("10")},
}},
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
{Key: []byte("amount"), Value: []byte("1000")},
{Key: []byte("txGasUsed"), Value: []byte("0x01")},
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
}},
},
},
nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parsed, err := ParseTxResult(&tc.response, nil)
if tc.expTxs == nil {
require.Error(t, err)
} else {
require.NoError(t, err)
for msgIndex, expTx := range tc.expTxs {
require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex))
require.Equal(t, expTx, parsed.GetTxByHash(expTx.Hash))
require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex)))
}
// non-exists tx hash
require.Nil(t, parsed.GetTxByHash(common.Hash{}))
// out of range
require.Nil(t, parsed.GetTxByMsgIndex(len(tc.expTxs)))
require.Nil(t, parsed.GetTxByTxIndex(99999999))
}
})
}
}
@@ -15,9 +15,9 @@ import (
)
// QueryClient defines a gRPC Client used for:
// - Transaction simulation
// - EVM module queries
// - Fee market module queries
// - Transaction simulation
// - EVM module queries
// - Fee market module queries
type QueryClient struct {
tx.ServiceClient
evmtypes.QueryClient
@@ -3,10 +3,9 @@ package types
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"math/big"
"strconv"
"strings"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
@@ -21,8 +20,13 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
)
// ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit.
// The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API.
const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:"
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
@@ -36,6 +40,7 @@ func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEth
if !ok {
return nil, fmt.Errorf("invalid message type %T, expected %T", msg, &evmtypes.MsgEthereumTx{})
}
ethTx.Hash = ethTx.AsTransaction().Hash().Hex()
ethTxs[i] = ethTx
}
return ethTxs, nil
@@ -132,37 +137,6 @@ func FormatBlock(
return result
}
type DataError interface {
Error() string // returns the message
ErrorData() interface{} // returns the error data
}
type dataError struct {
msg string
data string
}
func (d *dataError) Error() string {
return d.msg
}
func (d *dataError) ErrorData() interface{} {
return d.data
}
type SDKTxLogs struct {
Log string `json:"log"`
}
const LogRevertedFlag = "transaction reverted"
func ErrRevertedWith(data []byte) DataError {
return &dataError{
msg: "VM execution error.",
data: fmt.Sprintf("0x%s", hex.EncodeToString(data)),
}
}
// NewTransactionFromMsg returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func NewTransactionFromMsg(
@@ -250,118 +224,38 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int {
return nil
}
// if bytes.Equal(attr.Key, []byte(feemarkettypes.AttributeKeyBaseFee)) {
// result, success := new(big.Int).SetString(string(attr.Value), 10)
// if success {
// return result
// }
// return nil
// }
}
}
return nil
}
// FindTxAttributes returns the msg index of the eth tx in cosmos tx, and the attributes,
// returns -1 and nil if not found.
func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]string) {
msgIndex := -1
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
msgIndex++
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyEthereumTxHash))
if !bytes.Equal(value, []byte(txHash)) {
continue
}
// found, convert attributes to map for later lookup
attrs := make(map[string]string, len(event.Attributes))
for _, attr := range event.Attributes {
attrs[string(attr.Key)] = string(attr.Value)
}
return msgIndex, attrs
// CheckTxFee is an internal function used to check whether the fee of
// the given transaction is _reasonable_(under the cap).
func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
// Short circuit if there is no cap for transaction fee at all.
if cap == 0 {
return nil
}
// not found
return -1, nil
}
// FindTxAttributesByIndex search the msg in tx events by txIndex
// returns the msgIndex, returns -1 if not found.
func FindTxAttributesByIndex(events []abci.Event, txIndex uint64) int {
strIndex := []byte(strconv.FormatUint(txIndex, 10))
txIndexKey := []byte(evmtypes.AttributeKeyTxIndex)
msgIndex := -1
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
msgIndex++
value := FindAttribute(event.Attributes, txIndexKey)
if !bytes.Equal(value, strIndex) {
continue
}
// found, convert attributes to map for later lookup
return msgIndex
}
// not found
return -1
}
// FindAttribute find event attribute with specified key, if not found returns nil.
func FindAttribute(attrs []abci.EventAttribute, key []byte) []byte {
for _, attr := range attrs {
if !bytes.Equal([]byte(attr.Key), key) {
continue
}
return []byte(attr.Value)
totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas)))
// 1 photon in 10^18 aphoton
oneToken := new(big.Float).SetInt(big.NewInt(params.Ether))
// quo = rounded(x/y)
feeEth := new(big.Float).Quo(totalfee, oneToken)
// no need to check error from parsing
feeFloat, _ := feeEth.Float64()
if feeFloat > cap {
return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap)
}
return nil
}
// GetUint64Attribute parses the uint64 value from event attributes
func GetUint64Attribute(attrs map[string]string, key string) (uint64, error) {
value, found := attrs[key]
if !found {
return 0, fmt.Errorf("tx index attribute not found: %s", key)
}
var result int64
result, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
if result < 0 {
return 0, fmt.Errorf("negative tx index: %d", result)
}
return uint64(result), nil
// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit.
func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool {
return strings.Contains(res.Log, ExceedBlockGasLimitError)
}
// AccumulativeGasUsedOfMsg accumulate the gas used by msgs before `msgIndex`.
func AccumulativeGasUsedOfMsg(events []abci.Event, msgIndex int) (gasUsed uint64) {
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
if msgIndex < 0 {
break
}
msgIndex--
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyTxGasUsed))
var result int64
result, err := strconv.ParseInt(string(value), 10, 64)
if err != nil {
continue
}
gasUsed += uint64(result)
}
return
// TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful
// or if it failed with an ExceedBlockGasLimit error
func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool {
return res.Code == 0 || TxExceedBlockGasLimit(res)
}
+72 -24
View File
@@ -5,7 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"math/big"
"net"
"net/http"
@@ -26,9 +26,9 @@ import (
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
tmtypes "github.com/tendermint/tendermint/types"
rpcfilters "github.com/cerc-io/laconicd/rpc/ethereum/namespaces/eth/filters"
"github.com/cerc-io/laconicd/rpc/ethereum/pubsub"
"github.com/cerc-io/laconicd/rpc/ethereum/types"
rpcfilters "github.com/cerc-io/laconicd/rpc/namespaces/ethereum/eth/filters"
"github.com/cerc-io/laconicd/rpc/types"
"github.com/cerc-io/laconicd/server/config"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
)
@@ -74,7 +74,7 @@ type websocketsServer struct {
logger log.Logger
}
func NewWebsocketsServer(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer {
func NewWebsocketsServer(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient, cfg *config.Config) WebsocketsServer {
logger = logger.With("api", "websocket-server")
_, port, _ := net.SplitHostPort(cfg.JSONRPC.Address)
@@ -94,6 +94,7 @@ func (s *websocketsServer) Start() {
go func() {
var err error
/* #nosec G114 -- http functions have no support for timeouts */
if s.certFile == "" || s.keyFile == "" {
err = http.ListenAndServe(s.wsAddr, ws)
} else {
@@ -181,13 +182,20 @@ func (s *websocketsServer) readLoop(wsConn *wsConn) {
_, mb, err := wsConn.ReadMessage()
if err != nil {
_ = wsConn.Close()
s.logger.Error("read message error, breaking read loop", "error", err.Error())
return
}
if isBatch(mb) {
if err := s.tcpGetAndSendResponse(wsConn, mb); err != nil {
s.sendErrResponse(wsConn, err.Error())
}
continue
}
var msg map[string]interface{}
err = json.Unmarshal(mb, &msg)
if err != nil {
s.sendErrResponse(wsConn, "invalid request")
if err = json.Unmarshal(mb, &msg); err != nil {
s.sendErrResponse(wsConn, err.Error())
continue
}
@@ -195,20 +203,26 @@ func (s *websocketsServer) readLoop(wsConn *wsConn) {
method, ok := msg["method"].(string)
if !ok {
// otherwise, call the usual rpc server to respond
err = s.tcpGetAndSendResponse(wsConn, mb)
if err != nil {
if err := s.tcpGetAndSendResponse(wsConn, mb); err != nil {
s.sendErrResponse(wsConn, err.Error())
}
continue
}
connID := msg["id"].(float64)
connID, ok := msg["id"].(float64)
if !ok {
s.sendErrResponse(
wsConn,
fmt.Errorf("invalid type for connection ID: %T", msg["id"]).Error(),
)
continue
}
switch method {
case "eth_subscribe":
params := msg["params"].([]interface{})
if len(params) == 0 {
s.sendErrResponse(wsConn, "invalid parameters")
params, ok := s.getParamsAndCheckValid(msg, wsConn)
if !ok {
continue
}
@@ -230,11 +244,11 @@ func (s *websocketsServer) readLoop(wsConn *wsConn) {
break
}
case "eth_unsubscribe":
params, ok := msg["params"].([]interface{})
params, ok := s.getParamsAndCheckValid(msg, wsConn)
if !ok {
s.sendErrResponse(wsConn, "invalid parameters")
continue
}
id, ok := params[0].(string)
if !ok {
s.sendErrResponse(wsConn, "invalid parameters")
@@ -247,6 +261,7 @@ func (s *websocketsServer) readLoop(wsConn *wsConn) {
delete(subscriptions, subID)
unsubFn()
}
res := &SubscriptionResponseJSON{
Jsonrpc: "2.0",
ID: connID,
@@ -258,14 +273,29 @@ func (s *websocketsServer) readLoop(wsConn *wsConn) {
}
default:
// otherwise, call the usual rpc server to respond
err = s.tcpGetAndSendResponse(wsConn, mb)
if err != nil {
if err := s.tcpGetAndSendResponse(wsConn, mb); err != nil {
s.sendErrResponse(wsConn, err.Error())
}
}
}
}
// tcpGetAndSendResponse sends error response to client if params is invalid
func (s *websocketsServer) getParamsAndCheckValid(msg map[string]interface{}, wsConn *wsConn) ([]interface{}, bool) {
params, ok := msg["params"].([]interface{})
if !ok {
s.sendErrResponse(wsConn, "invalid parameters")
return nil, false
}
if len(params) == 0 {
s.sendErrResponse(wsConn, "empty parameters")
return nil, false
}
return params, true
}
// tcpGetAndSendResponse connects to the rest-server over tcp, posts a JSON-RPC request, and sends the response
// to the client over websockets
func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) error {
@@ -283,7 +313,7 @@ func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) erro
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "could not read body from response")
}
@@ -426,9 +456,9 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID rpc.ID, extra interfac
}
if params["address"] != nil {
address, ok := params["address"].(string)
addresses, sok := params["address"].([]interface{})
if !ok && !sok {
address, isString := params["address"].(string)
addresses, isSlice := params["address"].([]interface{})
if !isString && !isSlice {
err := errors.New("invalid addresses; must be address or array of addresses")
api.logger.Debug("invalid addresses", "type", fmt.Sprintf("%T", params["address"]))
return nil, err
@@ -438,7 +468,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID rpc.ID, extra interfac
crit.Addresses = []common.Address{common.HexToAddress(address)}
}
if sok {
if isSlice {
crit.Addresses = []common.Address{}
for _, addr := range addresses {
address, ok := addr.(string)
@@ -536,7 +566,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID rpc.ID, extra interfac
continue
}
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data, api.clientCtx.Codec)
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
api.logger.Error("failed to decode tx response", "error", err.Error())
return
@@ -590,7 +620,12 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn, subID rpc.ID)
for {
select {
case ev := <-txsCh:
data, _ := ev.Data.(tmtypes.EventDataTx)
data, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
continue
}
ethTxs, err := types.RawTxToEthTx(api.clientCtx, data.Tx)
if err != nil {
// not ethereum tx
@@ -634,3 +669,16 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn, subID rpc.ID)
func (api *pubSubAPI) subscribeSyncing(wsConn *wsConn, subID rpc.ID) (pubsub.UnsubscribeFunc, error) {
return nil, errors.New("syncing subscription is not implemented")
}
// copy from github.com/ethereum/go-ethereum/rpc/json.go
// isBatch returns true when the first non-whitespace characters is '['
func isBatch(raw []byte) bool {
for _, c := range raw {
// skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d {
continue
}
return c == '['
}
return false
}