From eb9ef75d39fa83b66c682bbf3611fc201a1b7355 Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 13 Jun 2024 23:51:10 +0530 Subject: [PATCH 01/22] common,util,tx: implement aip 6493 stable container txs debug and fix the legacy ssz encoding decoding add a spec test for legacy ssz encoding decoding add the ssztx boilerplate to other tx types implement sszRaw value for 2930 tx add 2930 spec test and debug/fix ssz encoding/decoding add the ssz encoding decoding to 1559 tx add eip 1559 testcase and get it working add 4844 ssz encoding decoding add eip 4844 testcase and get it working define block transactions ssz type and test ssz transactionsRoot handle ssz roots for transactions and withdrawals in block when 6493 activated handle the roots gen in the build block fix the transaction stable container update the execution payload serialization deserialization for 6493 add 6493 hardfork for the testing/devnet refactor the transaction factory ssz tx deserialization add ssz profile<>stablecontaiber conversion spec test add eip6493 support to common debug and fix the block transaction withdrawal root comparision by removing null keccak hash hardcoding enhance eip6493 tx test by testing transaction factory deserialization which uses stable container add client eip6493 end to end spec and fix the payload generation refactor tx serialization deserializion with respect to execution/beacon payload add, debug and fix the transactionv1 or hex transactions validator and debug/fix the newpayloadeip6493 spec test add 6493 to electra for kurtosis testing console log error for debugging console log error for debugging txpool fix attempt add more descriptive checks for nulloroptional add more descriptive checks for nulloroptional log full error debug and fix handling of replay vs legacy tx w.r.t. v/ypartity and confirm via spec test build fix dev and add transaction inclusion proof to the getTransactionX apis workaround to get the proof since stable container impl for proof seems buggy and breaking refactor the proof format based on feedback debug, discuss and fix the signature packing scheme add hack to schedule 6493 on prague in cli for stablecontainer devnets debug and fix newpayload eip6493 spec debug rebase and spec fixes in tx utils debug and fix block build fix the vm build debug and get 6493 end to end client spec working rebase 4844 fixes add ssz blockheader type and update the blockhash to use when ssz activated debug and update client spec with ssz blockhash update ssz field to receiptstrie updates after discussion with etan update test --- package-lock.json | 30 ++ packages/block/package.json | 1 + packages/block/src/block/block.ts | 43 +- packages/block/src/block/constructors.ts | 46 ++- packages/block/src/from-beacon-payload.ts | 77 +++- packages/block/src/header/header.ts | 46 ++- packages/block/src/helpers.ts | 15 +- packages/block/src/index.ts | 2 + packages/block/src/types.ts | 3 +- packages/client/bin/cli.ts | 12 + packages/client/src/miner/pendingBlock.ts | 31 +- packages/client/src/rpc/helpers.ts | 19 +- .../client/src/rpc/modules/engine/engine.ts | 6 + .../src/rpc/modules/engine/util/newPayload.ts | 1 + .../src/rpc/modules/engine/validators.ts | 41 +- packages/client/src/rpc/modules/eth.ts | 41 +- packages/client/src/rpc/validation.ts | 39 ++ packages/client/src/service/txpool.ts | 3 + packages/client/src/types.ts | 1 + .../test/rpc/engine/newPayloadEip6493.spec.ts | 236 +++++++++++ packages/common/src/eips.ts | 10 + packages/common/src/enums.ts | 1 + packages/common/src/hardforks.ts | 8 + packages/common/src/utils.ts | 1 + packages/evm/src/evm.ts | 3 +- packages/tx/package.json | 1 + packages/tx/src/1559/constructors.ts | 54 ++- packages/tx/src/1559/tx.ts | 34 ++ packages/tx/src/2930/constructors.ts | 51 ++- packages/tx/src/2930/tx.ts | 35 ++ packages/tx/src/4844/constructors.ts | 51 ++- packages/tx/src/4844/tx.ts | 38 ++ packages/tx/src/7702/tx.ts | 4 + packages/tx/src/baseTransaction.ts | 11 +- packages/tx/src/index.ts | 1 + packages/tx/src/legacy/constructors.ts | 41 +- packages/tx/src/legacy/tx.ts | 43 ++ packages/tx/src/transactionFactory.ts | 70 +++- packages/tx/src/types.ts | 1 + packages/tx/src/util.ts | 121 ++++++ packages/tx/test/eip6493.spec.ts | 230 +++++++++++ packages/util/package.json | 2 + packages/util/src/index.ts | 1 + packages/util/src/ssz.ts | 387 ++++++++++++++++++ packages/util/test/ssz.spec.ts | 36 ++ packages/vm/src/buildBlock.ts | 26 +- 46 files changed, 1875 insertions(+), 79 deletions(-) create mode 100644 packages/client/test/rpc/engine/newPayloadEip6493.spec.ts create mode 100644 packages/tx/test/eip6493.spec.ts create mode 100644 packages/util/src/ssz.ts create mode 100644 packages/util/test/ssz.spec.ts diff --git a/package-lock.json b/package-lock.json index a2f61344cf1..f8d53825c74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -548,6 +548,12 @@ "tough-cookie": "^4.1.4" } }, + "node_modules/@chainsafe/as-sha256": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.4.2.tgz", + "integrity": "sha512-HJ8GZBRjLeWtRsAXf3EbNsNzmTGpzTFjfpSf4yHkLYC+E52DhT6hwz+7qpj6I/EmFzSUm5tYYvT9K8GZokLQCQ==", + "license": "Apache-2.0" + }, "node_modules/@chainsafe/is-ip": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.0.2.tgz", @@ -561,6 +567,26 @@ "@chainsafe/is-ip": "^2.0.1" } }, + "node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.7.2.tgz", + "integrity": "sha512-BUAqrmSUmy6bZhXxnhpR+aYoEDdCeS1dQvq/aje0CDEB14ZHF9UVN2mL9MolOD0ANUiP1OaPG3KfVBxvuW8aTg==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "^0.4.2", + "@noble/hashes": "^1.3.0" + } + }, + "node_modules/@chainsafe/ssz": { + "version": "0.16.0", + "resolved": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "integrity": "sha512-npPP4N+WM8oJxwjvlRPyWCc7oX5mjmg0maWpR93B68HREV1xTrsX5GDi7Q1iDF36L196WIZUd5a2PZNE/bjSWg==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.4.2", + "@chainsafe/persistent-merkle-tree": "0.7.2" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -17244,6 +17270,7 @@ "version": "5.3.0", "license": "MPL-2.0", "dependencies": { + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/common": "^4.4.0", "@ethereumjs/mpt": "^6.2.2", "@ethereumjs/rlp": "^5.0.2", @@ -17738,6 +17765,7 @@ "version": "5.4.0", "license": "MPL-2.0", "dependencies": { + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/common": "^4.4.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", @@ -17760,6 +17788,8 @@ "version": "9.1.0", "license": "MPL-2.0", "dependencies": { + "@chainsafe/persistent-merkle-tree": "^0.7.2", + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/rlp": "^5.0.2", "ethereum-cryptography": "^3.0.0" }, diff --git a/packages/block/package.json b/packages/block/package.json index e4eec3634af..407164cd865 100644 --- a/packages/block/package.json +++ b/packages/block/package.json @@ -47,6 +47,7 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/common": "^4.4.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/mpt": "^6.2.2", diff --git a/packages/block/src/block/block.ts b/packages/block/src/block/block.ts index f1867d68493..d7f463f66d4 100644 --- a/packages/block/src/block/block.ts +++ b/packages/block/src/block/block.ts @@ -18,6 +18,7 @@ import { keccak256 } from 'ethereum-cryptography/keccak.js' // TODO: See if there is an easier way to achieve the same result. // See: https://github.com/microsoft/TypeScript/issues/47558 // (situation will eventually improve on Typescript and/or Eslint update) +import { genTransactionsSszRoot, genWithdrawalsSszRoot } from '../helpers.js' import { genRequestsTrieRoot, genTransactionsTrieRoot, @@ -226,10 +227,9 @@ export class Block { * Generates transaction trie for validation. */ async genTxTrie(): Promise { - return genTransactionsTrieRoot( - this.transactions, - new MerklePatriciaTrie({ common: this.common }), - ) + return this.common.isActivatedEIP(6493) + ? genTransactionsSszRoot(this.transactions) + : genTransactionsTrieRoot(this.transactions, new MerklePatriciaTrie({ common: this.common })) } /** @@ -238,16 +238,10 @@ export class Block { * @returns True if the transaction trie is valid, false otherwise */ async transactionsTrieIsValid(): Promise { - let result - if (this.transactions.length === 0) { - result = equalsBytes(this.header.transactionsTrie, KECCAK256_RLP) - return result - } - if (this.cache.txTrieRoot === undefined) { this.cache.txTrieRoot = await this.genTxTrie() } - result = equalsBytes(this.cache.txTrieRoot, this.header.transactionsTrie) + const result = equalsBytes(this.cache.txTrieRoot, this.header.transactionsTrie) return result } @@ -367,7 +361,9 @@ export class Block { } if (!(await this.transactionsTrieIsValid())) { - const msg = this._errorMsg('invalid transaction trie') + const msg = this._errorMsg( + `invalid transaction trie expected=${bytesToHex(this.cache.txTrieRoot!)}`, + ) throw new Error(msg) } @@ -456,6 +452,12 @@ export class Block { return equalsBytes(this.keccakFunction(raw), this.header.uncleHash) } + async genWithdrawalsTrie(): Promise { + return this.common.isActivatedEIP(6493) + ? genWithdrawalsSszRoot(this.withdrawals!) + : genWithdrawalsTrieRoot(this.withdrawals!, new MerklePatriciaTrie({ common: this.common })) + } + /** * Validates the withdrawal root * @returns true if the withdrawals trie root is valid, false otherwise @@ -465,19 +467,10 @@ export class Block { throw new Error('EIP 4895 is not activated') } - let result - if (this.withdrawals!.length === 0) { - result = equalsBytes(this.header.withdrawalsRoot!, KECCAK256_RLP) - return result - } - if (this.cache.withdrawalsTrieRoot === undefined) { - this.cache.withdrawalsTrieRoot = await genWithdrawalsTrieRoot( - this.withdrawals!, - new MerklePatriciaTrie({ common: this.common }), - ) + this.cache.withdrawalsTrieRoot = await this.genWithdrawalsTrie() } - result = equalsBytes(this.cache.withdrawalsTrieRoot, this.header.withdrawalsRoot!) + const result = equalsBytes(this.cache.withdrawalsTrieRoot, this.header.withdrawalsRoot!) return result } @@ -546,7 +539,9 @@ export class Block { toExecutionPayload(): ExecutionPayload { const blockJSON = this.toJSON() const header = blockJSON.header! - const transactions = this.transactions.map((tx) => bytesToHex(tx.serialize())) ?? [] + const transactions = this.common.isActivatedEIP(6493) + ? this.transactions.map((tx) => tx.toExecutionPayloadTx()) + : this.transactions.map((tx) => bytesToHex(tx.serialize())) const withdrawalsArr = blockJSON.withdrawals ? { withdrawals: blockJSON.withdrawals } : {} const executionPayload: ExecutionPayload = { diff --git a/packages/block/src/block/constructors.ts b/packages/block/src/block/constructors.ts index 0bc2b45182b..9c231014395 100644 --- a/packages/block/src/block/constructors.ts +++ b/packages/block/src/block/constructors.ts @@ -4,6 +4,7 @@ import { type TxOptions, createTx, createTxFromBlockBodyData, + createTxFromExecutionPayloadTx, createTxFromRLP, normalizeTxParams, } from '@ethereumjs/tx' @@ -25,7 +26,13 @@ import { } from '@ethereumjs/util' import { generateCliqueBlockExtraData } from '../consensus/clique.js' -import { genRequestsTrieRoot, genTransactionsTrieRoot, genWithdrawalsTrieRoot } from '../helpers.js' +import { + genRequestsTrieRoot, + genTransactionsSszRoot, + genTransactionsTrieRoot, + genWithdrawalsSszRoot, + genWithdrawalsTrieRoot, +} from '../helpers.js' import { Block, createBlockHeader, @@ -46,6 +53,7 @@ import type { RequestsBytes, WithdrawalsBytes, } from '../types.js' +import type { Common } from '@ethereumjs/common' import type { TypedTransaction } from '@ethereumjs/tx' import type { CLRequest, @@ -373,7 +381,7 @@ export const createBlockFromJSONRPCProvider = async ( */ export async function createBlockFromExecutionPayload( payload: ExecutionPayload, - opts?: BlockOptions, + opts: BlockOptions & { common: Common }, ): Promise { const { blockNumber: number, @@ -389,11 +397,24 @@ export async function createBlockFromExecutionPayload( } = payload const txs = [] - for (const [index, serializedTx] of transactions.entries()) { + for (const [index, serializedTxOrPayload] of transactions.entries()) { try { - const tx = createTxFromRLP(hexToBytes(serializedTx as PrefixedHexString), { - common: opts?.common, - }) + let tx + if (opts.common.isActivatedEIP(6493)) { + if (typeof serializedTxOrPayload === 'string') { + throw Error('EIP 6493 activated for transaction bytes') + } + tx = createTxFromExecutionPayloadTx(serializedTxOrPayload, { + common: opts?.common, + }) + } else { + if (typeof serializedTxOrPayload !== 'string') { + throw Error('EIP 6493 not activated for transaction payload') + } + tx = createTxFromRLP(hexToBytes(serializedTxOrPayload as PrefixedHexString), { + common: opts?.common, + }) + } txs.push(tx) } catch (error) { const validationError = `Invalid tx at index ${index}: ${error}` @@ -401,13 +422,14 @@ export async function createBlockFromExecutionPayload( } } - const transactionsTrie = await genTransactionsTrieRoot( - txs, - new MerklePatriciaTrie({ common: opts?.common }), - ) + const transactionsTrie = opts.common.isActivatedEIP(6493) + ? await genTransactionsSszRoot(txs) + : await genTransactionsTrieRoot(txs, new MerklePatriciaTrie({ common: opts?.common })) const withdrawals = withdrawalsData?.map((wData) => createWithdrawal(wData)) const withdrawalsRoot = withdrawals - ? await genWithdrawalsTrieRoot(withdrawals, new MerklePatriciaTrie({ common: opts?.common })) + ? opts.common.isActivatedEIP(6493) + ? genWithdrawalsSszRoot(withdrawals) + : await genWithdrawalsTrieRoot(withdrawals, new MerklePatriciaTrie({ common: opts?.common })) : undefined const hasDepositRequests = depositRequests !== undefined && depositRequests !== null @@ -481,7 +503,7 @@ export async function createBlockFromExecutionPayload( */ export async function createBlockFromBeaconPayloadJSON( payload: BeaconPayloadJSON, - opts?: BlockOptions, + opts: BlockOptions & { common: Common }, ): Promise { const executionPayload = executionPayloadFromBeaconPayload(payload) return createBlockFromExecutionPayload(executionPayload, opts) diff --git a/packages/block/src/from-beacon-payload.ts b/packages/block/src/from-beacon-payload.ts index d6d93467f69..db773fd7ab1 100644 --- a/packages/block/src/from-beacon-payload.ts +++ b/packages/block/src/from-beacon-payload.ts @@ -1,7 +1,12 @@ import { bigIntToHex } from '@ethereumjs/util' import type { ExecutionPayload } from './types.js' -import type { NumericString, PrefixedHexString, VerkleExecutionWitness } from '@ethereumjs/util' +import type { + NumericString, + PrefixedHexString, + VerkleExecutionWitness, + ssz, +} from '@ethereumjs/util' type BeaconWithdrawal = { index: PrefixedHexString @@ -30,7 +35,41 @@ type BeaconConsolidationRequest = { target_pubkey: PrefixedHexString } -// Payload JSON that one gets using the beacon apis +export type BeaconFeesPerGasV1 = { + regular: PrefixedHexString | null // Quantity 64 bytes + blob: PrefixedHexString | null // Quantity 64 bytes +} + +export type BeaconAccessTupleV1 = { + address: PrefixedHexString // DATA 20 bytes + storage_keys: PrefixedHexString[] // Data 32 bytes MAX_ACCESS_LIST_STORAGE_KEYS array +} + +export type BeaconTransactionPayloadV1 = { + type: PrefixedHexString | null // Quantity, 1 byte + chain_id: PrefixedHexString | null // Quantity 8 bytes + nonce: PrefixedHexString | null // Quantity 8 bytes + max_fees_per_gas: BeaconFeesPerGasV1 | null + gas: PrefixedHexString | null // Quantity 8 bytes + to: PrefixedHexString | null // DATA 20 bytes + value: PrefixedHexString | null // Quantity 64 bytes + input: PrefixedHexString | null // max MAX_CALLDATA_SIZE bytes, + access_list: BeaconAccessTupleV1[] | null + max_priority_fees_per_gas: BeaconFeesPerGasV1 | null + blob_versioned_hashes: PrefixedHexString[] | null // DATA 32 bytes array +} + +export type BeaconTransactionSignatureV1 = { + from: PrefixedHexString | null // DATA 20 bytes + ecdsa_signature: PrefixedHexString | null // DATA 65 bytes or null +} + +type BeaconTransactionV1 = { + payload: BeaconTransactionPayloadV1 + signature: BeaconTransactionSignatureV1 +} + +// Payload json that one gets using the beacon apis // curl localhost:5052/eth/v2/beacon/blocks/56610 | jq .data.message.body.execution_payload export type BeaconPayloadJSON = { parent_hash: PrefixedHexString @@ -46,7 +85,7 @@ export type BeaconPayloadJSON = { extra_data: PrefixedHexString base_fee_per_gas: NumericString block_hash: PrefixedHexString - transactions: PrefixedHexString[] + transactions: PrefixedHexString[] | BeaconTransactionV1[] withdrawals?: BeaconWithdrawal[] blob_gas_used?: NumericString excess_blob_gas?: NumericString @@ -121,6 +160,36 @@ function parseExecutionWitnessFromSnakeJSON({ * The JSON data can be retrieved from a consensus layer (CL) client on this Beacon API `/eth/v2/beacon/blocks/[block number]` */ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJSON): ExecutionPayload { + const transactions = + typeof payload.transactions[0] === 'object' + ? (payload.transactions as BeaconTransactionV1[]).map((btxv1) => { + return { + payload: { + type: btxv1.payload.type, + chainId: btxv1.payload.chain_id, + nonce: btxv1.payload.nonce, + maxFeesPerGas: btxv1.payload.max_fees_per_gas, + to: btxv1.payload.to, + value: btxv1.payload.value, + input: btxv1.payload.input, + accessList: + btxv1.payload.access_list?.map((bal: BeaconAccessTupleV1) => { + return { + address: bal.address, + storageKeys: bal.storage_keys, + } + }) ?? null, + maxPriorityFeesPerGas: btxv1.payload.max_priority_fees_per_gas, + blobVersionedHashes: btxv1.payload.blob_versioned_hashes, + }, + signature: { + from: btxv1.signature.from, + ecdsaSignature: btxv1.signature.ecdsa_signature, + }, + } as ssz.TransactionV1 + }) + : (payload.transactions as PrefixedHexString[]) + const executionPayload: ExecutionPayload = { parentHash: payload.parent_hash, feeRecipient: payload.fee_recipient, @@ -135,7 +204,7 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJSON): E extraData: payload.extra_data, baseFeePerGas: bigIntToHex(BigInt(payload.base_fee_per_gas)), blockHash: payload.block_hash, - transactions: payload.transactions, + transactions, } if (payload.withdrawals !== undefined && payload.withdrawals !== null) { diff --git a/packages/block/src/header/header.ts b/packages/block/src/header/header.ts index 79675dafedd..e9b7dba26c9 100644 --- a/packages/block/src/header/header.ts +++ b/packages/block/src/header/header.ts @@ -16,6 +16,7 @@ import { createZeroAddress, equalsBytes, hexToBytes, + ssz, toType, } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' @@ -29,6 +30,9 @@ import { fakeExponential } from '../helpers.js' import { paramsBlock } from '../params.js' import type { BlockHeaderBytes, BlockOptions, HeaderData, JSONHeader } from '../types.js' +import type { ValueOf } from '@chainsafe/ssz' + +export type SSZHeaderType = ValueOf interface HeaderCache { hash: Uint8Array | undefined @@ -632,17 +636,55 @@ export class BlockHeader { return rawItems } + sszRaw(): SSZHeaderType { + const header = { + parentHash: this.parentHash, + coinbase: this.coinbase.bytes, + stateRoot: this.stateRoot, + transactionsTrie: this.transactionsTrie, + receiptsTrie: this.receiptTrie, + number: this.number, + gasLimits: { + regular: this.gasLimit, + blob: this.common.isActivatedEIP(4844) ? this.common.param('maxblobGasPerBlock') : null, + }, + gasUsed: { regular: this.gasUsed, blob: this.blobGasUsed ?? null }, + timestamp: this.timestamp, + extraData: this.extraData, + mixHash: this.mixHash, + baseFeePerGas: { + regular: this.baseFeePerGas ?? null, + blob: this.common.isActivatedEIP(4844) ? this.getBlobGasPrice() : null, + }, + withdrawalsRoot: this.withdrawalsRoot ?? null, + excessGas: { regular: null, blob: this.excessBlobGas ?? null }, + parentBeaconBlockRoot: this.parentBeaconBlockRoot ?? null, + requestsRoot: this.requestsRoot ?? null, + } + + return header + } + + calcHash(): Uint8Array { + if (this.common.isActivatedEIP(6493)) { + const hash = ssz.BlockHeader.hashTreeRoot(this.sszRaw()) + return hash + } else { + return this.keccakFunction(RLP.encode(this.raw())) + } + } + /** * Returns the hash of the block header. */ hash(): Uint8Array { if (Object.isFrozen(this)) { if (!this.cache.hash) { - this.cache.hash = this.keccakFunction(RLP.encode(this.raw())) as Uint8Array + this.cache.hash = this.calcHash() } return this.cache.hash } - return this.keccakFunction(RLP.encode(this.raw())) + return this.calcHash() } /** diff --git a/packages/block/src/helpers.ts b/packages/block/src/helpers.ts index 9fc406788bc..f9e884e05bb 100644 --- a/packages/block/src/helpers.ts +++ b/packages/block/src/helpers.ts @@ -1,12 +1,15 @@ import { MerklePatriciaTrie } from '@ethereumjs/mpt' import { RLP } from '@ethereumjs/rlp' import { Blob4844Tx } from '@ethereumjs/tx' -import { BIGINT_0, BIGINT_1, TypeOutput, isHexString, toType } from '@ethereumjs/util' +import { BIGINT_0, BIGINT_1, TypeOutput, isHexString, ssz, toType } from '@ethereumjs/util' import type { BlockHeaderBytes, HeaderData } from './types.js' +import type { ValueOf } from '@chainsafe/ssz' import type { TypedTransaction } from '@ethereumjs/tx' import type { CLRequest, CLRequestType, PrefixedHexString, Withdrawal } from '@ethereumjs/util' +export type SSZTransactionType = ValueOf + /** * Returns a 0x-prefixed hex number string from a hex string or string integer. * @param {string} input string to check, convert, and return @@ -132,6 +135,11 @@ export async function genWithdrawalsTrieRoot(wts: Withdrawal[], emptyTrie?: Merk return trie.root() } +export function genWithdrawalsSszRoot(wts: Withdrawal[]) { + const withdrawals = wts.map((wt) => wt.toValue()) + return ssz.Withdrawals.hashTreeRoot(withdrawals) +} + /** * Returns the txs trie root for array of TypedTransaction * @param txs array of TypedTransaction to compute the root of @@ -148,6 +156,11 @@ export async function genTransactionsTrieRoot( return trie.root() } +export async function genTransactionsSszRoot(txs: TypedTransaction[]) { + const transactions = txs.map((tx) => tx.sszRaw() as unknown as SSZTransactionType) + return ssz.Transactions.hashTreeRoot(transactions) +} + /** * Returns the requests trie root for an array of CLRequests * @param requests - an array of CLRequests diff --git a/packages/block/src/index.ts b/packages/block/src/index.ts index 074d1a1c045..887b75358bb 100644 --- a/packages/block/src/index.ts +++ b/packages/block/src/index.ts @@ -5,7 +5,9 @@ export { type BeaconPayloadJSON, executionPayloadFromBeaconPayload } from './fro export * from './header/index.js' export { genRequestsTrieRoot, + genTransactionsSszRoot, genTransactionsTrieRoot, + genWithdrawalsSszRoot, genWithdrawalsTrieRoot, getDifficulty, valuesArrayToHeaderData, diff --git a/packages/block/src/types.ts b/packages/block/src/types.ts index 145ef4ef209..7f7f74eb7f0 100644 --- a/packages/block/src/types.ts +++ b/packages/block/src/types.ts @@ -17,6 +17,7 @@ import type { WithdrawalBytes, WithdrawalData, WithdrawalRequestV1, + ssz, } from '@ethereumjs/util' /** @@ -267,7 +268,7 @@ export type ExecutionPayload = { extraData: PrefixedHexString // DATA, 0 to 32 Bytes baseFeePerGas: PrefixedHexString // QUANTITY, 256 Bits blockHash: PrefixedHexString // DATA, 32 Bytes - transactions: PrefixedHexString[] // Array of DATA - Array of transaction rlp strings, + transactions: PrefixedHexString[] | ssz.TransactionV1[] // Array of DATA - Array of transaction rlp strings, withdrawals?: WithdrawalV1[] // Array of withdrawal objects blobGasUsed?: PrefixedHexString // QUANTITY, 64 Bits excessBlobGas?: PrefixedHexString // QUANTITY, 64 Bits diff --git a/packages/client/bin/cli.ts b/packages/client/bin/cli.ts index 0fc6c1bcab0..5b19bd65beb 100755 --- a/packages/client/bin/cli.ts +++ b/packages/client/bin/cli.ts @@ -151,6 +151,12 @@ const args: ClientOpts = yargs boolean: true, default: true, }) + // just a hack to insert 6493 on pragueTime for input genesis + .option('eip6493AtPrague', { + describe: 'Just for stablecontainer devnets testing', + boolean: true, + default: true, + }) .option('bootnodes', { describe: 'Comma-separated list of network bootnodes (format: "enode://@,enode://..." ("[?discport=]" not supported) or path to a bootnode.txt file', @@ -1028,6 +1034,12 @@ async function run() { // Use geth genesis parameters file if specified const genesisFile = JSON.parse(readFileSync(args.gethGenesis, 'utf-8')) const chainName = path.parse(args.gethGenesis).base.split('.')[0] + // just a hack for stable container devnets to schedule 6493 at prague + if (args.eip6493AtPrague === true) { + genesisFile.config.eip6493Time = genesisFile.config.pragueTime + console.log('Scheduling eip6493AtPrague', genesisFile.config) + } + common = createCommonFromGethGenesis(genesisFile, { chain: chainName, mergeForkIdPostMerge: args.mergeForkIdPostMerge, diff --git a/packages/client/src/miner/pendingBlock.ts b/packages/client/src/miner/pendingBlock.ts index cc17cf83162..df3d6074a5e 100644 --- a/packages/client/src/miner/pendingBlock.ts +++ b/packages/client/src/miner/pendingBlock.ts @@ -199,10 +199,15 @@ export class PendingBlock { allowedBlobs = 0 } // Add current txs in pool - const txs = await this.txPool.txsByPriceAndNonce(vm, { - baseFee: baseFeePerGas, - allowedBlobs, - }) + const txs = await this.txPool + .txsByPriceAndNonce(vm, { + baseFee: baseFeePerGas, + allowedBlobs, + }) + .catch((e) => { + console.log('txsByPriceAndNonce', e) + return [] + }) this.config.logger.info( `Pending: Assembling block from ${txs.length} eligible txs (baseFee: ${baseFeePerGas})`, ) @@ -270,10 +275,15 @@ export class PendingBlock { // Add new txs that the pool received const txs = ( - await this.txPool.txsByPriceAndNonce(vm, { - baseFee: headerData.baseFeePerGas! as bigint, - allowedBlobs, - }) + await this.txPool + .txsByPriceAndNonce(vm, { + baseFee: headerData.baseFeePerGas! as bigint, + allowedBlobs, + }) + .catch((e) => { + console.log('txsByPriceAndNonce', e) + return [] + }) ).filter( (tx) => (builder as any).transactions.some((t: TypedTransaction) => @@ -325,6 +335,7 @@ export class PendingBlock { blockFull = true // Falls through default: + console.log({ addTxResult }) skippedByAddErrors++ } index++ @@ -347,6 +358,7 @@ export class PendingBlock { }) addTxResult = AddTxResult.Success } catch (error: any) { + console.log('addTransaction', error) if (error.message === 'tx has a higher gas limit than the remaining gas in the block') { if (builder.gasUsed > (builder as any).headerData.gasLimit - BigInt(21000)) { // If block has less than 21000 gas remaining, consider it full @@ -363,8 +375,9 @@ export class PendingBlock { ) addTxResult = AddTxResult.RemovedByErrors } else { + console.log(error) // If there is an error adding a tx, it will be skipped - this.config.logger.debug( + this.config.logger.warn( `Pending: Skipping tx ${bytesToHex( tx.hash(), )}, error encountered when trying to add tx:\n${error}`, diff --git a/packages/client/src/rpc/helpers.ts b/packages/client/src/rpc/helpers.ts index 24cc138b40d..cd12f244dc9 100644 --- a/packages/client/src/rpc/helpers.ts +++ b/packages/client/src/rpc/helpers.ts @@ -36,7 +36,16 @@ export function callWithStackTrace(handler: Function, debug: boolean) { /** * Returns tx formatted to the standard JSON-RPC fields */ -export const toJSONRPCTx = (tx: TypedTransaction, block?: Block, txIndex?: number): JSONRPCTx => { +export const toJSONRPCTx = ( + tx: TypedTransaction, + block?: Block, + txIndex?: number, + inclusionProof?: { + merkleBranch: Uint8Array[] + transactionsRoot: Uint8Array + transactionRoot: Uint8Array + }, +): JSONRPCTx => { const txJSON = tx.toJSON() return { blockHash: block ? bytesToHex(block.hash()) : null, @@ -61,6 +70,14 @@ export const toJSONRPCTx = (tx: TypedTransaction, block?: Block, txIndex?: numbe maxFeePerBlobGas: txJSON.maxFeePerBlobGas, blobVersionedHashes: txJSON.blobVersionedHashes, yParity: txJSON.yParity, + inclusionProof: + inclusionProof !== undefined + ? { + merkleBranch: inclusionProof.merkleBranch.map((elem) => bytesToHex(elem)), + transactionsRoot: bytesToHex(inclusionProof.transactionsRoot), + transactionRoot: bytesToHex(inclusionProof.transactionRoot), + } + : undefined, } } diff --git a/packages/client/src/rpc/modules/engine/engine.ts b/packages/client/src/rpc/modules/engine/engine.ts index edc3e694329..05ff66d9cf4 100644 --- a/packages/client/src/rpc/modules/engine/engine.ts +++ b/packages/client/src/rpc/modules/engine/engine.ts @@ -346,6 +346,7 @@ export class Engine { private async newPayload( params: [ExecutionPayload, (Bytes32[] | null)?, (Bytes32 | null)?], ): Promise { + try{ const [payload, blobVersionedHashes, parentBeaconBlockRoot] = params if (this.config.synchronized) { this.connectionManager.newPayloadLog() @@ -744,6 +745,10 @@ export class Engine { validationError: null, } return response + }catch(e){ + console.log("newPayload", e) + throw e; + } } /** @@ -1388,6 +1393,7 @@ export class Engine { ) return executionPayload } catch (error: any) { + console.log("getPayload", error) if (validEngineCodes.includes(error.code)) throw error throw { code: INTERNAL_ERROR, diff --git a/packages/client/src/rpc/modules/engine/util/newPayload.ts b/packages/client/src/rpc/modules/engine/util/newPayload.ts index 594f24b85f5..9a9ddab07bd 100644 --- a/packages/client/src/rpc/modules/engine/util/newPayload.ts +++ b/packages/client/src/rpc/modules/engine/util/newPayload.ts @@ -34,6 +34,7 @@ export const assembleBlock = async ( await block.validateData() return { block } } catch (error) { + console.log(error) const validationError = `Error assembling block from payload: ${error}` config.logger.error(validationError) const latestValidHash = await validHash( diff --git a/packages/client/src/rpc/modules/engine/validators.ts b/packages/client/src/rpc/modules/engine/validators.ts index a90704cb5dc..dd9b05a5c13 100644 --- a/packages/client/src/rpc/modules/engine/validators.ts +++ b/packages/client/src/rpc/modules/engine/validators.ts @@ -1,5 +1,44 @@ import { validators } from '../../validation.js' +const transaction = validators.hexOrObject( + validators.object({ + payload: validators.object({ + type: validators.nullOptional(validators.uint8), + chainId: validators.nullOptional(validators.uint64), + nonce: validators.nullOptional(validators.uint64), + maxFeesPerGas: validators.nullOptional( + validators.object({ + regular: validators.nullOptional(validators.uint256), + blob: validators.nullOptional(validators.uint256), + }), + ), + gas: validators.nullOptional(validators.uint64), + to: validators.nullOptional(validators.address), + value: validators.nullOptional(validators.uint256), + input: validators.nullOptional(validators.hex), + accessList: validators.nullOptional( + validators.array( + validators.object({ + address: validators.address, + storageKeys: validators.array(validators.bytes32), + }), + ), + ), + maxPriorityFeesPerGas: validators.nullOptional( + validators.object({ + regular: validators.nullOptional(validators.uint256), + blob: validators.nullOptional(validators.uint256), + }), + ), + blobVersionedHashes: validators.nullOptional(validators.array(validators.bytes32)), + }), + signature: validators.object({ + from: validators.nullOptional(validators.address), + ecdsaSignature: validators.nullOptional(validators.hex), + }), + }), +) + export const executionPayloadV1FieldValidators = { parentHash: validators.blockHash, feeRecipient: validators.address, @@ -14,7 +53,7 @@ export const executionPayloadV1FieldValidators = { extraData: validators.variableBytes32, baseFeePerGas: validators.uint256, blockHash: validators.blockHash, - transactions: validators.array(validators.hex), + transactions: validators.array(transaction), } export const executionPayloadV2FieldValidators = { ...executionPayloadV1FieldValidators, diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index d34c4434f84..43a89932d51 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -28,6 +28,7 @@ import { intToHex, isHexString, setLengthLeft, + ssz, toType, } from '@ethereumjs/util' import { @@ -818,7 +819,18 @@ export class Eth { } const tx = block.transactions[txIndex] - return toJSONRPCTx(tx, block, txIndex) + let inclusionProof = undefined + if (block.common.isActivatedEIP(6493)) { + inclusionProof = inclusionProof = { + transactionsRoot: block.header.transactionsTrie, + ...ssz.computeTransactionInclusionProof( + block.transactions.map((tx) => tx.sszRaw()), + txIndex, + ), + } + } + + return toJSONRPCTx(tx, block, txIndex, inclusionProof) } catch (error: any) { throw { code: INVALID_PARAMS, @@ -843,7 +855,18 @@ export class Eth { } const tx = block.transactions[txIndex] - return toJSONRPCTx(tx, block, txIndex) + let inclusionProof = undefined + if (block.common.isActivatedEIP(6493)) { + inclusionProof = inclusionProof = { + transactionsRoot: block.header.transactionsTrie, + ...ssz.computeTransactionInclusionProof( + block.transactions.map((tx) => tx.sszRaw()), + txIndex, + ), + } + } + + return toJSONRPCTx(tx, block, txIndex, inclusionProof) } catch (error: any) { throw { code: INVALID_PARAMS, @@ -864,8 +887,20 @@ export class Eth { if (!result) return null const [_receipt, blockHash, txIndex] = result const block = await this._chain.getBlock(blockHash) + const tx = block.transactions[txIndex] - return toJSONRPCTx(tx, block, txIndex) + let inclusionProof = undefined + if (block.common.isActivatedEIP(6493)) { + inclusionProof = { + transactionsRoot: block.header.transactionsTrie, + ...ssz.computeTransactionInclusionProof( + block.transactions.map((tx) => tx.sszRaw()), + txIndex, + ), + } + } + + return toJSONRPCTx(tx, block, txIndex, inclusionProof) } /** diff --git a/packages/client/src/rpc/validation.ts b/packages/client/src/rpc/validation.ts index cc929dfe4e6..091644fcbca 100644 --- a/packages/client/src/rpc/validation.ts +++ b/packages/client/src/rpc/validation.ts @@ -184,6 +184,9 @@ export const validators = { get bytes256() { return (params: any[], index: number) => bytes(256, params, index) }, + get uint8() { + return (params: any[], index: number) => uint(8, params, index) + }, get uint64() { return (params: any[], index: number) => uint(64, params, index) }, @@ -586,6 +589,24 @@ export const validators = { } }, + get hexOrObject() { + return (validator: Function) => { + return (params: any[], index: number) => { + const validate = (field: any, validator: Function) => { + if (field === undefined) return + const v = validator([field], 0) + if (v !== undefined) return v + } + + if (typeof params[index] !== 'object') { + return validate(params[index], this.hex) + } + + return validator(params, index) + } + } + }, + /** * object validator to check if type is object with * required keys and expected validation of values @@ -750,6 +771,24 @@ export const validators = { } }, + get nullOptional() { + return (validator: any) => { + return (params: any, index: number) => { + if (params[index] === null) { + return + } + + if (params[index] === undefined) { + return { + code: INVALID_PARAMS, + message: `invalid undefined argument for nullOptional at ${index}`, + } + } + return validator(params, index) + } + } + }, + /** * Validator that passes if any of the specified validators pass * @param validator validator to check against the value diff --git a/packages/client/src/service/txpool.ts b/packages/client/src/service/txpool.ts index 6a1ef4201a8..a66e53a9f87 100644 --- a/packages/client/src/service/txpool.ts +++ b/packages/client/src/service/txpool.ts @@ -839,6 +839,9 @@ export class TxPool { this.normalizedGasPrice(b, baseFee) - this.normalizedGasPrice(a, baseFee) < BIGINT_0, }) as QHeap for (const [address, txs] of byNonce) { + if (txs.length === 0) { + continue + } byPrice.insert(txs[0]) byNonce.set(address, txs.slice(1)) } diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index aa5faab06c7..06acd42d982 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -109,6 +109,7 @@ export interface ClientOpts { gethGenesis?: string trustedSetup?: string mergeForkIdPostMerge?: boolean + eip6493AtPrague?: boolean bootnodes?: string | string[] port?: number extIP?: string diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts new file mode 100644 index 00000000000..b8b7047244c --- /dev/null +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -0,0 +1,236 @@ +import { createTx } from '@ethereumjs/tx' +import { bigIntToHex, hexToBytes } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { beaconData } from '../../testdata/blocks/beacon.js' +import { postMergeData } from '../../testdata/geth-genesis/post-merge.js' +import { getRPCClient, setupChain } from '../helpers.js' + +const method = 'engine_newPayloadV4' +const [blockData] = beaconData + +const parentBeaconBlockRoot = '0x42942949c4ed512cd85c2cb54ca88591338cbb0564d3a2bea7961a639ef29d64' +const validForkChoiceState = { + headBlockHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', + safeBlockHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', + finalizedBlockHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', +} +const validPayloadAttributes = { + timestamp: '0x64ba84fd', + prevRandao: '0xff00000000000000000000000000000000000000000000000000000000000000', + suggestedFeeRecipient: '0xaa00000000000000000000000000000000000000', +} + +const validPayload = [ + validForkChoiceState, + { + ...validPayloadAttributes, + withdrawals: [], + parentBeaconBlockRoot, + }, +] + +function readyEip6493Genesis(genesisJSON: any) { + const pragueTime = 1689945325 + // deep copy json and add shanghai and cancun to genesis to avoid contamination + const pragueJson = JSON.parse(JSON.stringify(genesisJSON)) + pragueJson.config.shanghaiTime = pragueTime + pragueJson.config.cancunTime = pragueTime + pragueJson.config.pragueTime = pragueTime + pragueJson.config.eip6493Time = pragueTime + pragueJson.config.chainId = 1223334 + // eslint-disable-next-line @typescript-eslint/no-use-before-define + Object.assign(pragueJson.alloc, electraGenesisContracts) + return { pragueJson, pragueTime } +} + +describe(`${method}: call with executionPayloadV4`, () => { + it('valid data', async () => { + // get the genesis json with late enougt date with respect to block data in batchBlocks + + const { pragueJson, pragueTime } = readyEip6493Genesis(postMergeData) + const { service, server, common } = await setupChain(pragueJson, 'post-merge', { engine: true }) + const rpc = getRPCClient(server) + const validBlock = { + ...blockData, + timestamp: bigIntToHex(BigInt(pragueTime)), + withdrawals: [], + blobGasUsed: '0x0', + excessBlobGas: '0x0', + depositRequests: [], + withdrawalRequests: [], + consolidationRequests: [], + parentHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', + stateRoot: '0xbde9840c609ffa39cae0a2c9e354ac673920fcc2a5e6faeef5b78817c7fba7dd', + blockHash: '0x5c0486debe1ed1cb21ed9d93e34d0c2908b12b45d75090e59198b7fd71e26305', + } + let res + + res = await rpc.request(`eth_getBlockByNumber`, ['0x0', false]) + assert.equal(res.result.hash, validForkChoiceState.headBlockHash) + + res = await rpc.request(method, [validBlock, [], parentBeaconBlockRoot]) + assert.equal(res.result.status, 'VALID') + + res = await rpc.request('engine_forkchoiceUpdatedV3', validPayload) + const payloadId = res.result.payloadId + assert.ok(payloadId !== undefined && payloadId !== null, 'valid payloadId should be received') + + // address 0x610adc49ecd66cbf176a8247ebd59096c031bd9f has been sufficiently funded in genesis + const pk = hexToBytes('0x9c9996335451aab4fc4eac58e31a8c300e095cdbcee532d53d09280e83360355') + const depositTx = createTx( + { + data: '0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001208cd4e5a69709cf8ee5b1b73d6efbf3f33bcac92fb7e4ce62b2467542fb50a72d0000000000000000000000000000000000000000000000000000000000000030ac842878bb70009552a4cfcad801d6e659c50bd50d7d03306790cb455ce7363c5b6972f0159d170f625a99b2064dbefc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020010000000000000000000000818ccb1c4eda80270b04d6df822b1e72dd83c3030000000000000000000000000000000000000000000000000000000000000060a747f75c72d0cf0d2b52504c7385b516f0523e2f0842416399f42b4aee5c6384a5674f6426b1cc3d0827886fa9b909e616f5c9f61f986013ed2b9bf37071cbae951136265b549f44e3c8e26233c0433e9124b7fd0dc86e82f9fedfc0a179d769', + value: 32000000000000000000n, + gasLimit: 15000000n, + maxFeePerGas: 100n, + type: 2, + to: '0x00000000219ab540356cBB839Cbe05303d7705Fa', + }, + { common }, + ).sign(pk) + await service.txPool.add(depositTx, true) + + const normalLegacyTx = createTx( + { + data: '0x', + value: 32000000000000000000n, + gasLimit: 15000000n, + gasPrice: 100n, + type: 0, + to: '0x10000000219ab540356cBB839Cbe05303d7705Fa', + nonce: 1, + }, + { common }, + ).sign(pk) + await service.txPool.add(normalLegacyTx, true) + + console.log({ + normalLegacyTx: normalLegacyTx.toJSON(), + payloadjson: normalLegacyTx.toExecutionPayloadTx(), + }) + + res = await rpc.request('engine_getPayloadV4', [payloadId]) + const { executionPayload } = res.result + assert.ok(executionPayload.transactions.length === 2, 'two transactions should have been added') + assert.ok( + executionPayload.depositRequests?.length === 1, + 'depositRequests should have 1 deposit request', + ) + assert.ok( + executionPayload.withdrawalRequests !== undefined, + 'depositRequests field should be received', + ) + + res = await rpc.request(method, [executionPayload, [], parentBeaconBlockRoot]) + assert.equal(res.result.status, 'VALID') + + const newBlockHashHex = executionPayload.blockHash + // add this block to the blockchain + res = await rpc.request('engine_forkchoiceUpdatedV3', [ + { + safeBlockHash: newBlockHashHex, + finalizedBlockHash: newBlockHashHex, + headBlockHash: newBlockHashHex, + }, + null, + ]) + assert.equal(res.result.payloadStatus.status, 'VALID') + }) +}) + +const electraGenesisContracts = { + // sender corresponding to the priv key 0x9c9996335451aab4fc4eac58e31a8c300e095cdbcee532d53d09280e83360355 + '0x610adc49ecd66cbf176a8247ebd59096c031bd9f': { balance: '0x6d6172697573766477000000' }, + // eip 2925 contract + '0x0aae40965e6800cd9b1f4b05ff21581047e3f91e': { + balance: '0', + nonce: '1', + code: '0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500', + }, + // consolidation requests contract + '0x00b42dbF2194e931E80326D950320f7d9Dbeac02': { + balance: '0', + nonce: '1', + code: '0x3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd', + }, + // withdrawals request contract + '0x00A3ca265EBcb825B45F985A16CEFB49958cE017': { + balance: '0', + nonce: '1', + code: '0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd', + storage: { + '0x0000000000000000000000000000000000000000000000000000000000000000': + '0x000000000000000000000000000000000000000000000000000000000000049d', + }, + }, + // beacon deposit contract for deposit receipts + '0x00000000219ab540356cBB839Cbe05303d7705Fa': { + balance: '0', + code: '0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033', + storage: { + '0x0000000000000000000000000000000000000000000000000000000000000022': + '0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b', + '0x0000000000000000000000000000000000000000000000000000000000000023': + '0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71', + '0x0000000000000000000000000000000000000000000000000000000000000024': + '0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c', + '0x0000000000000000000000000000000000000000000000000000000000000025': + '0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c', + '0x0000000000000000000000000000000000000000000000000000000000000026': + '0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30', + '0x0000000000000000000000000000000000000000000000000000000000000027': + '0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1', + '0x0000000000000000000000000000000000000000000000000000000000000028': + '0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c', + '0x0000000000000000000000000000000000000000000000000000000000000029': + '0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193', + '0x000000000000000000000000000000000000000000000000000000000000002a': + '0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1', + '0x000000000000000000000000000000000000000000000000000000000000002b': + '0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b', + '0x000000000000000000000000000000000000000000000000000000000000002c': + '0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220', + '0x000000000000000000000000000000000000000000000000000000000000002d': + '0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f', + '0x000000000000000000000000000000000000000000000000000000000000002e': + '0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e', + '0x000000000000000000000000000000000000000000000000000000000000002f': + '0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784', + '0x0000000000000000000000000000000000000000000000000000000000000030': + '0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb', + '0x0000000000000000000000000000000000000000000000000000000000000031': + '0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb', + '0x0000000000000000000000000000000000000000000000000000000000000032': + '0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab', + '0x0000000000000000000000000000000000000000000000000000000000000033': + '0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4', + '0x0000000000000000000000000000000000000000000000000000000000000034': + '0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f', + '0x0000000000000000000000000000000000000000000000000000000000000035': + '0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa', + '0x0000000000000000000000000000000000000000000000000000000000000036': + '0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c', + '0x0000000000000000000000000000000000000000000000000000000000000037': + '0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167', + '0x0000000000000000000000000000000000000000000000000000000000000038': + '0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7', + '0x0000000000000000000000000000000000000000000000000000000000000039': + '0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0', + '0x000000000000000000000000000000000000000000000000000000000000003a': + '0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544', + '0x000000000000000000000000000000000000000000000000000000000000003b': + '0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765', + '0x000000000000000000000000000000000000000000000000000000000000003c': + '0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4', + '0x000000000000000000000000000000000000000000000000000000000000003d': + '0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1', + '0x000000000000000000000000000000000000000000000000000000000000003e': + '0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636', + '0x000000000000000000000000000000000000000000000000000000000000003f': + '0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c', + '0x0000000000000000000000000000000000000000000000000000000000000040': + '0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7', + }, + }, +} diff --git a/packages/common/src/eips.ts b/packages/common/src/eips.ts index 091fae9ce49..8e4d2a77ec9 100644 --- a/packages/common/src/eips.ts +++ b/packages/common/src/eips.ts @@ -327,6 +327,16 @@ export const eipsDict: EIPsDict = { minimumHardfork: Hardfork.London, requiredEIPs: [4750, 5450], }, + /** + * Description : SSZ Transaction Signature Scheme + * URL : https://eips.ethereum.org/EIPS/eip-6493 + * Status : Draft + */ + 6493: { + // TODO: Set correct minimum hardfork + minimumHardfork: Hardfork.Cancun, + requiredEIPs: [], + }, /** * Description : SELFDESTRUCT only in same transaction * URL : https://eips.ethereum.org/EIPS/eip-6780 diff --git a/packages/common/src/enums.ts b/packages/common/src/enums.ts index d4fbf08b0ad..9c82bedd61a 100644 --- a/packages/common/src/enums.ts +++ b/packages/common/src/enums.ts @@ -71,6 +71,7 @@ export enum Hardfork { Shanghai = 'shanghai', Cancun = 'cancun', Prague = 'prague', + Eip6493 = 'eip6493', Osaka = 'osaka', } diff --git a/packages/common/src/hardforks.ts b/packages/common/src/hardforks.ts index 527ae642a1c..c57422ca783 100644 --- a/packages/common/src/hardforks.ts +++ b/packages/common/src/hardforks.ts @@ -162,6 +162,14 @@ export const hardforksDict: HardforksDict = { //eips: [663, 3540, 3670, 4200, 4750, 5450, 6206, 7069, 7480, 7620, 7692, 7698], // This is EOF-only eips: [2537, 2935, 6110, 7002, 7251, 7685, 7702], // This is current prague without EOF }, + /** + * Description: Experimental hardfork to test eip 6493 for 6493 devnets will be removed(incomplete/experimental) + * URL : + * Status : Final + */ + eip6493: { + eips: [6493], + }, /** * Description: Next feature hardfork after prague, internally used for verkle testing/implementation (incomplete/experimental) * URL : https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/osaka.md diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 6fcbc92f44c..9c348adf035 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -145,6 +145,7 @@ function parseGethParams(json: any) { [Hardfork.Shanghai]: { name: 'shanghaiTime', postMerge: true, isTimestamp: true }, [Hardfork.Cancun]: { name: 'cancunTime', postMerge: true, isTimestamp: true }, [Hardfork.Prague]: { name: 'pragueTime', postMerge: true, isTimestamp: true }, + [Hardfork.Eip6493]: { name: 'eip6493Time', postMerge: true, isTimestamp: true }, [Hardfork.Osaka]: { name: 'osakaTime', postMerge: true, isTimestamp: true }, } diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 869b479db77..b28a3247442 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -84,6 +84,7 @@ export class EVM implements EVMInterface { Hardfork.Shanghai, Hardfork.Cancun, Hardfork.Prague, + Hardfork.Eip6493, Hardfork.Osaka, ] protected _tx?: { @@ -178,7 +179,7 @@ export class EVM implements EVMInterface { // Supported EIPs const supportedEIPs = [ 663, 1153, 1559, 2537, 2565, 2718, 2929, 2930, 2935, 3198, 3529, 3540, 3541, 3607, 3651, 3670, - 3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6780, 6800, + 3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6493, 6780, 6800, 7002, 7069, 7251, 7480, 7516, 7620, 7685, 7692, 7698, 7702, 7709, ] diff --git a/packages/tx/package.json b/packages/tx/package.json index f9d320e003b..7d1cf92a842 100644 --- a/packages/tx/package.json +++ b/packages/tx/package.json @@ -54,6 +54,7 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/common": "^4.4.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", diff --git a/packages/tx/src/1559/constructors.ts b/packages/tx/src/1559/constructors.ts index 623659832e9..c6045b7830e 100644 --- a/packages/tx/src/1559/constructors.ts +++ b/packages/tx/src/1559/constructors.ts @@ -1,5 +1,11 @@ import { RLP } from '@ethereumjs/rlp' -import { bytesToBigInt, bytesToHex, equalsBytes, validateNoLeadingZeroes } from '@ethereumjs/util' +import { + bigIntToUnpaddedBytes, + bytesToBigInt, + bytesToHex, + equalsBytes, + validateNoLeadingZeroes, +} from '@ethereumjs/util' import { TransactionType } from '../types.js' import { txTypeBytes, validateNotArray } from '../util.js' @@ -8,6 +14,9 @@ import { FeeMarket1559Tx } from './tx.js' import type { TxOptions } from '../types.js' import type { TxData, TxValuesArray } from './tx.js' +import type { ValueOf } from '@chainsafe/ssz' +import type { ssz } from '@ethereumjs/util' +export type Eip1559TransactionType = ValueOf /** * Instantiate a transaction from a data dictionary. @@ -98,3 +107,46 @@ export function createFeeMarket1559TxFromRLP(serialized: Uint8Array, opts: TxOpt return create1559FeeMarketTxFromBytesArray(values as TxValuesArray, opts) } + +export function createFeeMarket1559TxFromSszTx( + sszWrappedTx: Eip1559TransactionType, + opts: TxOptions = {}, +) { + const { + payload: { + nonce, + chainId, + maxFeesPerGas: { regular: maxFeePerGas }, + gas: gasLimit, + to, + value, + input: data, + accessList, + maxPriorityFeesPerGas: { regular: maxPriorityFeePerGas }, + }, + signature: { ecdsaSignature }, + } = sszWrappedTx + + // TODO: bytes to bigint => bigint to unpadded bytes seem redundant and set for optimization + const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) + const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) + const v = bytesToBigInt(ecdsaSignature.slice(64)) + + return create1559FeeMarketTxFromBytesArray( + [ + bigIntToUnpaddedBytes(chainId), + bigIntToUnpaddedBytes(nonce), + bigIntToUnpaddedBytes(maxPriorityFeePerGas), + bigIntToUnpaddedBytes(maxFeePerGas), + bigIntToUnpaddedBytes(gasLimit), + to ?? new Uint8Array(0), + bigIntToUnpaddedBytes(value), + data, + accessList.map(({ address, storageKeys }) => [address, storageKeys]), + bigIntToUnpaddedBytes(v), + bigIntToUnpaddedBytes(r), + bigIntToUnpaddedBytes(s), + ], + opts, + ) +} diff --git a/packages/tx/src/1559/tx.ts b/packages/tx/src/1559/tx.ts index 372f824e313..2941a97036f 100644 --- a/packages/tx/src/1559/tx.ts +++ b/packages/tx/src/1559/tx.ts @@ -6,6 +6,7 @@ import { bigIntToHex, bigIntToUnpaddedBytes, bytesToBigInt, + setLengthLeft, toBytes, } from '@ethereumjs/util' @@ -20,6 +21,7 @@ import { AccessLists, validateNotArray } from '../util.js' import { createFeeMarket1559Tx } from './constructors.js' +import type { SSZTransactionType } from '../baseTransaction.js' import type { AccessList, AccessListBytes, @@ -164,6 +166,38 @@ export class FeeMarket1559Tx extends BaseTransaction ({ address, storageKeys })), + maxPriorityFeesPerGas: { regular: this.maxPriorityFeePerGas, blob: null }, + blobVersionedHashes: null, + } + + const yParity = this.v + const signature = { + from: this.getSenderAddress().bytes, + ecdsaSignature: Uint8Array.from([ + ...setLengthLeft(bigIntToUnpaddedBytes(this.r), 32), + ...setLengthLeft(bigIntToUnpaddedBytes(this.s), 32), + ...setLengthLeft(bigIntToUnpaddedBytes(yParity), 1), + ]), + } + + return { payload, signature } + } + /** * Returns the serialized encoding of the EIP-1559 transaction. * diff --git a/packages/tx/src/2930/constructors.ts b/packages/tx/src/2930/constructors.ts index 7373239e974..8c7f7e2fc23 100644 --- a/packages/tx/src/2930/constructors.ts +++ b/packages/tx/src/2930/constructors.ts @@ -1,5 +1,11 @@ import { RLP } from '@ethereumjs/rlp' -import { bytesToBigInt, bytesToHex, equalsBytes, validateNoLeadingZeroes } from '@ethereumjs/util' +import { + bigIntToUnpaddedBytes, + bytesToBigInt, + bytesToHex, + equalsBytes, + validateNoLeadingZeroes, +} from '@ethereumjs/util' import { TransactionType } from '../types.js' import { txTypeBytes, validateNotArray } from '../util.js' @@ -8,6 +14,9 @@ import { AccessList2930Transaction } from './tx.js' import type { AccessList, TxOptions } from '../types.js' import type { TxData, TxValuesArray } from './tx.js' +import type { ValueOf } from '@chainsafe/ssz' +import type { ssz } from '@ethereumjs/util' +export type Eip2930TransactionType = ValueOf /** * Instantiate a transaction from a data dictionary. @@ -86,3 +95,43 @@ export function createAccessList2930TxFromRLP(serialized: Uint8Array, opts: TxOp return createAccessList2930TxFromBytesArray(values as TxValuesArray, opts) } + +export function createAccessList2930TxFromSszTx( + sszWrappedTx: Eip2930TransactionType, + opts: TxOptions = {}, +) { + const { + payload: { + nonce, + chainId, + maxFeesPerGas: { regular: gasPrice }, + gas: gasLimit, + to, + value, + input: data, + accessList, + }, + signature: { ecdsaSignature }, + } = sszWrappedTx + + const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) + const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) + const v = bytesToBigInt(ecdsaSignature.slice(64)) + + return createAccessList2930TxFromBytesArray( + [ + bigIntToUnpaddedBytes(chainId), + bigIntToUnpaddedBytes(nonce), + bigIntToUnpaddedBytes(gasPrice), + bigIntToUnpaddedBytes(gasLimit), + to, + bigIntToUnpaddedBytes(value), + data, + accessList.map(({ address, storageKeys }) => [address, storageKeys]), + bigIntToUnpaddedBytes(v), + bigIntToUnpaddedBytes(r), + bigIntToUnpaddedBytes(s), + ] as TxValuesArray, + opts, + ) +} diff --git a/packages/tx/src/2930/tx.ts b/packages/tx/src/2930/tx.ts index 10565728dbc..4dfa7ebce9a 100644 --- a/packages/tx/src/2930/tx.ts +++ b/packages/tx/src/2930/tx.ts @@ -5,6 +5,7 @@ import { bigIntToHex, bigIntToUnpaddedBytes, bytesToBigInt, + setLengthLeft, toBytes, } from '@ethereumjs/util' @@ -18,6 +19,7 @@ import { AccessLists, validateNotArray } from '../util.js' import { createAccessList2930Tx } from './constructors.js' +import type { SSZTransactionType } from '../baseTransaction.js' import type { AccessList, AccessListBytes, @@ -146,6 +148,39 @@ export class AccessList2930Transaction extends BaseTransaction ({ address, storageKeys })), + maxPriorityFeesPerGas: null, + blobVersionedHashes: null, + } + + const yParity = this.v + + const signature = { + from: this.getSenderAddress().bytes, + ecdsaSignature: Uint8Array.from([ + ...setLengthLeft(bigIntToUnpaddedBytes(this.r), 32), + ...setLengthLeft(bigIntToUnpaddedBytes(this.s), 32), + ...setLengthLeft(bigIntToUnpaddedBytes(yParity), 1), + ]), + } + + return { payload, signature } + } + /** * Returns the serialized encoding of the EIP-2930 transaction. * diff --git a/packages/tx/src/4844/constructors.ts b/packages/tx/src/4844/constructors.ts index ec2ff0ecec5..b3ba2bab738 100644 --- a/packages/tx/src/4844/constructors.ts +++ b/packages/tx/src/4844/constructors.ts @@ -1,6 +1,7 @@ import { RLP } from '@ethereumjs/rlp' import { bigIntToHex, + bigIntToUnpaddedBytes, blobsToCommitments, blobsToProofs, bytesToBigInt, @@ -24,7 +25,10 @@ import type { TxOptions, } from '../types.js' import type { TxData, TxValuesArray } from './tx.js' -import type { KZG, PrefixedHexString } from '@ethereumjs/util' +import type { ValueOf } from '@chainsafe/ssz' +import type { KZG, PrefixedHexString, ssz } from '@ethereumjs/util' + +export type Eip4844TransactionType = ValueOf const validateBlobTransactionNetworkWrapper = ( blobVersionedHashes: PrefixedHexString[], @@ -334,3 +338,48 @@ export function blobTxNetworkWrapperToJSON( kzgProofs: tx.kzgProofs!, } } + +export function createBlob4844TxFromSszTx( + sszWrappedTx: Eip4844TransactionType, + opts: TxOptions = {}, +) { + const { + payload: { + nonce, + chainId, + maxFeesPerGas: { regular: maxFeePerGas, blob: maxFeePerBlobGas }, + gas: gasLimit, + to, + value, + input: data, + accessList, + maxPriorityFeesPerGas: { regular: maxPriorityFeePerGas }, + blobVersionedHashes, + }, + signature: { ecdsaSignature }, + } = sszWrappedTx + + const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) + const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) + const v = bytesToBigInt(ecdsaSignature.slice(64)) + + return createBlob4844TxFromBytesArray( + [ + bigIntToUnpaddedBytes(chainId), + bigIntToUnpaddedBytes(nonce), + bigIntToUnpaddedBytes(maxPriorityFeePerGas), + bigIntToUnpaddedBytes(maxFeePerGas), + bigIntToUnpaddedBytes(gasLimit), + to, + bigIntToUnpaddedBytes(value), + data, + accessList.map(({ address, storageKeys }) => [address, storageKeys]), + bigIntToUnpaddedBytes(maxFeePerBlobGas), + blobVersionedHashes, + bigIntToUnpaddedBytes(v), + bigIntToUnpaddedBytes(r), + bigIntToUnpaddedBytes(s), + ], + opts, + ) +} diff --git a/packages/tx/src/4844/tx.ts b/packages/tx/src/4844/tx.ts index 909f462a557..7b93b47e348 100644 --- a/packages/tx/src/4844/tx.ts +++ b/packages/tx/src/4844/tx.ts @@ -8,6 +8,7 @@ import { bigIntToUnpaddedBytes, bytesToBigInt, hexToBytes, + setLengthLeft, toBytes, toType, } from '@ethereumjs/util' @@ -24,6 +25,7 @@ import { AccessLists, validateNotArray } from '../util.js' import { createBlob4844Tx } from './constructors.js' +import type { SSZTransactionType } from '../baseTransaction.js' import type { AccessList, AccessListBytes, @@ -217,6 +219,42 @@ export class Blob4844Tx extends BaseTransaction { ] } + sszRaw(): SSZTransactionType { + if (this.r === undefined || this.s === undefined || this.v === undefined) { + throw Error(`Transaction not signed for sszSerialize`) + } + + const payload = { + type: BigInt(this.type), + chainId: this.chainId, + nonce: this.nonce, + maxFeesPerGas: { regular: this.maxFeePerGas, blob: this.maxFeePerBlobGas }, + gas: this.gasLimit, + to: this.to?.bytes ?? null, + value: this.value, + input: this.data, + accessList: this.accessList.map(([address, storageKeys]) => ({ address, storageKeys })), + maxPriorityFeesPerGas: { + regular: this.maxPriorityFeePerGas, + blob: this.maxPriorityFeePerGas, + }, + blobVersionedHashes: this.blobVersionedHashes.map((vh) => hexToBytes(vh)), + } + + const yParity = this.v + + const signature = { + from: this.getSenderAddress().bytes, + ecdsaSignature: Uint8Array.from([ + ...setLengthLeft(bigIntToUnpaddedBytes(this.r), 32), + ...setLengthLeft(bigIntToUnpaddedBytes(this.s), 32), + ...setLengthLeft(bigIntToUnpaddedBytes(yParity), 1), + ]), + } + + return { payload, signature } + } + /** * Returns the serialized encoding of the EIP-4844 transaction. * diff --git a/packages/tx/src/7702/tx.ts b/packages/tx/src/7702/tx.ts index d9ccc9086c2..c78f5246780 100644 --- a/packages/tx/src/7702/tx.ts +++ b/packages/tx/src/7702/tx.ts @@ -20,6 +20,7 @@ import { AccessLists, AuthorizationLists, validateNotArray } from '../util.js' import { createEOACode7702Tx } from './constructors.js' +import type { SSZTransactionType } from '../baseTransaction.js' import type { AccessList, AccessListBytes, @@ -184,6 +185,9 @@ export class EOACode7702Transaction extends BaseTransaction /** * This base class will likely be subject to further @@ -247,6 +251,7 @@ export abstract class BaseTransaction * representation for external signing use {@link BaseTransaction.getMessageToSign}. */ abstract raw(): TxValuesArray[T] + abstract sszRaw(): SSZTransactionType /** * Returns the encoding of the transaction. @@ -361,6 +366,10 @@ export abstract class BaseTransaction } } + toExecutionPayloadTx(): ssz.TransactionV1 { + return toPayloadJson(this.sszRaw()) + } + /** * Returns a new transaction with the same data fields as the current, but now signed * @param v The `v` value of the signature diff --git a/packages/tx/src/index.ts b/packages/tx/src/index.ts index 281e7aeff66..2021b66af6e 100644 --- a/packages/tx/src/index.ts +++ b/packages/tx/src/index.ts @@ -11,6 +11,7 @@ export * from './params.js' export { createTx, createTxFromBlockBodyData, + createTxFromExecutionPayloadTx, createTxFromJSONRPCProvider, createTxFromRLP, createTxFromRPC, diff --git a/packages/tx/src/legacy/constructors.ts b/packages/tx/src/legacy/constructors.ts index f69acedb255..b227dd58b83 100644 --- a/packages/tx/src/legacy/constructors.ts +++ b/packages/tx/src/legacy/constructors.ts @@ -1,10 +1,15 @@ import { RLP } from '@ethereumjs/rlp' -import { validateNoLeadingZeroes } from '@ethereumjs/util' +import { BIGINT_2, bytesToBigInt, validateNoLeadingZeroes } from '@ethereumjs/util' import { LegacyTx } from './tx.js' import type { TxOptions } from '../types.js' import type { TxData, TxValuesArray } from './tx.js' +import type { ValueOf } from '@chainsafe/ssz' +import type { ssz } from '@ethereumjs/util' + +export type ReplayableTransactionType = ValueOf +export type LegacyTransactionType = ValueOf /** * Instantiate a transaction from a data dictionary. @@ -67,3 +72,37 @@ export function createLegacyTxFromRLP(serialized: Uint8Array, opts: TxOptions = return createLegacyTxFromBytesArray(values as TxValuesArray, opts) } + +export function createLegacyTxFromSszTx( + sszWrappedTx: ReplayableTransactionType | LegacyTransactionType, + opts: TxOptions = {}, +) { + const { + payload: { + nonce, + chainId, + maxFeesPerGas: { regular: gasPrice }, + gas: gasLimit, + to, + value, + input: data, + }, + signature: { ecdsaSignature }, + } = sszWrappedTx as LegacyTransactionType + + const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) + const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) + const yParity = bytesToBigInt(ecdsaSignature.slice(64)) + + let v + if (chainId !== null && chainId !== undefined) { + v = yParity + BIGINT_2 * chainId + BigInt(35) + } else { + v = yParity + BigInt(27) + } + + return createLegacyTxFromBytesArray( + [nonce, gasPrice, gasLimit, to, value, data, v, r, s] as TxValuesArray, + opts, + ) +} diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index 2b02ec2e026..b9cc7cf0218 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -1,12 +1,17 @@ import { Common } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { + BIGINT_0, + BIGINT_1, BIGINT_2, BIGINT_8, MAX_INTEGER, + bigIntToBytes, bigIntToHex, bigIntToUnpaddedBytes, bytesToBigInt, + calculateSigRecovery, + setLengthLeft, toBytes, unpadBytes, } from '@ethereumjs/util' @@ -20,6 +25,7 @@ import { validateNotArray } from '../util.js' import { createLegacyTx } from './constructors.js' +import type { SSZTransactionType } from '../baseTransaction.js' import type { TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, @@ -128,6 +134,43 @@ export class LegacyTx extends BaseTransaction { ] } + sszRaw(): SSZTransactionType { + if (this.r === undefined || this.s === undefined || this.v === undefined) { + throw Error(`Transaction not signed for sszSerialize`) + } + + const chainId = this.supports(Capability.EIP155ReplayProtection) ? this.common.chainId() : null + const payload = { + type: BigInt(this.type), + chainId, + nonce: this.nonce, + maxFeesPerGas: { regular: this.gasPrice, blob: null }, + gas: this.gasLimit, + to: this.to?.bytes ?? null, + value: this.value, + input: this.data, + accessList: null, + maxPriorityFeesPerGas: null, + blobVersionedHashes: null, + } + + const yParity = calculateSigRecovery(this.v, chainId ?? undefined) + if (yParity !== BIGINT_0 && yParity !== BIGINT_1) { + throw Error(`Invalid yParity=${yParity} v=${this.v} chainid:${this.common.chainId()}`) + } + + const signature = { + from: this.getSenderAddress().bytes, + ecdsaSignature: Uint8Array.from([ + ...setLengthLeft(bigIntToBytes(this.r), 32), + ...setLengthLeft(bigIntToBytes(this.s), 32), + ...setLengthLeft(bigIntToBytes(yParity), 1), + ]), + } + + return { payload, signature } + } + /** * Returns the serialized encoding of the legacy transaction. * diff --git a/packages/tx/src/transactionFactory.ts b/packages/tx/src/transactionFactory.ts index a9d2153ec7e..59d988f0f3a 100644 --- a/packages/tx/src/transactionFactory.ts +++ b/packages/tx/src/transactionFactory.ts @@ -1,13 +1,26 @@ import { fetchFromProvider, getProvider } from '@ethereumjs/util' -import { createFeeMarket1559Tx, createFeeMarket1559TxFromRLP } from './1559/constructors.js' -import { createAccessList2930Tx, createAccessList2930TxFromRLP } from './2930/constructors.js' -import { createBlob4844Tx, createBlob4844TxFromRLP } from './4844/constructors.js' +import { + createFeeMarket1559Tx, + createFeeMarket1559TxFromRLP, + createFeeMarket1559TxFromSszTx, +} from './1559/constructors.js' +import { + createAccessList2930Tx, + createAccessList2930TxFromRLP, + createAccessList2930TxFromSszTx, +} from './2930/constructors.js' +import { + createBlob4844Tx, + createBlob4844TxFromRLP, + createBlob4844TxFromSszTx, +} from './4844/constructors.js' import { createEOACode7702Tx, createEOACode7702TxFromRLP } from './7702/constructors.js' import { createLegacyTx, createLegacyTxFromBytesArray, createLegacyTxFromRLP, + createLegacyTxFromSszTx, } from './legacy/constructors.js' import { TransactionType, @@ -17,10 +30,15 @@ import { isFeeMarket1559TxData, isLegacyTxData, } from './types.js' -import { normalizeTxParams } from './util.js' +import { fromPayloadJson, normalizeTxParams } from './util.js' +import type { Eip1559TransactionType } from './1559/constructors.js' +import type { Eip2930TransactionType } from './2930/constructors.js' +import type { Eip4844TransactionType } from './4844/constructors.js' +import type { LegacyTransactionType, ReplayableTransactionType } from './legacy/constructors.js' import type { Transaction, TxData, TxOptions, TypedTxData } from './types.js' -import type { EthersProvider } from '@ethereumjs/util' +import type { SSZTransaction } from './util.js' +import type { EthersProvider, ssz } from '@ethereumjs/util' /** * Create a transaction from a `txData` object * @@ -139,3 +157,45 @@ export async function createTxFromJSONRPCProvider( } return createTxFromRPC(txData, txOptions) } + +export function createTxFromSszTx( + sszStableTx: SSZTransaction, + txOptions: TxOptions = {}, +): Transaction[T] { + const txType = Number(sszStableTx.payload.type) + + switch (txType) { + case TransactionType.Legacy: + return createLegacyTxFromSszTx( + sszStableTx as ReplayableTransactionType | LegacyTransactionType, + txOptions, + ) as Transaction[T] + case TransactionType.AccessListEIP2930: + return createAccessList2930TxFromSszTx( + sszStableTx as Eip2930TransactionType, + txOptions, + ) as Transaction[T] + case TransactionType.FeeMarketEIP1559: + return createFeeMarket1559TxFromSszTx( + sszStableTx as Eip1559TransactionType, + txOptions, + ) as Transaction[T] + case TransactionType.BlobEIP4844: + return createBlob4844TxFromSszTx( + sszStableTx as Eip4844TransactionType, + txOptions, + ) as Transaction[T] + case TransactionType.EOACodeEIP7702: + throw Error('not implemented') + default: + throw new Error(`TypedTransaction with ID ${txType} unknown`) + } +} + +export function createTxFromExecutionPayloadTx( + data: ssz.TransactionV1, + txOptions: TxOptions = {}, +): Transaction[T] { + const sszStableTx = fromPayloadJson(data) + return createTxFromSszTx(sszStableTx, txOptions) +} diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index 118a5f67914..32dcf987e0d 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -576,6 +576,7 @@ export interface JSONRPCTx { maxFeePerBlobGas?: string // QUANTITY - max data fee for blob transactions blobVersionedHashes?: string[] // DATA - array of 32 byte versioned hashes for blob transactions yParity?: string // DATA - parity of the y-coordinate of the public key + inclusionProof?: { merkleBranch: string[]; transactionsRoot: string; transactionRoot: string } // DATA - array of 32 byte merkle hash for eip 6493 inclusion proof with 0 as transactions root } /* diff --git a/packages/tx/src/util.ts b/packages/tx/src/util.ts index 7e6f30964bd..5568e563423 100644 --- a/packages/tx/src/util.ts +++ b/packages/tx/src/util.ts @@ -6,8 +6,10 @@ import { type PrefixedHexString, SECP256K1_ORDER_DIV_2, TypeOutput, + bigIntToHex, bytesToBigInt, bytesToHex, + hexToBigInt, hexToBytes, setLengthLeft, toBytes, @@ -27,7 +29,9 @@ import type { TransactionType, TypedTxData, } from './types.js' +import type { ValueOf } from '@chainsafe/ssz' import type { Common } from '@ethereumjs/common' +import type { ssz } from '@ethereumjs/util' export function checkMaxInitCodeSize(common: Common, length: number) { const maxInitCodeSize = common.param('maxInitCodeSize') @@ -305,3 +309,120 @@ export const normalizeTxParams = (txParamsFromRPC: any): TypedTxData => { return txParams } + +function getDataOrNull(elem: PrefixedHexString | null) { + if (elem === null) { + return null + } + + return hexToBytes(elem) +} + +function getQuantityOrNull(elem: PrefixedHexString | null) { + if (elem === null) { + return null + } + + return hexToBigInt(elem) +} + +export type SSZTransaction = ValueOf +export function fromPayloadJson(payloadTx: ssz.TransactionV1): SSZTransaction { + const { payload, signature } = payloadTx + return { + payload: { + type: getQuantityOrNull(payload.type), + chainId: getQuantityOrNull(payload.chainId), + nonce: getQuantityOrNull(payload.nonce), + maxFeesPerGas: payload.maxFeesPerGas + ? { + regular: getQuantityOrNull(payload.maxFeesPerGas.regular), + blob: getQuantityOrNull(payload.maxFeesPerGas.blob), + } + : null, + gas: getQuantityOrNull(payload.gas), + to: getDataOrNull(payload.to), + value: getQuantityOrNull(payload.value), + input: getDataOrNull(payload.input), + accessList: payload.accessList + ? payload.accessList.map((pal) => { + return { + address: hexToBytes(pal.address), + storageKeys: pal.storageKeys.map((sk) => hexToBytes(sk)), + } + }) + : null, + maxPriorityFeesPerGas: payload.maxPriorityFeesPerGas + ? { + regular: getQuantityOrNull(payload.maxPriorityFeesPerGas.regular), + blob: getQuantityOrNull(payload.maxPriorityFeesPerGas.blob), + } + : null, + blobVersionedHashes: payload.blobVersionedHashes + ? payload.blobVersionedHashes.map((vh) => hexToBytes(vh)) + : null, + }, + signature: { + from: getDataOrNull(signature.from), + ecdsaSignature: getDataOrNull(signature.ecdsaSignature), + }, + } +} + +function setDataOrNull(elem: Uint8Array | null) { + if (elem === null) { + return null + } + + return bytesToHex(elem) +} + +function setQuantityOrNull(elem: bigint | null) { + if (elem === null) { + return null + } + + return bigIntToHex(elem) +} + +export function toPayloadJson(sszTx: SSZTransaction): ssz.TransactionV1 { + const { payload, signature } = sszTx + return { + payload: { + type: setQuantityOrNull(payload.type), + chainId: setQuantityOrNull(payload.chainId), + nonce: setQuantityOrNull(payload.nonce), + maxFeesPerGas: payload.maxFeesPerGas + ? { + regular: setQuantityOrNull(payload.maxFeesPerGas.regular), + blob: setQuantityOrNull(payload.maxFeesPerGas.blob), + } + : null, + gas: setQuantityOrNull(payload.gas), + to: setDataOrNull(payload.to), + value: setQuantityOrNull(payload.value), + input: setDataOrNull(payload.input), + accessList: payload.accessList + ? payload.accessList.map((pal) => { + return { + address: bytesToHex(pal.address), + storageKeys: pal.storageKeys.map((sk) => bytesToHex(sk)), + } + }) + : null, + maxPriorityFeesPerGas: payload.maxPriorityFeesPerGas + ? { + regular: setQuantityOrNull(payload.maxPriorityFeesPerGas.regular), + blob: setQuantityOrNull(payload.maxPriorityFeesPerGas.blob), + } + : null, + blobVersionedHashes: payload.blobVersionedHashes + ? payload.blobVersionedHashes.map((vh) => bytesToHex(vh)) + : null, + }, + signature: { + from: setDataOrNull(signature.from), + ecdsaSignature: setDataOrNull(signature.ecdsaSignature), + }, + } +} diff --git a/packages/tx/test/eip6493.spec.ts b/packages/tx/test/eip6493.spec.ts new file mode 100644 index 00000000000..b529e81db09 --- /dev/null +++ b/packages/tx/test/eip6493.spec.ts @@ -0,0 +1,230 @@ +import { Hardfork, Mainnet, createCustomCommon } from '@ethereumjs/common' +import { bytesToHex, hexToBytes, ssz } from '@ethereumjs/util' +import { loadKZG } from 'kzg-wasm' +import { assert, describe, it } from 'vitest' + +import { + AccessListEIP2930Transaction, + BlobEIP4844Transaction, + FeeMarketEIP1559Transaction, + LegacyTransaction, + toPayloadJson, +} from '../src/index.js' +import { createTx, createTxFromExecutionPayloadTx } from '../src/transactionFactory.js' + +import type { Kzg } from '@ethereumjs/util' +function getLegacyTestCaseData() { + const txData = { + type: '0x0', + nonce: '0x0', + to: null, + gasLimit: '0x3d090', + gasPrice: '0xe8d4a51000', + maxPriorityFeePerGas: null, + maxFeePerGas: null, + value: '0x0', + data: '0x60608060095f395ff33373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35600143035500', + v: '0x1b', + r: '0x539', + s: '0x1b9b6eb1f0', + } + + return [ + txData, + // hash + '0xe43ec833884324f31c2e8314534d5b15233d84f32f05a05ea2a45649b587a9df', + // sender + '0x72eed28860ac985f1ec32306564b5926ea7c0b70', + // no special common required + undefined, + ] +} + +function get2930TestCaseData() { + const txData = { + type: '0x01', + data: '0x', + gasLimit: 0x62d4, + gasPrice: 0x3b9aca00, + nonce: 0x00, + to: '0xdf0a88b2b68c673713a8ec826003676f272e3573', + value: 0x01, + chainId: '0x796f6c6f763378', + accessList: [ + [ + hexToBytes('0x0000000000000000000000000000000000001337'), + [hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000000')], + ], + ], + v: '0x0', + r: '0x294ac94077b35057971e6b4b06dfdf55a6fbed819133a6c1d31e187f1bca938d', + s: '0x0be950468ba1c25a5cb50e9f6d8aa13c8cd21f24ba909402775b262ac76d374d', + } + + const customChainParams = { + name: 'custom', + chainId: txData.chainId, + eips: [2930], + } + const usedCommon = createCustomCommon(customChainParams, Mainnet, { + hardfork: Hardfork.Berlin, + }) + usedCommon.setEIPs([2930]) + + return [ + txData, + // hash + '0xbbd570a3c6acc9bb7da0d5c0322fe4ea2a300db80226f7df4fef39b2d6649eec', + // sender + '0x96216849c49358b10257cb55b28ea603c874b05e', + // 2930 common + usedCommon, + ] +} + +function get1559TestCaseData() { + const txData = { + type: '0x02', + data: '0x', + gasLimit: 0x62d4, + maxFeesPerGas: 0x3b9aca00, + maxPriorityFeesPerGas: 0x1b9aca00, + nonce: 0x00, + to: '0xdf0a88b2b68c673713a8ec826003676f272e3573', + value: 0x01, + chainId: '0x796f6c6f763378', + accessList: [ + [ + hexToBytes('0x0000000000000000000000000000000000001337'), + [hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000000')], + ], + ], + v: '0x0', + r: '0x294ac94077b35057971e6b4b06dfdf55a6fbed819133a6c1d31e187f1bca938d', + s: '0x0be950468ba1c25a5cb50e9f6d8aa13c8cd21f24ba909402775b262ac76d374d', + } + + const customChainParams = { + name: 'custom', + chainId: txData.chainId, + eips: [1559], + } + const usedCommon = createCustomCommon(customChainParams, Mainnet, { + hardfork: Hardfork.Berlin, + }) + usedCommon.setEIPs([1559]) + + return [ + txData, + // hash + '0x1390bffdfec7959c976754e55b1849dd7cbbdca78068cc544f2c8e8e8fe3bd8e', + // sender + '0xdcf0e8f6d5c3876912db8e06e2a690b99004b798', + // 1559 common + usedCommon, + ] +} + +function get4844TestCaseData(kzg: Kzg) { + const txData = { + type: '0x3', + nonce: '0x0', + gasPrice: null, + maxPriorityFeePerGas: '0x12a05f200', + maxFeePerGas: '0x12a05f200', + gasLimit: '0x33450', + value: '0xbc614e', + data: '0x', + v: '0x0', + r: '0x8a83833ec07806485a4ded33f24f5cea4b8d4d24dc8f357e6d446bcdae5e58a7', + s: '0x68a2ba422a50cf84c0b5fcbda32ee142196910c97198ffd99035d920c2b557f8', + to: '0xffb38a7a99e3e2335be83fc74b7faa19d5531243', + chainId: '0x28757b3', + accessList: null, + maxFeePerBlobGas: '0xb2d05e00', + blobVersionedHashes: ['0x01b0a4cdd5f55589f5c5b4d46c76704bb6ce95c0a8c09f77f197a57808dded28'], + } + + const customChainParams = { + name: 'custom', + chainId: txData.chainId, + eips: [4844], + } + const usedCommon = createCustomCommon(customChainParams, Mainnet, { + hardfork: Hardfork.Cancun, + customCrypto: { kzg }, + }) + usedCommon.setEIPs([4844]) + + return [ + txData, + // hash + '0xe5e02be0667b6d31895d1b5a8b916a6761cbc9865225c6144a3e2c50936d173e', + // sender + '0xa95d8b63835662e0d6fb0fb096994e2897072e2a', + // 4844 common + usedCommon, + ] +} + +describe('ssz <> rlp converstion', async () => { + const kzg = await loadKZG() + + const testCases = [ + ['LegacyTransaction', LegacyTransaction, ssz.ReplayableTransaction, ...getLegacyTestCaseData()], + [ + 'AccessListEIP2930Transaction', + AccessListEIP2930Transaction, + ssz.Eip2930Transaction, + ...get2930TestCaseData(), + ], + [ + 'FeeMarketEIP1559Transaction', + FeeMarketEIP1559Transaction, + ssz.Eip1559Transaction, + ...get1559TestCaseData(), + ], + [ + 'BlobEIP4844Transaction', + BlobEIP4844Transaction, + ssz.Eip4844Transaction, + ...get4844TestCaseData(kzg), + ], + ] + + for (const [txTypeName, _txType, sszType, txData, txHash, txSender, common] of testCases) { + it(`${txTypeName}`, () => { + const origTx = createTx(txData, { common }) + const calTxHash = bytesToHex(origTx.hash()) + assert.equal(calTxHash, txHash, 'transaction should be correctly loaded') + + const sszTx = origTx.sszRaw() + const sszJson = sszType.toJson(origTx.sszRaw()) + assert.equal(sszJson.signature.from, txSender, 'ssz format should be correct') + + const payloadJson = toPayloadJson(sszTx) + const payloadTx = createTxFromExecutionPayloadTx(payloadJson, { common }) + const payloadTxHash = bytesToHex(payloadTx.hash()) + assert.equal(payloadTxHash, txHash, 'transaction should be correctly loaded') + + const payloadSszJson = sszType.toJson(payloadTx.sszRaw()) + assert.equal(payloadSszJson.signature.from, txSender, 'ssz format should be correct') + }) + } + + it(`hashTree root of different transactions`, () => { + const transactions = testCases.map( + ([_txTypeName, _txType, _sszType, txData, _txHash, _txSender, common]) => { + const origTx = createTx(txData, { common }) + return origTx.sszRaw() + }, + ) + + const transactionsRoot = ssz.Transactions.hashTreeRoot(transactions) + assert.equal( + bytesToHex(transactionsRoot), + '0xe15ff0a75fc9889f4ce89afd2ae65ec570881a7ac6bf78ca664b1d04d0419e34', + 'transactions root should match', + ) + }) +}) diff --git a/packages/util/package.json b/packages/util/package.json index 6bafb0191c2..531d647e1ac 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -92,6 +92,8 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { + "@chainsafe/persistent-merkle-tree": "^0.7.2", + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/rlp": "^5.0.2", "ethereum-cryptography": "^3.0.0" }, diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index d1f6dd95e73..9aeb5eaf7f3 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -65,5 +65,6 @@ export * from './lock.js' export * from './mapDB.js' export * from './provider.js' export * from './request.js' +export * as ssz from './ssz.js' export * from './tasks.js' export * from './verkle.js' diff --git a/packages/util/src/ssz.ts b/packages/util/src/ssz.ts new file mode 100644 index 00000000000..c19d2994a86 --- /dev/null +++ b/packages/util/src/ssz.ts @@ -0,0 +1,387 @@ +import { Tree, hasher } from '@chainsafe/persistent-merkle-tree' +import { + BitArray, + ByteListType, + ByteVectorType, + ContainerType, + ListCompositeType, + OptionalType, + ProfileType, + StableContainerType, + UintBigintType, + byteArrayEquals, +} from '@chainsafe/ssz' + +import type { PrefixedHexString } from './types.js' +import type { ValueOf } from '@chainsafe/ssz' + +export const MAX_CALLDATA_SIZE = 16_777_216 +export const MAX_ACCESS_LIST_STORAGE_KEYS = 524_288 +export const MAX_ACCESS_LIST_SIZE = 524_288 +export const ECDSA_SIGNATURE_SIZE = 65 + +export const MAX_FEES_PER_GAS_FIELDS = 16 +export const MAX_TRANSACTION_PAYLOAD_FIELDS = 32 +export const MAX_TRANSACTION_SIGNATURE_FIELDS = 16 +export const MAX_BLOB_COMMITMENTS_PER_BLOCK = 4096 + +export const Uint8 = new UintBigintType(1) +export const Uint64 = new UintBigintType(8) +export const Uint256 = new UintBigintType(32) + +export const Bytes20 = new ByteVectorType(20) +export const Bytes32 = new ByteVectorType(32) +export const Bytes256 = new ByteVectorType(256) + +export const FeePerGas = Uint256 +export const ChainId = Uint64 +export const TransactionType = Uint8 +export const ExecutionAddress = Bytes20 + +export const FeesPerGas = new StableContainerType( + { + regular: new OptionalType(FeePerGas), + blob: new OptionalType(FeePerGas), + }, + MAX_FEES_PER_GAS_FIELDS, + { typeName: 'BasicFeesPerGas', jsonCase: 'eth2' }, +) + +export const AccessTuple = new ContainerType( + { + address: ExecutionAddress, + storageKeys: new ListCompositeType(Bytes32, MAX_ACCESS_LIST_STORAGE_KEYS), + }, + { typeName: 'AccessTuple', jsonCase: 'eth2' }, +) + +export const AccessList = new ListCompositeType(AccessTuple, MAX_ACCESS_LIST_SIZE) +export const TransactionTo = new OptionalType(ExecutionAddress) +export const TransactionInput = new ByteListType(MAX_CALLDATA_SIZE) +export const VersionedHashes = new ListCompositeType(Bytes32, MAX_BLOB_COMMITMENTS_PER_BLOCK) + +export const TransactionPayload = new StableContainerType( + { + type: new OptionalType(TransactionType), + chainId: new OptionalType(ChainId), + nonce: new OptionalType(Uint64), + maxFeesPerGas: new OptionalType(FeesPerGas), + gas: new OptionalType(Uint64), + to: TransactionTo, + value: new OptionalType(Uint256), + input: new OptionalType(TransactionInput), + accessList: new OptionalType(AccessList), + maxPriorityFeesPerGas: new OptionalType(FeesPerGas), + blobVersionedHashes: new OptionalType(VersionedHashes), + }, + MAX_TRANSACTION_PAYLOAD_FIELDS, + { typeName: 'TransactionPayload', jsonCase: 'eth2' }, +) + +export const EcdsaSignature = new ByteVectorType(ECDSA_SIGNATURE_SIZE) +export const TransactionSignature = new StableContainerType( + { + from: new OptionalType(ExecutionAddress), + ecdsaSignature: new OptionalType(EcdsaSignature), + }, + MAX_TRANSACTION_SIGNATURE_FIELDS, + { typeName: 'TransactionSignature', jsonCase: 'eth2' }, +) + +export const Transaction = new ContainerType( + { + payload: TransactionPayload, + signature: TransactionSignature, + }, + { typeName: 'Transaction', jsonCase: 'eth2' }, +) + +function getFullArray(prefixVec: boolean[], maxVecLength: number): BitArray { + const fullVec = [ + ...prefixVec, + ...Array.from({ length: maxVecLength - prefixVec.length }, () => false), + ] + return BitArray.fromBoolArray(fullVec) +} + +export const BasicFeesPerGas = new ProfileType( + { regular: FeePerGas }, + getFullArray([true], MAX_FEES_PER_GAS_FIELDS), + { typeName: 'BasicFeesPerGas', jsonCase: 'eth2' }, +) + +export const BlobFeesPerGas = new ProfileType( + { + regular: FeePerGas, + blob: FeePerGas, + }, + getFullArray([true, true], MAX_FEES_PER_GAS_FIELDS), + { typeName: 'BlobFeesPerGas', jsonCase: 'eth2' }, +) + +export const EcdsaTransactionSignature = new ProfileType( + { + from: ExecutionAddress, + ecdsaSignature: EcdsaSignature, + }, + getFullArray([true, true], MAX_TRANSACTION_SIGNATURE_FIELDS), + { typeName: 'EcdsaTransactionSignature', jsonCase: 'eth2' }, +) + +export const ReplayableTransactionPayload = new ProfileType( + { + type: TransactionType, + nonce: Uint64, + maxFeesPerGas: BasicFeesPerGas, + gas: Uint64, + to: TransactionTo, + value: Uint256, + input: TransactionInput, + }, + getFullArray([true, false, true, true, true, true, true, true], MAX_FEES_PER_GAS_FIELDS), + { typeName: 'ReplayableTransactionPayload', jsonCase: 'eth2' }, +) + +export const ReplayableTransaction = new ContainerType( + { + payload: ReplayableTransactionPayload, + signature: EcdsaTransactionSignature, + }, + { typeName: 'ReplayableTransaction', jsonCase: 'eth2' }, +) + +export const LegacyTransactionPayload = new ProfileType( + { + type: TransactionType, + chainId: ChainId, + nonce: Uint64, + maxFeesPerGas: BasicFeesPerGas, + gas: Uint64, + to: TransactionTo, + value: Uint256, + input: TransactionInput, + }, + getFullArray([true, true, true, true, true, true, true, true], MAX_FEES_PER_GAS_FIELDS), + { typeName: 'LegacyTransactionPayload', jsonCase: 'eth2' }, +) + +export const LegacyTransaction = new ContainerType( + { + payload: LegacyTransactionPayload, + signature: EcdsaTransactionSignature, + }, + { typeName: 'LegacyTransaction', jsonCase: 'eth2' }, +) + +export const Eip2930TransactionPayload = new ProfileType( + { + type: TransactionType, + chainId: ChainId, + nonce: Uint64, + maxFeesPerGas: BasicFeesPerGas, + gas: Uint64, + to: TransactionTo, + value: Uint256, + input: TransactionInput, + accessList: AccessList, + }, + getFullArray([true, true, true, true, true, true, true, true, true], MAX_FEES_PER_GAS_FIELDS), + { typeName: 'Eip2930TransactionPayload', jsonCase: 'eth2' }, +) + +export const Eip2930Transaction = new ContainerType( + { + payload: Eip2930TransactionPayload, + signature: EcdsaTransactionSignature, + }, + { typeName: 'Eip2930Transaction', jsonCase: 'eth2' }, +) + +export const Eip1559TransactionPayload = new ProfileType( + { + type: TransactionType, + chainId: ChainId, + nonce: Uint64, + maxFeesPerGas: BasicFeesPerGas, + gas: Uint64, + to: TransactionTo, + value: Uint256, + input: TransactionInput, + accessList: AccessList, + maxPriorityFeesPerGas: BasicFeesPerGas, + }, + getFullArray( + [true, true, true, true, true, true, true, true, true, true], + MAX_FEES_PER_GAS_FIELDS, + ), + { typeName: 'Eip1559TransactionPayload', jsonCase: 'eth2' }, +) + +export const Eip1559Transaction = new ContainerType( + { + payload: Eip1559TransactionPayload, + signature: EcdsaTransactionSignature, + }, + { typeName: 'Eip1559Transaction', jsonCase: 'eth2' }, +) + +export const Eip4844TransactionPayload = new ProfileType( + { + type: TransactionType, + chainId: ChainId, + nonce: Uint64, + maxFeesPerGas: BlobFeesPerGas, + gas: Uint64, + to: ExecutionAddress, + value: Uint256, + input: TransactionInput, + accessList: AccessList, + maxPriorityFeesPerGas: BlobFeesPerGas, + blobVersionedHashes: VersionedHashes, + }, + getFullArray( + [true, true, true, true, true, true, true, true, true, true, true], + MAX_FEES_PER_GAS_FIELDS, + ), + { typeName: 'Eip4844TransactionPayload', jsonCase: 'eth2' }, +) + +export const Eip4844Transaction = new ContainerType( + { + payload: Eip4844TransactionPayload, + signature: EcdsaTransactionSignature, + }, + { typeName: 'Eip4844Transaction', jsonCase: 'eth2' }, +) + +const MAX_WITHDRAWALS_PER_PAYLOAD = 16 +export const Withdrawal = new ContainerType( + { + index: Uint64, + validatorIndex: Uint64, + address: ExecutionAddress, + amount: Uint64, + }, + { typeName: 'Withdrawal', jsonCase: 'eth2' }, +) +export const Withdrawals = new ListCompositeType(Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD) + +const MAX_TRANSACTIONS_PER_PAYLOAD = 1048576 +export const Transactions = new ListCompositeType(Transaction, MAX_TRANSACTIONS_PER_PAYLOAD) +export const TransactionRootsList = new ListCompositeType(Bytes32, MAX_TRANSACTIONS_PER_PAYLOAD) +export type TransactionsType = ValueOf + +const TRANSACTION_GINDEX0 = 2097152n +export function computeTransactionInclusionProof( + transactions: TransactionsType, + index: number, + fromRoots = true, +): { merkleBranch: Uint8Array[]; transactionRoot: Uint8Array } { + if (index >= transactions.length) { + throw Error(`Invalid index=${index} > transactions=${transactions.length}`) + } + + const transactionRoot = Transaction.hashTreeRoot(transactions[index]) + + let merkleBranch + if (fromRoots === true) { + const transactionRoots = transactions.map((tx) => Transaction.hashTreeRoot(tx)) + const TransactionsRootView = TransactionRootsList.toView(transactionRoots) + // transaction index is its g index in the list + merkleBranch = new Tree(TransactionsRootView.node).getSingleProof( + TRANSACTION_GINDEX0 + BigInt(index), + ) + } else { + const TransactionsView = Transactions.toView(transactions) + // transaction index is its g index in the list + merkleBranch = new Tree(TransactionsView.node).getSingleProof( + TRANSACTION_GINDEX0 + BigInt(index), + ) + } + + return { merkleBranch, transactionRoot } +} + +const TRANSACTION_PROOF_DEPTH = 21 +/** + * Verify that the given ``leaf`` is on the merkle branch ``proof`` + * starting with the given ``root``. + * + * Browser friendly version of verifyMerkleBranch + */ +export function isValidTransactionProof( + transactionRoot: Uint8Array, + proof: Uint8Array[], + index: number, + transactionsRoot: Uint8Array, +): boolean { + let value = transactionRoot + for (let i = 0; i < TRANSACTION_PROOF_DEPTH; i++) { + if (Math.floor(index / 2 ** i) % 2) { + value = hasher.digest64(proof[i], value) + } else { + value = hasher.digest64(value, proof[i]) + } + } + return byteArrayEquals(value, transactionsRoot) +} + +export type FeesPerGasV1 = { + regular: PrefixedHexString | null // Quantity 64 bytes + blob: PrefixedHexString | null // Quantity 64 bytes +} + +export type AccessTupleV1 = { + address: PrefixedHexString // DATA 20 bytes + storageKeys: PrefixedHexString[] // Data 32 bytes MAX_ACCESS_LIST_STORAGE_KEYS array +} + +export type TransactionPayloadV1 = { + type: PrefixedHexString | null // Quantity, 1 byte + chainId: PrefixedHexString | null // Quantity 8 bytes + nonce: PrefixedHexString | null // Quantity 8 bytes + maxFeesPerGas: FeesPerGasV1 | null + gas: PrefixedHexString | null // Quantity 8 bytes + to: PrefixedHexString | null // DATA 20 bytes + value: PrefixedHexString | null // Quantity 64 bytes + input: PrefixedHexString | null // max MAX_CALLDATA_SIZE bytes, + accessList: AccessTupleV1[] | null + maxPriorityFeesPerGas: FeesPerGasV1 | null + blobVersionedHashes: PrefixedHexString[] | null // DATA 32 bytes array +} + +export type TransactionSignatureV1 = { + from: PrefixedHexString | null // DATA 20 bytes + ecdsaSignature: PrefixedHexString | null // DATA 65 bytes or null +} + +export type TransactionV1 = { + payload: TransactionPayloadV1 + signature: TransactionSignatureV1 +} + +export const MAX_BLOCKHEADER_FIELDS = 64 +const MAX_EXTRA_DATA_BYTES = 32 + +export const BlockHeader = new StableContainerType( + { + parentHash: new OptionalType(Bytes32), + coinbase: new OptionalType(Bytes20), + stateRoot: new OptionalType(Bytes32), + transactionsTrie: new OptionalType(Bytes32), + receiptsTrie: new OptionalType(Bytes32), + number: new OptionalType(Uint64), + gasLimits: new OptionalType(FeesPerGas), + gasUsed: new OptionalType(FeesPerGas), + timestamp: new OptionalType(Uint64), + extraData: new OptionalType(new ByteListType(MAX_EXTRA_DATA_BYTES)), + mixHash: new OptionalType(Bytes32), + baseFeePerGas: new OptionalType(FeesPerGas), + withdrawalsRoot: new OptionalType(Bytes32), + excessGas: new OptionalType(FeesPerGas), + parentBeaconBlockRoot: new OptionalType(Bytes32), + requestsRoot: new OptionalType(Bytes32), + }, + MAX_BLOCKHEADER_FIELDS, + { typeName: 'BlockHeader', jsonCase: 'eth2' }, +) diff --git a/packages/util/test/ssz.spec.ts b/packages/util/test/ssz.spec.ts new file mode 100644 index 00000000000..1ac7457844a --- /dev/null +++ b/packages/util/test/ssz.spec.ts @@ -0,0 +1,36 @@ +import { assert, describe, it } from 'vitest' + +import { ssz } from '../src/index.js' + +const eip1559SszJson = { + payload: { + type: '2', + chain_id: '1', + nonce: '0', + max_fees_per_gas: { regular: '100' }, + gas: '30000000', + to: '0x00000000219ab540356cbb839cbe05303d7705fa', + value: '32000000000000000000', + input: + '0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001208cd4e5a69709cf8ee5b1b73d6efbf3f33bcac92fb7e4ce62b2467542fb50a72d0000000000000000000000000000000000000000000000000000000000000030ac842878bb70009552a4cfcad801d6e659c50bd50d7d03306790cb455ce7363c5b6972f0159d170f625a99b2064dbefc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020010000000000000000000000818ccb1c4eda80270b04d6df822b1e72dd83c3030000000000000000000000000000000000000000000000000000000000000060a747f75c72d0cf0d2b52504c7385b516f0523e2f0842416399f42b4aee5c6384a5674f6426b1cc3d0827886fa9b909e616f5c9f61f986013ed2b9bf37071cbae951136265b549f44e3c8e26233c0433e9124b7fd0dc86e82f9fedfc0a179d769', + access_list: [], + max_priority_fees_per_gas: { regular: '0' }, + }, + signature: { + from: '0x610adc49ecd66cbf176a8247ebd59096c031bd9f', + ecdsa_signature: + '0x5f8397122e00d9cdea67c83ec99a4694af24c3d6f25c4dde8f2fa4277d85c96754b2ea7851948fe99288049edfd8ca53c4aee79043e91afb513de0664822277900', + }, +} + +describe('profile<>stable tx container', function () { + it(`EIP 1559 tx profile<>stable conversion`, () => { + const profileSszValue = ssz.Eip1559Transaction.fromJson(eip1559SszJson) + const profileSszBytes = ssz.Eip1559Transaction.serialize(profileSszValue) + + const stableTx = ssz.Transaction.deserialize(profileSszBytes) + const stableTxJson = ssz.Transaction.toJson(stableTx) + + assert.deepEqual(stableTxJson, eip1559SszJson, 'the transaction jsons should match') + }) +}) diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index b95fec859d2..75f0a3b578b 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -2,7 +2,9 @@ import { createBlock, createSealedCliqueBlock, genRequestsTrieRoot, + genTransactionsSszRoot, genTransactionsTrieRoot, + genWithdrawalsSszRoot, genWithdrawalsTrieRoot, } from '@ethereumjs/block' import { ConsensusType, Hardfork } from '@ethereumjs/common' @@ -143,10 +145,19 @@ export class BlockBuilder { * Calculates and returns the transactionsTrie for the block. */ public async transactionsTrie() { - return genTransactionsTrieRoot( - this.transactions, - new MerklePatriciaTrie({ common: this.vm.common }), - ) + return this.vm.common.isActivatedEIP(6493) + ? genTransactionsSszRoot(this.transactions) + : genTransactionsTrieRoot(this.transactions, new MerklePatriciaTrie({ common: this.vm.common })) + } + + public async withdrawalsTrie() { + if (this.withdrawals === undefined) { + return + } + + return this.vm.common.isActivatedEIP(6493) + ? genWithdrawalsSszRoot(this.withdrawals) + : genWithdrawalsTrieRoot(this.withdrawals, new MerklePatriciaTrie({ common: this.vm.common })) } /** @@ -324,12 +335,7 @@ export class BlockBuilder { await this.processWithdrawals() const transactionsTrie = await this.transactionsTrie() - const withdrawalsRoot = this.withdrawals - ? await genWithdrawalsTrieRoot( - this.withdrawals, - new MerklePatriciaTrie({ common: this.vm.common }), - ) - : undefined + const withdrawalsRoot = await this.withdrawalsTrie() const receiptTrie = await this.receiptTrie() const logsBloom = this.logsBloom() const gasUsed = this.gasUsed From 73d9dca956e3e0fdb113cc77dd6581f71ac3334e Mon Sep 17 00:00:00 2001 From: harkamal Date: Fri, 4 Oct 2024 20:06:06 +0530 Subject: [PATCH 02/22] update and proagate ssz signature scheme as well as authroization list --- packages/block/src/from-beacon-payload.ts | 40 +- .../client/src/rpc/modules/engine/engine.ts | 635 +++++++++--------- .../src/rpc/modules/engine/validators.ts | 3 +- packages/tx/src/1559/constructors.ts | 8 +- packages/tx/src/1559/tx.ts | 4 +- packages/tx/src/2930/constructors.ts | 8 +- packages/tx/src/2930/tx.ts | 4 +- packages/tx/src/4844/constructors.ts | 8 +- packages/tx/src/4844/tx.ts | 4 +- packages/tx/src/legacy/constructors.ts | 8 +- packages/tx/src/legacy/tx.ts | 4 +- packages/tx/src/util.ts | 38 +- packages/util/src/ssz.ts | 113 ++-- 13 files changed, 479 insertions(+), 398 deletions(-) diff --git a/packages/block/src/from-beacon-payload.ts b/packages/block/src/from-beacon-payload.ts index db773fd7ab1..47aa261954c 100644 --- a/packages/block/src/from-beacon-payload.ts +++ b/packages/block/src/from-beacon-payload.ts @@ -45,6 +45,22 @@ export type BeaconAccessTupleV1 = { storage_keys: PrefixedHexString[] // Data 32 bytes MAX_ACCESS_LIST_STORAGE_KEYS array } +export type BeaconAuthorizationPayloadV1 = { + magic: PrefixedHexString + chain_id: PrefixedHexString + address: PrefixedHexString + nonce: PrefixedHexString +} + +export type BeaconExecutionSignatureV1 = { + secp256k1: PrefixedHexString | null // DATA 65 bytes or null +} + +export type BeaconAuthorizationV1 = { + payload: BeaconAuthorizationPayloadV1 + signature: BeaconExecutionSignatureV1 +} + export type BeaconTransactionPayloadV1 = { type: PrefixedHexString | null // Quantity, 1 byte chain_id: PrefixedHexString | null // Quantity 8 bytes @@ -57,16 +73,12 @@ export type BeaconTransactionPayloadV1 = { access_list: BeaconAccessTupleV1[] | null max_priority_fees_per_gas: BeaconFeesPerGasV1 | null blob_versioned_hashes: PrefixedHexString[] | null // DATA 32 bytes array -} - -export type BeaconTransactionSignatureV1 = { - from: PrefixedHexString | null // DATA 20 bytes - ecdsa_signature: PrefixedHexString | null // DATA 65 bytes or null + authorization_list: BeaconAuthorizationV1[] | null } type BeaconTransactionV1 = { payload: BeaconTransactionPayloadV1 - signature: BeaconTransactionSignatureV1 + signature: BeaconExecutionSignatureV1 } // Payload json that one gets using the beacon apis @@ -181,10 +193,22 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJSON): E }) ?? null, maxPriorityFeesPerGas: btxv1.payload.max_priority_fees_per_gas, blobVersionedHashes: btxv1.payload.blob_versioned_hashes, + authorizationList: + btxv1.payload.authorization_list?.map((bal: BeaconAuthorizationV1) => { + const { payload, signature } = bal + return { + payload: { + magic: payload.magic, + chainId: payload.chain_id, + address: payload.address, + nonce: payload.nonce, + }, + signature, + } + }) ?? null, }, signature: { - from: btxv1.signature.from, - ecdsaSignature: btxv1.signature.ecdsa_signature, + secp256k1: btxv1.signature.secp256k1, }, } as ssz.TransactionV1 }) diff --git a/packages/client/src/rpc/modules/engine/engine.ts b/packages/client/src/rpc/modules/engine/engine.ts index 05ff66d9cf4..0b40d422cbd 100644 --- a/packages/client/src/rpc/modules/engine/engine.ts +++ b/packages/client/src/rpc/modules/engine/engine.ts @@ -346,117 +346,63 @@ export class Engine { private async newPayload( params: [ExecutionPayload, (Bytes32[] | null)?, (Bytes32 | null)?], ): Promise { - try{ - const [payload, blobVersionedHashes, parentBeaconBlockRoot] = params - if (this.config.synchronized) { - this.connectionManager.newPayloadLog() - } - const { parentHash, blockHash } = payload - - // we can be strict and return with invalid if this block was previous invalidated in - // invalidBlocks cache, but to have a more robust behavior instead: - // - // we remove this block from invalidBlocks for it to be evaluated again against the - // new data/corrections the CL might be calling newPayload with - this.invalidBlocks.delete(blockHash.slice(2)) - - /** - * See if block can be assembled from payload - */ - // newpayloadv3 comes with parentBeaconBlockRoot out of the payload - const { block: headBlock, error } = await assembleBlock( - { - ...payload, - // ExecutionPayload only handles undefined - parentBeaconBlockRoot: parentBeaconBlockRoot ?? undefined, - }, - this.chain, - this.chainCache, - ) - if (headBlock === undefined || error !== undefined) { - let response = error - if (!response) { - const validationError = `Error assembling block from payload during initialization` - this.config.logger.debug(validationError) - const latestValidHash = await validHash( - hexToBytes(parentHash as PrefixedHexString), - this.chain, - this.chainCache, - ) - response = { status: Status.INVALID, latestValidHash, validationError } + try { + const [payload, blobVersionedHashes, parentBeaconBlockRoot] = params + if (this.config.synchronized) { + this.connectionManager.newPayloadLog() } - // skip marking the block invalid as this is more of a data issue from CL - return response - } + const { parentHash, blockHash } = payload - /** - * Validate blob versioned hashes in the context of EIP-4844 blob transactions - */ - if (headBlock.common.isActivatedEIP(4844)) { - let validationError: string | null = null - if (blobVersionedHashes === undefined || blobVersionedHashes === null) { - validationError = `Error verifying blobVersionedHashes: received none` - } else { - validationError = validate4844BlobVersionedHashes(headBlock, blobVersionedHashes) - } + // we can be strict and return with invalid if this block was previous invalidated in + // invalidBlocks cache, but to have a more robust behavior instead: + // + // we remove this block from invalidBlocks for it to be evaluated again against the + // new data/corrections the CL might be calling newPayload with + this.invalidBlocks.delete(blockHash.slice(2)) - // if there was a validation error return invalid - if (validationError !== null) { - this.config.logger.debug(validationError) - const latestValidHash = await validHash( - hexToBytes(parentHash as PrefixedHexString), - this.chain, - this.chainCache, - ) - const response = { status: Status.INVALID, latestValidHash, validationError } - // skip marking the block invalid as this is more of a data issue from CL - return response - } - } else if (blobVersionedHashes !== undefined && blobVersionedHashes !== null) { - const validationError = `Invalid blobVersionedHashes before EIP-4844 is activated` - const latestValidHash = await validHash( - hexToBytes(parentHash as PrefixedHexString), + /** + * See if block can be assembled from payload + */ + // newpayloadv3 comes with parentBeaconBlockRoot out of the payload + const { block: headBlock, error } = await assembleBlock( + { + ...payload, + // ExecutionPayload only handles undefined + parentBeaconBlockRoot: parentBeaconBlockRoot ?? undefined, + }, this.chain, this.chainCache, ) - const response = { status: Status.INVALID, latestValidHash, validationError } - // skip marking the block invalid as this is more of a data issue from CL - return response - } - - /** - * Stats and hardfork updates - */ - this.connectionManager.updatePayloadStats(headBlock) - const hardfork = headBlock.common.hardfork() - if (hardfork !== this.lastNewPayloadHF && this.lastNewPayloadHF !== '') { - this.config.logger.info( - `Hardfork change along new payload block number=${headBlock.header.number} hash=${short( - headBlock.hash(), - )} old=${this.lastNewPayloadHF} new=${hardfork}`, - ) - } - this.lastNewPayloadHF = hardfork - - try { - /** - * get the parent from beacon skeleton or from remoteBlocks cache or from the chain - * to run basic validations based on parent - */ - const parent = - (await this.skeleton.getBlockByHash(hexToBytes(parentHash as PrefixedHexString), true)) ?? - this.remoteBlocks.get(parentHash.slice(2)) ?? - (await this.chain.getBlock(hexToBytes(parentHash as PrefixedHexString))) + if (headBlock === undefined || error !== undefined) { + let response = error + if (!response) { + const validationError = `Error assembling block from payload during initialization` + this.config.logger.debug(validationError) + const latestValidHash = await validHash( + hexToBytes(parentHash as PrefixedHexString), + this.chain, + this.chainCache, + ) + response = { status: Status.INVALID, latestValidHash, validationError } + } + // skip marking the block invalid as this is more of a data issue from CL + return response + } /** - * validate 4844 transactions and fields as these validations generally happen on putBlocks - * when parent is confirmed to be in the chain. But we can do it here early + * Validate blob versioned hashes in the context of EIP-4844 blob transactions */ if (headBlock.common.isActivatedEIP(4844)) { - try { - headBlock.validateBlobTransactions(parent.header) - } catch (error: any) { - const validationError = `Invalid 4844 transactions: ${error}` + let validationError: string | null = null + if (blobVersionedHashes === undefined || blobVersionedHashes === null) { + validationError = `Error verifying blobVersionedHashes: received none` + } else { + validationError = validate4844BlobVersionedHashes(headBlock, blobVersionedHashes) + } + + // if there was a validation error return invalid + if (validationError !== null) { + this.config.logger.debug(validationError) const latestValidHash = await validHash( hexToBytes(parentHash as PrefixedHexString), this.chain, @@ -466,26 +412,149 @@ export class Engine { // skip marking the block invalid as this is more of a data issue from CL return response } + } else if (blobVersionedHashes !== undefined && blobVersionedHashes !== null) { + const validationError = `Invalid blobVersionedHashes before EIP-4844 is activated` + const latestValidHash = await validHash( + hexToBytes(parentHash as PrefixedHexString), + this.chain, + this.chainCache, + ) + const response = { status: Status.INVALID, latestValidHash, validationError } + // skip marking the block invalid as this is more of a data issue from CL + return response } /** - * Check for executed parent + * Stats and hardfork updates */ - const executedParentExists = - this.executedBlocks.get(parentHash.slice(2)) ?? - (await validExecutedChainBlock(hexToBytes(parentHash as PrefixedHexString), this.chain)) - // If the parent is not executed throw an error, it will be caught and return SYNCING or ACCEPTED. - if (!executedParentExists) { - throw new Error(`Parent block not yet executed number=${parent.header.number}`) + this.connectionManager.updatePayloadStats(headBlock) + const hardfork = headBlock.common.hardfork() + if (hardfork !== this.lastNewPayloadHF && this.lastNewPayloadHF !== '') { + this.config.logger.info( + `Hardfork change along new payload block number=${headBlock.header.number} hash=${short( + headBlock.hash(), + )} old=${this.lastNewPayloadHF} new=${hardfork}`, + ) + } + this.lastNewPayloadHF = hardfork + + try { + /** + * get the parent from beacon skeleton or from remoteBlocks cache or from the chain + * to run basic validations based on parent + */ + const parent = + (await this.skeleton.getBlockByHash(hexToBytes(parentHash as PrefixedHexString), true)) ?? + this.remoteBlocks.get(parentHash.slice(2)) ?? + (await this.chain.getBlock(hexToBytes(parentHash as PrefixedHexString))) + + /** + * validate 4844 transactions and fields as these validations generally happen on putBlocks + * when parent is confirmed to be in the chain. But we can do it here early + */ + if (headBlock.common.isActivatedEIP(4844)) { + try { + headBlock.validateBlobTransactions(parent.header) + } catch (error: any) { + const validationError = `Invalid 4844 transactions: ${error}` + const latestValidHash = await validHash( + hexToBytes(parentHash as PrefixedHexString), + this.chain, + this.chainCache, + ) + const response = { status: Status.INVALID, latestValidHash, validationError } + // skip marking the block invalid as this is more of a data issue from CL + return response + } + } + + /** + * Check for executed parent + */ + const executedParentExists = + this.executedBlocks.get(parentHash.slice(2)) ?? + (await validExecutedChainBlock(hexToBytes(parentHash as PrefixedHexString), this.chain)) + // If the parent is not executed throw an error, it will be caught and return SYNCING or ACCEPTED. + if (!executedParentExists) { + throw new Error(`Parent block not yet executed number=${parent.header.number}`) + } + } catch (error: any) { + // Stash the block for a potential forced forkchoice update to it later. + this.remoteBlocks.set(bytesToUnprefixedHex(headBlock.hash()), headBlock) + + const optimisticLookup = !(await this.skeleton.setHead(headBlock, false)) + /** + * Invalid skeleton PUT + */ + if ( + this.skeleton.fillStatus?.status === PutStatus.INVALID && + optimisticLookup && + headBlock.header.number >= this.skeleton.fillStatus.height + ) { + const latestValidHash = + this.chain.blocks.latest !== null + ? await validHash(this.chain.blocks.latest.hash(), this.chain, this.chainCache) + : bytesToHex(new Uint8Array(32)) + const response = { + status: Status.INVALID, + validationError: this.skeleton.fillStatus.validationError ?? '', + latestValidHash, + } + return response + } + + /** + * Invalid execution + */ + if ( + this.execution.chainStatus?.status === ExecStatus.INVALID && + optimisticLookup && + headBlock.header.number >= this.execution.chainStatus.height + ) { + // if the invalid block is canonical along the current chain return invalid + const invalidBlock = await this.skeleton.getBlockByHash( + this.execution.chainStatus.hash, + true, + ) + if (invalidBlock !== undefined) { + // hard luck: block along canonical chain is invalid + const latestValidHash = await validHash( + invalidBlock.header.parentHash, + this.chain, + this.chainCache, + ) + const validationError = `Block number=${invalidBlock.header.number} hash=${short( + invalidBlock.hash(), + )} root=${short(invalidBlock.header.stateRoot)} along the canonical chain is invalid` + + const response = { + status: Status.INVALID, + latestValidHash, + validationError, + } + return response + } + } + + const status = + // If the transitioned to beacon sync and this block can extend beacon chain then + optimisticLookup === true ? Status.SYNCING : Status.ACCEPTED + const response = { status, validationError: null, latestValidHash: null } + return response } - } catch (error: any) { - // Stash the block for a potential forced forkchoice update to it later. - this.remoteBlocks.set(bytesToUnprefixedHex(headBlock.hash()), headBlock) + // This optimistic lookup keeps skeleton updated even if for e.g. beacon sync might not have + // been initialized here but a batch of blocks new payloads arrive, most likely during sync + // We still can't switch to beacon sync here especially if the chain is pre merge and there + // is pow block which this client would like to mint and attempt proposing it + // + // Call skeleton.setHead without forcing head change to return if the block is reorged or not + // Do optimistic lookup if not reorged + // + // TODO: Determine if this optimistic lookup can be combined with the optimistic lookup above + // from within the catch clause (by skipping the code from the catch clause), code looks + // identical, same for executedBlockExists code below ?? const optimisticLookup = !(await this.skeleton.setHead(headBlock, false)) - /** - * Invalid skeleton PUT - */ if ( this.skeleton.fillStatus?.status === PutStatus.INVALID && optimisticLookup && @@ -503,9 +572,23 @@ export class Engine { return response } - /** - * Invalid execution - */ + this.remoteBlocks.set(bytesToUnprefixedHex(headBlock.hash()), headBlock) + + // we should check if the block exists executed in remoteBlocks or in chain as a check since stateroot + // exists in statemanager is not sufficient because an invalid crafted block with valid block hash with + // some pre-executed stateroot can be sent + const executedBlockExists = + this.executedBlocks.get(blockHash.slice(2)) ?? + (await validExecutedChainBlock(hexToBytes(blockHash as PrefixedHexString), this.chain)) + if (executedBlockExists) { + const response = { + status: Status.VALID, + latestValidHash: blockHash as PrefixedHexString, + validationError: null, + } + return response + } + if ( this.execution.chainStatus?.status === ExecStatus.INVALID && optimisticLookup && @@ -536,219 +619,143 @@ export class Engine { } } - const status = - // If the transitioned to beacon sync and this block can extend beacon chain then - optimisticLookup === true ? Status.SYNCING : Status.ACCEPTED - const response = { status, validationError: null, latestValidHash: null } - return response - } - - // This optimistic lookup keeps skeleton updated even if for e.g. beacon sync might not have - // been initialized here but a batch of blocks new payloads arrive, most likely during sync - // We still can't switch to beacon sync here especially if the chain is pre merge and there - // is pow block which this client would like to mint and attempt proposing it - // - // Call skeleton.setHead without forcing head change to return if the block is reorged or not - // Do optimistic lookup if not reorged - // - // TODO: Determine if this optimistic lookup can be combined with the optimistic lookup above - // from within the catch clause (by skipping the code from the catch clause), code looks - // identical, same for executedBlockExists code below ?? - const optimisticLookup = !(await this.skeleton.setHead(headBlock, false)) - if ( - this.skeleton.fillStatus?.status === PutStatus.INVALID && - optimisticLookup && - headBlock.header.number >= this.skeleton.fillStatus.height - ) { - const latestValidHash = - this.chain.blocks.latest !== null - ? await validHash(this.chain.blocks.latest.hash(), this.chain, this.chainCache) - : bytesToHex(new Uint8Array(32)) - const response = { - status: Status.INVALID, - validationError: this.skeleton.fillStatus.validationError ?? '', - latestValidHash, + /** + * 1. Determine non-executed blocks from beyond vmHead to headBlock + * 2. Iterate through non-executed blocks + * 3. Determine if block should be executed by some extra conditions + * 4. Execute block with this.execution.runWithoutSetHead() + */ + const vmHead = + this.chainCache.executedBlocks.get(parentHash.slice(2)) ?? + (await this.chain.blockchain.getIteratorHead()) + let blocks: Block[] + try { + // find parents till vmHead but limit lookups till engineParentLookupMaxDepth + blocks = await recursivelyFindParents( + vmHead.hash(), + headBlock.header.parentHash, + this.chain, + ) + } catch (error) { + const response = { status: Status.SYNCING, latestValidHash: null, validationError: null } + return response } - return response - } - this.remoteBlocks.set(bytesToUnprefixedHex(headBlock.hash()), headBlock) + blocks.push(headBlock) - // we should check if the block exists executed in remoteBlocks or in chain as a check since stateroot - // exists in statemanager is not sufficient because an invalid crafted block with valid block hash with - // some pre-executed stateroot can be sent - const executedBlockExists = - this.executedBlocks.get(blockHash.slice(2)) ?? - (await validExecutedChainBlock(hexToBytes(blockHash as PrefixedHexString), this.chain)) - if (executedBlockExists) { - const response = { - status: Status.VALID, - latestValidHash: blockHash as PrefixedHexString, - validationError: null, - } - return response - } - - if ( - this.execution.chainStatus?.status === ExecStatus.INVALID && - optimisticLookup && - headBlock.header.number >= this.execution.chainStatus.height - ) { - // if the invalid block is canonical along the current chain return invalid - const invalidBlock = await this.skeleton.getBlockByHash(this.execution.chainStatus.hash, true) - if (invalidBlock !== undefined) { - // hard luck: block along canonical chain is invalid + let lastBlock: Block + try { + for (const [i, block] of blocks.entries()) { + lastBlock = block + const bHash = block.hash() + + const isBlockExecuted = + (this.executedBlocks.get(bytesToUnprefixedHex(bHash)) ?? + (await validExecutedChainBlock(bHash, this.chain))) !== null + + if (!isBlockExecuted) { + // Only execute + // i) if number of blocks pending to be executed are within limit + // ii) Txs to execute in blocking call is within the supported limit + // else return SYNCING/ACCEPTED and let skeleton led chain execution catch up + const shouldExecuteBlock = + blocks.length - i <= this.chain.config.engineNewpayloadMaxExecute && + block.transactions.length <= this.chain.config.engineNewpayloadMaxTxsExecute + + const executed = + shouldExecuteBlock && + (await (async () => { + // just keeping its name different from the parentBlock to not confuse the context even + // though scope rules will not let it conflict with the parent of the new payload block + const blockParent = + i > 0 + ? blocks[i - 1] + : (this.chainCache.remoteBlocks.get( + bytesToHex(block.header.parentHash).slice(2), + ) ?? (await this.chain.getBlock(block.header.parentHash))) + const blockExecuted = await this.execution.runWithoutSetHead({ + block, + root: blockParent.header.stateRoot, + setHardfork: true, + parentBlock: blockParent, + }) + return blockExecuted + })()) + + // if can't be executed then return syncing/accepted + if (!executed) { + this.config.logger.debug( + `Skipping block(s) execution for headBlock=${headBlock.header.number} hash=${short( + headBlock.hash(), + )} : pendingBlocks=${blocks.length - i}(limit=${ + this.chain.config.engineNewpayloadMaxExecute + }) transactions=${block.transactions.length}(limit=${ + this.chain.config.engineNewpayloadMaxTxsExecute + }) executionBusy=${this.execution.running}`, + ) + // determined status to be returned depending on if block could extend chain or not + const status = optimisticLookup === true ? Status.SYNCING : Status.ACCEPTED + const response = { status, latestValidHash: null, validationError: null } + return response + } else { + this.executedBlocks.set(bytesToUnprefixedHex(block.hash()), block) + } + } + } + } catch (error) { const latestValidHash = await validHash( - invalidBlock.header.parentHash, + headBlock.header.parentHash, this.chain, this.chainCache, ) - const validationError = `Block number=${invalidBlock.header.number} hash=${short( - invalidBlock.hash(), - )} root=${short(invalidBlock.header.stateRoot)} along the canonical chain is invalid` - const response = { - status: Status.INVALID, - latestValidHash, - validationError, - } - return response - } - } - - /** - * 1. Determine non-executed blocks from beyond vmHead to headBlock - * 2. Iterate through non-executed blocks - * 3. Determine if block should be executed by some extra conditions - * 4. Execute block with this.execution.runWithoutSetHead() - */ - const vmHead = - this.chainCache.executedBlocks.get(parentHash.slice(2)) ?? - (await this.chain.blockchain.getIteratorHead()) - let blocks: Block[] - try { - // find parents till vmHead but limit lookups till engineParentLookupMaxDepth - blocks = await recursivelyFindParents(vmHead.hash(), headBlock.header.parentHash, this.chain) - } catch (error) { - const response = { status: Status.SYNCING, latestValidHash: null, validationError: null } - return response - } - - blocks.push(headBlock) - - let lastBlock: Block - try { - for (const [i, block] of blocks.entries()) { - lastBlock = block - const bHash = block.hash() - - const isBlockExecuted = - (this.executedBlocks.get(bytesToUnprefixedHex(bHash)) ?? - (await validExecutedChainBlock(bHash, this.chain))) !== null - - if (!isBlockExecuted) { - // Only execute - // i) if number of blocks pending to be executed are within limit - // ii) Txs to execute in blocking call is within the supported limit - // else return SYNCING/ACCEPTED and let skeleton led chain execution catch up - const shouldExecuteBlock = - blocks.length - i <= this.chain.config.engineNewpayloadMaxExecute && - block.transactions.length <= this.chain.config.engineNewpayloadMaxTxsExecute - - const executed = - shouldExecuteBlock && - (await (async () => { - // just keeping its name different from the parentBlock to not confuse the context even - // though scope rules will not let it conflict with the parent of the new payload block - const blockParent = - i > 0 - ? blocks[i - 1] - : (this.chainCache.remoteBlocks.get( - bytesToHex(block.header.parentHash).slice(2), - ) ?? (await this.chain.getBlock(block.header.parentHash))) - const blockExecuted = await this.execution.runWithoutSetHead({ - block, - root: blockParent.header.stateRoot, - setHardfork: true, - parentBlock: blockParent, - }) - return blockExecuted - })()) - - // if can't be executed then return syncing/accepted - if (!executed) { - this.config.logger.debug( - `Skipping block(s) execution for headBlock=${headBlock.header.number} hash=${short( - headBlock.hash(), - )} : pendingBlocks=${blocks.length - i}(limit=${ - this.chain.config.engineNewpayloadMaxExecute - }) transactions=${block.transactions.length}(limit=${ - this.chain.config.engineNewpayloadMaxTxsExecute - }) executionBusy=${this.execution.running}`, - ) - // determined status to be returned depending on if block could extend chain or not - const status = optimisticLookup === true ? Status.SYNCING : Status.ACCEPTED - const response = { status, latestValidHash: null, validationError: null } + const errorMsg = `${error}`.toLowerCase() + if (errorMsg.includes('block') && errorMsg.includes('not found')) { + if (blocks.length > 1) { + // this error can come if the block tries to load a previous block yet not in the chain via BLOCKHASH + // opcode. + // + // i) error coding of the evm errors should be a better way to handle this OR + // ii) figure out a way to pass let the evm access the above blocks which is what connects this + // chain to vmhead. to be handled in skeleton refactoring to blockchain class + + const response = { status: Status.SYNCING, latestValidHash, validationError: null } return response } else { - this.executedBlocks.set(bytesToUnprefixedHex(block.hash()), block) + throw { + code: INTERNAL_ERROR, + message: errorMsg, + } } } - } - } catch (error) { - const latestValidHash = await validHash( - headBlock.header.parentHash, - this.chain, - this.chainCache, - ) - const errorMsg = `${error}`.toLowerCase() - if (errorMsg.includes('block') && errorMsg.includes('not found')) { - if (blocks.length > 1) { - // this error can come if the block tries to load a previous block yet not in the chain via BLOCKHASH - // opcode. - // - // i) error coding of the evm errors should be a better way to handle this OR - // ii) figure out a way to pass let the evm access the above blocks which is what connects this - // chain to vmhead. to be handled in skeleton refactoring to blockchain class - - const response = { status: Status.SYNCING, latestValidHash, validationError: null } - return response - } else { - throw { - code: INTERNAL_ERROR, - message: errorMsg, - } - } - } + const validationError = `Error verifying block while running: ${errorMsg}` + this.config.logger.error(validationError) - const validationError = `Error verifying block while running: ${errorMsg}` - this.config.logger.error(validationError) + const response = { status: Status.INVALID, latestValidHash, validationError } + this.invalidBlocks.set(blockHash.slice(2), error as Error) + this.remoteBlocks.delete(blockHash.slice(2)) + try { + await this.chain.blockchain.delBlock(lastBlock!.hash()) + // eslint-disable-next-line no-empty + } catch {} + try { + await this.skeleton.deleteBlock(lastBlock!) + // eslint-disable-next-line no-empty + } catch {} + return response + } - const response = { status: Status.INVALID, latestValidHash, validationError } - this.invalidBlocks.set(blockHash.slice(2), error as Error) - this.remoteBlocks.delete(blockHash.slice(2)) - try { - await this.chain.blockchain.delBlock(lastBlock!.hash()) - // eslint-disable-next-line no-empty - } catch {} - try { - await this.skeleton.deleteBlock(lastBlock!) - // eslint-disable-next-line no-empty - } catch {} + const response = { + status: Status.VALID, + latestValidHash: bytesToHex(headBlock.hash()), + validationError: null, + } return response + } catch (e) { + console.log('newPayload', e) + throw e } - - const response = { - status: Status.VALID, - latestValidHash: bytesToHex(headBlock.hash()), - validationError: null, - } - return response - }catch(e){ - console.log("newPayload", e) - throw e; - } } /** @@ -1393,7 +1400,7 @@ export class Engine { ) return executionPayload } catch (error: any) { - console.log("getPayload", error) + console.log('getPayload', error) if (validEngineCodes.includes(error.code)) throw error throw { code: INTERNAL_ERROR, diff --git a/packages/client/src/rpc/modules/engine/validators.ts b/packages/client/src/rpc/modules/engine/validators.ts index dd9b05a5c13..f2789b8a94a 100644 --- a/packages/client/src/rpc/modules/engine/validators.ts +++ b/packages/client/src/rpc/modules/engine/validators.ts @@ -33,8 +33,7 @@ const transaction = validators.hexOrObject( blobVersionedHashes: validators.nullOptional(validators.array(validators.bytes32)), }), signature: validators.object({ - from: validators.nullOptional(validators.address), - ecdsaSignature: validators.nullOptional(validators.hex), + secp256k1: validators.nullOptional(validators.hex), }), }), ) diff --git a/packages/tx/src/1559/constructors.ts b/packages/tx/src/1559/constructors.ts index c6045b7830e..7663bb6855d 100644 --- a/packages/tx/src/1559/constructors.ts +++ b/packages/tx/src/1559/constructors.ts @@ -124,13 +124,13 @@ export function createFeeMarket1559TxFromSszTx( accessList, maxPriorityFeesPerGas: { regular: maxPriorityFeePerGas }, }, - signature: { ecdsaSignature }, + signature: { secp256k1 }, } = sszWrappedTx // TODO: bytes to bigint => bigint to unpadded bytes seem redundant and set for optimization - const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) - const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) - const v = bytesToBigInt(ecdsaSignature.slice(64)) + const r = bytesToBigInt(secp256k1.slice(0, 32)) + const s = bytesToBigInt(secp256k1.slice(32, 64)) + const v = bytesToBigInt(secp256k1.slice(64)) return create1559FeeMarketTxFromBytesArray( [ diff --git a/packages/tx/src/1559/tx.ts b/packages/tx/src/1559/tx.ts index 2941a97036f..fe71419b69c 100644 --- a/packages/tx/src/1559/tx.ts +++ b/packages/tx/src/1559/tx.ts @@ -183,12 +183,12 @@ export class FeeMarket1559Tx extends BaseTransaction ({ address, storageKeys })), maxPriorityFeesPerGas: { regular: this.maxPriorityFeePerGas, blob: null }, blobVersionedHashes: null, + authorizationList: null, } const yParity = this.v const signature = { - from: this.getSenderAddress().bytes, - ecdsaSignature: Uint8Array.from([ + secp256k1: Uint8Array.from([ ...setLengthLeft(bigIntToUnpaddedBytes(this.r), 32), ...setLengthLeft(bigIntToUnpaddedBytes(this.s), 32), ...setLengthLeft(bigIntToUnpaddedBytes(yParity), 1), diff --git a/packages/tx/src/2930/constructors.ts b/packages/tx/src/2930/constructors.ts index 8c7f7e2fc23..bb3d0682895 100644 --- a/packages/tx/src/2930/constructors.ts +++ b/packages/tx/src/2930/constructors.ts @@ -111,12 +111,12 @@ export function createAccessList2930TxFromSszTx( input: data, accessList, }, - signature: { ecdsaSignature }, + signature: { secp256k1 }, } = sszWrappedTx - const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) - const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) - const v = bytesToBigInt(ecdsaSignature.slice(64)) + const r = bytesToBigInt(secp256k1.slice(0, 32)) + const s = bytesToBigInt(secp256k1.slice(32, 64)) + const v = bytesToBigInt(secp256k1.slice(64)) return createAccessList2930TxFromBytesArray( [ diff --git a/packages/tx/src/2930/tx.ts b/packages/tx/src/2930/tx.ts index 4dfa7ebce9a..118fa6b41ed 100644 --- a/packages/tx/src/2930/tx.ts +++ b/packages/tx/src/2930/tx.ts @@ -165,13 +165,13 @@ export class AccessList2930Transaction extends BaseTransaction ({ address, storageKeys })), maxPriorityFeesPerGas: null, blobVersionedHashes: null, + authorizationList: null, } const yParity = this.v const signature = { - from: this.getSenderAddress().bytes, - ecdsaSignature: Uint8Array.from([ + secp256k1: Uint8Array.from([ ...setLengthLeft(bigIntToUnpaddedBytes(this.r), 32), ...setLengthLeft(bigIntToUnpaddedBytes(this.s), 32), ...setLengthLeft(bigIntToUnpaddedBytes(yParity), 1), diff --git a/packages/tx/src/4844/constructors.ts b/packages/tx/src/4844/constructors.ts index b3ba2bab738..063a3c06e52 100644 --- a/packages/tx/src/4844/constructors.ts +++ b/packages/tx/src/4844/constructors.ts @@ -356,12 +356,12 @@ export function createBlob4844TxFromSszTx( maxPriorityFeesPerGas: { regular: maxPriorityFeePerGas }, blobVersionedHashes, }, - signature: { ecdsaSignature }, + signature: { secp256k1 }, } = sszWrappedTx - const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) - const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) - const v = bytesToBigInt(ecdsaSignature.slice(64)) + const r = bytesToBigInt(secp256k1.slice(0, 32)) + const s = bytesToBigInt(secp256k1.slice(32, 64)) + const v = bytesToBigInt(secp256k1.slice(64)) return createBlob4844TxFromBytesArray( [ diff --git a/packages/tx/src/4844/tx.ts b/packages/tx/src/4844/tx.ts index 7b93b47e348..0e5b0646a9c 100644 --- a/packages/tx/src/4844/tx.ts +++ b/packages/tx/src/4844/tx.ts @@ -239,13 +239,13 @@ export class Blob4844Tx extends BaseTransaction { blob: this.maxPriorityFeePerGas, }, blobVersionedHashes: this.blobVersionedHashes.map((vh) => hexToBytes(vh)), + authorizationList: null, } const yParity = this.v const signature = { - from: this.getSenderAddress().bytes, - ecdsaSignature: Uint8Array.from([ + secp256k1: Uint8Array.from([ ...setLengthLeft(bigIntToUnpaddedBytes(this.r), 32), ...setLengthLeft(bigIntToUnpaddedBytes(this.s), 32), ...setLengthLeft(bigIntToUnpaddedBytes(yParity), 1), diff --git a/packages/tx/src/legacy/constructors.ts b/packages/tx/src/legacy/constructors.ts index b227dd58b83..30c77dbef85 100644 --- a/packages/tx/src/legacy/constructors.ts +++ b/packages/tx/src/legacy/constructors.ts @@ -87,12 +87,12 @@ export function createLegacyTxFromSszTx( value, input: data, }, - signature: { ecdsaSignature }, + signature: { secp256k1 }, } = sszWrappedTx as LegacyTransactionType - const r = bytesToBigInt(ecdsaSignature.slice(0, 32)) - const s = bytesToBigInt(ecdsaSignature.slice(32, 64)) - const yParity = bytesToBigInt(ecdsaSignature.slice(64)) + const r = bytesToBigInt(secp256k1.slice(0, 32)) + const s = bytesToBigInt(secp256k1.slice(32, 64)) + const yParity = bytesToBigInt(secp256k1.slice(64)) let v if (chainId !== null && chainId !== undefined) { diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index b9cc7cf0218..e7c5b7b1f1d 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -152,6 +152,7 @@ export class LegacyTx extends BaseTransaction { accessList: null, maxPriorityFeesPerGas: null, blobVersionedHashes: null, + authorizationList: null, } const yParity = calculateSigRecovery(this.v, chainId ?? undefined) @@ -160,8 +161,7 @@ export class LegacyTx extends BaseTransaction { } const signature = { - from: this.getSenderAddress().bytes, - ecdsaSignature: Uint8Array.from([ + secp256k1: Uint8Array.from([ ...setLengthLeft(bigIntToBytes(this.r), 32), ...setLengthLeft(bigIntToBytes(this.s), 32), ...setLengthLeft(bigIntToBytes(yParity), 1), diff --git a/packages/tx/src/util.ts b/packages/tx/src/util.ts index 5568e563423..474a9c70535 100644 --- a/packages/tx/src/util.ts +++ b/packages/tx/src/util.ts @@ -358,13 +358,22 @@ export function fromPayloadJson(payloadTx: ssz.TransactionV1): SSZTransaction { blob: getQuantityOrNull(payload.maxPriorityFeesPerGas.blob), } : null, - blobVersionedHashes: payload.blobVersionedHashes - ? payload.blobVersionedHashes.map((vh) => hexToBytes(vh)) - : null, + blobVersionedHashes: payload.blobVersionedHashes?.map((vh) => hexToBytes(vh)) ?? null, + authorizationList: + payload.authorizationList?.map((al) => ({ + payload: { + magic: getQuantityOrNull(al.payload.magic), + chainId: getQuantityOrNull(al.payload.chainId), + address: getDataOrNull(al.payload.address), + nonce: getQuantityOrNull(al.payload.nonce), + }, + signature: { + secp256k1: getDataOrNull(al.signature.secp256k1), + }, + })) ?? null, }, signature: { - from: getDataOrNull(signature.from), - ecdsaSignature: getDataOrNull(signature.ecdsaSignature), + secp256k1: getDataOrNull(signature.secp256k1), }, } } @@ -416,13 +425,22 @@ export function toPayloadJson(sszTx: SSZTransaction): ssz.TransactionV1 { blob: setQuantityOrNull(payload.maxPriorityFeesPerGas.blob), } : null, - blobVersionedHashes: payload.blobVersionedHashes - ? payload.blobVersionedHashes.map((vh) => bytesToHex(vh)) - : null, + blobVersionedHashes: payload.blobVersionedHashes?.map((vh) => bytesToHex(vh)) ?? null, + authorizationList: + payload.authorizationList?.map((al) => ({ + payload: { + magic: setQuantityOrNull(al.payload.magic), + chainId: setQuantityOrNull(al.payload.chainId), + address: setDataOrNull(al.payload.address), + nonce: setQuantityOrNull(al.payload.nonce), + }, + signature: { + secp256k1: setDataOrNull(al.signature.secp256k1), + }, + })) ?? null, }, signature: { - from: setDataOrNull(signature.from), - ecdsaSignature: setDataOrNull(signature.ecdsaSignature), + secp256k1: setDataOrNull(signature.secp256k1), }, } } diff --git a/packages/util/src/ssz.ts b/packages/util/src/ssz.ts index c19d2994a86..58e23c1e065 100644 --- a/packages/util/src/ssz.ts +++ b/packages/util/src/ssz.ts @@ -18,7 +18,6 @@ import type { ValueOf } from '@chainsafe/ssz' export const MAX_CALLDATA_SIZE = 16_777_216 export const MAX_ACCESS_LIST_STORAGE_KEYS = 524_288 export const MAX_ACCESS_LIST_SIZE = 524_288 -export const ECDSA_SIGNATURE_SIZE = 65 export const MAX_FEES_PER_GAS_FIELDS = 16 export const MAX_TRANSACTION_PAYLOAD_FIELDS = 32 @@ -38,6 +37,14 @@ export const ChainId = Uint64 export const TransactionType = Uint8 export const ExecutionAddress = Bytes20 +function getFullArray(prefixVec: boolean[], maxVecLength: number): BitArray { + const fullVec = [ + ...prefixVec, + ...Array.from({ length: maxVecLength - prefixVec.length }, () => false), + ] + return BitArray.fromBoolArray(fullVec) +} + export const FeesPerGas = new StableContainerType( { regular: new OptionalType(FeePerGas), @@ -60,6 +67,46 @@ export const TransactionTo = new OptionalType(ExecutionAddress) export const TransactionInput = new ByteListType(MAX_CALLDATA_SIZE) export const VersionedHashes = new ListCompositeType(Bytes32, MAX_BLOB_COMMITMENTS_PER_BLOCK) +export const SECP256K1_SIGNATURE_SIZE = 65 +export const Secp256k1Signature = new ByteVectorType(SECP256K1_SIGNATURE_SIZE) + +export const MAX_EXECUTION_SIGNATURE_FIELDS = 8 +export const ExecutionSignature = new StableContainerType( + { + secp256k1: new OptionalType(Secp256k1Signature), + }, + MAX_EXECUTION_SIGNATURE_FIELDS, + { typeName: 'ExecutionSignature', jsonCase: 'eth2' }, +) +export const Secp256k1ExecutionSignature = new ProfileType( + { secp256k1: Secp256k1Signature }, + getFullArray([true], MAX_EXECUTION_SIGNATURE_FIELDS), + { typeName: 'Secp256k1ExecutionSignature', jsonCase: 'eth2' }, +) + +export const MAX_AUTHORIZATION_PAYLOAD_FIELDS = 16 +export const AuthorizationPayload = new StableContainerType( + { + magic: new OptionalType(TransactionType), + chainId: new OptionalType(ChainId), + address: new OptionalType(ExecutionAddress), + nonce: new OptionalType(Uint64), + }, + MAX_AUTHORIZATION_PAYLOAD_FIELDS, + { typeName: 'AuthorizationPayload', jsonCase: 'eth2' }, +) + +export const Authorization = new ContainerType( + { + payload: AuthorizationPayload, + signature: ExecutionSignature, + }, + { typeName: 'Authorization', jsonCase: 'eth2' }, +) + +export const MAX_AUTHORIZATION_LIST_SIZE = 65_536 +export const AuthorizationList = new ListCompositeType(Authorization, MAX_AUTHORIZATION_LIST_SIZE) + export const TransactionPayload = new StableContainerType( { type: new OptionalType(TransactionType), @@ -73,37 +120,20 @@ export const TransactionPayload = new StableContainerType( accessList: new OptionalType(AccessList), maxPriorityFeesPerGas: new OptionalType(FeesPerGas), blobVersionedHashes: new OptionalType(VersionedHashes), + authorizationList: new OptionalType(AuthorizationList), }, MAX_TRANSACTION_PAYLOAD_FIELDS, { typeName: 'TransactionPayload', jsonCase: 'eth2' }, ) -export const EcdsaSignature = new ByteVectorType(ECDSA_SIGNATURE_SIZE) -export const TransactionSignature = new StableContainerType( - { - from: new OptionalType(ExecutionAddress), - ecdsaSignature: new OptionalType(EcdsaSignature), - }, - MAX_TRANSACTION_SIGNATURE_FIELDS, - { typeName: 'TransactionSignature', jsonCase: 'eth2' }, -) - export const Transaction = new ContainerType( { payload: TransactionPayload, - signature: TransactionSignature, + signature: ExecutionSignature, }, { typeName: 'Transaction', jsonCase: 'eth2' }, ) -function getFullArray(prefixVec: boolean[], maxVecLength: number): BitArray { - const fullVec = [ - ...prefixVec, - ...Array.from({ length: maxVecLength - prefixVec.length }, () => false), - ] - return BitArray.fromBoolArray(fullVec) -} - export const BasicFeesPerGas = new ProfileType( { regular: FeePerGas }, getFullArray([true], MAX_FEES_PER_GAS_FIELDS), @@ -119,15 +149,6 @@ export const BlobFeesPerGas = new ProfileType( { typeName: 'BlobFeesPerGas', jsonCase: 'eth2' }, ) -export const EcdsaTransactionSignature = new ProfileType( - { - from: ExecutionAddress, - ecdsaSignature: EcdsaSignature, - }, - getFullArray([true, true], MAX_TRANSACTION_SIGNATURE_FIELDS), - { typeName: 'EcdsaTransactionSignature', jsonCase: 'eth2' }, -) - export const ReplayableTransactionPayload = new ProfileType( { type: TransactionType, @@ -145,7 +166,7 @@ export const ReplayableTransactionPayload = new ProfileType( export const ReplayableTransaction = new ContainerType( { payload: ReplayableTransactionPayload, - signature: EcdsaTransactionSignature, + signature: Secp256k1ExecutionSignature, }, { typeName: 'ReplayableTransaction', jsonCase: 'eth2' }, ) @@ -168,7 +189,7 @@ export const LegacyTransactionPayload = new ProfileType( export const LegacyTransaction = new ContainerType( { payload: LegacyTransactionPayload, - signature: EcdsaTransactionSignature, + signature: Secp256k1ExecutionSignature, }, { typeName: 'LegacyTransaction', jsonCase: 'eth2' }, ) @@ -192,7 +213,7 @@ export const Eip2930TransactionPayload = new ProfileType( export const Eip2930Transaction = new ContainerType( { payload: Eip2930TransactionPayload, - signature: EcdsaTransactionSignature, + signature: Secp256k1ExecutionSignature, }, { typeName: 'Eip2930Transaction', jsonCase: 'eth2' }, ) @@ -220,7 +241,7 @@ export const Eip1559TransactionPayload = new ProfileType( export const Eip1559Transaction = new ContainerType( { payload: Eip1559TransactionPayload, - signature: EcdsaTransactionSignature, + signature: Secp256k1ExecutionSignature, }, { typeName: 'Eip1559Transaction', jsonCase: 'eth2' }, ) @@ -249,7 +270,7 @@ export const Eip4844TransactionPayload = new ProfileType( export const Eip4844Transaction = new ContainerType( { payload: Eip4844TransactionPayload, - signature: EcdsaTransactionSignature, + signature: Secp256k1ExecutionSignature, }, { typeName: 'Eip4844Transaction', jsonCase: 'eth2' }, ) @@ -336,6 +357,22 @@ export type AccessTupleV1 = { storageKeys: PrefixedHexString[] // Data 32 bytes MAX_ACCESS_LIST_STORAGE_KEYS array } +export type ExecutionSignatureV1 = { + secp256k1: PrefixedHexString | null // DATA 65 bytes +} + +export type AuthorizationPayloadV1 = { + magic: PrefixedHexString | null // Quantity 1 byte, + chainId: PrefixedHexString | null // Quantity 8 bytes + address: PrefixedHexString | null // DATA 20 bytes + nonce: PrefixedHexString | null //Quantity 8 bytes +} + +export type AuthorizationV1 = { + payload: AuthorizationPayloadV1 + signature: ExecutionSignatureV1 +} + export type TransactionPayloadV1 = { type: PrefixedHexString | null // Quantity, 1 byte chainId: PrefixedHexString | null // Quantity 8 bytes @@ -348,16 +385,12 @@ export type TransactionPayloadV1 = { accessList: AccessTupleV1[] | null maxPriorityFeesPerGas: FeesPerGasV1 | null blobVersionedHashes: PrefixedHexString[] | null // DATA 32 bytes array -} - -export type TransactionSignatureV1 = { - from: PrefixedHexString | null // DATA 20 bytes - ecdsaSignature: PrefixedHexString | null // DATA 65 bytes or null + authorizationList: AuthorizationV1[] | null } export type TransactionV1 = { payload: TransactionPayloadV1 - signature: TransactionSignatureV1 + signature: ExecutionSignatureV1 } export const MAX_BLOCKHEADER_FIELDS = 64 From 8fc90ca159df38fee36817674cbe3d0f853dd15b Mon Sep 17 00:00:00 2001 From: harkamal Date: Fri, 4 Oct 2024 20:18:19 +0530 Subject: [PATCH 03/22] check and remove an invalid failing spec test --- packages/block/test/eip4895block.spec.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/block/test/eip4895block.spec.ts b/packages/block/test/eip4895block.spec.ts index 98d2baed05a..5ec4c0f90ec 100644 --- a/packages/block/test/eip4895block.spec.ts +++ b/packages/block/test/eip4895block.spec.ts @@ -5,7 +5,6 @@ import { KECCAK256_RLP, createWithdrawalFromBytesArray, hexToBytes, - randomBytes, } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' @@ -230,14 +229,4 @@ describe('EIP4895 tests', () => { 'should provide withdrawals array when 4895 is active', ) }) - - it('should return early when withdrawals root equals KECCAK256_RLP', async () => { - const block = createBlock({}, { common }) - // Set invalid withdrawalsRoot in cache - block['cache'].withdrawalsTrieRoot = randomBytes(32) - assert.ok( - await block.withdrawalsTrieIsValid(), - 'correctly executed code path where withdrawals length is 0', - ) - }) }) From a624dae685a503dd9b7e461fbe2bb045f523e79b Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 5 Oct 2024 15:08:46 +0530 Subject: [PATCH 04/22] impl ssz receipts and dev modify the receipts rooting work --- package-lock.json | 1 + packages/util/src/ssz.ts | 34 +++++++++++++++++++++++ packages/vm/package.json | 1 + packages/vm/src/buildBlock.ts | 33 +++++++++++++++-------- packages/vm/src/runBlock.ts | 51 +++++++++++++++++++++++++++++------ packages/vm/src/runTx.ts | 2 ++ packages/vm/src/types.ts | 5 ++++ 7 files changed, 108 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8d53825c74..a90a0e01d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17831,6 +17831,7 @@ "version": "8.1.0", "license": "MPL-2.0", "dependencies": { + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/block": "^5.3.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/evm": "^3.1.0", diff --git a/packages/util/src/ssz.ts b/packages/util/src/ssz.ts index 58e23c1e065..2df20529991 100644 --- a/packages/util/src/ssz.ts +++ b/packages/util/src/ssz.ts @@ -1,6 +1,7 @@ import { Tree, hasher } from '@chainsafe/persistent-merkle-tree' import { BitArray, + BooleanType, ByteListType, ByteVectorType, ContainerType, @@ -24,6 +25,8 @@ export const MAX_TRANSACTION_PAYLOAD_FIELDS = 32 export const MAX_TRANSACTION_SIGNATURE_FIELDS = 16 export const MAX_BLOB_COMMITMENTS_PER_BLOCK = 4096 +export const Boolean = new BooleanType() + export const Uint8 = new UintBigintType(1) export const Uint64 = new UintBigintType(8) export const Uint256 = new UintBigintType(32) @@ -393,6 +396,37 @@ export type TransactionV1 = { signature: ExecutionSignatureV1 } +const MAX_TOPICS_PER_LOG = 4 +const MAX_LOG_DATA_SIZE = 16_777_216 +const MAX_RECEIPT_FIELDS = 32 +const MAX_LOGS_PER_RECEIPT = 2_097_152 + +export const LogTopics = new ListCompositeType(Bytes32, MAX_TOPICS_PER_LOG) +export const Log = new ContainerType( + { + address: ExecutionAddress, + topics: LogTopics, + data: new ByteListType(MAX_LOG_DATA_SIZE), + }, + { typeName: 'Log', jsonCase: 'eth2' }, +) +export const LogList = new ListCompositeType(Log, MAX_LOGS_PER_RECEIPT) +export const AuthoritiesList = new ListCompositeType(ExecutionAddress, MAX_AUTHORIZATION_LIST_SIZE) + +export const Receipt = new StableContainerType( + { + root: new OptionalType(Bytes32), + gasUsed: new OptionalType(Uint64), + contractAddress: new OptionalType(ExecutionAddress), + logs: new OptionalType(LogList), + status: new OptionalType(Boolean), + authorities: new OptionalType(AuthoritiesList), + }, + MAX_RECEIPT_FIELDS, + { typeName: 'Receipt', jsonCase: 'eth2' }, +) +export const Receipts = new ListCompositeType(Receipt, MAX_TRANSACTIONS_PER_PAYLOAD) + export const MAX_BLOCKHEADER_FIELDS = 64 const MAX_EXTRA_DATA_BYTES = 32 diff --git a/packages/vm/package.json b/packages/vm/package.json index 32934c073e1..e2d67ede580 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -64,6 +64,7 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { + "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", "@ethereumjs/block": "^5.3.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/evm": "^3.1.0", diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index 75f0a3b578b..512f22236b6 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -17,10 +17,10 @@ import { BIGINT_1, BIGINT_2, GWEI_TO_WEI, - KECCAK256_RLP, TypeOutput, createWithdrawal, createZeroAddress, + ssz, toBytes, toType, } from '@ethereumjs/util' @@ -32,11 +32,13 @@ import { accumulateParentBlockHash, calculateMinerReward, encodeReceipt, + encodeSszReceipt, rewardAccount, } from './runBlock.js' import { runTx } from './index.js' +import type { SSZReceiptType } from './runBlock.js' import type { BuildBlockOpts, BuilderOpts, RunTxResult, SealBlockOpts } from './types.js' import type { VM } from './vm.js' import type { Block, HeaderData } from '@ethereumjs/block' @@ -147,7 +149,10 @@ export class BlockBuilder { public async transactionsTrie() { return this.vm.common.isActivatedEIP(6493) ? genTransactionsSszRoot(this.transactions) - : genTransactionsTrieRoot(this.transactions, new MerklePatriciaTrie({ common: this.vm.common })) + : genTransactionsTrieRoot( + this.transactions, + new MerklePatriciaTrie({ common: this.vm.common }), + ) } public async withdrawalsTrie() { @@ -176,16 +181,22 @@ export class BlockBuilder { * Calculates and returns the receiptTrie for the block. */ public async receiptTrie() { - if (this.transactionResults.length === 0) { - return KECCAK256_RLP - } - const receiptTrie = new MerklePatriciaTrie({ common: this.vm.common }) - for (const [i, txResult] of this.transactionResults.entries()) { - const tx = this.transactions[i] - const encodedReceipt = encodeReceipt(txResult.receipt, tx.type) - await receiptTrie.put(RLP.encode(i), encodedReceipt) + if (this.vm.common.isActivatedEIP(6493)) { + const sszReceipts: SSZReceiptType[] = [] + for (const [i, txResult] of this.transactionResults.entries()) { + const tx = this.transactions[i] + sszReceipts.push(encodeSszReceipt(txResult.receipt, tx.type)) + } + return ssz.Receipts.hashTreeRoot(sszReceipts) + } else { + const receiptTrie = new MerklePatriciaTrie({ common: this.vm.common }) + for (const [i, txResult] of this.transactionResults.entries()) { + const tx = this.transactions[i] + const encodedReceipt = encodeReceipt(txResult.receipt, tx.type) + await receiptTrie.put(RLP.encode(i), encodedReceipt) + } + return receiptTrie.root() } - return receiptTrie.root() } /** diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 41589f1d8c4..fd6d9c1652e 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -23,6 +23,7 @@ import { intToBytes, setLengthLeft, short, + ssz, unprefixedHexToBytes, } from '@ethereumjs/util' import debugDefault from 'debug' @@ -44,11 +45,14 @@ import type { TxReceipt, } from './types.js' import type { VM } from './vm.js' +import type { ValueOf } from '@chainsafe/ssz' import type { Block } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' import type { EVM, EVMInterface } from '@ethereumjs/evm' import type { CLRequest, CLRequestType, PrefixedHexString } from '@ethereumjs/util' +export type SSZReceiptType = ValueOf + const debug = debugDefault('vm:block') const parentBeaconBlockRootAddress = createAddressFromString( @@ -583,11 +587,6 @@ async function applyTransactions(vm: VM, block: Block, opts: RunBlockOpts) { // the total amount of gas used processing these transactions let gasUsed = BIGINT_0 - let receiptTrie: MerklePatriciaTrie | undefined = undefined - if (block.transactions.length !== 0) { - receiptTrie = new MerklePatriciaTrie({ common: vm.common }) - } - const receipts: TxReceipt[] = [] const txResults: RunTxResult[] = [] @@ -637,8 +636,6 @@ async function applyTransactions(vm: VM, block: Block, opts: RunBlockOpts) { // Add receipt to trie to later calculate receipt root receipts.push(txRes.receipt) - const encodedReceipt = encodeReceipt(txRes.receipt, tx.type) - await receiptTrie!.put(RLP.encode(txIdx), encodedReceipt) } if (enableProfiler) { @@ -646,7 +643,23 @@ async function applyTransactions(vm: VM, block: Block, opts: RunBlockOpts) { console.timeEnd(processTxsLabel) } - const receiptsRoot = receiptTrie !== undefined ? receiptTrie.root() : KECCAK256_RLP + let receiptsRoot + if (vm.common.isActivatedEIP(6493)) { + const sszReceipts: SSZReceiptType[] = [] + for (const [i, txReceipt] of receipts.entries()) { + const tx = block.transactions[i] + sszReceipts.push(encodeSszReceipt(txReceipt, tx.type)) + } + receiptsRoot = ssz.Receipts.hashTreeRoot(sszReceipts) + } else { + const receiptTrie = new MerklePatriciaTrie({ common: vm.common }) + for (const [i, txReceipt] of receipts.entries()) { + const tx = block.transactions[i] + const encodedReceipt = encodeReceipt(txReceipt, tx.type) + await receiptTrie!.put(RLP.encode(i), encodedReceipt) + } + receiptsRoot = receiptTrie.root() + } return { bloom, @@ -761,6 +774,28 @@ export function encodeReceipt(receipt: TxReceipt, txType: TransactionType) { return concatBytes(intToBytes(txType), encoded) } +export function encodeSszReceipt(receipt: TxReceipt, _txType: TransactionType) { + const sszRaw: SSZReceiptType = { + root: (receipt as PreByzantiumTxReceipt).stateRoot ?? null, + gasUsed: receipt.cumulativeBlockGasUsed, + contractAddress: receipt.contractAddress?.bytes ?? null, + logs: receipt.logs.map((log) => ({ + address: log[0], + topics: log[1], + data: log[2], + })), + status: + (receipt as PostByzantiumTxReceipt).status !== undefined + ? (receipt as PostByzantiumTxReceipt).status === 0 + ? false + : true + : null, + authorities: receipt.authorities?.map((auth) => auth.bytes) ?? null, + } + + return sszRaw +} + /** * Apply the DAO fork changes to the VM */ diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index 1036c6e30d3..f7329bc077a 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -808,6 +808,8 @@ export async function generateTxReceipt( cumulativeBlockGasUsed: cumulativeGasUsed, bitvector: txResult.bloom.bitvector, logs: txResult.execResult.logs ?? [], + contractAddress: txResult.createdAddress, + authorities: txResult.authorities, } let receipt diff --git a/packages/vm/src/types.ts b/packages/vm/src/types.ts index 0e3d7e49cd4..60bf6cbcdb1 100644 --- a/packages/vm/src/types.ts +++ b/packages/vm/src/types.ts @@ -10,6 +10,7 @@ import type { } from '@ethereumjs/evm' import type { AccessList, TypedTransaction } from '@ethereumjs/tx' import type { + Address, BigIntLike, CLRequest, CLRequestType, @@ -34,6 +35,8 @@ export interface BaseTxReceipt { * Logs emitted */ logs: Log[] + contractAddress?: Address + authorities?: Address[] } /** @@ -488,6 +491,8 @@ export interface RunTxResult extends EVMResult { * This is the blob gas units times the fee per blob gas for 4844 transactions */ blobGasUsed?: bigint + + authorities?: Address[] } export interface AfterTxEvent extends RunTxResult { From e9c6f58f6cd60689cfa4acbfc1e23f32ead3f244 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 5 Oct 2024 15:29:35 +0530 Subject: [PATCH 05/22] debug and fix the client 6493 end to end spec test --- packages/client/test/rpc/engine/newPayloadEip6493.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index b8b7047244c..3c94d656d68 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -60,9 +60,10 @@ describe(`${method}: call with executionPayloadV4`, () => { depositRequests: [], withdrawalRequests: [], consolidationRequests: [], + receiptsRoot: "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1", parentHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', stateRoot: '0xbde9840c609ffa39cae0a2c9e354ac673920fcc2a5e6faeef5b78817c7fba7dd', - blockHash: '0x5c0486debe1ed1cb21ed9d93e34d0c2908b12b45d75090e59198b7fd71e26305', + blockHash: '0x5e9dcd3f3e55e9dde218cad2958ef3f0f1c263a85d923d1e1d3821f96510e1dc', } let res From e8ff671532d7009409b901aa4499b4e0260ef416 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 5 Oct 2024 20:47:20 +0530 Subject: [PATCH 06/22] accumulate logs into an ivc contract for advance proofing capabilities --- packages/util/src/ssz.ts | 9 ++++++++ packages/vm/src/params.ts | 5 +++++ packages/vm/src/runTx.ts | 47 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/packages/util/src/ssz.ts b/packages/util/src/ssz.ts index 2df20529991..8b75af6bc20 100644 --- a/packages/util/src/ssz.ts +++ b/packages/util/src/ssz.ts @@ -452,3 +452,12 @@ export const BlockHeader = new StableContainerType( MAX_BLOCKHEADER_FIELDS, { typeName: 'BlockHeader', jsonCase: 'eth2' }, ) + +export const IVCEntry = new ContainerType( + { + prevTopicRoot: Bytes32, + number: Uint64, + logRoot: Bytes32, + }, + { typeName: 'IVCEntry', jsonCase: 'eth2' }, +) diff --git a/packages/vm/src/params.ts b/packages/vm/src/params.ts index 1d3a576b2c8..1120a16a6f8 100644 --- a/packages/vm/src/params.ts +++ b/packages/vm/src/params.ts @@ -89,4 +89,9 @@ export const paramsVM: ParamsDict = { systemAddress: '0xfffffffffffffffffffffffffffffffffffffffe', // The system address to perform operations on the consolidation requests predeploy address consolidationRequestPredeployAddress: '0x00b42dbF2194e931E80326D950320f7d9Dbeac02', // Address of the consolidations contract }, + 6493: { + systemAddress: '0xfffffffffffffffffffffffffffffffffffffffe', // The system address to perform operations on the consolidation requests predeploy address + // dummu address right now as actual will be determined with the deployment of ivc contract + ivcPredeployAddress: '0x' + '6493'.repeat(10), + }, } diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index f7329bc077a..13d92859c8b 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -8,6 +8,7 @@ import { BIGINT_0, BIGINT_1, KECCAK256_NULL, + bigIntToAddressBytes, bytesToBigInt, bytesToHex, bytesToUnprefixedHex, @@ -17,10 +18,13 @@ import { hexToBytes, publicToAddress, short, + ssz, } from '@ethereumjs/util' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' +import { setLengthLeft } from '../../util/src/bytes.js' + import { Bloom } from './bloom/index.js' import { emitEVMProfile } from './emitEVMProfile.js' @@ -37,7 +41,7 @@ import type { import type { VM } from './vm.js' import type { Block } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' -import type { EVM } from '@ethereumjs/evm' +import type { EVM, Log } from '@ethereumjs/evm' import type { AccessList, AccessList2930Transaction, @@ -741,6 +745,10 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { blobGasPrice, ) + if (vm.common.isActivatedEIP(6493)) { + await accumulateIVCLogs(vm, block?.header.number ?? DEFAULT_HEADER.number, results.receipt.logs) + } + if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(receiptsLabel) @@ -766,6 +774,43 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { return results } +async function accumulateIVCLogs(vm: VM, number: bigint, logs: Log[]) { + const ivcContractAddress = new Address( + bigIntToAddressBytes(vm.common.param('withdrawalRequestPredeployAddress')), + ) + + if ((await vm.stateManager.getAccount(ivcContractAddress)) === undefined) { + // store with nonce of 1 to prevent 158 cleanup + const ivcContract = new Account() + ivcContract.nonce = BIGINT_1 + await vm.stateManager.putAccount(ivcContractAddress, ivcContract) + } + + for (const log of logs) { + const sszLog = { + address: log[0], + topics: log[1], + data: log[2], + } + + const logRoot = ssz.Log.hashTreeRoot(sszLog) + for (const topic of sszLog.topics) { + // should be 32 bytes but 0 bytes in case value doesn't exist so just left pad + const prevTopicRoot = setLengthLeft( + await vm.stateManager.getStorage(ivcContractAddress, topic), + 32, + ) + const newTopicRoot = ssz.IVCEntry.hashTreeRoot({ + prevTopicRoot, + number, + logRoot, + }) + + await vm.stateManager.putStorage(ivcContractAddress, topic, newTopicRoot) + } + } +} + /** * @method txLogsBloom * @private From 6e53fce3e9a20040e8b6df9ab5e5a2e06ed80354 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 5 Oct 2024 21:43:02 +0530 Subject: [PATCH 07/22] add ivc spec test in end to end client spec and debug and fix it --- .../test/rpc/engine/newPayloadEip6493.spec.ts | 17 +++++++++++++++-- packages/vm/src/runTx.ts | 6 ++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index 3c94d656d68..0e682aa3c39 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -1,5 +1,5 @@ import { createTx } from '@ethereumjs/tx' -import { bigIntToHex, hexToBytes } from '@ethereumjs/util' +import { bigIntToAddressBytes, bigIntToHex, bytesToHex, hexToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' import { beaconData } from '../../testdata/blocks/beacon.js' @@ -60,7 +60,7 @@ describe(`${method}: call with executionPayloadV4`, () => { depositRequests: [], withdrawalRequests: [], consolidationRequests: [], - receiptsRoot: "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1", + receiptsRoot: '0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1', parentHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', stateRoot: '0xbde9840c609ffa39cae0a2c9e354ac673920fcc2a5e6faeef5b78817c7fba7dd', blockHash: '0x5e9dcd3f3e55e9dde218cad2958ef3f0f1c263a85d923d1e1d3821f96510e1dc', @@ -137,6 +137,19 @@ describe(`${method}: call with executionPayloadV4`, () => { null, ]) assert.equal(res.result.payloadStatus.status, 'VALID') + + const ivcContractHex = bytesToHex(bigIntToAddressBytes(common.param('ivcPredeployAddress'))) + + res = await rpc.request('eth_getStorageAt', [ + ivcContractHex, + '0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5', + 'latest', + ]) + assert.equal( + res.result, + '0x020c33960f53470c2e7f78f67cdadf28800eb3d6b1c4e434dc9be7681baba37a', + 'ivc root at updated topic should match', + ) }) }) diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index 13d92859c8b..b66ab1e3e5a 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -17,14 +17,13 @@ import { equalsBytes, hexToBytes, publicToAddress, + setLengthLeft, short, ssz, } from '@ethereumjs/util' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { setLengthLeft } from '../../util/src/bytes.js' - import { Bloom } from './bloom/index.js' import { emitEVMProfile } from './emitEVMProfile.js' @@ -776,7 +775,7 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { async function accumulateIVCLogs(vm: VM, number: bigint, logs: Log[]) { const ivcContractAddress = new Address( - bigIntToAddressBytes(vm.common.param('withdrawalRequestPredeployAddress')), + bigIntToAddressBytes(vm.common.param('ivcPredeployAddress')), ) if ((await vm.stateManager.getAccount(ivcContractAddress)) === undefined) { @@ -805,7 +804,6 @@ async function accumulateIVCLogs(vm: VM, number: bigint, logs: Log[]) { number, logRoot, }) - await vm.stateManager.putStorage(ivcContractAddress, topic, newTopicRoot) } } From 1292329a65c348b264d581d37974dfa0ff0b6c69 Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 9 Oct 2024 17:30:04 +0530 Subject: [PATCH 08/22] modify the ivc accumulator code for smartcontract comaptible compute and add log proofing capabilities by multiple filters --- packages/vm/src/runTx.ts | 42 +++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index b66ab1e3e5a..eb1cec8a454 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -23,6 +23,7 @@ import { } from '@ethereumjs/util' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' +import { sha256 } from 'ethereum-cryptography/sha256.js' import { Bloom } from './bloom/index.js' import { emitEVMProfile } from './emitEVMProfile.js' @@ -774,10 +775,22 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { } async function accumulateIVCLogs(vm: VM, number: bigint, logs: Log[]) { + // keep all declarations here for ease of movement and diff + const LOG_ADDRESS_STORAGE_SLOT = setLengthLeft(new Uint8Array([0]), 32) + const LOG_TOPICS_STORAGE_SLOT = setLengthLeft(new Uint8Array([1]), 32) + const LOG_ADDRESS_TOPICS_STORAGE_SLOT = setLengthLeft(new Uint8Array([2]), 32) + + const commonSHA256 = vm.common.customCrypto.sha256 ?? sha256 const ivcContractAddress = new Address( bigIntToAddressBytes(vm.common.param('ivcPredeployAddress')), ) + async function accumulateLog(key: Uint8Array, logRoot: Uint8Array) { + const prevRoot = setLengthLeft(await vm.stateManager.getStorage(ivcContractAddress, key), 32) + const newRoot = commonSHA256(concatBytes(logRoot, prevRoot)) + await vm.stateManager.putStorage(ivcContractAddress, key, newRoot) + } + if ((await vm.stateManager.getAccount(ivcContractAddress)) === undefined) { // store with nonce of 1 to prevent 158 cleanup const ivcContract = new Account() @@ -791,20 +804,27 @@ async function accumulateIVCLogs(vm: VM, number: bigint, logs: Log[]) { topics: log[1], data: log[2], } - const logRoot = ssz.Log.hashTreeRoot(sszLog) + + // Allow eth_getLogs proof via `address` filter + // abi.encode(log.address, LOG_ADDRESS_STORAGE_SLOT) + const paddedAddress = setLengthLeft(sszLog.address, 32) + const addressKey = keccak256(concatBytes(paddedAddress, LOG_ADDRESS_STORAGE_SLOT)) + await accumulateLog(addressKey, logRoot) + for (const topic of sszLog.topics) { - // should be 32 bytes but 0 bytes in case value doesn't exist so just left pad - const prevTopicRoot = setLengthLeft( - await vm.stateManager.getStorage(ivcContractAddress, topic), - 32, + // Allow eth_getLogs proof via `topics` filter + // abi.encode(topic, LOG_TOPICS_STORAGE_SLOT) + const topicKey = keccak256(concatBytes(topic, LOG_TOPICS_STORAGE_SLOT)) + await accumulateLog(topicKey, logRoot) + + // Allow eth_getLogs proof via combined `address` + `topics` filter + // abi.encode(log.address, topic) + const addressAndTopic = keccak256(concatBytes(paddedAddress, topic)) + const addressAndTopicKey = keccak256( + concatBytes(addressAndTopic, LOG_ADDRESS_TOPICS_STORAGE_SLOT), ) - const newTopicRoot = ssz.IVCEntry.hashTreeRoot({ - prevTopicRoot, - number, - logRoot, - }) - await vm.stateManager.putStorage(ivcContractAddress, topic, newTopicRoot) + await accumulateLog(addressAndTopicKey, logRoot) } } } From a2d598fc800c06adb4b5552a5a24dc56e2622bbc Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 9 Oct 2024 19:20:11 +0530 Subject: [PATCH 09/22] move the log accumulation to post executions --- .../test/rpc/engine/newPayloadEip6493.spec.ts | 4 +- packages/vm/src/buildBlock.ts | 8 +++ packages/vm/src/runBlock.ts | 65 ++++++++++++++++++- packages/vm/src/runTx.ts | 65 +------------------ 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index 0e682aa3c39..886f2322a01 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -142,12 +142,12 @@ describe(`${method}: call with executionPayloadV4`, () => { res = await rpc.request('eth_getStorageAt', [ ivcContractHex, - '0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5', + '0x4026bcffe6920ff0e02a91018a719f2080a2463f25b23d34d6ed73aadae3264a', 'latest', ]) assert.equal( res.result, - '0x020c33960f53470c2e7f78f67cdadf28800eb3d6b1c4e434dc9be7681baba37a', + '0x88cce54f379f5607098522664e399bf4fee6f3e90127f8fc88f760fd4529211b', 'ivc root at updated topic should match', ) }) diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index 512f22236b6..8a375904a80 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -28,6 +28,7 @@ import { import { Bloom } from './bloom/index.js' import { accumulateRequests } from './requests.js' import { + accumulateIVCLogs, accumulateParentBeaconBlockRoot, accumulateParentBlockHash, calculateMinerReward, @@ -337,6 +338,13 @@ export class BlockBuilder { */ async build(sealOpts?: SealBlockOpts) { this.checkStatus() + + if (this.vm.common.isActivatedEIP(6493)) { + for (const txReceipt of this.transactionReceipts) { + await accumulateIVCLogs(this.vm, txReceipt.logs) + } + } + const blockOpts = this.blockOpts const consensusType = this.vm.common.consensusType() diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index fd6d9c1652e..e955b6645a9 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -27,6 +27,8 @@ import { unprefixedHexToBytes, } from '@ethereumjs/util' import debugDefault from 'debug' +import { keccak256 } from 'ethereum-cryptography/keccak.js' +import { sha256 } from 'ethereum-cryptography/sha256.js' import { Bloom } from './bloom/index.js' import { emitEVMProfile } from './emitEVMProfile.js' @@ -48,7 +50,7 @@ import type { VM } from './vm.js' import type { ValueOf } from '@chainsafe/ssz' import type { Block } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' -import type { EVM, EVMInterface } from '@ethereumjs/evm' +import type { EVM, EVMInterface, Log } from '@ethereumjs/evm' import type { CLRequest, CLRequestType, PrefixedHexString } from '@ethereumjs/util' export type SSZReceiptType = ValueOf @@ -638,6 +640,12 @@ async function applyTransactions(vm: VM, block: Block, opts: RunBlockOpts) { receipts.push(txRes.receipt) } + if (vm.common.isActivatedEIP(6493)) { + for (const txReceipt of receipts) { + await accumulateIVCLogs(vm, txReceipt.logs) + } + } + if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(processTxsLabel) @@ -796,6 +804,61 @@ export function encodeSszReceipt(receipt: TxReceipt, _txType: TransactionType) { return sszRaw } +export async function accumulateIVCLogs(vm: VM, logs: Log[]) { + // keep all declarations here for ease of movement and diff + const LOG_ADDRESS_STORAGE_SLOT = setLengthLeft(new Uint8Array([0]), 32) + const LOG_TOPICS_STORAGE_SLOT = setLengthLeft(new Uint8Array([1]), 32) + const LOG_ADDRESS_TOPICS_STORAGE_SLOT = setLengthLeft(new Uint8Array([2]), 32) + + const commonSHA256 = vm.common.customCrypto.sha256 ?? sha256 + const ivcContractAddress = new Address( + bigIntToAddressBytes(vm.common.param('ivcPredeployAddress')), + ) + + async function accumulateLog(key: Uint8Array, logRoot: Uint8Array) { + const prevRoot = setLengthLeft(await vm.stateManager.getStorage(ivcContractAddress, key), 32) + const newRoot = commonSHA256(concatBytes(logRoot, prevRoot)) + await vm.stateManager.putStorage(ivcContractAddress, key, newRoot) + } + + if ((await vm.stateManager.getAccount(ivcContractAddress)) === undefined) { + // store with nonce of 1 to prevent 158 cleanup + const ivcContract = new Account() + ivcContract.nonce = BIGINT_1 + await vm.stateManager.putAccount(ivcContractAddress, ivcContract) + } + + for (const log of logs) { + const sszLog = { + address: log[0], + topics: log[1], + data: log[2], + } + const logRoot = ssz.Log.hashTreeRoot(sszLog) + + // Allow eth_getLogs proof via `address` filter + // abi.encode(log.address, LOG_ADDRESS_STORAGE_SLOT) + const paddedAddress = setLengthLeft(sszLog.address, 32) + const addressKey = keccak256(concatBytes(paddedAddress, LOG_ADDRESS_STORAGE_SLOT)) + await accumulateLog(addressKey, logRoot) + + for (const topic of sszLog.topics) { + // Allow eth_getLogs proof via `topics` filter + // abi.encode(topic, LOG_TOPICS_STORAGE_SLOT) + const topicKey = keccak256(concatBytes(topic, LOG_TOPICS_STORAGE_SLOT)) + await accumulateLog(topicKey, logRoot) + + // Allow eth_getLogs proof via combined `address` + `topics` filter + // abi.encode(log.address, topic) + const addressAndTopic = keccak256(concatBytes(paddedAddress, topic)) + const addressAndTopicKey = keccak256( + concatBytes(addressAndTopic, LOG_ADDRESS_TOPICS_STORAGE_SLOT), + ) + await accumulateLog(addressAndTopicKey, logRoot) + } + } +} + /** * Apply the DAO fork changes to the VM */ diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index eb1cec8a454..f7329bc077a 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -8,7 +8,6 @@ import { BIGINT_0, BIGINT_1, KECCAK256_NULL, - bigIntToAddressBytes, bytesToBigInt, bytesToHex, bytesToUnprefixedHex, @@ -17,13 +16,10 @@ import { equalsBytes, hexToBytes, publicToAddress, - setLengthLeft, short, - ssz, } from '@ethereumjs/util' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { sha256 } from 'ethereum-cryptography/sha256.js' import { Bloom } from './bloom/index.js' import { emitEVMProfile } from './emitEVMProfile.js' @@ -41,7 +37,7 @@ import type { import type { VM } from './vm.js' import type { Block } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' -import type { EVM, Log } from '@ethereumjs/evm' +import type { EVM } from '@ethereumjs/evm' import type { AccessList, AccessList2930Transaction, @@ -745,10 +741,6 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { blobGasPrice, ) - if (vm.common.isActivatedEIP(6493)) { - await accumulateIVCLogs(vm, block?.header.number ?? DEFAULT_HEADER.number, results.receipt.logs) - } - if (enableProfiler) { // eslint-disable-next-line no-console console.timeEnd(receiptsLabel) @@ -774,61 +766,6 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { return results } -async function accumulateIVCLogs(vm: VM, number: bigint, logs: Log[]) { - // keep all declarations here for ease of movement and diff - const LOG_ADDRESS_STORAGE_SLOT = setLengthLeft(new Uint8Array([0]), 32) - const LOG_TOPICS_STORAGE_SLOT = setLengthLeft(new Uint8Array([1]), 32) - const LOG_ADDRESS_TOPICS_STORAGE_SLOT = setLengthLeft(new Uint8Array([2]), 32) - - const commonSHA256 = vm.common.customCrypto.sha256 ?? sha256 - const ivcContractAddress = new Address( - bigIntToAddressBytes(vm.common.param('ivcPredeployAddress')), - ) - - async function accumulateLog(key: Uint8Array, logRoot: Uint8Array) { - const prevRoot = setLengthLeft(await vm.stateManager.getStorage(ivcContractAddress, key), 32) - const newRoot = commonSHA256(concatBytes(logRoot, prevRoot)) - await vm.stateManager.putStorage(ivcContractAddress, key, newRoot) - } - - if ((await vm.stateManager.getAccount(ivcContractAddress)) === undefined) { - // store with nonce of 1 to prevent 158 cleanup - const ivcContract = new Account() - ivcContract.nonce = BIGINT_1 - await vm.stateManager.putAccount(ivcContractAddress, ivcContract) - } - - for (const log of logs) { - const sszLog = { - address: log[0], - topics: log[1], - data: log[2], - } - const logRoot = ssz.Log.hashTreeRoot(sszLog) - - // Allow eth_getLogs proof via `address` filter - // abi.encode(log.address, LOG_ADDRESS_STORAGE_SLOT) - const paddedAddress = setLengthLeft(sszLog.address, 32) - const addressKey = keccak256(concatBytes(paddedAddress, LOG_ADDRESS_STORAGE_SLOT)) - await accumulateLog(addressKey, logRoot) - - for (const topic of sszLog.topics) { - // Allow eth_getLogs proof via `topics` filter - // abi.encode(topic, LOG_TOPICS_STORAGE_SLOT) - const topicKey = keccak256(concatBytes(topic, LOG_TOPICS_STORAGE_SLOT)) - await accumulateLog(topicKey, logRoot) - - // Allow eth_getLogs proof via combined `address` + `topics` filter - // abi.encode(log.address, topic) - const addressAndTopic = keccak256(concatBytes(paddedAddress, topic)) - const addressAndTopicKey = keccak256( - concatBytes(addressAndTopic, LOG_ADDRESS_TOPICS_STORAGE_SLOT), - ) - await accumulateLog(addressAndTopicKey, logRoot) - } - } -} - /** * @method txLogsBloom * @private From 023887274cd55ebbff93182ab0e1ffc21a62e1c5 Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 9 Oct 2024 19:45:57 +0530 Subject: [PATCH 10/22] move the ivc log processing to pre 7685 to allow adding transfer logs for cl withdrawals --- packages/vm/src/buildBlock.ts | 13 ++++++------- packages/vm/src/runBlock.ts | 13 +++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index 8a375904a80..d754504806c 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -338,13 +338,6 @@ export class BlockBuilder { */ async build(sealOpts?: SealBlockOpts) { this.checkStatus() - - if (this.vm.common.isActivatedEIP(6493)) { - for (const txReceipt of this.transactionReceipts) { - await accumulateIVCLogs(this.vm, txReceipt.logs) - } - } - const blockOpts = this.blockOpts const consensusType = this.vm.common.consensusType() @@ -366,6 +359,12 @@ export class BlockBuilder { blobGasUsed = this.blobGasUsed } + if (this.vm.common.isActivatedEIP(6493)) { + for (const txReceipt of this.transactionReceipts) { + await accumulateIVCLogs(this.vm, txReceipt.logs) + } + } + let requests let requestsRoot if (this.vm.common.isActivatedEIP(7685)) { diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index e955b6645a9..b1200206dd2 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -218,6 +218,12 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise[] | undefined if (block.common.isActivatedEIP(7685)) { @@ -474,6 +480,7 @@ async function applyBlock(vm: VM, block: Block, opts: RunBlockOpts): Promise Date: Mon, 14 Oct 2024 14:41:49 +0530 Subject: [PATCH 11/22] add and process systemslogs root to header for allowing system logs verification --- packages/block/src/block/block.ts | 1 + packages/block/src/from-beacon-payload.ts | 5 ++ packages/block/src/header/header.ts | 23 +++++++ packages/block/src/helpers.ts | 2 + packages/block/src/types.ts | 4 ++ .../client/src/rpc/modules/engine/engine.ts | 2 +- .../src/rpc/modules/engine/validators.ts | 1 + packages/vm/src/buildBlock.ts | 64 ++++++++++++++----- packages/vm/src/runBlock.ts | 56 +++++++++++----- packages/vm/src/types.ts | 9 +-- 10 files changed, 130 insertions(+), 37 deletions(-) diff --git a/packages/block/src/block/block.ts b/packages/block/src/block/block.ts index d7f463f66d4..8c10acfd9a6 100644 --- a/packages/block/src/block/block.ts +++ b/packages/block/src/block/block.ts @@ -569,6 +569,7 @@ export class Block { depositRequests: this.common.isActivatedEIP(6110) ? [] : undefined, withdrawalRequests: this.common.isActivatedEIP(7002) ? [] : undefined, consolidationRequests: this.common.isActivatedEIP(7251) ? [] : undefined, + systemLogsRoot: this.common.isActivatedEIP(6493) ? header.systemLogsRoot : undefined, } if (this.requests !== undefined) { diff --git a/packages/block/src/from-beacon-payload.ts b/packages/block/src/from-beacon-payload.ts index 47aa261954c..5f713cba23e 100644 --- a/packages/block/src/from-beacon-payload.ts +++ b/packages/block/src/from-beacon-payload.ts @@ -106,6 +106,7 @@ export type BeaconPayloadJSON = { deposit_requests?: BeaconDepositRequest[] withdrawal_requests?: BeaconWithdrawalRequest[] consolidation_requests?: BeaconConsolidationRequest[] + system_logs_root?: PrefixedHexString // the casing of VerkleExecutionWitness remains same camel case for now execution_witness?: VerkleExecutionWitness @@ -277,6 +278,10 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJSON): E ) } + if (payload.system_logs_root !== undefined && payload.system_logs_root !== null) { + executionPayload.systemLogsRoot = payload.system_logs_root + } + if (payload.execution_witness !== undefined && payload.execution_witness !== null) { // the casing structure in payload could be camel case or snake depending upon the CL executionPayload.executionWitness = diff --git a/packages/block/src/header/header.ts b/packages/block/src/header/header.ts index e9b7dba26c9..3e3c8284689 100644 --- a/packages/block/src/header/header.ts +++ b/packages/block/src/header/header.ts @@ -65,6 +65,7 @@ export class BlockHeader { public readonly excessBlobGas?: bigint public readonly parentBeaconBlockRoot?: Uint8Array public readonly requestsRoot?: Uint8Array + public readonly systemLogsRoot?: Uint8Array public readonly common: Common @@ -166,6 +167,7 @@ export class BlockHeader { excessBlobGas: this.common.isActivatedEIP(4844) ? BIGINT_0 : undefined, parentBeaconBlockRoot: this.common.isActivatedEIP(4788) ? new Uint8Array(32) : undefined, requestsRoot: this.common.isActivatedEIP(7685) ? KECCAK256_RLP : undefined, + systemLogsRoot: this.common.isActivatedEIP(6493) ? KECCAK256_RLP : undefined, } const baseFeePerGas = @@ -181,6 +183,8 @@ export class BlockHeader { hardforkDefaults.parentBeaconBlockRoot const requestsRoot = toType(headerData.requestsRoot, TypeOutput.Uint8Array) ?? hardforkDefaults.requestsRoot + const systemLogsRoot = + toType(headerData.systemLogsRoot, TypeOutput.Uint8Array) ?? hardforkDefaults.systemLogsRoot if (!this.common.isActivatedEIP(1559) && baseFeePerGas !== undefined) { throw new Error('A base fee for a block can only be set with EIP1559 being activated') @@ -212,6 +216,10 @@ export class BlockHeader { throw new Error('requestsRoot can only be provided with EIP 7685 activated') } + if (!this.common.isActivatedEIP(6493) && systemLogsRoot !== undefined) { + throw new Error('systemLogsRoot can only be provided with EIP 6493 activated') + } + this.parentHash = parentHash this.uncleHash = uncleHash this.coinbase = coinbase @@ -233,6 +241,7 @@ export class BlockHeader { this.excessBlobGas = excessBlobGas this.parentBeaconBlockRoot = parentBeaconBlockRoot this.requestsRoot = requestsRoot + this.systemLogsRoot = systemLogsRoot this._genericFormatValidation() this._validateDAOExtraData() @@ -353,6 +362,13 @@ export class BlockHeader { throw new Error(msg) } } + + if (this.common.isActivatedEIP(6493)) { + if (this.systemLogsRoot === undefined) { + const msg = this._errorMsg('EIP6493 block has no systemLogsRoot field') + throw new Error(msg) + } + } } /** @@ -632,6 +648,9 @@ export class BlockHeader { if (this.common.isActivatedEIP(7685)) { rawItems.push(this.requestsRoot!) } + if (this.common.isActivatedEIP(7685)) { + rawItems.push(this.systemLogsRoot!) + } return rawItems } @@ -660,6 +679,7 @@ export class BlockHeader { excessGas: { regular: null, blob: this.excessBlobGas ?? null }, parentBeaconBlockRoot: this.parentBeaconBlockRoot ?? null, requestsRoot: this.requestsRoot ?? null, + systemLogsRoot: this.systemLogsRoot ?? null, } return header @@ -812,6 +832,9 @@ export class BlockHeader { if (this.common.isActivatedEIP(7685)) { JSONDict.requestsRoot = bytesToHex(this.requestsRoot!) } + if (this.common.isActivatedEIP(6493)) { + JSONDict.systemLogsRoot = bytesToHex(this.systemLogsRoot!) + } return JSONDict } diff --git a/packages/block/src/helpers.ts b/packages/block/src/helpers.ts index f9e884e05bb..e5c8cd09dee 100644 --- a/packages/block/src/helpers.ts +++ b/packages/block/src/helpers.ts @@ -50,6 +50,7 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { excessBlobGas, parentBeaconBlockRoot, requestsRoot, + systemLogsRoot, ] = values if (values.length > 21) { @@ -85,6 +86,7 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { excessBlobGas, parentBeaconBlockRoot, requestsRoot, + systemLogsRoot, } } diff --git a/packages/block/src/types.ts b/packages/block/src/types.ts index 7f7f74eb7f0..52d35c3df28 100644 --- a/packages/block/src/types.ts +++ b/packages/block/src/types.ts @@ -116,6 +116,7 @@ export interface HeaderData { excessBlobGas?: BigIntLike parentBeaconBlockRoot?: BytesLike requestsRoot?: BytesLike + systemLogsRoot?: BytesLike } /** @@ -209,6 +210,7 @@ export interface JSONHeader { excessBlobGas?: PrefixedHexString parentBeaconBlockRoot?: PrefixedHexString requestsRoot?: PrefixedHexString + systemLogsRoot?: PrefixedHexString } /* @@ -243,6 +245,7 @@ export interface JSONRPCBlock { parentBeaconBlockRoot?: PrefixedHexString // If EIP-4788 is enabled for this block, returns parent beacon block root executionWitness?: VerkleExecutionWitness | null // If Verkle is enabled for this block requestsRoot?: PrefixedHexString // If EIP-7685 is enabled for this block, returns the requests root + systemLogsRoot?: PrefixedHexString requests?: Array // If EIP-7685 is enabled for this block, array of serialized CL requests } @@ -278,4 +281,5 @@ export type ExecutionPayload = { depositRequests?: DepositRequestV1[] // Array of 6110 deposit requests withdrawalRequests?: WithdrawalRequestV1[] // Array of 7002 withdrawal requests consolidationRequests?: ConsolidationRequestV1[] // Array of 7251 consolidation requests + systemLogsRoot?: PrefixedHexString } diff --git a/packages/client/src/rpc/modules/engine/engine.ts b/packages/client/src/rpc/modules/engine/engine.ts index 0b40d422cbd..98dfda5fccb 100644 --- a/packages/client/src/rpc/modules/engine/engine.ts +++ b/packages/client/src/rpc/modules/engine/engine.ts @@ -1356,7 +1356,7 @@ export class Engine { throw Error(`runWithoutSetHead did not execute the block for payload=${payloadId}`) } - this.executedBlocks.set(bytesToUnprefixedHex(block.hash()), block) + // this.executedBlocks.set(bytesToUnprefixedHex(block.hash()), block) /** * Creates the payload in ExecutionPayloadV1 format to be returned */ diff --git a/packages/client/src/rpc/modules/engine/validators.ts b/packages/client/src/rpc/modules/engine/validators.ts index f2789b8a94a..5ce3b6ae1c2 100644 --- a/packages/client/src/rpc/modules/engine/validators.ts +++ b/packages/client/src/rpc/modules/engine/validators.ts @@ -69,6 +69,7 @@ export const executionPayloadV4FieldValidators = { depositRequests: validators.array(validators.depositRequest()), withdrawalRequests: validators.array(validators.withdrawalRequest()), consolidationRequests: validators.array(validators.consolidationRequest()), + systemLogsRoot: validators.bytes32, } export const forkchoiceFieldValidators = { diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index d754504806c..3d7c78ff672 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -43,6 +43,7 @@ import type { SSZReceiptType } from './runBlock.js' import type { BuildBlockOpts, BuilderOpts, RunTxResult, SealBlockOpts } from './types.js' import type { VM } from './vm.js' import type { Block, HeaderData } from '@ethereumjs/block' +import type { Log } from '@ethereumjs/evm' import type { TypedTransaction } from '@ethereumjs/tx' import type { Withdrawal } from '@ethereumjs/util' @@ -322,6 +323,35 @@ export class BlockBuilder { this.blockStatus = { status: BuildStatus.Reverted } } + async finishBlockBuild() { + const consensusType = this.vm.common.consensusType() + + if (consensusType === ConsensusType.ProofOfWork) { + await this.rewardMiner() + } + await this.processWithdrawals() + let requests + if (this.vm.common.isActivatedEIP(7685)) { + requests = await accumulateRequests(this.vm, this.transactionResults) + } + + let systemLogs: Log[] | undefined + if (this.vm.common.isActivatedEIP(6493)) { + // dummy assignment + systemLogs = [] as Log[] + } + + if (this.vm.common.isActivatedEIP(6493)) { + for (const txReceipt of this.transactionReceipts) { + await accumulateIVCLogs(this.vm, txReceipt.logs) + } + + await accumulateIVCLogs(this.vm, systemLogs!) + } + + return { requests, systemLogs } + } + /** * This method constructs the finalized block, including withdrawals and any CLRequests. * It also: @@ -341,10 +371,23 @@ export class BlockBuilder { const blockOpts = this.blockOpts const consensusType = this.vm.common.consensusType() - if (consensusType === ConsensusType.ProofOfWork) { - await this.rewardMiner() + const { requests, systemLogs } = await this.finishBlockBuild() + + let requestsRoot + if (this.vm.common.isActivatedEIP(7685)) { + requestsRoot = await genRequestsTrieRoot(requests!) + } + + let systemLogsRoot + if (this.vm.common.isActivatedEIP(6493)) { + systemLogsRoot = ssz.LogList.hashTreeRoot( + systemLogs!.map((log) => ({ + address: log[0], + topics: log[1], + data: log[2], + })), + ) } - await this.processWithdrawals() const transactionsTrie = await this.transactionsTrie() const withdrawalsRoot = await this.withdrawalsTrie() @@ -359,20 +402,6 @@ export class BlockBuilder { blobGasUsed = this.blobGasUsed } - if (this.vm.common.isActivatedEIP(6493)) { - for (const txReceipt of this.transactionReceipts) { - await accumulateIVCLogs(this.vm, txReceipt.logs) - } - } - - let requests - let requestsRoot - if (this.vm.common.isActivatedEIP(7685)) { - requests = await accumulateRequests(this.vm, this.transactionResults) - requestsRoot = await genRequestsTrieRoot(requests) - // Do other validations per request type - } - // get stateRoot after all the accumulateRequests etc have been done const stateRoot = await this.vm.stateManager.getStateRoot() const headerData = { @@ -387,6 +416,7 @@ export class BlockBuilder { // correct excessBlobGas should already be part of headerData used above blobGasUsed, requestsRoot, + systemLogsRoot, } if (consensusType === ConsensusType.ProofOfWork) { diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index b1200206dd2..9055c6dcbec 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -51,7 +51,7 @@ import type { ValueOf } from '@chainsafe/ssz' import type { Block } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' import type { EVM, EVMInterface, Log } from '@ethereumjs/evm' -import type { CLRequest, CLRequestType, PrefixedHexString } from '@ethereumjs/util' +import type { PrefixedHexString } from '@ethereumjs/util' export type SSZReceiptType = ValueOf @@ -218,17 +218,22 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise[] | undefined if (block.common.isActivatedEIP(7685)) { - requests = await accumulateRequests(vm, result.results) - requestsRoot = await genRequestsTrieRoot(requests) + requestsRoot = await genRequestsTrieRoot(result.requests!) + } + + let systemLogsRoot: Uint8Array | undefined + if (block.common.isActivatedEIP(6493)) { + // dummy for time being + const systemLogs = result.systemLogs ?? [] + systemLogsRoot = ssz.LogList.hashTreeRoot( + systemLogs!.map((log) => ({ + address: log[0], + topics: log[1], + data: log[2], + })), + ) } // Persist state @@ -254,18 +259,19 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise + /** + * Any CL requests that were processed in the course of this block + */ + requests?: CLRequest[] + systemLogs?: Log[] } /** @@ -371,10 +376,6 @@ export interface RunBlockResult extends Omit { * The requestsRoot for any CL requests in the block */ requestsRoot?: Uint8Array - /** - * Any CL requests that were processed in the course of this block - */ - requests?: CLRequest[] } export interface AfterBlockEvent extends RunBlockResult { From 223ce1aa5cb7ccf0b2bfcaf17f88fb81636e1f0f Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 15 Oct 2024 02:44:29 +0530 Subject: [PATCH 12/22] debug and fix issues introduced by systemlogs field and fix the 6493 client spec --- packages/block/src/header/header.ts | 2 +- packages/block/src/helpers.ts | 2 +- .../test/rpc/engine/newPayloadEip6493.spec.ts | 9 +++++++-- packages/util/src/ssz.ts | 1 + packages/vm/src/runBlock.ts | 13 +++++++++++++ 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/block/src/header/header.ts b/packages/block/src/header/header.ts index 3e3c8284689..6db0a8a5d3d 100644 --- a/packages/block/src/header/header.ts +++ b/packages/block/src/header/header.ts @@ -648,7 +648,7 @@ export class BlockHeader { if (this.common.isActivatedEIP(7685)) { rawItems.push(this.requestsRoot!) } - if (this.common.isActivatedEIP(7685)) { + if (this.common.isActivatedEIP(6493)) { rawItems.push(this.systemLogsRoot!) } diff --git a/packages/block/src/helpers.ts b/packages/block/src/helpers.ts index e5c8cd09dee..24ec12f795d 100644 --- a/packages/block/src/helpers.ts +++ b/packages/block/src/helpers.ts @@ -53,7 +53,7 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { systemLogsRoot, ] = values - if (values.length > 21) { + if (values.length > 22) { throw new Error( `invalid header. More values than expected were received. Max: 20, got: ${values.length}`, ) diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index 886f2322a01..ef532f6e805 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -60,10 +60,11 @@ describe(`${method}: call with executionPayloadV4`, () => { depositRequests: [], withdrawalRequests: [], consolidationRequests: [], + systemLogsRoot: "0x7eb7361dbf56cbb93e71275f214be5686dc18a0be0b32beda3abed277cac9795", receiptsRoot: '0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1', parentHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', - stateRoot: '0xbde9840c609ffa39cae0a2c9e354ac673920fcc2a5e6faeef5b78817c7fba7dd', - blockHash: '0x5e9dcd3f3e55e9dde218cad2958ef3f0f1c263a85d923d1e1d3821f96510e1dc', + stateRoot: '0xfa0e24c691687e8b30347365ae10e2e7cd1f6ee8a702a008a637b54b56fbdeae', + blockHash: '0x052a6f779247de67b476f024b8b98373870777561b9c6fa000a873506e073ee9', } let res @@ -71,6 +72,7 @@ describe(`${method}: call with executionPayloadV4`, () => { assert.equal(res.result.hash, validForkChoiceState.headBlockHash) res = await rpc.request(method, [validBlock, [], parentBeaconBlockRoot]) + console.log(res) assert.equal(res.result.status, 'VALID') res = await rpc.request('engine_forkchoiceUpdatedV3', validPayload) @@ -123,6 +125,8 @@ describe(`${method}: call with executionPayloadV4`, () => { 'depositRequests field should be received', ) + console.log(executionPayload) + res = await rpc.request(method, [executionPayload, [], parentBeaconBlockRoot]) assert.equal(res.result.status, 'VALID') @@ -136,6 +140,7 @@ describe(`${method}: call with executionPayloadV4`, () => { }, null, ]) + console.log(res) assert.equal(res.result.payloadStatus.status, 'VALID') const ivcContractHex = bytesToHex(bigIntToAddressBytes(common.param('ivcPredeployAddress'))) diff --git a/packages/util/src/ssz.ts b/packages/util/src/ssz.ts index 8b75af6bc20..ef2888803b5 100644 --- a/packages/util/src/ssz.ts +++ b/packages/util/src/ssz.ts @@ -448,6 +448,7 @@ export const BlockHeader = new StableContainerType( excessGas: new OptionalType(FeesPerGas), parentBeaconBlockRoot: new OptionalType(Bytes32), requestsRoot: new OptionalType(Bytes32), + systemLogsRoot: new OptionalType(Bytes32), }, MAX_BLOCKHEADER_FIELDS, { typeName: 'BlockHeader', jsonCase: 'eth2' }, diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 9055c6dcbec..3cb0f5b11db 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -268,6 +268,19 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise Date: Tue, 15 Oct 2024 19:08:24 +0530 Subject: [PATCH 13/22] add log for the combined miner/priority reward and debug/fix/validate client eip6493 spec --- .../test/rpc/engine/newPayloadEip6493.spec.ts | 6 ++-- packages/vm/src/buildBlock.ts | 31 +++++++++++++++++-- packages/vm/src/runBlock.ts | 27 +++++++++++++--- packages/vm/src/runTx.ts | 23 ++++++++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index ef532f6e805..9a7d8de8842 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -60,11 +60,11 @@ describe(`${method}: call with executionPayloadV4`, () => { depositRequests: [], withdrawalRequests: [], consolidationRequests: [], - systemLogsRoot: "0x7eb7361dbf56cbb93e71275f214be5686dc18a0be0b32beda3abed277cac9795", + systemLogsRoot: "0x3e216ef4d6b7acd369556559721439ff626dd99a52fab60efb7cee1ad99408f4", receiptsRoot: '0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1', parentHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', - stateRoot: '0xfa0e24c691687e8b30347365ae10e2e7cd1f6ee8a702a008a637b54b56fbdeae', - blockHash: '0x052a6f779247de67b476f024b8b98373870777561b9c6fa000a873506e073ee9', + stateRoot: '0x5c09695fb5296f4dcd50294b06662a56c99600b4942231682148024b1085af6c', + blockHash: '0xb53b5861dc405ae0a709f6b2ebc4f71ffe141bd58d0e217648e8d831574def9a', } let res diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index 3d7c78ff672..b5164a53dfa 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -18,12 +18,17 @@ import { BIGINT_2, GWEI_TO_WEI, TypeOutput, + bigIntToBytes, createWithdrawal, createZeroAddress, + hexToBytes, + setLengthLeft, ssz, toBytes, toType, + utf8ToBytes, } from '@ethereumjs/util' +import { keccak256 } from 'ethereum-cryptography/keccak.js' import { Bloom } from './bloom/index.js' import { accumulateRequests } from './requests.js' @@ -337,8 +342,30 @@ export class BlockBuilder { let systemLogs: Log[] | undefined if (this.vm.common.isActivatedEIP(6493)) { - // dummy assignment - systemLogs = [] as Log[] + // decide to add individual or total reward logs + const totalPriorityReward = this.transactionResults.reduce( + (acc, elem) => acc + elem.minerValue, + BIGINT_0, + ) + const systemAddressBytes = hexToBytes('0xfffffffffffffffffffffffffffffffffffffffe') + const coinbase = + this.headerData.coinbase !== undefined + ? new Address(toBytes(this.headerData.coinbase)) + : createZeroAddress() + + const logData = { + address: systemAddressBytes, + // operation, from, to + topics: [ + keccak256(utf8ToBytes('PriorityFee(address,uint256)')), + setLengthLeft(systemAddressBytes, 32), + setLengthLeft(coinbase.toBytes(), 32), + ], + // amount be uint256 + data: setLengthLeft(bigIntToBytes(totalPriorityReward), 32), + } + + systemLogs = [[logData.address, logData.topics, logData.data]] } if (this.vm.common.isActivatedEIP(6493)) { diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 3cb0f5b11db..3035a0c443c 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -25,6 +25,7 @@ import { short, ssz, unprefixedHexToBytes, + utf8ToBytes, } from '@ethereumjs/util' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' @@ -268,8 +269,8 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise acc + elem.minerValue, + BIGINT_0, + ) + const systemAddressBytes = hexToBytes('0xfffffffffffffffffffffffffffffffffffffffe') + const logData = { + address: systemAddressBytes, + // operation, from, to + topics: [ + keccak256(utf8ToBytes('PriorityFee(address,uint256)')), + setLengthLeft(systemAddressBytes, 32), + setLengthLeft(block.header.coinbase.toBytes(), 32), + ], + // amount be uint256 + data: setLengthLeft(bigIntToBytes(totalPriorityReward), 32), + } + + result.systemLogs = [[logData.address, logData.topics, logData.data]] } if (vm.common.isActivatedEIP(6493)) { diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index f7329bc077a..e901bc6f0bc 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -8,6 +8,7 @@ import { BIGINT_0, BIGINT_1, KECCAK256_NULL, + bigIntToBytes, bytesToBigInt, bytesToHex, bytesToUnprefixedHex, @@ -16,7 +17,9 @@ import { equalsBytes, hexToBytes, publicToAddress, + setLengthLeft, short, + utf8ToBytes, } from '@ethereumjs/util' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' @@ -619,6 +622,26 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { ) } + if (vm.common.isActivatedEIP(6493)) { + const systemAddressBytes = hexToBytes('0xfffffffffffffffffffffffffffffffffffffffe') + const logData = { + address: systemAddressBytes, + // operation, from, to + topics: [ + keccak256(utf8ToBytes('Fee(address,uint256)')), + setLengthLeft(caller.toBytes(), 32), + setLengthLeft(systemAddressBytes, 32), + ], + // amount be uint256 + data: setLengthLeft(bigIntToBytes(actualTxCost), 32), + } + + if (results.execResult.logs === undefined) { + results.execResult.logs = [] + } + results.execResult.logs.push([logData.address, logData.topics, logData.data]) + } + // Update miner's balance let miner if (vm.common.consensusType() === ConsensusType.ProofOfAuthority) { From f6f5ad8c03ce2588d1c721efd8d1b51b66436280 Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 15 Oct 2024 19:41:53 +0530 Subject: [PATCH 14/22] simplify log based on discussion with etan and make changes --- packages/client/test/rpc/engine/newPayloadEip6493.spec.ts | 6 +++--- packages/vm/src/buildBlock.ts | 3 +-- packages/vm/src/runBlock.ts | 3 +-- packages/vm/src/runTx.ts | 8 ++------ 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index 9a7d8de8842..3b1b0171f49 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -60,11 +60,11 @@ describe(`${method}: call with executionPayloadV4`, () => { depositRequests: [], withdrawalRequests: [], consolidationRequests: [], - systemLogsRoot: "0x3e216ef4d6b7acd369556559721439ff626dd99a52fab60efb7cee1ad99408f4", + systemLogsRoot: "0x3850240388ff8bed46a8631179e63ad67e28c343be54906cfaec0c3a2d95e71e", receiptsRoot: '0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1', parentHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', - stateRoot: '0x5c09695fb5296f4dcd50294b06662a56c99600b4942231682148024b1085af6c', - blockHash: '0xb53b5861dc405ae0a709f6b2ebc4f71ffe141bd58d0e217648e8d831574def9a', + stateRoot: '0x9d95c5098ef0f1b45fef49659318055ac4f06dc6601d7baf3656a391381981e3', + blockHash: '0x390042a0aefa4a11387652e215dd698a45dc5698d152ee0270a162e697420352', } let res diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index b5164a53dfa..d477e5e0ccd 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -357,8 +357,7 @@ export class BlockBuilder { address: systemAddressBytes, // operation, from, to topics: [ - keccak256(utf8ToBytes('PriorityFee(address,uint256)')), - setLengthLeft(systemAddressBytes, 32), + keccak256(utf8ToBytes('PriorityRewards(address,uint256)')), setLengthLeft(coinbase.toBytes(), 32), ], // amount be uint256 diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 3035a0c443c..a8910b554c0 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -524,8 +524,7 @@ async function applyBlock(vm: VM, block: Block, opts: RunBlockOpts): Promise { const systemAddressBytes = hexToBytes('0xfffffffffffffffffffffffffffffffffffffffe') const logData = { address: systemAddressBytes, - // operation, from, to - topics: [ - keccak256(utf8ToBytes('Fee(address,uint256)')), - setLengthLeft(caller.toBytes(), 32), - setLengthLeft(systemAddressBytes, 32), - ], + // operation, to + topics: [keccak256(utf8ToBytes('Fee(address,uint256)')), setLengthLeft(caller.toBytes(), 32)], // amount be uint256 data: setLengthLeft(bigIntToBytes(actualTxCost), 32), } From f60853edd9e40d8218ac579148ab716c82e716c1 Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 17 Oct 2024 14:32:35 +0530 Subject: [PATCH 15/22] remove console trace --- packages/vm/src/runBlock.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index a8910b554c0..ab06369b53a 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -875,7 +875,6 @@ export async function accumulateIVCLogs(vm: VM, logs: Log[]) { async function accumulateLog(key: Uint8Array, logRoot: Uint8Array) { const prevRoot = setLengthLeft(await vm.stateManager.getStorage(ivcContractAddress, key), 32) const newRoot = commonSHA256(concatBytes(logRoot, prevRoot)) - console.trace({ key: bytesToHex(key), newRoot: bytesToHex(newRoot) }) await vm.stateManager.putStorage(ivcContractAddress, key, newRoot) } From 61935e72967082938496975548000c5492fdf03c Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 17 Oct 2024 14:42:09 +0530 Subject: [PATCH 16/22] update ssz to stablecontainer released version --- package-lock.json | 149 ++++++++++++++++++++++++++++++++++++- packages/tx/package.json | 2 +- packages/util/package.json | 2 +- packages/vm/package.json | 2 +- 4 files changed, 149 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index a90a0e01d2a..6cbc6f4abf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -554,6 +554,68 @@ "integrity": "sha512-HJ8GZBRjLeWtRsAXf3EbNsNzmTGpzTFjfpSf4yHkLYC+E52DhT6hwz+7qpj6I/EmFzSUm5tYYvT9K8GZokLQCQ==", "license": "Apache-2.0" }, + "node_modules/@chainsafe/hashtree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree/-/hashtree-1.0.1.tgz", + "integrity": "sha512-bleu9FjqBeR/l6W1u2Lz+HsS0b0LLJX2eUt3hOPBN7VqOhidx8wzkVh2S7YurS+iTQtfdK4K5QU9tcTGNrGwDg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "optionalDependencies": { + "@chainsafe/hashtree-darwin-arm64": "1.0.1", + "@chainsafe/hashtree-linux-arm64-gnu": "1.0.1", + "@chainsafe/hashtree-linux-x64-gnu": "1.0.1" + } + }, + "node_modules/@chainsafe/hashtree-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree-darwin-arm64/-/hashtree-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-+KmEgQMpO7FDL3klAcpXbQ4DPZvfCe0qSaBBrtT4vLF8V1JGm3sp+j7oibtxtOsLKz7nJMiK1pZExi7vjXu8og==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@chainsafe/hashtree-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree-linux-arm64-gnu/-/hashtree-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-p1hnhGq2aFY+Zhdn1Q6L/6yLYNKjqXfn/Pc8jiM0e3+Lf/hB+yCdqYVu1pto26BrZjugCFZfupHaL4DjUTDttw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@chainsafe/hashtree-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@chainsafe/hashtree-linux-x64-gnu/-/hashtree-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-uCIGuUWuWV0LiB4KLMy6JFa7Jp6NmPl3hKF5BYWu8TzUBe7vSXMZfqTzGxXPggFYN2/0KymfRdG9iDCOJfGRqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@chainsafe/is-ip": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.0.2.tgz", @@ -17765,7 +17827,7 @@ "version": "5.4.0", "license": "MPL-2.0", "dependencies": { - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", @@ -17783,13 +17845,40 @@ "node": ">=18" } }, + "packages/tx/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==", + "license": "Apache-2.0" + }, + "packages/tx/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", + "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/hashtree": "1.0.1", + "@noble/hashes": "^1.3.0" + } + }, + "packages/tx/node_modules/@chainsafe/ssz": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", + "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/persistent-merkle-tree": "0.8.0" + } + }, "packages/util": { "name": "@ethereumjs/util", "version": "9.1.0", "license": "MPL-2.0", "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.7.2", - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/rlp": "^5.0.2", "ethereum-cryptography": "^3.0.0" }, @@ -17803,6 +17892,33 @@ "node": ">=18" } }, + "packages/util/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==", + "license": "Apache-2.0" + }, + "packages/util/node_modules/@chainsafe/ssz": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", + "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/persistent-merkle-tree": "0.8.0" + } + }, + "packages/util/node_modules/@chainsafe/ssz/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", + "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/hashtree": "1.0.1", + "@noble/hashes": "^1.3.0" + } + }, "packages/verkle": { "name": "@ethereumjs/verkle", "version": "0.1.0", @@ -17831,7 +17947,7 @@ "version": "8.1.0", "license": "MPL-2.0", "dependencies": { - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/block": "^5.3.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/evm": "^3.1.0", @@ -17865,6 +17981,33 @@ "node": ">=18" } }, + "packages/vm/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==", + "license": "Apache-2.0" + }, + "packages/vm/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", + "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/hashtree": "1.0.1", + "@noble/hashes": "^1.3.0" + } + }, + "packages/vm/node_modules/@chainsafe/ssz": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", + "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/persistent-merkle-tree": "0.8.0" + } + }, "packages/wallet": { "name": "@ethereumjs/wallet", "version": "2.0.4", diff --git a/packages/tx/package.json b/packages/tx/package.json index 7d1cf92a842..fb0414d8444 100644 --- a/packages/tx/package.json +++ b/packages/tx/package.json @@ -54,7 +54,7 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.1.0", diff --git a/packages/util/package.json b/packages/util/package.json index 531d647e1ac..819681a94f9 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -93,7 +93,7 @@ }, "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.7.2", - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/rlp": "^5.0.2", "ethereum-cryptography": "^3.0.0" }, diff --git a/packages/vm/package.json b/packages/vm/package.json index e2d67ede580..54be337121a 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -64,7 +64,7 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/block": "^5.3.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/evm": "^3.1.0", From 8821e701427643706b9d19beba785cab7603011f Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 17 Oct 2024 14:48:03 +0530 Subject: [PATCH 17/22] fix missing sz update --- package-lock.json | 110 ++++++++---------------------------- packages/block/package.json | 2 +- 2 files changed, 24 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6cbc6f4abf9..efb4935d5df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -640,13 +640,30 @@ } }, "node_modules/@chainsafe/ssz": { - "version": "0.16.0", - "resolved": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", - "integrity": "sha512-npPP4N+WM8oJxwjvlRPyWCc7oX5mjmg0maWpR93B68HREV1xTrsX5GDi7Q1iDF36L196WIZUd5a2PZNE/bjSWg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", + "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/persistent-merkle-tree": "0.8.0" + } + }, + "node_modules/@chainsafe/ssz/node_modules/@chainsafe/as-sha256": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", + "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==", + "license": "Apache-2.0" + }, + "node_modules/@chainsafe/ssz/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", + "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", "license": "Apache-2.0", "dependencies": { - "@chainsafe/as-sha256": "0.4.2", - "@chainsafe/persistent-merkle-tree": "0.7.2" + "@chainsafe/as-sha256": "0.5.0", + "@chainsafe/hashtree": "1.0.1", + "@noble/hashes": "^1.3.0" } }, "node_modules/@colors/colors": { @@ -17332,7 +17349,7 @@ "version": "5.3.0", "license": "MPL-2.0", "dependencies": { - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/mpt": "^6.2.2", "@ethereumjs/rlp": "^5.0.2", @@ -17845,33 +17862,6 @@ "node": ">=18" } }, - "packages/tx/node_modules/@chainsafe/as-sha256": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", - "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==", - "license": "Apache-2.0" - }, - "packages/tx/node_modules/@chainsafe/persistent-merkle-tree": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", - "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", - "license": "Apache-2.0", - "dependencies": { - "@chainsafe/as-sha256": "0.5.0", - "@chainsafe/hashtree": "1.0.1", - "@noble/hashes": "^1.3.0" - } - }, - "packages/tx/node_modules/@chainsafe/ssz": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", - "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", - "license": "Apache-2.0", - "dependencies": { - "@chainsafe/as-sha256": "0.5.0", - "@chainsafe/persistent-merkle-tree": "0.8.0" - } - }, "packages/util": { "name": "@ethereumjs/util", "version": "9.1.0", @@ -17892,33 +17882,6 @@ "node": ">=18" } }, - "packages/util/node_modules/@chainsafe/as-sha256": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", - "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==", - "license": "Apache-2.0" - }, - "packages/util/node_modules/@chainsafe/ssz": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", - "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", - "license": "Apache-2.0", - "dependencies": { - "@chainsafe/as-sha256": "0.5.0", - "@chainsafe/persistent-merkle-tree": "0.8.0" - } - }, - "packages/util/node_modules/@chainsafe/ssz/node_modules/@chainsafe/persistent-merkle-tree": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", - "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", - "license": "Apache-2.0", - "dependencies": { - "@chainsafe/as-sha256": "0.5.0", - "@chainsafe/hashtree": "1.0.1", - "@noble/hashes": "^1.3.0" - } - }, "packages/verkle": { "name": "@ethereumjs/verkle", "version": "0.1.0", @@ -17981,33 +17944,6 @@ "node": ">=18" } }, - "packages/vm/node_modules/@chainsafe/as-sha256": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz", - "integrity": "sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA==", - "license": "Apache-2.0" - }, - "packages/vm/node_modules/@chainsafe/persistent-merkle-tree": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz", - "integrity": "sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A==", - "license": "Apache-2.0", - "dependencies": { - "@chainsafe/as-sha256": "0.5.0", - "@chainsafe/hashtree": "1.0.1", - "@noble/hashes": "^1.3.0" - } - }, - "packages/vm/node_modules/@chainsafe/ssz": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.18.0.tgz", - "integrity": "sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA==", - "license": "Apache-2.0", - "dependencies": { - "@chainsafe/as-sha256": "0.5.0", - "@chainsafe/persistent-merkle-tree": "0.8.0" - } - }, "packages/wallet": { "name": "@ethereumjs/wallet", "version": "2.0.4", diff --git a/packages/block/package.json b/packages/block/package.json index 407164cd865..5b607d079e7 100644 --- a/packages/block/package.json +++ b/packages/block/package.json @@ -47,7 +47,7 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { - "@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz", + "@chainsafe/ssz": "^0.18.0", "@ethereumjs/common": "^4.4.0", "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/mpt": "^6.2.2", From e2bf0bc8475458ca91d4713ac0693b86c4925021 Mon Sep 17 00:00:00 2001 From: harkamal Date: Mon, 21 Oct 2024 16:35:36 +0530 Subject: [PATCH 18/22] propagate, serve and save system logs and modify, debug and fix client end to end spec test --- packages/client/src/execution/receipt.ts | 33 +++++++++++++++++-- packages/client/src/execution/vmexecution.ts | 22 ++++++++----- packages/client/src/miner/pendingBlock.ts | 22 ++++++++++--- .../client/src/rpc/modules/engine/engine.ts | 9 +++-- packages/client/src/util/metaDBManager.ts | 1 + .../test/rpc/engine/newPayloadEip6493.spec.ts | 18 +++++++++- packages/vm/src/buildBlock.ts | 2 ++ packages/vm/src/runBlock.ts | 2 +- 8 files changed, 90 insertions(+), 19 deletions(-) diff --git a/packages/client/src/execution/receipt.ts b/packages/client/src/execution/receipt.ts index b560b60c0f0..5027d4cadcf 100644 --- a/packages/client/src/execution/receipt.ts +++ b/packages/client/src/execution/receipt.ts @@ -43,8 +43,8 @@ type GetReceiptByTxHashReturn = [ type GetLogsReturn = { log: Log block: Block - tx: TypedTransaction - txIndex: number + tx?: TypedTransaction + txIndex?: number logIndex: number }[] @@ -112,6 +112,15 @@ export class ReceiptsManager extends MetaDBManager { void this.updateIndex(IndexOperation.Delete, IndexType.TxHash, block) } + async saveSystemLogs(block: Block, logs: Log[]) { + const encoded = this.rlp(RlpConvert.Encode, RlpType.Logs, logs) + await this.put(DBKey.SystemLogs, block.hash(), encoded) + } + + async deleteSystemLogs(block: Block) { + await this.delete(DBKey.SystemLogs, block.hash()) + } + /** * Returns receipts for given blockHash * @param blockHash the block hash @@ -152,6 +161,13 @@ export class ReceiptsManager extends MetaDBManager { return receipts } + async getSystemLogs(blockHash: Uint8Array): Promise { + const encoded = await this.get(DBKey.SystemLogs, blockHash) + if (!encoded) return [] + const systemLogs = this.rlp(RlpConvert.Decode, RlpType.Logs, encoded as unknown as rlpLog[]) + return systemLogs + } + /** * Returns receipt by tx hash with additional metadata for the JSON RPC response, or null if not found * @param txHash the tx hash @@ -183,7 +199,8 @@ export class ReceiptsManager extends MetaDBManager { for (let i = from.header.number; i <= to.header.number; i++) { const block = await this.chain.getBlock(i) const receipts = await this.getReceipts(block.hash()) - if (receipts.length === 0) continue + const systemLogs = await this.getSystemLogs(block.hash()) + if (receipts.length === 0 && systemLogs.length === 0) continue let logs: GetLogsReturn = [] let logIndex = 0 for (const [receiptIndex, receipt] of receipts.entries()) { @@ -197,6 +214,16 @@ export class ReceiptsManager extends MetaDBManager { })), ) } + + // push system logs + logs.push( + ...systemLogs.map((log) => ({ + log, + block, + logIndex: logIndex++, + })), + ) + if (addresses && addresses.length > 0) { logs = logs.filter((l) => addresses.some((a) => equalsBytes(a, l.log[0]))) } diff --git a/packages/client/src/execution/vmexecution.ts b/packages/client/src/execution/vmexecution.ts index 0becfd70f1f..540e07b04b9 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -40,6 +40,7 @@ import { ReceiptsManager } from './receipt.js' import type { ExecutionOptions } from './execution.js' import type { Block } from '@ethereumjs/block' +import type { Log } from '@ethereumjs/evm' import type { PrefixedHexString } from '@ethereumjs/util' import type { RunBlockOpts, TxReceipt, VM } from '@ethereumjs/vm' @@ -68,7 +69,7 @@ export class VMExecution extends Execution { public receiptsManager?: ReceiptsManager public preimagesManager?: PreimagesManager - private pendingReceipts?: Map + private pendingReceipts?: Map private vmPromise?: Promise /** Maximally tolerated block time before giving a warning on console */ @@ -126,6 +127,7 @@ export class VMExecution extends Execution { // Once a block gets deleted from the chain, delete the receipts also for (const block of blocks) { await this.receiptsManager?.deleteReceipts(block) + await this.receiptsManager?.deleteSystemLogs(block) } if (resolve !== undefined) { resolve() @@ -368,7 +370,7 @@ export class VMExecution extends Execution { */ async runWithoutSetHead( opts: RunBlockOpts & { parentBlock?: Block }, - receipts?: TxReceipt[], + receiptsAndSystemLogs?: { receipts: TxReceipt[]; systemLogs?: Log[] }, blocking: boolean = false, skipBlockchain: boolean = false, ): Promise { @@ -385,7 +387,7 @@ export class VMExecution extends Execution { this.running = true const { block, root } = opts - if (receipts === undefined) { + if (receiptsAndSystemLogs === undefined) { // Check if we need to pass flag to clear statemanager cache or not const prevVMStateRoot = await this.vm.stateManager.getStateRoot() // If root is not provided its mean to be run on the same set state @@ -451,11 +453,11 @@ export class VMExecution extends Execution { if (this.config.savePreimages && result.preimages !== undefined) { await this.savePreimages(result.preimages) } - receipts = result.receipts + receiptsAndSystemLogs = { receipts: result.receipts, systemLogs: result.systemLogs } } - if (receipts !== undefined) { + if (receiptsAndSystemLogs !== undefined) { // Save receipts - this.pendingReceipts?.set(bytesToHex(block.hash()), receipts) + this.pendingReceipts?.set(bytesToHex(block.hash()), receiptsAndSystemLogs) } if (!skipBlockchain) { @@ -519,9 +521,13 @@ export class VMExecution extends Execution { // skip emitting the chain update event as we will manually do it await this.chain.putBlocks(blocks, true, true) for (const block of blocks) { - const receipts = this.pendingReceipts?.get(bytesToHex(block.hash())) - if (receipts) { + const receiptsAndSystemLogs = this.pendingReceipts?.get(bytesToHex(block.hash())) + if (receiptsAndSystemLogs) { + const { receipts, systemLogs } = receiptsAndSystemLogs await this.receiptsManager?.saveReceipts(block, receipts) + if (systemLogs !== undefined) { + await this.receiptsManager?.saveSystemLogs(block, systemLogs) + } this.pendingReceipts?.delete(bytesToHex(block.hash())) } } diff --git a/packages/client/src/miner/pendingBlock.ts b/packages/client/src/miner/pendingBlock.ts index df3d6074a5e..ae0145d0d42 100644 --- a/packages/client/src/miner/pendingBlock.ts +++ b/packages/client/src/miner/pendingBlock.ts @@ -18,6 +18,7 @@ import { keccak256 } from 'ethereum-cryptography/keccak' import type { Config } from '../config.js' import type { TxPool } from '../service/txpool.js' import type { Block, HeaderData } from '@ethereumjs/block' +import type { Log } from '@ethereumjs/evm' import type { TypedTransaction } from '@ethereumjs/tx' import type { PrefixedHexString, WithdrawalData } from '@ethereumjs/util' import type { BlockBuilder, TxReceipt, VM } from '@ethereumjs/vm' @@ -244,7 +245,15 @@ export class PendingBlock { */ async build( payloadIdBytes: Uint8Array | string, - ): Promise { + ): Promise< + | void + | [ + block: Block, + receiptsAndSystemLogs: { receipts: TxReceipt[]; systemLogs?: Log[] }, + value: bigint, + blobs?: BlobsBundle, + ] + > { const payloadId = typeof payloadIdBytes !== 'string' ? bytesToHex(payloadIdBytes) : payloadIdBytes const builder = this.pendingPayloads.get(payloadId) @@ -255,7 +264,7 @@ export class PendingBlock { if (blockStatus.status === BuildStatus.Build) { return [ blockStatus.block, - builder.transactionReceipts, + { receipts: builder.transactionReceipts, systemLogs: builder.systemLogs }, builder.minerValue, this.blobsBundles.get(payloadId), ] @@ -308,10 +317,15 @@ export class PendingBlock { block.transactions.length }${withdrawalsStr}${blobsStr} skippedByAddErrors=${skippedByAddErrors} hash=${bytesToHex( block.hash(), - )}`, + )} systemLogs=${builder.systemLogs ? builder.systemLogs.length : undefined}`, ) - return [block, builder.transactionReceipts, builder.minerValue, blobs] + return [ + block, + { receipts: builder.transactionReceipts, systemLogs: builder.systemLogs }, + builder.minerValue, + blobs, + ] } private async addTransactions(builder: BlockBuilder, txs: TypedTransaction[]) { diff --git a/packages/client/src/rpc/modules/engine/engine.ts b/packages/client/src/rpc/modules/engine/engine.ts index 98dfda5fccb..7be8067ba6a 100644 --- a/packages/client/src/rpc/modules/engine/engine.ts +++ b/packages/client/src/rpc/modules/engine/engine.ts @@ -1346,12 +1346,17 @@ export class Engine { } // The third arg returned is the minerValue which we will use to // value the block - const [block, receipts, value, blobs] = built + const [block, receiptsAndSystemLogs, value, blobs] = built // do a blocking call even if execution might be busy for the moment and skip putting // it into chain till CL confirms with full data via new payload like versioned hashes // parent beacon block root - const executed = await this.execution.runWithoutSetHead({ block }, receipts, true, true) + const executed = await this.execution.runWithoutSetHead( + { block }, + receiptsAndSystemLogs, + true, + true, + ) if (!executed) { throw Error(`runWithoutSetHead did not execute the block for payload=${payloadId}`) } diff --git a/packages/client/src/util/metaDBManager.ts b/packages/client/src/util/metaDBManager.ts index a553cc8c445..701f62d1b5d 100644 --- a/packages/client/src/util/metaDBManager.ts +++ b/packages/client/src/util/metaDBManager.ts @@ -21,6 +21,7 @@ export enum DBKey { SkeletonStatus, SkeletonUnfinalizedBlockByHash, Preimage, + SystemLogs, } export interface MetaDBManagerOptions { diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index 3b1b0171f49..2cb619805e3 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -60,7 +60,7 @@ describe(`${method}: call with executionPayloadV4`, () => { depositRequests: [], withdrawalRequests: [], consolidationRequests: [], - systemLogsRoot: "0x3850240388ff8bed46a8631179e63ad67e28c343be54906cfaec0c3a2d95e71e", + systemLogsRoot: '0x3850240388ff8bed46a8631179e63ad67e28c343be54906cfaec0c3a2d95e71e', receiptsRoot: '0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1', parentHash: '0x5040e6b0056398536751c187683a3ecde8aff8fd9ea1d3450d687d7032134caf', stateRoot: '0x9d95c5098ef0f1b45fef49659318055ac4f06dc6601d7baf3656a391381981e3', @@ -155,6 +155,22 @@ describe(`${method}: call with executionPayloadV4`, () => { '0x88cce54f379f5607098522664e399bf4fee6f3e90127f8fc88f760fd4529211b', 'ivc root at updated topic should match', ) + + // check system logs available + res = await rpc.request('eth_getLogs', [{ blockHash: executionPayload.blockHash }]) + assert.equal(res.result.length, 4, '4 logs should be found including system logs') + const systemLogs = res.result[3]! + assert.equal(systemLogs.transactionHash, null, 'last log should be system log') + assert.equal( + systemLogs.topics[0], + '0x5dfe9c0fd3043bb299f97cfece428f0396cf8b7890c525756e4ea5c0ff7d61b2', + 'priority reward topic', + ) + assert.equal( + systemLogs.topics[1].includes(executionPayload.feeRecipient.slice(2)), + true, + 'fee recipient in topic', + ) }) }) diff --git a/packages/vm/src/buildBlock.ts b/packages/vm/src/buildBlock.ts index d477e5e0ccd..d85451f13ae 100644 --- a/packages/vm/src/buildBlock.ts +++ b/packages/vm/src/buildBlock.ts @@ -86,6 +86,7 @@ export class BlockBuilder { private checkpointed = false private blockStatus: BlockStatus = { status: BuildStatus.Pending } + systemLogs?: Log[] get transactionReceipts() { return this.transactionResults.map((result) => result.receipt) } @@ -398,6 +399,7 @@ export class BlockBuilder { const consensusType = this.vm.common.consensusType() const { requests, systemLogs } = await this.finishBlockBuild() + this.systemLogs = systemLogs let requestsRoot if (this.vm.common.isActivatedEIP(7685)) { diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index ab06369b53a..a1c15fd6f5d 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -278,7 +278,6 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise Date: Sun, 3 Nov 2024 12:19:13 +0530 Subject: [PATCH 19/22] add systemlogs root --- packages/client/src/rpc/modules/eth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 43a89932d51..4c7a964f5e3 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -155,6 +155,7 @@ const toJSONRPCBlock = async ( parentBeaconBlockRoot: header.parentBeaconBlockRoot, requestsRoot: header.requestsRoot, requests: block.requests?.map((req) => bytesToHex(req.serialize())), + systemLogsRoot: header.systemLogsRoot, } } From cd9b75c091632bc105b5db4905faaba9fcf1072b Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 6 Nov 2024 23:42:03 +0530 Subject: [PATCH 20/22] add transfer logs --- packages/evm/src/evm.ts | 52 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index b28a3247442..3cc88b10b05 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -14,9 +14,13 @@ import { equalsBytes, generateAddress, generateAddress2, + hexToBytes, + setLengthLeft, short, + utf8ToBytes, } from '@ethereumjs/util' import debugDefault from 'debug' +import { keccak256 } from 'ethereum-cryptography/keccak.js' import { FORMAT } from './eof/constants.js' import { isEOF } from './eof/util.js' @@ -51,6 +55,7 @@ import type { MessageWithTo } from './message.js' import type { AsyncDynamicGasHandler, SyncDynamicGasHandler } from './opcodes/gas.js' import type { OpHandler, OpcodeList, OpcodeMap } from './opcodes/index.js' import type { CustomPrecompile, PrecompileFunc } from './precompiles/index.js' +import type { Log } from './types.js' import type { Common, StateManagerInterface } from '@ethereumjs/common' const debug = debugDefault('evm:evm') @@ -179,8 +184,8 @@ export class EVM implements EVMInterface { // Supported EIPs const supportedEIPs = [ 663, 1153, 1559, 2537, 2565, 2718, 2929, 2930, 2935, 3198, 3529, 3540, 3541, 3607, 3651, 3670, - 3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6493, 6780, 6800, - 7002, 7069, 7251, 7480, 7516, 7620, 7685, 7692, 7698, 7702, 7709, + 3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6493, 6780, + 6800, 7002, 7069, 7251, 7480, 7516, 7620, 7685, 7692, 7698, 7702, 7709, ] for (const eip of this.common.eips()) { @@ -249,6 +254,7 @@ export class EVM implements EVMInterface { protected async _executeCall(message: MessageWithTo): Promise { let gasLimit = message.gasLimit const fromAddress = message.caller + let logs: Log[] | undefined = undefined if (this.common.isActivatedEIP(6800)) { const sendsValue = message.value !== BIGINT_0 @@ -320,6 +326,22 @@ export class EVM implements EVMInterface { if (!message.delegatecall) { try { await this._addToBalance(toAccount, message) + if (this.common.isActivatedEIP(6493) && message.value > 0) { + const systemAddressBytes = hexToBytes('0xfffffffffffffffffffffffffffffffffffffffe') + const logData = { + address: systemAddressBytes, + // operation, to + topics: [ + keccak256(utf8ToBytes('Transfer(address,address,uint256)')), + setLengthLeft(fromAddress.toBytes(), 32), + setLengthLeft(message.to.toBytes(), 32), + ], + // amount be uint256 + data: setLengthLeft(bigIntToBytes(message.value), 32), + } + logs = logs ?? [] + logs.push([logData.address, logData.topics, logData.data]) + } } catch (e: any) { errorMessage = e } @@ -347,6 +369,7 @@ export class EVM implements EVMInterface { executionGasUsed: message.gasLimit - gasLimit, exceptionError: errorMessage, // Only defined if addToBalance failed returnValue: new Uint8Array(0), + logs, }, } } @@ -386,6 +409,9 @@ export class EVM implements EVMInterface { } result.executionGasUsed += message.gasLimit - gasLimit + if (this.common.isActivatedEIP(6493) && logs !== undefined) { + result.logs = [...(logs ?? []), ...(result.logs ?? [])] + } return { execResult: result, @@ -395,6 +421,7 @@ export class EVM implements EVMInterface { protected async _executeCreate(message: Message): Promise { let gasLimit = message.gasLimit const fromAddress = message.caller + let logs: Log[] | undefined = undefined if (this.common.isActivatedEIP(6800)) { if (message.depth === 0) { @@ -505,6 +532,22 @@ export class EVM implements EVMInterface { let errorMessage try { await this._addToBalance(toAccount, message as MessageWithTo) + if (this.common.isActivatedEIP(6493) && message.value > 0) { + const systemAddressBytes = hexToBytes('0xfffffffffffffffffffffffffffffffffffffffe') + const logData = { + address: systemAddressBytes, + // operation, to + topics: [ + keccak256(utf8ToBytes('Transfer(address,address,uint256)')), + setLengthLeft(fromAddress.toBytes(), 32), + setLengthLeft(message.to.toBytes(), 32), + ], + // amount be uint256 + data: setLengthLeft(bigIntToBytes(message.value), 32), + } + logs = logs ?? [] + logs.push([logData.address, logData.topics, logData.data]) + } } catch (e: any) { errorMessage = e } @@ -552,6 +595,7 @@ export class EVM implements EVMInterface { gasRefund: message.gasRefund, exceptionError: errorMessage, // only defined if addToBalance failed returnValue: new Uint8Array(0), + logs, }, } } @@ -709,6 +753,10 @@ export class EVM implements EVMInterface { this.postMessageCleanup() } + if (this.common.isActivatedEIP(6493) && logs !== undefined) { + result.logs = [...(logs ?? []), ...(result.logs ?? [])] + } + return { createdAddress: message.to, execResult: result, From 1383b1ecdda0efc147a7e46031a0c107536933bf Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 7 Nov 2024 22:07:19 +0530 Subject: [PATCH 21/22] generate and add receipt proof to jsonrpc api --- packages/client/src/rpc/modules/eth.ts | 45 ++++++++++++++++++++++++++ packages/util/src/ssz.ts | 34 +++++++++++++++++++ packages/vm/src/index.ts | 2 +- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/packages/client/src/rpc/modules/eth.ts b/packages/client/src/rpc/modules/eth.ts index 4c7a964f5e3..62db0c68ebc 100644 --- a/packages/client/src/rpc/modules/eth.ts +++ b/packages/client/src/rpc/modules/eth.ts @@ -37,6 +37,7 @@ import { type PreByzantiumTxReceipt, type TxReceipt, type VM, + encodeSszReceipt, runBlock, runTx, } from '@ethereumjs/vm' @@ -91,6 +92,11 @@ type JSONRPCReceipt = { blobGasUsed?: string // QUANTITY, blob gas consumed by transaction (if blob transaction) blobGasPrice?: string // QUAntity, blob gas price for block including this transaction (if blob transaction) type: string // QUANTITY, transaction type + inclusionProof?: { + merkleBranch: string[] + receiptsRoot: string + receiptRoot: string + } } type JSONRPCLog = { removed: boolean // TAG - true when the log was removed, due to a chain reorganization. false if it's a valid log. @@ -194,6 +200,11 @@ const toJSONRPCReceipt = async ( contractAddress?: Address, blobGasUsed?: bigint, blobGasPrice?: bigint, + inclusionProof?: { + merkleBranch: Uint8Array[] + receiptsRoot: Uint8Array + receiptRoot: Uint8Array + }, ): Promise => ({ transactionHash: bytesToHex(tx.hash()), transactionIndex: intToHex(txIndex), @@ -220,6 +231,14 @@ const toJSONRPCReceipt = async ( blobGasUsed: blobGasUsed !== undefined ? bigIntToHex(blobGasUsed) : undefined, blobGasPrice: blobGasPrice !== undefined ? bigIntToHex(blobGasPrice) : undefined, type: intToHex(tx.type), + inclusionProof: + inclusionProof !== undefined + ? { + merkleBranch: inclusionProof.merkleBranch.map((elem) => bytesToHex(elem)), + receiptsRoot: bytesToHex(inclusionProof.receiptsRoot), + receiptRoot: bytesToHex(inclusionProof.receiptRoot), + } + : undefined, }) const calculateRewards = async ( @@ -996,6 +1015,10 @@ export class Eth { skipBlockValidation: true, }) + const sszReceipts = runBlockResult.receipts.map((txReceipt, index) => + encodeSszReceipt(txReceipt, block.transactions[index].type), + ) + const receipts = await Promise.all( result.map(async (r, i) => { const tx = block.transactions[i] @@ -1011,6 +1034,14 @@ export class Eth { block.header.baseFeePerGas! : (tx as LegacyTx).gasPrice + let inclusionProof = undefined + if (block.common.isActivatedEIP(6493)) { + inclusionProof = inclusionProof = { + receiptsRoot: block.header.receiptTrie, + ...ssz.computeReceiptInclusionProof(sszReceipts, i), + } + } + return toJSONRPCReceipt( r, totalGasSpent, @@ -1022,6 +1053,7 @@ export class Eth { createdAddress, blobGasUsed, blobGasPrice, + inclusionProof, ) }), ) @@ -1073,6 +1105,18 @@ export class Eth { const { totalGasSpent, createdAddress } = runBlockResult.results[txIndex] const { blobGasPrice, blobGasUsed } = runBlockResult.receipts[txIndex] as EIP4844BlobTxReceipt + + const sszReceipts = runBlockResult.receipts.map((txReceipt, index) => + encodeSszReceipt(txReceipt, block.transactions[index].type), + ) + let inclusionProof = undefined + if (block.common.isActivatedEIP(6493)) { + inclusionProof = inclusionProof = { + receiptsRoot: block.header.receiptTrie, + ...ssz.computeReceiptInclusionProof(sszReceipts, txIndex), + } + } + return toJSONRPCReceipt( receipt, totalGasSpent, @@ -1084,6 +1128,7 @@ export class Eth { createdAddress, blobGasUsed, blobGasPrice, + inclusionProof, ) } diff --git a/packages/util/src/ssz.ts b/packages/util/src/ssz.ts index ef2888803b5..281ee13334d 100644 --- a/packages/util/src/ssz.ts +++ b/packages/util/src/ssz.ts @@ -426,10 +426,44 @@ export const Receipt = new StableContainerType( { typeName: 'Receipt', jsonCase: 'eth2' }, ) export const Receipts = new ListCompositeType(Receipt, MAX_TRANSACTIONS_PER_PAYLOAD) +export const ReceiptRootsList = new ListCompositeType(Bytes32, MAX_TRANSACTIONS_PER_PAYLOAD) +export type ReceiptsType = ValueOf export const MAX_BLOCKHEADER_FIELDS = 64 const MAX_EXTRA_DATA_BYTES = 32 +export function computeReceiptInclusionProof( + receipts: ReceiptsType, + index: number, + fromRoots = true, +): { merkleBranch: Uint8Array[]; receiptRoot: Uint8Array } { + if (index >= receipts.length) { + throw Error(`Invalid index=${index} > receipts=${receipts.length}`) + } + + const receiptRoot = Receipt.hashTreeRoot(receipts[index]) + + let merkleBranch + if (fromRoots === true) { + const receiptRoots = receipts.map((tx) => Receipt.hashTreeRoot(tx)) + const ReceiptsRootView = ReceiptRootsList.toView(receiptRoots) + // transaction index is its g index in the list + merkleBranch = new Tree(ReceiptsRootView.node).getSingleProof( + // same gindex as transaction + TRANSACTION_GINDEX0 + BigInt(index), + ) + } else { + const ReceiptsView = Receipts.toView(receipts) + // transaction index is its g index in the list + merkleBranch = new Tree(ReceiptsView.node).getSingleProof( + // same gindex as transaction + TRANSACTION_GINDEX0 + BigInt(index), + ) + } + + return { merkleBranch, receiptRoot } +} + export const BlockHeader = new StableContainerType( { parentHash: new OptionalType(Bytes32), diff --git a/packages/vm/src/index.ts b/packages/vm/src/index.ts index f284760a753..29f3a3d434d 100644 --- a/packages/vm/src/index.ts +++ b/packages/vm/src/index.ts @@ -3,7 +3,7 @@ export { BlockBuilder, BuildStatus } from './buildBlock.js' export { buildBlock } from './buildBlock.js' export * from './constructors.js' export * from './params.js' -export { encodeReceipt } from './runBlock.js' +export { encodeReceipt, encodeSszReceipt } from './runBlock.js' export { runBlock } from './runBlock.js' export { runTx } from './runTx.js' export * from './types.js' From 2617e81adaf2fdcbac98778116b34ef09538d8c5 Mon Sep 17 00:00:00 2001 From: harkamal Date: Fri, 8 Nov 2024 13:40:39 +0530 Subject: [PATCH 22/22] added and verified receipt proof check in the test --- packages/client/test/rpc/engine/newPayloadEip6493.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts index 2cb619805e3..12897be6460 100644 --- a/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts +++ b/packages/client/test/rpc/engine/newPayloadEip6493.spec.ts @@ -158,8 +158,8 @@ describe(`${method}: call with executionPayloadV4`, () => { // check system logs available res = await rpc.request('eth_getLogs', [{ blockHash: executionPayload.blockHash }]) - assert.equal(res.result.length, 4, '4 logs should be found including system logs') - const systemLogs = res.result[3]! + assert.equal(res.result.length, 6, '6 logs should be found including system logs') + const systemLogs = res.result[5]! assert.equal(systemLogs.transactionHash, null, 'last log should be system log') assert.equal( systemLogs.topics[0], @@ -171,6 +171,10 @@ describe(`${method}: call with executionPayloadV4`, () => { true, 'fee recipient in topic', ) + + const txHash = res.result[4].transactionHash + res = await rpc.request('eth_getTransactionReceipt', [txHash]) + assert.ok(res.result.inclusionProof !== undefined, 'recepit should have inclusion proof') }) })