From eb9ef75d39fa83b66c682bbf3611fc201a1b7355 Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 13 Jun 2024 23:51:10 +0530 Subject: [PATCH 01/17] 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 a2f61344cf..f8d53825c7 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 e4eec3634a..407164cd86 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 f1867d6849..d7f463f66d 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 0bc2b45182..9c23101439 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 d6d93467f6..db773fd7ab 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 79675dafed..e9b7dba26c 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 9fc406788b..f9e884e05b 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 074d1a1c04..887b75358b 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 145ef4ef20..7f7f74eb7f 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 0fc6c1bcab..5b19bd65be 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 cc17cf8316..df3d6074a5 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 24cc138b40..cd12f244dc 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 edc3e69432..05ff66d9cf 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 594f24b85f..9a9ddab07b 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 a90704cb5d..dd9b05a5c1 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 d34c4434f8..43a89932d5 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 cc929dfe4e..091644fcbc 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 6a1ef4201a..a66e53a9f8 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 aa5faab06c..06acd42d98 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 0000000000..b8b7047244 --- /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 091fae9ce4..8e4d2a77ec 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 d4fbf08b0a..9c82bedd61 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 527ae642a1..c57422ca78 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 6fcbc92f44..9c348adf03 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 869b479db7..b28a324744 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 f9d320e003..7d1cf92a84 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 623659832e..c6045b7830 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 372f824e31..2941a97036 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 7373239e97..8c7f7e2fc2 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 10565728db..4dfa7ebce9 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 ec2ff0ecec..b3ba2bab73 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 909f462a55..7b93b47e34 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 d9ccc9086c..c78f524678 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 281e7aeff6..2021b66af6 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 f69acedb25..b227dd58b8 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 2b02ec2e02..b9cc7cf021 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 a9d2153ec7..59d988f0f3 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 118a5f6791..32dcf987e0 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 7e6f30964b..5568e56342 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 0000000000..b529e81db0 --- /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 6bafb0191c..531d647e1a 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 d1f6dd95e7..9aeb5eaf7f 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 0000000000..c19d2994a8 --- /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 0000000000..1ac7457844 --- /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 b95fec859d..75f0a3b578 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/17] 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 db773fd7ab..47aa261954 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 05ff66d9cf..0b40d422cb 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 dd9b05a5c1..f2789b8a94 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 c6045b7830..7663bb6855 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 2941a97036..fe71419b69 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 8c7f7e2fc2..bb3d068289 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 4dfa7ebce9..118fa6b41e 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 b3ba2bab73..063a3c06e5 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 7b93b47e34..0e5b0646a9 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 b227dd58b8..30c77dbef8 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 b9cc7cf021..e7c5b7b1f1 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 5568e56342..474a9c7053 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 c19d2994a8..58e23c1e06 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/17] 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 98d2baed05..5ec4c0f90e 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/17] 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 f8d53825c7..a90a0e01d2 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 58e23c1e06..2df2052999 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 32934c073e..e2d67ede58 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 75f0a3b578..512f22236b 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 41589f1d8c..fd6d9c1652 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 1036c6e30d..f7329bc077 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 0e3d7e49cd..60bf6cbcdb 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/17] 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 b8b7047244..3c94d656d6 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/17] 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 2df2052999..8b75af6bc2 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 1d3a576b2c..1120a16a6f 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 f7329bc077..13d92859c8 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/17] 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 3c94d656d6..0e682aa3c3 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 13d92859c8..b66ab1e3e5 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/17] 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 b66ab1e3e5..eb1cec8a45 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/17] 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 0e682aa3c3..886f2322a0 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 512f22236b..8a375904a8 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 fd6d9c1652..e955b6645a 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 eb1cec8a45..f7329bc077 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/17] 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 8a375904a8..d754504806 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 e955b6645a..b1200206dd 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/17] 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 d7f463f66d..8c10acfd9a 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 47aa261954..5f713cba23 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 e9b7dba26c..3e3c828468 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 f9e884e05b..e5c8cd09de 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 7f7f74eb7f..52d35c3df2 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 0b40d422cb..98dfda5fcc 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 f2789b8a94..5ce3b6ae1c 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 d754504806..3d7c78ff67 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 b1200206dd..9055c6dcbe 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/17] 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 3e3c828468..6db0a8a5d3 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 e5c8cd09de..24ec12f795 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 886f2322a0..ef532f6e80 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 8b75af6bc2..ef2888803b 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 9055c6dcbe..3cb0f5b11d 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/17] 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 ef532f6e80..9a7d8de884 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 3d7c78ff67..b5164a53df 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 3cb0f5b11d..3035a0c443 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 f7329bc077..e901bc6f0b 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/17] 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 9a7d8de884..3b1b0171f4 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 b5164a53df..d477e5e0cc 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 3035a0c443..a8910b554c 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/17] 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 a8910b554c..ab06369b53 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/17] 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 a90a0e01d2..6cbc6f4abf 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 7d1cf92a84..fb0414d844 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 531d647e1a..819681a94f 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 e2d67ede58..54be337121 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/17] 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 6cbc6f4abf..efb4935d5d 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 407164cd86..5b607d079e 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",