Skip to content

Commit

Permalink
client -> execution refactor: directly integrate VM.runBlockchain() c…
Browse files Browse the repository at this point in the history
…ode for the client runBlocks() logic
  • Loading branch information
holgerd77 committed Jan 22, 2021
1 parent 7633061 commit 413a392
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 63 deletions.
6 changes: 6 additions & 0 deletions packages/client/lib/sync/execution/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @module execution
*/

export * from './execution'
export * from './vmexecution'
127 changes: 73 additions & 54 deletions packages/client/lib/sync/execution/vmexecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import { short } from '../../util'
import VM from '@ethereumjs/vm'
import { DefaultStateManager } from '@ethereumjs/vm/dist/state'
import { SecureTrie as Trie } from '@ethereumjs/trie'
import { Block } from '@ethereumjs/block'

export class VMExecution extends Execution {
public vm: VM

private vmPromise?: Promise<void | number>
private stopSyncing = false
public syncing = false
private vmPromise?: Promise<number | undefined>

private NUM_BLOCKS_PER_ITERATION = 50

/**
* Create new VM excution module
Expand Down Expand Up @@ -36,75 +39,91 @@ export class VMExecution extends Execution {
}
}

/**
* This updates the VM once blocks were put in the VM
*/
async runBlocks() {
if (this.running) {
if (this.running || !this.syncing) {
return
}
this.running = true
let blockCounter = 0

let txCounter = 0
const NUM_BLOCKS_PER_LOG_MSG = 50
try {
let oldHead = Buffer.alloc(0)
const newHeadBlock = await this.vm.blockchain.getHead()
let newHead = newHeadBlock.hash()
let firstHeadBlock = newHeadBlock
let lastHeadBlock = newHeadBlock
let numExecuted: number | undefined

const blockchain = this.vm.blockchain
let startHeadBlock = await this.vm.blockchain.getHead()
let canonicalHead = await this.vm.blockchain.getLatestBlock()

let headBlock: Block | undefined
let parentState: Buffer | undefined

while (
(numExecuted === undefined || numExecuted === this.NUM_BLOCKS_PER_ITERATION) &&
!startHeadBlock.hash().equals(canonicalHead.hash()) &&
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (!newHead.equals(oldHead) && !this.stopSyncing) {
oldHead = newHead
this.vmPromise = this.vm.runBlockchain(this.vm.blockchain, 1)
const numExecuted = (await this.vmPromise) as number
if (numExecuted === 0) {
this.config.logger.warn(
`No blocks executed past chain head hash=${short(
newHead
)} number=${newHeadBlock.header.number.toNumber()}`
)
this.running = false
return 0
}
const headBlock = await this.vm.blockchain.getHead()
newHead = headBlock.hash()
if (blockCounter === 0) {
firstHeadBlock = headBlock
}
// check if we did run a new block:
if (!newHead.equals(oldHead)) {
blockCounter += 1
txCounter += headBlock.transactions.length
lastHeadBlock = headBlock
this.syncing
) {
headBlock = undefined
parentState = undefined

if (blockCounter >= NUM_BLOCKS_PER_LOG_MSG) {
const firstNumber = firstHeadBlock.header.number.toNumber()
const firstHash = short(firstHeadBlock.hash())
const lastNumber = lastHeadBlock.header.number.toNumber()
const lastHash = short(lastHeadBlock.hash())
this.config.logger.info(
`Executed blocks count=${blockCounter} first=${firstNumber} hash=${firstHash} last=${lastNumber} hash=${lastHash} with txs=${txCounter}`
)
blockCounter = 0
txCounter = 0
this.vmPromise = blockchain.iterator(
'vm',
async (block: Block, reorg: boolean) => {
// determine starting state for block run
// if we are just starting or if a chain re-org has happened
if (!headBlock || reorg) {
const parentBlock = await blockchain!.getBlock(block.header.parentHash)
parentState = parentBlock.header.stateRoot
// generate genesis state if we are at the genesis block
// we don't have the genesis state
if (!headBlock) {
await this.vm.stateManager.generateCanonicalGenesis()
} else {
parentState = headBlock.header.stateRoot
}
}
}
// run block, update head if valid
try {
await this.vm.runBlock({ block, root: parentState })
txCounter += block.transactions.length
// set as new head block
headBlock = block
} catch (error) {
// remove invalid block
await blockchain!.delBlock(block.header.hash())
throw error
}
},
this.NUM_BLOCKS_PER_ITERATION
)
numExecuted = (await this.vmPromise) as number

const endHeadBlock = await this.vm.blockchain.getHead()
if (numExecuted > 0) {
const firstNumber = startHeadBlock.header.number.toNumber()
const firstHash = short(startHeadBlock.hash())
const lastNumber = endHeadBlock.header.number.toNumber()
const lastHash = short(endHeadBlock.hash())
this.config.logger.info(
`Executed blocks count=${numExecuted} first=${firstNumber} hash=${firstHash} last=${lastNumber} hash=${lastHash} with txs=${txCounter}`
)
} else {
this.config.logger.warn(
`No blocks executed past chain head hash=${short(
endHeadBlock.hash()
)} number=${endHeadBlock.header.number.toNumber()}`
)
}
} catch (error) {
this.emit('error', error)
} finally {
this.running = false
startHeadBlock = endHeadBlock
canonicalHead = await this.vm.blockchain.getLatestBlock()
}
return blockCounter
this.running = false
return numExecuted
}

/**
* Stop VM execution. Returns a promise that resolves once its stopped.
* @returns {Promise}
*/
async stop(): Promise<boolean> {
this.stopSyncing = true
if (this.vmPromise) {
// ensure that we wait that the VM finishes executing the block (and flushing the trie cache)
await this.vmPromise
Expand Down
5 changes: 4 additions & 1 deletion packages/client/lib/sync/fullsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export class FullSynchronizer extends Synchronizer {
async open(): Promise<void> {
await this.chain.open()
await this.pool.open()
this.execution.syncing = true
const number = this.chain.blocks.height.toString(10)
const td = this.chain.blocks.td.toString(10)
const hash = this.chain.blocks.latest!.hash()
Expand All @@ -172,10 +173,12 @@ export class FullSynchronizer extends Synchronizer {
* @return {Promise}
*/
async stop(): Promise<boolean> {
this.execution.syncing = false
await this.execution.stop()

if (!this.running) {
return false
}
await this.execution.stop()

if (this.blockFetcher) {
this.blockFetcher.destroy()
Expand Down
4 changes: 2 additions & 2 deletions packages/client/test/integration/fullethereumservice.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ tape('[Integration:FullEthereumService]', async (t) => {
config: serviceConfig,
chain,
})
// Set runningBlocks to true to skip VM execution
service.synchronizer.runningBlocks = true
// Set syncing to false to skip VM execution
service.synchronizer.execution.syncing = false
await service.open()
await server.start()
await service.start()
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/integration/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function setup(
...serviceOpts,
lightserv: true,
})
service.synchronizer.runningBlocks = true
service.synchronizer.execution.syncing = false
}
await service.open()
await service.start()
Expand Down
17 changes: 12 additions & 5 deletions packages/client/test/sync/execution/vmexecution.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import tape from 'tape-catch'
import td from 'testdouble'
import { BN } from 'ethereumjs-util'
//import { BN } from 'ethereumjs-util'
import VM from '@ethereumjs/vm'
import Blockchain from '@ethereumjs/blockchain'
import { Config } from '../../../lib/config'
Expand Down Expand Up @@ -30,11 +30,18 @@ tape('[FullSynchronizer]', async (t) => {
config,
chain,
})
const oldHead = exec.vm.blockchain.getHead()
exec.syncing = true
const oldHead = await exec.vm.blockchain.getHead()
await exec.runBlocks()
t.deepEqual(exec.vm.blockchain.getHead(), oldHead, 'should not modify blockchain on emtpy run')
t.deepEqual(
(await exec.vm.blockchain.getHead()).hash(),
oldHead.hash(),
'should not modify blockchain on emtpy run'
)

blockchain.getHead = td.func<any>()
//TODO: replace with testdata blockchain tests, mocking not feasible on
// block execution getting more complex
/*blockchain.getHead = td.func<any>()
const getHeadResponse: any = []
for (let i = 2; i <= 100; i++) {
getHeadResponse.push({
Expand All @@ -56,7 +63,7 @@ tape('[FullSynchronizer]', async (t) => {
},
...getHeadResponse
)
t.equal(await exec.runBlocks(), 49)
t.equal(await exec.runBlocks(), 49)*/

t.end()
})
Expand Down

0 comments on commit 413a392

Please sign in to comment.