Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 4 additions & 1 deletion packages/client/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,14 @@ async function run() {
}

const common = new Common({ chain, hardfork: 'chainstart' })
const datadir = args.datadir ?? Config.DATADIR_DEFAULT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't make this a blocker, but this shouldn't be necessary since datadir has a local CLI-specific default (first was afraid that this would actually change the default, but this is not the case)? 🤔

const key = await Config.getClientKey(datadir, common)
const config = new Config({
common,
syncmode: args.syncmode,
lightserv: args.lightserv,
datadir: args.datadir,
datadir,
key,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder though if we want to make the name of this option a bit more expressive?

transports: args.transports,
bootnodes: args.bootnodes ? parseMultiaddrs(args.bootnodes) : undefined,
multiaddrs: args.multiaddrs ? parseMultiaddrs(args.multiaddrs) : undefined,
Expand Down
12 changes: 8 additions & 4 deletions packages/client/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,23 @@ 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,
})
return new EthereumClient({ config })
}

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()}`)
Expand Down
62 changes: 54 additions & 8 deletions packages/client/lib/config.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down Expand Up @@ -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')
*
Expand Down Expand Up @@ -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[]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

/**
Expand Down
9 changes: 6 additions & 3 deletions packages/client/lib/net/server/libp2pserver.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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] {
Expand Down
5 changes: 2 additions & 3 deletions packages/client/lib/net/server/rlpxserver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
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'
Expand Down Expand Up @@ -215,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',
Expand All @@ -241,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)),
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/net/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]

Expand All @@ -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
Expand Down
34 changes: 3 additions & 31 deletions packages/client/test/net/server/libp2pserver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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) => {
Expand All @@ -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 })
Expand Down Expand Up @@ -130,6 +100,7 @@ tape('[Libp2pServer]', async (t) => {
})
server.createPeer = td.func<typeof server['createPeer']>()
server.getPeerInfo = td.func<typeof server['getPeerInfo']>()
server.getPeerId = td.func<typeof server['getPeerId']>()
const peerId = {
toB58String() {
return 'id'
Expand All @@ -145,14 +116,15 @@ 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)
td.when(peer.accept(protos[0], 'conn0', server)).thenResolve(null)
;(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'))
Expand Down