From e5f8073d75f1f72975f1852777a852727c00a56d Mon Sep 17 00:00:00 2001 From: Thomas Jay Rush <jrush@quickblocks.io> Date: Mon, 14 Sep 2020 02:59:07 -0400 Subject: [PATCH] 1084 various rpc fixes (#1111) * Fixes issue #1110 - eth_getStorageAt returning inconsistent values * Adds support for various eth_getUncle calls * Adding a couple of comments * Adding support for eth_getTransactionCount * Cleaning up README's * Cleaning up README for rpcdaemon Co-authored-by: tjayrush <jrush@greathill.com> --- README.md | 9 +- cmd/rpcdaemon/README.md | 166 +++++++++++++++++++++++++++ cmd/rpcdaemon/Readme.md | 41 ------- cmd/rpcdaemon/commands/daemon.go | 2 + cmd/rpcdaemon/commands/eth_api.go | 39 +++++-- cmd/rpcdaemon/commands/get_uncles.go | 83 ++++++++++++++ 6 files changed, 287 insertions(+), 53 deletions(-) create mode 100644 cmd/rpcdaemon/README.md delete mode 100644 cmd/rpcdaemon/Readme.md create mode 100644 cmd/rpcdaemon/commands/get_uncles.go diff --git a/README.md b/README.md index 83bf73dce2..c05de3cdb1 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,9 @@ Run RPC daemon > ./build/bin/rpcdaemon --private.api.addr=localhost:9090 ``` -Supported JSON-RPC calls ([eth](./cmd/rpcdaemon/commands/eth_api.go), [debug](./cmd/rpcdaemon/commands/debug_api.go), [net](./cmd/rpcdaemon/commands/net_api.go)): +Supported JSON-RPC calls ([eth](./cmd/rpcdaemon/commands/eth_api.go), [debug](./cmd/rpcdaemon/commands/debug_api.go), [net](./cmd/rpcdaemon/commands/net_api.go), [web3](./cmd/rpcdaemon/commands/web3_api.go)): + +For a more detailed status, [see this table](./cmd/rpcdaemon/README.md#rpc-implementation-status). ``` eth_coinbase @@ -138,6 +140,11 @@ eth_getBlockTransactionCountByHash eth_getBlockTransactionCountByNumber eth_getBalance eth_getCode +eth_GetTransactionCount +eth_GetUncleByBlockNumberAndIndex +eth_GetUncleByBlockHashAndIndex +eth_GetUncleCountByBlockNumber +eth_GetUncleCountByBlockHash eth_getLogs eth_getStorageAt eth_getTransactionReceipt diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md new file mode 100644 index 0000000000..7963962c58 --- /dev/null +++ b/cmd/rpcdaemon/README.md @@ -0,0 +1,166 @@ +In turbo-geth RPC calls are extracted out of the main binary into a separate daemon. +This daemon can use both local or remote DBs. That means, that this RPC daemon +doesn't have to be running on the same machine as the main turbo-geth binary or +it can run from a snapshot of a database for read-only calls. [Docs](./cmd/rpcdaemon/Readme.md) + +### Get started +**For local DB** + +``` +> make rpcdaemon +> ./build/bin/rpcdaemon --chaindata ~/Library/TurboGeth/tg/chaindata --http.api=eth,debug,net +``` +**For remote DB** + +Run turbo-geth in one terminal window + +``` +> ./build/bin/tg --private.api.addr=localhost:9090 +``` + +Run RPC daemon +``` +> ./build/bin/rpcdaemon --private.api.addr=localhost:9090 +``` + +### Test + +Try `eth_blockNumber` call. In another console/tab, use `curl` to make RPC call: + +``` +curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber", "params": [], "id":1}' localhost:8545 +``` + +It should return something like this (depending on how far your turbo-geth node has synced): + +``` +{"jsonrpc":"2.0","id":1,"result":823909} +``` + +### For Developers + +**Code generation**: `go.mod` stores right version of generators, use `mage grpc` to install it and generate code. + +`protoc` version not managed but recommended version is 3.x, [install instruction](https://grpc.io/docs/protoc-installation/) + +### RPC Implementation Status + +eth_getBalance +eth_getCode +eth_getTransactionCount +eth_getStorageAt +eth_call +When requests are made that act on the state of ethereum, the last default block parameter determines the height of the block. + +The following options are possible for the defaultBlock parameter: + +HEX String - an integer block number +String "earliest" for the earliest/genesis block +String "latest" - for the latest mined block +String "pending" - for the pending state/transactions +Curl Examples Explained +The curl options below might return a response where the node complains about the content type, this is because the --data option sets the content type to application/x-www-form-urlencoded . If your node does complain, manually set the header by placing -H “Content-Type: application/json†at the start of the call. + +The examples also do not include the URL/IP & port combination which must be the last argument given to curl e.x. 127.0.0.1:8545 + +| Command | Available | Notes | +| --------------------------------------------- | --------- | ------------------------------------------------- | +| _**------------ Web3 ------------**_ | | +| web3_clientVersion | Y | +| web3_sha3 | Y | +| | | +| _**------------ Net ------------**_ | | +| net_listening | - | +| net_peerCount\* | Y | Returns a count of 25 as work continues on Sentry | +| net_version | Y | +| | | +| _**------------ System ------------**_ | | +| eth_blockNumber | Y | +| eth_chainID | - | +| eth_protocolVersion | - | +| eth_syncing | Y | +| eth_estimateGas | Y | +| eth_gasPrice | - | +| | | +| _**------------ Blocks/Uncles ------------**_ | | +| eth_getBlockByHash | Y | +| eth_getBlockByNumber | Y | +| eth_getBlockTransactionCountByHash | Y | +| eth_getBlockTransactionCountByNumber | Y | +| eth_getUncleByBlockHashAndIndex | Y | +| eth_getUncleByBlockNumberAndIndex | Y | +| eth_getUncleCountByBlockHash | Y | +| eth_getUncleCountByBlockNumber | Y | +| | | +| _**------------ Transactions ------------**_ | | +| eth_getTransactionByHash | Y | +| eth_getTransactionByBlockHashAndIndex | Y | +| eth_getTransactionByBlockNumberAndIndex | Y | +| eth_getTransactionReceipt | Y | +| eth_getLogs | Y | +| | | +| _**------------ State ------------**_ | | +| eth_getBalance | Y | +| eth_getCode | Y | +| eth_getStorageAt | Y | +| | | +| _**------------ Filters ------------**_ | | +| eth_newFilter | - | +| eth_newBlockFilter | - | +| eth_newPendingTransactionFilter | - | +| eth_getFilterChanges | - | +| eth_getFilterLogs | - | +| eth_uninstallFilter | - | +| | | +| _**------------ Accounts ------------**_ | | +| eth_accounts | | +| eth_call | Y | +| eth_getTransactionCount | Y | +| eth_sendRawTransaction | Y | +| eth_sendTransaction | - | +| eth_sign | - | +| eth_signTransaction | - | +| eth_signTypedData | - | +| | | +| _**------------ ????? ------------**_ | | +| eth_getProof | - | +| | | +| _**------------ Mining ------------**_ | | +| eth_mining | - | +| eth_coinbase | Y | +| eth_hashrate | - | +| eth_submitHashrate | - | +| eth_getWork | - | +| eth_submitWork | - | +| | | +| _**------------ Debug ------------**_ | | +| debug_accountRange | Y | +| debug_getModifiedAccountsByNumber | Y | +| debug_getModifiedAccountsByHash | Y | +| debug_storageRangeAt | Y | +| debug_traceTransaction | Y | +| | | +| _\*\*------------ trace_ ------------\*\*\_ | | +| trace_filter | Y | +| | | +| _\*\*------------ Retired ------------\*\*\_ | | +| eth_getCompilers | N | +| eth_compileLLL | N | +| eth_compileSolidity | N | +| eth_compileSerpent | N | +| | | +| db_putString | N | +| db_getString | N | +| db_putHex | N | +| db_getHex | N | +| | | +| shh_post | N | +| shh_version | N | +| shh_newIdentity | N | +| shh_hasIdentity | N | +| shh_newGroup | N | +| shh_addToGroup | N | +| shh_newFilter | N | +| shh_uninstallFilter | N | +| shh_getFilterChanges | N | +| shh_getMessages | N | diff --git a/cmd/rpcdaemon/Readme.md b/cmd/rpcdaemon/Readme.md deleted file mode 100644 index 6d3c6976fd..0000000000 --- a/cmd/rpcdaemon/Readme.md +++ /dev/null @@ -1,41 +0,0 @@ -In turbo-geth RPC calls are extracted out of the main binary into a separate daemon. -This daemon can use both local or remote DBs. That means, that this RPC daemon -doesn't have to be running on the same machine as the main turbo-geth binary or -it can run from a snapshot of a database for read-only calls. [Docs](./cmd/rpcdaemon/Readme.md) - -### Get started -**For local DB** - -``` -> make rpcdaemon -> ./build/bin/rpcdaemon --chaindata ~/Library/TurboGeth/tg/chaindata --http.api=eth,debug,net -``` -**For remote DB** - -Run turbo-geth in one terminal window - -``` -> ./build/bin/tg --private.api.addr=localhost:9090 -``` - -Run RPC daemon -``` -> ./build/bin/rpcdaemon --private.api.addr=localhost:9090 -``` - -### Test - -Try `eth_blockNumber` call. In another console/tab, use `curl` to make RPC call: -```` -curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber", "params": [], "id":1}' localhost:8545 -```` -It should return something like this (depending on how far your turbo-geth node has synced): -```` -{"jsonrpc":"2.0","id":1,"result":823909} -```` - -### For Developers - -**Code generation**: `go.mod` stores right version of generators, use `mage grpc` to install it and generate code. - -`protoc` version not managed but recommended version is 3.*, [install instruction](https://grpc.io/docs/protoc-installation/) \ No newline at end of file diff --git a/cmd/rpcdaemon/commands/daemon.go b/cmd/rpcdaemon/commands/daemon.go index 782c73821b..bf1561f1a3 100644 --- a/cmd/rpcdaemon/commands/daemon.go +++ b/cmd/rpcdaemon/commands/daemon.go @@ -63,6 +63,7 @@ func (api *APIImpl) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx return response, err } +// GetHeaderByNumber returns a block's header by number func (api *APIImpl) GetHeaderByNumber(_ context.Context, number rpc.BlockNumber) (*types.Header, error) { header := rawdb.ReadHeaderByNumber(api.dbReader, uint64(number.Int64())) if header == nil { @@ -72,6 +73,7 @@ func (api *APIImpl) GetHeaderByNumber(_ context.Context, number rpc.BlockNumber) return header, nil } +// GetHeaderByHash returns a block's header by hash func (api *APIImpl) GetHeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { header := rawdb.ReadHeaderByHash(api.dbReader, hash) if header == nil { diff --git a/cmd/rpcdaemon/commands/eth_api.go b/cmd/rpcdaemon/commands/eth_api.go index 48b9c4bde0..86a410e36c 100644 --- a/cmd/rpcdaemon/commands/eth_api.go +++ b/cmd/rpcdaemon/commands/eth_api.go @@ -40,6 +40,11 @@ type EthAPI interface { GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, txIndex hexutil.Uint) (*RPCTransaction, error) GetStorageAt(ctx context.Context, address common.Address, index string, blockNrOrHash rpc.BlockNumberOrHash) (string, error) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) + GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) + GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) + GetUncleByBlockHashAndIndex(ctx context.Context, hash common.Hash, index hexutil.Uint) (map[string]interface{}, error) + GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint + GetUncleCountByBlockHash(ctx context.Context, hash common.Hash) *hexutil.Uint } // APIImpl is implementation of the EthAPI interface based on remote Db access @@ -211,30 +216,27 @@ func (api *APIImpl) GetTransactionByBlockNumberAndIndex(ctx context.Context, blo return newRPCTransaction(txs[txIndex], block.Hash(), block.NumberU64(), uint64(txIndex)), nil } +// GetStorageAt returns a 32-byte long, zero-left-padded value at storage location 'index' of address 'address'. Returns '0x' if no value func (api *APIImpl) GetStorageAt(ctx context.Context, address common.Address, index string, blockNrOrHash rpc.BlockNumberOrHash) (string, error) { + var empty []byte + blockNumber, _, err := rpchelper.GetBlockNumber(blockNrOrHash, api.dbReader) if err != nil { - return "", err + return hexutil.Encode(common.LeftPadBytes(empty[:], 32)), err } reader := adapter.NewStateReader(api.db, blockNumber) acc, err := reader.ReadAccountData(address) - if err != nil { - return "", err - } - - if acc == nil { - return "", fmt.Errorf("account not found") + if acc == nil || err != nil { + return hexutil.Encode(common.LeftPadBytes(empty[:], 32)), err } location := common.HexToHash(index) - res, err := reader.ReadAccountStorage(address, acc.Incarnation, &location) if err != nil { - return "", err + res = empty } - - return hexutil.Encode(res), nil + return hexutil.Encode(common.LeftPadBytes(res[:], 32)), err } // GetCode returns the code stored at the given address in the state for the given block number. @@ -255,3 +257,18 @@ func (api *APIImpl) GetCode(ctx context.Context, address common.Address, blockNr } return res, nil } + +// GetTransactionCount returns the number of transactions the given address has sent for the given block number +func (api *APIImpl) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) { + blockNumber, _, err := rpchelper.GetBlockNumber(blockNrOrHash, api.dbReader) + if err != nil { + return nil, err + } + nonce := hexutil.Uint64(0) + reader := adapter.NewStateReader(api.db, blockNumber) + acc, err := reader.ReadAccountData(address) + if acc == nil || err != nil { + return &nonce, err + } + return (*hexutil.Uint64)(&acc.Nonce), err +} diff --git a/cmd/rpcdaemon/commands/get_uncles.go b/cmd/rpcdaemon/commands/get_uncles.go new file mode 100644 index 0000000000..4cc5697da3 --- /dev/null +++ b/cmd/rpcdaemon/commands/get_uncles.go @@ -0,0 +1,83 @@ +package commands + +import ( + "context" + "fmt" + + "github.com/ledgerwatch/turbo-geth/common" + "github.com/ledgerwatch/turbo-geth/common/hexutil" + "github.com/ledgerwatch/turbo-geth/core/rawdb" + "github.com/ledgerwatch/turbo-geth/core/types" + "github.com/ledgerwatch/turbo-geth/log" + "github.com/ledgerwatch/turbo-geth/rpc" + "github.com/ledgerwatch/turbo-geth/turbo/adapter/ethapi" +) + +// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true +// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. +func (api *APIImpl) GetUncleByBlockNumberAndIndex(ctx context.Context, number rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) { + blockNum, err := getBlockNumber(number, api.dbReader) + if err != nil { + return nil, err + } + block := rawdb.ReadBlockByNumber(api.dbReader, blockNum) + if block == nil { + return nil, fmt.Errorf("block not found: %d", blockNum) + } + hash := block.Hash() + additionalFields := make(map[string]interface{}) + additionalFields["totalDifficulty"] = (*hexutil.Big)(rawdb.ReadTd(api.dbReader, block.Hash(), blockNum)) + + uncles := block.Uncles() + if index >= hexutil.Uint(len(uncles)) { + log.Debug("Requested uncle not found", "number", block.Number(), "hash", hash, "index", index) + return nil, nil + } + uncle := types.NewBlockWithHeader(uncles[index]) + return ethapi.RPCMarshalBlock(uncle, false, false, additionalFields) +} + +// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index. When fullTx is true +// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. +func (api *APIImpl) GetUncleByBlockHashAndIndex(ctx context.Context, hash common.Hash, index hexutil.Uint) (map[string]interface{}, error) { + block := rawdb.ReadBlockByHash(api.dbReader, hash) + if block == nil { + return nil, fmt.Errorf("block not found: %x", hash) + } + number := block.NumberU64() + additionalFields := make(map[string]interface{}) + additionalFields["totalDifficulty"] = (*hexutil.Big)(rawdb.ReadTd(api.dbReader, hash, number)) + + uncles := block.Uncles() + if index >= hexutil.Uint(len(uncles)) { + log.Debug("Requested uncle not found", "number", block.Number(), "hash", hash, "index", index) + return nil, nil + } + uncle := types.NewBlockWithHeader(uncles[index]) + + return ethapi.RPCMarshalBlock(uncle, false, false, additionalFields) +} + +// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number +func (api *APIImpl) GetUncleCountByBlockNumber(ctx context.Context, number rpc.BlockNumber) *hexutil.Uint { + n := hexutil.Uint(0) + blockNum, err := getBlockNumber(number, api.dbReader) + if err != nil { + return &n + } + block := rawdb.ReadBlockByNumber(api.dbReader, blockNum) + if block != nil { + n = hexutil.Uint(len(block.Uncles())) + } + return &n +} + +// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash +func (api *APIImpl) GetUncleCountByBlockHash(ctx context.Context, hash common.Hash) *hexutil.Uint { + n := hexutil.Uint(0) + block := rawdb.ReadBlockByHash(api.dbReader, hash) + if block != nil { + n = hexutil.Uint(len(block.Uncles())) + } + return &n +} -- GitLab