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] 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') + }) +})