From d035da2325c2dd1f042abb65b0d427d1aa387866 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 20 Nov 2024 10:34:38 +0100 Subject: [PATCH 1/5] tx: add explicit types --- packages/tx/src/types.ts | 160 ++++++++++++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 20 deletions(-) diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index 16f442da16c..cf3d26599e7 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -1,4 +1,4 @@ -import { bytesToBigInt, toBytes } from '@ethereumjs/util' +import { BIGINT_0, BIGINT_1, bytesToBigInt, toBytes } from '@ethereumjs/util' import type { FeeMarket1559Tx } from './1559/tx.js' import type { AccessList2930Tx } from './2930/tx.js' @@ -24,6 +24,19 @@ export enum Capability { */ EIP155ReplayProtection = 155, + /** + * The tx supports the "legacy gas market": it has a `gasPrice` property + */ + LegacyGasMarket = 'LegacyGasMarket', + + /** + * The tx supports the "fee gas market": it has the `maxPriorityFeePerGas` and `maxFeePerGas` properties + */ + FeeGasMarket = 'FeeGasMarket', + + // Below here are tx-specfic Capabilities, used to distinguish transactions from other transactions + // These are used in methods such as `raw` + /** * Tx supports EIP-1559 gas fee market mechanism * See: [1559](https://eips.ethereum.org/EIPS/eip-1559) Fee Market EIP @@ -42,6 +55,12 @@ export enum Capability { */ EIP2930AccessLists = 2930, + /** + * Tx supports blobs generation as defined in EIP-4844 + * See: [4844](https://eips.ethereum.org/EIPS/eip-4844) Access Lists EIP + */ + EIP4844Blobs = 4844, + /** * Tx supports setting EOA code * See [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) @@ -165,26 +184,61 @@ export interface Transaction { export type TypedTransaction = Transaction[TransactionType] -export function isLegacyTx(tx: TypedTransaction): tx is LegacyTx { +export function isLegacyTx(tx: TxInterface): tx is LegacyTxInterface { return tx.type === TransactionType.Legacy } -export function isAccessList2930Tx(tx: TypedTransaction): tx is AccessList2930Tx { +export function isAccessList2930Tx(tx: TxInterface): tx is EIP2930CompatibleTx { return tx.type === TransactionType.AccessListEIP2930 } -export function isFeeMarket1559Tx(tx: TypedTransaction): tx is FeeMarket1559Tx { +export function isFeeMarket1559Tx(tx: TxInterface): tx is EIP1559CompatibleTx { return tx.type === TransactionType.FeeMarketEIP1559 } -export function isBlob4844Tx(tx: TypedTransaction): tx is Blob4844Tx { +export function isBlob4844Tx(tx: TxInterface): tx is EIP4844CompatibleTx { return tx.type === TransactionType.BlobEIP4844 } -export function isEOACode7702Tx(tx: TypedTransaction): tx is EOACode7702Tx { +export function isEOACode7702Tx(tx: TxInterface): tx is EIP7702CompatibleTx { return tx.type === TransactionType.EOACodeEIP7702 } +// Temp interface to replace TransactionInterface +export interface TxInterface { + readonly type: number + readonly cache: TransactionCache + readonly txOptions?: any // Placeholder for the saved "txOptions" when constructing a tx + readonly common: Common // TODO: remove Common from tx interfaces + // TODO: make this a Set. `supports()` method is removed in favour of just using a set. + readonly activeCapabilities: Capability[] // Necessary to determine the capabilities of the transaction in the respective methods +} + +export interface ECDSASignableInterface extends TxInterface { + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint +} + +export interface LegacyGasMarketInterface extends TxInterface { + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly value: bigint +} + +export interface FeeGasMarketInterface extends TxInterface { + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint + readonly gasLimit: bigint + readonly value: bigint +} + +export interface FeeGasMarketInterface extends TxInterface { + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint +} + +// TODO: this interface has to be removed or purged! export interface TransactionInterface { readonly common: Common readonly nonce: bigint @@ -227,42 +281,108 @@ export interface TransactionInterface - extends TransactionInterface {} +// Interface of a transaction which supports `to: undefined`, which is used to create a contract +export interface ContractCreationInterface extends TxInterface { + readonly to?: Address +} + +// Interface of a transaction which supports `to: Address`, which calls that address +// It is not possible to create a contract with this tx. +export interface ToInterface extends TxInterface { + readonly to: Address +} + +export interface LegacyTxInterface extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint +} +/* export interface EIP2718CompatibleTx extends TransactionInterface { readonly chainId: bigint getMessageToSign(): Uint8Array -} +}*/ -export interface EIP2930CompatibleTx - extends EIP2718CompatibleTx { +export interface EIP2930CompatibleTx // TODO (among the other types like `EIP1559CompatibleTx` below: rename this?) + extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint readonly accessList: AccessListBytes - readonly AccessListJSON: AccessList + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint } -export interface EIP1559CompatibleTx - extends EIP2930CompatibleTx { +export interface EIP1559CompatibleTx extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint + readonly accessList: AccessListBytes readonly maxPriorityFeePerGas: bigint readonly maxFeePerGas: bigint + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint } -export interface EIP4844CompatibleTx - extends EIP1559CompatibleTx { +export interface EIP4844CompatibleTx extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint + readonly accessList: AccessListBytes + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint readonly maxFeePerBlobGas: bigint + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint + + // Network wrapper format blobVersionedHashes: Uint8Array[] blobs?: Uint8Array[] kzgCommitments?: Uint8Array[] kzgProofs?: Uint8Array[] - serializeNetworkWrapper(): Uint8Array - numBlobs(): number + //serializeNetworkWrapper(): Uint8Array + //numBlobs(): number } -export interface EIP7702CompatibleTx - extends EIP1559CompatibleTx { +export interface EIP7702CompatibleTx extends TxInterface { // ChainID, Address, [nonce], y_parity, r, s + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint + readonly accessList: AccessListBytes readonly authorizationList: AuthorizationListBytes + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint } export interface TxData { From 0e45360734059d8922dc6032b88bc965fd0c4330 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 20 Nov 2024 10:59:21 +0100 Subject: [PATCH 2/5] tx: extract all legacy methods --- packages/tx/src/capabilities/legacy.ts | 209 +++++++++++++++++++++---- packages/tx/src/features/util.ts | 12 +- packages/tx/src/legacy/tx.ts | 103 ++---------- packages/tx/src/types.ts | 19 ++- 4 files changed, 217 insertions(+), 126 deletions(-) diff --git a/packages/tx/src/capabilities/legacy.ts b/packages/tx/src/capabilities/legacy.ts index be560641687..2a4c0b1d4e6 100644 --- a/packages/tx/src/capabilities/legacy.ts +++ b/packages/tx/src/capabilities/legacy.ts @@ -1,22 +1,36 @@ +import { RLP } from '@ethereumjs/rlp' import { Address, BIGINT_0, + BIGINT_2, + BIGINT_8, SECP256K1_ORDER_DIV_2, + bigIntToHex, bigIntToUnpaddedBytes, + bytesToBigInt, bytesToHex, ecrecover, ecsign, publicToAddress, + toBytes, unpadBytes, } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' +import { getBaseJSON } from '../features/util.js' +import { createLegacyTx } from '../legacy/constructors.js' import { Capability, TransactionType } from '../types.js' -import type { LegacyTxInterface, Transaction } from '../types.js' +import type { JSONTx, LegacyTxInterface, TxValuesArray } from '../types.js' + +export function errorStr(tx: LegacyTxInterface) { + let errorStr = getSharedErrorPostfix(tx) + errorStr += ` gasPrice=${tx.gasPrice}` + return errorStr +} export function errorMsg(tx: LegacyTxInterface, msg: string) { - return `${msg} (${tx.errorStr()})` + return `${msg} (${errorStr(tx)})` } export function isSigned(tx: LegacyTxInterface): boolean { @@ -68,9 +82,9 @@ export function getDataGas(tx: LegacyTxInterface, extraCost?: bigint): bigint { */ export function getIntrinsicGas(tx: LegacyTxInterface): bigint { const txFee = tx.common.param('txGas') - let fee = tx.getDataGas() + let fee = getDataGas(tx) if (txFee) fee += txFee - if (tx.common.gteHardfork('homestead') && tx.toCreationAddress()) { + if (tx.common.gteHardfork('homestead') && toCreationAddress(tx)) { const txCreationFee = tx.common.param('txCreationGas') if (txCreationFee) fee += txCreationFee } @@ -82,7 +96,7 @@ export function toCreationAddress(tx: LegacyTxInterface): boolean { } export function hash(tx: LegacyTxInterface): Uint8Array { - if (!tx.isSigned()) { + if (!isSigned(tx)) { const msg = errorMsg(tx, 'Cannot call hash method if transaction is not signed') throw new Error(msg) } @@ -91,12 +105,12 @@ export function hash(tx: LegacyTxInterface): Uint8Array { if (Object.isFrozen(tx)) { if (!tx.cache.hash) { - tx.cache.hash = keccakFunction(tx.serialize()) + tx.cache.hash = keccakFunction(serialize(tx)) } return tx.cache.hash } - return keccakFunction(tx.serialize()) + return keccakFunction(serialize(tx)) } /** @@ -119,7 +133,7 @@ export function getSenderPublicKey(tx: LegacyTxInterface): Uint8Array { return tx.cache.senderPubKey } - const msgHash = tx.getMessageToVerifySignature() + const msgHash = getMessageToVerifySignature(tx) const { v, r, s } = tx @@ -132,7 +146,9 @@ export function getSenderPublicKey(tx: LegacyTxInterface): Uint8Array { v!, bigIntToUnpaddedBytes(r!), bigIntToUnpaddedBytes(s!), - tx.supports(Capability.EIP155ReplayProtection) ? tx.common.chainId() : undefined, + tx.activeCapabilities.includes(Capability.EIP155ReplayProtection) + ? tx.common.chainId() + : undefined, ) if (Object.isFrozen(tx)) { tx.cache.senderPubKey = sender @@ -163,12 +179,12 @@ export function getEffectivePriorityFee(gasPrice: bigint, baseFee: bigint | unde export function getValidationErrors(tx: LegacyTxInterface): string[] { const errors = [] - if (tx.isSigned() && !tx.verifySignature()) { + if (isSigned(tx) && !verifySignature(tx)) { errors.push('Invalid Signature') } - if (tx.getIntrinsicGas() > tx.gasLimit) { - errors.push(`gasLimit is too low. given ${tx.gasLimit}, need at least ${tx.getIntrinsicGas()}`) + if (getIntrinsicGas(tx) > tx.gasLimit) { + errors.push(`gasLimit is too low. given ${tx.gasLimit}, need at least ${getIntrinsicGas(tx)}`) } return errors @@ -179,7 +195,7 @@ export function getValidationErrors(tx: LegacyTxInterface): string[] { * @returns {boolean} true if the transaction is valid, false otherwise */ export function isValid(tx: LegacyTxInterface): boolean { - const errors = tx.getValidationErrors() + const errors = getValidationErrors(tx) return errors.length === 0 } @@ -190,7 +206,7 @@ export function isValid(tx: LegacyTxInterface): boolean { export function verifySignature(tx: LegacyTxInterface): boolean { try { // Main signature verification is done in `getSenderPublicKey()` - const publicKey = tx.getSenderPublicKey() + const publicKey = getSenderPublicKey(tx) return unpadBytes(publicKey).length !== 0 } catch (e: any) { return false @@ -201,7 +217,7 @@ export function verifySignature(tx: LegacyTxInterface): boolean { * Returns the sender's address */ export function getSenderAddress(tx: LegacyTxInterface): Address { - return new Address(publicToAddress(tx.getSenderPublicKey())) + return new Address(publicToAddress(getSenderPublicKey(tx))) } /** @@ -213,7 +229,7 @@ export function getSenderAddress(tx: LegacyTxInterface): Address { * const signedTx = tx.sign(privateKey) * ``` */ -export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): Transaction[TransactionType] { +export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): LegacyTxInterface { if (privateKey.length !== 32) { // TODO figure out this errorMsg logic how this diverges on other txs const msg = errorMsg(tx, 'Private key must be 32 bytes in length.') @@ -230,17 +246,17 @@ export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): Transaction if ( tx.type === TransactionType.Legacy && tx.common.gteHardfork('spuriousDragon') && - !tx.supports(Capability.EIP155ReplayProtection) + !tx.activeCapabilities.includes(Capability.EIP155ReplayProtection) ) { // cast as any to edit the protected `activeCapabilities` ;(tx as any).activeCapabilities.push(Capability.EIP155ReplayProtection) hackApplied = true } - const msgHash = tx.getHashedMessageToSign() + const msgHash = getHashedMessageToSign(tx) const ecSignFunction = tx.common.customCrypto?.ecsign ?? ecsign const { v, r, s } = ecSignFunction(msgHash, privateKey) - const signedTx = tx.addSignature(v, r, s, true) + const signedTx = addSignature(tx, v, r, s, true) // Hack part 2 if (hackApplied) { @@ -257,17 +273,17 @@ export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): Transaction // TODO maybe move this to shared methods (util.ts in features) export function getSharedErrorPostfix(tx: LegacyTxInterface) { - let hash = '' + let hashStr = '' try { - hash = tx.isSigned() ? bytesToHex(tx.hash()) : 'not available (unsigned)' + hashStr = isSigned(tx) ? bytesToHex(hash(tx)) : 'not available (unsigned)' } catch (e: any) { - hash = 'error' + hashStr = 'error' } - let isSigned = '' + let isSignedStr = '' try { - isSigned = tx.isSigned().toString() + isSignedStr = isSigned(tx).toString() } catch (e: any) { - hash = 'error' + hashStr = 'error' } let hf = '' try { @@ -276,8 +292,147 @@ export function getSharedErrorPostfix(tx: LegacyTxInterface) { hf = 'error' } - let postfix = `tx type=${tx.type} hash=${hash} nonce=${tx.nonce} value=${tx.value} ` - postfix += `signed=${isSigned} hf=${hf}` + let postfix = `tx type=${tx.type} hash=${hashStr} nonce=${tx.nonce} value=${tx.value} ` + postfix += `signed=${isSignedStr} hf=${hf}` return postfix } + +/** + * Returns the serialized encoding of the legacy transaction. + * + * Format: `rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])` + * + * For an unsigned tx this method uses the empty Uint8Array values for the + * signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant + * representation for external signing use {@link Transaction.getMessageToSign}. + */ +export function serialize(tx: LegacyTxInterface): Uint8Array { + return RLP.encode(raw(tx)) +} + +/** + * Returns a Uint8Array Array of the raw Bytes of the legacy transaction, in order. + * + * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` + * + * For legacy txs this is also the correct format to add transactions + * to a block with {@link createBlockFromBytesArray} (use the `serialize()` method + * for typed txs). + * + * For an unsigned tx this method returns the empty Bytes values + * for the signature parameters `v`, `r` and `s`. For an EIP-155 compliant + * representation have a look at {@link Transaction.getMessageToSign}. + */ +export function raw(tx: LegacyTxInterface): TxValuesArray[TransactionType.Legacy] { + return [ + bigIntToUnpaddedBytes(tx.nonce), + bigIntToUnpaddedBytes(tx.gasPrice), + bigIntToUnpaddedBytes(tx.gasLimit), + tx.to !== undefined ? tx.to.bytes : new Uint8Array(0), + bigIntToUnpaddedBytes(tx.value), + tx.data, + tx.v !== undefined ? bigIntToUnpaddedBytes(tx.v) : new Uint8Array(0), + tx.r !== undefined ? bigIntToUnpaddedBytes(tx.r) : new Uint8Array(0), + tx.s !== undefined ? bigIntToUnpaddedBytes(tx.s) : new Uint8Array(0), + ] +} + +/** + * Returns the raw unsigned tx, which can be used + * to sign the transaction (e.g. for sending to a hardware wallet). + * + * Note: the raw message message format for the legacy tx is not RLP encoded + * and you might need to do yourself with: + * + * ```javascript + * import { RLP } from '@ethereumjs/rlp' + * const message = tx.getMessageToSign() + * const serializedMessage = RLP.encode(message)) // use this for the HW wallet input + * ``` + */ +export function getMessageToSign(tx: LegacyTxInterface): Uint8Array[] { + const message = [ + bigIntToUnpaddedBytes(tx.nonce), + bigIntToUnpaddedBytes(tx.gasPrice), + bigIntToUnpaddedBytes(tx.gasLimit), + tx.to !== undefined ? tx.to.bytes : new Uint8Array(0), + bigIntToUnpaddedBytes(tx.value), + tx.data, + ] + + if (tx.activeCapabilities.includes(Capability.EIP155ReplayProtection)) { + message.push(bigIntToUnpaddedBytes(tx.common.chainId())) + message.push(unpadBytes(toBytes(0))) + message.push(unpadBytes(toBytes(0))) + } + + return message +} + +export function getHashedMessageToSign(tx: LegacyTxInterface) { + const message = getMessageToSign(tx) + const keccakFunction = tx.common.customCrypto.keccak256 ?? keccak256 + return keccakFunction(RLP.encode(message)) +} + +/** + * The up front amount that an account must have for this transaction to be valid + */ +export function getUpfrontCost(tx: LegacyTxInterface): bigint { + return tx.gasLimit * tx.gasPrice + tx.value +} + +/** + * Computes a sha3-256 hash which can be used to verify the signature + */ +export function getMessageToVerifySignature(tx: LegacyTxInterface) { + if (!isSigned(tx)) { + const msg = errorMsg(tx, 'This transaction is not signed') + throw new Error(msg) + } + return getHashedMessageToSign(tx) +} + +export function addSignature( + tx: LegacyTxInterface, + v: bigint, + r: Uint8Array | bigint, + s: Uint8Array | bigint, + convertV: boolean = false, +): LegacyTxInterface { + r = toBytes(r) + s = toBytes(s) + if (convertV && tx.activeCapabilities.includes(Capability.EIP155ReplayProtection)) { + v += tx.common.chainId() * BIGINT_2 + BIGINT_8 + } + + const opts = { ...tx.txOptions, common: tx.common } + + return createLegacyTx( + { + nonce: tx.nonce, + gasPrice: tx.gasPrice, + gasLimit: tx.gasLimit, + to: tx.to, + value: tx.value, + data: tx.data, + v, + r: bytesToBigInt(r), + s: bytesToBigInt(s), + }, + opts, + ) +} + +/** + * Returns an object with the JSON representation of the transaction. + */ +export function toJSON(tx: LegacyTxInterface): JSONTx { + // TODO this is just copied. Make this execution-api compliant + + const baseJSON = getBaseJSON(tx) as JSONTx + baseJSON.gasPrice = bigIntToHex(tx.gasPrice) + + return baseJSON +} diff --git a/packages/tx/src/features/util.ts b/packages/tx/src/features/util.ts index 36bd9546187..00761aae541 100644 --- a/packages/tx/src/features/util.ts +++ b/packages/tx/src/features/util.ts @@ -12,7 +12,13 @@ import { import { paramsTx } from '../params.js' import { checkMaxInitCodeSize, validateNotArray } from '../util.js' -import type { TransactionInterface, TransactionType, TxData, TxOptions } from '../types.js' +import type { + L1TxInterface, + TransactionInterface, + TransactionType, + TxData, + TxOptions, +} from '../types.js' export function getCommon(common?: Common): Common { return common?.copy() ?? new Common({ chain: Mainnet }) @@ -70,7 +76,7 @@ type Mutable = { // represents the constructor of baseTransaction // Note: have to use `Mutable` to write to readonly props. Only call this in constructor of txs. export function sharedConstructor( - tx: Mutable, + tx: Mutable, txData: TxData[TransactionType], opts: TxOptions = {}, ) { @@ -122,7 +128,7 @@ export function sharedConstructor( } } -export function getBaseJSON(tx: TransactionInterface) { +export function getBaseJSON(tx: L1TxInterface) { return { type: bigIntToHex(BigInt(tx.type)), nonce: bigIntToHex(tx.nonce), diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index a576f4f7ff7..7a7be349d8d 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -22,6 +22,7 @@ import type { TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, JSONTx, + LegacyTxInterface, TransactionCache, TransactionInterface, TxOptions, @@ -78,7 +79,7 @@ function validateVAndExtractChainID(common: Common, _v?: bigint): BigInt | undef /** * An Ethereum non-typed (legacy) transaction */ -export class LegacyTx implements TransactionInterface { +export class LegacyTx implements LegacyTxInterface { /* Tx public data fields */ public type: number = TransactionType.Legacy // Legacy tx type @@ -110,7 +111,7 @@ export class LegacyTx implements TransactionInterface { * e.g. 1559 (fee market) and 2930 (access lists) * for FeeMarket1559Tx objects */ - protected activeCapabilities: number[] = [] + public activeCapabilities: number[] = [] /** * This constructor takes the values, validates them, assigns them and freezes the object. @@ -163,26 +164,6 @@ export class LegacyTx implements TransactionInterface { } } - /** - * Checks if a tx type defining capability is active - * on a tx, for example the EIP-1559 fee market mechanism - * or the EIP-2930 access list feature. - * - * Note that this is different from the tx type itself, - * so EIP-2930 access lists can very well be active - * on an EIP-1559 tx for example. - * - * This method can be useful for feature checks if the - * tx type is unknown (e.g. when instantiated with - * the tx factory). - * - * See `Capabilities` in the `types` module for a reference - * on all supported capabilities. - */ - supports(capability: Capability) { - return this.activeCapabilities.includes(capability) - } - isSigned(): boolean { return Legacy.isSigned(this) } @@ -205,17 +186,7 @@ export class LegacyTx implements TransactionInterface { * representation have a look at {@link Transaction.getMessageToSign}. */ raw(): TxValuesArray { - return [ - bigIntToUnpaddedBytes(this.nonce), - bigIntToUnpaddedBytes(this.gasPrice), - bigIntToUnpaddedBytes(this.gasLimit), - this.to !== undefined ? this.to.bytes : new Uint8Array(0), - bigIntToUnpaddedBytes(this.value), - this.data, - 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), - ] + return Legacy.raw(this) } /** @@ -228,7 +199,7 @@ export class LegacyTx implements TransactionInterface { * representation for external signing use {@link Transaction.getMessageToSign}. */ serialize(): Uint8Array { - return RLP.encode(this.raw()) + return Legacy.serialize(this) } /** @@ -245,22 +216,7 @@ export class LegacyTx implements TransactionInterface { * ``` */ getMessageToSign(): Uint8Array[] { - const message = [ - bigIntToUnpaddedBytes(this.nonce), - bigIntToUnpaddedBytes(this.gasPrice), - bigIntToUnpaddedBytes(this.gasLimit), - this.to !== undefined ? this.to.bytes : new Uint8Array(0), - bigIntToUnpaddedBytes(this.value), - this.data, - ] - - if (this.supports(Capability.EIP155ReplayProtection)) { - message.push(bigIntToUnpaddedBytes(this.common.chainId())) - message.push(unpadBytes(toBytes(0))) - message.push(unpadBytes(toBytes(0))) - } - - return message + return Legacy.getMessageToSign(this) } /** @@ -268,8 +224,7 @@ export class LegacyTx implements TransactionInterface { * to sign the transaction (e.g. for sending to a hardware wallet). */ getHashedMessageToSign() { - const message = this.getMessageToSign() - return this.keccakFunction(RLP.encode(message)) + return Legacy.getHashedMessageToSign(this) } /** @@ -300,7 +255,7 @@ export class LegacyTx implements TransactionInterface { * The up front amount that an account must have for this transaction to be valid */ getUpfrontCost(): bigint { - return this.gasLimit * this.gasPrice + this.value + return Legacy.getUpfrontCost(this) } /** @@ -317,11 +272,7 @@ export class LegacyTx implements TransactionInterface { * Computes a sha3-256 hash which can be used to verify the signature */ getMessageToVerifySignature() { - if (!this.isSigned()) { - const msg = Legacy.errorMsg(this, 'This transaction is not signed') - throw new Error(msg) - } - return this.getHashedMessageToSign() + return Legacy.getMessageToVerifySignature(this) } /** @@ -336,41 +287,15 @@ export class LegacyTx implements TransactionInterface { r: Uint8Array | bigint, s: Uint8Array | bigint, convertV: boolean = false, - ): LegacyTx { - r = toBytes(r) - s = toBytes(s) - if (convertV && this.supports(Capability.EIP155ReplayProtection)) { - v += this.common.chainId() * BIGINT_2 + BIGINT_8 - } - - const opts = { ...this.txOptions, common: this.common } - - return createLegacyTx( - { - nonce: this.nonce, - gasPrice: this.gasPrice, - gasLimit: this.gasLimit, - to: this.to, - value: this.value, - data: this.data, - v, - r: bytesToBigInt(r), - s: bytesToBigInt(s), - }, - opts, - ) + ) { + return Legacy.addSignature(this, v, r, s, convertV) } /** * Returns an object with the JSON representation of the transaction. */ toJSON(): JSONTx { - // TODO this is just copied. Make this execution-api compliant - - const baseJSON = getBaseJSON(this) as JSONTx - baseJSON.gasPrice = bigIntToHex(this.gasPrice) - - return baseJSON + return Legacy.toJSON(this) } getValidationErrors(): string[] { @@ -397,8 +322,6 @@ export class LegacyTx implements TransactionInterface { * Return a compact error string representation of the object */ public errorStr() { - let errorStr = Legacy.getSharedErrorPostfix(this) - errorStr += ` gasPrice=${this.gasPrice}` - return errorStr + return Legacy.errorStr(this) } } diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index cf3d26599e7..944e809aa23 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -214,6 +214,19 @@ export interface TxInterface { readonly activeCapabilities: Capability[] // Necessary to determine the capabilities of the transaction in the respective methods } +// NOTE: this type should be removed and covers the "shared" properties of all L1 txs currently +// TODO: figure out how to handle this in a much cleaner way +export interface L1TxInterface extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint +} + export interface ECDSASignableInterface extends TxInterface { readonly v?: bigint readonly r?: bigint @@ -233,11 +246,6 @@ export interface FeeGasMarketInterface extends TxInterface { readonly value: bigint } -export interface FeeGasMarketInterface extends TxInterface { - readonly maxPriorityFeePerGas: bigint - readonly maxFeePerGas: bigint -} - // TODO: this interface has to be removed or purged! export interface TransactionInterface { readonly common: Common @@ -250,7 +258,6 @@ export interface TransactionInterface Date: Wed, 20 Nov 2024 11:01:19 +0100 Subject: [PATCH 3/5] tx: remove methods from legacy tx + lint --- packages/tx/src/features/util.ts | 8 +- packages/tx/src/legacy/tx.ts | 181 +------------------------------ packages/tx/src/types.ts | 2 +- 3 files changed, 5 insertions(+), 186 deletions(-) diff --git a/packages/tx/src/features/util.ts b/packages/tx/src/features/util.ts index 00761aae541..5a6cfbc72d4 100644 --- a/packages/tx/src/features/util.ts +++ b/packages/tx/src/features/util.ts @@ -12,13 +12,7 @@ import { import { paramsTx } from '../params.js' import { checkMaxInitCodeSize, validateNotArray } from '../util.js' -import type { - L1TxInterface, - TransactionInterface, - TransactionType, - TxData, - TxOptions, -} from '../types.js' +import type { L1TxInterface, TransactionType, TxData, TxOptions } from '../types.js' export function getCommon(common?: Common): Common { return common?.copy() ?? new Common({ chain: Mainnet }) diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index 7a7be349d8d..03e8b75b093 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -1,30 +1,16 @@ -import { RLP } from '@ethereumjs/rlp' -import { - BIGINT_2, - BIGINT_8, - MAX_INTEGER, - bigIntToHex, - bigIntToUnpaddedBytes, - bytesToBigInt, - toBytes, - unpadBytes, -} from '@ethereumjs/util' +import { BIGINT_2, MAX_INTEGER, bytesToBigInt, toBytes } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' import * as Legacy from '../capabilities/legacy.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' +import { sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { paramsTx } from '../index.js' import { Capability, TransactionType } from '../types.js' -import { createLegacyTx } from './constructors.js' - import type { TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, - JSONTx, LegacyTxInterface, TransactionCache, - TransactionInterface, TxOptions, } from '../types.js' import type { Common } from '@ethereumjs/common' @@ -143,7 +129,7 @@ export class LegacyTx implements LegacyTxInterface { } if (this.common.gteHardfork('spuriousDragon')) { - if (!this.isSigned()) { + if (!Legacy.isSigned(this)) { this.activeCapabilities.push(Capability.EIP155ReplayProtection) } else { // EIP155 spec: @@ -163,165 +149,4 @@ export class LegacyTx implements LegacyTxInterface { Object.freeze(this) } } - - isSigned(): boolean { - return Legacy.isSigned(this) - } - - getEffectivePriorityFee(baseFee?: bigint): bigint { - return Legacy.getEffectivePriorityFee(this.gasPrice, baseFee) - } - - /** - * Returns a Uint8Array Array of the raw Bytes of the legacy transaction, in order. - * - * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` - * - * For legacy txs this is also the correct format to add transactions - * to a block with {@link createBlockFromBytesArray} (use the `serialize()` method - * for typed txs). - * - * For an unsigned tx this method returns the empty Bytes values - * for the signature parameters `v`, `r` and `s`. For an EIP-155 compliant - * representation have a look at {@link Transaction.getMessageToSign}. - */ - raw(): TxValuesArray { - return Legacy.raw(this) - } - - /** - * Returns the serialized encoding of the legacy transaction. - * - * Format: `rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])` - * - * For an unsigned tx this method uses the empty Uint8Array values for the - * signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant - * representation for external signing use {@link Transaction.getMessageToSign}. - */ - serialize(): Uint8Array { - return Legacy.serialize(this) - } - - /** - * Returns the raw unsigned tx, which can be used - * to sign the transaction (e.g. for sending to a hardware wallet). - * - * Note: the raw message message format for the legacy tx is not RLP encoded - * and you might need to do yourself with: - * - * ```javascript - * import { RLP } from '@ethereumjs/rlp' - * const message = tx.getMessageToSign() - * const serializedMessage = RLP.encode(message)) // use this for the HW wallet input - * ``` - */ - getMessageToSign(): Uint8Array[] { - return Legacy.getMessageToSign(this) - } - - /** - * Returns the hashed serialized unsigned tx, which can be used - * to sign the transaction (e.g. for sending to a hardware wallet). - */ - getHashedMessageToSign() { - return Legacy.getHashedMessageToSign(this) - } - - /** - * The amount of gas paid for the data in this tx - */ - getDataGas(): bigint { - return Legacy.getDataGas(this) - } - - // TODO figure out if this is necessary - /** - * If the tx's `to` is to the creation address - */ - toCreationAddress(): boolean { - return Legacy.toCreationAddress(this) - } - - /** - * The minimum gas limit which the tx to have to be valid. - * This covers costs as the standard fee (21000 gas), the data fee (paid for each calldata byte), - * the optional creation fee (if the transaction creates a contract), and if relevant the gas - * to be paid for access lists (EIP-2930) and authority lists (EIP-7702). - */ - getIntrinsicGas(): bigint { - return Legacy.getIntrinsicGas(this) - } - /** - * The up front amount that an account must have for this transaction to be valid - */ - getUpfrontCost(): bigint { - return Legacy.getUpfrontCost(this) - } - - /** - * Computes a sha3-256 hash of the serialized tx. - * - * This method can only be used for signed txs (it throws otherwise). - * Use {@link Transaction.getMessageToSign} to get a tx hash for the purpose of signing. - */ - hash(): Uint8Array { - return Legacy.hash(this) - } - - /** - * Computes a sha3-256 hash which can be used to verify the signature - */ - getMessageToVerifySignature() { - return Legacy.getMessageToVerifySignature(this) - } - - /** - * Returns the public key of the sender - */ - getSenderPublicKey(): Uint8Array { - return Legacy.getSenderPublicKey(this) - } - - addSignature( - v: bigint, - r: Uint8Array | bigint, - s: Uint8Array | bigint, - convertV: boolean = false, - ) { - return Legacy.addSignature(this, v, r, s, convertV) - } - - /** - * Returns an object with the JSON representation of the transaction. - */ - toJSON(): JSONTx { - return Legacy.toJSON(this) - } - - getValidationErrors(): string[] { - return Legacy.getValidationErrors(this) - } - - isValid(): boolean { - return Legacy.isValid(this) - } - - verifySignature(): boolean { - return Legacy.verifySignature(this) - } - - getSenderAddress(): Address { - return Legacy.getSenderAddress(this) - } - - sign(privateKey: Uint8Array): LegacyTx { - return Legacy.sign(this, privateKey) - } - - /** - * Return a compact error string representation of the object - */ - public errorStr() { - return Legacy.errorStr(this) - } } diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index 944e809aa23..4c7d6b82fd0 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -1,4 +1,4 @@ -import { BIGINT_0, BIGINT_1, bytesToBigInt, toBytes } from '@ethereumjs/util' +import { bytesToBigInt, toBytes } from '@ethereumjs/util' import type { FeeMarket1559Tx } from './1559/tx.js' import type { AccessList2930Tx } from './2930/tx.js' From df9f770d3d5056639740a6ff2638bc3186095707 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 20 Nov 2024 11:04:20 +0100 Subject: [PATCH 4/5] tx: move features/util -> capabilties/generic --- packages/tx/src/2930/tx.ts | 2 +- packages/tx/src/4844/tx.ts | 2 +- packages/tx/src/7702/tx.ts | 2 +- packages/tx/src/{features/util.ts => capabilities/generic.ts} | 0 packages/tx/src/capabilities/legacy.ts | 2 +- packages/tx/src/legacy/tx.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename packages/tx/src/{features/util.ts => capabilities/generic.ts} (100%) diff --git a/packages/tx/src/2930/tx.ts b/packages/tx/src/2930/tx.ts index f827d2518ff..8b6eacd3a03 100644 --- a/packages/tx/src/2930/tx.ts +++ b/packages/tx/src/2930/tx.ts @@ -9,8 +9,8 @@ import { import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import * as Legacy from '../capabilities/legacy.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' import { AccessLists } from '../util.js' diff --git a/packages/tx/src/4844/tx.ts b/packages/tx/src/4844/tx.ts index f1af09dc119..2d947d909aa 100644 --- a/packages/tx/src/4844/tx.ts +++ b/packages/tx/src/4844/tx.ts @@ -14,9 +14,9 @@ import { import * as EIP1559 from '../capabilities/eip1559.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import * as Legacy from '../capabilities/legacy.js' import { LIMIT_BLOBS_PER_TX } from '../constants.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' import { AccessLists, validateNotArray } from '../util.js' diff --git a/packages/tx/src/7702/tx.ts b/packages/tx/src/7702/tx.ts index 685293b88db..df5065cee2e 100644 --- a/packages/tx/src/7702/tx.ts +++ b/packages/tx/src/7702/tx.ts @@ -11,8 +11,8 @@ import { import * as EIP1559 from '../capabilities/eip1559.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP7702 from '../capabilities/eip7702.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import * as Legacy from '../capabilities/legacy.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' import { AccessLists, AuthorizationLists, validateNotArray } from '../util.js' diff --git a/packages/tx/src/features/util.ts b/packages/tx/src/capabilities/generic.ts similarity index 100% rename from packages/tx/src/features/util.ts rename to packages/tx/src/capabilities/generic.ts diff --git a/packages/tx/src/capabilities/legacy.ts b/packages/tx/src/capabilities/legacy.ts index 2a4c0b1d4e6..394219dce16 100644 --- a/packages/tx/src/capabilities/legacy.ts +++ b/packages/tx/src/capabilities/legacy.ts @@ -17,7 +17,7 @@ import { } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { getBaseJSON } from '../features/util.js' +import { getBaseJSON } from '../capabilities/generic.js' import { createLegacyTx } from '../legacy/constructors.js' import { Capability, TransactionType } from '../types.js' diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index 03e8b75b093..5357ab5f7e3 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -1,8 +1,8 @@ import { BIGINT_2, MAX_INTEGER, bytesToBigInt, toBytes } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' +import { sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import * as Legacy from '../capabilities/legacy.js' -import { sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { paramsTx } from '../index.js' import { Capability, TransactionType } from '../types.js' From ca07058ef644299fb42e8963cee820b3c0b84130 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 20 Nov 2024 11:30:11 +0100 Subject: [PATCH 5/5] fix some type-related erorrs --- packages/tx/src/1559/tx.ts | 2 +- packages/tx/src/2930/tx.ts | 22 +--------------------- packages/tx/src/4844/tx.ts | 2 +- packages/tx/src/7702/tx.ts | 2 +- 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/packages/tx/src/1559/tx.ts b/packages/tx/src/1559/tx.ts index 00423967c81..753baa65454 100644 --- a/packages/tx/src/1559/tx.ts +++ b/packages/tx/src/1559/tx.ts @@ -12,7 +12,7 @@ import * as EIP1559 from '../capabilities/eip1559.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' import * as Legacy from '../capabilities/legacy.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import { TransactionType } from '../types.js' import { AccessLists } from '../util.js' diff --git a/packages/tx/src/2930/tx.ts b/packages/tx/src/2930/tx.ts index 8b6eacd3a03..1f965f87e09 100644 --- a/packages/tx/src/2930/tx.ts +++ b/packages/tx/src/2930/tx.ts @@ -72,7 +72,7 @@ export class AccessList2930Tx implements TransactionInterface