-
Notifications
You must be signed in to change notification settings - Fork 791
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: consolidate generic tx capabilities #2993
Changes from all commits
dd5c76f
fa6549b
78dc797
418541f
84280e7
339dc1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { Transaction, TransactionType } from '../types.js' | ||
|
||
type TypedTransactionEIP1559 = | ||
| Transaction[TransactionType.BlobEIP4844] | ||
| Transaction[TransactionType.FeeMarketEIP1559] | ||
|
||
export function getUpfrontCost(tx: TypedTransactionEIP1559, baseFee: bigint): bigint { | ||
const prio = tx.maxPriorityFeePerGas | ||
const maxBase = tx.maxFeePerGas - baseFee | ||
const inclusionFeePerGas = prio < maxBase ? prio : maxBase | ||
const gasPrice = inclusionFeePerGas + baseFee | ||
return tx.gasLimit * gasPrice + tx.value | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I will just go through the methods one-by-one. Think this initial structure is really crucial so that things can be build cleanly on top of this work. So: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { RLP } from '@ethereumjs/rlp' | ||
import { concatBytes, hexToBytes } from '@ethereumjs/util' | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js' | ||
|
||
import { BaseTransaction } from '../baseTransaction.js' | ||
import { type TypedTransaction } from '../types.js' | ||
import { AccessLists } from '../util.js' | ||
|
||
import type { Transaction, TransactionType } from '../types.js' | ||
|
||
type TypedTransactionEIP2930 = Exclude<TypedTransaction, Transaction[TransactionType.Legacy]> | ||
|
||
/** | ||
* The amount of gas paid for the data in this tx | ||
*/ | ||
export function getDataFee(tx: TypedTransactionEIP2930): bigint { | ||
if (tx['cache'].dataFee && tx['cache'].dataFee.hardfork === tx.common.hardfork()) { | ||
return tx['cache'].dataFee.value | ||
} | ||
|
||
let cost = BaseTransaction.prototype.getDataFee.bind(tx)() | ||
cost += BigInt(AccessLists.getDataFeeEIP2930(tx.accessList, tx.common)) | ||
|
||
if (Object.isFrozen(tx)) { | ||
tx['cache'].dataFee = { | ||
value: cost, | ||
hardfork: tx.common.hardfork(), | ||
} | ||
} | ||
|
||
return cost | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would cautiously say this one is actually well placed in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And I would - also cautiously - say that the "other" version of Also to re-iterate here: so Since also typed transactions have just inherited specific functionality from the legacy transaction, it would very much be ok if typed transactions call into the Distinction between When thinking about it: when staying in this logic it might be even more consistent to skip |
||
|
||
export function getHashedMessageToSign(tx: TypedTransactionEIP2930): Uint8Array { | ||
return keccak256(tx.getMessageToSign()) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -> EIP-2718. (just to mention: there is also a legacy version of this) |
||
|
||
export function serialize(tx: TypedTransactionEIP2930): Uint8Array { | ||
const base = tx.raw() | ||
const txTypeBytes = hexToBytes('0x' + tx.type.toString(16).padStart(2, '0')) | ||
return concatBytes(txTypeBytes, RLP.encode(base)) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, so this is definitely an EIP-2718 thing. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { SECP256K1_ORDER_DIV_2, bigIntToUnpaddedBytes, ecrecover } from '@ethereumjs/util' | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js' | ||
|
||
import { Capability, type TypedTransaction } from '../types.js' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I didn't know you could import a type inside a general import statement like this. Neat! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Found that out recently too! |
||
|
||
export function isSigned(tx: TypedTransaction): boolean { | ||
const { v, r, s } = tx | ||
if (v === undefined || r === undefined || s === undefined) { | ||
return false | ||
} else { | ||
return true | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, guess ok, respectively: -> legacy.ts |
||
|
||
export function errorMsg(tx: TypedTransaction, msg: string) { | ||
return `${msg} (${tx.errorStr()})` | ||
} | ||
|
||
export function hash(tx: TypedTransaction): Uint8Array { | ||
if (!tx.isSigned()) { | ||
const msg = errorMsg(tx, 'Cannot call hash method if transaction is not signed') | ||
throw new Error(msg) | ||
} | ||
|
||
if (Object.isFrozen(tx)) { | ||
if (!tx['cache'].hash) { | ||
tx['cache'].hash = keccak256(tx.serialize()) | ||
} | ||
return tx['cache'].hash | ||
} | ||
|
||
return keccak256(tx.serialize()) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I see correctly this is actually also an EIP-2718 thing, so this specific implementation (sorry: I suggested this placing wrongly initially) |
||
|
||
/** | ||
* EIP-2: All transaction signatures whose s-value is greater than secp256k1n/2are considered invalid. | ||
* Reasoning: https://ethereum.stackexchange.com/a/55728 | ||
*/ | ||
export function validateHighS(tx: TypedTransaction): void { | ||
const { s } = tx | ||
if (tx.common.gteHardfork('homestead') && s !== undefined && s > SECP256K1_ORDER_DIV_2) { | ||
const msg = errorMsg( | ||
tx, | ||
'Invalid Signature: s-values greater than secp256k1n/2 are considered invalid' | ||
) | ||
throw new Error(msg) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -> legacy.ts (I think I am getting more and more a fan of the idea to remove generic.ts while going through here) |
||
|
||
export function validateYParity(tx: TypedTransaction) { | ||
const { v } = tx | ||
if (v !== undefined && v !== BigInt(0) && v !== BigInt(1)) { | ||
const msg = errorMsg(tx, 'The y-parity of the transaction should either be 0 or 1') | ||
throw new Error(msg) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually an EIP-2718 thing and definitely not generic. |
||
|
||
export function getSenderPublicKey(tx: TypedTransaction): Uint8Array { | ||
if (tx['cache'].senderPubKey !== undefined) { | ||
return tx['cache'].senderPubKey | ||
} | ||
|
||
const msgHash = tx.getMessageToVerifySignature() | ||
|
||
const { v, r, s } = tx | ||
|
||
validateHighS(tx) | ||
|
||
try { | ||
const sender = ecrecover( | ||
msgHash, | ||
v!, | ||
bigIntToUnpaddedBytes(r!), | ||
bigIntToUnpaddedBytes(s!), | ||
tx.supports(Capability.EIP155ReplayProtection) ? tx.common.chainId() : undefined | ||
) | ||
if (Object.isFrozen(tx)) { | ||
tx['cache'].senderPubKey = sender | ||
} | ||
return sender | ||
} catch (e: any) { | ||
const msg = errorMsg(tx, 'Invalid Signature') | ||
throw new Error(msg) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -> legacy.ts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it generally possible to type in a way that these methods can also be called by custom transaction type implementations? 🤔 So without the need to predefine all the existing tx types which come into question atm here?
Also coming to surface here again: we really really need to be super careful to make the distinction clear between "EIP-1559 -> the specific transaction" and "Capabilities derived from EIP-1559".
If we do not make this very clear things will get blurry over time and mixed up all the time I fear. In this context the naming above "TypedTransactionEIP1559" is too close to the wrong association thing and we should rename these throughout the files for clarity.
Just need to think right now myself: hmm. I would find
EIP1559CompatibleTx
already better. 🤔 Not sure if it is the last word of wisdom here though.(maybe this whole discussion interplays with an eventually possible re-typing though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll think about this a bit more about how to architect this. You're right that these "capabilities" are currently tightly paired with "transaction types", whereas they shouldn't. I'm currently wondering if we make use of the existing tx Capability enum. Will reflect a bit more and follow up...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's what I feel is a more sound approach: #3010, curious to hear your thoughts!