diff --git a/ethereum/rpc/apis.go b/ethereum/rpc/apis.go index 9c31c891..f688d8a0 100644 --- a/ethereum/rpc/apis.go +++ b/ethereum/rpc/apis.go @@ -37,10 +37,8 @@ const ( func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, selectedAPIs []string) []rpc.API { nonceLock := new(types.AddrLocker) evmBackend := backend.NewEVMBackend(ctx.Logger, clientCtx) - ethAPI := eth.NewPublicAPI(ctx.Logger, clientCtx, evmBackend, nonceLock) var apis []rpc.API - // remove duplicates selectedAPIs = unique(selectedAPIs) @@ -51,7 +49,7 @@ func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpccl rpc.API{ Namespace: EthNamespace, Version: apiVersion, - Service: ethAPI, + Service: eth.NewPublicAPI(ctx.Logger, clientCtx, evmBackend, nonceLock), Public: true, }, rpc.API{ @@ -84,7 +82,7 @@ func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpccl rpc.API{ Namespace: PersonalNamespace, Version: apiVersion, - Service: personal.NewAPI(ctx.Logger, ethAPI), + Service: personal.NewAPI(ctx.Logger, clientCtx, evmBackend), Public: true, }, ) @@ -111,7 +109,7 @@ func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpccl rpc.API{ Namespace: MinerNamespace, Version: apiVersion, - Service: miner.NewMinerAPI(ctx, ethAPI, evmBackend), + Service: miner.NewMinerAPI(ctx, clientCtx, evmBackend), Public: true, }, ) diff --git a/ethereum/rpc/backend/backend.go b/ethereum/rpc/backend/backend.go index 0213b218..ca33aa76 100644 --- a/ethereum/rpc/backend/backend.go +++ b/ethereum/rpc/backend/backend.go @@ -2,11 +2,17 @@ package backend import ( "context" + "encoding/json" "fmt" "math/big" "regexp" "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/metadata" @@ -26,27 +32,22 @@ import ( 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. type Backend interface { - // Used by block filter; also used for polling 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) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) - - // returns the logs of a given block - GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) - - // Used by pending transaction filter + HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) + HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) PendingTransactions() ([]*sdk.Tx, error) - - // Used by log filter 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) - GetCoinbase() (sdk.AccAddress, error) + EstimateGas(args evmtypes.CallArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error) } var _ Backend = (*EVMBackend)(nil) @@ -444,3 +445,139 @@ func (e *EVMBackend) GetCoinbase() (sdk.AccAddress, error) { address, _ := sdk.AccAddressFromBech32(res.AccountAddress) 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 +} diff --git a/ethereum/rpc/backend/utils.go b/ethereum/rpc/backend/utils.go new file mode 100644 index 00000000..c155b3d8 --- /dev/null +++ b/ethereum/rpc/backend/utils.go @@ -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 +} diff --git a/ethereum/rpc/namespaces/eth/api.go b/ethereum/rpc/namespaces/eth/api.go index aa06ff51..bd17d5c5 100644 --- a/ethereum/rpc/namespaces/eth/api.go +++ b/ethereum/rpc/namespaces/eth/api.go @@ -1,7 +1,6 @@ package eth import ( - "bytes" "context" "encoding/json" "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. func (e *PublicAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) { e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number", 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 + return e.backend.GetTransactionCount(address, blockNum) } // 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. func (e *PublicAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, error) { e.logger.Debug("eth_sendTransaction", "args", args.String()) - - // 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 + return e.backend.SendTransaction(args) } // 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. func (e *PublicAPI) EstimateGas(args evmtypes.CallArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) { e.logger.Debug("eth_estimateGas") - - 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 + return e.backend.EstimateGas(args, blockNrOptional) } // GetBlockByHash returns the block identified by hash. @@ -982,122 +856,3 @@ func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, block StorageProof: storageProofs, }, 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 -} diff --git a/ethereum/rpc/namespaces/miner/api.go b/ethereum/rpc/namespaces/miner/api.go index 5041dc25..32e4f5a0 100644 --- a/ethereum/rpc/namespaces/miner/api.go +++ b/ethereum/rpc/namespaces/miner/api.go @@ -4,6 +4,8 @@ import ( "errors" "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" @@ -20,29 +22,28 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/tharsis/ethermint/ethereum/rpc/backend" - "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth" rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types" ) // API is the miner prefixed set of APIs in the Miner JSON-RPC spec. type API struct { - ctx *server.Context - logger log.Logger - ethAPI *eth.PublicAPI - backend backend.Backend + ctx *server.Context + logger log.Logger + clientCtx client.Context + backend backend.Backend } // NewMinerAPI creates an instance of the Miner API. func NewMinerAPI( ctx *server.Context, - ethAPI *eth.PublicAPI, + clientCtx client.Context, backend backend.Backend, ) *API { return &API{ - ctx: ctx, - ethAPI: ethAPI, - logger: ctx.Logger.With("api", "miner"), - backend: backend, + ctx: ctx, + clientCtx: clientCtx, + logger: ctx.Logger.With("api", "miner"), + backend: backend, } } @@ -65,7 +66,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool { } // Assemble transaction from fields - builder, ok := api.ethAPI.ClientCtx().TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) + builder, ok := api.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) if !ok { api.logger.Debug("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error()) return false @@ -93,7 +94,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool { denom := minGasPrices[0].Denom delCommonAddr := common.BytesToAddress(delAddr.Bytes()) - nonce, err := api.ethAPI.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber) + nonce, err := api.backend.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber) if err != nil { api.logger.Debug("failed to get nonce", "error", err.Error()) return false @@ -101,13 +102,13 @@ func (api *API) SetEtherbase(etherbase common.Address) bool { txFactory := tx.Factory{} txFactory = txFactory. - WithChainID(api.ethAPI.ClientCtx().ChainID). - WithKeybase(api.ethAPI.ClientCtx().Keyring). - WithTxConfig(api.ethAPI.ClientCtx().TxConfig). + WithChainID(api.clientCtx.ChainID). + WithKeybase(api.clientCtx.Keyring). + WithTxConfig(api.clientCtx.TxConfig). WithSequence(uint64(*nonce)). WithGasAdjustment(1.25) - _, gas, err := tx.CalculateGas(api.ethAPI.ClientCtx(), txFactory, msg) + _, gas, err := tx.CalculateGas(api.clientCtx, txFactory, msg) if err != nil { api.logger.Debug("failed to calculate gas", "error", err.Error()) return false @@ -120,7 +121,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool { builder.SetFeeAmount(fees) builder.SetGasLimit(gas) - keyInfo, err := api.ethAPI.ClientCtx().Keyring.KeyByAddress(delAddr) + 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 @@ -132,7 +133,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool { } // Encode transaction by default Tx encoder - txEncoder := api.ethAPI.ClientCtx().TxConfig.TxEncoder() + 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()) @@ -143,7 +144,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool { // Broadcast transaction in sync mode (default) // 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) if err != nil || rsp.Code != 0 { if err == nil { diff --git a/ethereum/rpc/namespaces/personal/api.go b/ethereum/rpc/namespaces/personal/api.go index 567033fa..0b955727 100644 --- a/ethereum/rpc/namespaces/personal/api.go +++ b/ethereum/rpc/namespaces/personal/api.go @@ -6,6 +6,10 @@ import ( "os" "time" + "github.com/tharsis/ethermint/ethereum/rpc/backend" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/tharsis/ethermint/crypto/hd" ethermint "github.com/tharsis/ethermint/types" @@ -21,19 +25,19 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/tharsis/ethermint/crypto/ethsecp256k1" - "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth" rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types" ) // PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec. type PrivateAccountAPI struct { - ethAPI *eth.PublicAPI + clientCtx client.Context + backend backend.Backend logger log.Logger hdPathIter ethermint.HDPathIterator } // 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() basePath := cfg.GetFullFundraiserPath() @@ -43,9 +47,10 @@ func NewAPI(logger log.Logger, ethAPI *eth.PublicAPI) *PrivateAccountAPI { } return &PrivateAccountAPI{ - ethAPI: ethAPI, + clientCtx: clientCtx, logger: logger.With("api", "personal"), hdPathIter: iterator, + backend: backend, } } @@ -67,17 +72,17 @@ func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Add ethereumAddr := common.BytesToAddress(addr) // 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 } // 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)) 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 } @@ -91,7 +96,7 @@ func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) { api.logger.Debug("personal_listAccounts") addrs := []common.Address{} - list, err := api.ethAPI.ClientCtx().Keyring.List() + list, err := api.clientCtx.Keyring.List() if err != nil { return nil, err } @@ -121,7 +126,7 @@ func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error // create the mnemonic and save the account 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 { 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 // able to decrypt the key it fails. func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args rpctypes.SendTxArgs, pwrd string) (common.Hash, error) { - - return api.ethAPI.SendTransaction(args) + api.logger.Debug("personal_sendTransaction", "address", args.To.String()) + return api.backend.SendTransaction(args) } // 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()) - sig, _, err := api.ethAPI.ClientCtx().Keyring.SignByAddress(cosmosAddr, accounts.TextHash(data)) + 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