diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md index 07fe740e3913e93022095c26e62d594bf6e80015..4df5c86f915cff3e2230a4de1901a2cc935a159c 100644 --- a/cmd/rpcdaemon/README.md +++ b/cmd/rpcdaemon/README.md @@ -87,7 +87,7 @@ The following table shows the current implementation status of turbo-geth's RPC | eth_chainID | Yes | | | eth_protocolVersion | Yes | | | eth_syncing | Yes | | -| eth_gasPrice | - | | +| eth_gasPrice | Yes | | | | | | | eth_getBlockByHash | Yes | | | eth_getBlockByNumber | Yes | | @@ -260,6 +260,52 @@ On the RPC daemon machine, these three files need to be placed: `CA-cert.pem`, ` When running turbo-geth instance in the Google Cloud, for example, you need to specify the **Internal IP** in the `--private.api.addr` option. And, you will need to open the firewall on the port you are using, to that connection to the turbo-geth instances can be made. +## Ethstats + +This version of the RPC daemon is compatible with [ethstats-client](https://github.com/goerli/ethstats-client). + +To run ethstats, run the RPC daemon remotely and open some of the APIs. + +`./build/bin/rpcdaemon --private.api.addr=localhost:9090 --http.api=net,eth,web3` + +Then update your `app.json` for ethstats-client like that: + +```json +[ + { + "name" : "ethstats", + "script" : "app.js", + "log_date_format" : "YYYY-MM-DD HH:mm Z", + "merge_logs" : false, + "watch" : false, + "max_restarts" : 10, + "exec_interpreter" : "node", + "exec_mode" : "fork_mode", + "env": + { + "NODE_ENV" : "production", + "RPC_HOST" : "localhost", + "RPC_PORT" : "8545", + "LISTENING_PORT" : "30303", + "INSTANCE_NAME" : "turbo-geth node", + "CONTACT_DETAILS" : <your twitter handle>, + "WS_SERVER" : "wss://ethstats.net/api", + "WS_SECRET" : <put your secret key there>, + "VERBOSITY" : 2 + } + } +] +``` + +Run ethstats-client through pm2 as usual. + +You will see these warnings in the RPC daemon output, but they are expected + +``` +WARN [11-05|09:03:47.911] Served conn=127.0.0.1:59753 method=eth_newBlockFilter reqid=5 t="21.194µs" err="the method eth_newBlockFilter does not exist/is not available" +WARN [11-05|09:03:47.911] Served conn=127.0.0.1:59754 method=eth_newPendingTransactionFilter reqid=6 t="9.053µs" err="the method eth_newPendingTransactionFilter does not exist/is not available" +``` + ## For Developers ### Code generation diff --git a/cmd/rpcdaemon/cli/config.go b/cmd/rpcdaemon/cli/config.go index 6f5d138d9466641c9d840fc4d4a9cee65327be94..055ad6ebb67163f0a776f51a8008d159afa905c7 100644 --- a/cmd/rpcdaemon/cli/config.go +++ b/cmd/rpcdaemon/cli/config.go @@ -3,10 +3,11 @@ package cli import ( "context" "fmt" - "github.com/ledgerwatch/turbo-geth/turbo/torrent" "net/http" "time" + "github.com/ledgerwatch/turbo-geth/turbo/torrent" + "github.com/ledgerwatch/turbo-geth/cmd/utils" "github.com/ledgerwatch/turbo-geth/ethdb" "github.com/ledgerwatch/turbo-geth/internal/debug" diff --git a/cmd/rpcdaemon/commands/eth_api.go b/cmd/rpcdaemon/commands/eth_api.go index 3b5b6e667004e82881e536de66b372c235d4a146..414a266934e99d62e3fbd28c35f6d1318cb0ea8f 100644 --- a/cmd/rpcdaemon/commands/eth_api.go +++ b/cmd/rpcdaemon/commands/eth_api.go @@ -19,7 +19,7 @@ import ( type EthAPI interface { // Block related (proposed file: ./eth_blocks.go) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) - GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) + GetBlockByHash(ctx context.Context, hash rpc.BlockNumberOrHash, fullTx bool) (map[string]interface{}, error) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error) diff --git a/cmd/rpcdaemon/commands/eth_block.go b/cmd/rpcdaemon/commands/eth_block.go index b02bb834eda0196bc3383853abb6b08342b961b6..ec0814bf5d32afe60ee00af1b2215ff805a1839d 100644 --- a/cmd/rpcdaemon/commands/eth_block.go +++ b/cmd/rpcdaemon/commands/eth_block.go @@ -51,7 +51,18 @@ func (api *APIImpl) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber } // GetBlockByHash implements eth_getBlockByHash. Returns information about a block given the block's hash. -func (api *APIImpl) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) { +func (api *APIImpl) GetBlockByHash(ctx context.Context, numberOrHash rpc.BlockNumberOrHash, fullTx bool) (map[string]interface{}, error) { + if numberOrHash.BlockHash == nil { + // some web3.js based apps (like ethstats client) for some reason call + // eth_getBlockByHash with a block number as a parameter + // so no matter how weird that is, we would love to support that. + if numberOrHash.BlockNumber != nil { + return api.GetBlockByNumber(ctx, *numberOrHash.BlockNumber, fullTx) + } + return nil, fmt.Errorf("block not found") + } + + hash := *numberOrHash.BlockHash tx, err := api.dbReader.Begin(ctx, ethdb.RO) if err != nil { return nil, err diff --git a/cmd/rpcdaemon/commands/eth_mining.go b/cmd/rpcdaemon/commands/eth_mining.go index 2504aa91472b15144c9aa44d2c7a51c419771dfc..8d58d6ba01d6a4c7a48222872262f33e5d22e176 100644 --- a/cmd/rpcdaemon/commands/eth_mining.go +++ b/cmd/rpcdaemon/commands/eth_mining.go @@ -26,7 +26,9 @@ func (api *APIImpl) Hashrate(_ context.Context) (uint64, error) { // Mining implements eth_mining. Returns true if client is actively mining new blocks. func (api *APIImpl) Mining(_ context.Context) (bool, error) { - return false, fmt.Errorf(NotImplemented, "eth_mining") + // ethstats needs this method, and even though we don't support mining, + // we can easily say that we don't do that. + return false, nil } // GetWork implements eth_getWork. Returns the hash of the current block, the seedHash, and the boundary condition to be met ('target'). diff --git a/cmd/rpcdaemon/commands/eth_system.go b/cmd/rpcdaemon/commands/eth_system.go index 699db3de27364a9c594772413d8fb00057d30cf3..8f323507a11b58d74590b0dc0c19686f9e955e4d 100644 --- a/cmd/rpcdaemon/commands/eth_system.go +++ b/cmd/rpcdaemon/commands/eth_system.go @@ -5,9 +5,14 @@ import ( "fmt" "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/eth" + "github.com/ledgerwatch/turbo-geth/eth/gasprice" "github.com/ledgerwatch/turbo-geth/eth/stagedsync/stages" "github.com/ledgerwatch/turbo-geth/ethdb" + "github.com/ledgerwatch/turbo-geth/params" + "github.com/ledgerwatch/turbo-geth/rpc" ) // BlockNumber implements eth_blockNumber. Returns the block number of most recent block. @@ -63,8 +68,57 @@ func (api *APIImpl) ProtocolVersion(_ context.Context) (hexutil.Uint, error) { } // GasPrice implements eth_gasPrice. Returns the current price per gas in wei. -func (api *APIImpl) GasPrice(_ context.Context) (*hexutil.Big, error) { - return nil, fmt.Errorf(NotImplemented, "eth_getPrice") - // price, err := eth.SuggestPrice(ctx) - // return (*hexutil.Big)(price), err +func (api *APIImpl) GasPrice(ctx context.Context) (*hexutil.Big, error) { + oracle := gasprice.NewOracle(api, eth.DefaultFullGPOConfig) + price, err := oracle.SuggestPrice(ctx) + return (*hexutil.Big)(price), err +} + +// HeaderByNumber is necessary for gasprice.OracleBackend implementation +func (api *APIImpl) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + tx, err := api.dbReader.Begin(ctx, ethdb.RO) + if err != nil { + return nil, err + } + defer tx.Rollback() + + blockNum, err := getBlockNumber(number, tx) + if err != nil { + return nil, err + } + + header := rawdb.ReadHeaderByNumber(tx, blockNum) + if header == nil { + return nil, fmt.Errorf("header not found: %d", blockNum) + } + return header, nil +} + +// BlockByNumber is necessary for gasprice.OracleBackend implementation +func (api *APIImpl) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + tx, err := api.dbReader.Begin(ctx, ethdb.RO) + if err != nil { + return nil, err + } + defer tx.Rollback() + + blockNum, err := getBlockNumber(number, tx) + if err != nil { + return nil, err + } + + block, err := rawdb.ReadBlockByNumber(tx, blockNum) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block not found: %d", blockNum) + } + return block, nil +} + +// ChainConfig is necessary for gasprice.OracleBackend implementation +func (api *APIImpl) ChainConfig() *params.ChainConfig { + // we just harcode mainnet there for now + return params.MainnetChainConfig } diff --git a/rpc/types.go b/rpc/types.go index 413febbd68a445e9fa98022f6a0cf5965f03a336..f24b248f1fbe0f13e83cdad36c69c30c7de45632 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -97,6 +97,9 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error { case "pending": *bn = PendingBlockNumber return nil + case "null": + *bn = LatestBlockNumber + return nil } // Try to parse it as a number