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

Commit

Permalink
Merge pull request #188 from ethereumjs/add-programmatic-api
Browse files Browse the repository at this point in the history
Early-stage Programmatic API / Custom VM Option
  • Loading branch information
holgerd77 authored Nov 30, 2020
2 parents fda06f1 + c975e9d commit 79eaf15
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 165 deletions.
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Project summary from [this document](./PROJECT.md) is currently outdated. Please

## Client Setup

**Installing the Client**
### Installing the Client

```shell
npm install ethereumjs-client
Expand All @@ -34,9 +34,9 @@ npm link
Note: for development purposes you can invoke the client by build with `npm run build:node` and
then run `node ./dist/bin/cli.js`.

**Running the Client**
### Running the Client

Some building blocks for the client have already been implemented or outlined to further build upon.
#### CLI

You can run the current state of the client with:

Expand Down Expand Up @@ -69,6 +69,32 @@ for all output or something more targeted by listing the loggers like
DEBUG=devp2p:rlpx,devp2p:eth,-babel [CLIENT_START_COMMAND]
```

#### Node.js

To programmatically run a client do:

```typescript
import { Config, EthereumClient } from '@ethereumjs/client'
const config = new Config()
const client = new EthereumClient({ config })

client.open()
client.start()
client.stop()
```

You can also provide your custom [@ethereumjs/vm](https://github.com/ethereumjs/ethereumjs-vm/tree/master/packages/vm) instance:

```typescript
import VM from '@ethereumjs/vm'
import { Config, EthereumClient } from '@ethereumjs/client'
const vm = new VM()
const config = new Config({ vm })
const client = new EthereumClient({ config })
```

[WORK-IN-PROGRESS] Programmatic invocation on the client is in a very early stage and only meant for experimental purposes. You are invited to play around, please let us know what control functionality you would want the client to expose and what information you would need to get out of the client to be useful in your usage context.

## API

[API Reference](./docs/README.md)
Expand All @@ -77,7 +103,7 @@ See also this [diagram](./diagram/client.svg) for an overview of the client stru

## JSON-RPC

**Overview**
### Overview

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

Expand All @@ -98,7 +124,7 @@ Currently only a small subset of `RPC` methods are implemented.(\*) You can have
(*) Side note: implementing RPC methods is actually an extremely thankful task for a first-time
contribution on the project *hint\* _hint_. 😄

**API Examples**
### 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):
Expand Down
32 changes: 17 additions & 15 deletions bin/cli.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#!/usr/bin/env node
#!/usr/bin/env client

import { Server as RPCServer } from 'jayson'
import Common from '@ethereumjs/common'
import { parseParams } from '../lib/util'
import Node from '../lib/node'
import EthereumClient from '../lib/client'
import { Config } from '../lib/config'
import { Logger } from '../lib/logging'
import { RPCManager } from '../lib/rpc'
const os = require('os')
const path = require('path')
const fs = require('fs-extra')
const chains = require('@ethereumjs/common/dist/chains').chains
Expand Down Expand Up @@ -85,7 +86,7 @@ let logger: Logger | null = null

/**
* Initializes and starts a Node and reacts on the
* main node lifecycle events
* main client lifecycle events
*
* @param config
*/
Expand All @@ -98,28 +99,28 @@ async function runNode(config: Config) {
if (config.lightserv) {
config.logger.info(`Serving light peer requests`)
}
const node = new Node({
const client = new EthereumClient({
config,
db: level(syncDataDir),
})
node.on('error', (err: any) => config.logger.error(err))
node.on('listening', (details: any) => {
client.on('error', (err: any) => config.logger.error(err))
client.on('listening', (details: any) => {
config.logger.info(`Listener up transport=${details.transport} url=${details.url}`)
})
node.on('synchronized', () => {
client.on('synchronized', () => {
config.logger.info('Synchronized')
})
config.logger.info(`Connecting to network: ${config.common.chainName()}`)
await node.open()
await client.open()
config.logger.info('Synchronizing blockchain...')
await node.start()
await client.start()

return node
return client
}

function runRpcServer(node: Node, config: Config) {
function runRpcServer(client: EthereumClient, config: Config) {
const { rpcport, rpcaddr } = config
const manager = new RPCManager(node, config)
const manager = new RPCManager(client, config)
const server = new RPCServer(manager.getMethods())
config.logger.info(`RPC HTTP endpoint opened: http://${rpcaddr}:${rpcport}`)
server.http().listen(rpcport)
Expand All @@ -144,6 +145,7 @@ async function run() {
common,
syncmode: args.syncmode,
lightserv: args.lightserv,
datadir: `${os.homedir()}/Library/Ethereum`,
transports: args.transports,
rpc: args.rpc,
rpcport: args.rpcport,
Expand All @@ -158,13 +160,13 @@ async function run() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const chainParams = args.params ? await parseParams(args.params) : args.network

const node = await runNode(config)
const server = config.rpc ? runRpcServer(node, config) : null
const client = await runNode(config)
const server = config.rpc ? runRpcServer(client, config) : null

process.on('SIGINT', async () => {
config.logger.info('Caught interrupt signal. Shutting down...')
if (server) server.http().close()
await node.stop()
await client.stop()
config.logger.info('Exiting.')
process.exit()
})
Expand Down
26 changes: 13 additions & 13 deletions browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export * from '../lib/net/protocol/flowcontrol'
export * from '../lib/net/server/server'
export * from '../lib/net/server/libp2pserver'

// Node
export * from '../lib/node'
// EthereumClient
export * from '../lib/client'

// Service
export * from '../lib/service/service'
Expand All @@ -42,7 +42,7 @@ export * from '../lib/util'
export * from './logging'
import { getLogger } from './logging'

export function createNode(args: any) {
export function createClient(args: any) {
const logger = getLogger({ loglevel: args.loglevel })
const options = {
common: new Common({ chain: args.network ?? 'mainnet' }),
Expand All @@ -51,24 +51,24 @@ export function createNode(args: any) {
db: level(args.db ?? 'ethereumjs'),
logger: logger,
}
return new exports.Node(options)
return new exports.EthereumClient(options)
}

export function run(args: any) {
const node = createNode(args)
const logger = node.logger
const client = createClient(args)
const logger = client.logger
logger.info('Initializing Ethereumjs client...')
logger.info(`Connecting to network: ${node.common.chainName()}`)
node.on('error', (err: any) => logger.error(err))
node.on('listening', (details: any) => {
logger.info(`Connecting to network: ${client.common.chainName()}`)
client.on('error', (err: any) => logger.error(err))
client.on('listening', (details: any) => {
logger.info(`Listener up transport=${details.transport} url=${details.url}`)
})
node.on('synchronized', () => {
client.on('synchronized', () => {
logger.info('Synchronized')
})
node.open().then(() => {
client.open().then(() => {
logger.info('Synchronizing blockchain...')
node.start()
client.start()
})
return node
return client
}
14 changes: 9 additions & 5 deletions lib/node.ts → lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { BootnodeLike } from './types'
import { Config } from './config'
import { FullEthereumService, LightEthereumService } from './service'

export interface NodeOptions {
export interface EthereumClientOptions {
/* Client configuration */
config: Config

/* Blockchain database (default: null) */
/**
* Database to store blocks and metadata. Should be an abstract-leveldown compliant store.
*
* Default: Database created by the Blockchain class
*/
db?: LevelUp

/* List of bootnodes to use for discovery */
Expand All @@ -26,7 +30,7 @@ export interface NodeOptions {
* lifecycle of included services.
* @memberof module:node
*/
export default class Node extends events.EventEmitter {
export default class EthereumClient extends events.EventEmitter {
public config: Config

public services: (FullEthereumService | LightEthereumService)[]
Expand All @@ -36,9 +40,9 @@ export default class Node extends events.EventEmitter {

/**
* Create new node
* @param {NodeOptions}
* @param {EthereumClientOptions}
*/
constructor(options: NodeOptions) {
constructor(options: EthereumClientOptions) {
super()

this.config = options.config
Expand Down
13 changes: 11 additions & 2 deletions lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const os = require('os')
import Common from '@ethereumjs/common'
import VM from '@ethereumjs/vm'
import { getLogger, Logger } from './logging'
import { Libp2pServer, RlpxServer } from './net/server'
import { parseTransports } from './util'
Expand All @@ -19,6 +19,13 @@ export interface ConfigOptions {
*/
syncmode?: string

/**
* Provide a custom VM instance to process blocks
*
* Default: VM instance created by client
*/
vm?: VM

/**
* Serve light peer requests
*
Expand Down Expand Up @@ -103,7 +110,7 @@ export class Config {
public static readonly COMMON_DEFAULT = new Common({ chain: 'mainnet', hardfork: 'chainstart' })
public static readonly SYNCMODE_DEFAULT = 'full'
public static readonly LIGHTSERV_DEFAULT = false
public static readonly DATADIR_DEFAULT = `${os.homedir()}/Library/Ethereum`
public static readonly DATADIR_DEFAULT = `./datadir`
public static readonly TRANSPORTS_DEFAULT = ['rlpx:port=30303', 'libp2p']
public static readonly RPC_DEFAULT = false
public static readonly RPCPORT_DEFAULT = 8545
Expand All @@ -115,6 +122,7 @@ export class Config {
public readonly common: Common
public readonly logger: Logger
public readonly syncmode: string
public readonly vm?: VM
public readonly lightserv: boolean
public readonly datadir: string
public readonly transports: string[]
Expand All @@ -131,6 +139,7 @@ export class Config {
// TODO: map chainParams (and lib/util.parseParams) to new Common format
this.common = options.common ?? Config.COMMON_DEFAULT
this.syncmode = options.syncmode ?? Config.SYNCMODE_DEFAULT
this.vm = options.vm
this.lightserv = options.lightserv ?? Config.LIGHTSERV_DEFAULT
this.transports = options.transports ?? Config.TRANSPORTS_DEFAULT
this.datadir = options.datadir ?? Config.DATADIR_DEFAULT
Expand Down
77 changes: 2 additions & 75 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,2 @@
/**
* Define a library component for lazy loading. Borrowed from
* https://github.com/bcoin-org/bcoin/blob/master/lib/bcoin.js
* @param {string} name
* @param {string} path
*/
exports.define = function define(name: string, path: string) {
let cache: any = null
Object.defineProperty(exports, name, {
enumerable: true,
get() {
if (!cache) {
cache = require(path)
}
return cache
},
})
}

// Blockchain
exports.define('blockchain', './blockchain')
exports.define('Chain', './blockchain/chain')

// Handler
exports.define('handler', './handler')
exports.define('Handler', './handler/handler')
exports.define('EthHandler', './handler/ethhandler')
exports.define('LesHandler', './handler/leshandler')

// Peer
exports.define('peer', './net/peer')
exports.define('Peer', './net/peer/peer')
exports.define('RlpxPeer', './net/peer/rlpxpeer')
exports.define('Libp2pPeer', './net/peer/libp2ppeer')

// Peer Pool
exports.define('PeerPool', './net/peerpool')

// Protocol
exports.define('protocol', './net/protocol')
exports.define('Protocol', './net/protocol/protocol')
exports.define('EthProtocol', './net/protocol/ethprotocol')
exports.define('LesProtocol', './net/protocol/lesprotocol')
exports.define('FlowControl', './net/protocol/flowcontrol')

// Server
exports.define('server', './net/server')
exports.define('Server', './net/server/server')
exports.define('RlpxServer', './net/server/rlpxserver')
exports.define('Libp2pServer', './net/server/libp2pserver')

// Node
exports.define('Node', './node')

// RPC Manager
exports.define('RPCManager', './rpc')

// Service
exports.define('service', './service')
exports.define('Service', './service/service')
exports.define('EthereumService', './service/ethereumservice')

// Synchronizer
exports.define('sync', './sync')
exports.define('Synchronizer', './sync/sync')
exports.define('FullSynchronizer', './sync/fullsync')
exports.define('LightSynchronizer', './sync/lightsync')

// Utilities
exports.define('util', './util')

// Logging
exports.define('logging', './logging')

export = exports
export * from './config'
export { default as EthereumClient } from './client'
Loading

0 comments on commit 79eaf15

Please sign in to comment.