diff --git a/packages/block/test/from-beacon-payload.spec.ts b/packages/block/test/from-beacon-payload.spec.ts index 3ab97ee2b1..ce7dad7f65 100644 --- a/packages/block/test/from-beacon-payload.spec.ts +++ b/packages/block/test/from-beacon-payload.spec.ts @@ -20,6 +20,7 @@ tape('[fromExecutionPayloadJson]: 4844 devnet 5', async function (t) { for (const payload of [payload87335, payload87475]) { try { const block = await Block.fromBeaconPayloadJson(payload, { common }) + block.validateBlobTransactions({ excessDataGas: BigInt(0), _common: common } as any) st.pass(`successfully constructed block=${block.header.number}`) } catch (e) { st.fail(`failed to construct block, error: ${e}`) @@ -30,6 +31,7 @@ tape('[fromExecutionPayloadJson]: 4844 devnet 5', async function (t) { t.test('should validate block hash', async function (st) { try { + // construct a payload with differing block hash await Block.fromBeaconPayloadJson( { ...payload87335, block_hash: payload87475.block_hash }, { common } @@ -40,4 +42,23 @@ tape('[fromExecutionPayloadJson]: 4844 devnet 5', async function (t) { st.ok(`${e}`.includes('Invalid blockHash'), 'failed with correct error') } }) + + t.test('should validate excess data gas', async function (st) { + try { + // construct a payload with a different excess data gas but matching hash + const block = await Block.fromBeaconPayloadJson( + { + ...payload87475, + excess_data_gas: payload87335.excess_data_gas, + block_hash: '0x506ff15910ef7c5a713b21f225b3ffb4bfb4aeea4ea4891e3a71c0ad7bf6a8e0', + }, + { common } + ) + block.validateBlobTransactions({ excessDataGas: BigInt(0), _common: common } as any) + st.fail(`should have failed constructing the block`) + } catch (e) { + st.pass(`correctly failed constructing block, error: ${e}`) + st.ok(`${e}`.includes('block excessDataGas mismatch'), 'failed with correct error') + } + }) }) diff --git a/packages/block/test/testdata/payload-slot-87335.json b/packages/block/test/testdata/payload-slot-87335.json index 6476e4925b..e2147f3d6e 100644 --- a/packages/block/test/testdata/payload-slot-87335.json +++ b/packages/block/test/testdata/payload-slot-87335.json @@ -11,12 +11,12 @@ "timestamp": "1683551220", "extra_data": "0x4e65746865726d696e64", "base_fee_per_gas": "7", - "block_hash": "0x8479de330419e8acc597cda0fb467dcd1fdd4fb0f20f54970328df8c328a1f37", + "block_hash": "0x56351338f4a1531e2b591693bdd1d028deee00f7850396677560eafaa6017e30", "transactions": [ - "0x034500000001e9bb1e21fa4231a0050610632a251534ec30db0bf16522d75984c6ee2352780786ce89539029546384c7b6bafb0ef813e5e0e09e4d2e20019a7bfa81e0f9bb7ded96b92001000000000000000000000000000000000000000000000000000000360000000000000060641a2a0100000000000000000000000000000000000000000000000000000060641a2a010000000000000000000000000000000000000000000000000000005034030000000000c000000002a8080000000000000000000000000000000000000000000000000000000000d5000000d500000060641a2a01000000000000000000000000000000000000000000000000000000d500000001ffb38a7a99e3e2335be83fc74b7faa19d55312410153a6a1e053cf4c5a09e84088ed8ad7cb53d76c8168f1b82f7cfebfcd06da1a", - "0x03450000000082d39507b7ef0730d68f3f34efbb8a39554190ed59a85ebf9b78a9b532371014f228a560fd39464d0eff6f46f87673722e00b9e208dfea92a089273847e87272ed96b92001000000000000000000000000000000000000000000000000000000f0000000000000000094357700000000000000000000000000000000000000000000000000000000155ed0b20000000000000000000000000000000000000000000000000000000020a1070000000000c00000000100000000000000000000000000000000000000000000000000000000000000c1000000c6000000155ed0b200000000000000000000000000000000000000000000000000000000c6000000005f495f495501d343d3cd62abd9c5754cbe5128c25ea90786a8ae75fb79c8cf95f4dcdd08ec", - "0x0345000000002941f6a7f01787837c09d5ff963c2c1455859e8a0eb6dc67df79fee5860769b698d105864d1a4793f026aa235256713f791bbcfb3617f09564b8d30ced012f11ed96b92001000000000000000000000000000000000000000000000000000000f10000000000000000ca9a3b000000000000000000000000000000000000000000000000000000000e9435770000000000000000000000000000000000000000000000000000000020a1070000000000c00000000100000000000000000000000000000000000000000000000000000000000000c1000000c60000000e94357700000000000000000000000000000000000000000000000000000000c6000000005f495f495501d552e24560ec2f168be1d4a6385df61c70afe4288f00a3ad172da1a6f2b4f2", - "0x0345000000002901e4b944c111f83699f8633f713a17752cd28fa5c8b44d2202586ef022f13232daec47ffb63f822261edd54c2e0d195fbefe486adf7a002757facb72a8a243ed96b92001000000000000000000000000000000000000000000000000000000000000000000000000c2eb0b0000000000000000000000000000000000000000000000000000000007ca9a3b000000000000000000000000000000000000000000000000000000005034030000000000c00000000000000000000000000000000000000000000000000000000000000000000000d5000000d500000000a3e11100000000000000000000000000000000000000000000000000000000d500000001c8d369b164361a8961286cfbab3bc10f962185a8011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b0" + "0x03f89d850120b996ed3685012a1a646085012a1a64608303345094ffb38a7a99e3e2335be83fc74b7faa19d55312418308a80280c085012a1a6460e1a00153a6a1e053cf4c5a09e84088ed8ad7cb53d76c8168f1b82f7cfebfcd06da1a01a007785223eec68459d72265f10bdb30ec3415252a63100605a03142fa211ebbe9a07dbbf9e081fa7b9a01202e4d9ee0e0e513f80efbbab6c784635429905389ce86", + "0x03f889850120b996ed81f0847735940084b2d05e158307a1208001855f495f4955c084b2d05e15e1a001d343d3cd62abd9c5754cbe5128c25ea90786a8ae75fb79c8cf95f4dcdd08ec80a014103732b5a9789bbf5ea859ed904155398abbef343f8fd63007efb70795d382a07272e847382789a092eadf08e2b9002e727376f8466fff0e4d4639fd60a528f2", + "0x03f889850120b996ed81f1843b9aca00847735940e8307a1208001855f495f4955c0847735940ee1a001d552e24560ec2f168be1d4a6385df61c70afe4288f00a3ad172da1a6f2b4f280a0b6690786e5fe79df67dcb60e8a9e8555142c3c96ffd5097c838717f0a7f64129a0112f01ed0cd3b86495f01736fbbc1b793f71565223aa26f093471a4d8605d198", + "0x03f897850120b996ed80840bebc200843b9aca078303345094c8d369b164361a8961286cfbab3bc10f962185a88080c08411e1a300e1a0011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b080a032f122f06e5802224db4c8a58fd22c75173a713f63f89936f811c144b9e40129a043a2a872cbfa5727007adf6a48febe5f190d2e4cd5ed6122823fb6ff47ecda32" ], "withdrawals": [], "excess_data_gas": "262144" diff --git a/packages/block/test/testdata/payload-slot-87475.json b/packages/block/test/testdata/payload-slot-87475.json index 61a20a6bad..51f24c3b5c 100644 --- a/packages/block/test/testdata/payload-slot-87475.json +++ b/packages/block/test/testdata/payload-slot-87475.json @@ -11,12 +11,12 @@ "timestamp": "1683552900", "extra_data": "0x4e65746865726d696e64", "base_fee_per_gas": "9", - "block_hash": "0xfb640e94f3c2250ab223c41f07c3d72d5d0a589226ce09aab1827933d9a72548", + "block_hash": "0xd1ede53dab0cf183787351c90d4077a6e8a9b759781b2e7500a4fb607342892f", "transactions": [ "0x02f8f3850120b996ed1a85012a1645a985012a1645b0830186a0948e4fc0f136ba56a151fd1530d4d6695df6a0fb4a80b880d08cc67f792879a8e1d0d5569a51af02f456f410ad93b1de0c038b667b7c5577898a95d960f0527fb7298b05f6a2c83ed6f508eee540edb1248b9235bb26fa566927967d32652b88610110527fe29d11468a1e028eedc6143170491b87a32609c236cb7068ebb1799c616c393061013052604a61015053600d6101515360b661c001a0f49f3f2b301cc72120fd1dcb1fe218012623035f5824d17343e802a4741c4e60a02a40bf4d7a5d52c8f554b04c635a898650584c5452be62a5304e50ca59e4d55f", - "0x03450000000035b76d7ab2125a71b16cd133f628bcc74d36ad96911c3f76792dba3079846028a9ff798cc5073a117864b0165b0364fe3b51e6d74d485e037a217d3f0734042ded96b92001000000000000000000000000000000000000000000000000000000040000000000000000c2eb0b0000000000000000000000000000000000000000000000000000000007ca9a3b000000000000000000000000000000000000000000000000000000005034030000000000c00000000000000000000000000000000000000000000000000000000000000000000000d50000001901000000a3e1110000000000000000000000000000000000000000000000000000000019010000018a185c5a7941b20253a2923690dd54a9e7bfd0a9a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d7034000000000000000000000000000000000000000000000000000000000000000001a974d0ba16f7a378867bb0dd359d78fdf831a2b73823a8a1eea34e6615cb99", - "0x034500000000843fc3910959c8f1ca89f33616d514d345a92327d9497a4a4b4ce77310889ef539092ff31a42fe9b2299b451eddcac33292b6739905e43eea7f84e3f00baab5ded96b92001000000000000000000000000000000000000000000000000000000050000000000000000c2eb0b0000000000000000000000000000000000000000000000000000000007ca9a3b000000000000000000000000000000000000000000000000000000005034030000000000c00000000000000000000000000000000000000000000000000000000000000000000000d50000001901000000a3e1110000000000000000000000000000000000000000000000000000000019010000018a185c5a7941b20253a2923690dd54a9e7bfd0a9a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b0", - "0x0345000000015259d613c5dc94b70c901f2bba5192b74ac9907bf9076efed6da2667a81be5f693178852ae064b88d5e1b19dbb41300209318044a054af6cd8211c9387f8ab2bed96b9200100000000000000000000000000000000000000000000000000000006000000000000000065cd1d0000000000000000000000000000000000000000000000000000000007ca9a3b000000000000000000000000000000000000000000000000000000005034030000000000c00000000000000000000000000000000000000000000000000000000000000000000000d50000001901000000a3e1110000000000000000000000000000000000000000000000000000000019010000018a185c5a7941b20253a2923690dd54a9e7bfd0a9a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b0" + "0x03f8dc850120b996ed04840bebc200843b9aca0783033450948a185c5a7941b20253a2923690dd54a9e7bfd0a980b844a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000c08411e1a300e1a001a974d0ba16f7a378867bb0dd359d78fdf831a2b73823a8a1eea34e6615cb9980a02860847930ba2d79763f1c9196ad364dc7bc28f633d16cb1715a12b27a6db735a02d0434073f7d217a035e484dd7e6513bfe64035b16b06478113a07c58c79ffa9", + "0x03f8dc850120b996ed05840bebc200843b9aca0783033450948a185c5a7941b20253a2923690dd54a9e7bfd0a980b844a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000c08411e1a300e1a0011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b080a0f59e881073e74c4b4a7a49d92723a945d314d51636f389caf1c8590991c33f84a05dabba003f4ef8a7ee435e9039672b2933acdced51b499229bfe421af32f0939", + "0x03f8dc850120b996ed06841dcd6500843b9aca0783033450948a185c5a7941b20253a2923690dd54a9e7bfd0a980b844a8d55bda000000000000000000000000573d9cd570267bb9d1547192e51e5c8d017d70340000000000000000000000000000000000000000000000000000000000000000c08411e1a300e1a0011df88a2971c8a7ac494a7ba37ec1acaa1fc1edeeb38c839b5d1693d47b69b001a0f6e51ba86726dad6fe6e07f97b90c94ab79251ba2b1f900cb794dcc513d65952a02babf887931c21d86caf54a044803109023041bb9db1e1d5884b06ae52881793" ], "withdrawals": [], "excess_data_gas": "131072" diff --git a/packages/client/lib/net/protocol/ethprotocol.ts b/packages/client/lib/net/protocol/ethprotocol.ts index 71e681fcdf..a8c41a49d1 100644 --- a/packages/client/lib/net/protocol/ethprotocol.ts +++ b/packages/client/lib/net/protocol/ethprotocol.ts @@ -246,11 +246,12 @@ export class EthProtocol extends Protocol { return [ bytesToBigInt(reqId), txs.map((txData) => { + // Blob transactions are deserialized with network wrapper if (txData[0] === 5) { - // Blob transactions are deserialized with network wrapper - return BlobEIP4844Transaction.fromSerializedBlobTxNetworkWrapper(txData) + return BlobEIP4844Transaction.fromSerializedBlobTxNetworkWrapper(txData, { common }) + } else { + return TransactionFactory.fromBlockBodyData(txData, { common }) } - return TransactionFactory.fromBlockBodyData(txData) }), ] }, diff --git a/packages/client/test/rpc/engine/getPayloadV3.spec.ts b/packages/client/test/rpc/engine/getPayloadV3.spec.ts index 21ac158808..65cd3fc31c 100644 --- a/packages/client/test/rpc/engine/getPayloadV3.spec.ts +++ b/packages/client/test/rpc/engine/getPayloadV3.spec.ts @@ -112,7 +112,7 @@ tape(`${method}: call with known payload`, async (t) => { const { executionPayload, blobsBundle } = res.body.result t.equal( executionPayload.blockHash, - '0x3c599ece59439d2dc938e7a2b5e1c675cf8173b6be654f0a689b96936eba96e2', + '0xc95747af99348f5fd5f0e694973fbac29f3206155babc2232bfff202fdad8e2c', 'built expected block' ) const { commitments, proofs, blobs } = blobsBundle diff --git a/packages/tx/src/baseTransaction.ts b/packages/tx/src/baseTransaction.ts index d5252440a1..3c961dc9f0 100644 --- a/packages/tx/src/baseTransaction.ts +++ b/packages/tx/src/baseTransaction.ts @@ -20,6 +20,8 @@ import { checkMaxInitCodeSize } from './util' import type { AccessListEIP2930TxData, AccessListEIP2930ValuesArray, + BlobEIP4844TxData, + BlobEIP4844ValuesArray, FeeMarketEIP1559TxData, FeeMarketEIP1559ValuesArray, JsonTx, @@ -91,7 +93,10 @@ export abstract class BaseTransaction { */ protected DEFAULT_HARDFORK: string | Hardfork = Hardfork.Shanghai - constructor(txData: TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData, opts: TxOptions) { + constructor( + txData: TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData | BlobEIP4844TxData, + opts: TxOptions + ) { const { nonce, gasLimit, to, value, data, v, r, s, type } = txData this._type = Number(bytesToBigInt(toBytes(type))) @@ -257,7 +262,11 @@ export abstract class BaseTransaction { * signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant * representation for external signing use {@link BaseTransaction.getMessageToSign}. */ - abstract raw(): TxValuesArray | AccessListEIP2930ValuesArray | FeeMarketEIP1559ValuesArray + abstract raw(): + | TxValuesArray + | AccessListEIP2930ValuesArray + | FeeMarketEIP1559ValuesArray + | BlobEIP4844ValuesArray /** * Returns the encoding of the transaction. diff --git a/packages/tx/src/eip4844Transaction.ts b/packages/tx/src/eip4844Transaction.ts index 1fcf579e12..d4d12d3048 100644 --- a/packages/tx/src/eip4844Transaction.ts +++ b/packages/tx/src/eip4844Transaction.ts @@ -1,39 +1,36 @@ import { byteArrayEquals } from '@chainsafe/ssz' +import { RLP } from '@ethereumjs/rlp' import { - Address, MAX_INTEGER, bigIntToHex, bigIntToUnpaddedBytes, bytesToBigInt, + bytesToHex, bytesToPrefixedHexString, computeVersionedHash, concatBytes, ecrecover, + equalsBytes, hexStringToBytes, kzg, toBytes, + validateNoLeadingZeroes, } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak' import { BaseTransaction } from './baseTransaction' import { LIMIT_BLOBS_PER_TX } from './constants' -import { - BlobNetworkTransactionWrapper, - BlobTransactionType, - SignedBlobTransactionType, -} from './types' -import { AccessLists, blobTxToNetworkWrapperDataFormat } from './util' +import { AccessLists } from './util' import type { AccessList, AccessListBytes, - AccessListBytesItem, + BlobEIP4844NetworkValuesArray, BlobEIP4844TxData, + BlobEIP4844ValuesArray, JsonTx, TxOptions, - TxValuesArray, } from './types' -import type { ValueOf } from '@chainsafe/ssz' import type { Common } from '@ethereumjs/common' const TRANSACTION_TYPE = 0x03 @@ -190,20 +187,108 @@ export class BlobEIP4844Transaction extends BaseTransaction { - const to = { - selector: this.to !== undefined ? 1 : 0, - value: this.to?.toBytes() ?? null, - } - return { - message: { - chainId: this.common.chainId(), - nonce: this.nonce, - maxPriorityFeePerGas: this.maxPriorityFeePerGas, - maxFeePerGas: this.maxFeePerGas, - gas: this.gasLimit, - to, - value: this.value, - data: this.data, - accessList: this.accessList.map((listItem) => { - return { address: listItem[0], storageKeys: listItem[1] } - }), - blobVersionedHashes: this.versionedHashes, - maxFeePerDataGas: this.maxFeePerDataGas, - }, - // TODO: Decide how to serialize an unsigned transaction - signature: { - r: this.r ?? BigInt(0), - s: this.s ?? BigInt(0), - yParity: this.v === BigInt(1) ? true : false, - }, - } + raw(): BlobEIP4844ValuesArray { + return [ + bigIntToUnpaddedBytes(this.chainId), + bigIntToUnpaddedBytes(this.nonce), + bigIntToUnpaddedBytes(this.maxPriorityFeePerGas), + bigIntToUnpaddedBytes(this.maxFeePerGas), + bigIntToUnpaddedBytes(this.gasLimit), + this.to !== undefined ? this.to.bytes : new Uint8Array(0), + bigIntToUnpaddedBytes(this.value), + this.data, + this.accessList, + bigIntToUnpaddedBytes(this.maxFeePerDataGas), + this.versionedHashes, + this.v !== undefined ? bigIntToUnpaddedBytes(this.v) : new Uint8Array(0), + this.r !== undefined ? bigIntToUnpaddedBytes(this.r) : new Uint8Array(0), + this.s !== undefined ? bigIntToUnpaddedBytes(this.s) : new Uint8Array(0), + ] } /** - * Serialize a blob transaction to the execution payload variant - * @returns the minimum (execution payload) serialization of a signed transaction + * Returns the serialized encoding of the EIP-4844 transaction. + * + * Format: `0x03 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, + * access_list, max_fee_per_data_gas, blob_versioned_hashes, y_parity, r, s])`. + * + * Note that in contrast to the legacy tx serialization format this is not + * valid RLP any more due to the raw tx type preceding and concatenated to + * the RLP encoding of the values. */ serialize(): Uint8Array { - const sszEncodedTx = SignedBlobTransactionType.serialize(this.toValue()) - return concatBytes(TRANSACTION_TYPE_BYTES, sszEncodedTx) + const base = this.raw() + return concatBytes(TRANSACTION_TYPE_BYTES, RLP.encode(base)) } /** @@ -358,36 +414,39 @@ export class BlobEIP4844Transaction extends BaseTransaction Uint8Array.from(blob)) ?? [] - const serializedTxWrapper = BlobNetworkTransactionWrapper.serialize({ - blobs: blobArrays, - blobKzgs: this.kzgCommitments?.map((commitment) => Uint8Array.from(commitment)) ?? [], - tx: { ...blobTxToNetworkWrapperDataFormat(this), ...to }, - blobKzgProofs: this.kzgProofs?.map((proof) => Uint8Array.from(proof)) ?? [], - }) - return concatBytes(new Uint8Array([0x03]), serializedTxWrapper) + const tx_payload = this.raw() + return concatBytes( + TRANSACTION_TYPE_BYTES, + RLP.encode([tx_payload, this.blobs, this.kzgCommitments, this.kzgProofs]) + ) } - getMessageToSign(hashMessage: false): Uint8Array | Uint8Array[] - getMessageToSign(hashMessage?: true | undefined): Uint8Array - getMessageToSign(_hashMessage?: unknown): Uint8Array | Uint8Array[] { - return this.unsignedHash() + getMessageToSign(hashMessage = true): Uint8Array { + const base = this.raw().slice(0, 11) + const message = concatBytes(TRANSACTION_TYPE_BYTES, RLP.encode(base)) + return hashMessage ? keccak256(message) : message } /** - * Returns the hash of a blob transaction + * Computes a sha3-256 hash of the serialized tx. + * + * This method can only be used for signed txs (it throws otherwise). + * Use {@link BlobEIP4844Transaction.getMessageToSign} to get a tx hash for the purpose of signing. */ - unsignedHash(): Uint8Array { - const serializedTx = BlobTransactionType.serialize(this.toValue().message) - return keccak256(concatBytes(TRANSACTION_TYPE_BYTES, serializedTx)) - } + public hash(): Uint8Array { + if (!this.isSigned()) { + const msg = this._errorMsg('Cannot call hash method if transaction is not signed') + throw new Error(msg) + } + + if (Object.isFrozen(this)) { + if (!this.cache.hash) { + this.cache.hash = keccak256(this.serialize()) + } + return this.cache.hash + } - hash(): Uint8Array { return keccak256(this.serialize()) } diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index 1d0a02e3b5..7dbd89ad24 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -1,24 +1,3 @@ -import { - BooleanType, - ByteListType, - ByteVectorType, - ContainerType, - ListCompositeType, - NoneType, - UintBigintType, - UnionType, -} from '@chainsafe/ssz' - -import { - BYTES_PER_FIELD_ELEMENT, - FIELD_ELEMENTS_PER_BLOB, - LIMIT_BLOBS_PER_TX, - MAX_ACCESS_LIST_SIZE, - MAX_CALLDATA_SIZE, - MAX_TX_WRAP_KZG_COMMITMENTS, - MAX_VERSIONED_HASHES_LIST_SIZE, -} from './constants' - import type { FeeMarketEIP1559Transaction } from './eip1559Transaction' import type { AccessListEIP2930Transaction } from './eip2930Transaction' import type { BlobEIP4844Transaction } from './eip4844Transaction' @@ -32,13 +11,6 @@ export type { AccessListItem, } from '@ethereumjs/common' -const Bytes20 = new ByteVectorType(20) -const Bytes32 = new ByteVectorType(32) -const Bytes48 = new ByteVectorType(48) - -const Uint64 = new UintBigintType(8) -const Uint256 = new UintBigintType(32) - /** * Can be used in conjunction with {@link Transaction.supports} * to query on tx capabilities @@ -287,6 +259,33 @@ export type FeeMarketEIP1559ValuesArray = [ Uint8Array? ] +/** + * Bytes values array for a {@link BlobEIP4844Transaction} + */ +export type BlobEIP4844ValuesArray = [ + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + AccessListBytes, + Uint8Array, + Uint8Array[], + Uint8Array?, + Uint8Array?, + Uint8Array? +] + +export type BlobEIP4844NetworkValuesArray = [ + BlobEIP4844ValuesArray, + Uint8Array[], + Uint8Array[], + Uint8Array[] +] + type JsonAccessListItem = { address: string; storageKeys: string[] } /** @@ -342,55 +341,3 @@ export interface JsonRpcTx { maxFeePerDataGas?: string // QUANTITY - max data fee for blob transactions versionedHashes?: string[] // DATA - array of 32 byte versioned hashes for blob transactions } - -/** EIP4844 types */ -export const AddressType = Bytes20 // SSZ encoded address - -// SSZ encoded container for address and storage keys -export const AccessTupleType = new ContainerType({ - address: AddressType, - storageKeys: new ListCompositeType(Bytes32, MAX_VERSIONED_HASHES_LIST_SIZE), -}) - -// SSZ encoded blob transaction -export const BlobTransactionType = new ContainerType({ - chainId: Uint256, - nonce: Uint64, - maxPriorityFeePerGas: Uint256, - maxFeePerGas: Uint256, - gas: Uint64, - to: new UnionType([new NoneType(), AddressType]), - value: Uint256, - data: new ByteListType(MAX_CALLDATA_SIZE), - accessList: new ListCompositeType(AccessTupleType, MAX_ACCESS_LIST_SIZE), - maxFeePerDataGas: Uint256, - blobVersionedHashes: new ListCompositeType(Bytes32, MAX_VERSIONED_HASHES_LIST_SIZE), -}) - -// SSZ encoded ECDSA Signature -export const ECDSASignatureType = new ContainerType({ - yParity: new BooleanType(), - r: Uint256, - s: Uint256, -}) - -// SSZ encoded signed blob transaction -export const SignedBlobTransactionType = new ContainerType({ - message: BlobTransactionType, - signature: ECDSASignatureType, -}) - -// SSZ encoded KZG Commitment/Proof (48 bytes) -export const KZGCommitmentType = Bytes48 -export const KZGProofType = KZGCommitmentType - -// SSZ encoded blob network transaction wrapper -export const BlobNetworkTransactionWrapper = new ContainerType({ - tx: SignedBlobTransactionType, - blobKzgs: new ListCompositeType(KZGCommitmentType, MAX_TX_WRAP_KZG_COMMITMENTS), - blobs: new ListCompositeType( - new ByteVectorType(FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT), - LIMIT_BLOBS_PER_TX - ), - blobKzgProofs: new ListCompositeType(KZGProofType, MAX_TX_WRAP_KZG_COMMITMENTS), -}) diff --git a/packages/tx/src/util.ts b/packages/tx/src/util.ts index bded6cba96..4790ba07a4 100644 --- a/packages/tx/src/util.ts +++ b/packages/tx/src/util.ts @@ -2,7 +2,6 @@ import { bytesToPrefixedHexString, hexStringToBytes, setLengthLeft } from '@ethe import { isAccessList } from './types' -import type { BlobEIP4844Transaction } from './eip4844Transaction' import type { AccessList, AccessListBytes, AccessListItem } from './types' import type { Common } from '@ethereumjs/common' @@ -116,33 +115,3 @@ export class AccessLists { return addresses * Number(accessListAddressCost) + slots * Number(accessListStorageKeyCost) } } - -export const blobTxToNetworkWrapperDataFormat = (tx: BlobEIP4844Transaction) => { - const to = { - selector: tx.to !== undefined ? 1 : 0, - value: tx.to?.toBytes() ?? null, - } - return { - message: { - chainId: tx.common.chainId(), - nonce: tx.nonce, - maxPriorityFeePerGas: tx.maxPriorityFeePerGas, - maxFeePerGas: tx.maxFeePerGas, - gas: tx.gasLimit, - to, - value: tx.value, - data: tx.data, - accessList: tx.accessList.map((listItem) => { - return { address: listItem[0], storageKeys: listItem[1] } - }), - blobVersionedHashes: tx.versionedHashes, - maxFeePerDataGas: tx.maxFeePerDataGas, - }, - // If transaction is unsigned, signature fields will be initialized to zeroes - signature: { - r: tx.r ?? BigInt(0), - s: tx.s ?? BigInt(0), - yParity: tx.v === BigInt(1) ? true : false, - }, - } -} diff --git a/packages/tx/test/eip4844.spec.ts b/packages/tx/test/eip4844.spec.ts index 779a3ed394..8a37d3b073 100644 --- a/packages/tx/test/eip4844.spec.ts +++ b/packages/tx/test/eip4844.spec.ts @@ -73,7 +73,7 @@ tape('fromTxData using from a json', (t) => { maxFeePerGas: '0x12a05f200', gasLimit: '0x33450', value: '0xbc614e', - input: '0x', + data: '0x', v: '0x0', r: '0x8a83833ec07806485a4ded33f24f5cea4b8d4d24dc8f357e6d446bcdae5e58a7', s: '0x68a2ba422a50cf84c0b5fcbda32ee142196910c97198ffd99035d920c2b557f8', @@ -82,12 +82,13 @@ tape('fromTxData using from a json', (t) => { accessList: null, maxFeePerDataGas: '0xb2d05e00', versionedHashes: ['0x01b0a4cdd5f55589f5c5b4d46c76704bb6ce95c0a8c09f77f197a57808dded28'], - kzgAggregatedProof: - '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - hash: '35cfcdb43774134e8a8b05e936222c35bc5c68b9aa672453eedf5897213b4a6b', + } + const txMeta = { + hash: 'e5e02be0667b6d31895d1b5a8b916a6761cbc9865225c6144a3e2c50936d173e', serialized: - '034500000000a7585eaecd6b446d7e358fdc244d8d4bea5c4ff233ed4d5a480678c03e83838af857b5c220d93590d9ff9871c910691942e12ea3bdfcb5c084cf502a42baa268b357870200000000000000000000000000000000000000000000000000000000000000000000000000f2052a0100000000000000000000000000000000000000000000000000000000f2052a010000000000000000000000000000000000000000000000000000005034030000000000c00000004e61bc0000000000000000000000000000000000000000000000000000000000d5000000d5000000005ed0b200000000000000000000000000000000000000000000000000000000d500000001ffb38a7a99e3e2335be83fc74b7faa19d553124301b0a4cdd5f55589f5c5b4d46c76704bb6ce95c0a8c09f77f197a57808dded28', + '03f89b84028757b38085012a05f20085012a05f2008303345094ffb38a7a99e3e2335be83fc74b7faa19d553124383bc614e80c084b2d05e00e1a001b0a4cdd5f55589f5c5b4d46c76704bb6ce95c0a8c09f77f197a57808dded2880a08a83833ec07806485a4ded33f24f5cea4b8d4d24dc8f357e6d446bcdae5e58a7a068a2ba422a50cf84c0b5fcbda32ee142196910c97198ffd99035d920c2b557f8', } + const c = common.copy() c['_chainParams'] = Object.assign({}, common['_chainParams'], { chainId: Number(txData.chainId), @@ -97,9 +98,27 @@ tape('fromTxData using from a json', (t) => { t.pass('Should be able to parse a json data and hash it') t.equal(typeof tx.maxFeePerDataGas, 'bigint', 'should be able to parse correctly') - t.equal(bytesToHex(tx.serialize()), txData.serialized, 'serialization should match') + t.equal(bytesToHex(tx.serialize()), txMeta.serialized, 'serialization should match') // TODO: fix the hash - t.equal(bytesToHex(tx.hash()), txData.hash, 'hash should match') + t.equal(bytesToHex(tx.hash()), txMeta.hash, 'hash should match') + + const jsonData = tx.toJSON() + // override few fields with equivalent values to have a match + t.deepEqual( + { ...txData, accessList: [] }, + { gasPrice: null, ...jsonData }, + 'toJSON should give correct json' + ) + + const fromSerializedTx = BlobEIP4844Transaction.fromSerializedTx( + hexToBytes(txMeta.serialized), + { common: c } + ) + t.equal( + bytesToHex(fromSerializedTx.hash()), + txMeta.hash, + 'fromSerializedTx hash should match' + ) } catch (e) { t.fail('failed to parse json data') } @@ -108,19 +127,6 @@ tape('fromTxData using from a json', (t) => { } }) -tape('fromSerializedTx - from bytes', (t) => { - const serializedBlobTx = hexToBytes( - '034500000001a34a3d6d997350dfa6c9645624b0a02b1c79591fe90d574f2ee5599103fbcff03e2156483cc73cac5648fa0348b487c90cc2713a7d636df7335333ca1b18c650010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000ff0000000000000000000000000000000000000000000000000000000000000040420f0000000000c00000000000000000000000000000000000000000000000000000000000000000000000d5000000d5000000e803000000000000000000000000000000000000000000000000000000000000d5000000013da33b9a0894b908ddbb00d96399e506515a1009016ebc7b0ffa71dc019db13caaf539032134295cc5e652fa5b82c8e67f0fd9e1' - ) - try { - BlobEIP4844Transaction.fromSerializedTx(serializedBlobTx, { common }) - t.pass('Should correctly deserialize blob tx from bytes') - } catch (e) { - t.fail(`Could not deserialize blob tx from bytes, Error: ${(e as Error).message}`) - } - t.end() -}) - tape('EIP4844 constructor tests - invalid scenarios', (t) => { if (isBrowser() === true) { t.end() @@ -344,8 +350,8 @@ tape('hash() and signature verification', async (t) => { { common } ) t.equal( - bytesToHex(unsignedTx.unsignedHash()), - 'a99daca5e246f242df985eca984d17ce1a510a780fdd5221d5635f96a5a1bebc', + bytesToHex(unsignedTx.getMessageToSign(true)), + '8ce8c3544ca173c0e8dd0e86319d4ebfe649e15a730137a6659ba3a721a9ff8b', 'produced the correct transaction hash' ) const signedTx = unsignedTx.sign(