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

Commit a7c24ed

Browse files
authored
Merge pull request #139 from ethereumjs/admin-rpc
Add admin_* RPC
2 parents d1906bd + 1c86422 commit a7c24ed

File tree

8 files changed

+179
-6
lines changed

8 files changed

+179
-6
lines changed

lib/net/server/rlpxserver.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class RlpxServer extends Server {
4242

4343
public rlpx: Devp2pRLPx | null = null
4444
public dpt: Devp2pDPT | null = null
45+
public ip: string = '::'
4546

4647
/**
4748
* Create new DevP2P/RLPx server
@@ -59,6 +60,8 @@ export class RlpxServer extends Server {
5960
super(options)
6061
options = { ...defaultOptions, ...options }
6162

63+
// TODO: get the external ip from the upnp service
64+
this.ip = '::'
6265
this.port = options.port
6366
this.key = options.key
6467
this.clientFilter = options.clientFilter
@@ -83,6 +86,30 @@ export class RlpxServer extends Server {
8386
}
8487
}
8588

89+
/**
90+
* Return Rlpx info
91+
*/
92+
getRlpxInfo() {
93+
// TODO: return undefined? note that this.rlpx might be undefined if called before initRlpx
94+
if (!this.rlpx) {
95+
return {
96+
enode: undefined,
97+
id: undefined,
98+
ip: this.ip,
99+
listenAddr: `[${this.ip}]:${this.port}`,
100+
ports: { discovery: this.port, listener: this.port },
101+
}
102+
}
103+
const id = this.rlpx._id.toString('hex')
104+
return {
105+
enode: `enode://${id}@[${this.ip}]:${this.port}`,
106+
id: id,
107+
ip: this.ip,
108+
listenAddr: `[${this.ip}]:${this.port}`,
109+
ports: { discovery: this.port, listener: this.port },
110+
}
111+
}
112+
86113
/**
87114
* Start Devp2p/RLPx server. Returns a promise that resolves once server has been started.
88115
* @return Resolves with true if server successfully started
@@ -239,7 +266,7 @@ export class RlpxServer extends Server {
239266
this.rlpx.on('listening', () => {
240267
this.emit('listening', {
241268
transport: this.name,
242-
url: `enode://${(this.rlpx as Devp2pRLPx)._id.toString('hex')}@[::]:${this.port}`,
269+
url: this.getRlpxInfo().enode,
243270
})
244271
})
245272

lib/rpc/modules/admin.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { bufferToHex } from 'ethereumjs-util'
2+
import { Chain } from '../../blockchain'
3+
import { EthProtocol, Protocol } from '../../net/protocol'
4+
import Node from '../../node'
5+
import { Service } from '../../service'
6+
import { getClientVersion } from '../../util'
7+
import { middleware } from '../validation'
8+
9+
/**
10+
* admin_* RPC module
11+
* @memberof module:rpc/modules
12+
*/
13+
export class Admin {
14+
readonly _chain: Chain
15+
readonly _node: Node
16+
readonly _ethProtocol: EthProtocol
17+
18+
/**
19+
* Create admin_* RPC module
20+
* @param {Node} Node to which the module binds
21+
*/
22+
constructor(node: Node) {
23+
const service = node.services.find((s: Service) => s.name === 'eth')
24+
this._chain = service.chain
25+
this._node = node
26+
this._ethProtocol = service.protocols.find((p: Protocol) => p.name === 'eth')
27+
28+
this.nodeInfo = middleware(this.nodeInfo.bind(this), 0, [])
29+
}
30+
31+
/**
32+
* Returns information about the currently running node.
33+
* see for reference: https://geth.ethereum.org/docs/rpc/ns-admin#admin_nodeinfo
34+
* @param {*} [params] An empty array
35+
* @param {*} [cb] A function with an error object as the first argument and the result as the second
36+
*/
37+
async nodeInfo(params: any, cb: Function) {
38+
const rlpxInfo = this._node.server('rlpx').getRlpxInfo()
39+
const { enode, id, ip, listenAddr, ports } = rlpxInfo
40+
const { discovery, listener } = ports
41+
const clientName = getClientVersion()
42+
43+
// TODO version not present in reference..
44+
// const ethVersion = Math.max.apply(Math, this._ethProtocol.versions)
45+
const latestHeader = (this._chain as any)._headers.latest
46+
const difficulty = latestHeader?.difficulty || 0 // should be number
47+
const genesis = bufferToHex(this._chain.genesis.hash)
48+
const head = bufferToHex(latestHeader?.mixHash || null)
49+
const network = this._chain.networkId
50+
51+
const nodeInfo = {
52+
name: clientName,
53+
enode,
54+
id,
55+
ip,
56+
listenAddr,
57+
ports: {
58+
discovery,
59+
listener,
60+
},
61+
protocols: {
62+
eth: {
63+
difficulty,
64+
genesis,
65+
head,
66+
network,
67+
},
68+
},
69+
}
70+
return cb(null, nodeInfo)
71+
}
72+
}

lib/rpc/modules/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export const list = ['Eth', 'Web3', 'Net']
1+
export const list = ['Eth', 'Web3', 'Net', 'Admin']
2+
23
export * from './eth'
34
export * from './web3'
45
export * from './net'
6+
export * from './admin'

lib/rpc/modules/web3.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { middleware, validators } from '../validation'
22
import { addHexPrefix, keccak, toBuffer } from 'ethereumjs-util'
3-
import { platform } from 'os'
3+
import { getClientVersion } from '../../util'
44

55
/**
66
* web3_* RPC module
@@ -29,9 +29,7 @@ export class Web3 {
2929
* client version as the second argument
3030
*/
3131
clientVersion(_params = [], cb: (err: null, version: string) => void) {
32-
const packageVersion = require('../../../package.json').version
33-
const { version } = process
34-
const ethJsVersion = `EthereumJS/${packageVersion}/${platform()}/node${version.substring(1)}`
32+
const ethJsVersion = getClientVersion()
3533
cb(null, ethJsVersion)
3634
}
3735

lib/util/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
/**
22
* @module util
33
*/
4+
import { platform } from 'os'
5+
import { version as packageVersion } from '../../package.json'
46

57
export * from './parse'
68

79
export function short(buffer: Buffer): string {
810
return buffer.toString('hex').slice(0, 8) + '...'
911
}
12+
13+
export function getClientVersion() {
14+
const { version } = process
15+
return `EthereumJS/${packageVersion}/${platform()}/node${version.substring(1)}`
16+
}

test/net/server/rlpxserver.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,42 @@ tape('[RlpxServer]', (t) => {
7272
t.end()
7373
})
7474

75+
t.test('should return rlpx server info', async (t) => {
76+
const mockId = '123'
77+
const server = new RlpxServer({
78+
bootnodes: '10.0.0.1:1234,10.0.0.2:1234',
79+
})
80+
server.initDpt = td.func()
81+
server.initRlpx = td.func()
82+
server.dpt = td.object()
83+
server.rlpx = td.object({
84+
_id: mockId,
85+
destroy: td.func(),
86+
})
87+
td.when(
88+
server.dpt?.bootstrap({ address: '10.0.0.1', udpPort: '1234', tcpPort: '1234' })
89+
).thenResolve()
90+
td.when(
91+
server.dpt?.bootstrap({ address: '10.0.0.2', udpPort: '1234', tcpPort: '1234' })
92+
).thenReject('err0')
93+
server.on('error', (err) => t.equals(err, 'err0', 'got error'))
94+
await server.start()
95+
const nodeInfo = server.getRlpxInfo()
96+
t.deepEqual(
97+
nodeInfo,
98+
{
99+
enode: `enode://${mockId}@[::]:30303`,
100+
id: mockId,
101+
ip: '::',
102+
listenAddr: '[::]:30303',
103+
ports: { discovery: 30303, listener: 30303 },
104+
},
105+
'get nodeInfo'
106+
)
107+
await server.stop()
108+
t.end()
109+
})
110+
75111
t.test('should handle errors', (t) => {
76112
t.plan(3)
77113
let count = 0

test/rpc/admin/nodeInfo.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import tape from 'tape'
2+
import { startRPC, createManager, createNode, params, baseRequest } from '../helpers'
3+
4+
const method = 'admin_nodeInfo'
5+
6+
tape(method, (t) => {
7+
const manager = createManager(createNode({ opened: true }))
8+
const server = startRPC(manager.getMethods())
9+
10+
const req = params(method, [])
11+
12+
const expectRes = (res: any) => {
13+
const { result } = res.body
14+
if (result) {
15+
t.pass('admin_nodeInfo returns a value')
16+
} else {
17+
throw new Error('no return value')
18+
}
19+
}
20+
baseRequest(t, server, req, 200, expectRes)
21+
})

test/rpc/helpers.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { RPCManager as Manager } from '../../lib/rpc'
66
import * as Logger from '../../lib/logging'
77
import { blockChain } from './blockChainStub'
88
import { Chain } from '../../lib/blockchain/chain'
9+
import { RlpxServer } from '../../lib/net/server/rlpxserver'
910
import Blockchain from '@ethereumjs/blockchain'
1011

1112
const config: any = { loglevel: 'error' }
@@ -36,6 +37,11 @@ export function createNode(nodeConfig?: any) {
3637
ethProtocolVersions: [63],
3738
}
3839
const trueNodeConfig = { ...defaultNodeConfig, ...nodeConfig }
40+
const servers = [
41+
new RlpxServer({
42+
bootnodes: '10.0.0.1:1234,10.0.0.2:1234',
43+
}),
44+
]
3945
return {
4046
services: [
4147
{
@@ -50,8 +56,12 @@ export function createNode(nodeConfig?: any) {
5056
],
5157
},
5258
],
59+
servers,
5360
common: trueNodeConfig.commonChain,
5461
opened: trueNodeConfig.opened,
62+
server: (name: string) => {
63+
return servers.find((s) => s.name === name)
64+
},
5565
}
5666
}
5767

0 commit comments

Comments
 (0)