diff --git a/app/rpc/backend/backend.go b/app/rpc/backend/backend.go index e519b9d124..e6c96f271b 100644 --- a/app/rpc/backend/backend.go +++ b/app/rpc/backend/backend.go @@ -22,6 +22,7 @@ import ( type Backend interface { // Used by block filter; also used for polling BlockNumber() (hexutil.Uint64, error) + LatestBlockNumber() (int64, error) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) @@ -60,13 +61,12 @@ func New(clientCtx clientcontext.CLIContext) *EthermintBackend { // BlockNumber returns the current block number. func (b *EthermintBackend) BlockNumber() (hexutil.Uint64, error) { - // NOTE: using 0 as min and max height returns the blockchain info up to the latest block. - info, err := b.clientCtx.Client.BlockchainInfo(0, 0) + blockNumber, err := b.LatestBlockNumber() if err != nil { return hexutil.Uint64(0), err } - return hexutil.Uint64(info.LastHeight), nil + return hexutil.Uint64(blockNumber), nil } // GetBlockByNumber returns the block identified by number. @@ -196,7 +196,7 @@ func (b *EthermintBackend) PendingTransactions() ([]*rpctypes.Transaction, error return nil, err } - transactions := make([]*rpctypes.Transaction, pendingTxs.Count) + transactions := make([]*rpctypes.Transaction, 0) for _, tx := range pendingTxs.Txs { ethTx, err := rpctypes.RawTxToEthTx(b.clientCtx, tx) if err != nil { @@ -257,3 +257,14 @@ func (b *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, er func (b *EthermintBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } + +// LatestBlockNumber gets the latest block height in int64 format. +func (b *EthermintBackend) LatestBlockNumber() (int64, error) { + // NOTE: using 0 as min and max height returns the blockchain info up to the latest block. + info, err := b.clientCtx.Client.BlockchainInfo(0, 0) + if err != nil { + return 0, err + } + + return info.LastHeight, nil +} \ No newline at end of file diff --git a/app/rpc/namespaces/eth/api.go b/app/rpc/namespaces/eth/api.go index 1e02c811ca..eba8c21b2e 100644 --- a/app/rpc/namespaces/eth/api.go +++ b/app/rpc/namespaces/eth/api.go @@ -8,9 +8,8 @@ import ( "math/big" "os" "sync" + "time" - "github.com/cosmos/cosmos-sdk/client/flags" - cmserver "github.com/cosmos/cosmos-sdk/server" "github.com/spf13/viper" "github.com/okex/okexchain/app/crypto/ethsecp256k1" @@ -34,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" clientcontext "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -94,7 +94,7 @@ func (api *PublicEthereumAPI) GetKeyringInfo() error { keybase, err := keys.NewKeyring( sdk.KeyringServiceName(), viper.GetString(flags.FlagKeyringBackend), - viper.GetString(cmserver.FlagUlockKeyHome), + viper.GetString(flags.FlagHome), api.clientCtx.Input, hd.EthSecp256k1Options()..., ) @@ -205,7 +205,6 @@ func (api *PublicEthereumAPI) Accounts() ([]common.Address, error) { return addresses, err } - for _, info := range infos { addressBytes := info.GetPubKey().Address().Bytes() addresses = append(addresses, common.BytesToAddress(addressBytes)) @@ -214,7 +213,7 @@ func (api *PublicEthereumAPI) Accounts() ([]common.Address, error) { return addresses, nil } -// rpctypes.BlockNumber returns the current block number. +// BlockNumber returns the current block number. func (api *PublicEthereumAPI) BlockNumber() (hexutil.Uint64, error) { api.logger.Debug("eth_blockNumber") return api.backend.BlockNumber() @@ -223,7 +222,12 @@ func (api *PublicEthereumAPI) BlockNumber() (hexutil.Uint64, error) { // GetBalance returns the provided account's balance up to the provided block number. func (api *PublicEthereumAPI) GetBalance(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Big, error) { api.logger.Debug("eth_getBalance", "address", address, "block number", blockNum) - clientCtx := api.clientCtx.WithHeight(blockNum.Int64()) + + clientCtx := api.clientCtx + if !(blockNum == rpctypes.PendingBlockNumber || blockNum == rpctypes.LatestBlockNumber) { + clientCtx = api.clientCtx.WithHeight(blockNum.Int64()) + } + res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/balance/%s", evmtypes.ModuleName, address.Hex()), nil) if err != nil { return nil, err @@ -236,6 +240,29 @@ func (api *PublicEthereumAPI) GetBalance(address common.Address, blockNum rpctyp return nil, err } + if blockNum != rpctypes.PendingBlockNumber { + return (*hexutil.Big)(val), nil + } + + // update the address balance with the pending transactions value (if applicable) + pendingTxs, err := api.backend.PendingTransactions() + if err != nil { + return nil, err + } + + for _, tx := range pendingTxs { + if tx == nil { + continue + } + + if tx.From == address { + val = new(big.Int).Sub(val, tx.Value.ToInt()) + } + if *tx.To == address { + val = new(big.Int).Add(val, tx.Value.ToInt()) + } + } + return (*hexutil.Big)(val), nil } @@ -256,20 +283,16 @@ func (api *PublicEthereumAPI) GetStorageAt(address common.Address, key string, b // GetTransactionCount returns the number of transactions at the given address up to the given block number. func (api *PublicEthereumAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) { api.logger.Debug("eth_getTransactionCount", "address", address, "block number", blockNum) - clientCtx := api.clientCtx.WithHeight(blockNum.Int64()) - // Get nonce (sequence) from account - from := sdk.AccAddress(address.Bytes()) - accRet := authtypes.NewAccountRetriever(clientCtx) + clientCtx := api.clientCtx + pending := blockNum == rpctypes.PendingBlockNumber - err := accRet.EnsureExists(from) - if err != nil { - // account doesn't exist yet, return 0 - n := hexutil.Uint64(0) - return &n, nil + // pass the given block height to the context if the height is not pending or latest + if !pending && blockNum != rpctypes.LatestBlockNumber { + clientCtx = api.clientCtx.WithHeight(blockNum.Int64()) } - _, nonce, err := accRet.GetAccountNumberSequence(from) + nonce, err := api.accountNonce(clientCtx, address, pending) if err != nil { return nil, err } @@ -300,17 +323,54 @@ func (api *PublicEthereumAPI) GetBlockTransactionCountByHash(hash common.Hash) * return &n } -// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number. +// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by its height. func (api *PublicEthereumAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint { api.logger.Debug("eth_getBlockTransactionCountByNumber", "block number", blockNum) - height := blockNum.Int64() - resBlock, err := api.clientCtx.Client.Block(&height) - if err != nil { - return nil + + var ( + height int64 + err error + txCount hexutil.Uint + txs int + ) + + switch blockNum { + case rpctypes.PendingBlockNumber: + height, err = api.backend.LatestBlockNumber() + if err != nil { + return nil + } + resBlock, err := api.clientCtx.Client.Block(&height) + if err != nil { + return nil + } + // get the pending transaction count + pendingTxs, err := api.backend.PendingTransactions() + if err != nil { + return nil + } + txs = len(resBlock.Block.Txs) + len(pendingTxs) + case rpctypes.LatestBlockNumber: + height, err = api.backend.LatestBlockNumber() + if err != nil { + return nil + } + resBlock, err := api.clientCtx.Client.Block(&height) + if err != nil { + return nil + } + txs = len(resBlock.Block.Txs) + default: + height = blockNum.Int64() + resBlock, err := api.clientCtx.Client.Block(&height) + if err != nil { + return nil + } + txs = len(resBlock.Block.Txs) } - n := hexutil.Uint(len(resBlock.Block.Txs)) - return &n + txCount = hexutil.Uint(txs) + return &txCount } // GetUncleCountByBlockHash returns the number of uncles in the block idenfied by hash. Always zero. @@ -387,6 +447,11 @@ func (api *PublicEthereumAPI) SendTransaction(args rpctypes.SendTxArgs) (common. return common.Hash{}, err } + if err := tx.ValidateBasic(); err != nil { + api.logger.Debug("tx failed basic validation", "error", err) + return common.Hash{}, err + } + // Sign transaction if err := tx.Sign(api.chainIDEpoch, key.ToECDSA()); err != nil { api.logger.Debug("failed to sign tx", "error", err) @@ -459,13 +524,13 @@ func (api *PublicEthereumAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.Bloc // DoCall performs a simulated call operation through the evmtypes. It returns the // estimated gas used on the operation or an error if fails. func (api *PublicEthereumAPI) doCall( - args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, globalGasCap *big.Int, + args rpctypes.CallArgs, blockNum rpctypes.BlockNumber, globalGasCap *big.Int, ) (*sdk.SimulationResponse, error) { - // Set height for historical queries - clientCtx := api.clientCtx - if blockNr.Int64() != 0 { - clientCtx = api.clientCtx.WithHeight(blockNr.Int64()) + clientCtx := api.clientCtx + // pass the given block height to the context if the height is not pending or latest + if !(blockNum == rpctypes.PendingBlockNumber || blockNum == rpctypes.LatestBlockNumber) { + clientCtx = api.clientCtx.WithHeight(blockNum.Int64()) } // Set sender address or use a default if none specified @@ -515,17 +580,29 @@ func (api *PublicEthereumAPI) doCall( toAddr = sdk.AccAddress(args.To.Bytes()) } + var msgs []sdk.Msg // Create new call message msg := evmtypes.NewMsgEthermint(0, &toAddr, sdk.NewIntFromBigInt(value), gas, sdk.NewIntFromBigInt(gasPrice), data, sdk.AccAddress(addr.Bytes())) + msgs = append(msgs, msg) - if err := msg.ValidateBasic(); err != nil { - return nil, err + // convert the pending transactions into ethermint msgs + if blockNum == rpctypes.PendingBlockNumber { + pendingMsgs, err := api.pendingMsgs() + if err != nil { + return nil, err + } + msgs = append(msgs, pendingMsgs...) } // Generate tx to be used to simulate (signature isn't needed) var stdSig authtypes.StdSignature - tx := authtypes.NewStdTx([]sdk.Msg{msg}, authtypes.StdFee{}, []authtypes.StdSignature{stdSig}, "") + stdSigs := []authtypes.StdSignature{stdSig} + + tx := authtypes.NewStdTx(msgs, authtypes.StdFee{}, stdSigs, "") + if err := tx.ValidateBasic(); err != nil { + return nil, err + } txEncoder := authclient.GetTxEncoder(clientCtx.Codec) txBytes, err := txEncoder(tx) @@ -573,14 +650,71 @@ func (api *PublicEthereumAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map // GetBlockByNumber returns the block identified by number. func (api *PublicEthereumAPI) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) { api.logger.Debug("eth_getBlockByNumber", "number", blockNum, "full", fullTx) - return api.backend.GetBlockByNumber(blockNum, fullTx) + + if blockNum != rpctypes.PendingBlockNumber { + return api.backend.GetBlockByNumber(blockNum, fullTx) + } + + height, err := api.backend.LatestBlockNumber() + if err != nil { + return nil, err + } + + // latest block info + latestBlock, err := api.clientCtx.Client.Block(&height) + if err != nil { + return nil, err + } + + // number of pending txs queried from the mempool + unconfirmedTxs, err := api.clientCtx.Client.UnconfirmedTxs(1000) + if err != nil { + return nil, err + } + + pendingTxs, gasUsed, err := rpctypes.EthTransactionsFromTendermint(api.clientCtx, unconfirmedTxs.Txs) + if err != nil { + return nil, err + } + + return rpctypes.FormatBlock( + tmtypes.Header{ + Version: latestBlock.Block.Version, + ChainID: api.clientCtx.ChainID, + Height: height + 1, + Time: time.Unix(0, 0), + LastBlockID: latestBlock.BlockID, + ValidatorsHash: latestBlock.Block.NextValidatorsHash, + }, + 0, + 0, + gasUsed, + pendingTxs, + ethtypes.Bloom{}, + ), nil + } // GetTransactionByHash returns the transaction identified by hash. func (api *PublicEthereumAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.Transaction, error) { api.logger.Debug("eth_getTransactionByHash", "hash", hash) + tx, err := api.clientCtx.Client.Tx(hash.Bytes(), false) if err != nil { + // check if the tx is on the mempool + pendingTxs, pendingErr := api.PendingTransactions() + if pendingErr != nil { + return nil, err + } + + if len(pendingTxs) != 0 { + for _, tx := range pendingTxs { + if tx != nil && hash == tx.Hash { + return tx, nil + } + } + } + // Return nil for transaction when not found return nil, nil } @@ -624,7 +758,37 @@ func (api *PublicEthereumAPI) GetTransactionByBlockHashAndIndex(hash common.Hash // GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. func (api *PublicEthereumAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.Transaction, error) { api.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx) - height := blockNum.Int64() + var ( + height int64 + err error + ) + + switch blockNum { + case rpctypes.PendingBlockNumber: + // get all the EVM pending txs + pendingTxs, err := api.backend.PendingTransactions() + if err != nil { + return nil, err + } + + // return if index out of bounds + if uint64(idx) >= uint64(len(pendingTxs)) { + return nil, nil + } + + // change back to pendingTxs[idx] once pending queue is fixed. + return pendingTxs[int(idx)], nil + + case rpctypes.LatestBlockNumber: + height, err = api.backend.LatestBlockNumber() + if err != nil { + return nil, err + } + + default: + height = blockNum.Int64() + } + resBlock, err := api.clientCtx.Client.Block(&height) if err != nil { return nil, err @@ -733,7 +897,7 @@ func (api *PublicEthereumAPI) GetTransactionReceipt(hash common.Hash) (map[strin // 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 (api *PublicEthereumAPI) PendingTransactions() ([]*rpctypes.Transaction, error) { - api.logger.Debug("eth_getPendingTransactions") + api.logger.Debug("eth_pendingTransactions") return api.backend.PendingTransactions() } @@ -823,40 +987,32 @@ func (api *PublicEthereumAPI) GetProof(address common.Address, storageKeys []str // generateFromArgs populates tx message with args (used in RPC API) func (api *PublicEthereumAPI) generateFromArgs(args rpctypes.SendTxArgs) (*evmtypes.MsgEthereumTx, error) { var ( - nonce uint64 - gasLimit uint64 - err error + nonce, gasLimit uint64 + err error ) amount := (*big.Int)(args.Value) gasPrice := (*big.Int)(args.GasPrice) if args.GasPrice == nil { - // Set default gas price // TODO: Change to min gas price from context once available through server/daemon gasPrice = big.NewInt(ethermint.DefaultGasPrice) } if args.Nonce == nil { - // Get nonce (sequence) from account - from := sdk.AccAddress(args.From.Bytes()) - accRet := authtypes.NewAccountRetriever(api.clientCtx) - - if api.clientCtx.Keybase == nil { - return nil, fmt.Errorf("clientCtx.Keybase is nil") - } - - _, nonce, err = accRet.GetAccountNumberSequence(from) - if err != nil { - return nil, err - } + // get the nonce from the account retriever and the pending transactions + nonce, err = api.accountNonce(api.clientCtx, args.From, true) } else { nonce = (uint64)(*args.Nonce) } + if err != nil { + return nil, err + } + if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { - return nil, errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) + return nil, errors.New("both 'data' and 'input' are set and not equal. Please use 'input' to pass transaction call data") } // Sets input to either Input or Data, if both are set and not equal error above returns @@ -893,3 +1049,89 @@ func (api *PublicEthereumAPI) generateFromArgs(args rpctypes.SendTxArgs) (*evmty return &msg, nil } + +// pendingMsgs constructs an array of sdk.Msg. This method will check pending transactions and convert +// those transactions into ethermint messages. +func (api *PublicEthereumAPI) pendingMsgs() ([]sdk.Msg, error) { + // nolint: prealloc + var msgs []sdk.Msg + + pendingTxs, err := api.PendingTransactions() + if err != nil { + return nil, err + } + + for _, pendingTx := range pendingTxs { + // NOTE: we have to construct the EVM transaction instead of just casting from the tendermint + // transactions because PendingTransactions only checks for MsgEthereumTx messages. + + pendingTo := sdk.AccAddress(pendingTx.To.Bytes()) + pendingFrom := sdk.AccAddress(pendingTx.From.Bytes()) + pendingGas, err := hexutil.DecodeUint64(pendingTx.Gas.String()) + if err != nil { + return nil, err + } + + pendingValue := pendingTx.Value.ToInt() + pendingGasPrice := new(big.Int).SetUint64(ethermint.DefaultGasPrice) + if pendingTx.GasPrice != nil { + pendingGasPrice = pendingTx.GasPrice.ToInt() + } + + pendingData := pendingTx.Input + + msg := evmtypes.NewMsgEthermint(0, &pendingTo, sdk.NewIntFromBigInt(pendingValue), pendingGas, + sdk.NewIntFromBigInt(pendingGasPrice), pendingData, pendingFrom) + + msgs = append(msgs, msg) + } + return msgs, nil +} + +// accountNonce returns looks up the transaction nonce count for a given address. If the pending boolean +// is set to true, it will add to the counter all the uncommitted EVM transactions sent from the address. +// NOTE: The function returns no error if the account doesn't exist. +func (api *PublicEthereumAPI) accountNonce( + clientCtx clientcontext.CLIContext, address common.Address, pending bool, +) (uint64, error) { + // Get nonce (sequence) from sender account + from := sdk.AccAddress(address.Bytes()) + + // use a the given client context in case its wrapped with a custom height + accRet := authtypes.NewAccountRetriever(clientCtx) + + if err := accRet.EnsureExists(from); err != nil { + // account doesn't exist yet, return 0 + return 0, nil + } + + _, nonce, err := accRet.GetAccountNumberSequence(from) + if err != nil { + return 0, err + } + + 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 := api.backend.PendingTransactions() + if err != nil { + return 0, err + } + + // add the uncommitted txs to the nonce counter + if len(pendingTxs) != 0 { + for i := range pendingTxs { + if pendingTxs[i] == nil { + continue + } + if pendingTxs[i].From == address { + nonce++ + } + } + } + + return nonce, nil +} \ No newline at end of file diff --git a/app/rpc/types/block.go b/app/rpc/types/block.go index 0951da25a9..a6168b43ba 100644 --- a/app/rpc/types/block.go +++ b/app/rpc/types/block.go @@ -18,6 +18,9 @@ const ( // EarliestBlockNumber mapping from "earliest" to 1 for tm query (earliest query not supported) EarliestBlockNumber = BlockNumber(1) + + // PendingBlockNumber mapping from "pending" to -1 for tm query + PendingBlockNumber = BlockNumber(-1) ) // NewBlockNumber creates a new BlockNumber instance. @@ -45,7 +48,7 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error { *bn = LatestBlockNumber return nil case "pending": - *bn = LatestBlockNumber + *bn = PendingBlockNumber return nil } diff --git a/app/rpc/types/utils.go b/app/rpc/types/utils.go index fcce48f92c..a6beedb8bc 100644 --- a/app/rpc/types/utils.go +++ b/app/rpc/types/utils.go @@ -31,7 +31,7 @@ func RawTxToEthTx(clientCtx clientcontext.CLIContext, bz []byte) (*evmtypes.MsgE ethTx, ok := tx.(evmtypes.MsgEthereumTx) if !ok { - return nil, fmt.Errorf("invalid transaction type %T, expected %T", tx, &evmtypes.MsgEthereumTx{}) + return nil, fmt.Errorf("invalid transaction type %T, expected %T", tx, evmtypes.MsgEthereumTx{}) } return ðTx, nil } @@ -90,7 +90,7 @@ func EthBlockFromTendermint(clientCtx clientcontext.CLIContext, block *tmtypes.B bloom := bloomRes.Bloom - return formatBlock(block.Header, block.Size(), gasLimit, gasUsed, transactions, bloom), nil + return FormatBlock(block.Header, block.Size(), gasLimit, gasUsed, transactions, bloom), nil } // EthHeaderFromTendermint is an util function that returns an Ethereum Header @@ -150,7 +150,9 @@ func BlockMaxGasFromConsensusParams(_ context.Context, clientCtx clientcontext.C return gasLimit, nil } -func formatBlock( +// FormatBlock creates an ethereum block from a tendermint header and ethereum-formatted +// transactions. +func FormatBlock( header tmtypes.Header, size int, gasLimit int64, gasUsed *big.Int, transactions interface{}, bloom ethtypes.Bloom, ) map[string]interface{} {