Skip to content

Commit 94651f7

Browse files
Merge pull request #1141 from ethereumjs/eip2718-eip2930-improvements
EIP-2718/EIP-2930 Typed Tx Improvements and Tests
2 parents c8908ea + 18aa0d3 commit 94651f7

File tree

9 files changed

+563
-513
lines changed

9 files changed

+563
-513
lines changed

packages/tx/src/baseTransaction.ts

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ecsign,
99
publicToAddress,
1010
} from 'ethereumjs-util'
11-
import { BaseTransactionData, BaseTxOptions, DEFAULT_COMMON, JsonTx } from './types'
11+
import { TxData, TxOptions, JsonTx } from './types'
1212

1313
export abstract class BaseTransaction<TransactionObject> {
1414
public readonly nonce: BN
@@ -19,8 +19,12 @@ export abstract class BaseTransaction<TransactionObject> {
1919
public readonly data: Buffer
2020
public readonly common: Common
2121

22-
constructor(txData: BaseTransactionData, txOptions: BaseTxOptions = {}) {
23-
const { nonce, gasLimit, gasPrice, to, value, data } = txData
22+
public readonly v?: BN
23+
public readonly r?: BN
24+
public readonly s?: BN
25+
26+
constructor(txData: TxData, txOptions: TxOptions = {}) {
27+
const { nonce, gasLimit, gasPrice, to, value, data, v, r, s } = txData
2428

2529
this.nonce = new BN(toBuffer(nonce))
2630
this.gasPrice = new BN(toBuffer(gasPrice))
@@ -29,50 +33,62 @@ export abstract class BaseTransaction<TransactionObject> {
2933
this.value = new BN(toBuffer(value))
3034
this.data = toBuffer(data)
3135

36+
this.v = v ? new BN(toBuffer(v)) : undefined
37+
this.r = r ? new BN(toBuffer(r)) : undefined
38+
this.s = s ? new BN(toBuffer(s)) : undefined
39+
3240
const validateCannotExceedMaxInteger = {
3341
nonce: this.nonce,
3442
gasPrice: this.gasPrice,
3543
gasLimit: this.gasLimit,
3644
value: this.value,
3745
}
3846

39-
this.validateExceedsMaxInteger(validateCannotExceedMaxInteger)
47+
this._validateExceedsMaxInteger(validateCannotExceedMaxInteger)
4048

4149
this.common =
4250
(txOptions.common &&
4351
Object.assign(Object.create(Object.getPrototypeOf(txOptions.common)), txOptions.common)) ??
44-
DEFAULT_COMMON
45-
}
46-
47-
protected validateExceedsMaxInteger(validateCannotExceedMaxInteger: { [key: string]: BN }) {
48-
for (const [key, value] of Object.entries(validateCannotExceedMaxInteger)) {
49-
if (value && value.gt(MAX_INTEGER)) {
50-
throw new Error(`${key} cannot exceed MAX_INTEGER, given ${value}`)
51-
}
52-
}
52+
new Common({ chain: 'mainnet' })
5353
}
5454

5555
/**
56-
* If the tx's `to` is to the creation address
56+
* Checks if the transaction has the minimum amount of gas required
57+
* (DataFee + TxFee + Creation Fee).
5758
*/
58-
toCreationAddress(): boolean {
59-
return this.to === undefined || this.to.buf.length === 0
60-
}
61-
6259
/**
63-
* Computes a sha3-256 hash of the serialized unsigned tx, which is used to sign the transaction.
60+
* Checks if the transaction has the minimum amount of gas required
61+
* (DataFee + TxFee + Creation Fee).
6462
*/
65-
rawTxHash(): Buffer {
66-
return this.getMessageToSign()
67-
}
63+
validate(): boolean
64+
/* eslint-disable-next-line no-dupe-class-members */
65+
validate(stringError: false): boolean
66+
/* eslint-disable-next-line no-dupe-class-members */
67+
validate(stringError: true): string[]
68+
/* eslint-disable-next-line no-dupe-class-members */
69+
validate(stringError: boolean = false): boolean | string[] {
70+
const errors = []
6871

69-
abstract getMessageToSign(): Buffer
72+
if (this.getBaseFee().gt(this.gasLimit)) {
73+
errors.push(`gasLimit is too low. given ${this.gasLimit}, need at least ${this.getBaseFee()}`)
74+
}
75+
76+
if (this.isSigned() && !this.verifySignature()) {
77+
errors.push('Invalid Signature')
78+
}
79+
80+
return stringError ? errors : errors.length === 0
81+
}
7082

7183
/**
72-
* Returns chain ID
84+
* The minimum amount of gas the tx must have (DataFee + TxFee + Creation Fee)
7385
*/
74-
getChainId(): number {
75-
return this.common.chainId()
86+
getBaseFee(): BN {
87+
const fee = this.getDataFee().addn(this.common.param('gasPrices', 'tx'))
88+
if (this.common.gteHardfork('homestead') && this.toCreationAddress()) {
89+
fee.iaddn(this.common.param('gasPrices', 'txCreation'))
90+
}
91+
return fee
7692
}
7793

7894
/**
@@ -89,17 +105,6 @@ export abstract class BaseTransaction<TransactionObject> {
89105
return new BN(cost)
90106
}
91107

92-
/**
93-
* The minimum amount of gas the tx must have (DataFee + TxFee + Creation Fee)
94-
*/
95-
getBaseFee(): BN {
96-
const fee = this.getDataFee().addn(this.common.param('gasPrices', 'tx'))
97-
if (this.common.gteHardfork('homestead') && this.toCreationAddress()) {
98-
fee.iaddn(this.common.param('gasPrices', 'txCreation'))
99-
}
100-
return fee
101-
}
102-
103108
/**
104109
* The up front amount that an account must have for this transaction to be valid
105110
*/
@@ -108,44 +113,45 @@ export abstract class BaseTransaction<TransactionObject> {
108113
}
109114

110115
/**
111-
* Checks if the transaction has the minimum amount of gas required
112-
* (DataFee + TxFee + Creation Fee).
116+
* If the tx's `to` is to the creation address
113117
*/
118+
toCreationAddress(): boolean {
119+
return this.to === undefined || this.to.buf.length === 0
120+
}
121+
114122
/**
115-
* Checks if the transaction has the minimum amount of gas required
116-
* (DataFee + TxFee + Creation Fee).
123+
* Returns the raw `Buffer[]` (Transaction) or `Buffer` (typed transaction).
124+
* This is the data which is found in the transactions of the block body.
125+
*
126+
* Note that if you want to use this function in a tx type independent way
127+
* to then use the raw data output for tx instantiation with
128+
* `Tx.fromValuesArray()` you should set the `asList` parameter to `true` -
129+
* which is ignored on a legacy tx but provides the correct format on
130+
* a typed tx.
131+
*
132+
* To prepare a tx to be added as block data with `Block.fromValuesArray()`
133+
* just use the plain `raw()` method.
117134
*/
118-
validate(): boolean
119-
/* eslint-disable-next-line no-dupe-class-members */
120-
validate(stringError: false): boolean
121-
/* eslint-disable-next-line no-dupe-class-members */
122-
validate(stringError: true): string[]
123-
/* eslint-disable-next-line no-dupe-class-members */
124-
validate(stringError: boolean = false): boolean | string[] {
125-
const errors = []
126-
127-
if (this.getBaseFee().gt(this.gasLimit)) {
128-
errors.push(`gasLimit is too low. given ${this.gasLimit}, need at least ${this.getBaseFee()}`)
129-
}
130-
131-
if (this.isSigned() && !this.verifySignature()) {
132-
errors.push('Invalid Signature')
133-
}
134-
135-
return stringError ? errors : errors.length === 0
136-
}
135+
abstract raw(asList: boolean): Buffer[] | Buffer
137136

138137
/**
139138
* Returns the encoding of the transaction.
140139
*/
141140
abstract serialize(): Buffer
142141

143142
/**
144-
* Returns an object with the JSON representation of the transaction
143+
* Computes a sha3-256 hash of the serialized unsigned tx, which is used to sign the transaction.
145144
*/
146-
abstract toJSON(): JsonTx
145+
abstract getMessageToSign(): Buffer
146+
147+
abstract hash(): Buffer
148+
149+
abstract getMessageToVerifySignature(): Buffer
147150

148-
abstract isSigned(): boolean
151+
public isSigned(): boolean {
152+
const { v, r, s } = this
153+
return !!v && !!r && !!s
154+
}
149155

150156
/**
151157
* Determines if the signature is valid
@@ -160,22 +166,21 @@ export abstract class BaseTransaction<TransactionObject> {
160166
}
161167
}
162168

163-
/**
164-
* Returns the raw `Buffer[]` (Transaction) or `Buffer` (typed transaction).
165-
* This is the data which is found in the transactions of the block body.
166-
*/
167-
abstract raw(): Buffer[] | Buffer
168-
abstract hash(): Buffer
169-
170-
abstract getMessageToVerifySignature(): Buffer
171169
/**
172170
* Returns the sender's address
173171
*/
174172
getSenderAddress(): Address {
175173
return new Address(publicToAddress(this.getSenderPublicKey()))
176174
}
175+
176+
/**
177+
* Returns the public key of the sender
178+
*/
177179
abstract getSenderPublicKey(): Buffer
178180

181+
/**
182+
* Signs a tx and returns a new signed tx object
183+
*/
179184
sign(privateKey: Buffer): TransactionObject {
180185
if (privateKey.length !== 32) {
181186
throw new Error('Private key must be 32 bytes in length.')
@@ -187,9 +192,22 @@ export abstract class BaseTransaction<TransactionObject> {
187192
/* eslint-disable-next-line prefer-const */
188193
let { v, r, s } = ecsign(msgHash, privateKey)
189194

190-
return this.processSignature(v, r, s)
195+
return this._processSignature(v, r, s)
191196
}
192197

198+
/**
199+
* Returns an object with the JSON representation of the transaction
200+
*/
201+
abstract toJSON(): JsonTx
202+
193203
// Accept the v,r,s values from the `sign` method, and convert this into a TransactionObject
194-
protected abstract processSignature(v: number, r: Buffer, s: Buffer): TransactionObject
204+
protected abstract _processSignature(v: number, r: Buffer, s: Buffer): TransactionObject
205+
206+
protected _validateExceedsMaxInteger(validateCannotExceedMaxInteger: { [key: string]: BN }) {
207+
for (const [key, value] of Object.entries(validateCannotExceedMaxInteger)) {
208+
if (value && value.gt(MAX_INTEGER)) {
209+
throw new Error(`${key} cannot exceed MAX_INTEGER, given ${value}`)
210+
}
211+
}
212+
}
195213
}

0 commit comments

Comments
 (0)