rpc: refactor backend package (#418)

* backend refractor

* Revert init file changes

* fix linter issues

* Update ethereum/rpc/namespaces/personal/api.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
crypto-facs 2021-08-10 12:04:16 +02:00 committed by GitHub
parent 9227e78c79
commit 640684c648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 328 additions and 296 deletions

View File

@ -37,10 +37,8 @@ const (
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, selectedAPIs []string) []rpc.API {
nonceLock := new(types.AddrLocker) nonceLock := new(types.AddrLocker)
evmBackend := backend.NewEVMBackend(ctx.Logger, clientCtx) evmBackend := backend.NewEVMBackend(ctx.Logger, clientCtx)
ethAPI := eth.NewPublicAPI(ctx.Logger, clientCtx, evmBackend, nonceLock)
var apis []rpc.API var apis []rpc.API
// remove duplicates // remove duplicates
selectedAPIs = unique(selectedAPIs) selectedAPIs = unique(selectedAPIs)
@ -51,7 +49,7 @@ func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpccl
rpc.API{ rpc.API{
Namespace: EthNamespace, Namespace: EthNamespace,
Version: apiVersion, Version: apiVersion,
Service: ethAPI, Service: eth.NewPublicAPI(ctx.Logger, clientCtx, evmBackend, nonceLock),
Public: true, Public: true,
}, },
rpc.API{ rpc.API{
@ -84,7 +82,7 @@ func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpccl
rpc.API{ rpc.API{
Namespace: PersonalNamespace, Namespace: PersonalNamespace,
Version: apiVersion, Version: apiVersion,
Service: personal.NewAPI(ctx.Logger, ethAPI), Service: personal.NewAPI(ctx.Logger, clientCtx, evmBackend),
Public: true, Public: true,
}, },
) )
@ -111,7 +109,7 @@ func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpccl
rpc.API{ rpc.API{
Namespace: MinerNamespace, Namespace: MinerNamespace,
Version: apiVersion, Version: apiVersion,
Service: miner.NewMinerAPI(ctx, ethAPI, evmBackend), Service: miner.NewMinerAPI(ctx, clientCtx, evmBackend),
Public: true, Public: true,
}, },
) )

View File

@ -2,11 +2,17 @@ package backend
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"regexp" "regexp"
"strconv" "strconv"
"github.com/cosmos/cosmos-sdk/client/flags"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/ethereum/go-ethereum/accounts/keystore"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
@ -26,27 +32,22 @@ import (
evmtypes "github.com/tharsis/ethermint/x/evm/types" evmtypes "github.com/tharsis/ethermint/x/evm/types"
) )
// Backend implements the functionality needed to filter changes. // Backend implements the functionality shared within namespaces.
// Implemented by EVMBackend. // Implemented by EVMBackend.
type Backend interface { type Backend interface {
// Used by block filter; also used for polling
BlockNumber() (hexutil.Uint64, error) BlockNumber() (hexutil.Uint64, error)
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
// returns the logs of a given block HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
// Used by pending transaction filter
PendingTransactions() ([]*sdk.Tx, error) PendingTransactions() ([]*sdk.Tx, error)
// Used by log filter
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error)
SendTransaction(args types.SendTxArgs) (common.Hash, error)
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
BloomStatus() (uint64, uint64) BloomStatus() (uint64, uint64)
GetCoinbase() (sdk.AccAddress, error) GetCoinbase() (sdk.AccAddress, error)
EstimateGas(args evmtypes.CallArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error)
} }
var _ Backend = (*EVMBackend)(nil) var _ Backend = (*EVMBackend)(nil)
@ -444,3 +445,139 @@ func (e *EVMBackend) GetCoinbase() (sdk.AccAddress, error) {
address, _ := sdk.AccAddressFromBech32(res.AccountAddress) address, _ := sdk.AccAddressFromBech32(res.AccountAddress)
return address, nil return address, nil
} }
func (e *EVMBackend) SendTransaction(args types.SendTxArgs) (common.Hash, error) {
// Look up the wallet containing the requested signer
_, err := e.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.From.Bytes()))
if err != nil {
e.logger.Error("failed to find key in keyring", "address", args.From, "error", err.Error())
return common.Hash{}, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
}
args, err = e.setTxDefaults(args)
if err != nil {
return common.Hash{}, err
}
msg := args.ToTransaction()
if err := msg.ValidateBasic(); err != nil {
e.logger.Debug("tx failed basic validation", "error", err.Error())
return common.Hash{}, err
}
// TODO: get from chain config
signer := ethtypes.LatestSignerForChainID(args.ChainID.ToInt())
// Sign transaction
if err := msg.Sign(signer, e.clientCtx.Keyring); err != nil {
e.logger.Debug("failed to sign tx", "error", err.Error())
return common.Hash{}, err
}
// Assemble transaction from fields
builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok {
e.logger.Error("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error())
}
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
if err != nil {
e.logger.Error("codectypes.NewAnyWithValue failed to pack an obvious value", "error", err.Error())
return common.Hash{}, err
}
builder.SetExtensionOptions(option)
err = builder.SetMsgs(msg)
if err != nil {
e.logger.Error("builder.SetMsgs failed", "error", err.Error())
}
// Query params to use the EVM denomination
res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
if err != nil {
e.logger.Error("failed to query evm params", "error", err.Error())
return common.Hash{}, err
}
txData, err := evmtypes.UnpackTxData(msg.Data)
if err != nil {
e.logger.Error("failed to unpack tx data", "error", err.Error())
return common.Hash{}, err
}
fees := sdk.Coins{sdk.NewCoin(res.Params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))}
builder.SetFeeAmount(fees)
builder.SetGasLimit(msg.GetGas())
// Encode transaction by default Tx encoder
txEncoder := e.clientCtx.TxConfig.TxEncoder()
txBytes, err := txEncoder(builder.GetTx())
if err != nil {
e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
return common.Hash{}, err
}
txHash := msg.AsTransaction().Hash()
// Broadcast transaction in sync mode (default)
// NOTE: If error is encountered on the node, the broadcast will not return an error
syncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastSync)
rsp, err := syncCtx.BroadcastTx(txBytes)
if err != nil || rsp.Code != 0 {
if err == nil {
err = errors.New(rsp.RawLog)
}
e.logger.Error("failed to broadcast tx", "error", err.Error())
return txHash, err
}
// Return transaction hash
return txHash, nil
}
// EstimateGas returns an estimate of gas usage for the given smart contract call.
func (e *EVMBackend) EstimateGas(args evmtypes.CallArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error) {
blockNr := types.EthPendingBlockNumber
if blockNrOptional != nil {
blockNr = *blockNrOptional
}
bz, err := json.Marshal(&args)
if err != nil {
return 0, err
}
req := evmtypes.EthCallRequest{Args: bz, GasCap: ethermint.DefaultRPCGasLimit}
// 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 := e.queryClient.EstimateGas(types.ContextWithHeight(blockNr.Int64()), &req)
if err != nil {
return 0, err
}
return hexutil.Uint64(res.Gas), nil
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
func (e *EVMBackend) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) {
// Get nonce (sequence) from account
from := sdk.AccAddress(address.Bytes())
accRet := e.clientCtx.AccountRetriever
err := accRet.EnsureExists(e.clientCtx, from)
if err != nil {
// account doesn't exist yet, return 0
n := hexutil.Uint64(0)
return &n, nil
}
includePending := blockNum == types.EthPendingBlockNumber
nonce, err := e.getAccountNonce(address, includePending, blockNum.Int64(), e.logger)
if err != nil {
return nil, err
}
n := hexutil.Uint64(nonce)
return &n, nil
}

View File

@ -0,0 +1,136 @@
package backend
import (
"bytes"
"errors"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/ethereum/go-ethereum/common"
"github.com/tendermint/tendermint/libs/log"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/tharsis/ethermint/ethereum/rpc/types"
ethermint "github.com/tharsis/ethermint/types"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
)
// setTxDefaults populates tx message with default values in case they are not
// provided on the args
func (e *EVMBackend) setTxDefaults(args types.SendTxArgs) (types.SendTxArgs, error) {
if args.GasPrice == nil {
// TODO: Change to either:
// - min gas price from context once available through server/daemon, or
// - suggest a gas price based on the previous included txs
args.GasPrice = (*hexutil.Big)(big.NewInt(ethermint.DefaultGasPrice))
}
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.CallArgs{
From: &args.From, // From shouldn't be nil
To: args.To,
Gas: args.Gas,
GasPrice: args.GasPrice,
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 {
msg, err := evmtypes.UnwrapEthereumMsg(tx)
if err != nil {
// not ethereum tx
continue
}
sender, err := msg.GetSender(e.chainID)
if err != nil {
continue
}
if sender == accAddr {
nonce++
}
}
return nonce, nil
}

View File

@ -1,7 +1,6 @@
package eth package eth
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -240,26 +239,7 @@ func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNum rp
// GetTransactionCount returns the number of transactions at the given address up to the given block number. // GetTransactionCount returns the number of transactions at the given address up to the given block number.
func (e *PublicAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) { func (e *PublicAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) {
e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number", blockNum) e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number", blockNum)
return e.backend.GetTransactionCount(address, blockNum)
// Get nonce (sequence) from account
from := sdk.AccAddress(address.Bytes())
accRet := e.clientCtx.AccountRetriever
err := accRet.EnsureExists(e.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 := e.getAccountNonce(address, includePending, blockNum.Int64(), e.logger)
if err != nil {
return nil, err
}
n := hexutil.Uint64(nonce)
return &n, nil
} }
// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash. // GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
@ -357,94 +337,7 @@ func (e *PublicAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.By
// SendTransaction sends an Ethereum transaction. // SendTransaction sends an Ethereum transaction.
func (e *PublicAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, error) { func (e *PublicAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, error) {
e.logger.Debug("eth_sendTransaction", "args", args.String()) e.logger.Debug("eth_sendTransaction", "args", args.String())
return e.backend.SendTransaction(args)
// Look up the wallet containing the requested signer
_, err := e.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.From.Bytes()))
if err != nil {
e.logger.Error("failed to find key in keyring", "address", args.From, "error", err.Error())
return common.Hash{}, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
}
args, err = e.setTxDefaults(args)
if err != nil {
return common.Hash{}, err
}
msg := args.ToTransaction()
if err := msg.ValidateBasic(); err != nil {
e.logger.Debug("tx failed basic validation", "error", err.Error())
return common.Hash{}, err
}
// TODO: get from chain config
signer := ethtypes.LatestSignerForChainID(args.ChainID.ToInt())
// Sign transaction
if err := msg.Sign(signer, e.clientCtx.Keyring); err != nil {
e.logger.Debug("failed to sign tx", "error", err.Error())
return common.Hash{}, err
}
// Assemble transaction from fields
builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok {
e.logger.Error("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error())
}
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
if err != nil {
e.logger.Error("codectypes.NewAnyWithValue failed to pack an obvious value", "error", err.Error())
return common.Hash{}, err
}
builder.SetExtensionOptions(option)
err = builder.SetMsgs(msg)
if err != nil {
e.logger.Error("builder.SetMsgs failed", "error", err.Error())
}
// Query params to use the EVM denomination
res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
if err != nil {
e.logger.Error("failed to query evm params", "error", err.Error())
return common.Hash{}, err
}
txData, err := evmtypes.UnpackTxData(msg.Data)
if err != nil {
e.logger.Error("failed to unpack tx data", "error", err.Error())
return common.Hash{}, err
}
fees := sdk.Coins{sdk.NewCoin(res.Params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))}
builder.SetFeeAmount(fees)
builder.SetGasLimit(msg.GetGas())
// Encode transaction by default Tx encoder
txEncoder := e.clientCtx.TxConfig.TxEncoder()
txBytes, err := txEncoder(builder.GetTx())
if err != nil {
e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
return common.Hash{}, err
}
txHash := msg.AsTransaction().Hash()
// Broadcast transaction in sync mode (default)
// NOTE: If error is encountered on the node, the broadcast will not return an error
syncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastSync)
rsp, err := syncCtx.BroadcastTx(txBytes)
if err != nil || rsp.Code != 0 {
if err == nil {
err = errors.New(rsp.RawLog)
}
e.logger.Error("failed to broadcast tx", "error", err.Error())
return txHash, err
}
// Return transaction hash
return txHash, nil
} }
// SendRawTransaction send a raw Ethereum transaction. // SendRawTransaction send a raw Ethereum transaction.
@ -568,26 +461,7 @@ func (e *PublicAPI) doCall(
// EstimateGas returns an estimate of gas usage for the given smart contract call. // EstimateGas returns an estimate of gas usage for the given smart contract call.
func (e *PublicAPI) EstimateGas(args evmtypes.CallArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) { func (e *PublicAPI) EstimateGas(args evmtypes.CallArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) {
e.logger.Debug("eth_estimateGas") e.logger.Debug("eth_estimateGas")
return e.backend.EstimateGas(args, blockNrOptional)
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: ethermint.DefaultRPCGasLimit}
// 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 := e.queryClient.EstimateGas(rpctypes.ContextWithHeight(blockNr.Int64()), &req)
if err != nil {
return 0, err
}
return hexutil.Uint64(res.Gas), nil
} }
// GetBlockByHash returns the block identified by hash. // GetBlockByHash returns the block identified by hash.
@ -982,122 +856,3 @@ func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, block
StorageProof: storageProofs, StorageProof: storageProofs,
}, nil }, nil
} }
// setTxDefaults populates tx message with default values in case they are not
// provided on the args
func (e *PublicAPI) setTxDefaults(args rpctypes.SendTxArgs) (rpctypes.SendTxArgs, error) {
if args.GasPrice == nil {
// TODO: Change to either:
// - min gas price from context once available through server/daemon, or
// - suggest a gas price based on the previous included txs
args.GasPrice = (*hexutil.Big)(big.NewInt(ethermint.DefaultGasPrice))
}
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.CallArgs{
From: &args.From, // From shouldn't be nil
To: args.To,
Gas: args.Gas,
GasPrice: args.GasPrice,
Value: args.Value,
Data: input,
AccessList: args.AccessList,
}
blockNr := rpctypes.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.chainIDEpoch)
}
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 *PublicAPI) getAccountNonce(accAddr common.Address, pending bool, height int64, logger log.Logger) (uint64, error) {
queryClient := authtypes.NewQueryClient(e.clientCtx)
res, err := queryClient.Account(rpctypes.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.backend.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 {
msg, err := evmtypes.UnwrapEthereumMsg(tx)
if err != nil {
// not ethereum tx
continue
}
sender, err := msg.GetSender(e.chainIDEpoch)
if err != nil {
continue
}
if sender == accAddr {
nonce++
}
}
return nonce, nil
}

View File

@ -4,6 +4,8 @@ import (
"errors" "errors"
"math/big" "math/big"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server"
@ -20,29 +22,28 @@ import (
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
"github.com/tharsis/ethermint/ethereum/rpc/backend" "github.com/tharsis/ethermint/ethereum/rpc/backend"
"github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth"
rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types" rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types"
) )
// API is the miner prefixed set of APIs in the Miner JSON-RPC spec. // API is the miner prefixed set of APIs in the Miner JSON-RPC spec.
type API struct { type API struct {
ctx *server.Context ctx *server.Context
logger log.Logger logger log.Logger
ethAPI *eth.PublicAPI clientCtx client.Context
backend backend.Backend backend backend.Backend
} }
// NewMinerAPI creates an instance of the Miner API. // NewMinerAPI creates an instance of the Miner API.
func NewMinerAPI( func NewMinerAPI(
ctx *server.Context, ctx *server.Context,
ethAPI *eth.PublicAPI, clientCtx client.Context,
backend backend.Backend, backend backend.Backend,
) *API { ) *API {
return &API{ return &API{
ctx: ctx, ctx: ctx,
ethAPI: ethAPI, clientCtx: clientCtx,
logger: ctx.Logger.With("api", "miner"), logger: ctx.Logger.With("api", "miner"),
backend: backend, backend: backend,
} }
} }
@ -65,7 +66,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool {
} }
// Assemble transaction from fields // Assemble transaction from fields
builder, ok := api.ethAPI.ClientCtx().TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) builder, ok := api.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok { if !ok {
api.logger.Debug("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error()) api.logger.Debug("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error())
return false return false
@ -93,7 +94,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool {
denom := minGasPrices[0].Denom denom := minGasPrices[0].Denom
delCommonAddr := common.BytesToAddress(delAddr.Bytes()) delCommonAddr := common.BytesToAddress(delAddr.Bytes())
nonce, err := api.ethAPI.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber) nonce, err := api.backend.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber)
if err != nil { if err != nil {
api.logger.Debug("failed to get nonce", "error", err.Error()) api.logger.Debug("failed to get nonce", "error", err.Error())
return false return false
@ -101,13 +102,13 @@ func (api *API) SetEtherbase(etherbase common.Address) bool {
txFactory := tx.Factory{} txFactory := tx.Factory{}
txFactory = txFactory. txFactory = txFactory.
WithChainID(api.ethAPI.ClientCtx().ChainID). WithChainID(api.clientCtx.ChainID).
WithKeybase(api.ethAPI.ClientCtx().Keyring). WithKeybase(api.clientCtx.Keyring).
WithTxConfig(api.ethAPI.ClientCtx().TxConfig). WithTxConfig(api.clientCtx.TxConfig).
WithSequence(uint64(*nonce)). WithSequence(uint64(*nonce)).
WithGasAdjustment(1.25) WithGasAdjustment(1.25)
_, gas, err := tx.CalculateGas(api.ethAPI.ClientCtx(), txFactory, msg) _, gas, err := tx.CalculateGas(api.clientCtx, txFactory, msg)
if err != nil { if err != nil {
api.logger.Debug("failed to calculate gas", "error", err.Error()) api.logger.Debug("failed to calculate gas", "error", err.Error())
return false return false
@ -120,7 +121,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool {
builder.SetFeeAmount(fees) builder.SetFeeAmount(fees)
builder.SetGasLimit(gas) builder.SetGasLimit(gas)
keyInfo, err := api.ethAPI.ClientCtx().Keyring.KeyByAddress(delAddr) keyInfo, err := api.clientCtx.Keyring.KeyByAddress(delAddr)
if err != nil { if err != nil {
api.logger.Debug("failed to get the wallet address using the keyring", "error", err.Error()) api.logger.Debug("failed to get the wallet address using the keyring", "error", err.Error())
return false return false
@ -132,7 +133,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool {
} }
// Encode transaction by default Tx encoder // Encode transaction by default Tx encoder
txEncoder := api.ethAPI.ClientCtx().TxConfig.TxEncoder() txEncoder := api.clientCtx.TxConfig.TxEncoder()
txBytes, err := txEncoder(builder.GetTx()) txBytes, err := txEncoder(builder.GetTx())
if err != nil { if err != nil {
api.logger.Debug("failed to encode eth tx using default encoder", "error", err.Error()) api.logger.Debug("failed to encode eth tx using default encoder", "error", err.Error())
@ -143,7 +144,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool {
// Broadcast transaction in sync mode (default) // Broadcast transaction in sync mode (default)
// NOTE: If error is encountered on the node, the broadcast will not return an error // NOTE: If error is encountered on the node, the broadcast will not return an error
syncCtx := api.ethAPI.ClientCtx().WithBroadcastMode(flags.BroadcastSync) syncCtx := api.clientCtx.WithBroadcastMode(flags.BroadcastSync)
rsp, err := syncCtx.BroadcastTx(txBytes) rsp, err := syncCtx.BroadcastTx(txBytes)
if err != nil || rsp.Code != 0 { if err != nil || rsp.Code != 0 {
if err == nil { if err == nil {

View File

@ -6,6 +6,10 @@ import (
"os" "os"
"time" "time"
"github.com/tharsis/ethermint/ethereum/rpc/backend"
"github.com/cosmos/cosmos-sdk/client"
"github.com/tharsis/ethermint/crypto/hd" "github.com/tharsis/ethermint/crypto/hd"
ethermint "github.com/tharsis/ethermint/types" ethermint "github.com/tharsis/ethermint/types"
@ -21,19 +25,19 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/tharsis/ethermint/crypto/ethsecp256k1" "github.com/tharsis/ethermint/crypto/ethsecp256k1"
"github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth"
rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types" rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types"
) )
// PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec. // PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PrivateAccountAPI struct { type PrivateAccountAPI struct {
ethAPI *eth.PublicAPI clientCtx client.Context
backend backend.Backend
logger log.Logger logger log.Logger
hdPathIter ethermint.HDPathIterator hdPathIter ethermint.HDPathIterator
} }
// NewAPI creates an instance of the public Personal Eth API. // NewAPI creates an instance of the public Personal Eth API.
func NewAPI(logger log.Logger, ethAPI *eth.PublicAPI) *PrivateAccountAPI { func NewAPI(logger log.Logger, clientCtx client.Context, backend backend.Backend) *PrivateAccountAPI {
cfg := sdk.GetConfig() cfg := sdk.GetConfig()
basePath := cfg.GetFullFundraiserPath() basePath := cfg.GetFullFundraiserPath()
@ -43,9 +47,10 @@ func NewAPI(logger log.Logger, ethAPI *eth.PublicAPI) *PrivateAccountAPI {
} }
return &PrivateAccountAPI{ return &PrivateAccountAPI{
ethAPI: ethAPI, clientCtx: clientCtx,
logger: logger.With("api", "personal"), logger: logger.With("api", "personal"),
hdPathIter: iterator, hdPathIter: iterator,
backend: backend,
} }
} }
@ -67,17 +72,17 @@ func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Add
ethereumAddr := common.BytesToAddress(addr) ethereumAddr := common.BytesToAddress(addr)
// return if the key has already been imported // return if the key has already been imported
if _, err := api.ethAPI.ClientCtx().Keyring.KeyByAddress(addr); err == nil { if _, err := api.clientCtx.Keyring.KeyByAddress(addr); err == nil {
return ethereumAddr, nil return ethereumAddr, nil
} }
// ignore error as we only care about the length of the list // ignore error as we only care about the length of the list
list, _ := api.ethAPI.ClientCtx().Keyring.List() list, _ := api.clientCtx.Keyring.List()
privKeyName := fmt.Sprintf("personal_%d", len(list)) privKeyName := fmt.Sprintf("personal_%d", len(list))
armor := sdkcrypto.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType) armor := sdkcrypto.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
if err := api.ethAPI.ClientCtx().Keyring.ImportPrivKey(privKeyName, armor, password); err != nil { if err := api.clientCtx.Keyring.ImportPrivKey(privKeyName, armor, password); err != nil {
return common.Address{}, err return common.Address{}, err
} }
@ -91,7 +96,7 @@ func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
api.logger.Debug("personal_listAccounts") api.logger.Debug("personal_listAccounts")
addrs := []common.Address{} addrs := []common.Address{}
list, err := api.ethAPI.ClientCtx().Keyring.List() list, err := api.clientCtx.Keyring.List()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,7 +126,7 @@ func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error
// create the mnemonic and save the account // create the mnemonic and save the account
hdPath := api.hdPathIter() hdPath := api.hdPathIter()
info, _, err := api.ethAPI.ClientCtx().Keyring.NewMnemonic(name, keyring.English, hdPath.String(), password, hd.EthSecp256k1) info, _, err := api.clientCtx.Keyring.NewMnemonic(name, keyring.English, hdPath.String(), password, hd.EthSecp256k1)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
@ -146,8 +151,8 @@ func (api *PrivateAccountAPI) UnlockAccount(_ context.Context, addr common.Addre
// tries to sign it with the key associated with args.To. If the given password isn't // tries to sign it with the key associated with args.To. If the given password isn't
// able to decrypt the key it fails. // able to decrypt the key it fails.
func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args rpctypes.SendTxArgs, pwrd string) (common.Hash, error) { func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args rpctypes.SendTxArgs, pwrd string) (common.Hash, error) {
api.logger.Debug("personal_sendTransaction", "address", args.To.String())
return api.ethAPI.SendTransaction(args) return api.backend.SendTransaction(args)
} }
// Sign calculates an Ethereum ECDSA signature for: // Sign calculates an Ethereum ECDSA signature for:
@ -164,7 +169,7 @@ func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr c
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
sig, _, err := api.ethAPI.ClientCtx().Keyring.SignByAddress(cosmosAddr, accounts.TextHash(data)) sig, _, err := api.clientCtx.Keyring.SignByAddress(cosmosAddr, accounts.TextHash(data))
if err != nil { if err != nil {
api.logger.Error("failed to sign with key", "data", data, "address", addr.String(), "error", err.Error()) api.logger.Error("failed to sign with key", "data", data, "address", addr.String(), "error", err.Error())
return nil, err return nil, err