@rosen-bridge/abstract-scanner 0.1.0-52fc0239

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.
Files changed (71) hide show
  1. package/README.md +49 -0
  2. package/dist/entities/blockEntity.d.ts +17 -0
  3. package/dist/entities/blockEntity.d.ts.map +1 -0
  4. package/dist/entities/blockEntity.js +78 -0
  5. package/dist/entities/extractorStatusEntity.d.ts +7 -0
  6. package/dist/entities/extractorStatusEntity.d.ts.map +1 -0
  7. package/dist/entities/extractorStatusEntity.js +37 -0
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +10 -0
  11. package/dist/migrations/index.d.ts +7 -0
  12. package/dist/migrations/index.d.ts.map +1 -0
  13. package/dist/migrations/index.js +31 -0
  14. package/dist/migrations/postgres/1688545690867-migration.d.ts +7 -0
  15. package/dist/migrations/postgres/1688545690867-migration.d.ts.map +1 -0
  16. package/dist/migrations/postgres/1688545690867-migration.js +30 -0
  17. package/dist/migrations/postgres/1713803486477-migration.d.ts +7 -0
  18. package/dist/migrations/postgres/1713803486477-migration.d.ts.map +1 -0
  19. package/dist/migrations/postgres/1713803486477-migration.js +20 -0
  20. package/dist/migrations/postgres/1718789786123-migration.d.ts +7 -0
  21. package/dist/migrations/postgres/1718789786123-migration.d.ts.map +1 -0
  22. package/dist/migrations/postgres/1718789786123-migration.js +12 -0
  23. package/dist/migrations/postgres/1722697954558-migration.d.ts +7 -0
  24. package/dist/migrations/postgres/1722697954558-migration.d.ts.map +1 -0
  25. package/dist/migrations/postgres/1722697954558-migration.js +52 -0
  26. package/dist/migrations/postgres/1746701087567-migration.d.ts +7 -0
  27. package/dist/migrations/postgres/1746701087567-migration.d.ts.map +1 -0
  28. package/dist/migrations/postgres/1746701087567-migration.js +32 -0
  29. package/dist/migrations/postgres/1747657653564-migration.d.ts +7 -0
  30. package/dist/migrations/postgres/1747657653564-migration.d.ts.map +1 -0
  31. package/dist/migrations/postgres/1747657653564-migration.js +134 -0
  32. package/dist/migrations/sqlite/1688555497475-migration.d.ts +7 -0
  33. package/dist/migrations/sqlite/1688555497475-migration.d.ts.map +1 -0
  34. package/dist/migrations/sqlite/1688555497475-migration.js +29 -0
  35. package/dist/migrations/sqlite/1713786682123-migration.d.ts +7 -0
  36. package/dist/migrations/sqlite/1713786682123-migration.d.ts.map +1 -0
  37. package/dist/migrations/sqlite/1713786682123-migration.js +20 -0
  38. package/dist/migrations/sqlite/1718789744123-migration.d.ts +7 -0
  39. package/dist/migrations/sqlite/1718789744123-migration.d.ts.map +1 -0
  40. package/dist/migrations/sqlite/1718789744123-migration.js +12 -0
  41. package/dist/migrations/sqlite/1722697111974-migration.d.ts +7 -0
  42. package/dist/migrations/sqlite/1722697111974-migration.d.ts.map +1 -0
  43. package/dist/migrations/sqlite/1722697111974-migration.js +112 -0
  44. package/dist/migrations/sqlite/1746701087234-migration.d.ts +7 -0
  45. package/dist/migrations/sqlite/1746701087234-migration.d.ts.map +1 -0
  46. package/dist/migrations/sqlite/1746701087234-migration.js +32 -0
  47. package/dist/migrations/sqlite/1747655941239-migration.d.ts +7 -0
  48. package/dist/migrations/sqlite/1747655941239-migration.d.ts.map +1 -0
  49. package/dist/migrations/sqlite/1747655941239-migration.js +134 -0
  50. package/dist/scanner/abstract/generalScanner.d.ts +63 -0
  51. package/dist/scanner/abstract/generalScanner.d.ts.map +1 -0
  52. package/dist/scanner/abstract/generalScanner.js +173 -0
  53. package/dist/scanner/abstract/scanner.d.ts +48 -0
  54. package/dist/scanner/abstract/scanner.d.ts.map +1 -0
  55. package/dist/scanner/abstract/scanner.js +154 -0
  56. package/dist/scanner/abstract/webSocketScanner.d.ts +29 -0
  57. package/dist/scanner/abstract/webSocketScanner.d.ts.map +1 -0
  58. package/dist/scanner/abstract/webSocketScanner.js +89 -0
  59. package/dist/scanner/action.d.ts +83 -0
  60. package/dist/scanner/action.d.ts.map +1 -0
  61. package/dist/scanner/action.js +251 -0
  62. package/dist/scanner/interfaces.d.ts +12 -0
  63. package/dist/scanner/interfaces.d.ts.map +1 -0
  64. package/dist/scanner/interfaces.js +2 -0
  65. package/dist/scanner/network/ConnectorSelectionStrategies.d.ts +27 -0
  66. package/dist/scanner/network/ConnectorSelectionStrategies.d.ts.map +1 -0
  67. package/dist/scanner/network/ConnectorSelectionStrategies.js +22 -0
  68. package/dist/scanner/network/NetworkConnectorManager.d.ts +64 -0
  69. package/dist/scanner/network/NetworkConnectorManager.d.ts.map +1 -0
  70. package/dist/scanner/network/NetworkConnectorManager.js +125 -0
  71. package/package.json +54 -0
@@ -0,0 +1,63 @@
1
+ import { AbstractScanner } from './scanner';
2
+ import { AbstractNetworkConnector, Block } from '@rosen-bridge/scanner-interfaces';
3
+ import { BlockEntity } from '../../entities/blockEntity';
4
+ import { AbstractLogger } from '@rosen-bridge/abstract-logger';
5
+ import { DataSource } from '@rosen-bridge/extended-typeorm';
6
+ declare abstract class GeneralScanner<TransactionType> extends AbstractScanner<TransactionType> {
7
+ private scannerName;
8
+ private dataSource;
9
+ private network;
10
+ private blockRetrieveGap;
11
+ private suffix?;
12
+ private readonly initialHeight;
13
+ protected blockChainLastHeight: number | undefined;
14
+ name: () => string;
15
+ constructor(scannerName: string, dataSource: DataSource, initialHeight: number, network: AbstractNetworkConnector<TransactionType>, blockRetrieveGap?: number, logger?: AbstractLogger, suffix?: string | undefined);
16
+ /**
17
+ * Get the first block to process
18
+ * @returns The first block at the configured initial height
19
+ */
20
+ protected getFirstBlock: () => Promise<Block>;
21
+ /**
22
+ * function that checks if fork is happen in the blockchain or not
23
+ * @return Promise<Boolean>
24
+ */
25
+ protected isForkHappen: () => Promise<boolean>;
26
+ /**
27
+ * This method introduces delay between consecutive block processing operations
28
+ */
29
+ protected delayBetweenBlocksProcessing: (startTime: number) => Promise<void>;
30
+ /**
31
+ * process a block and execute all extractor on it.
32
+ * @param block
33
+ */
34
+ protected processBlock: (block: Block) => Promise<false | BlockEntity>;
35
+ /**
36
+ * process forward in scanner. get blocks and store information from transactions.
37
+ * @param lastSavedBlock: last saved block entity in database
38
+ */
39
+ protected stepForward: (lastSavedBlock: BlockEntity) => Promise<void>;
40
+ /**
41
+ * Step backward in blockchain and find fork point.
42
+ * and remove all forked blocks from database
43
+ */
44
+ protected stepBackward: () => Promise<void>;
45
+ /**
46
+ * Initialize the extractors with the first block
47
+ * Process and store the first block in database
48
+ * @returns
49
+ */
50
+ protected initialize: () => Promise<BlockEntity>;
51
+ /**
52
+ * Return the latest height of the blockchain
53
+ * @returns { number | undefined }
54
+ */
55
+ getBlockChainLastHeight: () => number | undefined;
56
+ /**
57
+ * worker function that runs for syncing the database with the Cardano blockchain and checks if we have any fork
58
+ * scenario in the blockchain and invalidate the database till the database synced again.
59
+ */
60
+ update: () => Promise<void>;
61
+ }
62
+ export { GeneralScanner };
63
+ //# sourceMappingURL=generalScanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generalScanner.d.ts","sourceRoot":"","sources":["../../../lib/scanner/abstract/generalScanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EACL,wBAAwB,EACxB,KAAK,EACN,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAE5D,uBAAe,cAAc,CAC3B,eAAe,CACf,SAAQ,eAAe,CAAC,eAAe,CAAC;IAOtC,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,UAAU;IAElB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,gBAAgB;IAExB,OAAO,CAAC,MAAM,CAAC;IAZjB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,SAAS,CAAC,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAa;IAE/D,IAAI,eAAmE;gBAG7D,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,UAAU,EAC9B,aAAa,EAAE,MAAM,EACb,OAAO,EAAE,wBAAwB,CAAC,eAAe,CAAC,EAClD,gBAAgB,SAAI,EAC5B,MAAM,CAAC,EAAE,cAAc,EACf,MAAM,CAAC,oBAAQ;IAezB;;;OAGG;IACH,SAAS,CAAC,aAAa,QAAO,QAAQ,KAAK,CAAC,CAE1C;IAEF;;;OAGG;IACH,SAAS,CAAC,YAAY,QAAa,QAAQ,OAAO,CAAC,CAUjD;IAEF;;OAEG;IACH,SAAS,CAAC,4BAA4B,cAAqB,MAAM,mBAK/D;IAEF;;;OAGG;IACH,SAAS,CAAC,YAAY,UAAiB,KAAK,kCAyB1C;IAEF;;;OAGG;IACH,SAAS,CAAC,WAAW,mBAA0B,WAAW,mBA8BxD;IAEF;;;OAGG;IACH,SAAS,CAAC,YAAY,sBAepB;IAEF;;;;OAIG;IACH,SAAS,CAAC,UAAU,6BAYlB;IAEF;;;OAGG;IACI,uBAAuB,QAAO,MAAM,GAAG,SAAS,CAC3B;IAE5B;;;OAGG;IACH,MAAM,sBAqBJ;CACH;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,173 @@
1
+ import { AbstractScanner } from './scanner';
2
+ import JsonBI from '@rosen-bridge/json-bigint';
3
+ import { BlockDbAction } from '../action';
4
+ class GeneralScanner extends AbstractScanner {
5
+ scannerName;
6
+ dataSource;
7
+ network;
8
+ blockRetrieveGap;
9
+ suffix;
10
+ initialHeight;
11
+ blockChainLastHeight = undefined;
12
+ name = () => this.scannerName + (this.suffix ? `-${this.suffix}` : '');
13
+ constructor(scannerName, dataSource, initialHeight, network, blockRetrieveGap = 0, logger, suffix) {
14
+ super(logger);
15
+ this.scannerName = scannerName;
16
+ this.dataSource = dataSource;
17
+ this.network = network;
18
+ this.blockRetrieveGap = blockRetrieveGap;
19
+ this.suffix = suffix;
20
+ /**
21
+ * In order to keep the scanners functionalities consistent, we add config
22
+ * `initialHeight` by one so that it matches how other scanners work.
23
+ */
24
+ this.initialHeight = initialHeight + 1;
25
+ this.action = new BlockDbAction(this.dataSource, this.scannerName, this.logger);
26
+ }
27
+ /**
28
+ * Get the first block to process
29
+ * @returns The first block at the configured initial height
30
+ */
31
+ getFirstBlock = () => {
32
+ return this.network.getBlockAtHeight(this.initialHeight);
33
+ };
34
+ /**
35
+ * function that checks if fork is happen in the blockchain or not
36
+ * @return Promise<Boolean>
37
+ */
38
+ isForkHappen = async () => {
39
+ const lastSavedBlock = await this.action.getLastSavedBlock();
40
+ if (lastSavedBlock !== undefined) {
41
+ const lastSavedBlockFromNetwork = await this.network.getBlockAtHeight(lastSavedBlock.height);
42
+ return lastSavedBlockFromNetwork.hash !== lastSavedBlock.hash;
43
+ }
44
+ else {
45
+ return false;
46
+ }
47
+ };
48
+ /**
49
+ * This method introduces delay between consecutive block processing operations
50
+ */
51
+ delayBetweenBlocksProcessing = async (startTime) => {
52
+ const spentTime = new Date().getTime() - startTime;
53
+ await new Promise((resolve) => setTimeout(() => resolve(null), this.blockRetrieveGap - spentTime));
54
+ };
55
+ /**
56
+ * process a block and execute all extractor on it.
57
+ * @param block
58
+ */
59
+ processBlock = async (block) => {
60
+ const startTime = new Date().getTime();
61
+ this.logger.debug(`Processing block at height [${block.height}] in scanner ${this.name()}`);
62
+ const txs = await this.network.getBlockTxs(block.hash);
63
+ if (block.txCount) {
64
+ if (txs.length != block.txCount) {
65
+ this.logger.debug(`Aborting block process with hash [${block.hash}] expected to have ${block.txCount} transactions but had ${txs.length}`);
66
+ return false;
67
+ }
68
+ this.logger.debug(`processing ${block.txCount} transactions of block with hash [${block.hash}]`);
69
+ }
70
+ const result = await this.processBlockTransactions(block, txs);
71
+ // Spending time between block fetches if the setting is enabled
72
+ if (this.blockRetrieveGap)
73
+ await this.delayBetweenBlocksProcessing(startTime);
74
+ return result;
75
+ };
76
+ /**
77
+ * process forward in scanner. get blocks and store information from transactions.
78
+ * @param lastSavedBlock: last saved block entity in database
79
+ */
80
+ stepForward = async (lastSavedBlock) => {
81
+ const currentHeight = await this.network.getCurrentHeight();
82
+ const firstBlock = await this.action.getFirstSavedBlock();
83
+ if (!firstBlock || firstBlock.height >= currentHeight) {
84
+ return;
85
+ }
86
+ for (let height = lastSavedBlock.height + 1; height <= currentHeight; height++) {
87
+ const block = await this.network.getBlockAtHeight(height);
88
+ if (lastSavedBlock !== undefined) {
89
+ if (block.parentHash === lastSavedBlock.hash) {
90
+ const savedBlock = await this.processBlock(block);
91
+ if (typeof savedBlock === 'boolean') {
92
+ break;
93
+ }
94
+ else {
95
+ lastSavedBlock = savedBlock;
96
+ }
97
+ }
98
+ else {
99
+ this.logger.debug(`Invalid block at height ${height}. Block info is [${JsonBI.stringify(block)} and the expected parent hash is [${lastSavedBlock.hash}]`);
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ };
105
+ /**
106
+ * Step backward in blockchain and find fork point.
107
+ * and remove all forked blocks from database
108
+ */
109
+ stepBackward = async () => {
110
+ let block = await this.action.getLastSavedBlock();
111
+ while (block) {
112
+ const blockFromNetwork = await this.network.getBlockAtHeight(block.height);
113
+ if (blockFromNetwork.hash === block.hash &&
114
+ block.parentHash === blockFromNetwork.parentHash) {
115
+ return;
116
+ }
117
+ await this.forkBlock(block.height);
118
+ block = await this.action.getLastSavedBlock();
119
+ }
120
+ };
121
+ /**
122
+ * Initialize the extractors with the first block
123
+ * Process and store the first block in database
124
+ * @returns
125
+ */
126
+ initialize = async () => {
127
+ const block = await this.getFirstBlock();
128
+ await this.verifyExtractorsInitialization({
129
+ height: block.height - 1,
130
+ hash: block.parentHash,
131
+ });
132
+ await this.processBlock(block);
133
+ const entity = await this.action.getFirstSavedBlock();
134
+ if (entity === undefined) {
135
+ throw new Error('Can not store block in database');
136
+ }
137
+ return entity;
138
+ };
139
+ /**
140
+ * Return the latest height of the blockchain
141
+ * @returns { number | undefined }
142
+ */
143
+ getBlockChainLastHeight = () => this.blockChainLastHeight;
144
+ /**
145
+ * worker function that runs for syncing the database with the Cardano blockchain and checks if we have any fork
146
+ * scenario in the blockchain and invalidate the database till the database synced again.
147
+ */
148
+ update = async () => {
149
+ try {
150
+ const latestHeight = await this.network.getCurrentHeight();
151
+ if (!this.blockChainLastHeight ||
152
+ this.blockChainLastHeight < latestHeight)
153
+ this.blockChainLastHeight = latestHeight;
154
+ let lastSavedBlock = await this.action.getLastSavedBlock();
155
+ if (!lastSavedBlock) {
156
+ lastSavedBlock = await this.initialize();
157
+ }
158
+ else
159
+ await this.verifyExtractorsInitialization(lastSavedBlock);
160
+ if (!(await this.isForkHappen())) {
161
+ await this.stepForward(lastSavedBlock);
162
+ }
163
+ else {
164
+ await this.stepBackward();
165
+ }
166
+ }
167
+ catch (e) {
168
+ this.logger.error(`An error occurred during update process. ${e}`);
169
+ }
170
+ };
171
+ }
172
+ export { GeneralScanner };
173
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhbFNjYW5uZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9saWIvc2Nhbm5lci9hYnN0cmFjdC9nZW5lcmFsU2Nhbm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBTTVDLE9BQU8sTUFBTSxNQUFNLDJCQUEyQixDQUFDO0FBRS9DLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFHMUMsTUFBZSxjQUViLFNBQVEsZUFBZ0M7SUFPOUI7SUFDQTtJQUVBO0lBQ0E7SUFFQTtJQVpPLGFBQWEsQ0FBUztJQUM3QixvQkFBb0IsR0FBdUIsU0FBUyxDQUFDO0lBRS9ELElBQUksR0FBRyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBRXZFLFlBQ1UsV0FBbUIsRUFDbkIsVUFBc0IsRUFDOUIsYUFBcUIsRUFDYixPQUFrRCxFQUNsRCxtQkFBbUIsQ0FBQyxFQUM1QixNQUF1QixFQUNmLE1BQWU7UUFFdkIsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBUk4sZ0JBQVcsR0FBWCxXQUFXLENBQVE7UUFDbkIsZUFBVSxHQUFWLFVBQVUsQ0FBWTtRQUV0QixZQUFPLEdBQVAsT0FBTyxDQUEyQztRQUNsRCxxQkFBZ0IsR0FBaEIsZ0JBQWdCLENBQUk7UUFFcEIsV0FBTSxHQUFOLE1BQU0sQ0FBUztRQUd2Qjs7O1dBR0c7UUFDSCxJQUFJLENBQUMsYUFBYSxHQUFHLGFBQWEsR0FBRyxDQUFDLENBQUM7UUFDdkMsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLGFBQWEsQ0FDN0IsSUFBSSxDQUFDLFVBQVUsRUFDZixJQUFJLENBQUMsV0FBVyxFQUNoQixJQUFJLENBQUMsTUFBTSxDQUNaLENBQUM7SUFDSixDQUFDO0lBRUQ7OztPQUdHO0lBQ08sYUFBYSxHQUFHLEdBQW1CLEVBQUU7UUFDN0MsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUMzRCxDQUFDLENBQUM7SUFFRjs7O09BR0c7SUFDTyxZQUFZLEdBQUcsS0FBSyxJQUFzQixFQUFFO1FBQ3BELE1BQU0sY0FBYyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQzdELElBQUksY0FBYyxLQUFLLFNBQVMsRUFBRTtZQUNoQyxNQUFNLHlCQUF5QixHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FDbkUsY0FBYyxDQUFDLE1BQU0sQ0FDdEIsQ0FBQztZQUNGLE9BQU8seUJBQXlCLENBQUMsSUFBSSxLQUFLLGNBQWMsQ0FBQyxJQUFJLENBQUM7U0FDL0Q7YUFBTTtZQUNMLE9BQU8sS0FBSyxDQUFDO1NBQ2Q7SUFDSCxDQUFDLENBQUM7SUFFRjs7T0FFRztJQUNPLDRCQUE0QixHQUFHLEtBQUssRUFBRSxTQUFpQixFQUFFLEVBQUU7UUFDbkUsTUFBTSxTQUFTLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQyxPQUFPLEVBQUUsR0FBRyxTQUFTLENBQUM7UUFDbkQsTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQzVCLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixHQUFHLFNBQVMsQ0FBQyxDQUNuRSxDQUFDO0lBQ0osQ0FBQyxDQUFDO0lBRUY7OztPQUdHO0lBQ08sWUFBWSxHQUFHLEtBQUssRUFBRSxLQUFZLEVBQUUsRUFBRTtRQUM5QyxNQUFNLFNBQVMsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUNmLCtCQUErQixLQUFLLENBQUMsTUFBTSxnQkFBZ0IsSUFBSSxDQUFDLElBQUksRUFBRSxFQUFFLENBQ3pFLENBQUM7UUFDRixNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN2RCxJQUFJLEtBQUssQ0FBQyxPQUFPLEVBQUU7WUFDakIsSUFBSSxHQUFHLENBQUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxPQUFPLEVBQUU7Z0JBQy9CLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUNmLHFDQUFxQyxLQUFLLENBQUMsSUFBSSxzQkFBc0IsS0FBSyxDQUFDLE9BQU8seUJBQXlCLEdBQUcsQ0FBQyxNQUFNLEVBQUUsQ0FDeEgsQ0FBQztnQkFDRixPQUFPLEtBQUssQ0FBQzthQUNkO1lBQ0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQ2YsY0FBYyxLQUFLLENBQUMsT0FBTyxxQ0FBcUMsS0FBSyxDQUFDLElBQUksR0FBRyxDQUM5RSxDQUFDO1NBQ0g7UUFFRCxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFFL0QsZ0VBQWdFO1FBQ2hFLElBQUksSUFBSSxDQUFDLGdCQUFnQjtZQUN2QixNQUFNLElBQUksQ0FBQyw0QkFBNEIsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUVyRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDLENBQUM7SUFFRjs7O09BR0c7SUFDTyxXQUFXLEdBQUcsS0FBSyxFQUFFLGNBQTJCLEVBQUUsRUFBRTtRQUM1RCxNQUFNLGFBQWEsR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztRQUM1RCxNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUMxRCxJQUFJLENBQUMsVUFBVSxJQUFJLFVBQVUsQ0FBQyxNQUFNLElBQUksYUFBYSxFQUFFO1lBQ3JELE9BQU87U0FDUjtRQUNELEtBQ0UsSUFBSSxNQUFNLEdBQUcsY0FBYyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQ3RDLE1BQU0sSUFBSSxhQUFhLEVBQ3ZCLE1BQU0sRUFBRSxFQUNSO1lBQ0EsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQzFELElBQUksY0FBYyxLQUFLLFNBQVMsRUFBRTtnQkFDaEMsSUFBSSxLQUFLLENBQUMsVUFBVSxLQUFLLGNBQWMsQ0FBQyxJQUFJLEVBQUU7b0JBQzVDLE1BQU0sVUFBVSxHQUFHLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQztvQkFDbEQsSUFBSSxPQUFPLFVBQVUsS0FBSyxTQUFTLEVBQUU7d0JBQ25DLE1BQU07cUJBQ1A7eUJBQU07d0JBQ0wsY0FBYyxHQUFHLFVBQVUsQ0FBQztxQkFDN0I7aUJBQ0Y7cUJBQU07b0JBQ0wsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQ2YsMkJBQTJCLE1BQU0sb0JBQW9CLE1BQU0sQ0FBQyxTQUFTLENBQ25FLEtBQUssQ0FDTixxQ0FBcUMsY0FBYyxDQUFDLElBQUksR0FBRyxDQUM3RCxDQUFDO29CQUNGLE1BQU07aUJBQ1A7YUFDRjtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0lBRUY7OztPQUdHO0lBQ08sWUFBWSxHQUFHLEtBQUssSUFBSSxFQUFFO1FBQ2xDLElBQUksS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQ2xELE9BQU8sS0FBSyxFQUFFO1lBQ1osTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQzFELEtBQUssQ0FBQyxNQUFNLENBQ2IsQ0FBQztZQUNGLElBQ0UsZ0JBQWdCLENBQUMsSUFBSSxLQUFLLEtBQUssQ0FBQyxJQUFJO2dCQUNwQyxLQUFLLENBQUMsVUFBVSxLQUFLLGdCQUFnQixDQUFDLFVBQVUsRUFDaEQ7Z0JBQ0EsT0FBTzthQUNSO1lBQ0QsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNuQyxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGlCQUFpQixFQUFFLENBQUM7U0FDL0M7SUFDSCxDQUFDLENBQUM7SUFFRjs7OztPQUlHO0lBQ08sVUFBVSxHQUFHLEtBQUssSUFBSSxFQUFFO1FBQ2hDLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ3pDLE1BQU0sSUFBSSxDQUFDLDhCQUE4QixDQUFDO1lBQ3hDLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUM7WUFDeEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxVQUFVO1NBQ3ZCLENBQUMsQ0FBQztRQUNILE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMvQixNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUN0RCxJQUFJLE1BQU0sS0FBSyxTQUFTLEVBQUU7WUFDeEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQ0FBaUMsQ0FBQyxDQUFDO1NBQ3BEO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQyxDQUFDO0lBRUY7OztPQUdHO0lBQ0ksdUJBQXVCLEdBQUcsR0FBdUIsRUFBRSxDQUN4RCxJQUFJLENBQUMsb0JBQW9CLENBQUM7SUFFNUI7OztPQUdHO0lBQ0gsTUFBTSxHQUFHLEtBQUssSUFBSSxFQUFFO1FBQ2xCLElBQUk7WUFDRixNQUFNLFlBQVksR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUMzRCxJQUNFLENBQUMsSUFBSSxDQUFDLG9CQUFvQjtnQkFDMUIsSUFBSSxDQUFDLG9CQUFvQixHQUFHLFlBQVk7Z0JBRXhDLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxZQUFZLENBQUM7WUFFM0MsSUFBSSxjQUFjLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGlCQUFpQixFQUFFLENBQUM7WUFDM0QsSUFBSSxDQUFDLGNBQWMsRUFBRTtnQkFDbkIsY0FBYyxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2FBQzFDOztnQkFBTSxNQUFNLElBQUksQ0FBQyw4QkFBOEIsQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUNqRSxJQUFJLENBQUMsQ0FBQyxNQUFNLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQyxFQUFFO2dCQUNoQyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsY0FBYyxDQUFDLENBQUM7YUFDeEM7aUJBQU07Z0JBQ0wsTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7YUFDM0I7U0FDRjtRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNENBQTRDLENBQUMsRUFBRSxDQUFDLENBQUM7U0FDcEU7SUFDSCxDQUFDLENBQUM7Q0FDSDtBQUVELE9BQU8sRUFBRSxjQUFjLEVBQUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFic3RyYWN0U2Nhbm5lciB9IGZyb20gJy4vc2Nhbm5lcic7XG5pbXBvcnQge1xuICBBYnN0cmFjdE5ldHdvcmtDb25uZWN0b3IsXG4gIEJsb2NrLFxufSBmcm9tICdAcm9zZW4tYnJpZGdlL3NjYW5uZXItaW50ZXJmYWNlcyc7XG5pbXBvcnQgeyBCbG9ja0VudGl0eSB9IGZyb20gJy4uLy4uL2VudGl0aWVzL2Jsb2NrRW50aXR5JztcbmltcG9ydCBKc29uQkkgZnJvbSAnQHJvc2VuLWJyaWRnZS9qc29uLWJpZ2ludCc7XG5pbXBvcnQgeyBBYnN0cmFjdExvZ2dlciB9IGZyb20gJ0Byb3Nlbi1icmlkZ2UvYWJzdHJhY3QtbG9nZ2VyJztcbmltcG9ydCB7IEJsb2NrRGJBY3Rpb24gfSBmcm9tICcuLi9hY3Rpb24nO1xuaW1wb3J0IHsgRGF0YVNvdXJjZSB9IGZyb20gJ0Byb3Nlbi1icmlkZ2UvZXh0ZW5kZWQtdHlwZW9ybSc7XG5cbmFic3RyYWN0IGNsYXNzIEdlbmVyYWxTY2FubmVyPFxuICBUcmFuc2FjdGlvblR5cGVcbj4gZXh0ZW5kcyBBYnN0cmFjdFNjYW5uZXI8VHJhbnNhY3Rpb25UeXBlPiB7XG4gIHByaXZhdGUgcmVhZG9ubHkgaW5pdGlhbEhlaWdodDogbnVtYmVyO1xuICBwcm90ZWN0ZWQgYmxvY2tDaGFpbkxhc3RIZWlnaHQ6IG51bWJlciB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcblxuICBuYW1lID0gKCkgPT4gdGhpcy5zY2FubmVyTmFtZSArICh0aGlzLnN1ZmZpeCA/IGAtJHt0aGlzLnN1ZmZpeH1gIDogJycpO1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgc2Nhbm5lck5hbWU6IHN0cmluZyxcbiAgICBwcml2YXRlIGRhdGFTb3VyY2U6IERhdGFTb3VyY2UsXG4gICAgaW5pdGlhbEhlaWdodDogbnVtYmVyLFxuICAgIHByaXZhdGUgbmV0d29yazogQWJzdHJhY3ROZXR3b3JrQ29ubmVjdG9yPFRyYW5zYWN0aW9uVHlwZT4sXG4gICAgcHJpdmF0ZSBibG9ja1JldHJpZXZlR2FwID0gMCxcbiAgICBsb2dnZXI/OiBBYnN0cmFjdExvZ2dlcixcbiAgICBwcml2YXRlIHN1ZmZpeD86IHN0cmluZ1xuICApIHtcbiAgICBzdXBlcihsb2dnZXIpO1xuICAgIC8qKlxuICAgICAqIEluIG9yZGVyIHRvIGtlZXAgdGhlIHNjYW5uZXJzIGZ1bmN0aW9uYWxpdGllcyBjb25zaXN0ZW50LCB3ZSBhZGQgY29uZmlnXG4gICAgICogYGluaXRpYWxIZWlnaHRgIGJ5IG9uZSBzbyB0aGF0IGl0IG1hdGNoZXMgaG93IG90aGVyIHNjYW5uZXJzIHdvcmsuXG4gICAgICovXG4gICAgdGhpcy5pbml0aWFsSGVpZ2h0ID0gaW5pdGlhbEhlaWdodCArIDE7XG4gICAgdGhpcy5hY3Rpb24gPSBuZXcgQmxvY2tEYkFjdGlvbihcbiAgICAgIHRoaXMuZGF0YVNvdXJjZSxcbiAgICAgIHRoaXMuc2Nhbm5lck5hbWUsXG4gICAgICB0aGlzLmxvZ2dlclxuICAgICk7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBmaXJzdCBibG9jayB0byBwcm9jZXNzXG4gICAqIEByZXR1cm5zIFRoZSBmaXJzdCBibG9jayBhdCB0aGUgY29uZmlndXJlZCBpbml0aWFsIGhlaWdodFxuICAgKi9cbiAgcHJvdGVjdGVkIGdldEZpcnN0QmxvY2sgPSAoKTogUHJvbWlzZTxCbG9jaz4gPT4ge1xuICAgIHJldHVybiB0aGlzLm5ldHdvcmsuZ2V0QmxvY2tBdEhlaWdodCh0aGlzLmluaXRpYWxIZWlnaHQpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBmdW5jdGlvbiB0aGF0IGNoZWNrcyBpZiBmb3JrIGlzIGhhcHBlbiBpbiB0aGUgYmxvY2tjaGFpbiBvciBub3RcbiAgICogQHJldHVybiBQcm9taXNlPEJvb2xlYW4+XG4gICAqL1xuICBwcm90ZWN0ZWQgaXNGb3JrSGFwcGVuID0gYXN5bmMgKCk6IFByb21pc2U8Ym9vbGVhbj4gPT4ge1xuICAgIGNvbnN0IGxhc3RTYXZlZEJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0TGFzdFNhdmVkQmxvY2soKTtcbiAgICBpZiAobGFzdFNhdmVkQmxvY2sgIT09IHVuZGVmaW5lZCkge1xuICAgICAgY29uc3QgbGFzdFNhdmVkQmxvY2tGcm9tTmV0d29yayA9IGF3YWl0IHRoaXMubmV0d29yay5nZXRCbG9ja0F0SGVpZ2h0KFxuICAgICAgICBsYXN0U2F2ZWRCbG9jay5oZWlnaHRcbiAgICAgICk7XG4gICAgICByZXR1cm4gbGFzdFNhdmVkQmxvY2tGcm9tTmV0d29yay5oYXNoICE9PSBsYXN0U2F2ZWRCbG9jay5oYXNoO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBUaGlzIG1ldGhvZCBpbnRyb2R1Y2VzIGRlbGF5IGJldHdlZW4gY29uc2VjdXRpdmUgYmxvY2sgcHJvY2Vzc2luZyBvcGVyYXRpb25zXG4gICAqL1xuICBwcm90ZWN0ZWQgZGVsYXlCZXR3ZWVuQmxvY2tzUHJvY2Vzc2luZyA9IGFzeW5jIChzdGFydFRpbWU6IG51bWJlcikgPT4ge1xuICAgIGNvbnN0IHNwZW50VGltZSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpIC0gc3RhcnRUaW1lO1xuICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PlxuICAgICAgc2V0VGltZW91dCgoKSA9PiByZXNvbHZlKG51bGwpLCB0aGlzLmJsb2NrUmV0cmlldmVHYXAgLSBzcGVudFRpbWUpXG4gICAgKTtcbiAgfTtcblxuICAvKipcbiAgICogcHJvY2VzcyBhIGJsb2NrIGFuZCBleGVjdXRlIGFsbCBleHRyYWN0b3Igb24gaXQuXG4gICAqIEBwYXJhbSBibG9ja1xuICAgKi9cbiAgcHJvdGVjdGVkIHByb2Nlc3NCbG9jayA9IGFzeW5jIChibG9jazogQmxvY2spID0+IHtcbiAgICBjb25zdCBzdGFydFRpbWUgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgIGBQcm9jZXNzaW5nIGJsb2NrIGF0IGhlaWdodCBbJHtibG9jay5oZWlnaHR9XSBpbiBzY2FubmVyICR7dGhpcy5uYW1lKCl9YFxuICAgICk7XG4gICAgY29uc3QgdHhzID0gYXdhaXQgdGhpcy5uZXR3b3JrLmdldEJsb2NrVHhzKGJsb2NrLmhhc2gpO1xuICAgIGlmIChibG9jay50eENvdW50KSB7XG4gICAgICBpZiAodHhzLmxlbmd0aCAhPSBibG9jay50eENvdW50KSB7XG4gICAgICAgIHRoaXMubG9nZ2VyLmRlYnVnKFxuICAgICAgICAgIGBBYm9ydGluZyBibG9jayBwcm9jZXNzIHdpdGggaGFzaCBbJHtibG9jay5oYXNofV0gZXhwZWN0ZWQgdG8gaGF2ZSAke2Jsb2NrLnR4Q291bnR9IHRyYW5zYWN0aW9ucyBidXQgaGFkICR7dHhzLmxlbmd0aH1gXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHRoaXMubG9nZ2VyLmRlYnVnKFxuICAgICAgICBgcHJvY2Vzc2luZyAke2Jsb2NrLnR4Q291bnR9IHRyYW5zYWN0aW9ucyBvZiBibG9jayB3aXRoIGhhc2ggWyR7YmxvY2suaGFzaH1dYFxuICAgICAgKTtcbiAgICB9XG5cbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLnByb2Nlc3NCbG9ja1RyYW5zYWN0aW9ucyhibG9jaywgdHhzKTtcblxuICAgIC8vIFNwZW5kaW5nIHRpbWUgYmV0d2VlbiBibG9jayBmZXRjaGVzIGlmIHRoZSBzZXR0aW5nIGlzIGVuYWJsZWRcbiAgICBpZiAodGhpcy5ibG9ja1JldHJpZXZlR2FwKVxuICAgICAgYXdhaXQgdGhpcy5kZWxheUJldHdlZW5CbG9ja3NQcm9jZXNzaW5nKHN0YXJ0VGltZSk7XG5cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9O1xuXG4gIC8qKlxuICAgKiBwcm9jZXNzIGZvcndhcmQgaW4gc2Nhbm5lci4gZ2V0IGJsb2NrcyBhbmQgc3RvcmUgaW5mb3JtYXRpb24gZnJvbSB0cmFuc2FjdGlvbnMuXG4gICAqIEBwYXJhbSBsYXN0U2F2ZWRCbG9jazogbGFzdCBzYXZlZCBibG9jayBlbnRpdHkgaW4gZGF0YWJhc2VcbiAgICovXG4gIHByb3RlY3RlZCBzdGVwRm9yd2FyZCA9IGFzeW5jIChsYXN0U2F2ZWRCbG9jazogQmxvY2tFbnRpdHkpID0+IHtcbiAgICBjb25zdCBjdXJyZW50SGVpZ2h0ID0gYXdhaXQgdGhpcy5uZXR3b3JrLmdldEN1cnJlbnRIZWlnaHQoKTtcbiAgICBjb25zdCBmaXJzdEJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0Rmlyc3RTYXZlZEJsb2NrKCk7XG4gICAgaWYgKCFmaXJzdEJsb2NrIHx8IGZpcnN0QmxvY2suaGVpZ2h0ID49IGN1cnJlbnRIZWlnaHQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgZm9yIChcbiAgICAgIGxldCBoZWlnaHQgPSBsYXN0U2F2ZWRCbG9jay5oZWlnaHQgKyAxO1xuICAgICAgaGVpZ2h0IDw9IGN1cnJlbnRIZWlnaHQ7XG4gICAgICBoZWlnaHQrK1xuICAgICkge1xuICAgICAgY29uc3QgYmxvY2sgPSBhd2FpdCB0aGlzLm5ldHdvcmsuZ2V0QmxvY2tBdEhlaWdodChoZWlnaHQpO1xuICAgICAgaWYgKGxhc3RTYXZlZEJsb2NrICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgaWYgKGJsb2NrLnBhcmVudEhhc2ggPT09IGxhc3RTYXZlZEJsb2NrLmhhc2gpIHtcbiAgICAgICAgICBjb25zdCBzYXZlZEJsb2NrID0gYXdhaXQgdGhpcy5wcm9jZXNzQmxvY2soYmxvY2spO1xuICAgICAgICAgIGlmICh0eXBlb2Ygc2F2ZWRCbG9jayA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgbGFzdFNhdmVkQmxvY2sgPSBzYXZlZEJsb2NrO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgICAgICAgIGBJbnZhbGlkIGJsb2NrIGF0IGhlaWdodCAke2hlaWdodH0uIEJsb2NrIGluZm8gaXMgWyR7SnNvbkJJLnN0cmluZ2lmeShcbiAgICAgICAgICAgICAgYmxvY2tcbiAgICAgICAgICAgICl9IGFuZCB0aGUgZXhwZWN0ZWQgcGFyZW50IGhhc2ggaXMgWyR7bGFzdFNhdmVkQmxvY2suaGFzaH1dYFxuICAgICAgICAgICk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIFN0ZXAgYmFja3dhcmQgaW4gYmxvY2tjaGFpbiBhbmQgZmluZCBmb3JrIHBvaW50LlxuICAgKiBhbmQgcmVtb3ZlIGFsbCBmb3JrZWQgYmxvY2tzIGZyb20gZGF0YWJhc2VcbiAgICovXG4gIHByb3RlY3RlZCBzdGVwQmFja3dhcmQgPSBhc3luYyAoKSA9PiB7XG4gICAgbGV0IGJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0TGFzdFNhdmVkQmxvY2soKTtcbiAgICB3aGlsZSAoYmxvY2spIHtcbiAgICAgIGNvbnN0IGJsb2NrRnJvbU5ldHdvcmsgPSBhd2FpdCB0aGlzLm5ldHdvcmsuZ2V0QmxvY2tBdEhlaWdodChcbiAgICAgICAgYmxvY2suaGVpZ2h0XG4gICAgICApO1xuICAgICAgaWYgKFxuICAgICAgICBibG9ja0Zyb21OZXR3b3JrLmhhc2ggPT09IGJsb2NrLmhhc2ggJiZcbiAgICAgICAgYmxvY2sucGFyZW50SGFzaCA9PT0gYmxvY2tGcm9tTmV0d29yay5wYXJlbnRIYXNoXG4gICAgICApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgYXdhaXQgdGhpcy5mb3JrQmxvY2soYmxvY2suaGVpZ2h0KTtcbiAgICAgIGJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0TGFzdFNhdmVkQmxvY2soKTtcbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIEluaXRpYWxpemUgdGhlIGV4dHJhY3RvcnMgd2l0aCB0aGUgZmlyc3QgYmxvY2tcbiAgICogUHJvY2VzcyBhbmQgc3RvcmUgdGhlIGZpcnN0IGJsb2NrIGluIGRhdGFiYXNlXG4gICAqIEByZXR1cm5zXG4gICAqL1xuICBwcm90ZWN0ZWQgaW5pdGlhbGl6ZSA9IGFzeW5jICgpID0+IHtcbiAgICBjb25zdCBibG9jayA9IGF3YWl0IHRoaXMuZ2V0Rmlyc3RCbG9jaygpO1xuICAgIGF3YWl0IHRoaXMudmVyaWZ5RXh0cmFjdG9yc0luaXRpYWxpemF0aW9uKHtcbiAgICAgIGhlaWdodDogYmxvY2suaGVpZ2h0IC0gMSxcbiAgICAgIGhhc2g6IGJsb2NrLnBhcmVudEhhc2gsXG4gICAgfSk7XG4gICAgYXdhaXQgdGhpcy5wcm9jZXNzQmxvY2soYmxvY2spO1xuICAgIGNvbnN0IGVudGl0eSA9IGF3YWl0IHRoaXMuYWN0aW9uLmdldEZpcnN0U2F2ZWRCbG9jaygpO1xuICAgIGlmIChlbnRpdHkgPT09IHVuZGVmaW5lZCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdDYW4gbm90IHN0b3JlIGJsb2NrIGluIGRhdGFiYXNlJyk7XG4gICAgfVxuICAgIHJldHVybiBlbnRpdHk7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybiB0aGUgbGF0ZXN0IGhlaWdodCBvZiB0aGUgYmxvY2tjaGFpblxuICAgKiBAcmV0dXJucyB7IG51bWJlciB8IHVuZGVmaW5lZCB9XG4gICAqL1xuICBwdWJsaWMgZ2V0QmxvY2tDaGFpbkxhc3RIZWlnaHQgPSAoKTogbnVtYmVyIHwgdW5kZWZpbmVkID0+XG4gICAgdGhpcy5ibG9ja0NoYWluTGFzdEhlaWdodDtcblxuICAvKipcbiAgICogd29ya2VyIGZ1bmN0aW9uIHRoYXQgcnVucyBmb3Igc3luY2luZyB0aGUgZGF0YWJhc2Ugd2l0aCB0aGUgQ2FyZGFubyBibG9ja2NoYWluIGFuZCBjaGVja3MgaWYgd2UgaGF2ZSBhbnkgZm9ya1xuICAgKiBzY2VuYXJpbyBpbiB0aGUgYmxvY2tjaGFpbiBhbmQgaW52YWxpZGF0ZSB0aGUgZGF0YWJhc2UgdGlsbCB0aGUgZGF0YWJhc2Ugc3luY2VkIGFnYWluLlxuICAgKi9cbiAgdXBkYXRlID0gYXN5bmMgKCkgPT4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBsYXRlc3RIZWlnaHQgPSBhd2FpdCB0aGlzLm5ldHdvcmsuZ2V0Q3VycmVudEhlaWdodCgpO1xuICAgICAgaWYgKFxuICAgICAgICAhdGhpcy5ibG9ja0NoYWluTGFzdEhlaWdodCB8fFxuICAgICAgICB0aGlzLmJsb2NrQ2hhaW5MYXN0SGVpZ2h0IDwgbGF0ZXN0SGVpZ2h0XG4gICAgICApXG4gICAgICAgIHRoaXMuYmxvY2tDaGFpbkxhc3RIZWlnaHQgPSBsYXRlc3RIZWlnaHQ7XG5cbiAgICAgIGxldCBsYXN0U2F2ZWRCbG9jayA9IGF3YWl0IHRoaXMuYWN0aW9uLmdldExhc3RTYXZlZEJsb2NrKCk7XG4gICAgICBpZiAoIWxhc3RTYXZlZEJsb2NrKSB7XG4gICAgICAgIGxhc3RTYXZlZEJsb2NrID0gYXdhaXQgdGhpcy5pbml0aWFsaXplKCk7XG4gICAgICB9IGVsc2UgYXdhaXQgdGhpcy52ZXJpZnlFeHRyYWN0b3JzSW5pdGlhbGl6YXRpb24obGFzdFNhdmVkQmxvY2spO1xuICAgICAgaWYgKCEoYXdhaXQgdGhpcy5pc0ZvcmtIYXBwZW4oKSkpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5zdGVwRm9yd2FyZChsYXN0U2F2ZWRCbG9jayk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBhd2FpdCB0aGlzLnN0ZXBCYWNrd2FyZCgpO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIHRoaXMubG9nZ2VyLmVycm9yKGBBbiBlcnJvciBvY2N1cnJlZCBkdXJpbmcgdXBkYXRlIHByb2Nlc3MuICR7ZX1gKTtcbiAgICB9XG4gIH07XG59XG5cbmV4cG9ydCB7IEdlbmVyYWxTY2FubmVyIH07XG4iXX0=
@@ -0,0 +1,48 @@
1
+ import { Mutex } from 'await-semaphore';
2
+ import { Block, BlockInfo } from '@rosen-bridge/scanner-interfaces';
3
+ import { AbstractLogger } from '@rosen-bridge/abstract-logger';
4
+ import { AbstractExtractor } from '@rosen-bridge/abstract-extractor';
5
+ import { BlockDbAction } from '../action';
6
+ export declare abstract class AbstractScanner<TransactionType> {
7
+ action: BlockDbAction;
8
+ extractors: Array<AbstractExtractor<TransactionType>>;
9
+ newExtractors: Array<AbstractExtractor<TransactionType>>;
10
+ logger: AbstractLogger;
11
+ initializeMutex: Mutex;
12
+ constructor(logger?: AbstractLogger);
13
+ abstract name: () => string;
14
+ /**
15
+ * fork blocks from specific height from scanner.
16
+ * @param height: selected height
17
+ */
18
+ protected forkBlock: (height: number) => Promise<void>;
19
+ /**
20
+ * process a block and all of its transactions. store any information into database
21
+ * @param block: selected block
22
+ * @param transactions: list of transaction for selected block
23
+ */
24
+ protected processBlockTransactions: (block: Block, transactions: Array<TransactionType>) => Promise<false | import("../..").BlockEntity>;
25
+ /**
26
+ * register a nre extractor to scanner.
27
+ * @param extractor
28
+ */
29
+ registerExtractor: (extractor: AbstractExtractor<TransactionType>) => Promise<void>;
30
+ /**
31
+ * remove an extractor from scanner
32
+ * @param extractor
33
+ */
34
+ removeExtractor: (extractor: AbstractExtractor<TransactionType>) => Promise<void>;
35
+ /**
36
+ * Initialize all specified extractors and store the updated status
37
+ * @param extractorIds
38
+ * @param height
39
+ */
40
+ private initializeExtractors;
41
+ /**
42
+ * Initializes the extractors if they're not synced or not initialized yet,
43
+ * and update the active extractors list
44
+ * @param block
45
+ */
46
+ protected verifyExtractorsInitialization: (block: BlockInfo) => Promise<void>;
47
+ }
48
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../../lib/scanner/abstract/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAe,MAAM,+BAA+B,CAAC;AAE5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,8BAAsB,eAAe,CAAC,eAAe;IACnD,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,KAAK,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,KAAK,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,cAAc,CAAC;IACvB,eAAe,EAAE,KAAK,CAAC;gBAEX,MAAM,CAAC,EAAE,cAAc;IAOnC,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC;IAE5B;;;OAGG;IACH,SAAS,CAAC,SAAS,WAAkB,MAAM,mBAuBzC;IAEF;;;;OAIG;IACH,SAAS,CAAC,wBAAwB,UACzB,KAAK,gBACE,MAAM,eAAe,CAAC,kDAyBpC;IAEF;;;OAGG;IACH,iBAAiB,cACJ,kBAAkB,eAAe,CAAC,KAC5C,QAAQ,IAAI,CAAC,CAmBd;IAEF;;;OAGG;IACH,eAAe,cACF,kBAAkB,eAAe,CAAC,KAC5C,QAAQ,IAAI,CAAC,CAQd;IAEF;;;;OAIG;IACH,OAAO,CAAC,oBAAoB,CAkB1B;IAEF;;;;OAIG;IACH,SAAS,CAAC,8BAA8B,UAAiB,SAAS,mBA2DhE;CACH"}
@@ -0,0 +1,154 @@
1
+ import { Mutex } from 'await-semaphore';
2
+ import { DummyLogger } from '@rosen-bridge/abstract-logger';
3
+ import { difference, remove } from 'lodash-es';
4
+ export class AbstractScanner {
5
+ action;
6
+ extractors;
7
+ newExtractors;
8
+ logger;
9
+ initializeMutex;
10
+ constructor(logger) {
11
+ this.extractors = [];
12
+ this.newExtractors = [];
13
+ this.logger = logger ? logger : new DummyLogger();
14
+ this.initializeMutex = new Mutex();
15
+ }
16
+ /**
17
+ * fork blocks from specific height from scanner.
18
+ * @param height: selected height
19
+ */
20
+ forkBlock = async (height) => {
21
+ let lastBlock = await this.action.getLastSavedBlock();
22
+ while (lastBlock && lastBlock.height >= height) {
23
+ this.logger.debug(`Reverting block ${lastBlock.hash} at height ${lastBlock.height}`);
24
+ await this.action.revertBlockStatus(lastBlock.height, lastBlock.parentHash, this.extractors.map((e) => e.getId()));
25
+ for (const extractor of this.extractors) {
26
+ try {
27
+ await extractor.forkBlock(lastBlock.hash);
28
+ }
29
+ catch (e) {
30
+ this.logger.error(`An error occurred during fork block in extractor ${extractor.getId()}: ${e}`);
31
+ }
32
+ }
33
+ await this.action.removeBlocksFromHeight(lastBlock.height);
34
+ lastBlock = await this.action.getBlockAtHeight(lastBlock.height - 1);
35
+ }
36
+ };
37
+ /**
38
+ * process a block and all of its transactions. store any information into database
39
+ * @param block: selected block
40
+ * @param transactions: list of transaction for selected block
41
+ */
42
+ processBlockTransactions = async (block, transactions) => {
43
+ const savedBlock = await this.action.saveBlock(block);
44
+ if (typeof savedBlock === 'boolean') {
45
+ return false;
46
+ }
47
+ let success = true;
48
+ for (const extractor of this.extractors) {
49
+ if (!(await extractor.processTransactions(transactions, savedBlock))) {
50
+ success = false;
51
+ break;
52
+ }
53
+ }
54
+ if (success &&
55
+ (await this.action.updateBlockStatus(block.height, block.hash, this.extractors.map((e) => e.getId())))) {
56
+ return savedBlock;
57
+ }
58
+ return false;
59
+ };
60
+ /**
61
+ * register a nre extractor to scanner.
62
+ * @param extractor
63
+ */
64
+ registerExtractor = async (extractor) => {
65
+ const notRegisteredIn = (extractors) => extractors.filter((extractorItem) => extractorItem.getId() === extractor.getId()).length === 0;
66
+ const release = await this.initializeMutex.acquire();
67
+ if (notRegisteredIn(this.extractors) &&
68
+ notRegisteredIn(this.newExtractors)) {
69
+ this.newExtractors.push(extractor);
70
+ }
71
+ else {
72
+ this.logger.warn(`Extractor with id ${extractor.getId()} is already registered`);
73
+ }
74
+ release();
75
+ };
76
+ /**
77
+ * remove an extractor from scanner
78
+ * @param extractor
79
+ */
80
+ removeExtractor = async (extractor) => {
81
+ const removeFn = (ex) => ex.getId() === extractor.getId();
82
+ const release = await this.initializeMutex.acquire();
83
+ remove(this.extractors, removeFn);
84
+ remove(this.newExtractors, removeFn);
85
+ release();
86
+ };
87
+ /**
88
+ * Initialize all specified extractors and store the updated status
89
+ * @param extractorIds
90
+ * @param height
91
+ */
92
+ initializeExtractors = async (extractorIds, block) => {
93
+ const allExtractors = [...this.extractors, ...this.newExtractors];
94
+ const initRequiredExtractors = allExtractors.filter((extractor) => extractorIds.includes(extractor.getId()));
95
+ for (const extractor of initRequiredExtractors) {
96
+ this.logger.info(`Initializing [${extractor.getId()}] boxes`);
97
+ await extractor.initializeBoxes(block);
98
+ await this.action.updateOrInsertExtractorStatus(extractor.getId(), block.height, block.hash);
99
+ this.logger.debug(`Initialization finished for [${extractor.getId()}]`);
100
+ }
101
+ };
102
+ /**
103
+ * Initializes the extractors if they're not synced or not initialized yet,
104
+ * and update the active extractors list
105
+ * @param block
106
+ */
107
+ verifyExtractorsInitialization = async (block) => {
108
+ const getIds = (extractors) => {
109
+ return extractors.map((extractor) => extractor.getId());
110
+ };
111
+ this.logger.debug(`Initializing extractors for block [${block.height}]`);
112
+ let success = true;
113
+ const release = await this.initializeMutex.acquire();
114
+ try {
115
+ const extractorsStatus = await this.action.getExtractorsStatus([
116
+ ...getIds(this.extractors),
117
+ ...getIds(this.newExtractors),
118
+ ]);
119
+ this.logger.debug(`Stored extractors status are [${JSON.stringify(extractorsStatus)}]`);
120
+ // Find extractors not synced with the latest height
121
+ const notSyncedExtractorIds = extractorsStatus
122
+ .filter((es) => es.updateBlockHash != block.hash)
123
+ .map((es) => es.extractorId);
124
+ if (notSyncedExtractorIds.length > 0)
125
+ this.logger.debug(`Old not synced extractors are ${notSyncedExtractorIds}`);
126
+ // Find new extractors not available in database
127
+ const storedExtractorIds = extractorsStatus.map((es) => es.extractorId);
128
+ const newRegisteredExtractorIds = difference(getIds(this.newExtractors), storedExtractorIds);
129
+ if (newRegisteredExtractorIds.length > 0)
130
+ this.logger.debug(`New registered extractors are ${newRegisteredExtractorIds}`);
131
+ // Initialize required extractors
132
+ const initRequiredExtractors = [
133
+ ...newRegisteredExtractorIds,
134
+ ...notSyncedExtractorIds,
135
+ ];
136
+ if (initRequiredExtractors.length > 0) {
137
+ this.logger.info(`Initializing ${initRequiredExtractors.length} extractor(s) for [${this.name()}]`, { initRequiredExtractors });
138
+ await this.initializeExtractors(initRequiredExtractors, block);
139
+ }
140
+ this.extractors.push(...this.newExtractors);
141
+ this.newExtractors = [];
142
+ }
143
+ catch (e) {
144
+ this.logger.warn(`Initialization for extractors failed with error ${e}`);
145
+ success = false;
146
+ }
147
+ finally {
148
+ release();
149
+ }
150
+ if (!success)
151
+ throw new Error(`Initialization failed for new extractors ${getIds(this.newExtractors)}`);
152
+ };
153
+ }
154
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Nhbm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL2xpYi9zY2FubmVyL2Fic3RyYWN0L3NjYW5uZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXhDLE9BQU8sRUFBa0IsV0FBVyxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDNUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFLL0MsTUFBTSxPQUFnQixlQUFlO0lBQ25DLE1BQU0sQ0FBZ0I7SUFDdEIsVUFBVSxDQUE0QztJQUN0RCxhQUFhLENBQTRDO0lBQ3pELE1BQU0sQ0FBaUI7SUFDdkIsZUFBZSxDQUFRO0lBRXZCLFlBQVksTUFBdUI7UUFDakMsSUFBSSxDQUFDLFVBQVUsR0FBRyxFQUFFLENBQUM7UUFDckIsSUFBSSxDQUFDLGFBQWEsR0FBRyxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxXQUFXLEVBQUUsQ0FBQztRQUNsRCxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksS0FBSyxFQUFFLENBQUM7SUFDckMsQ0FBQztJQUlEOzs7T0FHRztJQUNPLFNBQVMsR0FBRyxLQUFLLEVBQUUsTUFBYyxFQUFFLEVBQUU7UUFDN0MsSUFBSSxTQUFTLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDdEQsT0FBTyxTQUFTLElBQUksU0FBUyxDQUFDLE1BQU0sSUFBSSxNQUFNLEVBQUU7WUFDOUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQ2YsbUJBQW1CLFNBQVMsQ0FBQyxJQUFJLGNBQWMsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUNsRSxDQUFDO1lBQ0YsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGlCQUFpQixD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Q3pFLE9BQU8sR0FBRyxLQUFLLENBQUM7U0FDakI7Z0JBQVM7WUFDUixPQUFPLEVBQUUsQ0FBQztTQUNYO1FBQ0QsSUFBSSxDQUFDLE9BQU87WUFDVixNQUFNLElBQUksS0FBSyxDQUNiLDRDQUE0QyxNQUFNLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQ3pFLENBQUM7SUFDTixDQUFDLENBQUM7Q0FDSCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IE11dGV4IH0gZnJvbSAnYXdhaXQtc2VtYXBob3JlJztcbmltcG9ydCB7IEJsb2NrLCBCbG9ja0luZm8gfSBmcm9tICdAcm9zZW4tYnJpZGdlL3NjYW5uZXItaW50ZXJmYWNlcyc7XG5pbXBvcnQgeyBBYnN0cmFjdExvZ2dlciwgRHVtbXlMb2dnZXIgfSBmcm9tICdAcm9zZW4tYnJpZGdlL2Fic3RyYWN0LWxvZ2dlcic7XG5pbXBvcnQgeyBkaWZmZXJlbmNlLCByZW1vdmUgfSBmcm9tICdsb2Rhc2gtZXMnO1xuaW1wb3J0IHsgQWJzdHJhY3RFeHRyYWN0b3IgfSBmcm9tICdAcm9zZW4tYnJpZGdlL2Fic3RyYWN0LWV4dHJhY3Rvcic7XG5cbmltcG9ydCB7IEJsb2NrRGJBY3Rpb24gfSBmcm9tICcuLi9hY3Rpb24nO1xuXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgQWJzdHJhY3RTY2FubmVyPFRyYW5zYWN0aW9uVHlwZT4ge1xuICBhY3Rpb246IEJsb2NrRGJBY3Rpb247XG4gIGV4dHJhY3RvcnM6IEFycmF5PEFic3RyYWN0RXh0cmFjdG9yPFRyYW5zYWN0aW9uVHlwZT4+O1xuICBuZXdFeHRyYWN0b3JzOiBBcnJheTxBYnN0cmFjdEV4dHJhY3RvcjxUcmFuc2FjdGlvblR5cGU+PjtcbiAgbG9nZ2VyOiBBYnN0cmFjdExvZ2dlcjtcbiAgaW5pdGlhbGl6ZU11dGV4OiBNdXRleDtcblxuICBjb25zdHJ1Y3Rvcihsb2dnZXI/OiBBYnN0cmFjdExvZ2dlcikge1xuICAgIHRoaXMuZXh0cmFjdG9ycyA9IFtdO1xuICAgIHRoaXMubmV3RXh0cmFjdG9ycyA9IFtdO1xuICAgIHRoaXMubG9nZ2VyID0gbG9nZ2VyID8gbG9nZ2VyIDogbmV3IER1bW15TG9nZ2VyKCk7XG4gICAgdGhpcy5pbml0aWFsaXplTXV0ZXggPSBuZXcgTXV0ZXgoKTtcbiAgfVxuXG4gIGFic3RyYWN0IG5hbWU6ICgpID0+IHN0cmluZztcblxuICAvKipcbiAgICogZm9yayBibG9ja3MgZnJvbSBzcGVjaWZpYyBoZWlnaHQgZnJvbSBzY2FubmVyLlxuICAgKiBAcGFyYW0gaGVpZ2h0OiBzZWxlY3RlZCBoZWlnaHRcbiAgICovXG4gIHByb3RlY3RlZCBmb3JrQmxvY2sgPSBhc3luYyAoaGVpZ2h0OiBudW1iZXIpID0+IHtcbiAgICBsZXQgbGFzdEJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0TGFzdFNhdmVkQmxvY2soKTtcbiAgICB3aGlsZSAobGFzdEJsb2NrICYmIGxhc3RCbG9jay5oZWlnaHQgPj0gaGVpZ2h0KSB7XG4gICAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgICAgYFJldmVydGluZyBibG9jayAke2xhc3RCbG9jay5oYXNofSBhdCBoZWlnaHQgJHtsYXN0QmxvY2suaGVpZ2h0fWBcbiAgICAgICk7XG4gICAgICBhd2FpdCB0aGlzLmFjdGlvbi5yZXZlcnRCbG9ja1N0YXR1cyhcbiAgICAgICAgbGFzdEJsb2NrLmhlaWdodCxcbiAgICAgICAgbGFzdEJsb2NrLnBhcmVudEhhc2gsXG4gICAgICAgIHRoaXMuZXh0cmFjdG9ycy5tYXAoKGUpID0+IGUuZ2V0SWQoKSlcbiAgICAgICk7XG4gICAgICBmb3IgKGNvbnN0IGV4dHJhY3RvciBvZiB0aGlzLmV4dHJhY3RvcnMpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBhd2FpdCBleHRyYWN0b3IuZm9ya0Jsb2NrKGxhc3RCbG9jay5oYXNoKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgIHRoaXMubG9nZ2VyLmVycm9yKFxuICAgICAgICAgICAgYEFuIGVycm9yIG9jY3VycmVkIGR1cmluZyBmb3JrIGJsb2NrIGluIGV4dHJhY3RvciAke2V4dHJhY3Rvci5nZXRJZCgpfTogJHtlfWBcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBhd2FpdCB0aGlzLmFjdGlvbi5yZW1vdmVCbG9ja3NGcm9tSGVpZ2h0KGxhc3RCbG9jay5oZWlnaHQpO1xuICAgICAgbGFzdEJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0QmxvY2tBdEhlaWdodChsYXN0QmxvY2suaGVpZ2h0IC0gMSk7XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBwcm9jZXNzIGEgYmxvY2sgYW5kIGFsbCBvZiBpdHMgdHJhbnNhY3Rpb25zLiBzdG9yZSBhbnkgaW5mb3JtYXRpb24gaW50byBkYXRhYmFzZVxuICAgKiBAcGFyYW0gYmxvY2s6IHNlbGVjdGVkIGJsb2NrXG4gICAqIEBwYXJhbSB0cmFuc2FjdGlvbnM6IGxpc3Qgb2YgdHJhbnNhY3Rpb24gZm9yIHNlbGVjdGVkIGJsb2NrXG4gICAqL1xuICBwcm90ZWN0ZWQgcHJvY2Vzc0Jsb2NrVHJhbnNhY3Rpb25zID0gYXN5bmMgKFxuICAgIGJsb2NrOiBCbG9jayxcbiAgICB0cmFuc2FjdGlvbnM6IEFycmF5PFRyYW5zYWN0aW9uVHlwZT5cbiAgKSA9PiB7XG4gICAgY29uc3Qgc2F2ZWRCbG9jayA9IGF3YWl0IHRoaXMuYWN0aW9uLnNhdmVCbG9jayhibG9jayk7XG4gICAgaWYgKHR5cGVvZiBzYXZlZEJsb2NrID09PSAnYm9vbGVhbicpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgbGV0IHN1Y2Nlc3MgPSB0cnVlO1xuICAgIGZvciAoY29uc3QgZXh0cmFjdG9yIG9mIHRoaXMuZXh0cmFjdG9ycykge1xuICAgICAgaWYgKCEoYXdhaXQgZXh0cmFjdG9yLnByb2Nlc3NUcmFuc2FjdGlvbnModHJhbnNhY3Rpb25zLCBzYXZlZEJsb2NrKSkpIHtcbiAgICAgICAgc3VjY2VzcyA9IGZhbHNlO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoXG4gICAgICBzdWNjZXNzICYmXG4gICAgICAoYXdhaXQgdGhpcy5hY3Rpb24udXBkYXRlQmxvY2tTdGF0dXMoXG4gICAgICAgIGJsb2NrLmhlaWdodCxcbiAgICAgICAgYmxvY2suaGFzaCxcbiAgICAgICAgdGhpcy5leHRyYWN0b3JzLm1hcCgoZSkgPT4gZS5nZXRJZCgpKVxuICAgICAgKSlcbiAgICApIHtcbiAgICAgIHJldHVybiBzYXZlZEJsb2NrO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH07XG5cbiAgLyoqXG4gICAqIHJlZ2lzdGVyIGEgbnJlIGV4dHJhY3RvciB0byBzY2FubmVyLlxuICAgKiBAcGFyYW0gZXh0cmFjdG9yXG4gICAqL1xuICByZWdpc3RlckV4dHJhY3RvciA9IGFzeW5jIChcbiAgICBleHRyYWN0b3I6IEFic3RyYWN0RXh0cmFjdG9yPFRyYW5zYWN0aW9uVHlwZT5cbiAgKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gICAgY29uc3Qgbm90UmVnaXN0ZXJlZEluID0gKFxuICAgICAgZXh0cmFjdG9yczogQXJyYXk8QWJzdHJhY3RFeHRyYWN0b3I8VHJhbnNhY3Rpb25UeXBlPj5cbiAgICApID0+XG4gICAgICBleHRyYWN0b3JzLmZpbHRlcihcbiAgICAgICAgKGV4dHJhY3Rvckl0ZW0pID0+IGV4dHJhY3Rvckl0ZW0uZ2V0SWQoKSA9PT0gZXh0cmFjdG9yLmdldElkKClcbiAgICAgICkubGVuZ3RoID09PSAwO1xuICAgIGNvbnN0IHJlbGVhc2UgPSBhd2FpdCB0aGlzLmluaXRpYWxpemVNdXRleC5hY3F1aXJlKCk7XG4gICAgaWYgKFxuICAgICAgbm90UmVnaXN0ZXJlZEluKHRoaXMuZXh0cmFjdG9ycykgJiZcbiAgICAgIG5vdFJlZ2lzdGVyZWRJbih0aGlzLm5ld0V4dHJhY3RvcnMpXG4gICAgKSB7XG4gICAgICB0aGlzLm5ld0V4dHJhY3RvcnMucHVzaChleHRyYWN0b3IpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmxvZ2dlci53YXJuKFxuICAgICAgICBgRXh0cmFjdG9yIHdpdGggaWQgJHtleHRyYWN0b3IuZ2V0SWQoKX0gaXMgYWxyZWFkeSByZWdpc3RlcmVkYFxuICAgICAgKTtcbiAgICB9XG4gICAgcmVsZWFzZSgpO1xuICB9O1xuXG4gIC8qKlxuICAgKiByZW1vdmUgYW4gZXh0cmFjdG9yIGZyb20gc2Nhbm5lclxuICAgKiBAcGFyYW0gZXh0cmFjdG9yXG4gICAqL1xuICByZW1vdmVFeHRyYWN0b3IgPSBhc3luYyAoXG4gICAgZXh0cmFjdG9yOiBBYnN0cmFjdEV4dHJhY3RvcjxUcmFuc2FjdGlvblR5cGU+XG4gICk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgIGNvbnN0IHJlbW92ZUZuID0gKGV4OiBBYnN0cmFjdEV4dHJhY3RvcjxUcmFuc2FjdGlvblR5cGU+KSA9PlxuICAgICAgZXguZ2V0SWQoKSA9PT0gZXh0cmFjdG9yLmdldElkKCk7XG5cbiAgICBjb25zdCByZWxlYXNlID0gYXdhaXQgdGhpcy5pbml0aWFsaXplTXV0ZXguYWNxdWlyZSgpO1xuICAgIHJlbW92ZSh0aGlzLmV4dHJhY3RvcnMsIHJlbW92ZUZuKTtcbiAgICByZW1vdmUodGhpcy5uZXdFeHRyYWN0b3JzLCByZW1vdmVGbik7XG4gICAgcmVsZWFzZSgpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplIGFsbCBzcGVjaWZpZWQgZXh0cmFjdG9ycyBhbmQgc3RvcmUgdGhlIHVwZGF0ZWQgc3RhdHVzXG4gICAqIEBwYXJhbSBleHRyYWN0b3JJZHNcbiAgICogQHBhcmFtIGhlaWdodFxuICAgKi9cbiAgcHJpdmF0ZSBpbml0aWFsaXplRXh0cmFjdG9ycyA9IGFzeW5jIChcbiAgICBleHRyYWN0b3JJZHM6IHN0cmluZ1tdLFxuICAgIGJsb2NrOiBCbG9ja0luZm9cbiAgKSA9PiB7XG4gICAgY29uc3QgYWxsRXh0cmFjdG9ycyA9IFsuLi50aGlzLmV4dHJhY3RvcnMsIC4uLnRoaXMubmV3RXh0cmFjdG9yc107XG4gICAgY29uc3QgaW5pdFJlcXVpcmVkRXh0cmFjdG9ycyA9IGFsbEV4dHJhY3RvcnMuZmlsdGVyKChleHRyYWN0b3IpID0+XG4gICAgICBleHRyYWN0b3JJZHMuaW5jbHVkZXMoZXh0cmFjdG9yLmdldElkKCkpXG4gICAgKTtcbiAgICBmb3IgKGNvbnN0IGV4dHJhY3RvciBvZiBpbml0UmVxdWlyZWRFeHRyYWN0b3JzKSB7XG4gICAgICB0aGlzLmxvZ2dlci5pbmZvKGBJbml0aWFsaXppbmcgWyR7ZXh0cmFjdG9yLmdldElkKCl9XSBib3hlc2ApO1xuICAgICAgYXdhaXQgZXh0cmFjdG9yLmluaXRpYWxpemVCb3hlcyhibG9jayk7XG4gICAgICBhd2FpdCB0aGlzLmFjdGlvbi51cGRhdGVPckluc2VydEV4dHJhY3RvclN0YXR1cyhcbiAgICAgICAgZXh0cmFjdG9yLmdldElkKCksXG4gICAgICAgIGJsb2NrLmhlaWdodCxcbiAgICAgICAgYmxvY2suaGFzaFxuICAgICAgKTtcbiAgICAgIHRoaXMubG9nZ2VyLmRlYnVnKGBJbml0aWFsaXphdGlvbiBmaW5pc2hlZCBmb3IgWyR7ZXh0cmFjdG9yLmdldElkKCl9XWApO1xuICAgIH1cbiAgfTtcblxuICAvKipcbiAgICogSW5pdGlhbGl6ZXMgdGhlIGV4dHJhY3RvcnMgaWYgdGhleSdyZSBub3Qgc3luY2VkIG9yIG5vdCBpbml0aWFsaXplZCB5ZXQsXG4gICAqIGFuZCB1cGRhdGUgdGhlIGFjdGl2ZSBleHRyYWN0b3JzIGxpc3RcbiAgICogQHBhcmFtIGJsb2NrXG4gICAqL1xuICBwcm90ZWN0ZWQgdmVyaWZ5RXh0cmFjdG9yc0luaXRpYWxpemF0aW9uID0gYXN5bmMgKGJsb2NrOiBCbG9ja0luZm8pID0+IHtcbiAgICBjb25zdCBnZXRJZHMgPSAoZXh0cmFjdG9yczogQXJyYXk8QWJzdHJhY3RFeHRyYWN0b3I8VHJhbnNhY3Rpb25UeXBlPj4pID0+IHtcbiAgICAgIHJldHVybiBleHRyYWN0b3JzLm1hcCgoZXh0cmFjdG9yKSA9PiBleHRyYWN0b3IuZ2V0SWQoKSk7XG4gICAgfTtcbiAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhgSW5pdGlhbGl6aW5nIGV4dHJhY3RvcnMgZm9yIGJsb2NrIFske2Jsb2NrLmhlaWdodH1dYCk7XG4gICAgbGV0IHN1Y2Nlc3MgPSB0cnVlO1xuICAgIGNvbnN0IHJlbGVhc2UgPSBhd2FpdCB0aGlzLmluaXRpYWxpemVNdXRleC5hY3F1aXJlKCk7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGV4dHJhY3RvcnNTdGF0dXMgPSBhd2FpdCB0aGlzLmFjdGlvbi5nZXRFeHRyYWN0b3JzU3RhdHVzKFtcbiAgICAgICAgLi4uZ2V0SWRzKHRoaXMuZXh0cmFjdG9ycyksXG4gICAgICAgIC4uLmdldElkcyh0aGlzLm5ld0V4dHJhY3RvcnMpLFxuICAgICAgXSk7XG4gICAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgICAgYFN0b3JlZCBleHRyYWN0b3JzIHN0YXR1cyBhcmUgWyR7SlNPTi5zdHJpbmdpZnkoZXh0cmFjdG9yc1N0YXR1cyl9XWBcbiAgICAgICk7XG4gICAgICAvLyBGaW5kIGV4dHJhY3RvcnMgbm90IHN5bmNlZCB3aXRoIHRoZSBsYXRlc3QgaGVpZ2h0XG4gICAgICBjb25zdCBub3RTeW5jZWRFeHRyYWN0b3JJZHMgPSBleHRyYWN0b3JzU3RhdHVzXG4gICAgICAgIC5maWx0ZXIoKGVzKSA9PiBlcy51cGRhdGVCbG9ja0hhc2ggIT0gYmxvY2suaGFzaClcbiAgICAgICAgLm1hcCgoZXMpID0+IGVzLmV4dHJhY3RvcklkKTtcbiAgICAgIGlmIChub3RTeW5jZWRFeHRyYWN0b3JJZHMubGVuZ3RoID4gMClcbiAgICAgICAgdGhpcy5sb2dnZXIuZGVidWcoXG4gICAgICAgICAgYE9sZCBub3Qgc3luY2VkIGV4dHJhY3RvcnMgYXJlICR7bm90U3luY2VkRXh0cmFjdG9ySWRzfWBcbiAgICAgICAgKTtcbiAgICAgIC8vIEZpbmQgbmV3IGV4dHJhY3RvcnMgbm90IGF2YWlsYWJsZSBpbiBkYXRhYmFzZVxuICAgICAgY29uc3Qgc3RvcmVkRXh0cmFjdG9ySWRzID0gZXh0cmFjdG9yc1N0YXR1cy5tYXAoKGVzKSA9PiBlcy5leHRyYWN0b3JJZCk7XG4gICAgICBjb25zdCBuZXdSZWdpc3RlcmVkRXh0cmFjdG9ySWRzID0gZGlmZmVyZW5jZShcbiAgICAgICAgZ2V0SWRzKHRoaXMubmV3RXh0cmFjdG9ycyksXG4gICAgICAgIHN0b3JlZEV4dHJhY3Rvcklkc1xuICAgICAgKTtcbiAgICAgIGlmIChuZXdSZWdpc3RlcmVkRXh0cmFjdG9ySWRzLmxlbmd0aCA+IDApXG4gICAgICAgIHRoaXMubG9nZ2VyLmRlYnVnKFxuICAgICAgICAgIGBOZXcgcmVnaXN0ZXJlZCBleHRyYWN0b3JzIGFyZSAke25ld1JlZ2lzdGVyZWRFeHRyYWN0b3JJZHN9YFxuICAgICAgICApO1xuICAgICAgLy8gSW5pdGlhbGl6ZSByZXF1aXJlZCBleHRyYWN0b3JzXG4gICAgICBjb25zdCBpbml0UmVxdWlyZWRFeHRyYWN0b3JzID0gW1xuICAgICAgICAuLi5uZXdSZWdpc3RlcmVkRXh0cmFjdG9ySWRzLFxuICAgICAgICAuLi5ub3RTeW5jZWRFeHRyYWN0b3JJZHMsXG4gICAgICBdO1xuICAgICAgaWYgKGluaXRSZXF1aXJlZEV4dHJhY3RvcnMubGVuZ3RoID4gMCkge1xuICAgICAgICB0aGlzLmxvZ2dlci5pbmZvKFxuICAgICAgICAgIGBJbml0aWFsaXppbmcgJHtcbiAgICAgICAgICAgIGluaXRSZXF1aXJlZEV4dHJhY3RvcnMubGVuZ3RoXG4gICAgICAgICAgfSBleHRyYWN0b3IocykgZm9yIFske3RoaXMubmFtZSgpfV1gLFxuICAgICAgICAgIHsgaW5pdFJlcXVpcmVkRXh0cmFjdG9ycyB9XG4gICAgICAgICk7XG4gICAgICAgIGF3YWl0IHRoaXMuaW5pdGlhbGl6ZUV4dHJhY3RvcnMoaW5pdFJlcXVpcmVkRXh0cmFjdG9ycywgYmxvY2spO1xuICAgICAgfVxuICAgICAgdGhpcy5leHRyYWN0b3JzLnB1c2goLi4udGhpcy5uZXdFeHRyYWN0b3JzKTtcbiAgICAgIHRoaXMubmV3RXh0cmFjdG9ycyA9IFtdO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIHRoaXMubG9nZ2VyLndhcm4oYEluaXRpYWxpemF0aW9uIGZvciBleHRyYWN0b3JzIGZhaWxlZCB3aXRoIGVycm9yICR7ZX1gKTtcbiAgICAgIHN1Y2Nlc3MgPSBmYWxzZTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgcmVsZWFzZSgpO1xuICAgIH1cbiAgICBpZiAoIXN1Y2Nlc3MpXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBJbml0aWFsaXphdGlvbiBmYWlsZWQgZm9yIG5ldyBleHRyYWN0b3JzICR7Z2V0SWRzKHRoaXMubmV3RXh0cmFjdG9ycyl9YFxuICAgICAgKTtcbiAgfTtcbn1cbiJdfQ==
@@ -0,0 +1,29 @@
1
+ import { AbstractScanner } from './scanner';
2
+ import { Block } from '@rosen-bridge/scanner-interfaces';
3
+ import { Mutex } from 'await-semaphore';
4
+ import { AbstractLogger } from '@rosen-bridge/abstract-logger';
5
+ declare abstract class WebSocketScanner<TransactionType> extends AbstractScanner<TransactionType> {
6
+ protected scannerName: string;
7
+ protected suffix?: string | undefined;
8
+ readonly maxTryBlock: number;
9
+ protected mutex: Mutex;
10
+ protected constructor(scannerName: string, logger?: AbstractLogger, maxTryBlock?: number, suffix?: string | undefined);
11
+ name: () => string;
12
+ abstract start: () => Promise<void>;
13
+ abstract stop: () => Promise<void>;
14
+ protected tryRunningFunction: (fn: () => Promise<boolean>, msg: string) => Promise<boolean>;
15
+ /**
16
+ * Handle new block arrived.
17
+ * save block and its transactions.
18
+ * @param block
19
+ * @param transactions
20
+ */
21
+ protected stepForward: (block: Block, transactions: Array<TransactionType>) => Promise<void>;
22
+ /**
23
+ * Handle forking a block
24
+ * @param block
25
+ */
26
+ protected stepBackward: (block: Block) => Promise<void>;
27
+ }
28
+ export { WebSocketScanner };
29
+ //# sourceMappingURL=webSocketScanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webSocketScanner.d.ts","sourceRoot":"","sources":["../../../lib/scanner/abstract/webSocketScanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,kCAAkC,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAI/D,uBAAe,gBAAgB,CAC7B,eAAe,CACf,SAAQ,eAAe,CAAC,eAAe,CAAC;IAMtC,SAAS,CAAC,WAAW,EAAE,MAAM;IAG7B,SAAS,CAAC,MAAM,CAAC;IARnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,SAAS,CAAC,KAAK,QAAe;IAE9B,SAAS,aACG,WAAW,EAAE,MAAM,EAC7B,MAAM,CAAC,EAAE,cAAc,EACvB,WAAW,GAAE,MAA8B,EACjC,MAAM,CAAC,oBAAQ;IAK3B,IAAI,eAAmE;IAEvE,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC,SAAS,CAAC,kBAAkB,OACtB,MAAM,QAAQ,OAAO,CAAC,OACrB,MAAM,KACV,QAAQ,OAAO,CAAC,CAUjB;IAEF;;;;;OAKG;IACH,SAAS,CAAC,WAAW,UACZ,KAAK,gBACE,MAAM,eAAe,CAAC,mBAkCpC;IAEF;;;OAGG;IACH,SAAS,CAAC,YAAY,UAAiB,KAAK,mBAe1C;CACH;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { AbstractScanner } from './scanner';
2
+ import { Mutex } from 'await-semaphore';
3
+ const DEFAULT_MAX_TRY_BLOCK = 10;
4
+ class WebSocketScanner extends AbstractScanner {
5
+ scannerName;
6
+ suffix;
7
+ maxTryBlock;
8
+ mutex = new Mutex();
9
+ constructor(scannerName, logger, maxTryBlock = DEFAULT_MAX_TRY_BLOCK, suffix) {
10
+ super(logger);
11
+ this.scannerName = scannerName;
12
+ this.suffix = suffix;
13
+ this.maxTryBlock = maxTryBlock;
14
+ }
15
+ name = () => this.scannerName + (this.suffix ? `-${this.suffix}` : '');
16
+ tryRunningFunction = async (fn, msg) => {
17
+ for (let tryRound = 1; tryRound <= this.maxTryBlock; tryRound++) {
18
+ if (await fn())
19
+ return true;
20
+ }
21
+ this.logger.error(`${msg} can not be proceed in ${this.maxTryBlock} try. scanner restarted automatically`);
22
+ await this.stop();
23
+ await this.start();
24
+ return false;
25
+ };
26
+ /**
27
+ * Handle new block arrived.
28
+ * save block and its transactions.
29
+ * @param block
30
+ * @param transactions
31
+ */
32
+ stepForward = async (block, transactions) => {
33
+ const release = await this.mutex.acquire();
34
+ await this.tryRunningFunction(async () => {
35
+ try {
36
+ await this.forkBlock(block.height);
37
+ const lastSavedBlock = await this.action.getLastSavedBlock();
38
+ if (lastSavedBlock && block.parentHash !== lastSavedBlock.hash) {
39
+ this.logger.error('It seems saved block is not valid in scanner.');
40
+ return false;
41
+ }
42
+ else {
43
+ await this.verifyExtractorsInitialization({
44
+ height: block.height - 1,
45
+ hash: block.parentHash,
46
+ });
47
+ const res = await this.processBlockTransactions(block, transactions);
48
+ if (res === false) {
49
+ this.logger.error(`Can not process block at height ${block.height}`);
50
+ }
51
+ else {
52
+ return true;
53
+ }
54
+ }
55
+ }
56
+ catch (e) {
57
+ this.logger.warn(`unknown error occurred ${e}`);
58
+ if (e instanceof Error && e.stack) {
59
+ this.logger.warn(e.stack);
60
+ }
61
+ }
62
+ return false;
63
+ }, `Block at height ${block.height}`);
64
+ release();
65
+ };
66
+ /**
67
+ * Handle forking a block
68
+ * @param block
69
+ */
70
+ stepBackward = async (block) => {
71
+ const release = await this.mutex.acquire();
72
+ await this.tryRunningFunction(async () => {
73
+ try {
74
+ await this.forkBlock(block.height + 1);
75
+ return true;
76
+ }
77
+ catch (e) {
78
+ this.logger.error(`unknown error occurred ${e}`);
79
+ if (e instanceof Error && e.stack) {
80
+ this.logger.warn(e.stack);
81
+ }
82
+ }
83
+ return false;
84
+ }, `Forking block at height ${block.height}`);
85
+ release();
86
+ };
87
+ }
88
+ export { WebSocketScanner };
89
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2ViU29ja2V0U2Nhbm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL2xpYi9zY2FubmVyL2Fic3RyYWN0L3dlYlNvY2tldFNjYW5uZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUU1QyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFHeEMsTUFBTSxxQkFBcUIsR0FBRyxFQUFFLENBQUM7QUFFakMsTUFBZSxnQkFFYixTQUFRLGVBQWdDO0lBTTVCO0lBR0E7SUFSSCxXQUFXLENBQVM7SUFFbkIsS0FBSyxHQUFHLElBQUksS0FBSyxFQUFFLENBQUM7SUFFOUIsWUFDWSxXQUFtQixFQUM3QixNQUF1QixFQUN2QixjQUFzQixxQkFBcUIsRUFDakMsTUFBZTtRQUV6QixLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7UUFMSixnQkFBVyxHQUFYLFdBQVcsQ0FBUTtRQUduQixXQUFNLEdBQU4sTUFBTSxDQUFTO1FBR3pCLElBQUksQ0FBQyxXQUFXLEdBQUcsV0FBVyxDQUFDO0lBQ2pDLENBQUM7SUFDRCxJQUFJLEdBQUcsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUs3RCxrQkFBa0IsR0FBRyxLQUFLLEVBQ2xDLEVBQTBCLEVBQzFCLEdBQVcsRUFDTyxFQUFFO1FBQ3BCLEtBQUssSUFBSSxRQUFRLEdBQUcsQ0FBQyxFQUFFLFFBQVEsSUFBSSxJQUFJLENBQUMsV0FBVyxFQUFFLFFBQVEsRUFBRSxFQUFFO1lBQy9ELElBQUksTUFBTSxFQUFFLEVBQUU7Z0JBQUUsT0FBTyxJQUFJLENBQUM7U0FDN0I7UUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUss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QUEyQixLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUM5QyxPQUFPLEVBQUUsQ0FBQztJQUNaLENBQUMsQ0FBQztDQUNIO0FBRUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBYnN0cmFjdFNjYW5uZXIgfSBmcm9tICcuL3NjYW5uZXInO1xuaW1wb3J0IHsgQmxvY2sgfSBmcm9tICdAcm9zZW4tYnJpZGdlL3NjYW5uZXItaW50ZXJmYWNlcyc7XG5pbXBvcnQgeyBNdXRleCB9IGZyb20gJ2F3YWl0LXNlbWFwaG9yZSc7XG5pbXBvcnQgeyBBYnN0cmFjdExvZ2dlciB9IGZyb20gJ0Byb3Nlbi1icmlkZ2UvYWJzdHJhY3QtbG9nZ2VyJztcblxuY29uc3QgREVGQVVMVF9NQVhfVFJZX0JMT0NLID0gMTA7XG5cbmFic3RyYWN0IGNsYXNzIFdlYlNvY2tldFNjYW5uZXI8XG4gIFRyYW5zYWN0aW9uVHlwZVxuPiBleHRlbmRzIEFic3RyYWN0U2Nhbm5lcjxUcmFuc2FjdGlvblR5cGU+IHtcbiAgcmVhZG9ubHkgbWF4VHJ5QmxvY2s6IG51bWJlcjtcblxuICBwcm90ZWN0ZWQgbXV0ZXggPSBuZXcgTXV0ZXgoKTtcblxuICBwcm90ZWN0ZWQgY29uc3RydWN0b3IoXG4gICAgcHJvdGVjdGVkIHNjYW5uZXJOYW1lOiBzdHJpbmcsXG4gICAgbG9nZ2VyPzogQWJzdHJhY3RMb2dnZXIsXG4gICAgbWF4VHJ5QmxvY2s6IG51bWJlciA9IERFRkFVTFRfTUFYX1RSWV9CTE9DSyxcbiAgICBwcm90ZWN0ZWQgc3VmZml4Pzogc3RyaW5nXG4gICkge1xuICAgIHN1cGVyKGxvZ2dlcik7XG4gICAgdGhpcy5tYXhUcnlCbG9jayA9IG1heFRyeUJsb2NrO1xuICB9XG4gIG5hbWUgPSAoKSA9PiB0aGlzLnNjYW5uZXJOYW1lICsgKHRoaXMuc3VmZml4ID8gYC0ke3RoaXMuc3VmZml4fWAgOiAnJyk7XG5cbiAgYWJzdHJhY3Qgc3RhcnQ6ICgpID0+IFByb21pc2U8dm9pZD47XG4gIGFic3RyYWN0IHN0b3A6ICgpID0+IFByb21pc2U8dm9pZD47XG5cbiAgcHJvdGVjdGVkIHRyeVJ1bm5pbmdGdW5jdGlvbiA9IGFzeW5jIChcbiAgICBmbjogKCkgPT4gUHJvbWlzZTxib29sZWFuPixcbiAgICBtc2c6IHN0cmluZ1xuICApOiBQcm9taXNlPGJvb2xlYW4+ID0+IHtcbiAgICBmb3IgKGxldCB0cnlSb3VuZCA9IDE7IHRyeVJvdW5kIDw9IHRoaXMubWF4VHJ5QmxvY2s7IHRyeVJvdW5kKyspIHtcbiAgICAgIGlmIChhd2FpdCBmbigpKSByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgdGhpcy5sb2dnZXIuZXJyb3IoXG4gICAgICBgJHttc2d9IGNhbiBub3QgYmUgcHJvY2VlZCBpbiAke3RoaXMubWF4VHJ5QmxvY2t9IHRyeS4gc2Nhbm5lciByZXN0YXJ0ZWQgYXV0b21hdGljYWxseWBcbiAgICApO1xuICAgIGF3YWl0IHRoaXMuc3RvcCgpO1xuICAgIGF3YWl0IHRoaXMuc3RhcnQoKTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH07XG5cbiAgLyoqXG4gICAqIEhhbmRsZSBuZXcgYmxvY2sgYXJyaXZlZC5cbiAgICogc2F2ZSBibG9jayBhbmQgaXRzIHRyYW5zYWN0aW9ucy5cbiAgICogQHBhcmFtIGJsb2NrXG4gICAqIEBwYXJhbSB0cmFuc2FjdGlvbnNcbiAgICovXG4gIHByb3RlY3RlZCBzdGVwRm9yd2FyZCA9IGFzeW5jIChcbiAgICBibG9jazogQmxvY2ssXG4gICAgdHJhbnNhY3Rpb25zOiBBcnJheTxUcmFuc2FjdGlvblR5cGU+XG4gICkgPT4ge1xuICAgIGNvbnN0IHJlbGVhc2UgPSBhd2FpdCB0aGlzLm11dGV4LmFjcXVpcmUoKTtcbiAgICBhd2FpdCB0aGlzLnRyeVJ1bm5pbmdGdW5jdGlvbihhc3luYyAoKSA9PiB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB0aGlzLmZvcmtCbG9jayhibG9jay5oZWlnaHQpO1xuXG4gICAgICAgIGNvbnN0IGxhc3RTYXZlZEJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0TGFzdFNhdmVkQmxvY2soKTtcbiAgICAgICAgaWYgKGxhc3RTYXZlZEJsb2NrICYmIGJsb2NrLnBhcmVudEhhc2ggIT09IGxhc3RTYXZlZEJsb2NrLmhhc2gpIHtcbiAgICAgICAgICB0aGlzLmxvZ2dlci5lcnJvcignSXQgc2VlbXMgc2F2ZWQgYmxvY2sgaXMgbm90IHZhbGlkIGluIHNjYW5uZXIuJyk7XG4gICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGF3YWl0IHRoaXMudmVyaWZ5RXh0cmFjdG9yc0luaXRpYWxpemF0aW9uKHtcbiAgICAgICAgICAgIGhlaWdodDogYmxvY2suaGVpZ2h0IC0gMSxcbiAgICAgICAgICAgIGhhc2g6IGJsb2NrLnBhcmVudEhhc2gsXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgdGhpcy5wcm9jZXNzQmxvY2tUcmFuc2FjdGlvbnMoYmxvY2ssIHRyYW5zYWN0aW9ucyk7XG4gICAgICAgICAgaWYgKHJlcyA9PT0gZmFsc2UpIHtcbiAgICAgICAgICAgIHRoaXMubG9nZ2VyLmVycm9yKFxuICAgICAgICAgICAgICBgQ2FuIG5vdCBwcm9jZXNzIGJsb2NrIGF0IGhlaWdodCAke2Jsb2NrLmhlaWdodH1gXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIud2FybihgdW5rbm93biBlcnJvciBvY2N1cnJlZCAke2V9YCk7XG4gICAgICAgIGlmIChlIGluc3RhbmNlb2YgRXJyb3IgJiYgZS5zdGFjaykge1xuICAgICAgICAgIHRoaXMubG9nZ2VyLndhcm4oZS5zdGFjayk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9LCBgQmxvY2sgYXQgaGVpZ2h0ICR7YmxvY2suaGVpZ2h0fWApO1xuICAgIHJlbGVhc2UoKTtcbiAgfTtcblxuICAvKipcbiAgICogSGFuZGxlIGZvcmtpbmcgYSBibG9ja1xuICAgKiBAcGFyYW0gYmxvY2tcbiAgICovXG4gIHByb3RlY3RlZCBzdGVwQmFja3dhcmQgPSBhc3luYyAoYmxvY2s6IEJsb2NrKSA9PiB7XG4gICAgY29uc3QgcmVsZWFzZSA9IGF3YWl0IHRoaXMubXV0ZXguYWNxdWlyZSgpO1xuICAgIGF3YWl0IHRoaXMudHJ5UnVubmluZ0Z1bmN0aW9uKGFzeW5jICgpID0+IHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMuZm9ya0Jsb2NrKGJsb2NrLmhlaWdodCArIDEpO1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIuZXJyb3IoYHVua25vd24gZXJyb3Igb2NjdXJyZWQgJHtlfWApO1xuICAgICAgICBpZiAoZSBpbnN0YW5jZW9mIEVycm9yICYmIGUuc3RhY2spIHtcbiAgICAgICAgICB0aGlzLmxvZ2dlci53YXJuKGUuc3RhY2spO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfSwgYEZvcmtpbmcgYmxvY2sgYXQgaGVpZ2h0ICR7YmxvY2suaGVpZ2h0fWApO1xuICAgIHJlbGVhc2UoKTtcbiAgfTtcbn1cblxuZXhwb3J0IHsgV2ViU29ja2V0U2Nhbm5lciB9O1xuIl19