From cfe942e2d98bfbe0ebbedb61e965b4e56c2338ca Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Thu, 16 May 2024 16:12:19 +0200 Subject: [PATCH 1/5] evm: fix eip3074 AUTH check (#3432) --- packages/evm/src/opcodes/functions.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/evm/src/opcodes/functions.ts b/packages/evm/src/opcodes/functions.ts index 9abdc9d32f..049046a29f 100644 --- a/packages/evm/src/opcodes/functions.ts +++ b/packages/evm/src/opcodes/functions.ts @@ -1157,9 +1157,16 @@ export const handlers: Map = new Map([ } const expectedAddress = new Address(setLengthLeft(bigIntToBytes(authority), 20).slice(-20)) - const accountNonce = ( - (await runState.stateManager.getAccount(expectedAddress)) ?? new Account() - ).nonce + const account = (await runState.stateManager.getAccount(expectedAddress)) ?? new Account() + + if (account.isContract()) { + // EXTCODESIZE > 0 + runState.stack.push(BIGINT_0) + runState.auth = undefined + return + } + + const accountNonce = account.nonce const invokedAddress = setLengthLeft(runState.interpreter._env.address.bytes, 32) const chainId = setLengthLeft(bigIntToBytes(runState.interpreter.getChainId()), 32) From c95499c5b9bbdb4b36cc05f6785dd62e8a4d91a3 Mon Sep 17 00:00:00 2001 From: Gabriel Rocheleau Date: Fri, 17 May 2024 06:13:09 -0400 Subject: [PATCH 2/5] verkle: implement verkle proof verification (#3423) * statemanager: proper type for verkleproof * verkle: add verifyProof wrapper in verkle crypto * verkle: remove unused import * chore: update package lock * statemanageR: add verifyProof implementation to stateless verkle statemanager * verkle: add jsdoc for verkle verifyProof method * verkle: verkle proof test * verkle: add failing test case * client: add the ability to provide and use the parentStateRoot * Update verkle crypto dep * Activate invalid proof test * src: cleanup * Update packages/client/src/config.ts * vm: move up error check --------- Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com> --- package-lock.json | 11 +-- packages/client/bin/cli.ts | 6 ++ packages/client/src/config.ts | 3 + packages/client/src/execution/vmexecution.ts | 6 +- packages/statemanager/package.json | 2 +- .../src/statelessVerkleStateManager.ts | 70 +++++++++---------- packages/verkle/package.json | 3 +- packages/verkle/src/util/crypto.ts | 20 ++++++ packages/verkle/test/crypto.spec.ts | 23 +++++- packages/vm/src/runBlock.ts | 36 +++++++--- packages/vm/src/types.ts | 6 ++ .../vm/test/api/EIPs/eip-6800-verkle.spec.ts | 10 ++- 12 files changed, 136 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index d343f080bf..2c9bc6ed7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12154,9 +12154,9 @@ } }, "node_modules/verkle-cryptography-wasm": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/verkle-cryptography-wasm/-/verkle-cryptography-wasm-0.4.1.tgz", - "integrity": "sha512-rbcNaZCiYVEuw+TECrnaql+SiulG5kp6MC8+bza8fojoR0HEK0BwLhdNm5Pek9XhFSXKtdcMcV36t4cGAFlhBg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/verkle-cryptography-wasm/-/verkle-cryptography-wasm-0.4.2.tgz", + "integrity": "sha512-Mji9ibiCBS0ObveKuIC6XY009zLJTVBwwPaLnOI3yoj26MbZjAI8ByhWSmoQCJkWj/j/hNjcq9Jbx39Fguh92w==", "dependencies": { "@scure/base": "^1.1.5" }, @@ -13536,7 +13536,7 @@ "ethereum-cryptography": "^2.1.3", "js-sdsl": "^4.1.4", "lru-cache": "10.1.0", - "verkle-cryptography-wasm": "^0.4.1" + "verkle-cryptography-wasm": "^0.4.2" }, "devDependencies": { "@ethereumjs/block": "^5.2.0", @@ -13615,10 +13615,11 @@ "version": "0.0.2", "license": "MIT", "dependencies": { + "@ethereumjs/block": "^5.2.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.0.3", "lru-cache": "10.1.0", - "verkle-cryptography-wasm": "^0.4.1" + "verkle-cryptography-wasm": "^0.4.2" }, "engines": { "node": ">=18" diff --git a/packages/client/bin/cli.ts b/packages/client/bin/cli.ts index 226426aaa5..aacff167b8 100755 --- a/packages/client/bin/cli.ts +++ b/packages/client/bin/cli.ts @@ -449,6 +449,12 @@ 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 959b89f929..7d0386d0ae 100644 --- a/packages/client/src/config.ts +++ b/packages/client/src/config.ts @@ -338,6 +338,7 @@ export interface ConfigOptions { statelessVerkle?: boolean startExecution?: boolean ignoreStatelessInvalidExecs?: boolean + initialVerkleStateRoot?: Uint8Array /** * Enables Prometheus Metrics that can be collected for monitoring client health @@ -451,6 +452,7 @@ export class Config { public readonly statelessVerkle: boolean public readonly startExecution: boolean public readonly ignoreStatelessInvalidExecs: boolean + public readonly initialVerkleStateRoot: Uint8Array public synchronized: boolean public lastsyncronized?: boolean @@ -544,6 +546,7 @@ 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 bc1961485b..2aad9d5ebf 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -190,7 +190,9 @@ export class VMExecution extends Execution { return } this.config.logger.info(`Setting up verkleVM`) - const stateManager = await StatelessVerkleStateManager.create() + const stateManager = await StatelessVerkleStateManager.create({ + initialStateRoot: this.config.initialVerkleStateRoot, + }) this.verkleVM = await VM.create({ common: this.config.execCommon, blockchain: this.chain.blockchain, @@ -439,6 +441,7 @@ export class VMExecution extends Execution { const result = await vm.runBlock({ clearCache, ...opts, + parentStateRoot: prevVMStateRoot, skipHeaderValidation, reportPreimages, }) @@ -733,6 +736,7 @@ export class VMExecution extends Execution { skipBlockValidation, skipHeaderValidation: true, reportPreimages: this.config.savePreimages, + parentStateRoot: parentState, }) const afterTS = Date.now() const diffSec = Math.round((afterTS - beforeTS) / 1000) diff --git a/packages/statemanager/package.json b/packages/statemanager/package.json index 0a56ccda52..9849f958e6 100644 --- a/packages/statemanager/package.json +++ b/packages/statemanager/package.json @@ -58,7 +58,7 @@ "ethereum-cryptography": "^2.1.3", "js-sdsl": "^4.1.4", "lru-cache": "10.1.0", - "verkle-cryptography-wasm": "^0.4.1" + "verkle-cryptography-wasm": "^0.4.2" }, "devDependencies": { "@ethereumjs/block": "^5.2.0", diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index 7a70dc795a..2b8d88b12f 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -19,6 +19,7 @@ import { getStem, getTreeKeyForCodeChunk, getTreeKeyForStorageSlot, + verifyProof, } from '@ethereumjs/verkle' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' @@ -30,7 +31,7 @@ import { OriginalStorageCache } from './cache/originalStorageCache.js' import type { AccessedStateWithAddress } from './accessWitness.js' import type { DefaultStateManager } from './stateManager.js' -import type { VerkleExecutionWitness } from '@ethereumjs/block' +import type { VerkleExecutionWitness, VerkleProof } from '@ethereumjs/block' import type { AccountFields, Common, @@ -110,6 +111,7 @@ export interface StatelessVerkleStateManagerOpts { codeCacheOpts?: CacheOptions accesses?: AccessWitness verkleCrypto?: VerkleCrypto + initialStateRoot?: Uint8Array } const PUSH_OFFSET = 95 @@ -135,6 +137,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { _accountCache?: AccountCache _storageCache?: StorageCache _codeCache?: CodeCache + _cachedStateRoot?: Uint8Array originalStorageCache: OriginalStorageCache @@ -156,7 +159,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { private _blockNum = BigInt(0) private _executionWitness?: VerkleExecutionWitness - private _proof: Uint8Array | undefined + private _proof: VerkleProof | undefined // State along execution (should update) private _state: VerkleState = {} @@ -229,6 +232,8 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { }) } + this._cachedStateRoot = opts.initialStateRoot + this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak256 if (opts.verkleCrypto === undefined) { @@ -261,7 +266,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { this._executionWitness = executionWitness this.accessWitness = accessWitness ?? new AccessWitness({ verkleCrypto: this.verkleCrypto }) - this._proof = executionWitness.verkleProof as unknown as Uint8Array + this._proof = executionWitness.verkleProof // Populate the pre-state and post-state from the executionWitness const preStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => { @@ -493,7 +498,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { } } - // Note from Gabriel: This is actually not possible in Verkle. + // Note from Gabriel: Clearing storage is not actually not possible in Verkle. // This is because the storage keys are scattered throughout the verkle tree. /** * Clears all storage entries for the account corresponding to `address`. @@ -657,30 +662,18 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { getProof(_: Address, __: Uint8Array[] = []): Promise { throw new Error('Not implemented yet') } + /** + * Verifies whether the execution witness matches the stateRoot + * @param {Uint8Array} stateRoot - The stateRoot to verify the executionWitness against + * @returns {boolean} - Returns true if the executionWitness matches the provided stateRoot, otherwise false + */ + verifyProof(stateRoot: Uint8Array): boolean { + if (this._executionWitness === undefined) { + debug('Missing executionWitness') + return false + } - // TODO: Re-implement this method once we have working verifyUpdate and the testnets have been updated to provide ingestible data - async verifyProof(_: Uint8Array): Promise { - // Implementation: https://github.com/crate-crypto/rust-verkle-wasm/blob/master/src/lib.rs#L45 - // The root is the root of the current (un-updated) trie - // The proof is proof of membership of all of the accessed values - // keys_values is a map from the key of the accessed value to a tuple - // the tuple contains the old value and the updated value - // - // This function returns the new root when all of the updated values are applied - - // const updatedStateRoot: Uint8Array = verifyUpdate( - // parentVerkleRoot, - // this._proof!, // TODO: Convert this into a Uint8Array ingestible by the method - // new Map() // TODO: Generate the keys_values map from the old to the updated value - // ) - - // TODO: Not sure if this should return the updated state Root (current block) or the un-updated one (parent block) - // const verkleRoot = await this.getStateRoot() - - // Verify that updatedStateRoot matches the state root of the block - // return equalsBytes(updatedStateRoot, verkleRoot) - - return true + return verifyProof(this.verkleCrypto, stateRoot, this._executionWitness) } // Verifies that the witness post-state matches the computed post-state @@ -903,21 +896,26 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { async flush(): Promise {} /** - * Gets the verkle root. - * NOTE: this needs some examination in the code where this is needed - * and if we have the verkle root present - * @returns {Promise} - Returns the verkle root of the `StateManager` + * Gets the cache state root. + * This is used to persist the stateRoot between blocks, so that blocks can retrieve the stateRoot of the parent block. + * This is required to verify and prove verkle execution witnesses. + * @returns {Promise} - Returns the cached state root */ async getStateRoot(): Promise { - return new Uint8Array(0) + if (this._cachedStateRoot === undefined) { + throw new Error('Cache state root missing') + } + return this._cachedStateRoot } /** - * TODO: needed? - * Maybe in this context: reset to original pre state suffice - * @param stateRoot - The verkle root to reset the instance to + * Sets the cache state root. + * This is used to persist the stateRoot between blocks, so that blocks can retrieve the stateRoot of the parent block. + * @param stateRoot - The stateRoot to set */ - async setStateRoot(_: Uint8Array): Promise {} + async setStateRoot(stateRoot: Uint8Array): Promise { + this._cachedStateRoot = stateRoot + } /** * Dumps the RLP-encoded storage values for an `account` specified by `address`. diff --git a/packages/verkle/package.json b/packages/verkle/package.json index 92a252f051..cafbf05fa0 100644 --- a/packages/verkle/package.json +++ b/packages/verkle/package.json @@ -53,7 +53,8 @@ }, "dependencies": { "lru-cache": "10.1.0", - "verkle-cryptography-wasm": "^0.4.1", + "verkle-cryptography-wasm": "^0.4.2", + "@ethereumjs/block": "^5.2.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.0.3" }, diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts index 905733e160..5ba3310159 100644 --- a/packages/verkle/src/util/crypto.ts +++ b/packages/verkle/src/util/crypto.ts @@ -1,11 +1,13 @@ import { type Address, bigIntToBytes, + bytesToHex, int32ToBytes, setLengthLeft, setLengthRight, } from '@ethereumjs/util' +import type { VerkleExecutionWitness } from '@ethereumjs/block' import type { VerkleCrypto } from 'verkle-cryptography-wasm' /** @@ -35,4 +37,22 @@ export function getStem( return treeStem } +/** + * Verifies that the executionWitness is valid for the given prestateRoot. + * @param ffi The verkle ffi object from verkle-crypotography-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 verifyProof( + ffi: VerkleCrypto, + prestateRoot: Uint8Array, + executionWitness: VerkleExecutionWitness +): boolean { + return ffi.verifyExecutionWitnessPreState( + bytesToHex(prestateRoot), + JSON.stringify(executionWitness) + ) +} + export const POINT_IDENTITY = new Uint8Array(0) diff --git a/packages/verkle/test/crypto.spec.ts b/packages/verkle/test/crypto.spec.ts index 604fd82d22..c6f21af379 100644 --- a/packages/verkle/test/crypto.spec.ts +++ b/packages/verkle/test/crypto.spec.ts @@ -1,10 +1,12 @@ -import { Address, bytesToHex } from '@ethereumjs/util' +import { Address, bytesToHex, hexToBytes, randomBytes } from '@ethereumjs/util' import { loadVerkleCrypto } from 'verkle-cryptography-wasm' import { assert, beforeAll, describe, it } from 'vitest' -import { getStem } from '../src/index.js' +import * as verkleBlockJSON from '../../statemanager/test/testdata/verkleKaustinen6Block72.json' +import { getStem, verifyProof } from '../src/index.js' import type { VerkleCrypto } from '../src/index.js' +import type { VerkleExecutionWitness } from '@ethereumjs/block' describe('Verkle cryptographic helpers', () => { let verkle: VerkleCrypto @@ -25,4 +27,21 @@ describe('Verkle cryptographic helpers', () => { '0x1540dfad7755b40be0768c6aa0a5096fbf0215e0e8cf354dd928a178346466' ) }) + + it('verifyProof(): should verify verkle proofs', () => { + // Src: Kaustinen6 testnet, block 71 state root (parent of block 72) + const prestateRoot = hexToBytes( + '0x64e1a647f42e5c2e3c434531ccf529e1b3e93363a40db9fc8eec81f492123510' + ) + const executionWitness = verkleBlockJSON.executionWitness as VerkleExecutionWitness + assert.isTrue(verifyProof(verkle, prestateRoot, executionWitness)) + }) + + it('verifyProof(): should return false for invalid verkle proofs', () => { + // Random preStateRoot + const prestateRoot = randomBytes(32) + const executionWitness = verkleBlockJSON.executionWitness as VerkleExecutionWitness + // Modify the proof to make it invalid + assert.isFalse(verifyProof(verkle, prestateRoot, executionWitness)) + }) }) diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 45335d3038..346a7dd61c 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -68,7 +68,7 @@ export async function runBlock(this: VM, opts: RunBlockOpts): Promise[] | undefined if (block.common.isActivatedEIP(7685)) { requests = await accumulateRequests(this, result.results) @@ -207,7 +221,7 @@ export async function runBlock(this: VM, opts: RunBlockOpts): Promise TransactionFactory.fromSerializedData(hexToBytes(tx as PrefixedHexString)) ) +const parentStateRoot = hexToBytes( + '0x64e1a647f42e5c2e3c434531ccf529e1b3e93363a40db9fc8eec81f492123510' +) + const block = Block.fromBlockData({ ...verkleBlockJSON, transactions: decodedTxs } as BlockData, { common, }) @@ -34,9 +38,9 @@ describe('EIP 6800 tests', () => { evm, stateManager: verkleStateManager, }) - verkleStateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) - // We need to skip validation of the header validation as otherwise the vm will attempt retrieving the parent block, which is not available statelessly - await vm.runBlock({ block, skipHeaderValidation: true }) + // We need to skip validation of the header validation + // As otherwise the vm will attempt retrieving the parent block, which is not available in a stateless context + await vm.runBlock({ block, skipHeaderValidation: true, parentStateRoot }) }) }) From 38c4a733e687cf1355b1081614f0dda1c6e4f8df Mon Sep 17 00:00:00 2001 From: g11tech Date: Sun, 19 May 2024 20:38:06 +0530 Subject: [PATCH 3/5] evm,vm: remove the hacks to prevent account cleanups of system contracts (#3418) * evm,vm: remove the hacks to prevent account cleanups of system contracts * lint --------- Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com> Co-authored-by: Holger Drewes --- packages/evm/src/journal.ts | 8 -------- packages/vm/src/runBlock.ts | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/evm/src/journal.ts b/packages/evm/src/journal.ts index 3fcfc496fe..1a84e51ac5 100644 --- a/packages/evm/src/journal.ts +++ b/packages/evm/src/journal.ts @@ -2,7 +2,6 @@ import { Hardfork } from '@ethereumjs/common' import { Address, RIPEMD160_ADDRESS_STRING, - bigIntToHex, bytesToHex, bytesToUnprefixedHex, stripHexPrefix, @@ -198,13 +197,6 @@ export class Journal { const address = new Address(hexToBytes(`0x${addressHex}`)) const account = await this.stateManager.getAccount(address) if (account === undefined || account.isEmpty()) { - if (this.common.isActivatedEIP(2935)) { - // The history storage address is exempt of state clearing by EIP-158 if the EIP is activated - const addr = bigIntToHex(this.common.param('vm', 'historyStorageAddress')).slice(2) - if (addressHex === addr) { - continue - } - } await this.deleteAccount(address) if (this.DEBUG) { this._debug(`Cleanup touched account address=${address} (>= SpuriousDragon)`) diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 346a7dd61c..e38f6035f7 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -545,6 +545,9 @@ export async function accumulateParentBlockHash( } // eslint-disable-next-line no-empty } catch (_e) {} + + // do cleanup if the code was not deployed + await this.evm.journal.cleanup() } export async function accumulateParentBeaconBlockRoot( @@ -590,6 +593,9 @@ export async function accumulateParentBeaconBlockRoot( setLengthLeft(bigIntToBytes(timestampExtended), 32), root ) + + // do cleanup if the code was not deployed + await this.evm.journal.cleanup() } /** From 1dceddf26d9c610a6a7ad787b1c654f42da9887d Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 21 May 2024 15:59:36 -0400 Subject: [PATCH 4/5] fix tx status in TxResult (#3435) --- packages/client/src/rpc/modules/eth.ts | 2 +- packages/client/test/rpc/eth/getTransactionReceipt.spec.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 722af76986..dfd159fc7c 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -201,7 +201,7 @@ const jsonRpcReceipt = async ( ? bytesToHex((receipt as PreByzantiumTxReceipt).stateRoot) : undefined, status: - ((receipt as PostByzantiumTxReceipt).status as unknown) instanceof Uint8Array + (receipt as PostByzantiumTxReceipt).status !== undefined ? intToHex((receipt as PostByzantiumTxReceipt).status) : undefined, blobGasUsed: blobGasUsed !== undefined ? bigIntToHex(blobGasUsed) : undefined, diff --git a/packages/client/test/rpc/eth/getTransactionReceipt.spec.ts b/packages/client/test/rpc/eth/getTransactionReceipt.spec.ts index 53513a6502..ba0868facc 100644 --- a/packages/client/test/rpc/eth/getTransactionReceipt.spec.ts +++ b/packages/client/test/rpc/eth/getTransactionReceipt.spec.ts @@ -69,6 +69,7 @@ describe(method, () => { const res = await rpc.request(method, [bytesToHex(tx.hash())]) assert.equal(res.result.transactionHash, bytesToHex(tx.hash()), 'should return the correct tx') + assert.equal(res.result.status, '0x1', 'transaction result is 1 since succeeded') }) it('call with unknown tx hash', async () => { From ee8e02f7be14f4311c2a967d9b8d9dd3ffc72166 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Wed, 22 May 2024 05:30:54 -0400 Subject: [PATCH 5/5] Add `eth_blobBaseFee` RPC endpoint (#3436) * Add blob base fee * client: remove unused variables in blobBaseFee test --------- Co-authored-by: Gabriel Rocheleau --- packages/client/src/rpc/modules/eth.ts | 15 +++ .../client/test/rpc/eth/blobBaseFee.spec.ts | 121 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 packages/client/test/rpc/eth/blobBaseFee.spec.ts diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index dfd159fc7c..f6c251b68a 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -452,6 +452,12 @@ export class Eth { [validators.rewardPercentiles], ] ) + + this.blobBaseFee = middleware( + callWithStackTrace(this.blobBaseFee.bind(this), this._rpcDebug), + 0, + [] + ) } /** @@ -1317,4 +1323,13 @@ export class Eth { reward: rewards.map((r) => r.map(bigIntToHex)), } } + + /** + * + * @returns the blob base fee for the next/pending block in wei + */ + async blobBaseFee() { + const headBlock = await this._chain.getCanonicalHeadHeader() + return bigIntToHex(headBlock.calcNextBlobGasPrice()) + } } diff --git a/packages/client/test/rpc/eth/blobBaseFee.spec.ts b/packages/client/test/rpc/eth/blobBaseFee.spec.ts new file mode 100644 index 0000000000..f4e2a12b0f --- /dev/null +++ b/packages/client/test/rpc/eth/blobBaseFee.spec.ts @@ -0,0 +1,121 @@ +import { Hardfork } from '@ethereumjs/common' +import { TransactionFactory } from '@ethereumjs/tx' +import { + Address, + BIGINT_0, + BIGINT_256, + blobsToCommitments, + commitmentsToVersionedHashes, + getBlobs, + hexToBytes, +} from '@ethereumjs/util' +import { loadKZG } from 'kzg-wasm' +import { assert, describe, it } from 'vitest' + +import genesisJSON from '../../testdata/geth-genesis/eip4844.json' +import { getRpcClient, setupChain } from '../helpers.js' + +import type { Chain } from '../../../src/blockchain/chain.js' +import type { VMExecution } from '../../../src/execution/vmexecution.js' +const method = 'eth_blobBaseFee' + +const privateKey = hexToBytes('0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8') +const accountAddress = Address.fromPrivateKey(privateKey) +const produceBlockWith4844Tx = async ( + execution: VMExecution, + chain: Chain, + blobsCount: number[] +) => { + const kzg = await loadKZG() + // 4844 sample blob + const sampleBlob = getBlobs('hello world') + const commitment = blobsToCommitments(kzg, sampleBlob) + const blobVersionedHash = commitmentsToVersionedHashes(commitment) + + const { vm } = execution + const account = await vm.stateManager.getAccount(accountAddress) + let nonce = account?.nonce ?? BIGINT_0 + const parentBlock = await chain.getCanonicalHeadBlock() + const vmCopy = await vm.shallowCopy() + // Set block's gas used to max + const blockBuilder = await vmCopy.buildBlock({ + parentBlock, + headerData: { + timestamp: parentBlock.header.timestamp + BigInt(1), + }, + blockOpts: { + calcDifficultyFromHeader: parentBlock.header, + putBlockIntoBlockchain: false, + }, + }) + for (let i = 0; i < blobsCount.length; i++) { + const blobVersionedHashes = [] + const blobs = [] + const kzgCommitments = [] + const to = Address.zero() + if (blobsCount[i] > 0) { + for (let blob = 0; blob < blobsCount[i]; blob++) { + blobVersionedHashes.push(...blobVersionedHash) + blobs.push(...sampleBlob) + kzgCommitments.push(...commitment) + } + } + await blockBuilder.addTransaction( + TransactionFactory.fromTxData( + { + type: 3, + gasLimit: 21000, + maxFeePerGas: 0xffffffff, + maxPriorityFeePerGas: BIGINT_256, + nonce, + to, + blobVersionedHashes, + blobs, + kzgCommitments, + maxFeePerBlobGas: BigInt(1000), + }, + { common: vmCopy.common } + ).sign(privateKey) + ) + nonce++ + } + + const block = await blockBuilder.build() + await chain.putBlocks([block], true) + await execution.run() +} + +describe(method, () => { + it('call', async () => { + const kzg = await loadKZG() + const { server } = await setupChain(genesisJSON, 'post-merge', { + engine: true, + hardfork: Hardfork.Cancun, + customCrypto: { + kzg, + }, + }) + + const rpc = getRpcClient(server) + const res = await rpc.request(method, []) + assert.equal(res.result, '0x1') + }) + + it('call with more realistic blockchain', async () => { + const kzg = await loadKZG() + const { server, execution, chain } = await setupChain(genesisJSON, 'post-merge', { + engine: true, + hardfork: Hardfork.Cancun, + customCrypto: { + kzg, + }, + }) + + for (let i = 0; i < 10; i++) { + await produceBlockWith4844Tx(execution, chain, [6]) + } + const rpc = getRpcClient(server) + const res = await rpc.request(method, []) + assert.equal(res.result, '0x3') + }) +})