Skip to content

Commit

Permalink
Client: Async VM Initialization (#3187)
Browse files Browse the repository at this point in the history
* Avoid VM double initialization, prepare for async setup for VM & friends

* Move to async VM setup, fix tests

* Remove unnecessary vm property in tx pool

* Test fixes

* Fix getPayloadV3.spec.ts test

* Fix client.spec.ts test

* Small miner test optimization

* Fix RPC net_version test

* Small lint fix

* client: fix getProof test

* client: lint

* Fix miner test

* adjust test timeouts

---------

Co-authored-by: acolytec3 <[email protected]>
Co-authored-by: Amir <[email protected]>
Co-authored-by: Jochem Brouwer <[email protected]>
  • Loading branch information
4 people authored Dec 12, 2023
1 parent 9698584 commit 98fc3ab
Show file tree
Hide file tree
Showing 44 changed files with 232 additions and 169 deletions.
78 changes: 35 additions & 43 deletions packages/client/src/execution/vmexecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type ChainStatus = {
export class VMExecution extends Execution {
private _lock = new Lock()

public vm: VM
public vm!: VM
public merkleVM: VM | undefined
public verkleVM: VM | undefined
public hardfork: string = ''
Expand Down Expand Up @@ -89,15 +89,7 @@ export class VMExecution extends Execution {
constructor(options: ExecutionOptions) {
super(options)

if (this.config.vm === undefined) {
if (this.config.chainCommon.gteHardfork(Hardfork.Prague)) {
this.setupVerkleVM()
this.vm = this.verkleVM!
} else {
this.setupMerkleVM()
this.vm = this.merkleVM!
}
} else {
if (this.config.vm !== undefined) {
this.vm = this.config.vm
;(this.vm as any).blockchain = this.chain.blockchain
}
Expand All @@ -124,11 +116,11 @@ export class VMExecution extends Execution {
}
}

setupMerkleVM() {
async setupMerkleVM() {
if (this.merkleVM !== undefined) {
return
}
const trie = new Trie({
const trie = await Trie.create({
db: new LevelDB(this.stateDB),
useKeyHashing: true,
cacheSize: this.config.trieCache,
Expand Down Expand Up @@ -160,22 +152,23 @@ export class VMExecution extends Execution {
size: this.config.codeCache,
},
})
this.merkleVM = new (VM as any)({
this.merkleVM = await VM.create({
common: this.config.execCommon,
blockchain: this.chain.blockchain,
stateManager,
profilerOpts: this.config.vmProfilerOpts,
})
this.vm = this.merkleVM
}

setupVerkleVM() {
async setupVerkleVM() {
if (this.verkleVM !== undefined) {
return
}

this.config.logger.info(`Setting up verkleVM`)
const stateManager = new StatelessVerkleStateManager()
this.verkleVM = new (VM as any)({
this.verkleVM = await VM.create({
common: this.config.execCommon,
blockchain: this.chain.blockchain,
stateManager,
Expand All @@ -190,13 +183,13 @@ export class VMExecution extends Execution {

return this.runWithLock<void>(async () => {
if (this.merkleVM === undefined) {
this.setupMerkleVM()
await this.setupMerkleVM()
}
const merkleVM = this.merkleVM!
const merkleStateManager = merkleVM.stateManager as DefaultStateManager

if (this.verkleVM === undefined) {
this.setupVerkleVM()
await this.setupVerkleVM()
}
const verkleVM = this.verkleVM!
const verkleStateManager = verkleVM.stateManager as StatelessVerkleStateManager
Expand All @@ -223,7 +216,21 @@ export class VMExecution extends Execution {
return
}

await this.vm.init()
if (this.config.execCommon.gteHardfork(Hardfork.Prague)) {
if (!this.config.statelessVerkle) {
throw Error(`Currently stateful verkle execution not supported`)
}
this.config.logger.info(`Skipping VM verkle statemanager genesis hardfork=${this.hardfork}`)
await this.setupVerkleVM()
this.vm = this.verkleVM!
} else {
this.config.logger.info(
`Initializing VM merkle statemanager genesis hardfork=${this.hardfork}`
)
await this.setupMerkleVM()
this.vm = this.merkleVM!
}

if (typeof this.vm.blockchain.getIteratorHead !== 'function') {
throw new Error('cannot get iterator head: blockchain has no getIteratorHead function')
}
Expand All @@ -235,6 +242,14 @@ export class VMExecution extends Execution {
root: stateRoot,
hash: headBlock.hash(),
}
if (number === BIGINT_0) {
const genesisState =
this.chain['_customGenesisState'] ?? getGenesis(Number(this.vm.common.chainId()))
if (!genesisState) {
throw new Error('genesisState not available')
}
await this.vm.stateManager.generateCanonicalGenesis(genesisState)
}

if (typeof this.vm.blockchain.getTotalDifficulty !== 'function') {
throw new Error('cannot get iterator head: blockchain has no getTotalDifficulty function')
Expand All @@ -243,29 +258,6 @@ export class VMExecution extends Execution {
this.config.execCommon.setHardforkBy({ blockNumber: number, td, timestamp })
this.hardfork = this.config.execCommon.hardfork()

if (this.config.execCommon.gteHardfork(Hardfork.Prague)) {
if (!this.config.statelessVerkle) {
throw Error(`Currently stateful verkle execution not supported`)
}
this.config.logger.info(`Skipping VM verkle statemanager genesis hardfork=${this.hardfork}`)
this.setupVerkleVM()
this.vm = this.verkleVM!
} else {
this.config.logger.info(
`Initializing VM merkle statemanager genesis hardfork=${this.hardfork}`
)
this.setupMerkleVM()
this.vm = this.merkleVM!
if (number === BIGINT_0) {
const genesisState =
this.chain['_customGenesisState'] ?? getGenesis(Number(this.vm.common.chainId()))
if (!genesisState) {
throw new Error('genesisState not available')
}
await this.vm.stateManager.generateCanonicalGenesis(genesisState)
}
}

await super.open()
// TODO: Should a run be started to execute any left over blocks?
// void this.run()
Expand Down Expand Up @@ -305,11 +297,11 @@ export class VMExecution extends Execution {
})
if (this.config.execCommon.gteHardfork(Hardfork.Prague)) {
// verkleVM should already exist but we can still do an allocation just to be safe
this.setupVerkleVM()
await this.setupVerkleVM()
this.vm = this.verkleVM!
} else {
// its could be a rest to a pre-merkle when the chain was never initialized
this.setupMerkleVM()
await this.setupMerkleVM()
this.vm = this.merkleVM!
}

Expand Down
4 changes: 1 addition & 3 deletions packages/client/src/service/txpool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ type GasPrice = {
export class TxPool {
private config: Config
private service: FullEthereumService
private vm: VM

private opened: boolean

Expand Down Expand Up @@ -170,7 +169,6 @@ export class TxPool {
constructor(options: TxPoolOptions) {
this.config = options.config
this.service = options.service
this.vm = this.service.execution.vm

this.pool = new Map<UnprefixedAddress, TxPoolObject[]>()
this.txsInPool = 0
Expand Down Expand Up @@ -316,7 +314,7 @@ export class TxPool {
}

// Copy VM in order to not overwrite the state root of the VMExecution module which may be concurrently running blocks
const vmCopy = await this.vm.shallowCopy()
const vmCopy = await this.service.execution.vm.shallowCopy()
// Set state root to latest block so that account balance is correct when doing balance check
await vmCopy.stateManager.setStateRoot(block.stateRoot)
let account = await vmCopy.stateManager.getAccount(senderAddress)
Expand Down
1 change: 1 addition & 0 deletions packages/client/test/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ describe('[EthereumClient]', async () => {
const server = new Server() as any
const config = new Config({ server, accountCache: 10000, storageCache: 1000 })
const client = await EthereumClient.create({ config, metaDB: new MemoryLevel() })
await (client.services[0] as any)['execution'].setupMerkleVM()
await client.start()
assert.ok(client.started, 'started')
assert.equal(await client.start(), false, 'already started')
Expand Down
20 changes: 17 additions & 3 deletions packages/client/test/miner/miner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ describe('[Miner]', async () => {
validateDifficulty: () => undefined,
},
validateHeader: () => {},
getIteratorHead: () => {
return Block.fromBlockData({ header: { number: 1 } })
},
getTotalDifficulty: () => {
return 1n
},
// eslint-disable-next-line no-invalid-this
shallowCopy: () => this.blockchain,
_init: async () => undefined,
Expand Down Expand Up @@ -225,6 +231,7 @@ describe('[Miner]', async () => {
})
const miner = new Miner({ config: customConfig, service, skipHardForkValidation: true })
const { txPool } = service
await service.execution.open()
const { vm } = service.execution

txPool.start()
Expand Down Expand Up @@ -257,8 +264,8 @@ describe('[Miner]', async () => {
// no skipHardForkValidation
const miner = new Miner({ config: goerliConfig, service })
const { txPool } = service
await service.execution.setupMerkleVM()
const { vm } = service.execution

txPool.start()
miner.start()

Expand Down Expand Up @@ -297,6 +304,7 @@ describe('[Miner]', async () => {
})
const miner = new Miner({ config: customConfig, service, skipHardForkValidation: true })
const { txPool } = service
await service.execution.open()
const { vm } = service.execution
txPool.start()
miner.start()
Expand Down Expand Up @@ -346,6 +354,7 @@ describe('[Miner]', async () => {
})
const miner = new Miner({ config, service, skipHardForkValidation: true })
const { txPool } = service
await service.execution.open()
const { vm, receiptsManager } = service.execution
txPool.start()
miner.start()
Expand Down Expand Up @@ -417,6 +426,7 @@ describe('[Miner]', async () => {
})
const miner = new Miner({ config, service, skipHardForkValidation: true })
const { txPool } = service
await service.execution.open()
const { vm } = service.execution
txPool.start()
miner.start()
Expand Down Expand Up @@ -448,7 +458,10 @@ describe('[Miner]', async () => {
it("assembleBlocks() -> should stop assembling a block after it's full", async () => {
const chain = new FakeChain() as any
const gasLimit = 100000
const block = Block.fromBlockData({ header: { gasLimit } }, { common: customCommon })
const block = Block.fromBlockData(
{ header: { gasLimit } },
{ common: customCommon, setHardfork: true }
)
Object.defineProperty(chain, 'headers', {
get() {
return { latest: block.header, height: BigInt(0) }
Expand All @@ -463,6 +476,7 @@ describe('[Miner]', async () => {
config: customConfig,
chain,
})
await service.execution.open()
const miner = new Miner({ config: customConfig, service, skipHardForkValidation: true })
const { txPool } = service
const { vm } = service.execution
Expand Down Expand Up @@ -492,7 +506,7 @@ describe('[Miner]', async () => {
miner.stop()
txPool.stop()
}
await (miner as any).queueNextAssembly(0)
await miner['queueNextAssembly'](0)
await wait(500)
})
/*****************************************************************************************
Expand Down
7 changes: 5 additions & 2 deletions packages/client/test/miner/pendingBlock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const setup = () => {
shallowCopy: () => service.execution.vm,
setStateRoot: () => {},
blockchain: mockBlockchain({}),
common: new Common({ chain: 'mainnet' }),
},
},
}
Expand Down Expand Up @@ -218,7 +219,7 @@ describe('[PendingBlock]', async () => {

// set gas limit low so that can accomodate 2 txs
const prevGasLimit = common['_chainParams'].genesis.gasLimit
common['_chainParams'].genesis.gasLimit = BigInt(50000)
common['_chainParams'].genesis.gasLimit = 50000

const vm = await VM.create({ common })
await setBalance(vm, A.address, BigInt(5000000000000000))
Expand Down Expand Up @@ -337,7 +338,9 @@ describe('[PendingBlock]', async () => {
it('should throw when blockchain does not have getTotalDifficulty function', async () => {
const { txPool } = setup()
const pendingBlock = new PendingBlock({ config, txPool, skipHardForkValidation: true })
const vm = (txPool as any).vm
const vm = txPool['service'].execution.vm
// override total difficulty function to trigger error case
vm.blockchain.getTotalDifficulty = undefined
try {
await pendingBlock.start(vm, new Block())
assert.fail('should have thrown')
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/admin/nodeInfo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const method = 'admin_nodeInfo'

describe(method, () => {
it('works', async () => {
const manager = createManager(createClient({ opened: true }))
const manager = createManager(await createClient({ opened: true }))
const rpc = getRpcClient(startRPC(manager.getMethods()))

const res = await rpc.request(method, [])
Expand Down
4 changes: 2 additions & 2 deletions packages/client/test/rpc/debug/traceCall.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import type { RpcTx } from '../../../src/rpc/types.js'

const method = 'debug_traceCall'

describe(method, () => {
const manager = createManager(createClient({ opened: true }))
describe(method, async () => {
const manager = createManager(await createClient({ opened: true }))
const methods = manager.getMethods()
const server = startRPC(methods)
const rpc = getRpcClient(server)
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/debug/traceTransaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const method = 'debug_traceTransaction'

describe(method, () => {
it('call with invalid configuration', async () => {
const { rpc } = baseSetup({ engine: false, includeVM: true })
const { rpc } = await baseSetup({ engine: false, includeVM: true })

const res = await rpc.request(method, ['0xabcd', {}])
assert.equal(res.error.code, INTERNAL_ERROR)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const method = 'engine_exchangeCapabilities'

describe(method, () => {
it('call with invalid payloadId', async () => {
const { rpc } = baseSetup({ engine: true })
const { rpc } = await baseSetup({ engine: true })

const res = await rpc.request(method, [])

Expand Down
4 changes: 2 additions & 2 deletions packages/client/test/rpc/engine/forkchoiceUpdatedV1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const validPayload = [validForkChoiceState, validPayloadAttributes]

describe(method, () => {
it('call with invalid head block hash without 0x', async () => {
const { rpc } = baseSetup({ engine: true, includeVM: true })
const { rpc } = await baseSetup({ engine: true, includeVM: true })
const invalidForkChoiceState = {
...validForkChoiceState,
headBlockHash: 'invalid formatted head block hash',
Expand All @@ -66,7 +66,7 @@ describe(method, () => {
})

it('call with invalid hex string as block hash', async () => {
const { rpc } = baseSetup({ engine: true, includeVM: true })
const { rpc } = await baseSetup({ engine: true, includeVM: true })

const invalidForkChoiceState = {
...validForkChoiceState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const method = 'engine_getPayloadBodiesByHashV1'

describe(method, () => {
it('call with too many hashes', async () => {
const { rpc } = baseSetup({ engine: true, includeVM: true })
const { rpc } = await baseSetup({ engine: true, includeVM: true })
const tooManyHashes: string[] = []
for (let x = 0; x < 35; x++) {
tooManyHashes.push(bytesToHex(randomBytes(32)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ const method = 'engine_getPayloadBodiesByRangeV1'

describe(method, () => {
it('call with too many hashes', async () => {
const { rpc } = baseSetup({ engine: true, includeVM: true })
const { rpc } = await baseSetup({ engine: true, includeVM: true })

const res = await rpc.request(method, ['0x1', '0x55'])
assert.equal(res.error.code, TOO_LARGE_REQUEST)
assert.ok(res.error.message.includes('More than 32 execution payload bodies requested'))
})

it('call with invalid parameters', async () => {
const { rpc } = baseSetup({ engine: true, includeVM: true })
const { rpc } = await baseSetup({ engine: true, includeVM: true })

const res = await rpc.request(method, ['0x0', '0x0'])
assert.equal(res.error.code, INVALID_PARAMS)
Expand Down
Loading

0 comments on commit 98fc3ab

Please sign in to comment.