@@ -7,9 +7,10 @@ import { Logger } from 'pino';
7
7
import { prepend0x } from '../formatters' ;
8
8
import { MirrorNodeClient } from './clients' ;
9
9
import constants from './constants' ;
10
- import { JsonRpcError , predefined } from './errors/JsonRpcError' ;
10
+ import { predefined } from './errors/JsonRpcError' ;
11
11
import { CommonService } from './services' ;
12
12
import { RequestDetails } from './types' ;
13
+ import { IAccountBalance } from './types/mirrorNode' ;
13
14
14
15
/**
15
16
* Precheck class for handling various prechecks before sending a raw transaction.
@@ -21,9 +22,9 @@ export class Precheck {
21
22
22
23
/**
23
24
* Creates an instance of Precheck.
24
- * @param { MirrorNodeClient } mirrorNodeClient - The MirrorNodeClient instance.
25
- * @param { Logger } logger - The logger instance.
26
- * @param { string } chainId - The chain ID.
25
+ * @param mirrorNodeClient - The MirrorNodeClient instance.
26
+ * @param logger - The logger instance.
27
+ * @param chainId - The chain ID.
27
28
*/
28
29
constructor ( mirrorNodeClient : MirrorNodeClient , logger : Logger , chainId : string ) {
29
30
this . mirrorNodeClient = mirrorNodeClient ;
@@ -33,7 +34,7 @@ export class Precheck {
33
34
34
35
/**
35
36
* Parses the transaction if needed.
36
- * @param { string | Transaction } transaction - The transaction to parse.
37
+ * @param transaction - The transaction to parse.
37
38
* @returns {Transaction } The parsed transaction.
38
39
*/
39
40
public static parseRawTransaction ( transaction : string | Transaction ) : Transaction {
@@ -46,7 +47,7 @@ export class Precheck {
46
47
47
48
/**
48
49
* Checks if the value of the transaction is valid.
49
- * @param { Transaction } tx - The transaction.
50
+ * @param tx - The transaction.
50
51
*/
51
52
value ( tx : Transaction ) : void {
52
53
if ( ( tx . value > 0 && tx . value < constants . TINYBAR_TO_WEIBAR_COEF ) || tx . value < 0 ) {
@@ -56,9 +57,9 @@ export class Precheck {
56
57
57
58
/**
58
59
* Sends a raw transaction after performing various prechecks.
59
- * @param { ethers.Transaction } parsedTx - The parsed transaction.
60
- * @param { number } networkGasPriceInWeiBars - The predefined gas price of the network in weibar.
61
- * @param { RequestDetails } requestDetails - The request details for logging and tracking.
60
+ * @param parsedTx - The parsed transaction.
61
+ * @param networkGasPriceInWeiBars - The predefined gas price of the network in weibar.
62
+ * @param requestDetails - The request details for logging and tracking.
62
63
*/
63
64
async sendRawTransactionCheck (
64
65
parsedTx : ethers . Transaction ,
@@ -69,33 +70,23 @@ export class Precheck {
69
70
this . transactionSize ( parsedTx ) ;
70
71
this . transactionType ( parsedTx ) ;
71
72
this . gasLimit ( parsedTx ) ;
72
- const mirrorAccountInfo = await this . verifyAccount ( parsedTx , requestDetails ) ;
73
- this . nonce ( parsedTx , mirrorAccountInfo . ethereum_nonce ) ;
74
73
this . chainId ( parsedTx ) ;
75
74
this . value ( parsedTx ) ;
76
75
this . gasPrice ( parsedTx , networkGasPriceInWeiBars ) ;
77
- this . balance ( parsedTx , mirrorAccountInfo ) ;
76
+ const signerAccountInfo = await this . verifyAccount ( parsedTx , requestDetails ) ;
77
+ this . nonce ( parsedTx , signerAccountInfo . ethereum_nonce ) ;
78
+ this . balance ( parsedTx , signerAccountInfo . balance ) ;
78
79
await this . receiverAccount ( parsedTx , requestDetails ) ;
79
80
}
80
81
81
82
/**
82
83
* Verifies the account.
83
- * @param {Transaction } tx - The transaction.
84
- * @param {RequestDetails } requestDetails - The request details for logging and tracking.
85
- * @returns {Promise<any> } A Promise.
84
+ * @param tx - The transaction.
85
+ * @param requestDetails - The request details for logging and tracking.
86
86
*/
87
87
async verifyAccount ( tx : Transaction , requestDetails : RequestDetails ) : Promise < any > {
88
88
const accountInfo = await this . mirrorNodeClient . getAccount ( tx . from ! , requestDetails ) ;
89
89
if ( accountInfo == null ) {
90
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
91
- this . logger . trace (
92
- `Failed to retrieve address '${
93
- tx . from
94
- } ' account details from mirror node on verify account precheck for sendRawTransaction(transaction=${ JSON . stringify (
95
- tx ,
96
- ) } )`,
97
- ) ;
98
- }
99
90
throw predefined . RESOURCE_NOT_FOUND ( `address '${ tx . from } '.` ) ;
100
91
}
101
92
@@ -105,35 +96,29 @@ export class Precheck {
105
96
/**
106
97
* Checks the nonce of the transaction.
107
98
* @param tx - The transaction.
108
- * @param accountInfoNonce - The nonce of the account.
99
+ * @param accountNonce - The nonce of the account.
109
100
*/
110
- nonce ( tx : Transaction , accountInfoNonce : number ) : void {
111
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
112
- this . logger . trace (
113
- `Nonce precheck for sendRawTransaction(tx.nonce=${ tx . nonce } , accountInfoNonce=${ accountInfoNonce } )` ,
114
- ) ;
101
+ nonce ( tx : Transaction , accountNonce : number | undefined ) : void {
102
+ if ( accountNonce == undefined ) {
103
+ throw predefined . RESOURCE_NOT_FOUND ( `Account nonce unavailable for address: ${ tx . from } .` ) ;
115
104
}
116
105
117
- if ( accountInfoNonce > tx . nonce ) {
118
- throw predefined . NONCE_TOO_LOW ( tx . nonce , accountInfoNonce ) ;
106
+ if ( accountNonce > tx . nonce ) {
107
+ throw predefined . NONCE_TOO_LOW ( tx . nonce , accountNonce ) ;
119
108
}
120
109
}
121
110
122
111
/**
123
- * Checks the chain ID of the transaction.
124
- * @param tx - The transaction.
112
+ * Validates that the transaction's chain ID matches the network's chain ID.
113
+ * Legacy unprotected transactions (pre-EIP155) are exempt from this check.
114
+ *
115
+ * @param tx - The transaction to validate.
116
+ * @throws {JsonRpcError } If the transaction's chain ID doesn't match the network's chain ID.
125
117
*/
126
118
chainId ( tx : Transaction ) : void {
127
119
const txChainId = prepend0x ( Number ( tx . chainId ) . toString ( 16 ) ) ;
128
120
const passes = this . isLegacyUnprotectedEtx ( tx ) || txChainId === this . chain ;
129
121
if ( ! passes ) {
130
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
131
- this . logger . trace (
132
- `Failed chainId precheck for sendRawTransaction(transaction=%s, chainId=%s)` ,
133
- JSON . stringify ( tx ) ,
134
- txChainId ,
135
- ) ;
136
- }
137
122
throw predefined . UNSUPPORTED_CHAIN_ID ( txChainId , this . chain ) ;
138
123
}
139
124
}
@@ -181,22 +166,14 @@ export class Precheck {
181
166
}
182
167
}
183
168
184
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
185
- this . logger . trace (
186
- `Failed gas price precheck for sendRawTransaction(transaction=%s, gasPrice=%s, requiredGasPrice=%s)` ,
187
- JSON . stringify ( tx ) ,
188
- txGasPrice ,
189
- networkGasPrice ,
190
- ) ;
191
- }
192
169
throw predefined . GAS_PRICE_TOO_LOW ( txGasPrice , networkGasPrice ) ;
193
170
}
194
171
}
195
172
196
173
/**
197
174
* Checks if a transaction is the deterministic deployment transaction.
198
- * @param { Transaction } tx - The transaction to check.
199
- * @returns { boolean } Returns true if the transaction is the deterministic deployment transaction, otherwise false.
175
+ * @param tx - The transaction to check.
176
+ * @returns Returns true if the transaction is the deterministic deployment transaction, otherwise false.
200
177
*/
201
178
static isDeterministicDeploymentTransaction ( tx : Transaction ) : boolean {
202
179
return tx . serialized === constants . DETERMINISTIC_DEPLOYER_TRANSACTION ;
@@ -205,58 +182,18 @@ export class Precheck {
205
182
/**
206
183
* Checks the balance of the sender account.
207
184
* @param tx - The transaction.
208
- * @param account - The account information.
185
+ * @param accountBalance - The account balance information.
209
186
*/
210
- balance ( tx : Transaction , account : any ) : void {
211
- const result = {
212
- passes : false ,
213
- error : predefined . INSUFFICIENT_ACCOUNT_BALANCE ,
214
- } ;
187
+ balance ( tx : Transaction , accountBalance : IAccountBalance | undefined ) : void {
188
+ if ( accountBalance ?. balance == undefined ) {
189
+ throw predefined . RESOURCE_NOT_FOUND ( `Account balance unavailable for address: ${ tx . from } .` ) ;
190
+ }
215
191
216
192
const txGasPrice = BigInt ( tx . gasPrice || tx . maxFeePerGas ! + tx . maxPriorityFeePerGas ! ) ;
217
193
const txTotalValue = tx . value + txGasPrice * tx . gasLimit ;
194
+ const accountBalanceInWeiBars = BigInt ( accountBalance . balance ) * BigInt ( constants . TINYBAR_TO_WEIBAR_COEF ) ;
218
195
219
- if ( account == null ) {
220
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
221
- this . logger . trace (
222
- `Failed to retrieve account details from mirror node on balance precheck for sendRawTransaction(transaction=${ JSON . stringify (
223
- tx ,
224
- ) } , totalValue=${ txTotalValue } )`,
225
- ) ;
226
- }
227
- throw predefined . RESOURCE_NOT_FOUND ( `tx.from '${ tx . from } '.` ) ;
228
- }
229
-
230
- let tinybars : bigint ;
231
- try {
232
- tinybars = BigInt ( account . balance . balance . toString ( ) ) * BigInt ( constants . TINYBAR_TO_WEIBAR_COEF ) ;
233
- result . passes = tinybars >= txTotalValue ;
234
- } catch ( error : any ) {
235
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
236
- this . logger . trace (
237
- `Error on balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, error=%s)` ,
238
- JSON . stringify ( tx ) ,
239
- txTotalValue ,
240
- error . message ,
241
- ) ;
242
- }
243
- if ( error instanceof JsonRpcError ) {
244
- // preserve original error
245
- throw error ;
246
- } else {
247
- throw predefined . INTERNAL_ERROR ( `balance precheck: ${ error . message } ` ) ;
248
- }
249
- }
250
-
251
- if ( ! result . passes ) {
252
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
253
- this . logger . trace (
254
- `Failed balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, accountTinyBarBalance=%s)` ,
255
- JSON . stringify ( tx ) ,
256
- txTotalValue ,
257
- tinybars ,
258
- ) ;
259
- }
196
+ if ( accountBalanceInWeiBars < txTotalValue ) {
260
197
throw predefined . INSUFFICIENT_ACCOUNT_BALANCE ;
261
198
}
262
199
}
@@ -267,29 +204,11 @@ export class Precheck {
267
204
*/
268
205
gasLimit ( tx : Transaction ) : void {
269
206
const gasLimit = Number ( tx . gasLimit ) ;
270
- const failBaseLog = 'Failed gasLimit precheck for sendRawTransaction(transaction=%s).' ;
271
-
272
207
const intrinsicGasCost = Precheck . transactionIntrinsicGasCost ( tx . data ) ;
273
208
274
209
if ( gasLimit > constants . MAX_TRANSACTION_FEE_THRESHOLD ) {
275
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
276
- this . logger . trace (
277
- `${ failBaseLog } Gas Limit was too high: %s, block gas limit: %s` ,
278
- JSON . stringify ( tx ) ,
279
- gasLimit ,
280
- constants . MAX_TRANSACTION_FEE_THRESHOLD ,
281
- ) ;
282
- }
283
210
throw predefined . GAS_LIMIT_TOO_HIGH ( gasLimit , constants . MAX_TRANSACTION_FEE_THRESHOLD ) ;
284
211
} else if ( gasLimit < intrinsicGasCost ) {
285
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
286
- this . logger . trace (
287
- `${ failBaseLog } Gas Limit was too low: %s, intrinsic gas cost: %s` ,
288
- JSON . stringify ( tx ) ,
289
- gasLimit ,
290
- intrinsicGasCost ,
291
- ) ;
292
- }
293
212
throw predefined . GAS_LIMIT_TOO_LOW ( gasLimit , intrinsicGasCost ) ;
294
213
}
295
214
}
@@ -298,8 +217,8 @@ export class Precheck {
298
217
* Calculates the intrinsic gas cost based on the number of bytes in the data field.
299
218
* Using a loop that goes through every two characters in the string it counts the zero and non-zero bytes.
300
219
* Every two characters that are packed together and are both zero counts towards zero bytes.
301
- * @param { string } data - The data with the bytes to be calculated
302
- * @returns { number } The intrinsic gas cost.
220
+ * @param data - The data with the bytes to be calculated
221
+ * @returns The intrinsic gas cost.
303
222
* @private
304
223
*/
305
224
public static transactionIntrinsicGasCost ( data : string ) : number {
@@ -326,7 +245,7 @@ export class Precheck {
326
245
* The serialized transaction length is converted from hex string length to byte count
327
246
* by subtracting the '0x' prefix (2 characters) and dividing by 2 (since each byte is represented by 2 hex characters).
328
247
*
329
- * @param { Transaction } tx - The transaction to validate.
248
+ * @param tx - The transaction to validate.
330
249
* @throws {JsonRpcError } If the transaction size exceeds the configured limit.
331
250
*/
332
251
transactionSize ( tx : Transaction ) : void {
@@ -342,7 +261,7 @@ export class Precheck {
342
261
* The data field length is converted from hex string length to byte count
343
262
* by subtracting the '0x' prefix (2 characters) and dividing by 2 (since each byte is represented by 2 hex characters).
344
263
*
345
- * @param { Transaction } tx - The transaction to validate.
264
+ * @param tx - The transaction to validate.
346
265
* @throws {JsonRpcError } If the call data size exceeds the configured limit.
347
266
*/
348
267
callDataSize ( tx : Transaction ) : void {
@@ -353,22 +272,23 @@ export class Precheck {
353
272
}
354
273
}
355
274
275
+ /**
276
+ * Validates the transaction type and throws an error if the transaction is unsupported.
277
+ * Specifically, blob transactions (type 3) are not supported as per HIP 866.
278
+ * @param tx The transaction object to validate.
279
+ * @throws {Error } Throws a predefined error if the transaction type is unsupported.
280
+ */
356
281
transactionType ( tx : Transaction ) {
357
282
// Blob transactions are not supported as per HIP 866
358
283
if ( tx . type === 3 ) {
359
- if ( this . logger . isLevelEnabled ( 'trace' ) ) {
360
- this . logger . trace (
361
- `Transaction with type=${ tx . type } is unsupported for sendRawTransaction(transaction=${ JSON . stringify ( tx ) } )` ,
362
- ) ;
363
- }
364
- throw predefined . UNSUPPORTED_TRANSACTION_TYPE ;
284
+ throw predefined . UNSUPPORTED_TRANSACTION_TYPE_3 ;
365
285
}
366
286
}
367
287
368
288
/**
369
289
* Checks if the receiver account exists and has receiver_sig_required set to true.
370
- * @param { Transaction } tx - The transaction.
371
- * @param { RequestDetails } requestDetails - The request details for logging and tracking.
290
+ * @param tx - The transaction.
291
+ * @param requestDetails - The request details for logging and tracking.
372
292
*/
373
293
async receiverAccount ( tx : Transaction , requestDetails : RequestDetails ) {
374
294
if ( tx . to ) {
0 commit comments