From f1c9fb9779c687f9b4fe2bd0d04643d0adc64dcc Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Thu, 21 Jan 2021 21:42:21 +0100 Subject: [PATCH 1/4] client: save private rlpx key to disk --- packages/client/lib/config.ts | 7 +++++++ packages/client/lib/net/server/rlpxserver.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/client/lib/config.ts b/packages/client/lib/config.ts index e30fe56ea32..bfd9672dbdc 100644 --- a/packages/client/lib/config.ts +++ b/packages/client/lib/config.ts @@ -294,4 +294,11 @@ export class Config { if (option !== undefined) return option return this.chainCommon.chainName() === 'mainnet' } + + getNetworkDir(): string { + const networkDirName = this.common.chainName() + const dataDir = `${this.datadir}/${networkDirName}` + + return dataDir + } } diff --git a/packages/client/lib/net/server/rlpxserver.ts b/packages/client/lib/net/server/rlpxserver.ts index 8a6bf5a4db2..ced43df7912 100644 --- a/packages/client/lib/net/server/rlpxserver.ts +++ b/packages/client/lib/net/server/rlpxserver.ts @@ -2,6 +2,7 @@ import { randomBytes } from 'crypto' import { RLPx as Devp2pRLPx, Peer as Devp2pRLPxPeer, DPT as Devp2pDPT } from '@ethereumjs/devp2p' import { RlpxPeer } from '../peer/rlpxpeer' import { Server, ServerOptions } from './server' +import fs from 'fs' export interface RlpxServerOptions extends ServerOptions { /* Local port to listen on (default: 30303) */ @@ -64,6 +65,20 @@ export class RlpxServer extends Server { constructor(options: RlpxServerOptions) { super(options) + if (this.key === undefined) { + const dataDir = this.config.getNetworkDir() + const fileName = dataDir + '/nodekey' + if (fs.existsSync(fileName)) { + this.key = Buffer.from(fs.readFileSync(fileName, { encoding: 'binary' }), 'binary') + } else { + const key = randomBytes(32) + this.key = key + fs.writeFileSync(fileName, key.toString('binary'), { + encoding: 'binary', + }) + } + } + // TODO: get the external ip from the upnp service this.ip = '::' this.port = options.port ?? 30303 From 96a2d1cbdeae15b902cfc9dbffd3c54d9540ccfc Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Fri, 22 Jan 2021 17:49:53 +0100 Subject: [PATCH 2/4] client: ensure dataDir exists before writing nodeKey --- packages/client/lib/config.ts | 2 +- packages/client/lib/net/server/rlpxserver.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/lib/config.ts b/packages/client/lib/config.ts index bfd9672dbdc..198496456a2 100644 --- a/packages/client/lib/config.ts +++ b/packages/client/lib/config.ts @@ -294,7 +294,7 @@ export class Config { if (option !== undefined) return option return this.chainCommon.chainName() === 'mainnet' } - + getNetworkDir(): string { const networkDirName = this.common.chainName() const dataDir = `${this.datadir}/${networkDirName}` diff --git a/packages/client/lib/net/server/rlpxserver.ts b/packages/client/lib/net/server/rlpxserver.ts index ced43df7912..a04663b2930 100644 --- a/packages/client/lib/net/server/rlpxserver.ts +++ b/packages/client/lib/net/server/rlpxserver.ts @@ -2,7 +2,7 @@ import { randomBytes } from 'crypto' import { RLPx as Devp2pRLPx, Peer as Devp2pRLPxPeer, DPT as Devp2pDPT } from '@ethereumjs/devp2p' import { RlpxPeer } from '../peer/rlpxpeer' import { Server, ServerOptions } from './server' -import fs from 'fs' +const fs = require('fs-extra') export interface RlpxServerOptions extends ServerOptions { /* Local port to listen on (default: 30303) */ @@ -73,6 +73,7 @@ export class RlpxServer extends Server { } else { const key = randomBytes(32) this.key = key + fs.ensureDirSync(dataDir) fs.writeFileSync(fileName, key.toString('binary'), { encoding: 'binary', }) From bebf18d69acaf742c15119ab974debd2a838851d Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 11 Mar 2021 14:41:46 -0800 Subject: [PATCH 3/4] introduce client config db for setting and retrieving the client key --- packages/client/bin/cli.ts | 5 +- packages/client/browser/index.ts | 12 ++-- packages/client/lib/config.ts | 69 +++++++++++++++---- .../client/lib/net/server/libp2pserver.ts | 9 ++- packages/client/lib/net/server/rlpxserver.ts | 21 +----- packages/client/lib/net/server/server.ts | 4 +- .../test/net/server/libp2pserver.spec.ts | 34 +-------- 7 files changed, 79 insertions(+), 75 deletions(-) diff --git a/packages/client/bin/cli.ts b/packages/client/bin/cli.ts index 67c0261b4f7..a9da2978d50 100755 --- a/packages/client/bin/cli.ts +++ b/packages/client/bin/cli.ts @@ -175,11 +175,14 @@ async function run() { } const common = new Common({ chain, hardfork: 'chainstart' }) + const datadir = args.datadir ?? Config.DATADIR_DEFAULT + const key = await Config.getClientKey(datadir, common) const config = new Config({ common, syncmode: args.syncmode, lightserv: args.lightserv, - datadir: args.datadir, + datadir, + key, transports: args.transports, bootnodes: args.bootnodes ? parseMultiaddrs(args.bootnodes) : undefined, multiaddrs: args.multiaddrs ? parseMultiaddrs(args.multiaddrs) : undefined, diff --git a/packages/client/browser/index.ts b/packages/client/browser/index.ts index 0914f1174ab..bc5b7f885a8 100644 --- a/packages/client/browser/index.ts +++ b/packages/client/browser/index.ts @@ -44,11 +44,15 @@ import { Config } from '../lib/config' export * from './logging' import { getLogger } from './logging' -export function createClient(args: any) { +export async function createClient(args: any) { const logger = getLogger({ loglevel: args.loglevel ?? 'info' }) + const datadir = args.datadir ?? Config.DATADIR_DEFAULT + const common = new Common({ chain: args.network ?? 'mainnet' }) + const key = await Config.getClientKey(datadir, common) const config = new Config({ - common: new Common({ chain: args.network ?? 'mainnet' }), - servers: [new Libp2pServer({ multiaddrs: [], config: new Config({ logger }), ...args })], + common, + key, + servers: [new Libp2pServer({ multiaddrs: [], config: new Config({ key, logger }), ...args })], syncmode: args.syncmode ?? 'full', logger, }) @@ -56,7 +60,7 @@ export function createClient(args: any) { } export async function run(args: any) { - const client = createClient(args) + const client = await createClient(args) const { logger, chainCommon: common } = client.config logger.info('Initializing Ethereumjs client...') logger.info(`Connecting to network: ${common.chainName()}`) diff --git a/packages/client/lib/config.ts b/packages/client/lib/config.ts index 198496456a2..8760480512e 100644 --- a/packages/client/lib/config.ts +++ b/packages/client/lib/config.ts @@ -1,9 +1,12 @@ import Common from '@ethereumjs/common' import VM from '@ethereumjs/vm' +import { genPrivateKey } from '@ethereumjs/devp2p' import Multiaddr from 'multiaddr' import { getLogger, Logger } from './logging' import { Libp2pServer, RlpxServer } from './net/server' import { parseTransports } from './util' +import type { LevelUp } from 'levelup' +const level = require('level') export interface ConfigOptions { /** @@ -40,6 +43,13 @@ export interface ConfigOptions { */ datadir?: string + /** + * Private key for the client. + * Use return value of `await Config.getClientKey(datadir, common)` + * If left blank, a random key will be generated and used. + */ + key?: Buffer + /** * Network transports ('rlpx' and/or 'libp2p') * @@ -172,6 +182,7 @@ export class Config { public readonly vm?: VM public readonly lightserv: boolean public readonly datadir: string + public readonly key: Buffer public readonly transports: string[] public readonly bootnodes?: Multiaddr[] public readonly multiaddrs?: Multiaddr[] @@ -199,6 +210,7 @@ export class Config { this.bootnodes = options.bootnodes this.multiaddrs = options.multiaddrs this.datadir = options.datadir ?? Config.DATADIR_DEFAULT + this.key = options.key ?? genPrivateKey() this.rpc = options.rpc ?? Config.RPC_DEFAULT this.rpcport = options.rpcport ?? Config.RPCPORT_DEFAULT this.rpcaddr = options.rpcaddr ?? Config.RPCADDR_DEFAULT @@ -243,25 +255,33 @@ export class Config { const bootnodes = this.bootnodes ?? this.chainCommon.bootstrapNodes() const dnsNetworks = options.dnsNetworks ?? this.chainCommon.dnsNetworks() return new RlpxServer({ config: this, bootnodes, dnsNetworks }) - } else { - // t.name === 'libp2p' + } else if (t.name === 'libp2p') { const multiaddrs = this.multiaddrs const bootnodes = this.bootnodes return new Libp2pServer({ config: this, multiaddrs, bootnodes }) + } else { + throw new Error(`unknown transport: ${t.name}`) } }) } } + /** + * Returns the network directory for the chain. + */ + getNetworkDirectory(): string { + const networkDirName = this.chainCommon.chainName() + const dataDir = `${this.datadir}/${networkDirName}` + return dataDir + } + /** * Returns the directory for storing the client chain data * based on syncmode and selected chain (subdirectory of 'datadir') */ getChainDataDirectory(): string { - const networkDirName = this.chainCommon.chainName() const chainDataDirName = this.syncmode === 'light' ? 'lightchain' : 'chain' - - const dataDir = `${this.datadir}/${networkDirName}/${chainDataDirName}` + const dataDir = `${this.getNetworkDirectory()}/${chainDataDirName}` return dataDir } @@ -270,10 +290,36 @@ export class Config { * based selected chain (subdirectory of 'datadir') */ getStateDataDirectory(): string { - const networkDirName = this.chainCommon.chainName() + return `${this.getNetworkDirectory()}/state` + } - const dataDir = `${this.datadir}/${networkDirName}/state` - return dataDir + /** + * Returns the config level db. + */ + static getConfigDB(networkDir: string): LevelUp { + const db = level(`${networkDir}/config` as any) + return db + } + + /** + * Gets the client private key from the config db. + */ + static async getClientKey(datadir: string, common: Common) { + const networkDir = `${datadir}/${common.chainName()}` + const db = this.getConfigDB(networkDir) + const encodingOpts = { keyEncoding: 'utf8', valueEncoding: 'binary' } + const dbKey = 'config:client_key' + let key + try { + key = await db.get(dbKey, encodingOpts) + } catch (error) { + if (error.type === 'NotFoundError') { + // generate and save a new key + key = genPrivateKey() + await db.put(dbKey, key, encodingOpts) + } + } + return key } /** @@ -294,11 +340,4 @@ export class Config { if (option !== undefined) return option return this.chainCommon.chainName() === 'mainnet' } - - getNetworkDir(): string { - const networkDirName = this.common.chainName() - const dataDir = `${this.datadir}/${networkDirName}` - - return dataDir - } } diff --git a/packages/client/lib/net/server/libp2pserver.ts b/packages/client/lib/net/server/libp2pserver.ts index 191c929727a..43a4ca22401 100644 --- a/packages/client/lib/net/server/libp2pserver.ts +++ b/packages/client/lib/net/server/libp2pserver.ts @@ -1,4 +1,5 @@ import PeerId from 'peer-id' +import crypto from 'libp2p-crypto' import multiaddr from 'multiaddr' import { Libp2pConnection as Connection } from '../../types' import { Libp2pNode } from '../peer/libp2pnode' @@ -55,7 +56,7 @@ export class Libp2pServer extends Server { let peerId: PeerId await super.start() if (!this.node) { - peerId = await this.createPeerId() + peerId = await this.getPeerId() const addresses = { listen: this.multiaddrs.map((ma) => ma.toString()) } this.node = new Libp2pNode({ peerId, @@ -153,8 +154,10 @@ export class Libp2pServer extends Server { this.emit('error', error) } - async createPeerId() { - return this.key ? PeerId.createFromPrivKey(this.key) : PeerId.create() + async getPeerId() { + const privKey = await crypto.keys.generateKeyPairFromSeed('ed25519', this.key, 512) + const protoBuf = crypto.keys.marshalPrivateKey(privKey) + return PeerId.createFromPrivKey(protoBuf) } getPeerInfo(connection: Connection): [PeerId, multiaddr] { diff --git a/packages/client/lib/net/server/rlpxserver.ts b/packages/client/lib/net/server/rlpxserver.ts index a04663b2930..5ee2adad7ea 100644 --- a/packages/client/lib/net/server/rlpxserver.ts +++ b/packages/client/lib/net/server/rlpxserver.ts @@ -1,8 +1,6 @@ -import { randomBytes } from 'crypto' import { RLPx as Devp2pRLPx, Peer as Devp2pRLPxPeer, DPT as Devp2pDPT } from '@ethereumjs/devp2p' import { RlpxPeer } from '../peer/rlpxpeer' import { Server, ServerOptions } from './server' -const fs = require('fs-extra') export interface RlpxServerOptions extends ServerOptions { /* Local port to listen on (default: 30303) */ @@ -65,21 +63,6 @@ export class RlpxServer extends Server { constructor(options: RlpxServerOptions) { super(options) - if (this.key === undefined) { - const dataDir = this.config.getNetworkDir() - const fileName = dataDir + '/nodekey' - if (fs.existsSync(fileName)) { - this.key = Buffer.from(fs.readFileSync(fileName, { encoding: 'binary' }), 'binary') - } else { - const key = randomBytes(32) - this.key = key - fs.ensureDirSync(dataDir) - fs.writeFileSync(fileName, key.toString('binary'), { - encoding: 'binary', - }) - } - } - // TODO: get the external ip from the upnp service this.ip = '::' this.port = options.port ?? 30303 @@ -231,7 +214,7 @@ export class RlpxServer extends Server { * @private */ initDpt() { - this.dpt = new Devp2pDPT(this.key ?? randomBytes(32), { + this.dpt = new Devp2pDPT(this.key, { refreshInterval: this.refreshInterval, endpoint: { address: '0.0.0.0', @@ -257,7 +240,7 @@ export class RlpxServer extends Server { * @private */ initRlpx() { - this.rlpx = new Devp2pRLPx(this.key ?? randomBytes(32), { + this.rlpx = new Devp2pRLPx(this.key, { dpt: this.dpt!, maxPeers: this.config.maxPeers, capabilities: RlpxPeer.capabilities(Array.from(this.protocols)), diff --git a/packages/client/lib/net/server/server.ts b/packages/client/lib/net/server/server.ts index e1411ba48e2..caa45ceda89 100644 --- a/packages/client/lib/net/server/server.ts +++ b/packages/client/lib/net/server/server.ts @@ -28,7 +28,7 @@ export interface ServerOptions { */ export class Server extends EventEmitter { public config: Config - public key: Buffer | undefined + public key: Buffer public bootnodes: multiaddr[] = [] public dnsNetworks: DnsNetwork[] @@ -45,7 +45,7 @@ export class Server extends EventEmitter { super() this.config = options.config - this.key = options.key ? parseKey(options.key) : undefined + this.key = options.key ? parseKey(options.key) : this.config.key this.bootnodes = options.bootnodes ? parseMultiaddrs(options.bootnodes) : [] this.dnsNetworks = options.dnsNetworks ?? [] this.refreshInterval = options.refreshInterval ?? 30000 diff --git a/packages/client/test/net/server/libp2pserver.spec.ts b/packages/client/test/net/server/libp2pserver.spec.ts index bbcd60c1e63..15636ef3afb 100644 --- a/packages/client/test/net/server/libp2pserver.spec.ts +++ b/packages/client/test/net/server/libp2pserver.spec.ts @@ -5,8 +5,6 @@ import multiaddr from 'multiaddr' import { Config } from '../../../lib/config' tape('[Libp2pServer]', async (t) => { - const PeerId = td.replace('peer-id') - const Libp2pPeer = td.replace('../../../lib/net/peer/libp2ppeer') Libp2pPeer.id = 'id0' @@ -36,16 +34,6 @@ tape('[Libp2pServer]', async (t) => { td.when(Libp2pNode.prototype.start()).thenResolve() td.when(Libp2pNode.prototype.stop()).thenResolve() - td.when(PeerId.create()).thenResolve('id0') - td.when(PeerId.createFromPrivKey(Buffer.from('1'))).thenResolve('id1') - td.when(PeerId.createFromPrivKey(Buffer.from('2'))).thenResolve('id2') - td.when(PeerId.createFromPrivKey(Buffer.from('3'))).thenReject(new Error('err0')) - td.when(PeerId.createFromPrivKey(Buffer.from('4'))).thenResolve({ - toB58String: () => { - return 'id4' - }, - }) - const { Libp2pServer } = await import('../../../lib/net/server/libp2pserver') t.test('should initialize correctly', async (t) => { @@ -71,24 +59,6 @@ tape('[Libp2pServer]', async (t) => { t.end() }) - t.test('should create peer id', async (t) => { - const config = new Config({ transports: [] }) - const multiaddrs = [multiaddr('/ip4/6.6.6.6')] - let server = new Libp2pServer({ config, multiaddrs }) - t.equals(await server.createPeerId(), 'id0', 'created') - server = new Libp2pServer({ config, multiaddrs, key: Buffer.from('1') }) - t.equals(await server.createPeerId(), 'id1', 'created with id') - server = new Libp2pServer({ config, multiaddrs, key: Buffer.from('2') }) - t.equals(await server.createPeerId(), 'id2', 'created with id') - server = new Libp2pServer({ config, multiaddrs, key: Buffer.from('3') }) - try { - await server.createPeerId() - } catch (err) { - t.equals(err.message, 'err0', 'handle error') - } - t.end() - }) - t.test('should get peer info', async (t) => { const config = new Config({ transports: [] }) const server = new Libp2pServer({ config }) @@ -130,6 +100,7 @@ tape('[Libp2pServer]', async (t) => { }) server.createPeer = td.func() server.getPeerInfo = td.func() + server.getPeerId = td.func() const peerId = { toB58String() { return 'id' @@ -145,6 +116,7 @@ tape('[Libp2pServer]', async (t) => { return 'id3' }, } as any + td.when(server.getPeerId()).thenResolve(peerId) td.when(server.getPeerInfo(conn0)).thenReturn([peerId]) td.when(server.getPeerInfo(conn1)).thenReturn([peerId2]) td.when(server.createPeer(peerId2)).thenReturn(peer2) @@ -152,7 +124,7 @@ tape('[Libp2pServer]', async (t) => { ;(server as any).peers.set('id', peer) server.addProtocols(protos) server.on('listening', (info: any) => - t.deepEquals(info, { transport: 'libp2p', url: 'ma0/p2p/id4' }, 'listening') + t.deepEquals(info, { transport: 'libp2p', url: 'ma0/p2p/id' }, 'listening') ) server.once('connected', (p: any) => t.equals(p, peer, 'peer connected')) server.on('error', (err: Error) => t.equals(err.message, 'err0', 'got err0')) From f1854b46a0f61e264fb235361492f63d66a2746b Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Thu, 11 Mar 2021 15:08:21 -0800 Subject: [PATCH 4/4] disable trie-benchmarks comment-on-alert --- .github/workflows/trie-build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/trie-build.yml b/.github/workflows/trie-build.yml index 49495a34f46..f99b8e9a62c 100644 --- a/.github/workflows/trie-build.yml +++ b/.github/workflows/trie-build.yml @@ -98,8 +98,6 @@ jobs: output-file-path: ${{ env.cwd }}/output.txt # Location of data in gh-pages branch benchmark-data-dir-path: dev/bench/trie - # Enable alert commit comment - comment-on-alert: true # GitHub API token to make a commit comment github-token: ${{ secrets.GITHUB_TOKEN }} # Push and deploy to GitHub pages branch automatically (if on master)