Skip to content

Commit 4870acf

Browse files
committed
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
1 parent f315fc0 commit 4870acf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1705
-65
lines changed

packages/block/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"tsc": "../../config/cli/ts-compile.sh"
4848
},
4949
"dependencies": {
50+
"@chainsafe/ssz": "https://github.com/ChainSafe/ssz/raw/cayman/stable-container/packages/ssz/package.tgz",
5051
"@ethereumjs/common": "^4.4.0",
5152
"@ethereumjs/rlp": "^5.0.2",
5253
"@ethereumjs/trie": "^6.2.1",

packages/block/src/block/block.ts

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import type {
4343
Withdrawal,
4444
WithdrawalRequest,
4545
} from '@ethereumjs/util'
46+
import { genTransactionsSszRoot, genWithdrawalsSszRoot } from '../helpers.js'
4647

4748
/**
4849
* Class representing a block in the Ethereum network. The {@link BlockHeader} has its own
@@ -226,7 +227,7 @@ export class Block {
226227
* Generates transaction trie for validation.
227228
*/
228229
async genTxTrie(): Promise<Uint8Array> {
229-
return genTransactionsTrieRoot(this.transactions, new Trie({ common: this.common }))
230+
return this.common.isActivatedEIP(6493)? genTransactionsSszRoot(this.transactions): genTransactionsTrieRoot(this.transactions, new Trie({ common: this.common }))
230231
}
231232

232233
/**
@@ -235,16 +236,10 @@ export class Block {
235236
* @returns True if the transaction trie is valid, false otherwise
236237
*/
237238
async transactionsTrieIsValid(): Promise<boolean> {
238-
let result
239-
if (this.transactions.length === 0) {
240-
result = equalsBytes(this.header.transactionsTrie, KECCAK256_RLP)
241-
return result
242-
}
243-
244239
if (this.cache.txTrieRoot === undefined) {
245240
this.cache.txTrieRoot = await this.genTxTrie()
246241
}
247-
result = equalsBytes(this.cache.txTrieRoot, this.header.transactionsTrie)
242+
const result = equalsBytes(this.cache.txTrieRoot, this.header.transactionsTrie)
248243
return result
249244
}
250245

@@ -364,7 +359,9 @@ export class Block {
364359
}
365360

366361
if (!(await this.transactionsTrieIsValid())) {
367-
const msg = this._errorMsg('invalid transaction trie')
362+
const msg = this._errorMsg(
363+
`invalid transaction trie expected=${bytesToHex(this.cache.txTrieRoot!)}`
364+
)
368365
throw new Error(msg)
369366
}
370367

@@ -453,6 +450,10 @@ export class Block {
453450
return equalsBytes(this.keccakFunction(raw), this.header.uncleHash)
454451
}
455452

453+
async genWithdrawalsTrie(): Promise<Uint8Array> {
454+
return this.common.isActivatedEIP(6493)? genWithdrawalsSszRoot(this.withdrawals!): genWithdrawalsTrieRoot(this.withdrawals!, new Trie({ common: this.common }))
455+
}
456+
456457
/**
457458
* Validates the withdrawal root
458459
* @returns true if the withdrawals trie root is valid, false otherwise
@@ -462,19 +463,10 @@ export class Block {
462463
throw new Error('EIP 4895 is not activated')
463464
}
464465

465-
let result
466-
if (this.withdrawals!.length === 0) {
467-
result = equalsBytes(this.header.withdrawalsRoot!, KECCAK256_RLP)
468-
return result
469-
}
470-
471466
if (this.cache.withdrawalsTrieRoot === undefined) {
472-
this.cache.withdrawalsTrieRoot = await genWithdrawalsTrieRoot(
473-
this.withdrawals!,
474-
new Trie({ common: this.common }),
475-
)
467+
this.cache.withdrawalsTrieRoot = await this.genWithdrawalsTrie()
476468
}
477-
result = equalsBytes(this.cache.withdrawalsTrieRoot, this.header.withdrawalsRoot!)
469+
const result = equalsBytes(this.cache.withdrawalsTrieRoot, this.header.withdrawalsRoot!)
478470
return result
479471
}
480472

@@ -543,7 +535,7 @@ export class Block {
543535
toExecutionPayload(): ExecutionPayload {
544536
const blockJSON = this.toJSON()
545537
const header = blockJSON.header!
546-
const transactions = this.transactions.map((tx) => bytesToHex(tx.serialize())) ?? []
538+
const transactions = this.common.isActivatedEIP(6493)? this.transactions.map((tx) => tx.toExecutionPayloadTx()) : this.transactions.map((tx) => bytesToHex(tx.serialize()))
547539
const withdrawalsArr = blockJSON.withdrawals ? { withdrawals: blockJSON.withdrawals } : {}
548540

549541
const executionPayload: ExecutionPayload = {

packages/block/src/block/constructors.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { RLP } from '@ethereumjs/rlp'
22
import { Trie } from '@ethereumjs/trie'
33
import {
44
type TxOptions,
5-
createTx,
65
createTxFromBlockBodyData,
7-
createTxFromRLP,
6+
createTxFromSerializedData,
7+
createTxFromTxData,
88
normalizeTxParams,
99
} from '@ethereumjs/tx'
1010
import {
@@ -25,7 +25,7 @@ import {
2525
} from '@ethereumjs/util'
2626

2727
import { generateCliqueBlockExtraData } from '../consensus/clique.js'
28-
import { genRequestsTrieRoot, genTransactionsTrieRoot, genWithdrawalsTrieRoot } from '../helpers.js'
28+
import { genRequestsTrieRoot, genTransactionsSszRoot, genTransactionsTrieRoot, genWithdrawalsSszRoot, genWithdrawalsTrieRoot } from '../helpers.js'
2929
import {
3030
Block,
3131
createBlockHeader,
@@ -55,6 +55,7 @@ import type {
5555
RequestBytes,
5656
WithdrawalBytes,
5757
} from '@ethereumjs/util'
58+
import type { Common } from '@ethereumjs/common'
5859

5960
/**
6061
* Static constructor to create a block from a block data dictionary
@@ -77,7 +78,7 @@ export function createBlock(blockData: BlockData = {}, opts?: BlockOptions) {
7778
// parse transactions
7879
const transactions = []
7980
for (const txData of txsData ?? []) {
80-
const tx = createTx(txData, {
81+
const tx = createTxFromTxData(txData, {
8182
...opts,
8283
// Use header common in case of setHardfork being activated
8384
common: header.common,
@@ -288,7 +289,7 @@ export function createBlockFromRPC(
288289
const opts = { common: header.common }
289290
for (const _txParams of blockParams.transactions ?? []) {
290291
const txParams = normalizeTxParams(_txParams)
291-
const tx = createTx(txParams, opts)
292+
const tx = createTxFromTxData(txParams, opts)
292293
transactions.push(tx)
293294
}
294295

@@ -373,7 +374,7 @@ export const createBlockFromJSONRPCProvider = async (
373374
*/
374375
export async function createBlockFromExecutionPayload(
375376
payload: ExecutionPayload,
376-
opts?: BlockOptions,
377+
opts: BlockOptions & {common: Common},
377378
): Promise<Block> {
378379
const {
379380
blockNumber: number,
@@ -389,22 +390,35 @@ export async function createBlockFromExecutionPayload(
389390
} = payload
390391

391392
const txs = []
392-
for (const [index, serializedTx] of transactions.entries()) {
393+
for (const [index, serializedTxOrPayload] of transactions.entries()) {
393394
try {
394-
const tx = createTxFromRLP(hexToBytes(serializedTx as PrefixedHexString), {
395-
common: opts?.common,
396-
})
395+
let tx;
396+
if (opts.common.isActivatedEIP(6493)) {
397+
if (typeof serializedTxOrPayload === 'string') {
398+
throw Error('EIP 6493 activated for transaction bytes')
399+
}
400+
tx = createTxFromExecutionPayloadTx(hexToBytes(serializedTxOrPayload), {
401+
common: opts?.common,
402+
})
403+
}else{
404+
if (typeof serializedTxOrPayload !== 'string') {
405+
throw Error('EIP 6493 not activated for transaction payload')
406+
}
407+
tx = createTxFromSerializedData(hexToBytes(serializedTxOrPayload as PrefixedHexString), {
408+
common: opts?.common,
409+
})
410+
}
397411
txs.push(tx)
398412
} catch (error) {
399413
const validationError = `Invalid tx at index ${index}: ${error}`
400414
throw validationError
401415
}
402416
}
403417

404-
const transactionsTrie = await genTransactionsTrieRoot(txs, new Trie({ common: opts?.common }))
418+
const transactionsTrie = opts.common.isActivatedEIP(6493)? await genTransactionsSszRoot(txs) : await genTransactionsTrieRoot(txs, new Trie({ common: opts?.common }))
405419
const withdrawals = withdrawalsData?.map((wData) => createWithdrawal(wData))
406420
const withdrawalsRoot = withdrawals
407-
? await genWithdrawalsTrieRoot(withdrawals, new Trie({ common: opts?.common }))
421+
? opts.common.isActivatedEIP(6493)? genWithdrawalsSszRoot(withdrawals) : await genWithdrawalsTrieRoot(withdrawals, new Trie({ common: opts?.common }))
408422
: undefined
409423

410424
const hasDepositRequests = depositRequests !== undefined && depositRequests !== null
@@ -478,7 +492,7 @@ export async function createBlockFromExecutionPayload(
478492
*/
479493
export async function createBlockFromBeaconPayloadJSON(
480494
payload: BeaconPayloadJSON,
481-
opts?: BlockOptions,
495+
opts: BlockOptions & { common: Common },
482496
): Promise<Block> {
483497
const executionPayload = executionPayloadFromBeaconPayload(payload)
484498
return createBlockFromExecutionPayload(executionPayload, opts)

packages/block/src/from-beacon-payload.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { bigIntToHex } from '@ethereumjs/util'
22

33
import type { ExecutionPayload } from './types.js'
4-
import type { NumericString, PrefixedHexString, VerkleExecutionWitness } from '@ethereumjs/util'
4+
import type { NumericString, PrefixedHexString, VerkleExecutionWitness, ssz } from '@ethereumjs/util'
55

66
type BeaconWithdrawal = {
77
index: PrefixedHexString
@@ -30,7 +30,41 @@ type BeaconConsolidationRequest = {
3030
target_pubkey: PrefixedHexString
3131
}
3232

33-
// Payload JSON that one gets using the beacon apis
33+
export type BeaconFeesPerGasV1 = {
34+
regular: PrefixedHexString | null // Quantity 64 bytes
35+
blob: PrefixedHexString | null // Quantity 64 bytes
36+
}
37+
38+
export type BeaconAccessTupleV1 = {
39+
address: PrefixedHexString // DATA 20 bytes
40+
storage_keys: PrefixedHexString[] // Data 32 bytes MAX_ACCESS_LIST_STORAGE_KEYS array
41+
}
42+
43+
export type BeaconTransactionPayloadV1 = {
44+
type: PrefixedHexString | null // Quantity, 1 byte
45+
chain_id: PrefixedHexString | null // Quantity 8 bytes
46+
nonce: PrefixedHexString | null // Quantity 8 bytes
47+
max_fees_per_gas: BeaconFeesPerGasV1 | null
48+
gas: PrefixedHexString | null // Quantity 8 bytes
49+
to: PrefixedHexString | null // DATA 20 bytes
50+
value: PrefixedHexString | null // Quantity 64 bytes
51+
input: PrefixedHexString | null // max MAX_CALLDATA_SIZE bytes,
52+
access_list: BeaconAccessTupleV1[] | null
53+
max_priority_fees_per_gas: BeaconFeesPerGasV1 | null
54+
blob_versioned_hashes: PrefixedHexString[] | null // DATA 32 bytes array
55+
}
56+
57+
export type BeaconTransactionSignatureV1 = {
58+
from: PrefixedHexString | null // DATA 20 bytes
59+
ecdsa_signature: PrefixedHexString | null // DATA 65 bytes or null
60+
}
61+
62+
type BeaconTransactionV1 = {
63+
payload: BeaconTransactionPayloadV1
64+
signature: BeaconTransactionSignatureV1
65+
}
66+
67+
// Payload json that one gets using the beacon apis
3468
// curl localhost:5052/eth/v2/beacon/blocks/56610 | jq .data.message.body.execution_payload
3569
export type BeaconPayloadJSON = {
3670
parent_hash: PrefixedHexString
@@ -46,7 +80,7 @@ export type BeaconPayloadJSON = {
4680
extra_data: PrefixedHexString
4781
base_fee_per_gas: NumericString
4882
block_hash: PrefixedHexString
49-
transactions: PrefixedHexString[]
83+
transactions: PrefixedHexString[] | BeaconTransactionV1[]
5084
withdrawals?: BeaconWithdrawal[]
5185
blob_gas_used?: NumericString
5286
excess_blob_gas?: NumericString
@@ -121,6 +155,36 @@ function parseExecutionWitnessFromSnakeJSON({
121155
* The JSON data can be retrieved from a consensus layer (CL) client on this Beacon API `/eth/v2/beacon/blocks/[block number]`
122156
*/
123157
export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJSON): ExecutionPayload {
158+
const transactions =
159+
typeof payload.transactions[0] === 'object'
160+
? (payload.transactions as BeaconTransactionV1[]).map((btxv1) => {
161+
return {
162+
payload: {
163+
type: btxv1.payload.type,
164+
chainId: btxv1.payload.chain_id,
165+
nonce: btxv1.payload.nonce,
166+
maxFeesPerGas: btxv1.payload.max_fees_per_gas,
167+
to: btxv1.payload.to,
168+
value: btxv1.payload.value,
169+
input: btxv1.payload.input,
170+
accessList:
171+
btxv1.payload.access_list?.map((bal: BeaconAccessTupleV1) => {
172+
return {
173+
address: bal.address,
174+
storageKeys: bal.storage_keys,
175+
}
176+
}) ?? null,
177+
maxPriorityFeesPerGas: btxv1.payload.max_priority_fees_per_gas,
178+
blobVersionedHashes: btxv1.payload.blob_versioned_hashes,
179+
},
180+
signature: {
181+
from: btxv1.signature.from,
182+
ecdsaSignature: btxv1.signature.ecdsa_signature,
183+
},
184+
} as ssz.TransactionV1
185+
})
186+
: (payload.transactions as PrefixedHexString[])
187+
124188
const executionPayload: ExecutionPayload = {
125189
parentHash: payload.parent_hash,
126190
feeRecipient: payload.fee_recipient,
@@ -135,7 +199,7 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJSON): E
135199
extraData: payload.extra_data,
136200
baseFeePerGas: bigIntToHex(BigInt(payload.base_fee_per_gas)),
137201
blockHash: payload.block_hash,
138-
transactions: payload.transactions,
202+
transactions,
139203
}
140204

141205
if (payload.withdrawals !== undefined && payload.withdrawals !== null) {

packages/block/src/helpers.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { BIGINT_0, BIGINT_1, TypeOutput, isHexString, toType } from '@ethereumjs
55

66
import type { BlockHeaderBytes, HeaderData } from './types.js'
77
import type { TypedTransaction } from '@ethereumjs/tx'
8-
import type { CLRequest, CLRequestType, PrefixedHexString, Withdrawal } from '@ethereumjs/util'
8+
import type { CLRequest, CLRequestType, PrefixedHexString, Withdrawal, ssz } from '@ethereumjs/util'
9+
10+
export type SszTransactionType = ValueOf<typeof ssz.Transaction>
911

1012
/**
1113
* Returns a 0x-prefixed hex number string from a hex string or string integer.
@@ -132,6 +134,11 @@ export async function genWithdrawalsTrieRoot(wts: Withdrawal[], emptyTrie?: Trie
132134
return trie.root()
133135
}
134136

137+
export async function genWithdrawalsSszRoot(wts: Withdrawal[]) {
138+
const withdrawals = wts.map((wt) => wt.toValue())
139+
return ssz.Withdrawals.hashTreeRoot(withdrawals)
140+
}
141+
135142
/**
136143
* Returns the txs trie root for array of TypedTransaction
137144
* @param txs array of TypedTransaction to compute the root of
@@ -145,6 +152,11 @@ export async function genTransactionsTrieRoot(txs: TypedTransaction[], emptyTrie
145152
return trie.root()
146153
}
147154

155+
export async function genTransactionsSszRoot(txs: TypedTransaction[]) {
156+
const transactions = txs.map((tx) => tx.sszRaw() as unknown as SszTransactionType)
157+
return ssz.Transactions.hashTreeRoot(transactions)
158+
}
159+
148160
/**
149161
* Returns the requests trie root for an array of CLRequests
150162
* @param requests - an array of CLRequests

packages/block/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
WithdrawalBytes,
1818
WithdrawalData,
1919
WithdrawalRequestV1,
20+
ssz,
2021
} from '@ethereumjs/util'
2122

2223
/**
@@ -267,7 +268,7 @@ export type ExecutionPayload = {
267268
extraData: PrefixedHexString // DATA, 0 to 32 Bytes
268269
baseFeePerGas: PrefixedHexString // QUANTITY, 256 Bits
269270
blockHash: PrefixedHexString // DATA, 32 Bytes
270-
transactions: PrefixedHexString[] // Array of DATA - Array of transaction rlp strings,
271+
transactions: PrefixedHexString[] | ssz.TransactionV1[] // Array of DATA - Array of transaction rlp strings,
271272
withdrawals?: WithdrawalV1[] // Array of withdrawal objects
272273
blobGasUsed?: PrefixedHexString // QUANTITY, 64 Bits
273274
excessBlobGas?: PrefixedHexString // QUANTITY, 64 Bits

packages/client/bin/cli.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ const args: ClientOpts = yargs
150150
boolean: true,
151151
default: true,
152152
})
153+
// just a hack to insert 6493 on pragueTime for input genesis
154+
.option('eip6493AtPrague', {
155+
describe: 'Just for stablecontainer devnets testing',
156+
boolean: true,
157+
default: true,
158+
})
153159
.option('bootnodes', {
154160
describe:
155161
'Comma-separated list of network bootnodes (format: "enode://<id>@<host:port>,enode://..." ("[?discport=<port>]" not supported) or path to a bootnode.txt file',
@@ -1029,6 +1035,12 @@ async function run() {
10291035
// Use geth genesis parameters file if specified
10301036
const genesisFile = JSON.parse(readFileSync(args.gethGenesis, 'utf-8'))
10311037
const chainName = path.parse(args.gethGenesis).base.split('.')[0]
1038+
// just a hack for stable container devnets to schedule 6493 at prague
1039+
if (args.eip6493AtPrague === true) {
1040+
genesisFile.config.eip6493Time = genesisFile.config.pragueTime
1041+
console.log('Scheduling eip6493AtPrague', genesisFile.config)
1042+
}
1043+
10321044
common = createCommonFromGethGenesis(genesisFile, {
10331045
chain: chainName,
10341046
mergeForkIdPostMerge: args.mergeForkIdPostMerge,

0 commit comments

Comments
 (0)