diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c8104f..653ef25e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,17 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (evm) [\#661](https://github.com/cosmos/ethermint/pull/661) `Balance` field has been removed from the evm module's `GenesisState`. +### Features + +* (rpc) [\#571](https://github.com/cosmos/ethermint/pull/571) Add pending queries to JSON-RPC calls. This allows for the querying of pending transactions and other relevant information that pertains to the pending state: + * `eth_getBalance` + * `eth_getTransactionCount` + * `eth_getBlockTransactionCountByNumber` + * `eth_getBlockByNumber` + * `eth_getTransactionByHash` + * `eth_getTransactionByBlockNumberAndIndex` + * `eth_sendTransaction` - the nonce will automatically update to its pending nonce (when none is explicitly provided) + ### Improvements * (evm) [\#661](https://github.com/cosmos/ethermint/pull/661) Add invariant check for account balance and account nonce. diff --git a/Makefile b/Makefile index 6315c431..fd46c766 100644 --- a/Makefile +++ b/Makefile @@ -266,7 +266,10 @@ test-import: rm -rf importer/tmp test-rpc: - ./scripts/integration-test-all.sh -q 1 -z 1 -s 2 + ./scripts/integration-test-all.sh -t "rpc" -q 1 -z 1 -s 2 -m "rpc" + +test-rpc-pending: + ./scripts/integration-test-all.sh -t "pending" -q 1 -z 1 -s 2 -m "pending" test-contract: @type "npm" 2> /dev/null || (echo 'Npm does not exist. Please install node.js and npm."' && exit 1) diff --git a/init.sh b/init.sh index 17223d4f..46acda79 100755 --- a/init.sh +++ b/init.sh @@ -32,6 +32,21 @@ cat $HOME/.ethermintd/config/genesis.json | jq '.app_state["mint"]["params"]["mi # Enable faucet cat $HOME/.ethermintd/config/genesis.json | jq '.app_state["faucet"]["enable_faucet"]=true' > $HOME/.ethermintd/config/tmp_genesis.json && mv $HOME/.ethermintd/config/tmp_genesis.json $HOME/.ethermintd/config/genesis.json +# increase block time (?) +cat $HOME/.ethermintd/config/genesis.json | jq '.consensus_params["block"]["time_iota_ms"]="30000"' > $HOME/.ethermintd/config/tmp_genesis.json && mv $HOME/.ethermintd/config/tmp_genesis.json $HOME/.ethermintd/config/genesis.json + +if [[ $1 == "pending" ]]; then + echo "pending mode on; block times will be set to 30s." + # sed -i 's/create_empty_blocks_interval = "0s"/create_empty_blocks_interval = "30s"/g' $HOME/.ethermintd/config/config.toml + sed -i 's/timeout_propose = "3s"/timeout_propose = "30s"/g' $HOME/.ethermintd/config/config.toml + sed -i 's/timeout_propose_delta = "500ms"/timeout_propose_delta = "5s"/g' $HOME/.ethermintd/config/config.toml + sed -i 's/timeout_prevote = "1s"/timeout_prevote = "10s"/g' $HOME/.ethermintd/config/config.toml + sed -i 's/timeout_prevote_delta = "500ms"/timeout_prevote_delta = "5s"/g' $HOME/.ethermintd/config/config.toml + sed -i 's/timeout_precommit = "1s"/timeout_precommit = "10s"/g' $HOME/.ethermintd/config/config.toml + sed -i 's/timeout_precommit_delta = "500ms"/timeout_precommit_delta = "5s"/g' $HOME/.ethermintd/config/config.toml + sed -i 's/timeout_commit = "5s"/timeout_commit = "150s"/g' $HOME/.ethermintd/config/config.toml +fi + # Allocate genesis accounts (cosmos formatted addresses) ethermintd add-genesis-account $(ethermintcli keys show $KEY -a) 100000000000000000000aphoton diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go index 99d71174..a8b2eae3 100644 --- a/rpc/backend/backend.go +++ b/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 { @@ -209,10 +209,8 @@ func (b *EthermintBackend) PendingTransactions() ([]*rpctypes.Transaction, error if err != nil { return nil, err } - transactions = append(transactions, rpcTx) } - return transactions, nil } @@ -257,3 +255,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 +} diff --git a/rpc/namespaces/eth/api.go b/rpc/namespaces/eth/api.go index 57e8a143..fb7ff5d5 100644 --- a/rpc/namespaces/eth/api.go +++ b/rpc/namespaces/eth/api.go @@ -8,6 +8,7 @@ import ( "math/big" "os" "sync" + "time" "github.com/spf13/viper" @@ -212,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() @@ -221,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 @@ -234,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 } @@ -254,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 } @@ -298,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. @@ -385,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) @@ -457,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 @@ -513,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) @@ -571,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 } @@ -622,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 @@ -731,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() } @@ -821,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 @@ -891,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 +} diff --git a/rpc/types/block.go b/rpc/types/block.go index 0951da25..a6168b43 100644 --- a/rpc/types/block.go +++ b/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/rpc/types/utils.go b/rpc/types/utils.go index f5a1ed67..0aa06a9f 100644 --- a/rpc/types/utils.go +++ b/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{} { diff --git a/scripts/integration-test-all.sh b/scripts/integration-test-all.sh index 0001f4aa..c148ab54 100755 --- a/scripts/integration-test-all.sh +++ b/scripts/integration-test-all.sh @@ -12,7 +12,6 @@ TEST_QTD=1 #PORT AND RPC_PORT 3 initial digits, to be concat with a suffix later when node is initialized RPC_PORT="854" IP_ADDR="0.0.0.0" -MODE="rpc" KEY="mykey" CHAINID="ethermint-2" @@ -33,7 +32,7 @@ usage() { exit 1 } -while getopts "h?t:q:z:s:" args; do +while getopts "h?t:q:z:s:m:" args; do case $args in h|\?) usage; @@ -42,6 +41,7 @@ while getopts "h?t:q:z:s:" args; do q ) QTD=${OPTARG};; z ) TEST_QTD=${OPTARG};; s ) SLEEP_TIMEOUT=${OPTARG};; + m ) MODE=${OPTARG};; esac done @@ -92,6 +92,18 @@ init_func() { "$PWD"/build/ethermintd collect-gentxs --home "$DATA_DIR$i" echo "prepare genesis: Run validate-genesis to ensure everything worked and that the genesis file is setup correctly" "$PWD"/build/ethermintd validate-genesis --home "$DATA_DIR$i" + + if [[ $MODE == "pending" ]]; then + ls $DATA_DIR$i + # sed -i 's/create_empty_blocks_interval = "0s"/create_empty_blocks_interval = "30s"/g' $DATA_DIR$i/config/config.toml + sed -i 's/timeout_propose = "3s"/timeout_propose = "30s"/g' $DATA_DIR$i/config/config.toml + sed -i 's/timeout_propose_delta = "500ms"/timeout_propose_delta = "2s"/g' $DATA_DIR$i/config/config.toml + sed -i 's/timeout_prevote = "1s"/timeout_prevote = "120s"/g' $DATA_DIR$i/config/config.toml + sed -i 's/timeout_prevote_delta = "500ms"/timeout_prevote_delta = "2s"/g' $DATA_DIR$i/config/config.toml + sed -i 's/timeout_precommit = "1s"/timeout_precommit = "10s"/g' $DATA_DIR$i/config/config.toml + sed -i 's/timeout_precommit_delta = "500ms"/timeout_precommit_delta = "2s"/g' $DATA_DIR$i/config/config.toml + sed -i 's/timeout_commit = "5s"/timeout_commit = "150s"/g' $DATA_DIR$i/config/config.toml + fi } start_func() { @@ -138,12 +150,17 @@ echo "done sleeping" set +e -if [[ -z $TEST || $TEST == "rpc" ]]; then - +if [[ -z $TEST || $TEST == "rpc" || $TEST == "pending" ]]; then + for i in $(seq 1 "$TEST_QTD"); do HOST_RPC=http://$IP_ADDR:$RPC_PORT"$i" echo "going to test ethermint node $HOST_RPC ..." - MODE=$MODE HOST=$HOST_RPC go test ./tests/... -timeout=300s -v -short + if [[ $MODE == "pending" ]]; then + sleep 150 + MODE=$MODE HOST=$HOST_RPC go test -v ./tests/tests-pending/rpc_pending_test.go + else + MODE=$MODE HOST=$HOST_RPC go test ./tests/... -timeout=300s -v -short + fi RPC_FAIL=$? done diff --git a/tests/personal_test.go b/tests/personal_test.go index 8f4cf923..4e90aee5 100644 --- a/tests/personal_test.go +++ b/tests/personal_test.go @@ -12,7 +12,7 @@ import ( ) func TestPersonal_ListAccounts(t *testing.T) { - rpcRes := call(t, "personal_listAccounts", []string{}) + rpcRes := Call(t, "personal_listAccounts", []string{}) var res []hexutil.Bytes err := json.Unmarshal(rpcRes.Result, &res) @@ -21,12 +21,12 @@ func TestPersonal_ListAccounts(t *testing.T) { } func TestPersonal_NewAccount(t *testing.T) { - rpcRes := call(t, "personal_newAccount", []string{"password"}) + rpcRes := Call(t, "personal_newAccount", []string{"password"}) var addr common.Address err := json.Unmarshal(rpcRes.Result, &addr) require.NoError(t, err) - rpcRes = call(t, "personal_listAccounts", []string{}) + rpcRes = Call(t, "personal_listAccounts", []string{}) var res []hexutil.Bytes err = json.Unmarshal(rpcRes.Result, &res) require.NoError(t, err) @@ -34,7 +34,7 @@ func TestPersonal_NewAccount(t *testing.T) { } func TestPersonal_Sign(t *testing.T) { - rpcRes := call(t, "personal_sign", []interface{}{hexutil.Bytes{0x88}, hexutil.Bytes(from), ""}) + rpcRes := Call(t, "personal_sign", []interface{}{hexutil.Bytes{0x88}, hexutil.Bytes(from), ""}) var res hexutil.Bytes err := json.Unmarshal(rpcRes.Result, &res) @@ -49,7 +49,7 @@ func TestPersonal_ImportRawKey(t *testing.T) { // parse priv key to hex hexPriv := common.Bytes2Hex(ethcrypto.FromECDSA(privkey)) - rpcRes := call(t, "personal_importRawKey", []string{hexPriv, "password"}) + rpcRes := Call(t, "personal_importRawKey", []string{hexPriv, "password"}) var res hexutil.Bytes err = json.Unmarshal(rpcRes.Result, &res) @@ -63,14 +63,14 @@ func TestPersonal_ImportRawKey(t *testing.T) { func TestPersonal_EcRecover(t *testing.T) { data := hexutil.Bytes{0x88} - rpcRes := call(t, "personal_sign", []interface{}{data, hexutil.Bytes(from), ""}) + rpcRes := Call(t, "personal_sign", []interface{}{data, hexutil.Bytes(from), ""}) var res hexutil.Bytes err := json.Unmarshal(rpcRes.Result, &res) require.NoError(t, err) require.Equal(t, 65, len(res)) - rpcRes = call(t, "personal_ecRecover", []interface{}{data, res}) + rpcRes = Call(t, "personal_ecRecover", []interface{}{data, res}) var ecrecoverRes common.Address err = json.Unmarshal(rpcRes.Result, &ecrecoverRes) require.NoError(t, err) @@ -79,23 +79,23 @@ func TestPersonal_EcRecover(t *testing.T) { func TestPersonal_UnlockAccount(t *testing.T) { pswd := "nootwashere" - rpcRes := call(t, "personal_newAccount", []string{pswd}) + rpcRes := Call(t, "personal_newAccount", []string{pswd}) var addr common.Address err := json.Unmarshal(rpcRes.Result, &addr) require.NoError(t, err) // try to sign, should be locked - _, err = callWithError("personal_sign", []interface{}{hexutil.Bytes{0x88}, addr, ""}) + _, err = CallWithError("personal_sign", []interface{}{hexutil.Bytes{0x88}, addr, ""}) require.Error(t, err) - rpcRes = call(t, "personal_unlockAccount", []interface{}{addr, ""}) + rpcRes = Call(t, "personal_unlockAccount", []interface{}{addr, ""}) var unlocked bool err = json.Unmarshal(rpcRes.Result, &unlocked) require.NoError(t, err) require.True(t, unlocked) // try to sign, should work now - rpcRes = call(t, "personal_sign", []interface{}{hexutil.Bytes{0x88}, addr, pswd}) + rpcRes = Call(t, "personal_sign", []interface{}{hexutil.Bytes{0x88}, addr, pswd}) var res hexutil.Bytes err = json.Unmarshal(rpcRes.Result, &res) require.NoError(t, err) @@ -104,24 +104,24 @@ func TestPersonal_UnlockAccount(t *testing.T) { func TestPersonal_LockAccount(t *testing.T) { pswd := "nootwashere" - rpcRes := call(t, "personal_newAccount", []string{pswd}) + rpcRes := Call(t, "personal_newAccount", []string{pswd}) var addr common.Address err := json.Unmarshal(rpcRes.Result, &addr) require.NoError(t, err) - rpcRes = call(t, "personal_unlockAccount", []interface{}{addr, ""}) + rpcRes = Call(t, "personal_unlockAccount", []interface{}{addr, ""}) var unlocked bool err = json.Unmarshal(rpcRes.Result, &unlocked) require.NoError(t, err) require.True(t, unlocked) - rpcRes = call(t, "personal_lockAccount", []interface{}{addr}) + rpcRes = Call(t, "personal_lockAccount", []interface{}{addr}) var locked bool err = json.Unmarshal(rpcRes.Result, &locked) require.NoError(t, err) require.True(t, locked) // try to sign, should be locked - _, err = callWithError("personal_sign", []interface{}{hexutil.Bytes{0x88}, addr, ""}) + _, err = CallWithError("personal_sign", []interface{}{hexutil.Bytes{0x88}, addr, ""}) require.Error(t, err) } diff --git a/tests/rpc_test.go b/tests/rpc_test.go index 1ec813e0..c1813dfe 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -8,7 +8,6 @@ package tests import ( "bytes" - "encoding/hex" "encoding/json" "fmt" "math/big" @@ -33,44 +32,19 @@ const ( ) var ( - MODE = os.Getenv("MODE") - HOST = os.Getenv("HOST") - - zeroString = "0x0" + MODE = os.Getenv("MODE") from = []byte{} + zeroString = "0x0" ) -type Request struct { - Version string `json:"jsonrpc"` - Method string `json:"method"` - Params interface{} `json:"params"` - ID int `json:"id"` -} - -type RPCError struct { - Code int `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` -} - -type Response struct { - Error *RPCError `json:"error"` - ID int `json:"id"` - Result json.RawMessage `json:"result,omitempty"` -} - func TestMain(m *testing.M) { if MODE != "rpc" { _, _ = fmt.Fprintln(os.Stdout, "Skipping RPC test") return } - if HOST == "" { - HOST = "http://localhost:8545" - } - var err error - from, err = getAddress() + from, err = GetAddress() if err != nil { fmt.Printf("failed to get account: %s\n", err) os.Exit(1) @@ -81,106 +55,19 @@ func TestMain(m *testing.M) { os.Exit(code) } -func getAddress() ([]byte, error) { - rpcRes, err := callWithError("eth_accounts", []string{}) - if err != nil { - return nil, err - } - - var res []hexutil.Bytes - err = json.Unmarshal(rpcRes.Result, &res) - if err != nil { - return nil, err - } - - return res[0], nil -} - -func createRequest(method string, params interface{}) Request { - return Request{ - Version: "2.0", - Method: method, - Params: params, - ID: 1, - } -} - -func call(t *testing.T, method string, params interface{}) *Response { - req, err := json.Marshal(createRequest(method, params)) - require.NoError(t, err) - - var rpcRes *Response - time.Sleep(1 * time.Second) - /* #nosec */ - res, err := http.Post(HOST, "application/json", bytes.NewBuffer(req)) - require.NoError(t, err) - - decoder := json.NewDecoder(res.Body) - rpcRes = new(Response) - err = decoder.Decode(&rpcRes) - require.NoError(t, err) - - err = res.Body.Close() - require.NoError(t, err) - require.Nil(t, rpcRes.Error) - - return rpcRes -} - -func callWithError(method string, params interface{}) (*Response, error) { - req, err := json.Marshal(createRequest(method, params)) - if err != nil { - return nil, err - } - - var rpcRes *Response - time.Sleep(1 * time.Second) - /* #nosec */ - res, err := http.Post(HOST, "application/json", bytes.NewBuffer(req)) - if err != nil { - return nil, err - } - - decoder := json.NewDecoder(res.Body) - rpcRes = new(Response) - err = decoder.Decode(&rpcRes) - if err != nil { - return nil, err - } - - err = res.Body.Close() - if err != nil { - return nil, err - } - - if rpcRes.Error != nil { - return nil, fmt.Errorf(rpcRes.Error.Message) - } - - return rpcRes, nil -} - -// turns a 0x prefixed hex string to a big.Int -func hexToBigInt(t *testing.T, in string) *big.Int { - s := in[2:] - b, err := hex.DecodeString(s) - require.NoError(t, err) - return big.NewInt(0).SetBytes(b) -} - func TestBlockBloom(t *testing.T) { - hash := deployTestContractWithFunction(t) - receipt := waitForReceipt(t, hash) + hash := DeployTestContractWithFunction(t, from) + receipt := WaitForReceipt(t, hash) number := receipt["blockNumber"].(string) param := []interface{}{number, false} - rpcRes := call(t, "eth_getBlockByNumber", param) + rpcRes := Call(t, "eth_getBlockByNumber", param) block := make(map[string]interface{}) err := json.Unmarshal(rpcRes.Result, &block) require.NoError(t, err) - lb := hexToBigInt(t, block["logsBloom"].(string)) + lb := HexToBigInt(t, block["logsBloom"].(string)) require.NotEqual(t, big.NewInt(0), lb) require.Equal(t, hash.String(), block["transactions"].([]interface{})[0]) } @@ -189,7 +76,7 @@ func TestEth_GetLogs_NoLogs(t *testing.T) { param := make([]map[string][]string, 1) param[0] = make(map[string][]string) param[0]["topics"] = []string{} - rpcRes := call(t, "eth_getLogs", param) + rpcRes := Call(t, "eth_getLogs", param) require.NotNil(t, rpcRes) require.Nil(t, rpcRes.Error) @@ -205,7 +92,7 @@ func TestEth_GetLogs_Topics_AB(t *testing.T) { t.Skip("skipping TestEth_GetLogs_Topics_AB") } - rpcRes := call(t, "eth_blockNumber", []string{}) + rpcRes := Call(t, "eth_blockNumber", []string{}) var res hexutil.Uint64 err := res.UnmarshalJSON(rpcRes.Result) @@ -216,10 +103,10 @@ func TestEth_GetLogs_Topics_AB(t *testing.T) { param[0]["topics"] = []string{helloTopic, worldTopic} param[0]["fromBlock"] = res.String() - hash := deployTestContractWithFunction(t) - waitForReceipt(t, hash) + hash := DeployTestContractWithFunction(t, from) + WaitForReceipt(t, hash) - rpcRes = call(t, "eth_getLogs", param) + rpcRes = Call(t, "eth_getLogs", param) var logs []*ethtypes.Log err = json.Unmarshal(rpcRes.Result, &logs) @@ -234,9 +121,9 @@ func TestEth_GetTransactionCount(t *testing.T) { t.Skip("skipping TestEth_GetTransactionCount") } - prev := getNonce(t) - sendTestTransaction(t) - post := getNonce(t) + prev := GetNonce(t, "latest") + SendTestTransaction(t, from) + post := GetNonce(t, "latest") require.Equal(t, prev, post-1) } @@ -246,10 +133,10 @@ func TestEth_GetTransactionLogs(t *testing.T) { t.Skip("skipping TestEth_GetTransactionLogs") } - hash, _ := deployTestContract(t) + hash, _ := DeployTestContract(t, from) param := []string{hash.String()} - rpcRes := call(t, "eth_getTransactionLogs", param) + rpcRes := Call(t, "eth_getTransactionLogs", param) logs := new([]*ethtypes.Log) err := json.Unmarshal(rpcRes.Result, logs) @@ -260,7 +147,7 @@ func TestEth_GetTransactionLogs(t *testing.T) { func TestEth_protocolVersion(t *testing.T) { expectedRes := hexutil.Uint(ethermint.ProtocolVersion) - rpcRes := call(t, "eth_protocolVersion", []string{}) + rpcRes := Call(t, "eth_protocolVersion", []string{}) var res hexutil.Uint err := res.UnmarshalJSON(rpcRes.Result) @@ -271,7 +158,7 @@ func TestEth_protocolVersion(t *testing.T) { } func TestEth_chainId(t *testing.T) { - rpcRes := call(t, "eth_chainId", []string{}) + rpcRes := Call(t, "eth_chainId", []string{}) var res hexutil.Uint err := res.UnmarshalJSON(rpcRes.Result) @@ -280,7 +167,7 @@ func TestEth_chainId(t *testing.T) { } func TestEth_blockNumber(t *testing.T) { - rpcRes := call(t, "eth_blockNumber", []string{}) + rpcRes := Call(t, "eth_blockNumber", []string{}) var res hexutil.Uint64 err := res.UnmarshalJSON(rpcRes.Result) @@ -291,7 +178,7 @@ func TestEth_blockNumber(t *testing.T) { func TestEth_coinbase(t *testing.T) { zeroAddress := hexutil.Bytes(ethcmn.Address{}.Bytes()) - rpcRes := call(t, "eth_coinbase", []string{}) + rpcRes := Call(t, "eth_coinbase", []string{}) var res hexutil.Bytes err := res.UnmarshalJSON(rpcRes.Result) @@ -302,7 +189,7 @@ func TestEth_coinbase(t *testing.T) { } func TestEth_GetBalance(t *testing.T) { - rpcRes := call(t, "eth_getBalance", []string{addrA, zeroString}) + rpcRes := Call(t, "eth_getBalance", []string{addrA, zeroString}) var res hexutil.Big err := res.UnmarshalJSON(rpcRes.Result) @@ -318,7 +205,7 @@ func TestEth_GetBalance(t *testing.T) { func TestEth_GetStorageAt(t *testing.T) { expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - rpcRes := call(t, "eth_getStorageAt", []string{addrA, fmt.Sprint(addrAStoreKey), zeroString}) + rpcRes := Call(t, "eth_getStorageAt", []string{addrA, fmt.Sprint(addrAStoreKey), zeroString}) var storage hexutil.Bytes err := storage.UnmarshalJSON(rpcRes.Result) @@ -334,7 +221,7 @@ func TestEth_GetProof(t *testing.T) { params[0] = addrA params[1] = []string{fmt.Sprint(addrAStoreKey)} params[2] = "latest" - rpcRes := call(t, "eth_getProof", params) + rpcRes := Call(t, "eth_getProof", params) require.NotNil(t, rpcRes) var accRes rpctypes.AccountResult @@ -348,7 +235,7 @@ func TestEth_GetProof(t *testing.T) { func TestEth_GetCode(t *testing.T) { expectedRes := hexutil.Bytes{} - rpcRes := call(t, "eth_getCode", []string{addrA, zeroString}) + rpcRes := Call(t, "eth_getCode", []string{addrA, zeroString}) var code hexutil.Bytes err := code.UnmarshalJSON(rpcRes.Result) @@ -368,13 +255,13 @@ func TestEth_SendTransaction_Transfer(t *testing.T) { param[0]["gasLimit"] = "0x5208" param[0]["gasPrice"] = "0x55ae82600" - rpcRes := call(t, "eth_sendTransaction", param) + rpcRes := Call(t, "eth_sendTransaction", param) var hash hexutil.Bytes err := json.Unmarshal(rpcRes.Result, &hash) require.NoError(t, err) - receipt := waitForReceipt(t, hash) + receipt := WaitForReceipt(t, hash) require.NotNil(t, receipt) require.Equal(t, "0x1", receipt["status"].(string)) } @@ -385,7 +272,7 @@ func TestEth_SendTransaction_ContractDeploy(t *testing.T) { param[0]["from"] = "0x" + fmt.Sprintf("%x", from) param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" - rpcRes := call(t, "eth_sendTransaction", param) + rpcRes := Call(t, "eth_sendTransaction", param) var hash hexutil.Bytes err := json.Unmarshal(rpcRes.Result, &hash) @@ -396,7 +283,7 @@ func TestEth_NewFilter(t *testing.T) { param := make([]map[string][]string, 1) param[0] = make(map[string][]string) param[0]["topics"] = []string{"0x0000000000000000000000000000000000000000000000000000000012341234"} - rpcRes := call(t, "eth_newFilter", param) + rpcRes := Call(t, "eth_newFilter", param) var ID string err := json.Unmarshal(rpcRes.Result, &ID) @@ -404,7 +291,7 @@ func TestEth_NewFilter(t *testing.T) { } func TestEth_NewBlockFilter(t *testing.T) { - rpcRes := call(t, "eth_newBlockFilter", []string{}) + rpcRes := Call(t, "eth_newBlockFilter", []string{}) var ID string err := json.Unmarshal(rpcRes.Result, &ID) @@ -412,7 +299,7 @@ func TestEth_NewBlockFilter(t *testing.T) { } func TestEth_GetFilterChanges_BlockFilter(t *testing.T) { - rpcRes := call(t, "eth_newBlockFilter", []string{}) + rpcRes := Call(t, "eth_newBlockFilter", []string{}) var ID string err := json.Unmarshal(rpcRes.Result, &ID) @@ -420,7 +307,7 @@ func TestEth_GetFilterChanges_BlockFilter(t *testing.T) { time.Sleep(5 * time.Second) - changesRes := call(t, "eth_getFilterChanges", []string{ID}) + changesRes := Call(t, "eth_getFilterChanges", []string{ID}) var hashes []ethcmn.Hash err = json.Unmarshal(changesRes.Result, &hashes) require.NoError(t, err) @@ -431,13 +318,13 @@ func TestEth_GetFilterChanges_NoLogs(t *testing.T) { param := make([]map[string][]string, 1) param[0] = make(map[string][]string) param[0]["topics"] = []string{} - rpcRes := call(t, "eth_newFilter", param) + rpcRes := Call(t, "eth_newFilter", param) var ID string err := json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) - changesRes := call(t, "eth_getFilterChanges", []string{ID}) + changesRes := Call(t, "eth_getFilterChanges", []string{ID}) var logs []*ethtypes.Log err = json.Unmarshal(changesRes.Result, &logs) @@ -445,7 +332,7 @@ func TestEth_GetFilterChanges_NoLogs(t *testing.T) { } func TestEth_GetFilterChanges_WrongID(t *testing.T) { - req, err := json.Marshal(createRequest("eth_getFilterChanges", []string{"0x1122334400000077"})) + req, err := json.Marshal(CreateRequest("eth_getFilterChanges", []string{"0x1122334400000077"})) require.NoError(t, err) var rpcRes *Response @@ -464,28 +351,13 @@ func TestEth_GetFilterChanges_WrongID(t *testing.T) { require.NotNil(t, "invalid filter ID", rpcRes.Error.Message) } -// sendTestTransaction sends a dummy transaction -func sendTestTransaction(t *testing.T) hexutil.Bytes { - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["to"] = "0x1122334455667788990011223344556677889900" - param[0]["value"] = "0x1" - rpcRes := call(t, "eth_sendTransaction", param) - - var hash hexutil.Bytes - err := json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - return hash -} - func TestEth_GetTransactionReceipt(t *testing.T) { - hash := sendTestTransaction(t) + hash := SendTestTransaction(t, from) time.Sleep(time.Second * 5) param := []string{hash.String()} - rpcRes := call(t, "eth_getTransactionReceipt", param) + rpcRes := Call(t, "eth_getTransactionReceipt", param) require.Nil(t, rpcRes.Error) receipt := make(map[string]interface{}) @@ -496,34 +368,13 @@ func TestEth_GetTransactionReceipt(t *testing.T) { require.Equal(t, []interface{}{}, receipt["logs"].([]interface{})) } -// deployTestContract deploys a contract that emits an event in the constructor -func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) { - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" - param[0]["gas"] = "0x200000" - - rpcRes := call(t, "eth_sendTransaction", param) - - var hash hexutil.Bytes - err := json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - - receipt := waitForReceipt(t, hash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - - return hash, receipt -} - func TestEth_GetTransactionReceipt_ContractDeployment(t *testing.T) { - hash, _ := deployTestContract(t) + hash, _ := DeployTestContract(t, from) time.Sleep(time.Second * 5) param := []string{hash.String()} - rpcRes := call(t, "eth_getTransactionReceipt", param) + rpcRes := Call(t, "eth_getTransactionReceipt", param) receipt := make(map[string]interface{}) err := json.Unmarshal(rpcRes.Result, &receipt) @@ -535,32 +386,8 @@ func TestEth_GetTransactionReceipt_ContractDeployment(t *testing.T) { } -func getTransactionReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { - param := []string{hash.String()} - rpcRes := call(t, "eth_getTransactionReceipt", param) - - receipt := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &receipt) - require.NoError(t, err) - - return receipt -} - -func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { - for i := 0; i < 12; i++ { - receipt := getTransactionReceipt(t, hash) - if receipt != nil { - return receipt - } - - time.Sleep(time.Second) - } - - return nil -} - func TestEth_GetFilterChanges_NoTopics(t *testing.T) { - rpcRes := call(t, "eth_blockNumber", []string{}) + rpcRes := Call(t, "eth_blockNumber", []string{}) var res hexutil.Uint64 err := res.UnmarshalJSON(rpcRes.Result) @@ -572,17 +399,17 @@ func TestEth_GetFilterChanges_NoTopics(t *testing.T) { param[0]["fromBlock"] = res.String() // instantiate new filter - rpcRes = call(t, "eth_newFilter", param) + rpcRes = Call(t, "eth_newFilter", param) require.Nil(t, rpcRes.Error) var ID string err = json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) // deploy contract, emitting some event - deployTestContract(t) + DeployTestContract(t, from) // get filter changes - changesRes := call(t, "eth_getFilterChanges", []string{ID}) + changesRes := Call(t, "eth_getFilterChanges", []string{ID}) var logs []*ethtypes.Log err = json.Unmarshal(changesRes.Result, &logs) @@ -606,51 +433,11 @@ var helloTopic = "0x775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73 // world parameter in Hello event var worldTopic = "0x0000000000000000000000000000000000000000000000000000000000000011" -func deployTestContractWithFunction(t *testing.T) hexutil.Bytes { - // pragma solidity ^0.5.1; - - // contract Test { - // event Hello(uint256 indexed world); - // event TestEvent(uint256 indexed a, uint256 indexed b); - - // uint256 myStorage; - - // constructor() public { - // emit Hello(17); - // } - - // function test(uint256 a, uint256 b) public { - // myStorage = a; - // emit TestEvent(a, b); - // } - // } - - bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032" - - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["data"] = bytecode - param[0]["gas"] = "0x200000" - - rpcRes := call(t, "eth_sendTransaction", param) - - var hash hexutil.Bytes - err := json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - - receipt := waitForReceipt(t, hash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - - return hash -} - // Tests topics case where there are topics in first two positions func TestEth_GetFilterChanges_Topics_AB(t *testing.T) { time.Sleep(time.Second) - rpcRes := call(t, "eth_blockNumber", []string{}) + rpcRes := Call(t, "eth_blockNumber", []string{}) var res hexutil.Uint64 err := res.UnmarshalJSON(rpcRes.Result) @@ -662,15 +449,15 @@ func TestEth_GetFilterChanges_Topics_AB(t *testing.T) { param[0]["fromBlock"] = res.String() // instantiate new filter - rpcRes = call(t, "eth_newFilter", param) + rpcRes = Call(t, "eth_newFilter", param) var ID string err = json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err, string(rpcRes.Result)) - deployTestContractWithFunction(t) + DeployTestContractWithFunction(t, from) // get filter changes - changesRes := call(t, "eth_getFilterChanges", []string{ID}) + changesRes := Call(t, "eth_getFilterChanges", []string{ID}) var logs []*ethtypes.Log err = json.Unmarshal(changesRes.Result, &logs) @@ -680,7 +467,7 @@ func TestEth_GetFilterChanges_Topics_AB(t *testing.T) { } func TestEth_GetFilterChanges_Topics_XB(t *testing.T) { - rpcRes := call(t, "eth_blockNumber", []string{}) + rpcRes := Call(t, "eth_blockNumber", []string{}) var res hexutil.Uint64 err := res.UnmarshalJSON(rpcRes.Result) @@ -692,15 +479,15 @@ func TestEth_GetFilterChanges_Topics_XB(t *testing.T) { param[0]["fromBlock"] = res.String() // instantiate new filter - rpcRes = call(t, "eth_newFilter", param) + rpcRes = Call(t, "eth_newFilter", param) var ID string err = json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) - deployTestContractWithFunction(t) + DeployTestContractWithFunction(t, from) // get filter changes - changesRes := call(t, "eth_getFilterChanges", []string{ID}) + changesRes := Call(t, "eth_getFilterChanges", []string{ID}) var logs []*ethtypes.Log err = json.Unmarshal(changesRes.Result, &logs) @@ -715,20 +502,20 @@ func TestEth_GetFilterChanges_Topics_XXC(t *testing.T) { } func TestEth_PendingTransactionFilter(t *testing.T) { - rpcRes := call(t, "eth_newPendingTransactionFilter", []string{}) + rpcRes := Call(t, "eth_newPendingTransactionFilter", []string{}) var ID string err := json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) for i := 0; i < 5; i++ { - deployTestContractWithFunction(t) + DeployTestContractWithFunction(t, from) } time.Sleep(10 * time.Second) // get filter changes - changesRes := call(t, "eth_getFilterChanges", []string{ID}) + changesRes := Call(t, "eth_getFilterChanges", []string{ID}) require.NotNil(t, changesRes) var txs []*hexutil.Bytes @@ -738,23 +525,13 @@ func TestEth_PendingTransactionFilter(t *testing.T) { require.True(t, len(txs) >= 2, "could not get any txs", "changesRes.Result", string(changesRes.Result)) } -func getNonce(t *testing.T) hexutil.Uint64 { - param := []interface{}{hexutil.Bytes(from), "latest"} - rpcRes := call(t, "eth_getTransactionCount", param) - - var nonce hexutil.Uint64 - err := json.Unmarshal(rpcRes.Result, &nonce) - require.NoError(t, err) - return nonce -} - func TestEth_EstimateGas(t *testing.T) { param := make([]map[string]string, 1) param[0] = make(map[string]string) param[0]["from"] = "0x" + fmt.Sprintf("%x", from) param[0]["to"] = "0x1122334455667788990011223344556677889900" param[0]["value"] = "0x1" - rpcRes := call(t, "eth_estimateGas", param) + rpcRes := Call(t, "eth_estimateGas", param) require.NotNil(t, rpcRes) require.NotEmpty(t, rpcRes.Result) @@ -773,7 +550,7 @@ func TestEth_EstimateGas_ContractDeployment(t *testing.T) { param[0]["from"] = "0x" + fmt.Sprintf("%x", from) param[0]["data"] = bytecode - rpcRes := call(t, "eth_estimateGas", param) + rpcRes := Call(t, "eth_estimateGas", param) require.NotNil(t, rpcRes) require.NotEmpty(t, rpcRes.Result) @@ -786,7 +563,7 @@ func TestEth_EstimateGas_ContractDeployment(t *testing.T) { func TestEth_GetBlockByNumber(t *testing.T) { param := []interface{}{"0x1", false} - rpcRes := call(t, "eth_getBlockByNumber", param) + rpcRes := Call(t, "eth_getBlockByNumber", param) block := make(map[string]interface{}) err := json.Unmarshal(rpcRes.Result, &block) diff --git a/tests/tests-pending/rpc_pending_test.go b/tests/tests-pending/rpc_pending_test.go new file mode 100644 index 00000000..a7679dc6 --- /dev/null +++ b/tests/tests-pending/rpc_pending_test.go @@ -0,0 +1,323 @@ +// This is a test utility for Ethermint's Web3 JSON-RPC services. +// +// To run these tests please first ensure you have the ethermintd running +// and have started the RPC service with `ethermintcli rest-server`. +// +// You can configure the desired HOST and MODE as well +package pending + +import ( + "encoding/json" + "fmt" + "math/big" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + rpctypes "github.com/cosmos/ethermint/rpc/types" + util "github.com/cosmos/ethermint/tests" +) + +const ( + addrA = "0xc94770007dda54cF92009BFF0dE90c06F603a09f" + addrAStoreKey = 0 +) + +var ( + MODE = os.Getenv("MODE") + from = []byte{} +) + +type Request struct { + Version string `json:"jsonrpc"` + Method string `json:"method"` + Params interface{} `json:"params"` + ID int `json:"id"` +} + +type RPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +type Response struct { + Error *RPCError `json:"error"` + ID int `json:"id"` + Result json.RawMessage `json:"result,omitempty"` +} + +func TestMain(m *testing.M) { + if MODE != "pending" { + _, _ = fmt.Fprintln(os.Stdout, "Skipping pending RPC test") + return + } + + var err error + from, err = util.GetAddress() + if err != nil { + fmt.Printf("failed to get account: %s\n", err) + os.Exit(1) + } + + // Start all tests + code := m.Run() + os.Exit(code) +} + +func TestEth_Pending_GetBalance(t *testing.T) { + var res hexutil.Big + rpcRes := util.Call(t, "eth_getBalance", []string{addrA, "latest"}) + err := res.UnmarshalJSON(rpcRes.Result) + require.NoError(t, err) + preTxLatestBalance := res.ToInt() + + rpcRes = util.Call(t, "eth_getBalance", []string{addrA, "pending"}) + err = res.UnmarshalJSON(rpcRes.Result) + require.NoError(t, err) + preTxPendingBalance := res.ToInt() + + t.Logf("Got pending balance %s for %s pre tx\n", preTxPendingBalance, addrA) + t.Logf("Got latest balance %s for %s pre tx\n", preTxLatestBalance, addrA) + + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addrA + param[0]["value"] = "0xA" + param[0]["gasLimit"] = "0x5208" + param[0]["gasPrice"] = "0x1" + + rpcRes = util.Call(t, "eth_sendTransaction", param) + require.Nil(t, rpcRes.Error) + + rpcRes = util.Call(t, "eth_getBalance", []string{addrA, "pending"}) + err = res.UnmarshalJSON(rpcRes.Result) + require.NoError(t, err) + postTxPendingBalance := res.ToInt() + t.Logf("Got pending balance %s for %s post tx\n", postTxPendingBalance, addrA) + + require.Equal(t, preTxPendingBalance.Add(preTxPendingBalance, big.NewInt(10)), postTxPendingBalance) + + rpcRes = util.Call(t, "eth_getBalance", []string{addrA, "latest"}) + err = res.UnmarshalJSON(rpcRes.Result) + require.NoError(t, err) + postTxLatestBalance := res.ToInt() + t.Logf("Got latest balance %s for %s post tx\n", postTxLatestBalance, addrA) + + require.Equal(t, preTxLatestBalance, postTxLatestBalance) +} + +func TestEth_Pending_GetTransactionCount(t *testing.T) { + prePendingNonce := util.GetNonce(t, "pending") + t.Logf("Pending nonce before tx is %d", prePendingNonce) + + currentNonce := util.GetNonce(t, "latest") + t.Logf("Current nonce is %d", currentNonce) + + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addrA + param[0]["value"] = "0xA" + param[0]["gasLimit"] = "0x5208" + param[0]["gasPrice"] = "0x1" + + txRes := util.Call(t, "eth_sendTransaction", param) + require.Nil(t, txRes.Error) + + pendingNonce := util.GetNonce(t, "pending") + latestNonce := util.GetNonce(t, "latest") + t.Logf("Latest nonce is %d", latestNonce) + require.Equal(t, currentNonce, latestNonce) + t.Logf("Pending nonce is %d", pendingNonce) + require.NotEqual(t, latestNonce, pendingNonce) + + require.Greater(t, uint64(pendingNonce), uint64(latestNonce)) + require.Equal(t, uint64(prePendingNonce)+uint64(1), uint64(pendingNonce)) +} + +func TestEth_Pending_GetBlockTransactionCountByNumber(t *testing.T) { + rpcRes := util.Call(t, "eth_getBlockTransactionCountByNumber", []interface{}{"pending"}) + var preTxPendingTxCount hexutil.Uint + err := json.Unmarshal(rpcRes.Result, &preTxPendingTxCount) + require.NoError(t, err) + t.Logf("Pre tx pending nonce is %d", preTxPendingTxCount) + + rpcRes = util.Call(t, "eth_getBlockTransactionCountByNumber", []interface{}{"latest"}) + var preTxLatestTxCount hexutil.Uint + err = json.Unmarshal(rpcRes.Result, &preTxLatestTxCount) + require.NoError(t, err) + t.Logf("Pre tx latest nonce is %d", preTxLatestTxCount) + + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addrA + param[0]["value"] = "0xA" + param[0]["gasLimit"] = "0x5208" + param[0]["gasPrice"] = "0x1" + + txRes := util.Call(t, "eth_sendTransaction", param) + require.Nil(t, txRes.Error) + + rpcRes = util.Call(t, "eth_getBlockTransactionCountByNumber", []interface{}{"pending"}) + var postTxPendingTxCount hexutil.Uint + err = json.Unmarshal(rpcRes.Result, &postTxPendingTxCount) + require.NoError(t, err) + t.Logf("Post tx pending nonce is %d", postTxPendingTxCount) + + rpcRes = util.Call(t, "eth_getBlockTransactionCountByNumber", []interface{}{"latest"}) + var postTxLatestTxCount hexutil.Uint + err = json.Unmarshal(rpcRes.Result, &postTxLatestTxCount) + require.NoError(t, err) + t.Logf("Post tx latest nonce is %d", postTxLatestTxCount) + + require.Equal(t, uint64(preTxPendingTxCount)+uint64(1), uint64(postTxPendingTxCount)) + require.NotEqual(t, uint64(postTxPendingTxCount)-uint64(preTxPendingTxCount), uint64(postTxLatestTxCount)-uint64(preTxLatestTxCount)) +} + +func TestEth_Pending_GetBlockByNumber(t *testing.T) { + rpcRes := util.Call(t, "eth_getBlockByNumber", []interface{}{"latest", true}) + var preTxLatestBlock map[string]interface{} + err := json.Unmarshal(rpcRes.Result, &preTxLatestBlock) + require.NoError(t, err) + preTxLatestTxs := len(preTxLatestBlock["transactions"].([]interface{})) + + rpcRes = util.Call(t, "eth_getBlockByNumber", []interface{}{"pending", true}) + var preTxPendingBlock map[string]interface{} + err = json.Unmarshal(rpcRes.Result, &preTxPendingBlock) + require.NoError(t, err) + preTxPendingTxs := len(preTxPendingBlock["transactions"].([]interface{})) + + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addrA + param[0]["value"] = "0xA" + param[0]["gasLimit"] = "0x5208" + param[0]["gasPrice"] = "0x1" + + txRes := util.Call(t, "eth_sendTransaction", param) + require.Nil(t, txRes.Error) + + rpcRes = util.Call(t, "eth_getBlockByNumber", []interface{}{"pending", true}) + var postTxPendingBlock map[string]interface{} + err = json.Unmarshal(rpcRes.Result, &postTxPendingBlock) + require.NoError(t, err) + postTxPendingTxs := len(postTxPendingBlock["transactions"].([]interface{})) + require.Greater(t, postTxPendingTxs, preTxPendingTxs) + + rpcRes = util.Call(t, "eth_getBlockByNumber", []interface{}{"latest", true}) + var postTxLatestBlock map[string]interface{} + err = json.Unmarshal(rpcRes.Result, &postTxLatestBlock) + require.NoError(t, err) + postTxLatestTxs := len(postTxLatestBlock["transactions"].([]interface{})) + require.Equal(t, preTxLatestTxs, postTxLatestTxs) + + require.Greater(t, postTxPendingTxs, preTxPendingTxs) +} + +func TestEth_Pending_GetTransactionByBlockNumberAndIndex(t *testing.T) { + var pendingTx []*rpctypes.Transaction + resPendingTxs := util.Call(t, "eth_pendingTransactions", []string{}) + err := json.Unmarshal(resPendingTxs.Result, &pendingTx) + require.NoError(t, err) + pendingTxCount := len(pendingTx) + + data := "0x608060405234801561001057600080fd5b5061011e806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063bc9c707d14602d575b600080fd5b603360ab565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101560715780820151818401526020810190506058565b50505050905090810190601f168015609d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60606040518060400160405280600681526020017f617261736b61000000000000000000000000000000000000000000000000000081525090509056fea2646970667358221220a31fa4c1ce0b3651fbf5401c511b483c43570c7de4735b5c3b0ad0db30d2573164736f6c63430007050033" + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addrA + param[0]["value"] = "0xA" + param[0]["gasLimit"] = "0x5208" + param[0]["gasPrice"] = "0x1" + param[0]["data"] = data + + txRes := util.Call(t, "eth_sendTransaction", param) + require.Nil(t, txRes.Error) + + rpcRes := util.Call(t, "eth_getTransactionByBlockNumberAndIndex", []interface{}{"pending", "0x" + fmt.Sprintf("%X", pendingTxCount)}) + var pendingBlockTx map[string]interface{} + err = json.Unmarshal(rpcRes.Result, &pendingBlockTx) + require.NoError(t, err) + + // verify the pending tx has all the correct fields from the tx sent. + require.NotEmpty(t, pendingBlockTx["hash"]) + require.Equal(t, pendingBlockTx["value"], "0xa") + require.Equal(t, data, pendingBlockTx["input"]) + + rpcRes = util.Call(t, "eth_getTransactionByBlockNumberAndIndex", []interface{}{"latest", "0x" + fmt.Sprintf("%X", pendingTxCount)}) + var latestBlock map[string]interface{} + err = json.Unmarshal(rpcRes.Result, &latestBlock) + require.NoError(t, err) + + // verify the pending trasnaction does not exist in the latest block info. + require.Empty(t, latestBlock) +} + +func TestEth_Pending_GetTransactionByHash(t *testing.T) { + data := "0x608060405234801561001057600080fd5b5061011e806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806302eb691b14602d575b600080fd5b603360ab565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101560715780820151818401526020810190506058565b50505050905090810190601f168015609d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60606040518060400160405280600d81526020017f617261736b61776173686572650000000000000000000000000000000000000081525090509056fea264697066735822122060917c5c2fab8c058a17afa6d3c1d23a7883b918ea3c7157131ea5b396e1aa7564736f6c63430007050033" + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addrA + param[0]["value"] = "0xA" + param[0]["gasLimit"] = "0x5208" + param[0]["gasPrice"] = "0x1" + param[0]["data"] = data + + txRes := util.Call(t, "eth_sendTransaction", param) + var txHash common.Hash + err := txHash.UnmarshalJSON(txRes.Result) + require.NoError(t, err) + + rpcRes := util.Call(t, "eth_getTransactionByHash", []interface{}{txHash}) + var pendingBlockTx map[string]interface{} + err = json.Unmarshal(rpcRes.Result, &pendingBlockTx) + require.NoError(t, err) + + // verify the pending tx has all the correct fields from the tx sent. + require.NotEmpty(t, pendingBlockTx) + require.NotEmpty(t, pendingBlockTx["hash"]) + require.Equal(t, pendingBlockTx["value"], "0xa") + require.Equal(t, pendingBlockTx["input"], data) +} + +func TestEth_Pending_SendTransaction_PendingNonce(t *testing.T) { + currNonce := util.GetNonce(t, "latest") + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", from) + param[0]["to"] = addrA + param[0]["value"] = "0xA" + param[0]["gasLimit"] = "0x5208" + param[0]["gasPrice"] = "0x1" + + // first transaction + txRes1 := util.Call(t, "eth_sendTransaction", param) + require.Nil(t, txRes1.Error) + pendingNonce1 := util.GetNonce(t, "pending") + require.Greater(t, uint64(pendingNonce1), uint64(currNonce)) + + // second transaction + param[0]["to"] = "0x7f0f463c4d57b1bd3e3b79051e6c5ab703e803d9" + txRes2 := util.Call(t, "eth_sendTransaction", param) + require.Nil(t, txRes2.Error) + pendingNonce2 := util.GetNonce(t, "pending") + require.Greater(t, uint64(pendingNonce2), uint64(currNonce)) + require.Greater(t, uint64(pendingNonce2), uint64(pendingNonce1)) + + // third transaction + param[0]["to"] = "0x7fb24493808b3f10527e3e0870afeb8a953052d2" + txRes3 := util.Call(t, "eth_sendTransaction", param) + require.Nil(t, txRes3.Error) + pendingNonce3 := util.GetNonce(t, "pending") + require.Greater(t, uint64(pendingNonce3), uint64(currNonce)) + require.Greater(t, uint64(pendingNonce3), uint64(pendingNonce2)) +} diff --git a/tests/utils.go b/tests/utils.go new file mode 100644 index 00000000..fc459be1 --- /dev/null +++ b/tests/utils.go @@ -0,0 +1,265 @@ +package tests + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "net/http" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +type Request struct { + Version string `json:"jsonrpc"` + Method string `json:"method"` + Params interface{} `json:"params"` + ID int `json:"id"` +} + +type RPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +type Response struct { + Error *RPCError `json:"error"` + ID int `json:"id"` + Result json.RawMessage `json:"result,omitempty"` +} + +var ( + HOST = os.Getenv("HOST") +) + +func GetAddress() ([]byte, error) { + rpcRes, err := CallWithError("eth_accounts", []string{}) + if err != nil { + return nil, err + } + + var res []hexutil.Bytes + err = json.Unmarshal(rpcRes.Result, &res) + if err != nil { + return nil, err + } + + return res[0], nil +} + +func CreateRequest(method string, params interface{}) Request { + return Request{ + Version: "2.0", + Method: method, + Params: params, + ID: 1, + } +} + +func Call(t *testing.T, method string, params interface{}) *Response { + req, err := json.Marshal(CreateRequest(method, params)) + require.NoError(t, err) + + var rpcRes *Response + time.Sleep(1 * time.Second) + /* #nosec */ + + if HOST == "" { + HOST = "http://localhost:8545" + } + res, err := http.Post(HOST, "application/json", bytes.NewBuffer(req)) //nolint:gosec + require.NoError(t, err) + + decoder := json.NewDecoder(res.Body) + rpcRes = new(Response) + err = decoder.Decode(&rpcRes) + require.NoError(t, err) + + err = res.Body.Close() + require.NoError(t, err) + require.Nil(t, rpcRes.Error) + + return rpcRes +} + +func CallWithError(method string, params interface{}) (*Response, error) { + req, err := json.Marshal(CreateRequest(method, params)) + if err != nil { + return nil, err + } + + var rpcRes *Response + time.Sleep(1 * time.Second) + /* #nosec */ + + if HOST == "" { + HOST = "http://localhost:8545" + } + res, err := http.Post(HOST, "application/json", bytes.NewBuffer(req)) //nolint:gosec + if err != nil { + return nil, err + } + + decoder := json.NewDecoder(res.Body) + rpcRes = new(Response) + err = decoder.Decode(&rpcRes) + if err != nil { + return nil, err + } + + err = res.Body.Close() + if err != nil { + return nil, err + } + + if rpcRes.Error != nil { + return nil, fmt.Errorf(rpcRes.Error.Message) + } + + return rpcRes, nil +} + +// turns a 0x prefixed hex string to a big.Int +func HexToBigInt(t *testing.T, in string) *big.Int { + s := in[2:] + b, err := hex.DecodeString(s) + require.NoError(t, err) + return big.NewInt(0).SetBytes(b) +} + +// sendTestTransaction sends a dummy transaction +func SendTestTransaction(t *testing.T, addr []byte) hexutil.Bytes { + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", addr) + param[0]["to"] = "0x1122334455667788990011223344556677889900" + param[0]["value"] = "0x1" + rpcRes := Call(t, "eth_sendTransaction", param) + + var hash hexutil.Bytes + err := json.Unmarshal(rpcRes.Result, &hash) + require.NoError(t, err) + return hash +} + +// deployTestContract deploys a contract that emits an event in the constructor +func DeployTestContract(t *testing.T, addr []byte) (hexutil.Bytes, map[string]interface{}) { + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", addr) + param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" + param[0]["gas"] = "0x200000" + + rpcRes := Call(t, "eth_sendTransaction", param) + + var hash hexutil.Bytes + err := json.Unmarshal(rpcRes.Result, &hash) + require.NoError(t, err) + + receipt := WaitForReceipt(t, hash) + require.NotNil(t, receipt, "transaction failed") + require.Equal(t, "0x1", receipt["status"].(string)) + + return hash, receipt +} + +func DeployTestContractWithFunction(t *testing.T, addr []byte) hexutil.Bytes { + // pragma solidity ^0.5.1; + + // contract Test { + // event Hello(uint256 indexed world); + // event TestEvent(uint256 indexed a, uint256 indexed b); + + // uint256 myStorage; + + // constructor() public { + // emit Hello(17); + // } + + // function test(uint256 a, uint256 b) public { + // myStorage = a; + // emit TestEvent(a, b); + // } + // } + + bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032" + + param := make([]map[string]string, 1) + param[0] = make(map[string]string) + param[0]["from"] = "0x" + fmt.Sprintf("%x", addr) + param[0]["data"] = bytecode + param[0]["gas"] = "0x200000" + + rpcRes := Call(t, "eth_sendTransaction", param) + + var hash hexutil.Bytes + err := json.Unmarshal(rpcRes.Result, &hash) + require.NoError(t, err) + + receipt := WaitForReceipt(t, hash) + require.NotNil(t, receipt, "transaction failed") + require.Equal(t, "0x1", receipt["status"].(string)) + + return hash +} + +//nolint +func GetTransactionReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { + param := []string{hash.String()} + rpcRes := Call(t, "eth_getTransactionReceipt", param) + + receipt := make(map[string]interface{}) + err := json.Unmarshal(rpcRes.Result, &receipt) + require.NoError(t, err) + + return receipt +} + +func WaitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { + for i := 0; i < 12; i++ { + receipt := GetTransactionReceipt(t, hash) + if receipt != nil { + return receipt + } + + time.Sleep(time.Second) + } + + return nil +} + +func GetNonce(t *testing.T, block string) hexutil.Uint64 { + from, err := GetAddress() + require.NoError(t, err) + + param := []interface{}{hexutil.Bytes(from), block} + rpcRes := Call(t, "eth_getTransactionCount", param) + + var nonce hexutil.Uint64 + err = json.Unmarshal(rpcRes.Result, &nonce) + require.NoError(t, err) + return nonce +} + +func UnlockAllAccounts(t *testing.T) { + var accts []common.Address + rpcRes := Call(t, "eth_accounts", []map[string]string{}) + err := json.Unmarshal(rpcRes.Result, &accts) + require.NoError(t, err) + + for _, acct := range accts { + t.Logf("account: %v", acct) + rpcRes = Call(t, "personal_unlockAccount", []interface{}{acct, ""}) + var unlocked bool + err = json.Unmarshal(rpcRes.Result, &unlocked) + require.NoError(t, err) + require.True(t, unlocked) + } +}