@rosen-bridge/tx-pot 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/db/entities/TransactionEntity.d.ts +2 -2
- package/dist/db/entities/TransactionEntity.d.ts.map +1 -1
- package/dist/db/entities/TransactionEntity.js +3 -3
- package/dist/db/migrations/index.d.ts +3 -3
- package/dist/db/migrations/index.js +3 -3
- package/dist/db/migrations/postgres/1706350644686-migration.d.ts +4 -4
- package/dist/db/migrations/postgres/1706350644686-migration.js +8 -8
- package/dist/db/migrations/sqlite/1706007154531-migration.d.ts +4 -4
- package/dist/db/migrations/sqlite/1706007154531-migration.js +8 -8
- package/dist/index.d.ts +1 -1
- package/dist/network/AbstractPotChainManager.d.ts +36 -33
- package/dist/transaction/TxPot.d.ts +55 -7
- package/dist/transaction/TxPot.d.ts.map +1 -1
- package/dist/transaction/TxPot.js +173 -26
- package/dist/transaction/types.d.ts +1 -1
- package/dist/transaction/types.d.ts.map +1 -1
- package/dist/transaction/types.js +1 -1
- package/dist/transaction/utils.d.ts +4 -2
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/lib/db/entities/TransactionEntity.ts +2 -2
- package/lib/transaction/TxPot.ts +240 -31
- package/lib/transaction/types.ts +1 -1
- package/package.json +1 -1
- package/tests/transaction/TestTxPot.ts +1 -1
- package/tests/transaction/TxPot.spec.ts +366 -2
- package/tsconfig.build.tsbuildinfo +1 -1
package/lib/transaction/TxPot.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DataSource, Repository } from 'typeorm';
|
|
2
2
|
import { TransactionEntity } from '../db/entities/TransactionEntity';
|
|
3
|
-
import { AbstractLogger, DummyLogger } from '@rosen-bridge/logger
|
|
3
|
+
import { AbstractLogger, DummyLogger } from '@rosen-bridge/abstract-logger';
|
|
4
4
|
import {
|
|
5
5
|
CallbackFunction,
|
|
6
6
|
SigningStatus,
|
|
@@ -16,10 +16,17 @@ export class TxPot {
|
|
|
16
16
|
protected static instance: TxPot;
|
|
17
17
|
protected readonly txRepository: Repository<TransactionEntity>;
|
|
18
18
|
protected chains = new Map<string, AbstractPotChainManager>();
|
|
19
|
-
protected validators = new Map<
|
|
19
|
+
protected validators = new Map<
|
|
20
|
+
string,
|
|
21
|
+
Map<string, Map<string, ValidatorFunction>>
|
|
22
|
+
>();
|
|
20
23
|
protected txTypeCallbacks = new Map<
|
|
21
24
|
string,
|
|
22
|
-
Map<TransactionStatus, CallbackFunction
|
|
25
|
+
Map<TransactionStatus, Map<string, CallbackFunction>>
|
|
26
|
+
>();
|
|
27
|
+
protected submissionAllowance = new Map<
|
|
28
|
+
string,
|
|
29
|
+
Map<string, ValidatorFunction>
|
|
23
30
|
>();
|
|
24
31
|
protected logger: AbstractLogger;
|
|
25
32
|
|
|
@@ -71,22 +78,107 @@ export class TxPot {
|
|
|
71
78
|
* registers a validator function
|
|
72
79
|
* @param chain
|
|
73
80
|
* @param txType
|
|
81
|
+
* @param id
|
|
74
82
|
* @param validator
|
|
75
83
|
*/
|
|
76
84
|
registerValidator = (
|
|
77
85
|
chain: string,
|
|
78
86
|
txType: string,
|
|
87
|
+
id: string,
|
|
79
88
|
validator: ValidatorFunction
|
|
80
89
|
): void => {
|
|
81
90
|
let chainValidators = this.validators.get(chain);
|
|
82
91
|
if (!chainValidators) {
|
|
83
|
-
chainValidators = new Map<string, ValidatorFunction
|
|
92
|
+
chainValidators = new Map<string, Map<string, ValidatorFunction>>();
|
|
84
93
|
this.validators.set(chain, chainValidators);
|
|
85
94
|
}
|
|
86
95
|
|
|
87
|
-
chainValidators.
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
let typeValidators = chainValidators.get(txType);
|
|
97
|
+
if (!typeValidators) {
|
|
98
|
+
typeValidators = new Map<string, ValidatorFunction>();
|
|
99
|
+
chainValidators.set(txType, typeValidators);
|
|
100
|
+
}
|
|
101
|
+
const currentValidator = typeValidators.get(id);
|
|
102
|
+
typeValidators.set(id, validator);
|
|
103
|
+
if (currentValidator) {
|
|
104
|
+
this.logger.debug(
|
|
105
|
+
`The tx validator function for chain [${chain}], type [${txType}] and id [${id}] is replaced`
|
|
106
|
+
);
|
|
107
|
+
} else {
|
|
108
|
+
this.logger.info(
|
|
109
|
+
`New tx validator function is registered for chain [${chain}] and type [${txType}] by id [${id}]`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* removes a validator function
|
|
116
|
+
* @param chain
|
|
117
|
+
* @param txType
|
|
118
|
+
* @param id
|
|
119
|
+
*/
|
|
120
|
+
unregisterValidator = (chain: string, txType: string, id: string): void => {
|
|
121
|
+
const validators = this.validators.get(chain)?.get(txType);
|
|
122
|
+
if (!validators) {
|
|
123
|
+
this.logger.debug(
|
|
124
|
+
`No tx validator function is set for chain [${chain}], type [${txType}] and id [${id}]`
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
validators.delete(id);
|
|
130
|
+
this.logger.info(
|
|
131
|
+
`Removed tx validator function for chain [${chain}], type [${txType}] and id [${id}]`
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* registers a submit validator function
|
|
137
|
+
* @param chain
|
|
138
|
+
* @param id
|
|
139
|
+
* @param validator
|
|
140
|
+
*/
|
|
141
|
+
registerSubmitValidator = (
|
|
142
|
+
chain: string,
|
|
143
|
+
id: string,
|
|
144
|
+
validator: ValidatorFunction
|
|
145
|
+
): void => {
|
|
146
|
+
let chainAllowance = this.submissionAllowance.get(chain);
|
|
147
|
+
if (!chainAllowance) {
|
|
148
|
+
chainAllowance = new Map<string, ValidatorFunction>();
|
|
149
|
+
this.submissionAllowance.set(chain, chainAllowance);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const currentValidator = chainAllowance.get(id);
|
|
153
|
+
chainAllowance.set(id, validator);
|
|
154
|
+
if (currentValidator) {
|
|
155
|
+
this.logger.debug(
|
|
156
|
+
`The tx submit validator function for chain [${chain}] and id [${id}] is replaced`
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
this.logger.info(
|
|
160
|
+
`New tx submit validator function is registered for chain [${chain}] by id [${id}]`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* removes a submit validator function
|
|
167
|
+
* @param chain
|
|
168
|
+
* @param id
|
|
169
|
+
*/
|
|
170
|
+
unregisterSubmitValidator = (chain: string, id: string): void => {
|
|
171
|
+
const chainAllowance = this.submissionAllowance.get(chain);
|
|
172
|
+
if (!chainAllowance) {
|
|
173
|
+
this.logger.debug(
|
|
174
|
+
`No tx submit validator function is set for chain [${chain}] and id [${id}]`
|
|
175
|
+
);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
chainAllowance.delete(id);
|
|
180
|
+
this.logger.info(
|
|
181
|
+
`Removed tx submit validator function for chain [${chain}] and id [${id}]`
|
|
90
182
|
);
|
|
91
183
|
};
|
|
92
184
|
|
|
@@ -96,22 +188,65 @@ export class TxPot {
|
|
|
96
188
|
* of given type changes to given status
|
|
97
189
|
* @param txType
|
|
98
190
|
* @param status
|
|
191
|
+
* @param id
|
|
99
192
|
* @param callback
|
|
100
193
|
*/
|
|
101
194
|
registerCallback = (
|
|
102
195
|
txType: string,
|
|
103
196
|
status: TransactionStatus,
|
|
197
|
+
id: string,
|
|
104
198
|
callback: CallbackFunction
|
|
105
199
|
): void => {
|
|
106
200
|
let typeCallbacks = this.txTypeCallbacks.get(txType);
|
|
107
201
|
if (!typeCallbacks) {
|
|
108
|
-
typeCallbacks = new Map<
|
|
202
|
+
typeCallbacks = new Map<
|
|
203
|
+
TransactionStatus,
|
|
204
|
+
Map<string, CallbackFunction>
|
|
205
|
+
>();
|
|
109
206
|
this.txTypeCallbacks.set(txType, typeCallbacks);
|
|
110
207
|
}
|
|
111
208
|
|
|
112
|
-
typeCallbacks.
|
|
113
|
-
|
|
114
|
-
|
|
209
|
+
let statusCallbacks = typeCallbacks.get(status);
|
|
210
|
+
if (!statusCallbacks) {
|
|
211
|
+
statusCallbacks = new Map<string, CallbackFunction>();
|
|
212
|
+
typeCallbacks.set(status, statusCallbacks);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const currentCallback = statusCallbacks.get(id);
|
|
216
|
+
statusCallbacks.set(id, callback);
|
|
217
|
+
if (currentCallback) {
|
|
218
|
+
this.logger.debug(
|
|
219
|
+
`The tx status callback function for type [${txType}] and status [${status}] and id [${id}] is replaced`
|
|
220
|
+
);
|
|
221
|
+
} else {
|
|
222
|
+
this.logger.info(
|
|
223
|
+
`New tx status callback function is registered for type [${txType}] and status [${status}] by id [${id}]`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* removes a callback function
|
|
230
|
+
* @param txType
|
|
231
|
+
* @param status
|
|
232
|
+
* @param id
|
|
233
|
+
*/
|
|
234
|
+
unregisterCallback = (
|
|
235
|
+
txType: string,
|
|
236
|
+
status: TransactionStatus,
|
|
237
|
+
id: string
|
|
238
|
+
): void => {
|
|
239
|
+
const callbacks = this.txTypeCallbacks.get(txType)?.get(status);
|
|
240
|
+
if (!callbacks) {
|
|
241
|
+
this.logger.debug(
|
|
242
|
+
`No tx status callback function is set for type [${txType}] and status [${status}] and id [${id}]`
|
|
243
|
+
);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
callbacks.delete(id);
|
|
248
|
+
this.logger.info(
|
|
249
|
+
`Removed tx status callback function for type [${txType}] and status [${status}] and id [${id}]`
|
|
115
250
|
);
|
|
116
251
|
};
|
|
117
252
|
|
|
@@ -162,18 +297,52 @@ export class TxPot {
|
|
|
162
297
|
* @param tx
|
|
163
298
|
*/
|
|
164
299
|
protected validateTx = async (tx: TransactionEntity): Promise<boolean> => {
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
300
|
+
const validators = this.validators.get(tx.chain)?.get(tx.txType);
|
|
301
|
+
if (validators === undefined) {
|
|
167
302
|
// tx is valid since no validator is found
|
|
168
303
|
this.logger.debug(
|
|
169
304
|
`No validator function is found for chain [${tx.chain}] and type [${tx.txType}]`
|
|
170
305
|
);
|
|
171
306
|
return true;
|
|
172
307
|
}
|
|
173
|
-
|
|
308
|
+
for (const idValidatorPair of validators) {
|
|
309
|
+
if ((await idValidatorPair[1](tx)) === false) {
|
|
310
|
+
this.logger.debug(
|
|
311
|
+
`tx [${tx.txId}] is recognized as invalid by validator [${idValidatorPair[0]}]`
|
|
312
|
+
);
|
|
313
|
+
await this.setTransactionAsInvalid(tx);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
};
|
|
174
319
|
|
|
175
|
-
|
|
176
|
-
|
|
320
|
+
/**
|
|
321
|
+
* checks a transaction for submission
|
|
322
|
+
* returns true if no validator functions is set or all validators allow tx to submit
|
|
323
|
+
* otherwise returns false
|
|
324
|
+
* @param tx
|
|
325
|
+
*/
|
|
326
|
+
protected isSubmitAllowed = async (
|
|
327
|
+
tx: TransactionEntity
|
|
328
|
+
): Promise<boolean> => {
|
|
329
|
+
const validators = this.submissionAllowance.get(tx.chain);
|
|
330
|
+
if (validators === undefined) {
|
|
331
|
+
// tx is allowed for submission since no validator is found
|
|
332
|
+
this.logger.debug(
|
|
333
|
+
`No submit validator function is found for chain [${tx.chain}]`
|
|
334
|
+
);
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
for (const idValidatorPair of validators) {
|
|
338
|
+
if ((await idValidatorPair[1](tx)) === false) {
|
|
339
|
+
this.logger.debug(
|
|
340
|
+
`tx [${tx.txId}] is not allowed for submission by submit validator [${idValidatorPair[0]}]`
|
|
341
|
+
);
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
177
346
|
};
|
|
178
347
|
|
|
179
348
|
/**
|
|
@@ -195,15 +364,17 @@ export class TxPot {
|
|
|
195
364
|
lastStatusUpdate: this.currentTime(),
|
|
196
365
|
}
|
|
197
366
|
);
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
367
|
+
const callbacks = this.txTypeCallbacks.get(tx.txType)?.get(status);
|
|
368
|
+
if (callbacks) {
|
|
369
|
+
for (const idCallbackPair of callbacks) {
|
|
370
|
+
idCallbackPair[1](tx, status).catch((e) => {
|
|
371
|
+
this.logger.debug(
|
|
372
|
+
`An error occurred while handling tx [${tx.txId}] status change in callback [${idCallbackPair[0]}]: ${e}`
|
|
373
|
+
);
|
|
374
|
+
if (e instanceof Error && e.stack) this.logger.debug(e.stack);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
} else
|
|
207
378
|
this.logger.debug(
|
|
208
379
|
`No callback function is set for type [${tx.txType}] and status [${status}]`
|
|
209
380
|
);
|
|
@@ -219,6 +390,7 @@ export class TxPot {
|
|
|
219
390
|
* @param tx
|
|
220
391
|
*/
|
|
221
392
|
protected processSignedTx = async (tx: TransactionEntity): Promise<void> => {
|
|
393
|
+
if (!(await this.isSubmitAllowed(tx))) return;
|
|
222
394
|
const manager = this.getChainManager(tx.chain);
|
|
223
395
|
try {
|
|
224
396
|
await manager.submitTransaction(tx.serializedTx);
|
|
@@ -261,8 +433,19 @@ export class TxPot {
|
|
|
261
433
|
|
|
262
434
|
if (isValidTx && isValidToType) {
|
|
263
435
|
// tx is valid. resending...
|
|
264
|
-
this.logger.info(
|
|
265
|
-
|
|
436
|
+
this.logger.info(
|
|
437
|
+
`Tx [${tx.txId}] is still valid. Attempting resend...`
|
|
438
|
+
);
|
|
439
|
+
if (await this.isSubmitAllowed(tx)) {
|
|
440
|
+
try {
|
|
441
|
+
await manager.submitTransaction(tx.serializedTx);
|
|
442
|
+
} catch (e) {
|
|
443
|
+
this.logger.warn(
|
|
444
|
+
`Failed to submit tx [${tx.txId}] to chain [${tx.chain}]: ${e}`
|
|
445
|
+
);
|
|
446
|
+
if (e instanceof Error && e.stack) this.logger.warn(e.stack);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
266
449
|
} else {
|
|
267
450
|
// tx seems invalid. reset status if enough blocks past.
|
|
268
451
|
await this.setTransactionAsInvalid(tx);
|
|
@@ -284,8 +467,11 @@ export class TxPot {
|
|
|
284
467
|
* - process sent txs
|
|
285
468
|
*/
|
|
286
469
|
update = async (): Promise<void> => {
|
|
287
|
-
//
|
|
470
|
+
// fetch signed and sent txs
|
|
288
471
|
const signedTxs = await this.getTxsByStatus(TransactionStatus.SIGNED);
|
|
472
|
+
const sentTxs = await this.getTxsByStatus(TransactionStatus.SENT);
|
|
473
|
+
|
|
474
|
+
// process signed txs
|
|
289
475
|
for (const tx of signedTxs) {
|
|
290
476
|
try {
|
|
291
477
|
await this.processSignedTx(tx);
|
|
@@ -301,7 +487,6 @@ export class TxPot {
|
|
|
301
487
|
);
|
|
302
488
|
|
|
303
489
|
// process sent txs
|
|
304
|
-
const sentTxs = await this.getTxsByStatus(TransactionStatus.SENT);
|
|
305
490
|
for (const tx of sentTxs) {
|
|
306
491
|
try {
|
|
307
492
|
await this.processesSentTx(tx);
|
|
@@ -344,13 +529,16 @@ export class TxPot {
|
|
|
344
529
|
|
|
345
530
|
/**
|
|
346
531
|
* inserts a new transaction into db
|
|
532
|
+
* Note: make sure to set `lastCheck` field if initialStatus is `signed` or `sent`
|
|
347
533
|
* @param txId
|
|
348
534
|
* @param chain
|
|
349
535
|
* @param txType
|
|
350
536
|
* @param requiredSign
|
|
351
537
|
* @param serializedTx
|
|
352
538
|
* @param initialStatus
|
|
353
|
-
* @param lastCheck
|
|
539
|
+
* @param lastCheck last blockchain height that tx was valid
|
|
540
|
+
* @param extra
|
|
541
|
+
* @param extra2
|
|
354
542
|
*/
|
|
355
543
|
addTx = async (
|
|
356
544
|
txId: string,
|
|
@@ -359,7 +547,9 @@ export class TxPot {
|
|
|
359
547
|
requiredSign: number,
|
|
360
548
|
serializedTx: string,
|
|
361
549
|
initialStatus = TransactionStatus.APPROVED,
|
|
362
|
-
lastCheck = 0
|
|
550
|
+
lastCheck = 0,
|
|
551
|
+
extra?: string | null,
|
|
552
|
+
extra2?: string | null
|
|
363
553
|
): Promise<void> => {
|
|
364
554
|
await this.txRepository.insert({
|
|
365
555
|
txId: txId,
|
|
@@ -372,6 +562,8 @@ export class TxPot {
|
|
|
372
562
|
failedInSign: false,
|
|
373
563
|
signFailedCount: 0,
|
|
374
564
|
serializedTx: serializedTx,
|
|
565
|
+
extra: extra,
|
|
566
|
+
extra2: extra2,
|
|
375
567
|
});
|
|
376
568
|
};
|
|
377
569
|
|
|
@@ -516,4 +708,21 @@ export class TxPot {
|
|
|
516
708
|
where: options.map(txOptionToClause),
|
|
517
709
|
});
|
|
518
710
|
};
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* updates extra fields of a transaction
|
|
714
|
+
* @param txId
|
|
715
|
+
* @param chain
|
|
716
|
+
* @param extra
|
|
717
|
+
* @param extra2
|
|
718
|
+
*/
|
|
719
|
+
updateExtra = async (
|
|
720
|
+
txId: string,
|
|
721
|
+
chain: string,
|
|
722
|
+
extra?: string | null,
|
|
723
|
+
extra2?: string | null
|
|
724
|
+
): Promise<void> => {
|
|
725
|
+
if (extra === undefined && extra2 === undefined) return;
|
|
726
|
+
await this.txRepository.update({ txId, chain }, { extra, extra2 });
|
|
727
|
+
};
|
|
519
728
|
}
|
package/lib/transaction/types.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataSource } from 'typeorm';
|
|
2
|
-
import { AbstractLogger } from '@rosen-bridge/logger
|
|
2
|
+
import { AbstractLogger } from '@rosen-bridge/abstract-logger';
|
|
3
3
|
import { TransactionEntity, TransactionStatus, TxPot } from '../../lib';
|
|
4
4
|
|
|
5
5
|
export class TestTxPot extends TxPot {
|