Skip to content

Commit e8bdbf6

Browse files
authored
Merge pull request #1144 from ethereumjs/tx-followup-work
Tx: Small fixes and improvements
2 parents 53150c1 + 405cadf commit e8bdbf6

File tree

15 files changed

+176
-216
lines changed

15 files changed

+176
-216
lines changed

packages/block/src/block.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,14 @@ export class Block {
149149
}
150150

151151
/**
152-
* Returns a Buffer Array of the raw Buffers of this block, in order.
152+
* Returns a Buffer Array of the raw Buffers of this block, in order.
153153
*/
154154
raw(): BlockBuffer {
155155
return [
156156
this.header.raw(),
157-
this.transactions.map((tx) => <Buffer[]>tx.raw()),
157+
this.transactions.map((tx) =>
158+
'transactionType' in tx && tx.transactionType > 0 ? tx.serialize() : tx.raw()
159+
) as Buffer[],
158160
this.uncleHeaders.map((uh) => uh.raw()),
159161
]
160162
}

packages/block/src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ export interface BlockData {
100100
export type BlockBuffer = [BlockHeaderBuffer, TransactionsBuffer, UncleHeadersBuffer]
101101
export type BlockHeaderBuffer = Buffer[]
102102
export type BlockBodyBuffer = [TransactionsBuffer, UncleHeadersBuffer]
103-
export type TransactionsBuffer = Buffer[][]
103+
/**
104+
* TransactionsBuffer can be an array of serialized txs for Typed Transactions or an array of Buffer Arrays for legacy transactions.
105+
*/
106+
export type TransactionsBuffer = Buffer[][] | Buffer[]
104107
export type UncleHeadersBuffer = Buffer[][]
105108

106109
/**

packages/client/lib/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ export class Config {
194194
// TODO: map chainParams (and lib/util.parseParams) to new Common format
195195
const common =
196196
options.common ?? new Common({ chain: Config.CHAIN_DEFAULT, hardfork: 'chainstart' })
197-
this.chainCommon = Object.assign(Object.create(Object.getPrototypeOf(common)), common)
198-
this.execCommon = Object.assign(Object.create(Object.getPrototypeOf(common)), common)
197+
this.chainCommon = common.copy()
198+
this.execCommon = common.copy()
199199

200200
this.discDns = this.getDnsDiscovery(options.discDns)
201201
this.discV4 = this.getV4Discovery(options.discV4)

packages/common/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,4 +742,11 @@ export default class Common extends EventEmitter {
742742
consensusConfig(): any {
743743
return (<any>this._chainParams)['consensus'][this.consensusAlgorithm()]
744744
}
745+
746+
/**
747+
* Returns a deep copy of this common instance.
748+
*/
749+
copy(): Common {
750+
return Object.assign(Object.create(Object.getPrototypeOf(this)), this)
751+
}
745752
}

packages/tx/.eslintrc.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module.exports = {
2-
extends: "@ethereumjs/eslint-config-defaults",
3-
ignorePatterns: ["examples", "karma.conf.js", "test-build"],
2+
extends: '@ethereumjs/eslint-config-defaults',
3+
ignorePatterns: ['examples', 'karma.conf.js', 'test-build'],
44
rules: {
5-
"@typescript-eslint/no-unnecessary-condition": "off"
6-
}
5+
'@typescript-eslint/no-unnecessary-condition': 'off',
6+
'no-dupe-class-members': 'off',
7+
},
78
}

packages/tx/examples/ropsten-tx.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const txData = toBuffer(
77
)
88

99
const common = new Common({ chain: 'ropsten', hardfork: 'petersburg' })
10-
const tx = Transaction.fromRlpSerializedTx(txData, { common })
10+
const tx = Transaction.fromSerializedTx(txData, { common })
1111

1212
if (
1313
tx.validate() &&

packages/tx/src/baseTransaction.ts

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

1313
export abstract class BaseTransaction<TransactionObject> {
1414
public readonly nonce: BN
@@ -37,19 +37,14 @@ export abstract class BaseTransaction<TransactionObject> {
3737
this.r = r ? new BN(toBuffer(r)) : undefined
3838
this.s = s ? new BN(toBuffer(s)) : undefined
3939

40-
const validateCannotExceedMaxInteger = {
40+
this._validateCannotExceedMaxInteger({
4141
nonce: this.nonce,
4242
gasPrice: this.gasPrice,
4343
gasLimit: this.gasLimit,
4444
value: this.value,
45-
}
46-
47-
this._validateExceedsMaxInteger(validateCannotExceedMaxInteger)
45+
})
4846

49-
this.common =
50-
(txOptions.common &&
51-
Object.assign(Object.create(Object.getPrototypeOf(txOptions.common)), txOptions.common)) ??
52-
new Common({ chain: 'mainnet' })
47+
this.common = txOptions.common?.copy() ?? new Common({ chain: 'mainnet' })
5348
}
5449

5550
/**
@@ -61,11 +56,8 @@ export abstract class BaseTransaction<TransactionObject> {
6156
* (DataFee + TxFee + Creation Fee).
6257
*/
6358
validate(): boolean
64-
/* eslint-disable-next-line no-dupe-class-members */
6559
validate(stringError: false): boolean
66-
/* eslint-disable-next-line no-dupe-class-members */
6760
validate(stringError: true): string[]
68-
/* eslint-disable-next-line no-dupe-class-members */
6961
validate(stringError: boolean = false): boolean | string[] {
7062
const errors = []
7163

@@ -120,19 +112,9 @@ export abstract class BaseTransaction<TransactionObject> {
120112
}
121113

122114
/**
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.
115+
* Returns a Buffer Array of the raw Buffers of this transaction, in order.
134116
*/
135-
abstract raw(asList: boolean): Buffer[] | Buffer
117+
abstract raw(): Buffer[] | AccessListEIP2930ValuesArray
136118

137119
/**
138120
* Returns the encoding of the transaction.
@@ -185,13 +167,8 @@ export abstract class BaseTransaction<TransactionObject> {
185167
if (privateKey.length !== 32) {
186168
throw new Error('Private key must be 32 bytes in length.')
187169
}
188-
189170
const msgHash = this.getMessageToSign()
190-
191-
// Only `v` is reassigned.
192-
/* eslint-disable-next-line prefer-const */
193-
let { v, r, s } = ecsign(msgHash, privateKey)
194-
171+
const { v, r, s } = ecsign(msgHash, privateKey)
195172
return this._processSignature(v, r, s)
196173
}
197174

@@ -203,9 +180,9 @@ export abstract class BaseTransaction<TransactionObject> {
203180
// Accept the v,r,s values from the `sign` method, and convert this into a TransactionObject
204181
protected abstract _processSignature(v: number, r: Buffer, s: Buffer): TransactionObject
205182

206-
protected _validateExceedsMaxInteger(validateCannotExceedMaxInteger: { [key: string]: BN }) {
207-
for (const [key, value] of Object.entries(validateCannotExceedMaxInteger)) {
208-
if (value && value.gt(MAX_INTEGER)) {
183+
protected _validateCannotExceedMaxInteger(values: { [key: string]: BN | undefined }) {
184+
for (const [key, value] of Object.entries(values)) {
185+
if (value?.gt(MAX_INTEGER)) {
209186
throw new Error(`${key} cannot exceed MAX_INTEGER, given ${value}`)
210187
}
211188
}

packages/tx/src/eip2930Transaction.ts

Lines changed: 40 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,14 @@ import {
1515
AccessList,
1616
AccessListBuffer,
1717
AccessListEIP2930TxData,
18+
AccessListEIP2930ValuesArray,
1819
AccessListItem,
1920
isAccessList,
2021
JsonTx,
2122
TxOptions,
23+
N_DIV_2,
2224
} from './types'
2325

24-
// secp256k1n/2
25-
const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16)
26-
27-
type EIP2930ValuesArray = [
28-
Buffer,
29-
Buffer,
30-
Buffer,
31-
Buffer,
32-
Buffer,
33-
Buffer,
34-
Buffer,
35-
AccessListBuffer,
36-
Buffer?,
37-
Buffer?,
38-
Buffer?
39-
]
40-
4126
/**
4227
* Typed transaction with optional access lists
4328
*
@@ -88,21 +73,22 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
8873
}
8974

9075
const values = rlp.decode(serialized.slice(1))
76+
9177
if (!Array.isArray(values)) {
9278
throw new Error('Invalid serialized tx input: must be array')
9379
}
9480

95-
return AccessListEIP2930Transaction.fromValuesArray(values, opts)
81+
return AccessListEIP2930Transaction.fromValuesArray(values as any, opts)
9682
}
9783

9884
/**
9985
* Instantiate a transaction from the serialized tx.
100-
* (alias of fromSerializedTx())
86+
* (alias of `fromSerializedTx()`)
10187
*
10288
* Note: This means that the Buffer should start with 0x01.
10389
*
10490
* @deprecated this constructor alias is deprecated and will be removed
105-
* in favor of the from SerializedTx() constructor
91+
* in favor of the `fromSerializedTx()` constructor
10692
*/
10793
public static fromRlpSerializedTx(serialized: Buffer, opts: TxOptions = {}) {
10894
return AccessListEIP2930Transaction.fromSerializedTx(serialized, opts)
@@ -114,34 +100,33 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
114100
* The format is:
115101
* chainId, nonce, gasPrice, gasLimit, to, value, data, access_list, yParity (v), senderR (r), senderS (s)
116102
*/
117-
public static fromValuesArray(values: (Buffer | AccessListBuffer)[], opts: TxOptions = {}) {
118-
if (values.length == 8 || values.length == 11) {
119-
const [chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, v, r, s] = <
120-
EIP2930ValuesArray
121-
>values
122-
const emptyBuffer = Buffer.from([])
123-
124-
return new AccessListEIP2930Transaction(
125-
{
126-
chainId: new BN(chainId),
127-
nonce: new BN(nonce),
128-
gasPrice: new BN(gasPrice),
129-
gasLimit: new BN(gasLimit),
130-
to: to && to.length > 0 ? new Address(to) : undefined,
131-
value: new BN(value),
132-
data: data ?? emptyBuffer,
133-
accessList: accessList ?? emptyBuffer,
134-
v: v !== undefined ? new BN(v) : undefined, // EIP2930 supports v's with value 0 (empty Buffer)
135-
r: r !== undefined && !r.equals(emptyBuffer) ? new BN(r) : undefined,
136-
s: s !== undefined && !s.equals(emptyBuffer) ? new BN(s) : undefined,
137-
},
138-
opts ?? {}
139-
)
140-
} else {
103+
public static fromValuesArray(values: AccessListEIP2930ValuesArray, opts: TxOptions = {}) {
104+
if (values.length !== 8 && values.length !== 11) {
141105
throw new Error(
142106
'Invalid EIP-2930 transaction. Only expecting 8 values (for unsigned tx) or 11 values (for signed tx).'
143107
)
144108
}
109+
110+
const [chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, v, r, s] = values
111+
112+
const emptyBuffer = Buffer.from([])
113+
114+
return new AccessListEIP2930Transaction(
115+
{
116+
chainId: new BN(chainId),
117+
nonce: new BN(nonce),
118+
gasPrice: new BN(gasPrice),
119+
gasLimit: new BN(gasLimit),
120+
to: to && to.length > 0 ? new Address(to) : undefined,
121+
value: new BN(value),
122+
data: data ?? emptyBuffer,
123+
accessList: accessList ?? emptyBuffer,
124+
v: v !== undefined ? new BN(v) : undefined, // EIP2930 supports v's with value 0 (empty Buffer)
125+
r: r !== undefined && !r.equals(emptyBuffer) ? new BN(r) : undefined,
126+
s: s !== undefined && !s.equals(emptyBuffer) ? new BN(s) : undefined,
127+
},
128+
opts
129+
)
145130
}
146131

147132
/**
@@ -158,17 +143,14 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
158143
throw new Error('EIP-2930 not enabled on Common')
159144
}
160145

161-
// check the type of AccessList. If it's a JSON-type, we have to convert it to a buffer.
162-
146+
// check the type of AccessList. If it's a JSON-type, we have to convert it to a Buffer.
163147
let usedAccessList
164148
if (accessList && isAccessList(accessList)) {
165149
this.AccessListJSON = accessList
166-
167150
const newAccessList: AccessListBuffer = []
168151

169152
for (let i = 0; i < accessList.length; i++) {
170153
const item: AccessListItem = accessList[i]
171-
//const addItem: AccessListBufferItem = []
172154
const addressBuffer = toBuffer(item.address)
173155
const storageItems: Buffer[] = []
174156
for (let index = 0; index < item.storageKeys.length; index++) {
@@ -208,7 +190,7 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
208190
throw new Error('The y-parity of the transaction should either be 0 or 1')
209191
}
210192

211-
if (this.common.gteHardfork('homestead') && this.s && this.s.gt(N_DIV_2)) {
193+
if (this.common.gteHardfork('homestead') && this.s?.gt(N_DIV_2)) {
212194
throw new Error(
213195
'Invalid Signature: s-values greater than secp256k1n/2 are considered invalid'
214196
)
@@ -263,20 +245,10 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
263245
/**
264246
* Returns a Buffer Array of the raw Buffers of this transaction, in order.
265247
*
266-
* Note that if you want to use this function in a tx type independent way
267-
* to then use the raw data output for tx instantiation with
268-
* `Tx.fromValuesArray()` you should set the `asList` parameter to `true` -
269-
* which is ignored on a legacy tx but provides the correct format on
270-
* a typed tx.
271-
*
272-
* To prepare a tx to be added as block data with `Block.fromValuesArray()`
273-
* just use the plain `raw()` method.
274-
*
275-
* @param asList - By default, this method returns a concatenated Buffer
276-
* If this is not desired, then set this to `true`, to get a Buffer array.
248+
* Use `serialize()` to add to block data for `Block.fromValuesArray()`.
277249
*/
278-
raw(asList = false): Buffer[] | Buffer {
279-
const base = <Buffer[]>[
250+
raw(): AccessListEIP2930ValuesArray {
251+
return [
280252
bnToRlp(this.chainId),
281253
bnToRlp(this.nonce),
282254
bnToRlp(this.gasPrice),
@@ -289,27 +261,22 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
289261
this.r !== undefined ? bnToRlp(this.r) : Buffer.from([]),
290262
this.s !== undefined ? bnToRlp(this.s) : Buffer.from([]),
291263
]
292-
if (!asList) {
293-
return Buffer.concat([Buffer.from('01', 'hex'), rlp.encode(base)])
294-
} else {
295-
return base
296-
}
297264
}
298265

299266
/**
300-
* Returns the encoding of the transaction. For typed transaction, this is the raw Buffer.
301-
* In Transaction, this is a Buffer array.
267+
* Returns the serialized encoding of the transaction.
302268
*/
303269
serialize(): Buffer {
304-
return <Buffer>this.raw()
270+
const base = this.raw()
271+
return Buffer.concat([Buffer.from('01', 'hex'), rlp.encode(base as any)])
305272
}
306273

307274
/**
308275
* Computes a sha3-256 hash of the serialized unsigned tx, which is used to sign the transaction.
309276
*/
310277
getMessageToSign() {
311-
const base = this.raw(true).slice(0, 8)
312-
return keccak256(Buffer.concat([Buffer.from('01', 'hex'), rlp.encode(base)]))
278+
const base = this.raw().slice(0, 8)
279+
return keccak256(Buffer.concat([Buffer.from('01', 'hex'), rlp.encode(base as any)]))
313280
}
314281

315282
/**
@@ -342,7 +309,7 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
342309

343310
// All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid.
344311
// TODO: verify if this is the case for EIP-2930
345-
if (this.common.gteHardfork('homestead') && this.s && this.s.gt(N_DIV_2)) {
312+
if (this.common.gteHardfork('homestead') && this.s?.gt(N_DIV_2)) {
346313
throw new Error(
347314
'Invalid Signature: s-values greater than secp256k1n/2 are considered invalid'
348315
)

0 commit comments

Comments
 (0)