Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tx: implement tx containers and tx workers [WIP] [Experimental] #3791

Closed
wants to merge 12 commits into from
234 changes: 234 additions & 0 deletions packages/tx/src/dataContainerTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
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',
Blobs = 'Blobs',
}

export type NestedUint8Array = (Uint8Array | NestedUint8Array)[]

export interface TxContainerMethods {
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 DefaultFields = {
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 nonce: bigint
readonly gasLimit: bigint
readonly value: bigint
readonly data: Uint8Array
}

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 type ECDSASignedFields = Required<ECDSAMaybeSignedFields>

// 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
}

type ECDSASignedInterfaceType = Required<ECDSAMaybeSignedInterface>

export interface ECDSASignedInterface extends ECDSASignedInterfaceType {}

export type LegacyGasMarketFields = {
gasPrice?: BigIntLike
}

export interface LegacyGasMarketInterface {
readonly gasPrice: bigint
}

export interface LegacyTxInterface
extends DefaultContainerInterface,
CreateContractInterface,
LegacyGasMarketInterface,
ECDSAMaybeSignedInterface {}

export type ContainerInterface = {
[TransactionType.Legacy]: LegacyTxInterface
}

// EIP-2930 (Access Lists) related types and interfaces
export type ChainIdFields = {
chainId?: BigIntLike
}

export interface ChainIdInterface {
chainId: bigint
}

export type AccessListFields = {
accessList?: AccessListBytes | AccessList
}

export type EIP2930Fields = ChainIdFields & AccessListFields

export interface AccessListInterface {
accessList: AccessListBytes
}

export interface AccessList2930Interface extends ChainIdInterface, AccessListInterface {}

// EIP-1559 (Fee market) related types and interfaces
export type FeeMarketFields = {
maxPriorityFeePerGas?: BigIntLike
maxFeePerGas?: BigIntLike
}

export interface FeeMarketInterface {
readonly maxPriorityFeePerGas: bigint
readonly maxFeePerGas: bigint
}

// EIP-4844 (Shard blob transactions) related types and fields
export type BlobFields = {
blobVersionedHashes?: BytesLike[]
maxFeePerBlobGas?: BigIntLike
blobs?: BytesLike[]
kzgCommitments?: BytesLike[]
kzgProofs?: BytesLike[]
blobsData?: string[] // TODO why is this string and not something like PrefixedHexString?
}

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
}

// EIP-7702 (EOA code transactions) related types and fields
export type AuthorizationListFields = {
authorizationList?: AuthorizationListBytes | AuthorizationList | never
}

export interface AuthorizationListInterface {
readonly authorizationList: AuthorizationListBytes
}

// 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]: Omit<
TxConstructorFields[TransactionType.AccessListEIP2930],
'gasPrice'
> &
FeeMarketFields
[TransactionType.BlobEIP4844]: Omit<TxConstructorFields[TransactionType.FeeMarketEIP1559], 'to'> &
ToFields &
BlobFields
[TransactionType.EOACodeEIP7702]: Omit<
TxConstructorFields[TransactionType.FeeMarketEIP1559],
'to'
> &
ToFields &
AuthorizationListFields
}
125 changes: 125 additions & 0 deletions packages/tx/src/dataContainers/EOA7702DataContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { RLP } from '@ethereumjs/rlp'
import { Address, bytesToBigInt, toBytes } from '@ethereumjs/util'

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>([
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
}
}
Loading
Loading