Skip to content

Commit 053a3a1

Browse files
ryanioacolytec3
andauthored
client/engine: speed and resiliency improvements (#1827)
* CLConnectionManager: start on first updateStatus in case PreMerge wasn't reached before merge * chain: safer closing to not cause db corruption during engine requests * parse: some improvements and reorganization * vm/client/engine: add ability to add blocks to blockchain without setting the head. this allows us to do runBlock work in newPayload once, and not again in fcU * CLConnectionManager: improve logging output * use ellipsis character * add space to align with fcu log msg * add last payload txs count * add baseFee to last payload log * fix type doc ref * add gasUsed to last payload log * rename PreMerge to MergeForkBlock for increased clarity use hardfork enums in vm supportedHardforks * format numbers to locale string, use compact num for gasUsed update from 5 to 4 chars on each side of hash * fix tests, use `pass` instead of `ok` * various fixes, remove toLocaleString, improve short func * add plural to timeDiffStr if not 1 * Add coverage on `formatNonce` * Add CLConnectionManager test skeleton and fix `running` method * Add forkchoice/newpayload tests * Remove listeners on test * Tighten up tests to check for specific block * More connection manager constructor tests * nits * remove leading zeros on cumulative gas in eth_getTransactionsReceipt (thanks @cbrzn) Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com>
1 parent c969f1f commit 053a3a1

File tree

20 files changed

+434
-253
lines changed

20 files changed

+434
-253
lines changed

packages/client/lib/blockchain/chain.ts

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,7 @@ export class Chain {
178178
* @returns false if chain is already open, otherwise void
179179
*/
180180
async open(): Promise<boolean | void> {
181-
if (this.opened) {
182-
return false
183-
}
184-
181+
if (this.opened) return false
185182
await this.blockchain.db.open()
186183
await this.blockchain.initPromise
187184
this.opened = true
@@ -206,9 +203,7 @@ export class Chain {
206203
* @returns false if chain is closed, otherwise void
207204
*/
208205
async close(): Promise<boolean | void> {
209-
if (!this.opened) {
210-
return false
211-
}
206+
if (!this.opened) return false
212207
this.reset()
213208
await this.blockchain.db.close()
214209
this.opened = false
@@ -220,9 +215,7 @@ export class Chain {
220215
* @returns false if chain is closed, otherwise void
221216
*/
222217
async update(emit = true): Promise<boolean | void> {
223-
if (!this.opened) {
224-
return false
225-
}
218+
if (!this.opened) return false
226219

227220
const headers: ChainHeaders = {
228221
latest: null,
@@ -263,7 +256,7 @@ export class Chain {
263256
* @returns an array of the blocks
264257
*/
265258
async getBlocks(block: Buffer | BN, max = 1, skip = 0, reverse = false): Promise<Block[]> {
266-
await this.open()
259+
if (!this.opened) throw new Error('Chain closed')
267260
return this.blockchain.getBlocks(block, max, skip, reverse)
268261
}
269262

@@ -273,7 +266,7 @@ export class Chain {
273266
* @throws if block is not found
274267
*/
275268
async getBlock(block: Buffer | BN): Promise<Block> {
276-
await this.open()
269+
if (!this.opened) throw new Error('Chain closed')
277270
return this.blockchain.getBlock(block)
278271
}
279272

@@ -284,10 +277,9 @@ export class Chain {
284277
* @returns number of blocks added
285278
*/
286279
async putBlocks(blocks: Block[], fromEngine = false): Promise<number> {
287-
if (blocks.length === 0) {
288-
return 0
289-
}
290-
await this.open()
280+
if (!this.opened) throw new Error('Chain closed')
281+
if (blocks.length === 0) return 0
282+
291283
let numAdded = 0
292284
for (const [i, b] of blocks.entries()) {
293285
if (!fromEngine && this.config.chainCommon.gteHardfork(Hardfork.Merge)) {
@@ -334,10 +326,9 @@ export class Chain {
334326
* @returns number of headers added
335327
*/
336328
async putHeaders(headers: BlockHeader[], mergeIncludes = false): Promise<number> {
337-
if (headers.length === 0) {
338-
return 0
339-
}
340-
await this.open()
329+
if (!this.opened) throw new Error('Chain closed')
330+
if (headers.length === 0) return 0
331+
341332
let numAdded = 0
342333
for (const [i, h] of headers.entries()) {
343334
if (!mergeIncludes && this.config.chainCommon.gteHardfork(Hardfork.Merge)) {
@@ -363,15 +354,15 @@ export class Chain {
363354
* Gets the latest header in the canonical chain
364355
*/
365356
async getLatestHeader(): Promise<BlockHeader> {
366-
await this.open()
357+
if (!this.opened) throw new Error('Chain closed')
367358
return this.blockchain.getLatestHeader()
368359
}
369360

370361
/**
371362
* Gets the latest block in the canonical chain
372363
*/
373364
async getLatestBlock(): Promise<Block> {
374-
await this.open()
365+
if (!this.opened) throw new Error('Chain closed')
375366
return this.blockchain.getLatestBlock()
376367
}
377368

@@ -382,7 +373,7 @@ export class Chain {
382373
* @returns the td
383374
*/
384375
async getTd(hash: Buffer, num: BN): Promise<BN> {
385-
await this.open()
376+
if (!this.opened) throw new Error('Chain closed')
386377
return this.blockchain.getTotalDifficulty(hash, num)
387378
}
388379
}

packages/client/lib/execution/vmexecution.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import {
2+
DBSetTD,
3+
DBSaveLookups,
4+
DBSetBlockOrHeader,
5+
DBSetHashToNumber,
6+
} from '@ethereumjs/blockchain/dist/db/helpers'
17
import { ConsensusType, Hardfork } from '@ethereumjs/common'
28
import VM from '@ethereumjs/vm'
39
import { DefaultStateManager } from '@ethereumjs/vm/dist/state'
@@ -6,16 +12,20 @@ import { short } from '../util'
612
import { debugCodeReplayBlock } from '../util/debug'
713
import { Event } from '../types'
814
import { Execution, ExecutionOptions } from './execution'
9-
import type { Block } from '@ethereumjs/block'
1015
import { ReceiptsManager } from './receipt'
16+
import type { Block } from '@ethereumjs/block'
17+
import type { RunBlockOpts } from '@ethereumjs/vm/dist/runBlock'
18+
import type { TxReceipt } from '@ethereumjs/vm/dist/types'
1119

1220
export class VMExecution extends Execution {
1321
public vm: VM
1422
public hardfork: string = ''
1523

1624
public receiptsManager?: ReceiptsManager
25+
private pendingReceipts?: Map<string, TxReceipt[]>
1726
private vmPromise?: Promise<number | undefined>
1827

28+
/** Number of maximum blocks to run per iteration of {@link VMExecution.run} */
1929
private NUM_BLOCKS_PER_ITERATION = 50
2030

2131
/**
@@ -48,6 +58,7 @@ export class VMExecution extends Execution {
4858
config: this.config,
4959
metaDB: this.metaDB,
5060
})
61+
this.pendingReceipts = new Map()
5162
}
5263
}
5364

@@ -66,6 +77,51 @@ export class VMExecution extends Execution {
6677
}
6778
}
6879

80+
/**
81+
* Executes the block, runs the necessary verification on it,
82+
* and persists the block and the associate state into the database.
83+
* The key difference is it won't do the canonical chain updating.
84+
* It relies on the additional {@link VMExecution.setHead} call to finalize
85+
* the entire procedure.
86+
* @param receipts If we built this block, pass the receipts to not need to run the block again
87+
*/
88+
async runWithoutSetHead(opts: RunBlockOpts, receipts?: TxReceipt[]): Promise<void> {
89+
const { block } = opts
90+
if (receipts === undefined) {
91+
const result = await this.vm.runBlock(opts)
92+
receipts = result.receipts
93+
}
94+
if (receipts) {
95+
// Save receipts
96+
this.pendingReceipts?.set(block.hash().toString('hex'), receipts)
97+
}
98+
// Bypass updating head by using blockchain db directly
99+
const [hash, num] = [block.hash(), block.header.number]
100+
const td = (await this.chain.getTd(block.header.parentHash, block.header.number.subn(1))).add(
101+
block.header.difficulty
102+
)
103+
await this.chain.blockchain.dbManager.batch([
104+
DBSetTD(td, num, hash),
105+
...DBSetBlockOrHeader(block),
106+
DBSetHashToNumber(hash, num),
107+
...DBSaveLookups(hash, num),
108+
])
109+
}
110+
111+
/**
112+
* Sets the chain to set the specified new head block.
113+
* Should only be used after {@link VMExecution.runWithoutSetHead}
114+
*/
115+
async setHead(block: Block): Promise<void> {
116+
await this.chain.putBlocks([block], true)
117+
const receipts = this.pendingReceipts?.get(block.hash().toString('hex'))
118+
if (receipts) {
119+
void this.receiptsManager?.saveReceipts(block, receipts)
120+
this.pendingReceipts?.delete(block.hash().toString('hex'))
121+
}
122+
await this.chain.blockchain.setIteratorHead('vm', block.hash())
123+
}
124+
69125
/**
70126
* Runs the VM execution
71127
*

packages/client/lib/miner/pendingBlock.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { randomBytes } from 'crypto'
22
import type VM from '@ethereumjs/vm'
3+
import type { TxReceipt } from '@ethereumjs/vm/dist/types'
34
import type { BlockBuilder } from '@ethereumjs/vm/dist/buildBlock'
45
import type { Block, HeaderData } from '@ethereumjs/block'
56
import type { TypedTransaction } from '@ethereumjs/tx'
@@ -113,7 +114,7 @@ export class PendingBlock {
113114
/**
114115
* Returns the completed block
115116
*/
116-
async build(payloadId: Buffer): Promise<Block | void> {
117+
async build(payloadId: Buffer): Promise<void | [block: Block, receipts: TxReceipt[]]> {
117118
const payload = this.pendingPayloads.find((p) => p[0].equals(payloadId))
118119
if (!payload) {
119120
return
@@ -165,6 +166,6 @@ export class PendingBlock {
165166
// Remove from pendingPayloads
166167
this.pendingPayloads = this.pendingPayloads.filter((p) => !p[0].equals(payloadId))
167168

168-
return block
169+
return [block, builder.transactionReceipts]
169170
}
170171
}

0 commit comments

Comments
 (0)