diff --git a/packages/client/src/rpc/modules/debug.ts b/packages/client/src/rpc/modules/debug.ts index c99331957c..9562c5cf3f 100644 --- a/packages/client/src/rpc/modules/debug.ts +++ b/packages/client/src/rpc/modules/debug.ts @@ -148,6 +148,9 @@ export class Debug { 1, [[validators.hex]], ) + this.setHead = middleware(callWithStackTrace(this.setHead.bind(this), this._rpcDebug), 1, [ + [validators.blockOption], + ]) this.verbosity = middleware(callWithStackTrace(this.verbosity.bind(this), this._rpcDebug), 1, [ [validators.unsignedInteger], ]) @@ -457,4 +460,30 @@ export class Debug { this.client.config.logger.configure({ level: logLevels[level] }) return `level: ${this.client.config.logger.level}` } + + /** + * Sets the current head of the local chain by block number. Note, this is a + * destructive action and may severely damage your chain. Use with extreme + * caution. + * @param blockOpt Block number or tag to set as head of chain + */ + async setHead(params: [string]) { + const [blockOpt] = params + if (blockOpt === 'pending') { + throw { + code: INVALID_PARAMS, + message: `"pending" is not supported`, + } + } + + const block = await getBlockByOption(blockOpt, this.chain) + try { + await this.service.skeleton?.setHead(block, true) + await this.service.execution.setHead([block]) + } catch (e) { + throw { + code: INTERNAL_ERROR, + } + } + } } diff --git a/packages/client/test/execution/vmexecution.spec.ts b/packages/client/test/execution/vmexecution.spec.ts index 4b30946748..50c9db8fd6 100644 --- a/packages/client/test/execution/vmexecution.spec.ts +++ b/packages/client/test/execution/vmexecution.spec.ts @@ -8,7 +8,7 @@ import { assert, describe, it } from 'vitest' import { Chain } from '../../src/blockchain/index.js' import { Config } from '../../src/config.js' import { VMExecution } from '../../src/execution/index.js' -import { closeRPC, setupChain } from '../rpc/helpers.js' +import { closeRPC, setupChain, testSetup } from '../rpc/helpers.js' import { goerliData } from '../testdata/blocks/goerli.js' import { mainnetData } from '../testdata/blocks/mainnet.js' import { testnetData } from '../testdata/common/testnet.js' @@ -94,15 +94,6 @@ describe('[VMExecution]', () => { assert.equal(exec.vm, vm, 'should use vm provided') }) - async function testSetup(blockchain: Blockchain, common?: Common) { - const config = new Config({ common, accountCache: 10000, storageCache: 1000 }) - const chain = await Chain.create({ config, blockchain }) - const exec = new VMExecution({ config, chain }) - await chain.open() - await exec.open() - return exec - } - it('Block execution / Hardforks PoW (mainnet)', async () => { let blockchain = await createBlockchain({ validateBlocks: true, diff --git a/packages/client/test/rpc/debug/setHead.spec.ts b/packages/client/test/rpc/debug/setHead.spec.ts new file mode 100644 index 0000000000..78121b3d1a --- /dev/null +++ b/packages/client/test/rpc/debug/setHead.spec.ts @@ -0,0 +1,48 @@ +import { createBlockchainFromBlocksData } from '@ethereumjs/blockchain' +import { assert, describe, it } from 'vitest' + +import { mainnetData } from '../../testdata/blocks/mainnet.js' +import { createClient, createManager, getRPCClient, startRPC, testSetup } from '../helpers.js' + +import type { Blockchain } from '@ethereumjs/blockchain' + +const method = 'debug_setHead' + +describe(method, async () => { + it('call with valid arguments', async () => { + const blockchain = await createBlockchainFromBlocksData(mainnetData, { + validateBlocks: true, + validateConsensus: false, + }) + const blocks = await blockchain.getBlocks(0, 6, 0, false) + const exec = await testSetup(blockchain) + await exec.run() + const newHead = await (exec.vm.blockchain as Blockchain).getIteratorHead!() + assert.equal(newHead.header.number, BigInt(5), 'should run all blocks') + + const a = await createClient({ blockchain }) + await a.service.skeleton?.open() + ;(a.service.execution as any) = exec + + const manager = createManager(a) + const rpc = getRPCClient(startRPC(manager.getMethods())) + assert.equal( + await a.service.skeleton?.headHash(), + undefined, + 'should return undefined when head is not set', + ) + for (let i = 0; i < blocks.length; i++) { + await rpc.request(method, [`0x${i}`]) + assert.deepEqual( + await a.service.skeleton?.headHash()!, + blocks[i].header.hash(), + `skeleton chain should return hash of block number ${i} set as head`, + ) + assert.deepEqual( + a.service.execution.chainStatus?.hash!, + blocks[i].header.hash(), + `vm execution should set hash to new head`, + ) + } + }, 30000) +}) diff --git a/packages/client/test/rpc/helpers.ts b/packages/client/test/rpc/helpers.ts index 71d71d5251..5796bc4441 100644 --- a/packages/client/test/rpc/helpers.ts +++ b/packages/client/test/rpc/helpers.ts @@ -348,3 +348,12 @@ export const batchBlocks = async (rpc: HttpClient, inputBlocks: any[]) => { assert.equal(res.result.status, 'VALID') } } + +export async function testSetup(blockchain: Blockchain, common?: Common) { + const config = new Config({ common, accountCache: 10000, storageCache: 1000 }) + const chain = await Chain.create({ config, blockchain }) + const exec = new VMExecution({ config, chain }) + await chain.open() + await exec.open() + return exec +}