From b9a386ce0904b47462044ce48b5ff28a7e0186e6 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Mon, 2 Sep 2024 13:50:29 -0400 Subject: [PATCH] client: kaustinen7 verkle testnet preparation (#3433) * client: remove initialVerkleStateRoot cli param * block: add parentStateRoot field to executionWitness * verkle: merkle to basic data leaf * verkle: address merge conflicts and refactors * verkle: use verkle code chunk size constant * verkle: adjust util tests * Fix build * remove stateroot from verifyVerkleProof * Add reserved bytes to encodeBasicData function * Merge commit + reserve bytes [no ci] * match EIP (bigEndian encoding) [no ci] * Switch to basic data packing [no ci] * skip tests that need kaustinen7 data --------- Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com> --- packages/block/src/block/block.ts | 2 + packages/block/src/from-beacon-payload.ts | 3 + packages/client/bin/cli.ts | 6 -- packages/client/src/config.ts | 3 - packages/client/src/execution/vmexecution.ts | 1 - packages/common/src/interfaces.ts | 2 +- packages/evm/scripts/stackDeltaGenerator.ts | 4 +- packages/evm/src/opcodes/gas.ts | 42 ++------ packages/statemanager/src/accessWitness.ts | 38 ++----- .../src/statelessVerkleStateManager.ts | 90 +++++++--------- .../test/statelessVerkleStateManager.spec.ts | 24 ++--- packages/util/src/verkle.ts | 102 +++++++++++++----- packages/util/test/verkle.spec.ts | 22 ++-- packages/vm/src/runBlock.ts | 6 +- .../vm/test/api/EIPs/eip-6800-verkle.spec.ts | 3 +- 15 files changed, 159 insertions(+), 189 deletions(-) diff --git a/packages/block/src/block/block.ts b/packages/block/src/block/block.ts index 890b6f43f4..18c170cb37 100644 --- a/packages/block/src/block/block.ts +++ b/packages/block/src/block/block.ts @@ -109,6 +109,8 @@ export class Block { // undefined indicates that the executionWitness should be initialized with the default state if (this.common.isActivatedEIP(6800) && this.executionWitness === undefined) { this.executionWitness = { + // TODO: Evaluate how default parentStateRoot should be handled? + parentStateRoot: '0x', stateDiff: [], verkleProof: { commitmentsByPath: [], diff --git a/packages/block/src/from-beacon-payload.ts b/packages/block/src/from-beacon-payload.ts index 6a213d9e93..15bd90e1ac 100644 --- a/packages/block/src/from-beacon-payload.ts +++ b/packages/block/src/from-beacon-payload.ts @@ -82,15 +82,18 @@ type VerkleStateDiffSnakeJson = { } type VerkleExecutionWitnessSnakeJson = { + parent_state_root: PrefixedHexString state_diff: VerkleStateDiffSnakeJson[] verkle_proof: VerkleProofSnakeJson } function parseExecutionWitnessFromSnakeJson({ + parent_state_root, state_diff, verkle_proof, }: VerkleExecutionWitnessSnakeJson): VerkleExecutionWitness { return { + parentStateRoot: parent_state_root, stateDiff: state_diff.map(({ stem, suffix_diffs }) => ({ stem, suffixDiffs: suffix_diffs.map(({ current_value, new_value, suffix }) => ({ diff --git a/packages/client/bin/cli.ts b/packages/client/bin/cli.ts index b07643b535..0067810b83 100755 --- a/packages/client/bin/cli.ts +++ b/packages/client/bin/cli.ts @@ -474,12 +474,6 @@ const args: ClientOpts = yargs boolean: true, hidden: true, }) - .option('initialVerkleStateRoot', { - describe: - 'Provides an initial stateRoot to start the StatelessVerkleStateManager. This is required to bootstrap verkle witness proof verification, since they depend on the stateRoot of the parent block', - string: true, - coerce: (initialVerkleStateRoot: PrefixedHexString) => hexToBytes(initialVerkleStateRoot), - }) .option('useJsCrypto', { describe: 'Use pure Javascript cryptography functions', boolean: true, diff --git a/packages/client/src/config.ts b/packages/client/src/config.ts index 1887149b31..389e615e07 100644 --- a/packages/client/src/config.ts +++ b/packages/client/src/config.ts @@ -338,7 +338,6 @@ export interface ConfigOptions { statelessVerkle?: boolean startExecution?: boolean ignoreStatelessInvalidExecs?: boolean - initialVerkleStateRoot?: Uint8Array /** * Enables Prometheus Metrics that can be collected for monitoring client health @@ -451,7 +450,6 @@ export class Config { public readonly statelessVerkle: boolean public readonly startExecution: boolean public readonly ignoreStatelessInvalidExecs: boolean - public readonly initialVerkleStateRoot: Uint8Array public synchronized: boolean public lastSynchronized?: boolean @@ -545,7 +543,6 @@ export class Config { this.ignoreStatelessInvalidExecs = options.ignoreStatelessInvalidExecs ?? false this.metrics = options.prometheusMetrics - this.initialVerkleStateRoot = options.initialVerkleStateRoot ?? new Uint8Array() // Start it off as synchronized if this is configured to mine or as single node this.synchronized = this.isSingleNode ?? this.mine diff --git a/packages/client/src/execution/vmexecution.ts b/packages/client/src/execution/vmexecution.ts index 70b0617b9e..238d3f3082 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -203,7 +203,6 @@ export class VMExecution extends Execution { this.config.logger.info(`Setting up verkleVM`) const verkleCrypto = await loadVerkleCrypto() const stateManager = new StatelessVerkleStateManager({ - initialStateRoot: this.config.initialVerkleStateRoot, verkleCrypto, }) await mcl.init(mcl.BLS12_381) diff --git a/packages/common/src/interfaces.ts b/packages/common/src/interfaces.ts index b1832d7428..54e6ec96ef 100644 --- a/packages/common/src/interfaces.ts +++ b/packages/common/src/interfaces.ts @@ -167,7 +167,7 @@ export interface StateManagerInterface { executionWitness?: VerkleExecutionWitness | null, accessWitness?: AccessWitnessInterface, ): void - verifyVerkleProof?(stateRoot: Uint8Array): boolean + verifyVerkleProof?(): boolean verifyPostState?(): boolean checkChunkWitnessPresent?(contract: Address, programCounter: number): Promise getAppliedKey?(address: Uint8Array): Uint8Array // only for preimages diff --git a/packages/evm/scripts/stackDeltaGenerator.ts b/packages/evm/scripts/stackDeltaGenerator.ts index d0914c8ee6..27b12a0c4c 100644 --- a/packages/evm/scripts/stackDeltaGenerator.ts +++ b/packages/evm/scripts/stackDeltaGenerator.ts @@ -6,7 +6,7 @@ class OpcodeInfo { opcode: number, inputs: number, outputs: number, - opSize: number + opSize: number, ) { return this.parse(instr, opcode, inputs, outputs, opSize) } @@ -16,7 +16,7 @@ class OpcodeInfo { opcode: number, inputs: number, outputs: number, - opSize: number + opSize: number, ) { return this.parse(instr, opcode, inputs, outputs, opSize) } diff --git a/packages/evm/src/opcodes/gas.ts b/packages/evm/src/opcodes/gas.ts index 05c576f47d..c06583f663 100644 --- a/packages/evm/src/opcodes/gas.ts +++ b/packages/evm/src/opcodes/gas.ts @@ -6,10 +6,8 @@ import { BIGINT_31, BIGINT_32, BIGINT_64, - VERKLE_BALANCE_LEAF_KEY, + VERKLE_BASIC_DATA_LEAF_KEY, VERKLE_CODE_HASH_LEAF_KEY, - VERKLE_CODE_SIZE_LEAF_KEY, - VERKLE_VERSION_LEAF_KEY, bigIntToBytes, getVerkleTreeIndexesForStorageSlot, setLengthLeft, @@ -95,7 +93,7 @@ export const dynamicGasHandlers: Map BIGINT_0) { gas += runState.env.accessWitness!.touchAddressOnWriteAndComputeGas( contractAddress, 0, - VERKLE_BALANCE_LEAF_KEY, + VERKLE_BASIC_DATA_LEAF_KEY, ) } @@ -1057,14 +1031,14 @@ export const dynamicGasHandlers: Map BIGINT_0) { selfDestructToColdAccessGas += runState.env.accessWitness!.touchAddressOnWriteAndComputeGas( selfdestructToAddress, 0, - VERKLE_BALANCE_LEAF_KEY, + VERKLE_BASIC_DATA_LEAF_KEY, ) } diff --git a/packages/statemanager/src/accessWitness.ts b/packages/statemanager/src/accessWitness.ts index 0bbedc7a07..4acd61670f 100644 --- a/packages/statemanager/src/accessWitness.ts +++ b/packages/statemanager/src/accessWitness.ts @@ -1,14 +1,11 @@ import { BIGINT_0, - VERKLE_BALANCE_LEAF_KEY, + VERKLE_BASIC_DATA_LEAF_KEY, VERKLE_CODE_HASH_LEAF_KEY, VERKLE_CODE_OFFSET, - VERKLE_CODE_SIZE_LEAF_KEY, VERKLE_HEADER_STORAGE_OFFSET, VERKLE_MAIN_STORAGE_OFFSET, VERKLE_NODE_WIDTH, - VERKLE_NONCE_LEAF_KEY, - VERKLE_VERSION_LEAF_KEY, bytesToBigInt, bytesToHex, getVerkleKey, @@ -89,11 +86,8 @@ export class AccessWitness implements AccessWitnessInterface { touchAndChargeProofOfAbsence(address: Address): bigint { let gas = BIGINT_0 - gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_VERSION_LEAF_KEY) - gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_BALANCE_LEAF_KEY) - gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_CODE_SIZE_LEAF_KEY) + gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY) gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_CODE_HASH_LEAF_KEY) - gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_NONCE_LEAF_KEY) return gas } @@ -101,8 +95,7 @@ export class AccessWitness implements AccessWitnessInterface { touchAndChargeMessageCall(address: Address): bigint { let gas = BIGINT_0 - gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_VERSION_LEAF_KEY) - gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_CODE_SIZE_LEAF_KEY) + gas += this.touchAddressOnReadAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY) return gas } @@ -110,8 +103,8 @@ export class AccessWitness implements AccessWitnessInterface { touchAndChargeValueTransfer(caller: Address, target: Address): bigint { let gas = BIGINT_0 - gas += this.touchAddressOnWriteAndComputeGas(caller, 0, VERKLE_BALANCE_LEAF_KEY) - gas += this.touchAddressOnWriteAndComputeGas(target, 0, VERKLE_BALANCE_LEAF_KEY) + gas += this.touchAddressOnWriteAndComputeGas(caller, 0, VERKLE_BASIC_DATA_LEAF_KEY) + gas += this.touchAddressOnWriteAndComputeGas(target, 0, VERKLE_BASIC_DATA_LEAF_KEY) return gas } @@ -119,8 +112,7 @@ export class AccessWitness implements AccessWitnessInterface { touchAndChargeContractCreateInit(address: Address): bigint { let gas = BIGINT_0 - gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_VERSION_LEAF_KEY) - gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_NONCE_LEAF_KEY) + gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY) return gas } @@ -128,11 +120,8 @@ export class AccessWitness implements AccessWitnessInterface { touchAndChargeContractCreateCompleted(address: Address): bigint { let gas = BIGINT_0 - gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_VERSION_LEAF_KEY) - gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_BALANCE_LEAF_KEY) - gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_CODE_SIZE_LEAF_KEY) + gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_BASIC_DATA_LEAF_KEY) gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_CODE_HASH_LEAF_KEY) - gas += this.touchAddressOnWriteAndComputeGas(address, 0, VERKLE_NONCE_LEAF_KEY) return gas } @@ -140,28 +129,21 @@ export class AccessWitness implements AccessWitnessInterface { touchTxOriginAndComputeGas(origin: Address): bigint { let gas = BIGINT_0 - gas += this.touchAddressOnReadAndComputeGas(origin, 0, VERKLE_VERSION_LEAF_KEY) - gas += this.touchAddressOnReadAndComputeGas(origin, 0, VERKLE_CODE_SIZE_LEAF_KEY) + gas += this.touchAddressOnReadAndComputeGas(origin, 0, VERKLE_BASIC_DATA_LEAF_KEY) gas += this.touchAddressOnReadAndComputeGas(origin, 0, VERKLE_CODE_HASH_LEAF_KEY) - gas += this.touchAddressOnWriteAndComputeGas(origin, 0, VERKLE_NONCE_LEAF_KEY) - gas += this.touchAddressOnWriteAndComputeGas(origin, 0, VERKLE_BALANCE_LEAF_KEY) - return gas } touchTxTargetAndComputeGas(target: Address, { sendsValue }: { sendsValue?: boolean } = {}) { let gas = BIGINT_0 - gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_VERSION_LEAF_KEY) - gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_CODE_SIZE_LEAF_KEY) gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_CODE_HASH_LEAF_KEY) - gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_NONCE_LEAF_KEY) if (sendsValue === true) { - gas += this.touchAddressOnWriteAndComputeGas(target, 0, VERKLE_BALANCE_LEAF_KEY) + gas += this.touchAddressOnWriteAndComputeGas(target, 0, VERKLE_BASIC_DATA_LEAF_KEY) } else { - gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_BALANCE_LEAF_KEY) + gas += this.touchAddressOnReadAndComputeGas(target, 0, VERKLE_BASIC_DATA_LEAF_KEY) } return gas diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index 8febedcfe8..94c988103c 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -2,13 +2,14 @@ import { Account, KECCAK256_NULL, KECCAK256_NULL_S, + VERKLE_CODE_CHUNK_SIZE, VerkleLeafType, bigIntToBytes, - bytesToBigInt, bytesToHex, - bytesToInt32, createPartialAccount, createPartialAccountFromRLP, + decodeVerkleLeafBasicData, + encodeVerkleLeafBasicData, getVerkleKey, getVerkleStem, getVerkleTreeKeyForCodeChunk, @@ -108,8 +109,6 @@ export class StatelessVerkleStateManager implements StateManagerInterface { this._caches = opts.caches - this._cachedStateRoot = opts.initialStateRoot - this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak256 if (opts.verkleCrypto === undefined) { @@ -195,7 +194,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } async checkChunkWitnessPresent(address: Address, codeOffset: number) { - const chunkId = codeOffset / 31 + const chunkId = codeOffset / VERKLE_CODE_CHUNK_SIZE const chunkKey = bytesToHex( await getVerkleTreeKeyForCodeChunk(address, chunkId, this.verkleCrypto), ) @@ -266,9 +265,9 @@ export class StatelessVerkleStateManager implements StateManagerInterface { // allocate the code and copy onto it from the available witness chunks const codeSize = account.codeSize // allocate enough to fit the last chunk - const accessedCode = new Uint8Array(codeSize + 31) + const accessedCode = new Uint8Array(codeSize + VERKLE_CODE_CHUNK_SIZE) - const chunks = Math.floor(codeSize / 31) + 1 + const chunks = Math.floor(codeSize / VERKLE_CODE_CHUNK_SIZE) + 1 for (let chunkId = 0; chunkId < chunks; chunkId++) { const chunkKey = bytesToHex( await getVerkleTreeKeyForCodeChunk(address, chunkId, this.verkleCrypto), @@ -280,7 +279,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { throw Error(errorMsg) } - const codeOffset = chunkId * 31 + const codeOffset = chunkId * VERKLE_CODE_CHUNK_SIZE // if code chunk was accessed as per the provided witnesses copy it over if (codeChunk !== undefined) { // actual code starts from index 1 in chunk, 0th index is if there are any push data bytes @@ -289,7 +288,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } else { // else fill this unaccessed segment with invalid opcode since the evm execution shouldn't // end up here - accessedCode.fill(0xfe, codeOffset, 31) + accessedCode.fill(0xfe, codeOffset, VERKLE_CODE_CHUNK_SIZE) } } @@ -392,27 +391,16 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } const stem = getVerkleStem(this.verkleCrypto, address, 0) - const versionKey = getVerkleKey(stem, VerkleLeafType.Version) - const balanceKey = getVerkleKey(stem, VerkleLeafType.Balance) - const nonceKey = getVerkleKey(stem, VerkleLeafType.Nonce) + const basicDataKey = getVerkleKey(stem, VerkleLeafType.BasicData) const codeHashKey = getVerkleKey(stem, VerkleLeafType.CodeHash) - const codeSizeKey = getVerkleKey(stem, VerkleLeafType.CodeSize) - const versionRaw = this._state[bytesToHex(versionKey)] - const balanceRaw = this._state[bytesToHex(balanceKey)] - const nonceRaw = this._state[bytesToHex(nonceKey)] + const basicDataRaw = this._state[bytesToHex(basicDataKey)] const codeHashRaw = this._state[bytesToHex(codeHashKey)] - const codeSizeRaw = this._state[bytesToHex(codeSizeKey)] // check if the account didn't exist if any of the basic keys have null - if (versionRaw === null || balanceRaw === null || nonceRaw === null || codeHashRaw === null) { + if (basicDataRaw === null || codeHashRaw === null) { // check any of the other key shouldn't have string input available as this account didn't exist - if ( - typeof versionRaw === `string` || - typeof balanceRaw === 'string' || - typeof nonceRaw === 'string' || - typeof codeHashRaw === 'string' - ) { + if (typeof basicDataRaw === `string` || typeof codeHashRaw === 'string') { const errorMsg = `Invalid witness for a non existing address=${address} stem=${bytesToHex( stem, )}` @@ -432,32 +420,25 @@ export class StatelessVerkleStateManager implements StateManagerInterface { throw Error(errorMsg) } - if ( - versionRaw === undefined && - balanceRaw === undefined && - nonceRaw === undefined && - codeHashRaw === undefined && - codeSizeRaw === undefined - ) { + if (basicDataRaw === undefined && codeHashRaw === undefined) { const errorMsg = `No witness bundled for address=${address} stem=${bytesToHex(stem)}` debug(errorMsg) throw Error(errorMsg) } + const { version, balance, nonce, codeSize } = decodeVerkleLeafBasicData( + hexToBytes(basicDataRaw), + ) + const account = createPartialAccount({ - version: typeof versionRaw === 'string' ? bytesToInt32(hexToBytes(versionRaw), true) : null, - balance: typeof balanceRaw === 'string' ? bytesToBigInt(hexToBytes(balanceRaw), true) : null, - nonce: typeof nonceRaw === 'string' ? bytesToBigInt(hexToBytes(nonceRaw), true) : null, + version, + balance, + nonce, codeHash: typeof codeHashRaw === 'string' ? hexToBytes(codeHashRaw) : null, // if codeSizeRaw is null, it means account didn't exist or it was EOA either way codeSize is 0 // if codeSizeRaw is undefined, then we pass in null which in our context of partial account means // not specified - codeSize: - typeof codeSizeRaw === 'string' - ? bytesToInt32(hexToBytes(codeSizeRaw), true) - : codeSizeRaw === null - ? 0 - : null, + codeSize, storageRoot: null, }) @@ -477,16 +458,15 @@ export class StatelessVerkleStateManager implements StateManagerInterface { if (this._caches?.account === undefined) { const stem = getVerkleStem(this.verkleCrypto, address, 0) - const balanceKey = getVerkleKey(stem, VerkleLeafType.Balance) - const nonceKey = getVerkleKey(stem, VerkleLeafType.Nonce) - const codeHashKey = getVerkleKey(stem, VerkleLeafType.CodeHash) - - const balanceBuf = setLengthRight(bigIntToBytes(account.balance, true), 32) - const nonceBuf = setLengthRight(bigIntToBytes(account.nonce, true), 32) + const basicDataKey = getVerkleKey(stem, VerkleLeafType.BasicData) + const basicDataBytes = encodeVerkleLeafBasicData({ + version: account.version, + balance: account.balance, + nonce: account.nonce, + codeSize: account.codeSize, + }) - this._state[bytesToHex(balanceKey)] = bytesToHex(balanceBuf) - this._state[bytesToHex(nonceKey)] = bytesToHex(nonceBuf) - this._state[bytesToHex(codeHashKey)] = bytesToHex(account.codeHash) + this._state[bytesToHex(basicDataKey)] = bytesToHex(basicDataBytes) } else { if (account !== undefined) { this._caches?.account?.put(address, account, true) @@ -520,13 +500,13 @@ export class StatelessVerkleStateManager implements StateManagerInterface { * @param {Uint8Array} stateRoot - The stateRoot to verify the executionWitness against * @returns {boolean} - Returns true if the executionWitness matches the provided stateRoot, otherwise false */ - verifyVerkleProof(stateRoot: Uint8Array): boolean { + verifyVerkleProof(): boolean { if (this._executionWitness === undefined) { debug('Missing executionWitness') return false } - return verifyVerkleProof(this.verkleCrypto, stateRoot, this._executionWitness) + return verifyVerkleProof(this.verkleCrypto, this._executionWitness) } // Verifies that the witness post-state matches the computed post-state @@ -686,8 +666,12 @@ export class StatelessVerkleStateManager implements StateManagerInterface { // we can only compare the actual code because to compare the first byte would // be very tricky and impossible in certain scenarios like when the previous code chunk // was not accessed and hence not even provided in the witness - const chunkSize = 31 - return bytesToHex(setLengthRight(code.slice(codeOffset, codeOffset + chunkSize), chunkSize)) + return bytesToHex( + setLengthRight( + code.slice(codeOffset, codeOffset + VERKLE_CODE_CHUNK_SIZE), + VERKLE_CODE_CHUNK_SIZE, + ), + ) } case AccessedStateType.Storage: { diff --git a/packages/statemanager/test/statelessVerkleStateManager.spec.ts b/packages/statemanager/test/statelessVerkleStateManager.spec.ts index 6fc153efae..5285f5c02a 100644 --- a/packages/statemanager/test/statelessVerkleStateManager.spec.ts +++ b/packages/statemanager/test/statelessVerkleStateManager.spec.ts @@ -4,10 +4,10 @@ import { createTxFromSerializedData } from '@ethereumjs/tx' import { Address, VerkleLeafType, - bytesToBigInt, bytesToHex, createAccount, createAddressFromString, + decodeVerkleLeafBasicData, getVerkleKey, getVerkleStem, hexToBytes, @@ -48,7 +48,8 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { assert.ok(Object.keys(stateManager['_state']).length !== 0, 'should initialize with state') }) - it('getAccount()', async () => { + // TODO: Turn back on once we have kaustinen7 data + it.skip('getAccount()', async () => { const stateManager = new StatelessVerkleStateManager({ common, verkleCrypto }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) @@ -123,26 +124,17 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { const address = createAddressFromString('0x6177843db3138ae69679a54b95cf345ed759450d') const stem = getVerkleStem(stateManager.verkleCrypto, address, 0n) - const balanceKey = getVerkleKey(stem, VerkleLeafType.Balance) - const nonceKey = getVerkleKey(stem, VerkleLeafType.Nonce) + const basicDataKey = getVerkleKey(stem, VerkleLeafType.BasicData) const codeHashKey = getVerkleKey(stem, VerkleLeafType.CodeHash) - const balanceRaw = stateManager['_state'][bytesToHex(balanceKey)] - const nonceRaw = stateManager['_state'][bytesToHex(nonceKey)] + const basicDataRaw = stateManager['_state'][bytesToHex(basicDataKey)] + const basicData = decodeVerkleLeafBasicData(hexToBytes(basicDataRaw!)) const codeHash = stateManager['_state'][bytesToHex(codeHashKey)] const account = await stateManager.getAccount(address) - assert.equal( - account!.balance, - bytesToBigInt(hexToBytes(balanceRaw!), true), - 'should have correct balance', - ) - assert.equal( - account!.nonce, - bytesToBigInt(hexToBytes(nonceRaw!), true), - 'should have correct nonce', - ) + assert.equal(account!.balance, basicData.balance, 'should have correct balance') + assert.equal(account!.nonce, basicData.nonce, 'should have correct nonce') assert.equal(bytesToHex(account!.codeHash), codeHash, 'should have correct codeHash') }) diff --git a/packages/util/src/verkle.ts b/packages/util/src/verkle.ts index 8be738518c..28392d68cd 100644 --- a/packages/util/src/verkle.ts +++ b/packages/util/src/verkle.ts @@ -1,6 +1,7 @@ import { bigIntToBytes, - bytesToHex, + bytesToBigInt, + bytesToInt32, concatBytes, int32ToBytes, intToBytes, @@ -64,19 +65,15 @@ export function getVerkleStem( /** * Verifies that the executionWitness is valid for the given prestateRoot. * @param ffi The verkle ffi object from verkle-cryptography-wasm. - * @param prestateRoot The prestateRoot matching the executionWitness. * @param executionWitness The verkle execution witness. * @returns {boolean} Whether or not the executionWitness belongs to the prestateRoot. */ export function verifyVerkleProof( ffi: VerkleCrypto, - prestateRoot: Uint8Array, executionWitness: VerkleExecutionWitness, ): boolean { - return ffi.verifyExecutionWitnessPreState( - bytesToHex(prestateRoot), - JSON.stringify(executionWitness), - ) + const { parentStateRoot, ...parsedExecutionWitness } = executionWitness + return ffi.verifyExecutionWitnessPreState(parentStateRoot, JSON.stringify(parsedExecutionWitness)) } /* Verkle Structure */ @@ -107,6 +104,10 @@ export interface VerkleStateDiff { * An object that provides the state and proof necessary for verkle stateless execution * */ export interface VerkleExecutionWitness { + /** + * The stateRoot of the parent block + */ + parentStateRoot: PrefixedHexString /** * An array of state diffs. * Each item corresponding to state accesses or state modifications of the block. @@ -121,23 +122,35 @@ export interface VerkleExecutionWitness { } export enum VerkleLeafType { - Version = 0, - Balance = 1, - Nonce = 2, - CodeHash = 3, - CodeSize = 4, + BasicData = 0, + CodeHash = 1, +} + +export type VerkleLeafBasicData = { + version: number + nonce: bigint + balance: bigint + codeSize: number } -export const VERKLE_VERSION_LEAF_KEY = intToBytes(VerkleLeafType.Version) -export const VERKLE_BALANCE_LEAF_KEY = intToBytes(VerkleLeafType.Balance) -export const VERKLE_NONCE_LEAF_KEY = intToBytes(VerkleLeafType.Nonce) +export const VERKLE_VERSION_OFFSET = 0 +export const VERKLE_NONCE_OFFSET = 4 +export const VERKLE_CODE_SIZE_OFFSET = 12 +export const VERKLE_BALANCE_OFFSET = 16 + +export const VERKLE_VERSION_BYTES_LENGTH = 1 +export const VERKLE_NONCE_BYTES_LENGTH = 8 +export const VERKLE_CODE_SIZE_BYTES_LENGTH = 4 +export const VERKLE_BALANCE_BYTES_LENGTH = 16 + +export const VERKLE_BASIC_DATA_LEAF_KEY = intToBytes(VerkleLeafType.BasicData) export const VERKLE_CODE_HASH_LEAF_KEY = intToBytes(VerkleLeafType.CodeHash) -export const VERKLE_CODE_SIZE_LEAF_KEY = intToBytes(VerkleLeafType.CodeSize) +export const VERKLE_CODE_CHUNK_SIZE = 31 export const VERKLE_HEADER_STORAGE_OFFSET = 64 export const VERKLE_CODE_OFFSET = 128 export const VERKLE_NODE_WIDTH = 256 -export const VERKLE_MAIN_STORAGE_OFFSET = BigInt(256) ** BigInt(31) +export const VERKLE_MAIN_STORAGE_OFFSET = BigInt(256) ** BigInt(VERKLE_CODE_CHUNK_SIZE) /** * @dev Returns the tree key for a given verkle tree stem, and sub index. @@ -149,16 +162,10 @@ export const VERKLE_MAIN_STORAGE_OFFSET = BigInt(256) ** BigInt(31) export const getVerkleKey = (stem: Uint8Array, leaf: VerkleLeafType | Uint8Array) => { switch (leaf) { - case VerkleLeafType.Version: - return concatBytes(stem, VERKLE_VERSION_LEAF_KEY) - case VerkleLeafType.Balance: - return concatBytes(stem, VERKLE_BALANCE_LEAF_KEY) - case VerkleLeafType.Nonce: - return concatBytes(stem, VERKLE_NONCE_LEAF_KEY) + case VerkleLeafType.BasicData: + return concatBytes(stem, VERKLE_BASIC_DATA_LEAF_KEY) case VerkleLeafType.CodeHash: return concatBytes(stem, VERKLE_CODE_HASH_LEAF_KEY) - case VerkleLeafType.CodeSize: - return concatBytes(stem, VERKLE_CODE_SIZE_LEAF_KEY) default: return concatBytes(stem, leaf) } @@ -197,9 +204,9 @@ export const getVerkleTreeKeyForCodeChunk = async ( } export const chunkifyCode = (code: Uint8Array) => { - // Pad code to multiple of 31 bytes - if (code.length % 31 !== 0) { - const paddingLength = 31 - (code.length % 31) + // Pad code to multiple of VERKLE_CODE_CHUNK_SIZE bytes + if (code.length % VERKLE_CODE_CHUNK_SIZE !== 0) { + const paddingLength = VERKLE_CODE_CHUNK_SIZE - (code.length % VERKLE_CODE_CHUNK_SIZE) code = setLengthRight(code, code.length + paddingLength) } @@ -215,3 +222,42 @@ export const getVerkleTreeKeyForStorageSlot = async ( return concatBytes(getVerkleStem(verkleCrypto, address, treeIndex), toBytes(subIndex)) } + +export function decodeVerkleLeafBasicData(encodedBasicData: Uint8Array): VerkleLeafBasicData { + const versionBytes = encodedBasicData.slice(0, VERKLE_VERSION_BYTES_LENGTH) + const nonceBytes = encodedBasicData.slice( + VERKLE_NONCE_OFFSET, + VERKLE_NONCE_OFFSET + VERKLE_NONCE_BYTES_LENGTH, + ) + const codeSizeBytes = encodedBasicData.slice( + VERKLE_CODE_SIZE_OFFSET, + VERKLE_CODE_SIZE_OFFSET + VERKLE_CODE_SIZE_BYTES_LENGTH, + ) + const balanceBytes = encodedBasicData.slice( + VERKLE_BALANCE_OFFSET, + VERKLE_BALANCE_OFFSET + VERKLE_BALANCE_BYTES_LENGTH, + ) + + const version = bytesToInt32(versionBytes, true) + const nonce = bytesToBigInt(nonceBytes, true) + const codeSize = bytesToInt32(codeSizeBytes, true) + const balance = bytesToBigInt(balanceBytes, true) + + return { version, nonce, codeSize, balance } +} + +export function encodeVerkleLeafBasicData(basicData: VerkleLeafBasicData): Uint8Array { + const encodedVersion = setLengthLeft(int32ToBytes(basicData.version), VERKLE_VERSION_BYTES_LENGTH) + // Per EIP-6800, bytes 1-4 are reserved for future use + const reservedBytes = new Uint8Array([0, 0, 0]) + const encodedNonce = setLengthLeft(bigIntToBytes(basicData.nonce), VERKLE_NONCE_BYTES_LENGTH) + const encodedCodeSize = setLengthLeft( + int32ToBytes(basicData.codeSize), + VERKLE_CODE_SIZE_BYTES_LENGTH, + ) + const encodedBalance = setLengthLeft( + bigIntToBytes(basicData.balance), + VERKLE_BALANCE_BYTES_LENGTH, + ) + return concatBytes(encodedVersion, reservedBytes, encodedNonce, encodedCodeSize, encodedBalance) +} diff --git a/packages/util/test/verkle.spec.ts b/packages/util/test/verkle.spec.ts index 8fef2d35bb..d8aa627ff2 100644 --- a/packages/util/test/verkle.spec.ts +++ b/packages/util/test/verkle.spec.ts @@ -52,29 +52,29 @@ describe('Verkle cryptographic helpers', () => { const prestateRoot = hexToBytes( '0x64e1a647f42e5c2e3c434531ccf529e1b3e93363a40db9fc8eec81f492123510', ) - const executionWitness = verkleBlockJSON.executionWitness as VerkleExecutionWitness - assert.isTrue(verifyVerkleProof(verkle, prestateRoot, executionWitness)) + const executionWitness = { + ...verkleBlockJSON.default.executionWitness, + parentStateRoot: bytesToHex(prestateRoot), + } as VerkleExecutionWitness + assert.isTrue(verifyVerkleProof(verkle, executionWitness)) }) it('verifyVerkleProof(): should return false for invalid verkle proofs', () => { // Random preStateRoot const prestateRoot = randomBytes(32) - const executionWitness = verkleBlockJSON.executionWitness as VerkleExecutionWitness + const executionWitness = { + ...verkleBlockJSON.default.executionWitness, + parentStateRoot: bytesToHex(prestateRoot), + } as VerkleExecutionWitness // Modify the proof to make it invalid - assert.isFalse(verifyVerkleProof(verkle, prestateRoot, executionWitness)) + assert.isFalse(verifyVerkleProof(verkle, executionWitness)) }) }) describe('should generate valid tree keys', () => { it('should generate valid keys for each VerkleLeafType', () => { const stem = hexToBytes('0x318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d') - for (const leaf of [ - VerkleLeafType.Version, - VerkleLeafType.Balance, - VerkleLeafType.Nonce, - VerkleLeafType.CodeHash, - VerkleLeafType.CodeSize, - ]) { + for (const leaf of [VerkleLeafType.BasicData, VerkleLeafType.CodeHash]) { const key = getVerkleKey(stem, leaf) assert.equal(key.length, 32) assert.deepEqual(key, concatBytes(stem, intToBytes(leaf))) diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 409b844728..6f009e055f 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -137,10 +137,6 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise { - it('successfully run transactions statelessly using the block witness', async () => { + // TODO: Turn back on once we have kaustinen7 block data + it.skip('successfully run transactions statelessly using the block witness', async () => { const verkleCrypto = await loadVerkleCrypto() const verkleStateManager = new StatelessVerkleStateManager({ caches: new Caches(),