Skip to content

Commit ec0f059

Browse files
authored
Merge pull request #1028 from ethereumjs/client-add-vm-execution-rebase
Client: add VM execution
2 parents 01031f2 + dc2c99e commit ec0f059

File tree

23 files changed

+307
-77
lines changed

23 files changed

+307
-77
lines changed

packages/client/bin/cli.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,21 @@ let logger: Logger | null = null
9191
* @param config
9292
*/
9393
async function runNode(config: Config) {
94-
const syncDataDir = config.getSyncDataDirectory()
95-
fs.ensureDirSync(syncDataDir)
96-
config.logger.info(`Sync data directory: ${syncDataDir}`)
94+
const chainDataDir = config.getChainDataDirectory()
95+
fs.ensureDirSync(chainDataDir)
96+
const stateDataDir = config.getStateDataDirectory()
97+
fs.ensureDirSync(stateDataDir)
98+
99+
config.logger.info(`Data directory: ${config.datadir}`)
97100

98101
config.logger.info('Initializing Ethereumjs client...')
99102
if (config.lightserv) {
100103
config.logger.info(`Serving light peer requests`)
101104
}
102105
const client = new EthereumClient({
103106
config,
104-
db: level(syncDataDir),
107+
chainDB: level(chainDataDir),
108+
stateDB: level(stateDataDir),
105109
})
106110
client.on('error', (err: any) => config.logger.error(err))
107111
client.on('listening', (details: any) => {
@@ -145,7 +149,7 @@ async function run() {
145149
common,
146150
syncmode: args.syncmode,
147151
lightserv: args.lightserv,
148-
datadir: `${os.homedir()}/Library/Ethereum`,
152+
datadir: `${os.homedir()}/Library/Ethereum/ethereumjs`,
149153
transports: args.transports,
150154
rpc: args.rpc,
151155
rpcport: args.rpcport,

packages/client/lib/blockchain/chain.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface ChainOptions {
1717
/**
1818
* Database to store blocks and metadata. Should be an abstract-leveldown compliant store.
1919
*/
20-
db?: LevelUp
20+
chainDB?: LevelUp
2121

2222
/**
2323
* Specify a blockchain which implements the Chain interface
@@ -79,7 +79,7 @@ export interface GenesisBlockParams {
7979
export class Chain extends EventEmitter {
8080
public config: Config
8181

82-
public db: LevelUp
82+
public chainDB: LevelUp
8383
public blockchain: Blockchain
8484
public opened: boolean
8585

@@ -107,13 +107,13 @@ export class Chain extends EventEmitter {
107107
this.blockchain =
108108
options.blockchain ??
109109
new Blockchain({
110-
db: options.db,
110+
db: options.chainDB,
111111
common: this.config.common,
112112
validateBlocks: false,
113113
validateConsensus: false,
114114
})
115115

116-
this.db = this.blockchain.db
116+
this.chainDB = this.blockchain.db
117117
this.opened = false
118118
}
119119

packages/client/lib/client.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ export interface EthereumClientOptions {
1313
*
1414
* Default: Database created by the Blockchain class
1515
*/
16-
db?: LevelUp
16+
chainDB?: LevelUp
17+
18+
/**
19+
* Database to store the state. Should be an abstract-leveldown compliant store.
20+
*
21+
* Default: Database created by the Trie class
22+
*/
23+
stateDB?: LevelUp
1724

1825
/* List of bootnodes to use for discovery */
1926
bootnodes?: BootnodeLike[]
@@ -51,11 +58,12 @@ export default class EthereumClient extends events.EventEmitter {
5158
this.config.syncmode === 'full'
5259
? new FullEthereumService({
5360
config: this.config,
54-
db: options.db,
61+
chainDB: options.chainDB,
62+
stateDB: options.stateDB,
5563
})
5664
: new LightEthereumService({
5765
config: this.config,
58-
db: options.db,
66+
chainDB: options.chainDB,
5967
}),
6068
]
6169
this.opened = false

packages/client/lib/config.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,25 @@ export class Config {
184184
}
185185

186186
/**
187-
* Returns the directory for storing the client sync data
187+
* Returns the directory for storing the client chain data
188188
* based on syncmode and selected chain (subdirectory of 'datadir')
189189
*/
190-
getSyncDataDirectory(): string {
191-
const syncDirName = this.syncmode === 'light' ? 'lightchaindata' : 'chaindata'
192-
const chain = this.common.chainName()
193-
const networkDirName = chain === 'mainnet' ? '' : `${chain}/`
190+
getChainDataDirectory(): string {
191+
const networkDirName = this.common.chainName()
192+
const chainDataDirName = this.syncmode === 'light' ? 'lightchain' : 'chain'
194193

195-
const dataDir = `${this.datadir}/${networkDirName}ethereumjs/${syncDirName}`
194+
const dataDir = `${this.datadir}/${networkDirName}/${chainDataDirName}`
195+
return dataDir
196+
}
197+
198+
/**
199+
* Returns the directory for storing the client state data
200+
* based selected chain (subdirectory of 'datadir')
201+
*/
202+
getStateDataDirectory(): string {
203+
const networkDirName = this.common.chainName()
204+
205+
const dataDir = `${this.datadir}/${networkDirName}/state`
196206
return dataDir
197207
}
198208
}

packages/client/lib/service/ethereumservice.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ export interface EthereumServiceOptions extends ServiceOptions {
99
chain?: Chain
1010

1111
/* Blockchain database */
12-
db?: LevelUp
12+
chainDB?: LevelUp
13+
14+
/* State database */
15+
stateDB?: LevelUp
1316

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

17-
/* Protocol timeout in ms (default: 2000) */
20+
/* Protocol timeout in ms (default: 4000) */
1821
timeout?: number
1922
}
2023

@@ -39,7 +42,7 @@ export class EthereumService extends Service {
3942
this.flow = new FlowControl()
4043
this.chain = options.chain ?? new Chain(options)
4144
this.interval = options.interval ?? 8000
42-
this.timeout = options.timeout ?? 2000
45+
this.timeout = options.timeout ?? 4000
4346
}
4447

4548
/**

packages/client/lib/service/fullethereumservice.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class FullEthereumService extends EthereumService {
3333
config: this.config,
3434
pool: this.pool,
3535
chain: this.chain,
36+
stateDB: options.stateDB,
3637
interval: this.interval,
3738
})
3839
}

packages/client/lib/sync/fetcher/blockfetcherbase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export abstract class BlockFetcherBase<JobResult, StorageItem> extends Fetcher<
3535
super(options)
3636

3737
this.chain = options.chain
38-
this.maxPerRequest = options.maxPerRequest ?? 128
38+
this.maxPerRequest = options.maxPerRequest ?? 50
3939
this.first = options.first
4040
this.count = options.count
4141
}

packages/client/lib/sync/fetcher/fetcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface FetcherOptions {
2424
/* Max write queue size (default: 16) */
2525
maxQueue?: number
2626

27-
/* Max items per request (default: 128) */
27+
/* Max items per request (default: 50) */
2828
maxPerRequest?: number
2929

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

7979
this.in = new Heap({
8080
comparBefore: (

packages/client/lib/sync/fullsync.ts

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,105 @@
11
import { BN } from 'ethereumjs-util'
2-
import VM from '@ethereumjs/vm'
32
import { Peer } from '../net/peer/peer'
43
import { short } from '../util'
54
import { Synchronizer, SynchronizerOptions } from './sync'
65
import { BlockFetcher } from './fetcher'
76
import { Block } from '@ethereumjs/block'
7+
import VM from '@ethereumjs/vm'
8+
import { DefaultStateManager } from '@ethereumjs/vm/dist/state'
9+
import { SecureTrie as Trie } from '@ethereumjs/trie'
810

911
/**
1012
* Implements an ethereum full sync synchronizer
1113
* @memberof module:sync
1214
*/
1315
export class FullSynchronizer extends Synchronizer {
16+
private blockFetcher: BlockFetcher | null
17+
1418
public vm: VM
19+
public runningBlocks: boolean
1520

16-
private blockFetcher: BlockFetcher | null
21+
private stopSyncing: boolean
22+
private vmPromise?: Promise<void>
23+
24+
// Tracking vars for log msg condensation on zero tx blocks
25+
private NUM_ZERO_TXS_PER_LOG_MSG = 50
26+
public zeroTxsBlockLogMsgCounter: number = 0
1727

1828
constructor(options: SynchronizerOptions) {
1929
super(options)
2030
this.blockFetcher = null
2131

2232
if (!this.config.vm) {
33+
const trie = new Trie(this.stateDB)
34+
35+
const stateManager = new DefaultStateManager({
36+
common: this.config.common,
37+
trie,
38+
})
39+
2340
this.vm = new VM({
2441
common: this.config.common,
2542
blockchain: this.chain.blockchain,
43+
stateManager,
2644
})
2745
} else {
2846
this.vm = this.config.vm
2947
//@ts-ignore blockchain has readonly property
3048
this.vm.blockchain = this.chain.blockchain
3149
}
50+
51+
this.runningBlocks = false
52+
this.stopSyncing = false
53+
54+
const self = this
55+
this.chain.on('updated', async function () {
56+
// for some reason, if we use .on('updated', this.runBlocks), it runs in the context of the Chain and not in the FullSync context..?
57+
await self.runBlocks()
58+
})
59+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
60+
this.chain.update()
61+
}
62+
63+
/**
64+
* This updates the VM once blocks were put in the VM
65+
*/
66+
async runBlocks() {
67+
if (!this.running || this.runningBlocks) {
68+
return
69+
}
70+
this.runningBlocks = true
71+
try {
72+
let oldHead = Buffer.alloc(0)
73+
let newHead = (await this.vm.blockchain.getHead()).hash()
74+
while (!newHead.equals(oldHead) && !this.stopSyncing) {
75+
oldHead = newHead
76+
this.vmPromise = this.vm.runBlockchain(this.vm.blockchain, 1)
77+
await this.vmPromise
78+
const headBlock = await this.vm.blockchain.getHead()
79+
newHead = headBlock.hash()
80+
// check if we did run a new block:
81+
if (!newHead.equals(oldHead)) {
82+
const number = headBlock.header.number.toNumber()
83+
const hash = short(newHead)
84+
const numTxs = headBlock.transactions.length
85+
if (numTxs === 0) {
86+
this.zeroTxsBlockLogMsgCounter += 1
87+
}
88+
if (
89+
(numTxs > 0 && this.zeroTxsBlockLogMsgCounter > 0) ||
90+
(numTxs === 0 && this.zeroTxsBlockLogMsgCounter >= this.NUM_ZERO_TXS_PER_LOG_MSG)
91+
) {
92+
this.config.logger.info(`Processed ${this.zeroTxsBlockLogMsgCounter} blocks with 0 txs`)
93+
this.zeroTxsBlockLogMsgCounter = 0
94+
}
95+
if (numTxs > 0) {
96+
this.config.logger.info(`Executed block number=${number} hash=${hash} txs=${numTxs}`)
97+
}
98+
}
99+
}
100+
} finally {
101+
this.runningBlocks = false
102+
}
32103
}
33104

34105
/**
@@ -167,6 +238,13 @@ export class FullSynchronizer extends Synchronizer {
167238
* @return {Promise}
168239
*/
169240
async stop(): Promise<boolean> {
241+
this.stopSyncing = true
242+
if (this.vmPromise) {
243+
// ensure that we wait that the VM finishes executing the block (and flushes the trie cache)
244+
await this.vmPromise
245+
}
246+
await this.stateDB?.close()
247+
170248
if (!this.running) {
171249
return false
172250
}
@@ -177,6 +255,7 @@ export class FullSynchronizer extends Synchronizer {
177255
delete this.blockFetcher
178256
}
179257
await super.stop()
258+
180259
return true
181260
}
182261
}

packages/client/lib/sync/sync.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Peer } from '../net/peer/peer'
44
import { FlowControl } from '../net/protocol'
55
import { Config } from '../config'
66
import { Chain } from '../blockchain'
7+
import { LevelUp } from 'levelup'
78

89
export interface SynchronizerOptions {
910
/* Config */
@@ -15,6 +16,9 @@ export interface SynchronizerOptions {
1516
/* Blockchain */
1617
chain: Chain
1718

19+
/* State database */
20+
stateDB?: LevelUp
21+
1822
/* Flow control manager */
1923
flow?: FlowControl
2024

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

3236
protected pool: PeerPool
3337
protected chain: Chain
38+
protected stateDB?: LevelUp
3439
protected flow: FlowControl
3540
protected interval: number
36-
protected running: boolean
41+
public running: boolean
3742
protected forceSync: boolean
3843

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

4853
this.pool = options.pool
4954
this.chain = options.chain
55+
this.stateDB = options.stateDB
5056
this.flow = options.flow ?? new FlowControl()
5157
this.interval = options.interval ?? 1000
5258
this.running = false
@@ -59,7 +65,7 @@ export abstract class Synchronizer extends EventEmitter {
5965
})
6066
}
6167

62-
abstract async sync(): Promise<boolean>
68+
abstract sync(): Promise<boolean>
6369

6470
/**
6571
* Returns synchronizer type

0 commit comments

Comments
 (0)