Skip to content

Commit 1a6da99

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
1 parent 2a774e5 commit 1a6da99

35 files changed

+1759
-59
lines changed

package-lock.json

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/block/package.json

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

packages/block/src/block.ts

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
hexToBytes,
2222
intToHex,
2323
isHexString,
24+
ssz,
2425
} from '@ethereumjs/util'
2526
import { keccak256 } from 'ethereum-cryptography/keccak.js'
2627

@@ -41,6 +42,7 @@ import type {
4142
RequestsBytes,
4243
WithdrawalsBytes,
4344
} from './types.js'
45+
import type { ValueOf } from '@chainsafe/ssz'
4446
import type { Common } from '@ethereumjs/common'
4547
import type {
4648
FeeMarketEIP1559Transaction,
@@ -57,6 +59,8 @@ import type {
5759
WithdrawalBytes,
5860
} from '@ethereumjs/util'
5961

62+
export type SszTransactionType = ValueOf<typeof ssz.Transaction>
63+
6064
/**
6165
* An object that represents the block.
6266
*/
@@ -95,6 +99,11 @@ export class Block {
9599
return trie.root()
96100
}
97101

102+
public static async genWithdrawalsSszRoot(wts: Withdrawal[]) {
103+
const withdrawals = wts.map((wt) => wt.toValue())
104+
return ssz.Withdrawals.hashTreeRoot(withdrawals)
105+
}
106+
98107
/**
99108
* Returns the txs trie root for array of TypedTransaction
100109
* @param txs array of TypedTransaction to compute the root of
@@ -108,6 +117,11 @@ export class Block {
108117
return trie.root()
109118
}
110119

120+
public static async genTransactionsSszRoot(txs: TypedTransaction[]) {
121+
const transactions = txs.map((tx) => tx.sszRaw() as unknown as SszTransactionType)
122+
return ssz.Transactions.hashTreeRoot(transactions)
123+
}
124+
111125
/**
112126
* Returns the requests trie root for an array of CLRequests
113127
* @param requests - an array of CLRequests
@@ -414,7 +428,7 @@ export class Block {
414428
*/
415429
public static async fromExecutionPayload(
416430
payload: ExecutionPayload,
417-
opts?: BlockOptions
431+
opts: BlockOptions & { common: Common }
418432
): Promise<Block> {
419433
const {
420434
blockNumber: number,
@@ -430,28 +444,44 @@ export class Block {
430444
} = payload
431445

432446
const txs = []
433-
for (const [index, serializedTx] of transactions.entries()) {
447+
for (const [index, serializedTxOrPayload] of transactions.entries()) {
434448
try {
435-
const tx = TransactionFactory.fromSerializedData(
436-
hexToBytes(serializedTx as PrefixedHexString),
437-
{
449+
let tx
450+
if (opts.common.isActivatedEIP(6493)) {
451+
if (typeof serializedTxOrPayload === 'string') {
452+
throw Error('EIP 6493 activated for transaction bytes')
453+
}
454+
455+
tx = TransactionFactory.fromExecutionPayloadTx(serializedTxOrPayload, {
438456
common: opts?.common,
457+
})
458+
} else {
459+
if (typeof serializedTxOrPayload !== 'string') {
460+
throw Error('EIP 6493 not activated for transaction payload')
439461
}
440-
)
462+
tx = TransactionFactory.fromSerializedData(
463+
hexToBytes(serializedTxOrPayload as PrefixedHexString),
464+
{
465+
common: opts?.common,
466+
}
467+
)
468+
}
469+
441470
txs.push(tx)
442471
} catch (error) {
443472
const validationError = `Invalid tx at index ${index}: ${error}`
444473
throw validationError
445474
}
446475
}
447476

448-
const transactionsTrie = await Block.genTransactionsTrieRoot(
449-
txs,
450-
new Trie({ common: opts?.common })
451-
)
477+
const transactionsTrie = opts.common.isActivatedEIP(6493)
478+
? await Block.genTransactionsSszRoot(txs)
479+
: await Block.genTransactionsTrieRoot(txs, new Trie({ common: opts?.common }))
452480
const withdrawals = withdrawalsData?.map((wData) => Withdrawal.fromWithdrawalData(wData))
453481
const withdrawalsRoot = withdrawals
454-
? await Block.genWithdrawalsTrieRoot(withdrawals, new Trie({ common: opts?.common }))
482+
? opts.common.isActivatedEIP(6493)
483+
? await Block.genWithdrawalsSszRoot(withdrawals)
484+
: await Block.genWithdrawalsTrieRoot(withdrawals, new Trie({ common: opts?.common }))
455485
: undefined
456486

457487
const hasDepositRequests = depositRequests !== undefined && depositRequests !== null
@@ -525,7 +555,7 @@ export class Block {
525555
*/
526556
public static async fromBeaconPayloadJson(
527557
payload: BeaconPayloadJson,
528-
opts?: BlockOptions
558+
opts: BlockOptions & { common: Common }
529559
): Promise<Block> {
530560
const executionPayload = executionPayloadFromBeaconPayload(payload)
531561
return Block.fromExecutionPayload(executionPayload, opts)
@@ -671,7 +701,9 @@ export class Block {
671701
* Generates transaction trie for validation.
672702
*/
673703
async genTxTrie(): Promise<Uint8Array> {
674-
return Block.genTransactionsTrieRoot(this.transactions, new Trie({ common: this.common }))
704+
return this.common.isActivatedEIP(6493)
705+
? Block.genTransactionsSszRoot(this.transactions)
706+
: Block.genTransactionsTrieRoot(this.transactions, new Trie({ common: this.common }))
675707
}
676708

677709
/**
@@ -680,16 +712,10 @@ export class Block {
680712
* @returns True if the transaction trie is valid, false otherwise
681713
*/
682714
async transactionsTrieIsValid(): Promise<boolean> {
683-
let result
684-
if (this.transactions.length === 0) {
685-
result = equalsBytes(this.header.transactionsTrie, KECCAK256_RLP)
686-
return result
687-
}
688-
689715
if (this.cache.txTrieRoot === undefined) {
690716
this.cache.txTrieRoot = await this.genTxTrie()
691717
}
692-
result = equalsBytes(this.cache.txTrieRoot, this.header.transactionsTrie)
718+
const result = equalsBytes(this.cache.txTrieRoot, this.header.transactionsTrie)
693719
return result
694720
}
695721

@@ -809,7 +835,9 @@ export class Block {
809835
}
810836

811837
if (!(await this.transactionsTrieIsValid())) {
812-
const msg = this._errorMsg('invalid transaction trie')
838+
const msg = this._errorMsg(
839+
`invalid transaction trie expected=${bytesToHex(this.cache.txTrieRoot!)}`
840+
)
813841
throw new Error(msg)
814842
}
815843

@@ -907,19 +935,12 @@ export class Block {
907935
throw new Error('EIP 4895 is not activated')
908936
}
909937

910-
let result
911-
if (this.withdrawals!.length === 0) {
912-
result = equalsBytes(this.header.withdrawalsRoot!, KECCAK256_RLP)
913-
return result
914-
}
915-
916938
if (this.cache.withdrawalsTrieRoot === undefined) {
917-
this.cache.withdrawalsTrieRoot = await Block.genWithdrawalsTrieRoot(
918-
this.withdrawals!,
919-
new Trie({ common: this.common })
920-
)
939+
this.cache.withdrawalsTrieRoot = this.common.isActivatedEIP(6493)
940+
? await Block.genWithdrawalsSszRoot(this.withdrawals!)
941+
: await Block.genWithdrawalsTrieRoot(this.withdrawals!, new Trie({ common: this.common }))
921942
}
922-
result = equalsBytes(this.cache.withdrawalsTrieRoot, this.header.withdrawalsRoot!)
943+
const result = equalsBytes(this.cache.withdrawalsTrieRoot, this.header.withdrawalsRoot!)
923944
return result
924945
}
925946

@@ -991,7 +1012,9 @@ export class Block {
9911012
toExecutionPayload(): ExecutionPayload {
9921013
const blockJson = this.toJSON()
9931014
const header = blockJson.header!
994-
const transactions = this.transactions.map((tx) => bytesToHex(tx.serialize())) ?? []
1015+
const transactions = this.common.isActivatedEIP(6493)
1016+
? this.transactions.map((tx) => tx.toExecutionPayloadTx())
1017+
: this.transactions.map((tx) => bytesToHex(tx.serialize()))
9951018
const withdrawalsArr = blockJson.withdrawals ? { withdrawals: blockJson.withdrawals } : {}
9961019

9971020
const executionPayload: ExecutionPayload = {

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

Lines changed: 67 additions & 3 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 { PrefixedHexString, VerkleExecutionWitness } from '@ethereumjs/util'
4+
import type { PrefixedHexString, VerkleExecutionWitness, ssz } from '@ethereumjs/util'
55

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

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+
3367
// 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 = {
@@ -46,7 +80,7 @@ export type BeaconPayloadJson = {
4680
extra_data: PrefixedHexString
4781
base_fee_per_gas: PrefixedHexString
4882
block_hash: PrefixedHexString
49-
transactions: PrefixedHexString[]
83+
transactions: PrefixedHexString[] | BeaconTransactionV1[]
5084
withdrawals?: BeaconWithdrawal[]
5185
blob_gas_used?: PrefixedHexString
5286
excess_blob_gas?: PrefixedHexString
@@ -118,6 +152,36 @@ function parseExecutionWitnessFromSnakeJson({
118152
* The JSON data can be retrieved from a consensus layer (CL) client on this Beacon API `/eth/v2/beacon/blocks/[block number]`
119153
*/
120154
export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJson): ExecutionPayload {
155+
const transactions =
156+
typeof payload.transactions[0] === 'object'
157+
? (payload.transactions as BeaconTransactionV1[]).map((btxv1) => {
158+
return {
159+
payload: {
160+
type: btxv1.payload.type,
161+
chainId: btxv1.payload.chain_id,
162+
nonce: btxv1.payload.nonce,
163+
maxFeesPerGas: btxv1.payload.max_fees_per_gas,
164+
to: btxv1.payload.to,
165+
value: btxv1.payload.value,
166+
input: btxv1.payload.input,
167+
accessList:
168+
btxv1.payload.access_list?.map((bal: BeaconAccessTupleV1) => {
169+
return {
170+
address: bal.address,
171+
storageKeys: bal.storage_keys,
172+
}
173+
}) ?? null,
174+
maxPriorityFeesPerGas: btxv1.payload.max_priority_fees_per_gas,
175+
blobVersionedHashes: btxv1.payload.blob_versioned_hashes,
176+
},
177+
signature: {
178+
from: btxv1.signature.from,
179+
ecdsaSignature: btxv1.signature.ecdsa_signature,
180+
},
181+
} as ssz.TransactionV1
182+
})
183+
: (payload.transactions as PrefixedHexString[])
184+
121185
const executionPayload: ExecutionPayload = {
122186
parentHash: payload.parent_hash,
123187
feeRecipient: payload.fee_recipient,
@@ -132,7 +196,7 @@ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJson): E
132196
extraData: payload.extra_data,
133197
baseFeePerGas: bigIntToHex(BigInt(payload.base_fee_per_gas)),
134198
blockHash: payload.block_hash,
135-
transactions: payload.transactions,
199+
transactions,
136200
}
137201

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

packages/block/src/types.ts

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

2122
/**
@@ -261,7 +262,7 @@ export type ExecutionPayload = {
261262
extraData: PrefixedHexString | string // DATA, 0 to 32 Bytes
262263
baseFeePerGas: PrefixedHexString | string // QUANTITY, 256 Bits
263264
blockHash: PrefixedHexString | string // DATA, 32 Bytes
264-
transactions: PrefixedHexString[] | string[] // Array of DATA - Array of transaction rlp strings,
265+
transactions: PrefixedHexString[] | string[] | ssz.TransactionV1[] // Array of DATA - Array of transaction rlp strings,
265266
withdrawals?: WithdrawalV1[] // Array of withdrawal objects
266267
blobGasUsed?: PrefixedHexString | string // QUANTITY, 64 Bits
267268
excessBlobGas?: PrefixedHexString | string // QUANTITY, 64 Bits

0 commit comments

Comments
 (0)