Skip to content

Commit 2c455a7

Browse files
authored
Merge pull request #1067 from ethereumjs/client-bootstrap
Client: Save node key
2 parents e8099f1 + f1854b4 commit 2c455a7

File tree

8 files changed

+79
-54
lines changed

8 files changed

+79
-54
lines changed

.github/workflows/trie-build.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,6 @@ jobs:
9898
output-file-path: ${{ env.cwd }}/output.txt
9999
# Location of data in gh-pages branch
100100
benchmark-data-dir-path: dev/bench/trie
101-
# Enable alert commit comment
102-
comment-on-alert: true
103101
# GitHub API token to make a commit comment
104102
github-token: ${{ secrets.GITHUB_TOKEN }}
105103
# Push and deploy to GitHub pages branch automatically (if on master)

packages/client/bin/cli.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,14 @@ async function run() {
175175
}
176176

177177
const common = new Common({ chain, hardfork: 'chainstart' })
178+
const datadir = args.datadir ?? Config.DATADIR_DEFAULT
179+
const key = await Config.getClientKey(datadir, common)
178180
const config = new Config({
179181
common,
180182
syncmode: args.syncmode,
181183
lightserv: args.lightserv,
182-
datadir: args.datadir,
184+
datadir,
185+
key,
183186
transports: args.transports,
184187
bootnodes: args.bootnodes ? parseMultiaddrs(args.bootnodes) : undefined,
185188
multiaddrs: args.multiaddrs ? parseMultiaddrs(args.multiaddrs) : undefined,

packages/client/browser/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,23 @@ import { Config } from '../lib/config'
4444
export * from './logging'
4545
import { getLogger } from './logging'
4646

47-
export function createClient(args: any) {
47+
export async function createClient(args: any) {
4848
const logger = getLogger({ loglevel: args.loglevel ?? 'info' })
49+
const datadir = args.datadir ?? Config.DATADIR_DEFAULT
50+
const common = new Common({ chain: args.network ?? 'mainnet' })
51+
const key = await Config.getClientKey(datadir, common)
4952
const config = new Config({
50-
common: new Common({ chain: args.network ?? 'mainnet' }),
51-
servers: [new Libp2pServer({ multiaddrs: [], config: new Config({ logger }), ...args })],
53+
common,
54+
key,
55+
servers: [new Libp2pServer({ multiaddrs: [], config: new Config({ key, logger }), ...args })],
5256
syncmode: args.syncmode ?? 'full',
5357
logger,
5458
})
5559
return new EthereumClient({ config })
5660
}
5761

5862
export async function run(args: any) {
59-
const client = createClient(args)
63+
const client = await createClient(args)
6064
const { logger, chainCommon: common } = client.config
6165
logger.info('Initializing Ethereumjs client...')
6266
logger.info(`Connecting to network: ${common.chainName()}`)

packages/client/lib/config.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import Common from '@ethereumjs/common'
22
import VM from '@ethereumjs/vm'
3+
import { genPrivateKey } from '@ethereumjs/devp2p'
34
import Multiaddr from 'multiaddr'
45
import { getLogger, Logger } from './logging'
56
import { Libp2pServer, RlpxServer } from './net/server'
67
import { parseTransports } from './util'
8+
import type { LevelUp } from 'levelup'
9+
const level = require('level')
710

811
export interface ConfigOptions {
912
/**
@@ -40,6 +43,13 @@ export interface ConfigOptions {
4043
*/
4144
datadir?: string
4245

46+
/**
47+
* Private key for the client.
48+
* Use return value of `await Config.getClientKey(datadir, common)`
49+
* If left blank, a random key will be generated and used.
50+
*/
51+
key?: Buffer
52+
4353
/**
4454
* Network transports ('rlpx' and/or 'libp2p')
4555
*
@@ -172,6 +182,7 @@ export class Config {
172182
public readonly vm?: VM
173183
public readonly lightserv: boolean
174184
public readonly datadir: string
185+
public readonly key: Buffer
175186
public readonly transports: string[]
176187
public readonly bootnodes?: Multiaddr[]
177188
public readonly multiaddrs?: Multiaddr[]
@@ -199,6 +210,7 @@ export class Config {
199210
this.bootnodes = options.bootnodes
200211
this.multiaddrs = options.multiaddrs
201212
this.datadir = options.datadir ?? Config.DATADIR_DEFAULT
213+
this.key = options.key ?? genPrivateKey()
202214
this.rpc = options.rpc ?? Config.RPC_DEFAULT
203215
this.rpcport = options.rpcport ?? Config.RPCPORT_DEFAULT
204216
this.rpcaddr = options.rpcaddr ?? Config.RPCADDR_DEFAULT
@@ -243,25 +255,33 @@ export class Config {
243255
const bootnodes = this.bootnodes ?? this.chainCommon.bootstrapNodes()
244256
const dnsNetworks = options.dnsNetworks ?? this.chainCommon.dnsNetworks()
245257
return new RlpxServer({ config: this, bootnodes, dnsNetworks })
246-
} else {
247-
// t.name === 'libp2p'
258+
} else if (t.name === 'libp2p') {
248259
const multiaddrs = this.multiaddrs
249260
const bootnodes = this.bootnodes
250261
return new Libp2pServer({ config: this, multiaddrs, bootnodes })
262+
} else {
263+
throw new Error(`unknown transport: ${t.name}`)
251264
}
252265
})
253266
}
254267
}
255268

269+
/**
270+
* Returns the network directory for the chain.
271+
*/
272+
getNetworkDirectory(): string {
273+
const networkDirName = this.chainCommon.chainName()
274+
const dataDir = `${this.datadir}/${networkDirName}`
275+
return dataDir
276+
}
277+
256278
/**
257279
* Returns the directory for storing the client chain data
258280
* based on syncmode and selected chain (subdirectory of 'datadir')
259281
*/
260282
getChainDataDirectory(): string {
261-
const networkDirName = this.chainCommon.chainName()
262283
const chainDataDirName = this.syncmode === 'light' ? 'lightchain' : 'chain'
263-
264-
const dataDir = `${this.datadir}/${networkDirName}/${chainDataDirName}`
284+
const dataDir = `${this.getNetworkDirectory()}/${chainDataDirName}`
265285
return dataDir
266286
}
267287

@@ -270,10 +290,36 @@ export class Config {
270290
* based selected chain (subdirectory of 'datadir')
271291
*/
272292
getStateDataDirectory(): string {
273-
const networkDirName = this.chainCommon.chainName()
293+
return `${this.getNetworkDirectory()}/state`
294+
}
274295

275-
const dataDir = `${this.datadir}/${networkDirName}/state`
276-
return dataDir
296+
/**
297+
* Returns the config level db.
298+
*/
299+
static getConfigDB(networkDir: string): LevelUp {
300+
const db = level(`${networkDir}/config` as any)
301+
return db
302+
}
303+
304+
/**
305+
* Gets the client private key from the config db.
306+
*/
307+
static async getClientKey(datadir: string, common: Common) {
308+
const networkDir = `${datadir}/${common.chainName()}`
309+
const db = this.getConfigDB(networkDir)
310+
const encodingOpts = { keyEncoding: 'utf8', valueEncoding: 'binary' }
311+
const dbKey = 'config:client_key'
312+
let key
313+
try {
314+
key = await db.get(dbKey, encodingOpts)
315+
} catch (error) {
316+
if (error.type === 'NotFoundError') {
317+
// generate and save a new key
318+
key = genPrivateKey()
319+
await db.put(dbKey, key, encodingOpts)
320+
}
321+
}
322+
return key
277323
}
278324

279325
/**

packages/client/lib/net/server/libp2pserver.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import PeerId from 'peer-id'
2+
import crypto from 'libp2p-crypto'
23
import multiaddr from 'multiaddr'
34
import { Libp2pConnection as Connection } from '../../types'
45
import { Libp2pNode } from '../peer/libp2pnode'
@@ -55,7 +56,7 @@ export class Libp2pServer extends Server {
5556
let peerId: PeerId
5657
await super.start()
5758
if (!this.node) {
58-
peerId = await this.createPeerId()
59+
peerId = await this.getPeerId()
5960
const addresses = { listen: this.multiaddrs.map((ma) => ma.toString()) }
6061
this.node = new Libp2pNode({
6162
peerId,
@@ -153,8 +154,10 @@ export class Libp2pServer extends Server {
153154
this.emit('error', error)
154155
}
155156

156-
async createPeerId() {
157-
return this.key ? PeerId.createFromPrivKey(this.key) : PeerId.create()
157+
async getPeerId() {
158+
const privKey = await crypto.keys.generateKeyPairFromSeed('ed25519', this.key, 512)
159+
const protoBuf = crypto.keys.marshalPrivateKey(privKey)
160+
return PeerId.createFromPrivKey(protoBuf)
158161
}
159162

160163
getPeerInfo(connection: Connection): [PeerId, multiaddr] {

packages/client/lib/net/server/rlpxserver.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { randomBytes } from 'crypto'
21
import { RLPx as Devp2pRLPx, Peer as Devp2pRLPxPeer, DPT as Devp2pDPT } from '@ethereumjs/devp2p'
32
import { RlpxPeer } from '../peer/rlpxpeer'
43
import { Server, ServerOptions } from './server'
@@ -215,7 +214,7 @@ export class RlpxServer extends Server {
215214
* @private
216215
*/
217216
initDpt() {
218-
this.dpt = new Devp2pDPT(this.key ?? randomBytes(32), {
217+
this.dpt = new Devp2pDPT(this.key, {
219218
refreshInterval: this.refreshInterval,
220219
endpoint: {
221220
address: '0.0.0.0',
@@ -241,7 +240,7 @@ export class RlpxServer extends Server {
241240
* @private
242241
*/
243242
initRlpx() {
244-
this.rlpx = new Devp2pRLPx(this.key ?? randomBytes(32), {
243+
this.rlpx = new Devp2pRLPx(this.key, {
245244
dpt: this.dpt!,
246245
maxPeers: this.config.maxPeers,
247246
capabilities: RlpxPeer.capabilities(Array.from(this.protocols)),

packages/client/lib/net/server/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface ServerOptions {
2828
*/
2929
export class Server extends EventEmitter {
3030
public config: Config
31-
public key: Buffer | undefined
31+
public key: Buffer
3232
public bootnodes: multiaddr[] = []
3333
public dnsNetworks: DnsNetwork[]
3434

@@ -45,7 +45,7 @@ export class Server extends EventEmitter {
4545
super()
4646

4747
this.config = options.config
48-
this.key = options.key ? parseKey(options.key) : undefined
48+
this.key = options.key ? parseKey(options.key) : this.config.key
4949
this.bootnodes = options.bootnodes ? parseMultiaddrs(options.bootnodes) : []
5050
this.dnsNetworks = options.dnsNetworks ?? []
5151
this.refreshInterval = options.refreshInterval ?? 30000

packages/client/test/net/server/libp2pserver.spec.ts

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import multiaddr from 'multiaddr'
55
import { Config } from '../../../lib/config'
66

77
tape('[Libp2pServer]', async (t) => {
8-
const PeerId = td.replace('peer-id')
9-
108
const Libp2pPeer = td.replace('../../../lib/net/peer/libp2ppeer')
119
Libp2pPeer.id = 'id0'
1210

@@ -36,16 +34,6 @@ tape('[Libp2pServer]', async (t) => {
3634
td.when(Libp2pNode.prototype.start()).thenResolve()
3735
td.when(Libp2pNode.prototype.stop()).thenResolve()
3836

39-
td.when(PeerId.create()).thenResolve('id0')
40-
td.when(PeerId.createFromPrivKey(Buffer.from('1'))).thenResolve('id1')
41-
td.when(PeerId.createFromPrivKey(Buffer.from('2'))).thenResolve('id2')
42-
td.when(PeerId.createFromPrivKey(Buffer.from('3'))).thenReject(new Error('err0'))
43-
td.when(PeerId.createFromPrivKey(Buffer.from('4'))).thenResolve({
44-
toB58String: () => {
45-
return 'id4'
46-
},
47-
})
48-
4937
const { Libp2pServer } = await import('../../../lib/net/server/libp2pserver')
5038

5139
t.test('should initialize correctly', async (t) => {
@@ -71,24 +59,6 @@ tape('[Libp2pServer]', async (t) => {
7159
t.end()
7260
})
7361

74-
t.test('should create peer id', async (t) => {
75-
const config = new Config({ transports: [] })
76-
const multiaddrs = [multiaddr('/ip4/6.6.6.6')]
77-
let server = new Libp2pServer({ config, multiaddrs })
78-
t.equals(await server.createPeerId(), 'id0', 'created')
79-
server = new Libp2pServer({ config, multiaddrs, key: Buffer.from('1') })
80-
t.equals(await server.createPeerId(), 'id1', 'created with id')
81-
server = new Libp2pServer({ config, multiaddrs, key: Buffer.from('2') })
82-
t.equals(await server.createPeerId(), 'id2', 'created with id')
83-
server = new Libp2pServer({ config, multiaddrs, key: Buffer.from('3') })
84-
try {
85-
await server.createPeerId()
86-
} catch (err) {
87-
t.equals(err.message, 'err0', 'handle error')
88-
}
89-
t.end()
90-
})
91-
9262
t.test('should get peer info', async (t) => {
9363
const config = new Config({ transports: [] })
9464
const server = new Libp2pServer({ config })
@@ -130,6 +100,7 @@ tape('[Libp2pServer]', async (t) => {
130100
})
131101
server.createPeer = td.func<typeof server['createPeer']>()
132102
server.getPeerInfo = td.func<typeof server['getPeerInfo']>()
103+
server.getPeerId = td.func<typeof server['getPeerId']>()
133104
const peerId = {
134105
toB58String() {
135106
return 'id'
@@ -145,14 +116,15 @@ tape('[Libp2pServer]', async (t) => {
145116
return 'id3'
146117
},
147118
} as any
119+
td.when(server.getPeerId()).thenResolve(peerId)
148120
td.when(server.getPeerInfo(conn0)).thenReturn([peerId])
149121
td.when(server.getPeerInfo(conn1)).thenReturn([peerId2])
150122
td.when(server.createPeer(peerId2)).thenReturn(peer2)
151123
td.when(peer.accept(protos[0], 'conn0', server)).thenResolve(null)
152124
;(server as any).peers.set('id', peer)
153125
server.addProtocols(protos)
154126
server.on('listening', (info: any) =>
155-
t.deepEquals(info, { transport: 'libp2p', url: 'ma0/p2p/id4' }, 'listening')
127+
t.deepEquals(info, { transport: 'libp2p', url: 'ma0/p2p/id' }, 'listening')
156128
)
157129
server.once('connected', (p: any) => t.equals(p, peer, 'peer connected'))
158130
server.on('error', (err: Error) => t.equals(err.message, 'err0', 'got err0'))

0 commit comments

Comments
 (0)