@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.
@@ -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-interface';
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<string, Map<string, ValidatorFunction>>();
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.set(txType, validator);
88
- this.logger.debug(
89
- `A tx validator function is registered for chain [${chain}] and type [${txType}]`
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<TransactionStatus, CallbackFunction>();
202
+ typeCallbacks = new Map<
203
+ TransactionStatus,
204
+ Map<string, CallbackFunction>
205
+ >();
109
206
  this.txTypeCallbacks.set(txType, typeCallbacks);
110
207
  }
111
208
 
112
- typeCallbacks.set(status, callback);
113
- this.logger.debug(
114
- `A tx status callback function is registered for type [${txType}] and status [${status}]`
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 validator = this.validators.get(tx.chain)?.get(tx.txType);
166
- if (validator === undefined) {
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
- if (await validator(tx)) return true;
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
- await this.setTransactionAsInvalid(tx);
176
- return false;
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 callback = this.txTypeCallbacks.get(tx.txType)?.get(status);
199
- if (callback)
200
- callback(tx, status).catch((e) => {
201
- this.logger.debug(
202
- `An error occurred while handling tx [${tx.txId}] status change: ${e}`
203
- );
204
- if (e instanceof Error && e.stack) this.logger.debug(e.stack);
205
- });
206
- else
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(`Tx [${tx.txId}] is still valid. Resending tx...`);
265
- await manager.submitTransaction(tx.serializedTx);
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
- // process signed txs
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
  }
@@ -36,7 +36,7 @@ export interface TxOptions {
36
36
  txType?: string;
37
37
  status?: FieldOption<TransactionStatus>;
38
38
  failedInSign?: boolean;
39
- extra?: FieldValue<string>;
39
+ extra?: FieldValue<string | null>;
40
40
  }
41
41
 
42
42
  export class UnregisteredChain extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rosen-bridge/tx-pot",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "a Typescript package to manage transactions storage, sign, submit and related processes",
5
5
  "repository": "@rosen-bridge/tx-pot",
6
6
  "license": "GPL-3.0",
@@ -1,5 +1,5 @@
1
1
  import { DataSource } from 'typeorm';
2
- import { AbstractLogger } from '@rosen-bridge/logger-interface';
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 {