Skip to content
This repository has been archived by the owner on Dec 10, 2020. It is now read-only.

fix(rpc): Handle rpc request with empty params #122

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,105 @@ DEBUG=devp2p:rlpx,devp2p:eth,-babel [CLIENT_START_COMMAND]

See also this [diagram](./client_diagram.png) with an overview of the client structure together with the initialization and message flow.

## JSON-RPC

**Overview**

You can expose a [JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) interface along a client run with:

```shell
ethereumjs --rpc
```

To run just the server without syncing:

```shell
ethereumjs --rpc --maxPeers=0
```

Currently only a small subset of `RPC` methods are implemented.(*) You can have a look at the
[./lib/rpc/modules/](./lib/rpc/modules) source folder or the tracking issue
[#17](https://github.com/ethereumjs/ethereumjs-client/issues/17) for an overview.

(*) Side note: implementing RPC methods is actually an extremely thankful task for a first-time
contribution on the project *hint* *hint*. 😄

**API Examples**

You can use `cURL` to request data from an API endpoint. Here is a simple example for
[web3_clientVersion](https://github.com/ethereum/wiki/wiki/JSON-RPC#web3_clientversion):

```shell
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","id":1,"method":"web3_clientVersion", "params": []}' http://localhost:8545
```

Note that `"params": []` can also be omitted in this case.

Or - somewhat more convenient and with formatted output - with a tool like [httpie](http://httpie.org/):

```shell
http POST http://localhost:8545 jsonrpc=2.0 id=1 method=web3_clientVersion params:=[]
```

Note the `:=` separator for the `params` parameter to
[indicate](https://httpie.org/docs#non-string-json-fields) raw `JSON` as an input.

This will give you an output like the following:

```json
{
"id": "1",
"jsonrpc": "2.0",
"result": "EthereumJS/0.0.5/darwin/node12.15.0"
}
```

Here an example for a call on an endpoint with the need for parameters. The following call uses
the [eth_getBlockByNumer](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber) endpoint
to request data for block number 436 (you can use an tool like
[RapidTables](https://www.rapidtables.com/convert/number/decimal-to-hex.html) for conversion to `hex`):


```shell
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x1b4", true],"id":1}' http://127.0.0.1:8545
```

Same with `httpie`:

```shell
http POST http://localhost:8545 jsonrpc=2.0 id=1 method=eth_getBlockByNumber params:='["0x1b4",true]'
```

Output:

```json
{
"id": "1",
"jsonrpc": "2.0",
"result": {
"header": {
"bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0xbb7b8287f3f0a933474a79eae42cbca977791171",
"difficulty": "0x04ea3f27bc",
"extraData": "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32",
"gasLimit": "0x1388",
"gasUsed": "0x",
"mixHash": "0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843",
"nonce": "0x689056015818adbe",
"number": "0x01b4",
"parentHash": "0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54",
"receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"stateRoot": "0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d",
"timestamp": "0x55ba467c",
"transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
},
"transactions": [],
"uncleHeaders": []
}
}
```

## EXAMPLES

### Example 1: Light sync
Expand Down Expand Up @@ -126,6 +225,8 @@ ethereumjs.run({ network: 'rinkeby', syncmode: 'light', bootnodes: '/ip4/127.0.0

That's it! Now, you should start seeing headers being downloaded to the local storage of your browser. Since IndexDB is being used, even if you close and re-open the browser window, the headers you'll already downloaded will be saved.

![EthereumJS Client Libp2p Browser Syncing](./browser_sync.png?raw=true)

## Design

**Goals**
Expand Down
Binary file added browser_sync.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions lib/rpc/modules/eth.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class Eth {
this.getBlockByHash = middleware(this.getBlockByHash.bind(this), 2,
[[validators.hex, validators.blockHash], [validators.bool]])

this.getBlockTransactionCountByHash = middleware(this.getBlockTransactionCountByHash.bind(this), 1,
[[validators.hex, validators.blockHash]])

this.protocolVersion = middleware(this.protocolVersion.bind(this), 0, [])
}

Expand Down Expand Up @@ -76,6 +79,26 @@ class Eth {
}
}

/**
* Returns the transaction count for a block given by the block hash
* @param {Array<string>} [params] An array of one parameter: A block hash
* @param {Function} [cb] A function with an error object as the first argument and an actual value
* as the second argument
* @return {Promise}
*/
async getBlockTransactionCountByHash (params, cb) {
let [blockHash] = params

try {
let block = await this._chain.getBlock(toBuffer(blockHash))

const json = block.toJSON(true)
cb(null, `0x${json.transactions.length.toString(16)}`)
} catch (err) {
cb(err)
}
}

/**
* Returns the current ethereum protocol version
* @param {Array<*>} [params] An empty array
Expand Down
2 changes: 1 addition & 1 deletion lib/rpc/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = {
* @param {Function[]} validators array of validator
*/
middleware (method, requiredParamsCount, validators) {
return function (params, cb) {
return function (params = [], cb) {
if (params.length < requiredParamsCount) {
const err = {
code: INVALID_PARAMS,
Expand Down
1 change: 1 addition & 0 deletions lib/service/ethereumservice.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class EthereumService extends Service {

this.flow = new FlowControl(options)
this.chain = options.chain || new Chain(options)
this.common = options.common
this.minPeers = options.minPeers
this.interval = options.interval
this.timeout = options.timeout
Expand Down
1 change: 1 addition & 0 deletions lib/service/fastethereumservice.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class FastEthereumService extends EthereumService {
logger: this.logger,
pool: this.pool,
chain: this.chain,
common: this.common,
minPeers: this.minPeers,
interval: this.interval
})
Expand Down
1 change: 1 addition & 0 deletions lib/service/lightethereumservice.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class LightEthereumService extends EthereumService {
logger: this.logger,
pool: this.pool,
chain: this.chain,
common: this.common,
minPeers: this.minPeers,
flow: this.flow,
interval: this.interval
Expand Down
1 change: 1 addition & 0 deletions lib/sync/fastsync.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class FastSynchronizer extends Synchronizer {
this.blockFetcher = new BlockFetcher({
pool: this.pool,
chain: this.chain,
common: this.common,
logger: this.logger,
interval: this.interval,
first,
Expand Down
2 changes: 1 addition & 1 deletion lib/sync/fetcher/blockfetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class BlockFetcher extends Fetcher {
let { first, count } = task
const headers = await peer.eth.getBlockHeaders({ block: first, max: count })
const bodies = await peer.eth.getBlockBodies(headers.map(h => h.hash()))
const blocks = bodies.map((body, i) => new Block([headers[i]].concat(body)))
const blocks = bodies.map((body, i) => new Block([headers[i]].concat(body), { common: this.common }))
return { blocks }
}

Expand Down
4 changes: 4 additions & 0 deletions lib/sync/fetcher/fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

const { Readable, Writable } = require('stream')
const Heap = require('qheap')
const Common = require('ethereumjs-common').default
const { defaultLogger } = require('../../logging')

const defaultOptions = {
common: new Common('mainnet', 'chainstart'),
logger: defaultLogger,
timeout: 5000,
interval: 1000,
Expand All @@ -24,6 +26,7 @@ class Fetcher extends Readable {
/**
* Create new fetcher
* @param {Object} options constructor parameters
* @param {Common} options.common common chain config
* @param {PeerPool} options.pool peer pool
* @param {number} [options.timeout] fetch task timeout
* @param {number} [options.banTime] how long to ban misbehaving peers
Expand All @@ -36,6 +39,7 @@ class Fetcher extends Readable {
super({ ...options, objectMode: true })
options = { ...defaultOptions, ...options }

this.common = options.common
this.pool = options.pool
this.logger = options.logger
this.timeout = options.timeout
Expand Down
1 change: 1 addition & 0 deletions lib/sync/lightsync.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class LightSynchronizer extends Synchronizer {
this.headerFetcher = new HeaderFetcher({
pool: this.pool,
chain: this.chain,
common: this.common,
flow: this.flow,
logger: this.logger,
interval: this.interval,
Expand Down
4 changes: 4 additions & 0 deletions lib/sync/sync.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use strict'

const Common = require('ethereumjs-common').default
const EventEmitter = require('events')
const { defaultLogger } = require('../logging')

const defaultOptions = {
common: new Common('mainnet', 'chainstart'),
logger: defaultLogger,
interval: 1000,
minPeers: 3
Expand All @@ -19,6 +21,7 @@ class Synchronizer extends EventEmitter {
* @param {Object} options constructor parameters
* @param {PeerPool} options.pool peer pool
* @param {Chain} options.chain blockchain
* @param {Common} options.common common chain config
* @param {FlowControl} options.flow flow control manager
* @param {number} [options.minPeers=3] number of peers needed before syncing
* @param {number} [options.interval] refresh interval
Expand All @@ -31,6 +34,7 @@ class Synchronizer extends EventEmitter {
this.logger = options.logger
this.pool = options.pool
this.chain = options.chain
this.common = options.common
this.flow = options.flow
this.minPeers = options.minPeers
this.interval = options.interval
Expand Down
Loading