From b15ce552b387c76cbd6d54c48752558d0dae4c9e Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 00:27:58 +0100 Subject: [PATCH 01/12] tx: implement tx containers and tx workers [WIP] --- packages/tx/src/dataContainerTypes.ts | 207 ++++++++++++++++++ .../dataContainers/AccessList2930Container.ts | 13 ++ .../dataContainers/EOA7702DataContainer.ts | 3 + .../tx/src/dataContainers/legacyContainer.ts | 84 +++++++ packages/tx/src/dataContainers/template.ts | 22 ++ packages/tx/src/txWorker.ts | 74 +++++++ 6 files changed, 403 insertions(+) create mode 100644 packages/tx/src/dataContainerTypes.ts create mode 100644 packages/tx/src/dataContainers/AccessList2930Container.ts create mode 100644 packages/tx/src/dataContainers/EOA7702DataContainer.ts create mode 100644 packages/tx/src/dataContainers/legacyContainer.ts create mode 100644 packages/tx/src/dataContainers/template.ts create mode 100644 packages/tx/src/txWorker.ts diff --git a/packages/tx/src/dataContainerTypes.ts b/packages/tx/src/dataContainerTypes.ts new file mode 100644 index 0000000000..beb9f12cc9 --- /dev/null +++ b/packages/tx/src/dataContainerTypes.ts @@ -0,0 +1,207 @@ +import type { + AccessList, + AccessListBytes, + AuthorizationList, + AuthorizationListBytes, + JSONTx, + TransactionType, +} from './types.js' +import type { + Address, + AddressLike, + BigIntLike, + BytesLike, + PrefixedHexString, +} from '@ethereumjs/util' + +// TODO +// Make a very simple "Features" class which handles supports/activate/deactivate (?) + +export enum Feature { + ReplayProtection = 'ReplayProtection', // For EIP-155 replay protection + ECDSASignable = 'ECDSASignable', // For unsigned/signed ECDSA containers + ECDSASigned = 'ECDSASigned', // For signed ECDSA containers + + LegacyGasMarket = 'LegacyGasMarket', // Txs with legacy gas market (pre-1559) + FeeMarket = 'FeeMarket', // Txs with EIP1559 gas market + + TypedTransaction = 'TypedTransaction', + + AccessLists = 'AccessLists', + EOACode = 'EOACode', +} + +export type NestedUint8Array = (Uint8Array | NestedUint8Array)[] + +export interface TxDataContainer { + supports(capability: Feature): boolean + type: TransactionType + + // Raw list of Uint8Arrays (can be nested) + raw(): NestedUint8Array // TODO make more tx-specific + + // The serialized version of the raw() one + // (current: RLP.encode) + serialize(): Uint8Array + + // Utility to convert to a JSON object + toJSON(): JSONTx + + /** Signature related stuff, TODO */ + /* + isSigned(): boolean + isValid(): boolean + verifySignature(): boolean + getSenderAddress(): Address + getSenderPublicKey(): Uint8Array + sign(privateKey: Uint8Array): Transaction[T] + errorStr(): string + + addSignature( + v: bigint, + r: Uint8Array | bigint, + s: Uint8Array | bigint, + convertV?: boolean, + ): Transaction[T] + + + // Get the non-hashed message to sign (this is input, but then hashed, is input to methods like ecsign) + getMessageToSign(): Uint8Array | Uint8Array[] + // Get the hashed message to sign (allows for flexibility over the hash method, now: keccak256) + getHashedMessageToSign(): Uint8Array + + // The hash of the transaction (note: hash currently has to do with signed txs but on L2 likely can also be of non-signed txs (?)) + hash(): Uint8Array + + */ +} + +// Container "fields" and container "interface" below +// Fields: used for the CONSTRUCTOR of the containers +// Interface: used for the resulting constructor, so each param of the field is converted to that type before resulting in the container + +export type DefaultContainerDataFields = { + nonce?: BigIntLike + gasLimit?: BigIntLike + to?: AddressLike + value?: BigIntLike + data?: BytesLike | '' // Note: '' is for empty data (TODO look if we want to keep this) +} + +export interface DefaultContainerInterface { + readonly gasPrice: bigint + readonly nonce: bigint + readonly gasLimit: bigint + readonly value: bigint + readonly data: Uint8Array + readonly to: Address | null // TODO: figure out how to handle this on txs which do not allow to:null (7702/4844) +} + +export type ECDSASignedContainerFields = { + v?: BigIntLike + r?: BigIntLike + s?: BigIntLike +} + +export interface ECDSAContainerInterface { + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint +} + +// The container / tx data fields if the tx can create contracts (to `null`) +/*export type ContractCreationDataFields = { + to?: DefaultContainerDataFields['to'] | null | '' +}*/ + +/* +export interface ContractCreationContainerInterface { + to: DefaultContainerInterface['to'] | null +} +*/ + +export type LegacyGasMarketFields = { + gasPrice: BigIntLike +} + +export interface LegacyGasMarketInterface { + readonly gasPrice: bigint +} + +interface L1DefaultContainer + extends TxDataContainer, + DefaultContainerInterface, + ECDSAContainerInterface {} + +export interface LegacyContainerInterface extends L1DefaultContainer, LegacyGasMarketInterface { + // to: DefaultContainerInterface['to'] | null +} + +export type ChainIdFields = { + chainId?: BigIntLike +} + +export interface ChainIdInterface { + chainId: bigint +} + +export type AccessListFields = { + accessList?: AccessListBytes | AccessList | null +} + +export interface AccessListInterface { + accessList: AccessListBytes +} + +interface L1_2930Interface extends L1DefaultContainer, ChainIdInterface, AccessListInterface {} + +export interface AccessList2930ContainerInterface + extends L1_2930Interface, + LegacyGasMarketInterface {} + +// interface AccessList2930Interface: L1DefaultFields, ContractCreationDataFields, LegacyGasMarket, ChainId, AccessList + +// EIP1559 txs +export type FeeMarketFields = { + maxPriorityFeePerGas?: BigIntLike + maxFeePerGas?: BigIntLike +} + +export interface FeeMarketInterface { + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint +} + +export interface FeeMarket1559Interface extends L1_2930Interface, FeeMarketInterface {} + +// EIP4844 txs +export type BlobFields = { + blobVersionedHashes?: BytesLike[] + maxFeePerBlobGas?: BigIntLike + blobs?: BytesLike[] + kzgCommitments?: BytesLike[] + kzgProofs?: BytesLike[] + blobsData?: string[] +} + +export interface BlobInterface { + readonly blobVersionedHashes: PrefixedHexString[] // TODO why is this a string and not uint8array? + readonly blobs?: PrefixedHexString[] + readonly kzgCommitments?: PrefixedHexString[] + readonly kzgProofs?: PrefixedHexString[] + readonly maxFeePerBlobGas: bigint +} + +export interface Blob4844Interface extends FeeMarket1559Interface, BlobInterface {} + +// EIP7702 txs + +export type AuthorizationListFields = { + authorizationList?: AuthorizationListBytes | AuthorizationList | never +} + +export interface AuthorizationListInterface { + readonly authorizationList: AuthorizationListBytes +} + +export interface EOA7702Interface extends FeeMarket1559Interface, AuthorizationListInterface {} diff --git a/packages/tx/src/dataContainers/AccessList2930Container.ts b/packages/tx/src/dataContainers/AccessList2930Container.ts new file mode 100644 index 0000000000..64036d03a7 --- /dev/null +++ b/packages/tx/src/dataContainers/AccessList2930Container.ts @@ -0,0 +1,13 @@ +import { Address, bytesToBigInt, toBytes } from '@ethereumjs/util' + +import { Feature } from '../dataContainerTypes.js' + +import type { AccessListInterface } from '../dataContainerTypes.js' + +const accessListFeatures = new Set([ + Feature.ECDSASignable, + Feature.LegacyGasMarket, + Feature.AccessLists, +]) + +export class AccessList2930Container implements AccessListInterface {} diff --git a/packages/tx/src/dataContainers/EOA7702DataContainer.ts b/packages/tx/src/dataContainers/EOA7702DataContainer.ts new file mode 100644 index 0000000000..ed2adc8429 --- /dev/null +++ b/packages/tx/src/dataContainers/EOA7702DataContainer.ts @@ -0,0 +1,3 @@ +import type { EOA7702Interface } from '../dataContainerTypes.js' + +export class EOA7702DataContainer implements EOA7702Interface {} diff --git a/packages/tx/src/dataContainers/legacyContainer.ts b/packages/tx/src/dataContainers/legacyContainer.ts new file mode 100644 index 0000000000..8ff7712279 --- /dev/null +++ b/packages/tx/src/dataContainers/legacyContainer.ts @@ -0,0 +1,84 @@ +import { RLP } from '@ethereumjs/rlp' +import { Address, bytesToBigInt, toBytes } from '@ethereumjs/util' + +import { Feature } from '../dataContainerTypes.js' +import { TransactionType } from '../types.js' + +import type { + ContractCreationContainerInterface, + DefaultContainerInterface, + ECDSAContainerInterface, + LegacyContainerDataFields, + LegacyGasMarketInterface, + TxDataContainer, +} from '../dataContainerTypes.js' +import type { TxOptions } from '../types.js' + +const legacyFeatures = new Set([Feature.ECDSASignable, Feature.LegacyGasMarket]) + +export class LegacyDataContainer + implements + TxDataContainer, + DefaultContainerInterface, + ContractCreationContainerInterface, + ECDSAContainerInterface, + LegacyGasMarketInterface +{ + public type: number = TransactionType.Legacy // Legacy tx type + + // Tx data part (part of the RLP) + public readonly gasPrice: bigint + public readonly nonce: bigint + public readonly gasLimit: bigint + public readonly value: bigint + public readonly data: Uint8Array + // TODO fix type (how to do this? need to somehow override the interface) + public readonly to: Address | null + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + // TODO: verify if txOptions is necessary + // TODO (optimizing): for reach tx we auto-convert the input values to the target values (mostly bigints) + // Is this necessary? What if we need the unconverted values? Convert it on the fly? + constructor(txData: LegacyContainerDataFields, txOptions: TxOptions) { + const { nonce, gasLimit, to, value, data, v, r, s } = txData + + // Set the tx properties + const toB = toBytes(to === '' ? '0x' : to) + this.to = toB.length > 0 ? new Address(toB) : null + + this.nonce = bytesToBigInt(toBytes(nonce)) + this.gasLimit = bytesToBigInt(toBytes(gasLimit)) + this.value = bytesToBigInt(toBytes(value)) + this.data = toBytes(data === '' ? '0x' : data) + this.gasPrice = bytesToBigInt(toBytes(txData.gasPrice)) + + // Set signature values (if the tx is signed) + + const vB = toBytes(v) + const rB = toBytes(r) + const sB = toBytes(s) + this.v = vB.length > 0 ? bytesToBigInt(vB) : undefined + this.r = rB.length > 0 ? bytesToBigInt(rB) : undefined + this.s = sB.length > 0 ? bytesToBigInt(sB) : undefined + } + + raw() { + // TODO + return [] + } + serialize() { + return RLP.encode(this.raw()) + } + + supports(feature: Feature) { + return legacyFeatures.has(feature) + } + + toJSON() { + return {} + } +} diff --git a/packages/tx/src/dataContainers/template.ts b/packages/tx/src/dataContainers/template.ts new file mode 100644 index 0000000000..1153b47d3b --- /dev/null +++ b/packages/tx/src/dataContainers/template.ts @@ -0,0 +1,22 @@ +import { RLP } from '@ethereumjs/rlp' + +import type { TxDataContainer } from '../dataContainerTypes.js' +import type { NestedUint8Array } from '@ethereumjs/rlp' + +export abstract class TemplateDataContainer implements TxDataContainer { + type = -1 + + abstract raw(): NestedUint8Array + serialize() { + // Defaults to use RLP.encode + return RLP.encode(this.raw()) + } + + supports(/*feature: Feature*/) { + return false + } + + toJSON() { + return {} + } +} diff --git a/packages/tx/src/txWorker.ts b/packages/tx/src/txWorker.ts new file mode 100644 index 0000000000..3e81184375 --- /dev/null +++ b/packages/tx/src/txWorker.ts @@ -0,0 +1,74 @@ +/** + * TxWorker.ts: helper methods to extract relevant data from + */ + +import { BIGINT_0 } from '@ethereumjs/util' + +import { Feature } from './dataContainerTypes.js' +import { AccessLists } from './util.js' + +import type { + AccessListInterface, + DefaultContainerInterface, + TxDataContainer, +} from './dataContainerTypes.js' +import type { Common } from '@ethereumjs/common' + +/** + * Gets the intrinsic gas which is the minimal gas limit a tx should have to be valid + * @param tx + * @param common + */ +export function getIntrinsicGas( + tx: TxDataContainer & DefaultContainerInterface, + common: Common, +): bigint { + // NOTE: TxDataContainer & DefaultContainerInterface + // This is the tx data container class interface WITH the default tx params + let intrincisGas = BIGINT_0 + const txFee = common.param('txGas') + if (txFee) intrincisGas += txFee + if (common.gteHardfork('homestead') && (tx.to === undefined || tx.to === null)) { + const txCreationFee = common.param('txCreationGas') + if (txCreationFee) intrincisGas += txCreationFee + } + return intrincisGas + getDataGas(tx, common) +} + +/** + * Gets the data gas part of the tx, this consists of calldata, access lists and authority lists + * @param tx + * @param common + */ +export function getDataGas( + tx: TxDataContainer & DefaultContainerInterface, + common: Common, +): bigint { + // Side note: can also do this method without the entire tx container and just use `tx.data` instead as param? + const txDataZero = common.param('txDataZeroGas') + const txDataNonZero = common.param('txDataNonZeroGas') + + let cost = BIGINT_0 + for (let i = 0; i < tx.data.length; i++) { + tx.data[i] === 0 ? (cost += txDataZero) : (cost += txDataNonZero) + } + + if ((tx.to === undefined || tx.to === null) && common.isActivatedEIP(3860)) { + const dataLength = BigInt(Math.ceil(tx.data.length / 32)) + const initCodeCost = common.param('initCodeWordGas') * dataLength + cost += initCodeCost + } + + if (tx.supports(Feature.AccessLists)) { + // calculate access list cost + + // TODO fix why this cannot be cast like this + cost += BigInt(AccessLists.getDataGasEIP2930((tx).accessList, common)) + } + + if (tx.supports(Feature.EOACode)) { + // calculate authority list cost + } + + return cost +} From 6cebdd5cc1f7cbd11f6c727c9b2c9d43939eea5d Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 20:59:44 +0100 Subject: [PATCH 02/12] tx: type related work --- packages/tx/src/dataContainerTypes.ts | 64 +++++++++++++------ .../tx/src/dataContainers/legacyContainer.ts | 28 +++++--- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/packages/tx/src/dataContainerTypes.ts b/packages/tx/src/dataContainerTypes.ts index beb9f12cc9..c12cf988dd 100644 --- a/packages/tx/src/dataContainerTypes.ts +++ b/packages/tx/src/dataContainerTypes.ts @@ -33,7 +33,7 @@ export enum Feature { export type NestedUint8Array = (Uint8Array | NestedUint8Array)[] -export interface TxDataContainer { +export interface TxContainerMethods { supports(capability: Feature): boolean type: TransactionType @@ -80,7 +80,7 @@ export interface TxDataContainer { // Fields: used for the CONSTRUCTOR of the containers // Interface: used for the resulting constructor, so each param of the field is converted to that type before resulting in the container -export type DefaultContainerDataFields = { +export type DefaultFields = { nonce?: BigIntLike gasLimit?: BigIntLike to?: AddressLike @@ -89,37 +89,44 @@ export type DefaultContainerDataFields = { } export interface DefaultContainerInterface { - readonly gasPrice: bigint readonly nonce: bigint readonly gasLimit: bigint readonly value: bigint readonly data: Uint8Array - readonly to: Address | null // TODO: figure out how to handle this on txs which do not allow to:null (7702/4844) } -export type ECDSASignedContainerFields = { +export type CreateContractFields = { + to?: AddressLike | '' | null +} + +export interface CreateContractInterface { + to: Address | null +} + +// Equivalent of CreateContractDataFields but does not allow "null" or the empty string. +export type ToFields = { + to?: AddressLike +} + +export interface ToInterface { + to: Address +} + +export type ECDSAMaybeSignedFields = { v?: BigIntLike r?: BigIntLike s?: BigIntLike } -export interface ECDSAContainerInterface { +export type ECDSASignedFields = Required + +// Note: only container interface with values which could be undefined due to unsigned containers +export interface ECDSAMaybeSignedInterface { readonly v?: bigint readonly r?: bigint readonly s?: bigint } -// The container / tx data fields if the tx can create contracts (to `null`) -/*export type ContractCreationDataFields = { - to?: DefaultContainerDataFields['to'] | null | '' -}*/ - -/* -export interface ContractCreationContainerInterface { - to: DefaultContainerInterface['to'] | null -} -*/ - export type LegacyGasMarketFields = { gasPrice: BigIntLike } @@ -128,11 +135,25 @@ export interface LegacyGasMarketInterface { readonly gasPrice: bigint } -interface L1DefaultContainer - extends TxDataContainer, - DefaultContainerInterface, - ECDSAContainerInterface {} +export type TxConstructorFields = { + [TransactionType.Legacy]: DefaultFields & + CreateContractFields & + LegacyGasMarketFields & + ECDSASignedFields +} +export interface LegacyTxInterface + extends DefaultContainerInterface, + CreateContractInterface, + LegacyGasMarketInterface, + ECDSAMaybeSignedInterface {} + +export type ContainerInterface = { + [TransactionType.Legacy]: LegacyTxInterface +} + +// Below here TODO +/* export interface LegacyContainerInterface extends L1DefaultContainer, LegacyGasMarketInterface { // to: DefaultContainerInterface['to'] | null } @@ -205,3 +226,4 @@ export interface AuthorizationListInterface { } export interface EOA7702Interface extends FeeMarket1559Interface, AuthorizationListInterface {} +*/ diff --git a/packages/tx/src/dataContainers/legacyContainer.ts b/packages/tx/src/dataContainers/legacyContainer.ts index 8ff7712279..dd7f46a71f 100644 --- a/packages/tx/src/dataContainers/legacyContainer.ts +++ b/packages/tx/src/dataContainers/legacyContainer.ts @@ -5,24 +5,28 @@ import { Feature } from '../dataContainerTypes.js' import { TransactionType } from '../types.js' import type { - ContractCreationContainerInterface, + CreateContractInterface, DefaultContainerInterface, - ECDSAContainerInterface, - LegacyContainerDataFields, + ECDSAMaybeSignedInterface, + ECDSASignedFields, + ECDSASignedInterface, LegacyGasMarketInterface, - TxDataContainer, + TxConstructorFields, + TxContainerMethods, } from '../dataContainerTypes.js' import type { TxOptions } from '../types.js' +type TxType = TransactionType.Legacy + const legacyFeatures = new Set([Feature.ECDSASignable, Feature.LegacyGasMarket]) export class LegacyDataContainer implements - TxDataContainer, + TxContainerMethods, DefaultContainerInterface, - ContractCreationContainerInterface, - ECDSAContainerInterface, - LegacyGasMarketInterface + CreateContractInterface, + LegacyGasMarketInterface, + ECDSAMaybeSignedInterface { public type: number = TransactionType.Legacy // Legacy tx type @@ -32,7 +36,6 @@ export class LegacyDataContainer public readonly gasLimit: bigint public readonly value: bigint public readonly data: Uint8Array - // TODO fix type (how to do this? need to somehow override the interface) public readonly to: Address | null // Props only for signed txs @@ -43,7 +46,7 @@ export class LegacyDataContainer // TODO: verify if txOptions is necessary // TODO (optimizing): for reach tx we auto-convert the input values to the target values (mostly bigints) // Is this necessary? What if we need the unconverted values? Convert it on the fly? - constructor(txData: LegacyContainerDataFields, txOptions: TxOptions) { + constructor(txData: TxConstructorFields[TxType], txOptions: TxOptions) { const { nonce, gasLimit, to, value, data, v, r, s } = txData // Set the tx properties @@ -81,4 +84,9 @@ export class LegacyDataContainer toJSON() { return {} } + + sign(privateKey: Uint8Array): LegacyDataContainer & ECDSASignedInterface { + // TODO + return this as LegacyDataContainer & ECDSASignedInterface // Type return value to have v/r/s set + } } From 852aa98b2e0ce3f4213629b730578231c4383ac2 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 22:16:23 +0100 Subject: [PATCH 03/12] tx: make data conatiner types more complete --- packages/tx/src/dataContainerTypes.ts | 71 +++++++++++++++------------ 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/packages/tx/src/dataContainerTypes.ts b/packages/tx/src/dataContainerTypes.ts index c12cf988dd..bf45212aa0 100644 --- a/packages/tx/src/dataContainerTypes.ts +++ b/packages/tx/src/dataContainerTypes.ts @@ -127,6 +127,10 @@ export interface ECDSAMaybeSignedInterface { readonly s?: bigint } +type ECDSASignedInterfaceType = Required + +export interface ECDSASignedInterface extends ECDSASignedInterfaceType {} + export type LegacyGasMarketFields = { gasPrice: BigIntLike } @@ -135,13 +139,6 @@ export interface LegacyGasMarketInterface { readonly gasPrice: bigint } -export type TxConstructorFields = { - [TransactionType.Legacy]: DefaultFields & - CreateContractFields & - LegacyGasMarketFields & - ECDSASignedFields -} - export interface LegacyTxInterface extends DefaultContainerInterface, CreateContractInterface, @@ -152,12 +149,7 @@ export type ContainerInterface = { [TransactionType.Legacy]: LegacyTxInterface } -// Below here TODO -/* -export interface LegacyContainerInterface extends L1DefaultContainer, LegacyGasMarketInterface { - // to: DefaultContainerInterface['to'] | null -} - +// EIP-2930 (Access Lists) related types and interfaces export type ChainIdFields = { chainId?: BigIntLike } @@ -167,22 +159,18 @@ export interface ChainIdInterface { } export type AccessListFields = { - accessList?: AccessListBytes | AccessList | null + accessList?: AccessListBytes | AccessList } +export type EIP2930Fields = ChainIdFields & AccessListFields + export interface AccessListInterface { accessList: AccessListBytes } -interface L1_2930Interface extends L1DefaultContainer, ChainIdInterface, AccessListInterface {} +export interface AccessList2930Interface extends ChainIdInterface, AccessListInterface {} -export interface AccessList2930ContainerInterface - extends L1_2930Interface, - LegacyGasMarketInterface {} - -// interface AccessList2930Interface: L1DefaultFields, ContractCreationDataFields, LegacyGasMarket, ChainId, AccessList - -// EIP1559 txs +// EIP-1559 (Fee market) related types and interfaces export type FeeMarketFields = { maxPriorityFeePerGas?: BigIntLike maxFeePerGas?: BigIntLike @@ -193,16 +181,14 @@ export interface FeeMarketInterface { readonly maxFeePerGas: bigint } -export interface FeeMarket1559Interface extends L1_2930Interface, FeeMarketInterface {} - -// EIP4844 txs +// EIP-4844 (Shard blob transactions) related types and fields export type BlobFields = { blobVersionedHashes?: BytesLike[] maxFeePerBlobGas?: BigIntLike blobs?: BytesLike[] kzgCommitments?: BytesLike[] kzgProofs?: BytesLike[] - blobsData?: string[] + blobsData?: string[] // TODO why is this string and not something like PrefixedHexString? } export interface BlobInterface { @@ -213,10 +199,7 @@ export interface BlobInterface { readonly maxFeePerBlobGas: bigint } -export interface Blob4844Interface extends FeeMarket1559Interface, BlobInterface {} - -// EIP7702 txs - +// EIP-7702 (EOA code transactions) related types and fields export type AuthorizationListFields = { authorizationList?: AuthorizationListBytes | AuthorizationList | never } @@ -225,5 +208,29 @@ export interface AuthorizationListInterface { readonly authorizationList: AuthorizationListBytes } -export interface EOA7702Interface extends FeeMarket1559Interface, AuthorizationListInterface {} -*/ +// Below here: helper types +// Helper type which is common on the txs: +type DefaultFieldsMaybeSigned = DefaultFields & ECDSAMaybeSignedFields + +// Helper type for the constructor fields of the txs +export type TxConstructorFields = { + [TransactionType.Legacy]: DefaultFieldsMaybeSigned & CreateContractFields & LegacyGasMarketFields + [TransactionType.AccessListEIP2930]: TxConstructorFields[TransactionType.Legacy] & EIP2930Fields + [TransactionType.FeeMarketEIP1559]: Exclude< + TxConstructorFields[TransactionType.AccessListEIP2930], + LegacyGasMarketFields + > & + FeeMarketFields + [TransactionType.BlobEIP4844]: Exclude< + TxConstructorFields[TransactionType.FeeMarketEIP1559], + CreateContractFields + > & + ToFields & + BlobFields + [TransactionType.EOACodeEIP7702]: Exclude< + TxConstructorFields[TransactionType.FeeMarketEIP1559], + CreateContractFields + > & + ToFields & + AuthorizationListFields +} From 40ba4518252e41b4b21a243853a4bb34315fb3c1 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:10:32 +0100 Subject: [PATCH 04/12] tx: fixup data type --- packages/tx/src/dataContainerTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tx/src/dataContainerTypes.ts b/packages/tx/src/dataContainerTypes.ts index bf45212aa0..c0278af850 100644 --- a/packages/tx/src/dataContainerTypes.ts +++ b/packages/tx/src/dataContainerTypes.ts @@ -132,7 +132,7 @@ type ECDSASignedInterfaceType = Required export interface ECDSASignedInterface extends ECDSASignedInterfaceType {} export type LegacyGasMarketFields = { - gasPrice: BigIntLike + gasPrice?: BigIntLike } export interface LegacyGasMarketInterface { From 1aa2c3633cca3f6c621b7154c56374d0de7b056e Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:10:40 +0100 Subject: [PATCH 05/12] tx: add 2930 container --- .../dataContainers/AccessList2930Container.ts | 100 +++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/packages/tx/src/dataContainers/AccessList2930Container.ts b/packages/tx/src/dataContainers/AccessList2930Container.ts index 64036d03a7..9e9af2897f 100644 --- a/packages/tx/src/dataContainers/AccessList2930Container.ts +++ b/packages/tx/src/dataContainers/AccessList2930Container.ts @@ -1,8 +1,23 @@ +import { RLP } from '@ethereumjs/rlp' import { Address, bytesToBigInt, toBytes } from '@ethereumjs/util' import { Feature } from '../dataContainerTypes.js' +import { TransactionType } from '../types.js' +import { AccessLists } from '../util.js' -import type { AccessListInterface } from '../dataContainerTypes.js' +import type { + AccessList2930Interface, + CreateContractInterface, + DefaultContainerInterface, + ECDSAMaybeSignedInterface, + ECDSASignedInterface, + LegacyGasMarketInterface, + TxConstructorFields, + TxContainerMethods, +} from '../dataContainerTypes.js' +import type { AccessListBytes, TxOptions } from '../types.js' + +type TxType = TransactionType.AccessListEIP2930 const accessListFeatures = new Set([ Feature.ECDSASignable, @@ -10,4 +25,85 @@ const accessListFeatures = new Set([ Feature.AccessLists, ]) -export class AccessList2930Container implements AccessListInterface {} +export class AccessList2930Container + implements + TxContainerMethods, + DefaultContainerInterface, + CreateContractInterface, + LegacyGasMarketInterface, + ECDSAMaybeSignedInterface, + AccessList2930Interface +{ + public type: number = TransactionType.AccessListEIP2930 // Legacy tx type + + // Tx data part (part of the RLP) + public readonly gasPrice: bigint + public readonly nonce: bigint + public readonly gasLimit: bigint + public readonly value: bigint + public readonly data: Uint8Array + public readonly to: Address | null + public readonly accessList: AccessListBytes + public readonly chainId: bigint + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + constructor(txData: TxConstructorFields[TxType], txOptions: TxOptions) { + const { nonce, gasLimit, to, value, data, v, r, s } = txData + + // Set the tx properties + const toB = toBytes(to === '' ? '0x' : to) + this.to = toB.length > 0 ? new Address(toB) : null + + this.nonce = bytesToBigInt(toBytes(nonce)) + this.gasLimit = bytesToBigInt(toBytes(gasLimit)) + this.value = bytesToBigInt(toBytes(value)) + this.data = toBytes(data === '' ? '0x' : data) + this.gasPrice = bytesToBigInt(toBytes(txData.gasPrice)) + + // Set signature values (if the tx is signed) + + const vB = toBytes(v) + const rB = toBytes(r) + const sB = toBytes(s) + this.v = vB.length > 0 ? bytesToBigInt(vB) : undefined + this.r = rB.length > 0 ? bytesToBigInt(rB) : undefined + this.s = sB.length > 0 ? bytesToBigInt(sB) : undefined + + const { chainId, accessList } = txData + + // NOTE: previously there was a check against common's chainId, this is not here + // Common is not available in the tx container + // Likely, we should now check this chain id when signing the tx (to prevent people signing on the wrong chain) + this.chainId = bytesToBigInt(toBytes(chainId)) + + const accessListData = AccessLists.getAccessListData(accessList ?? []) + this.accessList = accessListData.accessList + } + + raw() { + // TODO + return [new Uint8Array(), new Uint8Array()] + } + serialize() { + return RLP.encode(this.raw()) + } + + supports(feature: Feature) { + return accessListFeatures.has(feature) + } + + toJSON() { + return {} + } + + // TODO likely add common here: should check against the chain id in this container to prevent + // signing against the wrong chain id + sign(privateKey: Uint8Array): AccessList2930Container & ECDSASignedInterface { + // TODO + return this as AccessList2930Container & ECDSASignedInterface // Type return value to have v/r/s set + } +} From 13f984ddd2f8cedb7e2b6668711f4aa06e3a97d1 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:10:47 +0100 Subject: [PATCH 06/12] tx: lint legacy container --- packages/tx/src/dataContainers/legacyContainer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tx/src/dataContainers/legacyContainer.ts b/packages/tx/src/dataContainers/legacyContainer.ts index dd7f46a71f..fdbf6dc636 100644 --- a/packages/tx/src/dataContainers/legacyContainer.ts +++ b/packages/tx/src/dataContainers/legacyContainer.ts @@ -8,7 +8,6 @@ import type { CreateContractInterface, DefaultContainerInterface, ECDSAMaybeSignedInterface, - ECDSASignedFields, ECDSASignedInterface, LegacyGasMarketInterface, TxConstructorFields, From 84b00795542aceaf94f11349a4b40ad0d77c72f4 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:11:29 +0100 Subject: [PATCH 07/12] tx: rename 2930 container --- .../{AccessList2930Container.ts => accessList2930Container.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/tx/src/dataContainers/{AccessList2930Container.ts => accessList2930Container.ts} (100%) diff --git a/packages/tx/src/dataContainers/AccessList2930Container.ts b/packages/tx/src/dataContainers/accessList2930Container.ts similarity index 100% rename from packages/tx/src/dataContainers/AccessList2930Container.ts rename to packages/tx/src/dataContainers/accessList2930Container.ts From b2f670994e37bc1998d8dcd048fb05995a51e691 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:24:42 +0100 Subject: [PATCH 08/12] tx: fix constructor types --- packages/tx/src/dataContainerTypes.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/tx/src/dataContainerTypes.ts b/packages/tx/src/dataContainerTypes.ts index c0278af850..0c36ddb299 100644 --- a/packages/tx/src/dataContainerTypes.ts +++ b/packages/tx/src/dataContainerTypes.ts @@ -216,20 +216,17 @@ type DefaultFieldsMaybeSigned = DefaultFields & ECDSAMaybeSignedFields export type TxConstructorFields = { [TransactionType.Legacy]: DefaultFieldsMaybeSigned & CreateContractFields & LegacyGasMarketFields [TransactionType.AccessListEIP2930]: TxConstructorFields[TransactionType.Legacy] & EIP2930Fields - [TransactionType.FeeMarketEIP1559]: Exclude< + [TransactionType.FeeMarketEIP1559]: Omit< TxConstructorFields[TransactionType.AccessListEIP2930], - LegacyGasMarketFields + 'gasPrice' > & FeeMarketFields - [TransactionType.BlobEIP4844]: Exclude< - TxConstructorFields[TransactionType.FeeMarketEIP1559], - CreateContractFields - > & + [TransactionType.BlobEIP4844]: Omit & ToFields & BlobFields - [TransactionType.EOACodeEIP7702]: Exclude< + [TransactionType.EOACodeEIP7702]: Omit< TxConstructorFields[TransactionType.FeeMarketEIP1559], - CreateContractFields + 'to' > & ToFields & AuthorizationListFields From 4107947bc1378f65df6bc5d36eb34c19c8515dd7 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:39:04 +0100 Subject: [PATCH 09/12] tx: add 1559 container --- .../dataContainers/feeMarket1559Container.ts | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 packages/tx/src/dataContainers/feeMarket1559Container.ts diff --git a/packages/tx/src/dataContainers/feeMarket1559Container.ts b/packages/tx/src/dataContainers/feeMarket1559Container.ts new file mode 100644 index 0000000000..102836a3aa --- /dev/null +++ b/packages/tx/src/dataContainers/feeMarket1559Container.ts @@ -0,0 +1,111 @@ +import { RLP } from '@ethereumjs/rlp' +import { Address, bytesToBigInt, toBytes } from '@ethereumjs/util' + +import { Feature } from '../dataContainerTypes.js' +import { TransactionType } from '../types.js' +import { AccessLists } from '../util.js' + +import type { + AccessList2930Interface, + CreateContractInterface, + DefaultContainerInterface, + ECDSAMaybeSignedInterface, + ECDSASignedInterface, + FeeMarketInterface, + TxConstructorFields, + TxContainerMethods, +} from '../dataContainerTypes.js' +import type { AccessListBytes, TxOptions } from '../types.js' + +type TxType = TransactionType.FeeMarketEIP1559 + +const feeMarketFeatures = new Set([ + Feature.ECDSASignable, + Feature.FeeMarket, + Feature.AccessLists, +]) + +export class FeeMarket1559Container + implements + TxContainerMethods, + DefaultContainerInterface, + CreateContractInterface, + FeeMarketInterface, + ECDSAMaybeSignedInterface, + AccessList2930Interface +{ + public type: number = TransactionType.FeeMarketEIP1559 // Legacy tx type + + // Tx data part (part of the RLP) + public readonly maxFeePerGas: bigint + public readonly maxPriorityFeePerGas: bigint + public readonly nonce: bigint + public readonly gasLimit: bigint + public readonly value: bigint + public readonly data: Uint8Array + public readonly to: Address | null + public readonly accessList: AccessListBytes + public readonly chainId: bigint + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + constructor(txData: TxConstructorFields[TxType], txOptions: TxOptions) { + const { nonce, gasLimit, to, value, data, v, r, s, maxPriorityFeePerGas, maxFeePerGas } = txData + + // Set the tx properties + const toB = toBytes(to === '' ? '0x' : to) + this.to = toB.length > 0 ? new Address(toB) : null + + this.nonce = bytesToBigInt(toBytes(nonce)) + this.gasLimit = bytesToBigInt(toBytes(gasLimit)) + this.value = bytesToBigInt(toBytes(value)) + this.data = toBytes(data === '' ? '0x' : data) + this.maxFeePerGas = bytesToBigInt(toBytes(maxFeePerGas)) + this.maxPriorityFeePerGas = bytesToBigInt(toBytes(maxPriorityFeePerGas)) + + // Set signature values (if the tx is signed) + + const vB = toBytes(v) + const rB = toBytes(r) + const sB = toBytes(s) + this.v = vB.length > 0 ? bytesToBigInt(vB) : undefined + this.r = rB.length > 0 ? bytesToBigInt(rB) : undefined + this.s = sB.length > 0 ? bytesToBigInt(sB) : undefined + + const { chainId, accessList } = txData + + // NOTE: previously there was a check against common's chainId, this is not here + // Common is not available in the tx container + // Likely, we should now check this chain id when signing the tx (to prevent people signing on the wrong chain) + this.chainId = bytesToBigInt(toBytes(chainId)) + + const accessListData = AccessLists.getAccessListData(accessList ?? []) + this.accessList = accessListData.accessList + } + + raw() { + // TODO + return [new Uint8Array(), new Uint8Array()] + } + serialize() { + return RLP.encode(this.raw()) + } + + supports(feature: Feature) { + return feeMarketFeatures.has(feature) + } + + toJSON() { + return {} + } + + // TODO likely add common here: should check against the chain id in this container to prevent + // signing against the wrong chain id + sign(privateKey: Uint8Array): FeeMarket1559Container & ECDSASignedInterface { + // TODO + return this as FeeMarket1559Container & ECDSASignedInterface // Type return value to have v/r/s set + } +} From 64a63a3a716c3c7ef18197556d7756b66a62ba2f Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:39:13 +0100 Subject: [PATCH 10/12] tx: add blob type --- packages/tx/src/dataContainerTypes.ts | 1 + .../src/dataContainers/blob4844Container.ts | 185 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 packages/tx/src/dataContainers/blob4844Container.ts diff --git a/packages/tx/src/dataContainerTypes.ts b/packages/tx/src/dataContainerTypes.ts index 0c36ddb299..9e85c3c3a9 100644 --- a/packages/tx/src/dataContainerTypes.ts +++ b/packages/tx/src/dataContainerTypes.ts @@ -29,6 +29,7 @@ export enum Feature { AccessLists = 'AccessLists', EOACode = 'EOACode', + Blobs = 'Blobs', } export type NestedUint8Array = (Uint8Array | NestedUint8Array)[] diff --git a/packages/tx/src/dataContainers/blob4844Container.ts b/packages/tx/src/dataContainers/blob4844Container.ts new file mode 100644 index 0000000000..621a70ba66 --- /dev/null +++ b/packages/tx/src/dataContainers/blob4844Container.ts @@ -0,0 +1,185 @@ +import { RLP } from '@ethereumjs/rlp' +import { Address, TypeOutput, bytesToBigInt, toBytes, toType } from '@ethereumjs/util' + +import { Feature } from '../dataContainerTypes.js' +import { TransactionType } from '../types.js' +import { AccessLists } from '../util.js' + +import type { + AccessList2930Interface, + BlobInterface, + DefaultContainerInterface, + ECDSAMaybeSignedInterface, + ECDSASignedInterface, + FeeMarketInterface, + ToInterface, + TxConstructorFields, + TxContainerMethods, +} from '../dataContainerTypes.js' +import type { AccessListBytes, TxOptions } from '../types.js' +import type { PrefixedHexString } from '@ethereumjs/util' + +type TxType = TransactionType.BlobEIP4844 + +const feeMarketFeatures = new Set([ + Feature.ECDSASignable, + Feature.FeeMarket, + Feature.AccessLists, + Feature.Blobs, +]) + +export class Blob4844Container + implements + TxContainerMethods, + DefaultContainerInterface, + ToInterface, + FeeMarketInterface, + ECDSAMaybeSignedInterface, + AccessList2930Interface, + BlobInterface +{ + public type: number = TransactionType.BlobEIP4844 // Legacy tx type + + // Tx data part (part of the RLP) + public readonly maxFeePerGas: bigint + public readonly maxPriorityFeePerGas: bigint + public readonly maxFeePerBlobGas: bigint + public readonly nonce: bigint + public readonly gasLimit: bigint + public readonly value: bigint + public readonly data: Uint8Array + public readonly to: Address + public readonly accessList: AccessListBytes + public readonly chainId: bigint + public readonly blobVersionedHashes: PrefixedHexString[] + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + // Blob related properties + + public readonly blobs?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format + public readonly kzgCommitments?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format + public readonly kzgProofs?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format + + constructor(txData: TxConstructorFields[TxType], txOptions: TxOptions) { + const { + nonce, + gasLimit, + to, + value, + data, + v, + r, + s, + maxPriorityFeePerGas, + maxFeePerGas, + maxFeePerBlobGas, + } = txData + + // Set the tx properties + const toB = toBytes(to) + if (toB.length === 0) { + // TODO: better check + throw new Error('The to: field should be defined') + } + this.to = new Address(toB) + + this.nonce = bytesToBigInt(toBytes(nonce)) + this.gasLimit = bytesToBigInt(toBytes(gasLimit)) + this.value = bytesToBigInt(toBytes(value)) + this.data = toBytes(data === '' ? '0x' : data) + this.maxFeePerGas = bytesToBigInt(toBytes(maxFeePerGas)) + this.maxPriorityFeePerGas = bytesToBigInt(toBytes(maxPriorityFeePerGas)) + + // Set signature values (if the tx is signed) + + const vB = toBytes(v) + const rB = toBytes(r) + const sB = toBytes(s) + this.v = vB.length > 0 ? bytesToBigInt(vB) : undefined + this.r = rB.length > 0 ? bytesToBigInt(rB) : undefined + this.s = sB.length > 0 ? bytesToBigInt(sB) : undefined + + const { chainId, accessList } = txData + + // NOTE: previously there was a check against common's chainId, this is not here + // Common is not available in the tx container + // Likely, we should now check this chain id when signing the tx (to prevent people signing on the wrong chain) + this.chainId = bytesToBigInt(toBytes(chainId)) + + const accessListData = AccessLists.getAccessListData(accessList ?? []) + this.accessList = accessListData.accessList + + // Blob related stuff here + this.maxFeePerBlobGas = bytesToBigInt( + toBytes((maxFeePerBlobGas ?? '') === '' ? '0x' : maxFeePerBlobGas), + ) + + this.blobVersionedHashes = (txData.blobVersionedHashes ?? []).map((vh) => + toType(vh, TypeOutput.PrefixedHexString), + ) + + /* Validation, should be done elsewhere + for (const hash of this.blobVersionedHashes) { + if (hash.length !== 66) { + // 66 is the length of a 32 byte hash as a PrefixedHexString + const msg = Legacy.errorMsg(this, 'versioned hash is invalid length') + throw new Error(msg) + } + if (BigInt(parseInt(hash.slice(2, 4))) !== this.common.param('blobCommitmentVersionKzg')) { + // We check the first "byte" of the hash (starts at position 2 since hash is a PrefixedHexString) + const msg = Legacy.errorMsg( + this, + 'versioned hash does not start with KZG commitment version', + ) + throw new Error(msg) + } + } + if (this.blobVersionedHashes.length > LIMIT_BLOBS_PER_TX) { + const msg = Legacy.errorMsg(this, `tx can contain at most ${LIMIT_BLOBS_PER_TX} blobs`) + throw new Error(msg) + } else if (this.blobVersionedHashes.length === 0) { + const msg = Legacy.errorMsg(this, `tx should contain at least one blob`) + throw new Error(msg) + } + if (this.to === undefined) { + const msg = Legacy.errorMsg( + this, + `tx should have a "to" field and cannot be used to create contracts`, + ) + throw new Error(msg) + } */ + + this.blobs = txData.blobs?.map((blob) => toType(blob, TypeOutput.PrefixedHexString)) + this.kzgCommitments = txData.kzgCommitments?.map((commitment) => + toType(commitment, TypeOutput.PrefixedHexString), + ) + this.kzgProofs = txData.kzgProofs?.map((proof) => toType(proof, TypeOutput.PrefixedHexString)) + } + + raw() { + // TODO + return [new Uint8Array(), new Uint8Array()] + } + serialize() { + return RLP.encode(this.raw()) + } + + supports(feature: Feature) { + return feeMarketFeatures.has(feature) + } + + toJSON() { + return {} + } + + // TODO likely add common here: should check against the chain id in this container to prevent + // signing against the wrong chain id + sign(privateKey: Uint8Array): Blob4844Container & ECDSASignedInterface { + // TODO + return this as Blob4844Container & ECDSASignedInterface // Type return value to have v/r/s set + } +} From ee41dd9ccc66c567e8e45bf831ee5443df22e937 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:46:56 +0100 Subject: [PATCH 11/12] tx: add 7702 eoa container --- .../dataContainers/EOA7702DataContainer.ts | 126 +++++++++++++++++- 1 file changed, 124 insertions(+), 2 deletions(-) diff --git a/packages/tx/src/dataContainers/EOA7702DataContainer.ts b/packages/tx/src/dataContainers/EOA7702DataContainer.ts index ed2adc8429..805b7a0b47 100644 --- a/packages/tx/src/dataContainers/EOA7702DataContainer.ts +++ b/packages/tx/src/dataContainers/EOA7702DataContainer.ts @@ -1,3 +1,125 @@ -import type { EOA7702Interface } from '../dataContainerTypes.js' +import { RLP } from '@ethereumjs/rlp' +import { Address, bytesToBigInt, toBytes } from '@ethereumjs/util' -export class EOA7702DataContainer implements EOA7702Interface {} +import { Feature } from '../dataContainerTypes.js' +import { TransactionType } from '../types.js' +import { AccessLists, AuthorizationLists } from '../util.js' + +import type { + AccessList2930Interface, + AuthorizationListInterface, + DefaultContainerInterface, + ECDSAMaybeSignedInterface, + ECDSASignedInterface, + FeeMarketInterface, + ToInterface, + TxConstructorFields, + TxContainerMethods, +} from '../dataContainerTypes.js' +import type { AccessListBytes, AuthorizationListBytes, TxOptions } from '../types.js' + +type TxType = TransactionType.EOACodeEIP7702 + +const feeMarketFeatures = new Set([ + Feature.ECDSASignable, + Feature.FeeMarket, + Feature.AccessLists, + Feature.EOACode, +]) + +export class EOACode7702Container + implements + TxContainerMethods, + DefaultContainerInterface, + ToInterface, + FeeMarketInterface, + ECDSAMaybeSignedInterface, + AccessList2930Interface, + AuthorizationListInterface +{ + public type: number = TransactionType.EOACodeEIP7702 // Legacy tx type + + // Tx data part (part of the RLP) + public readonly maxFeePerGas: bigint + public readonly maxPriorityFeePerGas: bigint + public readonly nonce: bigint + public readonly gasLimit: bigint + public readonly value: bigint + public readonly data: Uint8Array + public readonly to: Address + public readonly accessList: AccessListBytes + public readonly authorizationList: AuthorizationListBytes + public readonly chainId: bigint + + // Props only for signed txs + public readonly v?: bigint + public readonly r?: bigint + public readonly s?: bigint + + constructor(txData: TxConstructorFields[TxType], txOptions: TxOptions) { + const { nonce, gasLimit, to, value, data, v, r, s, maxPriorityFeePerGas, maxFeePerGas } = txData + + // Set the tx properties + const toB = toBytes(to) + if (toB.length === 0) { + // TODO: better check + throw new Error('The to: field should be defined') + } + this.to = new Address(toB) + + this.nonce = bytesToBigInt(toBytes(nonce)) + this.gasLimit = bytesToBigInt(toBytes(gasLimit)) + this.value = bytesToBigInt(toBytes(value)) + this.data = toBytes(data === '' ? '0x' : data) + this.maxFeePerGas = bytesToBigInt(toBytes(maxFeePerGas)) + this.maxPriorityFeePerGas = bytesToBigInt(toBytes(maxPriorityFeePerGas)) + + // Set signature values (if the tx is signed) + + const vB = toBytes(v) + const rB = toBytes(r) + const sB = toBytes(s) + this.v = vB.length > 0 ? bytesToBigInt(vB) : undefined + this.r = rB.length > 0 ? bytesToBigInt(rB) : undefined + this.s = sB.length > 0 ? bytesToBigInt(sB) : undefined + + const { chainId, accessList, authorizationList } = txData + + // NOTE: previously there was a check against common's chainId, this is not here + // Common is not available in the tx container + // Likely, we should now check this chain id when signing the tx (to prevent people signing on the wrong chain) + this.chainId = bytesToBigInt(toBytes(chainId)) + + const accessListData = AccessLists.getAccessListData(accessList ?? []) + this.accessList = accessListData.accessList + + // Populate the authority list fields + const authorizationListData = AuthorizationLists.getAuthorizationListData( + authorizationList ?? [], + ) + this.authorizationList = authorizationListData.authorizationList + } + + raw() { + // TODO + return [new Uint8Array(), new Uint8Array()] + } + serialize() { + return RLP.encode(this.raw()) + } + + supports(feature: Feature) { + return feeMarketFeatures.has(feature) + } + + toJSON() { + return {} + } + + // TODO likely add common here: should check against the chain id in this container to prevent + // signing against the wrong chain id + sign(privateKey: Uint8Array): EOACode7702Container & ECDSASignedInterface { + // TODO + return this as EOACode7702Container & ECDSASignedInterface // Type return value to have v/r/s set + } +} From 545ec34f84f9f0e44964d9dcd3e61670600b9c4f Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 11 Nov 2024 23:51:12 +0100 Subject: [PATCH 12/12] tx: add/fix txworker methods --- packages/tx/src/txWorker.ts | 61 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/tx/src/txWorker.ts b/packages/tx/src/txWorker.ts index 3e81184375..8310cf66fa 100644 --- a/packages/tx/src/txWorker.ts +++ b/packages/tx/src/txWorker.ts @@ -5,43 +5,24 @@ import { BIGINT_0 } from '@ethereumjs/util' import { Feature } from './dataContainerTypes.js' -import { AccessLists } from './util.js' +import { AccessLists, AuthorizationLists } from './util.js' import type { AccessListInterface, + AuthorizationListInterface, + CreateContractInterface, DefaultContainerInterface, - TxDataContainer, + TxContainerMethods, } from './dataContainerTypes.js' import type { Common } from '@ethereumjs/common' -/** - * Gets the intrinsic gas which is the minimal gas limit a tx should have to be valid - * @param tx - * @param common - */ -export function getIntrinsicGas( - tx: TxDataContainer & DefaultContainerInterface, - common: Common, -): bigint { - // NOTE: TxDataContainer & DefaultContainerInterface - // This is the tx data container class interface WITH the default tx params - let intrincisGas = BIGINT_0 - const txFee = common.param('txGas') - if (txFee) intrincisGas += txFee - if (common.gteHardfork('homestead') && (tx.to === undefined || tx.to === null)) { - const txCreationFee = common.param('txCreationGas') - if (txCreationFee) intrincisGas += txCreationFee - } - return intrincisGas + getDataGas(tx, common) -} - /** * Gets the data gas part of the tx, this consists of calldata, access lists and authority lists * @param tx * @param common */ export function getDataGas( - tx: TxDataContainer & DefaultContainerInterface, + tx: TxContainerMethods & DefaultContainerInterface & CreateContractInterface, common: Common, ): bigint { // Side note: can also do this method without the entire tx container and just use `tx.data` instead as param? @@ -60,15 +41,37 @@ export function getDataGas( } if (tx.supports(Feature.AccessLists)) { - // calculate access list cost - - // TODO fix why this cannot be cast like this - cost += BigInt(AccessLists.getDataGasEIP2930((tx).accessList, common)) + // TODO: figure out how to get rid of the "unknown" + // (Likely: mark a type with all tx interfaces and mark them all as "optional") + const eip2930tx = tx as unknown as AccessListInterface + cost += BigInt(AccessLists.getDataGasEIP2930(eip2930tx.accessList, common)) } if (tx.supports(Feature.EOACode)) { - // calculate authority list cost + const eip7702tx = tx as unknown as AuthorizationListInterface + cost += BigInt(AuthorizationLists.getDataGasEIP7702(eip7702tx.authorizationList, common)) } return cost } + +/** + * Gets the intrinsic gas which is the minimal gas limit a tx should have to be valid + * @param tx + * @param common + */ +export function getIntrinsicGas( + tx: TxContainerMethods & DefaultContainerInterface & CreateContractInterface, + common: Common, +): bigint { + // NOTE: TxDataContainer & DefaultContainerInterface + // This is the tx data container class interface WITH the default tx params + let intrincisGas = BIGINT_0 + const txFee = common.param('txGas') + if (txFee) intrincisGas += txFee + if (common.gteHardfork('homestead') && (tx.to === undefined || tx.to === null)) { + const txCreationFee = common.param('txCreationGas') + if (txCreationFee) intrincisGas += txCreationFee + } + return intrincisGas + getDataGas(tx, common) +}