Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client: add VM execution #1028

Merged
merged 26 commits into from
Jan 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
04e9de1
client -> vm execution: removed headerfetcher -> blockfetcher inherit…
holgerd77 Dec 15, 2020
8116cda
client -> vm execution: moved VM to BlockFetcher, added VM execution
holgerd77 Dec 15, 2020
416597f
client -> vm execution: use vm.runBlockchain(), fix execution run, ad…
holgerd77 Dec 21, 2020
3f373e2
client: move VM and block execution logic to FullSync
jochem-brouwer Dec 21, 2020
8ad02f8
client: fullsync: handle runBlocks concurrency
jochem-brouwer Dec 22, 2020
451bba3
client -> vm execution: went back to 250 max import blocks (maxPerReq…
holgerd77 Dec 22, 2020
22af4af
client, vm -> vm execution: removed checkpointing in runBlock for blo…
holgerd77 Dec 22, 2020
75daf4b
client -> vm execution: added condensed zero txs block execution log msg
holgerd77 Dec 22, 2020
709aa59
client -> vm execution: fixed linting in vm
holgerd77 Dec 22, 2020
08e15ba
client -> vm execution: lowered maxPerRequest back to 50 due to conne…
holgerd77 Dec 22, 2020
9cc4ae9
client -> vm execution: fixed oldHead <-> newHead semantic switch
holgerd77 Dec 22, 2020
79e1095
client -> VM execution: fixed VM test
holgerd77 Dec 30, 2020
7c68d3f
client -> VM execution: rebase (fetcher typesafe improvements) fixes
holgerd77 Jan 2, 2021
727304a
client -> VM execution: close stateDB, fixed fullsync tests
holgerd77 Dec 31, 2020
2bd2ff1
client -> VM execution: renamed chain db instances to chainDB for dif…
holgerd77 Dec 31, 2020
475ccfc
client -> VM execution: made stateDB a client option analogue to chainDB
holgerd77 Dec 31, 2020
c0ec528
client -> VM execution: unified and consolidated chain and state data…
holgerd77 Dec 31, 2020
33fd42a
client -> VM execution: added some Config directory method tests, add…
holgerd77 Jan 1, 2021
a29ddc5
client -> VM execution: added simple runBlocks test
holgerd77 Jan 1, 2021
4d2680a
client -> VM execution: added more runBlocks() test cases
holgerd77 Jan 1, 2021
e2817fe
client, devp2p -> VM execution: subdivided DPT findneighbour peer cal…
holgerd77 Jan 1, 2021
54e1a78
client -> VM execution: made state DB closing more resilient
holgerd77 Jan 1, 2021
28733ac
client -> VM execution: fixed full-/lightsync integration tests, some…
holgerd77 Jan 2, 2021
4b2d1cc
client, devp2p -> VM execution: increased protocol timeout to 4000 fo…
holgerd77 Jan 5, 2021
bd32047
client, devp2p -> VM execution: fixed devp2p linting error, use nulli…
holgerd77 Jan 5, 2021
dc2c99e
client, vm -> vm execution: removed stateDB from LightEthereumService…
holgerd77 Jan 6, 2021
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
14 changes: 9 additions & 5 deletions packages/client/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,21 @@ let logger: Logger | null = null
* @param config
*/
async function runNode(config: Config) {
const syncDataDir = config.getSyncDataDirectory()
fs.ensureDirSync(syncDataDir)
config.logger.info(`Sync data directory: ${syncDataDir}`)
const chainDataDir = config.getChainDataDirectory()
fs.ensureDirSync(chainDataDir)
const stateDataDir = config.getStateDataDirectory()
fs.ensureDirSync(stateDataDir)

config.logger.info(`Data directory: ${config.datadir}`)

config.logger.info('Initializing Ethereumjs client...')
if (config.lightserv) {
config.logger.info(`Serving light peer requests`)
}
const client = new EthereumClient({
config,
db: level(syncDataDir),
chainDB: level(chainDataDir),
stateDB: level(stateDataDir),
})
client.on('error', (err: any) => config.logger.error(err))
client.on('listening', (details: any) => {
Expand Down Expand Up @@ -145,7 +149,7 @@ async function run() {
common,
syncmode: args.syncmode,
lightserv: args.lightserv,
datadir: `${os.homedir()}/Library/Ethereum`,
datadir: `${os.homedir()}/Library/Ethereum/ethereumjs`,
transports: args.transports,
rpc: args.rpc,
rpcport: args.rpcport,
Expand Down
8 changes: 4 additions & 4 deletions packages/client/lib/blockchain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface ChainOptions {
/**
* Database to store blocks and metadata. Should be an abstract-leveldown compliant store.
*/
db?: LevelUp
chainDB?: LevelUp

/**
* Specify a blockchain which implements the Chain interface
Expand Down Expand Up @@ -79,7 +79,7 @@ export interface GenesisBlockParams {
export class Chain extends EventEmitter {
public config: Config

public db: LevelUp
public chainDB: LevelUp
public blockchain: Blockchain
public opened: boolean

Expand Down Expand Up @@ -107,13 +107,13 @@ export class Chain extends EventEmitter {
this.blockchain =
options.blockchain ??
new Blockchain({
db: options.db,
db: options.chainDB,
common: this.config.common,
validateBlocks: false,
validateConsensus: false,
})

this.db = this.blockchain.db
this.chainDB = this.blockchain.db
this.opened = false
}

Expand Down
14 changes: 11 additions & 3 deletions packages/client/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ export interface EthereumClientOptions {
*
* Default: Database created by the Blockchain class
*/
db?: LevelUp
chainDB?: LevelUp

/**
* Database to store the state. Should be an abstract-leveldown compliant store.
*
* Default: Database created by the Trie class
*/
stateDB?: LevelUp

/* List of bootnodes to use for discovery */
bootnodes?: BootnodeLike[]
Expand Down Expand Up @@ -51,11 +58,12 @@ export default class EthereumClient extends events.EventEmitter {
this.config.syncmode === 'full'
? new FullEthereumService({
config: this.config,
db: options.db,
chainDB: options.chainDB,
stateDB: options.stateDB,
})
: new LightEthereumService({
config: this.config,
db: options.db,
chainDB: options.chainDB,
}),
]
this.opened = false
Expand Down
22 changes: 16 additions & 6 deletions packages/client/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,25 @@ export class Config {
}

/**
* Returns the directory for storing the client sync data
* Returns the directory for storing the client chain data
* based on syncmode and selected chain (subdirectory of 'datadir')
*/
getSyncDataDirectory(): string {
const syncDirName = this.syncmode === 'light' ? 'lightchaindata' : 'chaindata'
const chain = this.common.chainName()
const networkDirName = chain === 'mainnet' ? '' : `${chain}/`
getChainDataDirectory(): string {
const networkDirName = this.common.chainName()
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
const chainDataDirName = this.syncmode === 'light' ? 'lightchain' : 'chain'
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved

const dataDir = `${this.datadir}/${networkDirName}ethereumjs/${syncDirName}`
const dataDir = `${this.datadir}/${networkDirName}/${chainDataDirName}`
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
return dataDir
}

/**
* Returns the directory for storing the client state data
* based selected chain (subdirectory of 'datadir')
*/
getStateDataDirectory(): string {
const networkDirName = this.common.chainName()

const dataDir = `${this.datadir}/${networkDirName}/state`
return dataDir
}
}
9 changes: 6 additions & 3 deletions packages/client/lib/service/ethereumservice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ export interface EthereumServiceOptions extends ServiceOptions {
chain?: Chain

/* Blockchain database */
db?: LevelUp
chainDB?: LevelUp

/* State database */
stateDB?: LevelUp

/* Sync retry interval in ms (default: 8000) */
interval?: number

/* Protocol timeout in ms (default: 2000) */
/* Protocol timeout in ms (default: 4000) */
timeout?: number
}

Expand All @@ -39,7 +42,7 @@ export class EthereumService extends Service {
this.flow = new FlowControl()
this.chain = options.chain ?? new Chain(options)
this.interval = options.interval ?? 8000
this.timeout = options.timeout ?? 2000
this.timeout = options.timeout ?? 4000
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/client/lib/service/fullethereumservice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class FullEthereumService extends EthereumService {
config: this.config,
pool: this.pool,
chain: this.chain,
stateDB: options.stateDB,
interval: this.interval,
})
}
Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/sync/fetcher/blockfetcherbase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export abstract class BlockFetcherBase<JobResult, StorageItem> extends Fetcher<
super(options)

this.chain = options.chain
this.maxPerRequest = options.maxPerRequest ?? 128
this.maxPerRequest = options.maxPerRequest ?? 50
this.first = options.first
this.count = options.count
}
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/sync/fetcher/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface FetcherOptions {
/* Max write queue size (default: 16) */
maxQueue?: number

/* Max items per request (default: 128) */
/* Max items per request (default: 50) */
maxPerRequest?: number

/* Retry interval in ms (default: 1000) */
Expand Down Expand Up @@ -74,7 +74,7 @@ export abstract class Fetcher<JobTask, JobResult, StorageItem> extends Readable
this.interval = options.interval ?? 1000
this.banTime = options.banTime ?? 60000
this.maxQueue = options.maxQueue ?? 16
this.maxPerRequest = options.maxPerRequest ?? 128
this.maxPerRequest = options.maxPerRequest ?? 50

this.in = new Heap({
comparBefore: (
Expand Down
83 changes: 81 additions & 2 deletions packages/client/lib/sync/fullsync.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,105 @@
import { BN } from 'ethereumjs-util'
import VM from '@ethereumjs/vm'
import { Peer } from '../net/peer/peer'
import { short } from '../util'
import { Synchronizer, SynchronizerOptions } from './sync'
import { BlockFetcher } from './fetcher'
import { Block } from '@ethereumjs/block'
import VM from '@ethereumjs/vm'
import { DefaultStateManager } from '@ethereumjs/vm/dist/state'
import { SecureTrie as Trie } from '@ethereumjs/trie'

/**
* Implements an ethereum full sync synchronizer
* @memberof module:sync
*/
export class FullSynchronizer extends Synchronizer {
private blockFetcher: BlockFetcher | null

public vm: VM
public runningBlocks: boolean

private blockFetcher: BlockFetcher | null
private stopSyncing: boolean
private vmPromise?: Promise<void>

// Tracking vars for log msg condensation on zero tx blocks
private NUM_ZERO_TXS_PER_LOG_MSG = 50
public zeroTxsBlockLogMsgCounter: number = 0
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I don't think need the number type annotation since it's being initialized with 0


constructor(options: SynchronizerOptions) {
super(options)
this.blockFetcher = null

if (!this.config.vm) {
const trie = new Trie(this.stateDB)

const stateManager = new DefaultStateManager({
common: this.config.common,
trie,
})

this.vm = new VM({
common: this.config.common,
blockchain: this.chain.blockchain,
stateManager,
})
} else {
this.vm = this.config.vm
//@ts-ignore blockchain has readonly property
this.vm.blockchain = this.chain.blockchain
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
}

this.runningBlocks = false
this.stopSyncing = false

const self = this
this.chain.on('updated', async function () {
// for some reason, if we use .on('updated', this.runBlocks), it runs in the context of the Chain and not in the FullSync context..?
await self.runBlocks()
})
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.chain.update()
}

/**
* This updates the VM once blocks were put in the VM
*/
async runBlocks() {
if (!this.running || this.runningBlocks) {
return
}
this.runningBlocks = true
try {
let oldHead = Buffer.alloc(0)
let newHead = (await this.vm.blockchain.getHead()).hash()
while (!newHead.equals(oldHead) && !this.stopSyncing) {
oldHead = newHead
this.vmPromise = this.vm.runBlockchain(this.vm.blockchain, 1)
await this.vmPromise
const headBlock = await this.vm.blockchain.getHead()
newHead = headBlock.hash()
// check if we did run a new block:
if (!newHead.equals(oldHead)) {
const number = headBlock.header.number.toNumber()
const hash = short(newHead)
const numTxs = headBlock.transactions.length
if (numTxs === 0) {
this.zeroTxsBlockLogMsgCounter += 1
}
if (
(numTxs > 0 && this.zeroTxsBlockLogMsgCounter > 0) ||
(numTxs === 0 && this.zeroTxsBlockLogMsgCounter >= this.NUM_ZERO_TXS_PER_LOG_MSG)
) {
this.config.logger.info(`Processed ${this.zeroTxsBlockLogMsgCounter} blocks with 0 txs`)
this.zeroTxsBlockLogMsgCounter = 0
}
if (numTxs > 0) {
this.config.logger.info(`Executed block number=${number} hash=${hash} txs=${numTxs}`)
}
}
}
} finally {
this.runningBlocks = false
}
}

/**
Expand Down Expand Up @@ -167,6 +238,13 @@ export class FullSynchronizer extends Synchronizer {
* @return {Promise}
*/
async stop(): Promise<boolean> {
this.stopSyncing = true
if (this.vmPromise) {
// ensure that we wait that the VM finishes executing the block (and flushes the trie cache)
await this.vmPromise
}
await this.stateDB?.close()

if (!this.running) {
return false
}
Expand All @@ -177,6 +255,7 @@ export class FullSynchronizer extends Synchronizer {
delete this.blockFetcher
}
await super.stop()

return true
}
}
10 changes: 8 additions & 2 deletions packages/client/lib/sync/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Peer } from '../net/peer/peer'
import { FlowControl } from '../net/protocol'
import { Config } from '../config'
import { Chain } from '../blockchain'
import { LevelUp } from 'levelup'

export interface SynchronizerOptions {
/* Config */
Expand All @@ -15,6 +16,9 @@ export interface SynchronizerOptions {
/* Blockchain */
chain: Chain

/* State database */
stateDB?: LevelUp

/* Flow control manager */
flow?: FlowControl

Expand All @@ -31,9 +35,10 @@ export abstract class Synchronizer extends EventEmitter {

protected pool: PeerPool
protected chain: Chain
protected stateDB?: LevelUp
protected flow: FlowControl
protected interval: number
protected running: boolean
public running: boolean
protected forceSync: boolean

/**
Expand All @@ -47,6 +52,7 @@ export abstract class Synchronizer extends EventEmitter {

this.pool = options.pool
this.chain = options.chain
this.stateDB = options.stateDB
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need stateDB here, since it does not seem we use it anywhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

I put it there and not directly in FullSynchronizer since I thought this might be more flexible if we want to use stateDB in other synchronizers. Do you want me to move over to FullSynchronizer directly? Don't have a strong opinion on this myself, think these are all things which can be easily evolved and adopted further down the road.

Copy link
Member

Choose a reason for hiding this comment

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

I have no strong opinion either, it is OK to keep it here for now, if we focus on the light client then we can remove it if it turns out it is unnecessary.

this.flow = options.flow ?? new FlowControl()
this.interval = options.interval ?? 1000
this.running = false
Expand All @@ -59,7 +65,7 @@ export abstract class Synchronizer extends EventEmitter {
})
}

abstract async sync(): Promise<boolean>
abstract sync(): Promise<boolean>

/**
* Returns synchronizer type
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"lint:fix": "ethereumjs-config-lint-fix",
"tape": "tape -r ts-node/register",
"test": "npm run test:unit && npm run test:integration && npm run test:browser",
"test:unit": "npm run tape -- 'test/!(integration)/**/*.spec.ts'",
"test:unit": "npm run tape -- 'test/!(integration)/**/*.spec.ts' 'test/*.spec.ts'",
"test:integration": "npm run tape -- 'test/integration/**/*.spec.ts'",
"test:browser": "karma start karma.conf.js"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/blockchain/chain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ tape('[Chain]', (t) => {
t.test('should test blockchain DB is initialized', async (t) => {
const chain = new Chain({ config })

const db = chain.db
const db = chain.chainDB
const testKey = 'name'
const testValue = 'test'

Expand Down
Loading