@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.
- package/README.md +49 -0
- package/dist/entities/blockEntity.d.ts +17 -0
- package/dist/entities/blockEntity.d.ts.map +1 -0
- package/dist/entities/blockEntity.js +78 -0
- package/dist/entities/extractorStatusEntity.d.ts +7 -0
- package/dist/entities/extractorStatusEntity.d.ts.map +1 -0
- package/dist/entities/extractorStatusEntity.js +37 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/migrations/index.d.ts +7 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +31 -0
- package/dist/migrations/postgres/1688545690867-migration.d.ts +7 -0
- package/dist/migrations/postgres/1688545690867-migration.d.ts.map +1 -0
- package/dist/migrations/postgres/1688545690867-migration.js +30 -0
- package/dist/migrations/postgres/1713803486477-migration.d.ts +7 -0
- package/dist/migrations/postgres/1713803486477-migration.d.ts.map +1 -0
- package/dist/migrations/postgres/1713803486477-migration.js +20 -0
- package/dist/migrations/postgres/1718789786123-migration.d.ts +7 -0
- package/dist/migrations/postgres/1718789786123-migration.d.ts.map +1 -0
- package/dist/migrations/postgres/1718789786123-migration.js +12 -0
- package/dist/migrations/postgres/1722697954558-migration.d.ts +7 -0
- package/dist/migrations/postgres/1722697954558-migration.d.ts.map +1 -0
- package/dist/migrations/postgres/1722697954558-migration.js +52 -0
- package/dist/migrations/postgres/1746701087567-migration.d.ts +7 -0
- package/dist/migrations/postgres/1746701087567-migration.d.ts.map +1 -0
- package/dist/migrations/postgres/1746701087567-migration.js +32 -0
- package/dist/migrations/postgres/1747657653564-migration.d.ts +7 -0
- package/dist/migrations/postgres/1747657653564-migration.d.ts.map +1 -0
- package/dist/migrations/postgres/1747657653564-migration.js +134 -0
- package/dist/migrations/sqlite/1688555497475-migration.d.ts +7 -0
- package/dist/migrations/sqlite/1688555497475-migration.d.ts.map +1 -0
- package/dist/migrations/sqlite/1688555497475-migration.js +29 -0
- package/dist/migrations/sqlite/1713786682123-migration.d.ts +7 -0
- package/dist/migrations/sqlite/1713786682123-migration.d.ts.map +1 -0
- package/dist/migrations/sqlite/1713786682123-migration.js +20 -0
- package/dist/migrations/sqlite/1718789744123-migration.d.ts +7 -0
- package/dist/migrations/sqlite/1718789744123-migration.d.ts.map +1 -0
- package/dist/migrations/sqlite/1718789744123-migration.js +12 -0
- package/dist/migrations/sqlite/1722697111974-migration.d.ts +7 -0
- package/dist/migrations/sqlite/1722697111974-migration.d.ts.map +1 -0
- package/dist/migrations/sqlite/1722697111974-migration.js +112 -0
- package/dist/migrations/sqlite/1746701087234-migration.d.ts +7 -0
- package/dist/migrations/sqlite/1746701087234-migration.d.ts.map +1 -0
- package/dist/migrations/sqlite/1746701087234-migration.js +32 -0
- package/dist/migrations/sqlite/1747655941239-migration.d.ts +7 -0
- package/dist/migrations/sqlite/1747655941239-migration.d.ts.map +1 -0
- package/dist/migrations/sqlite/1747655941239-migration.js +134 -0
- package/dist/scanner/abstract/generalScanner.d.ts +63 -0
- package/dist/scanner/abstract/generalScanner.d.ts.map +1 -0
- package/dist/scanner/abstract/generalScanner.js +173 -0
- package/dist/scanner/abstract/scanner.d.ts +48 -0
- package/dist/scanner/abstract/scanner.d.ts.map +1 -0
- package/dist/scanner/abstract/scanner.js +154 -0
- package/dist/scanner/abstract/webSocketScanner.d.ts +29 -0
- package/dist/scanner/abstract/webSocketScanner.d.ts.map +1 -0
- package/dist/scanner/abstract/webSocketScanner.js +89 -0
- package/dist/scanner/action.d.ts +83 -0
- package/dist/scanner/action.d.ts.map +1 -0
- package/dist/scanner/action.js +251 -0
- package/dist/scanner/interfaces.d.ts +12 -0
- package/dist/scanner/interfaces.d.ts.map +1 -0
- package/dist/scanner/interfaces.js +2 -0
- package/dist/scanner/network/ConnectorSelectionStrategies.d.ts +27 -0
- package/dist/scanner/network/ConnectorSelectionStrategies.d.ts.map +1 -0
- package/dist/scanner/network/ConnectorSelectionStrategies.js +22 -0
- package/dist/scanner/network/NetworkConnectorManager.d.ts +64 -0
- package/dist/scanner/network/NetworkConnectorManager.d.ts.map +1 -0
- package/dist/scanner/network/NetworkConnectorManager.js +125 -0
- 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhbFNjYW5uZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9saWIvc2Nhbm5lci9hYnN0cmFjdC9nZW5lcmFsU2Nhbm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBTTVDLE9BQU8sTUFBTSxNQUFNLDJCQUEyQixDQUFDO0FBRS9DLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFHMUMsTUFBZSxjQUViLFNBQVEsZUFBZ0M7SUFPOUI7SUFDQTtJQUVBO0lBQ0E7SUFFQTtJQVpPLGFBQWEsQ0FBUztJQUM3QixvQkFBb0IsR0FBdUIsU0FBUyxDQUFDO0lBRS9ELElBQUksR0FBRyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBRXZFLFlBQ1UsV0FBbUIsRUFDbkIsVUFBc0IsRUFDOUIsYUFBcUIsRUFDYixPQUFrRCxFQUNsRCxtQkFBbUIsQ0FBQyxFQUM1QixNQUF1QixFQUNmLE1BQWU7UUFFdkIsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBUk4sZ0JBQVcsR0FBWCxXQUFXLENBQVE7UUFDbkIsZUFBVSxHQUFWLFVBQVUsQ0FBWTtRQUV0QixZQUFPLEdBQVAsT0FBTyxDQUEyQztRQUNsRCxxQkFBZ0IsR0FBaEIsZ0JBQWdCLENBQUk7UUFFcEIsV0FBTSxHQUFOLE1BQU0sQ0FBUztRQUd2Qjs7O1dBR0c7UUFDSCxJQUFJLENBQUMsYUFBYSxHQUFHLGFBQWEsR0FBRyxDQUFDLENBQUM7UUFDdkMsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLGFBQWEsQ0FDN0IsSUFBSSxDQUFDLFVBQVUsRUFDZixJQUFJLENBQUMsV0FBVyxFQUNoQixJQUFJLENB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QUFjLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGlCQUFpQixFQUFFLENBQUM7WUFDM0QsSUFBSSxDQUFDLGNBQWMsRUFBRTtnQkFDbkIsY0FBYyxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2FBQzFDOztnQkFBTSxNQUFNLElBQUksQ0FBQyw4QkFBOEIsQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUNqRSxJQUFJLENBQUMsQ0FBQyxNQUFNLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQyxFQUFFO2dCQUNoQyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsY0FBYyxDQUFDLENBQUM7YUFDeEM7aUJBQU07Z0JBQ0wsTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7YUFDM0I7U0FDRjtRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNENBQTRDLENBQUMsRUFBRSxDQUFDLENBQUM7U0FDcEU7SUFDSCxDQUFDLENBQUM7Q0FDSDtBQUVELE9BQU8sRUFBRSxjQUFjLEVBQUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFic3RyYWN0U2Nhbm5lciB9IGZyb20gJy4vc2Nhbm5lcic7XG5pbXBvcnQge1xuICBBYnN0cmFjdE5ldHdvcmtDb25uZWN0b3IsXG4gIEJsb2NrLFxufSBmcm9tICdAcm9zZW4tYnJpZGdlL3NjYW5uZXItaW50ZXJmYWNlcyc7XG5pbXBvcnQgeyBCbG9ja0VudGl0eSB9IGZyb20gJy4uLy4uL2VudGl0aWVzL2Jsb2NrRW50aXR5JztcbmltcG9ydCBKc29uQkkgZnJvbSAnQHJvc2VuLWJyaWRnZS9qc29uLWJpZ2ludCc7XG5pbXBvcnQgeyBBYnN0cmFjdExvZ2dlciB9IGZyb20gJ0Byb3Nlbi1icmlkZ2UvYWJzdHJhY3QtbG9nZ2VyJztcbmltcG9ydCB7IEJsb2NrRGJBY3Rpb24gfSBmcm9tICcuLi9hY3Rpb24nO1xuaW1wb3J0IHsgRGF0YVNvdXJjZSB9IGZyb20gJ0Byb3Nlbi1icmlkZ2UvZXh0ZW5kZWQtdHlwZW9ybSc7XG5cbmFic3RyYWN0IGNsYXNzIEdlbmVyYWxTY2FubmVyPFxuICBUcmFuc2FjdGlvblR5cGVcbj4gZXh0ZW5kcyBBYnN0cmFjdFNjYW5uZXI8VHJhbnNhY3Rpb25UeXBlPiB7XG4gIHByaXZhdGUgcmVhZG9ubHkgaW5pdGlhbEhlaWdodDogbnVtYmVyO1xuICBwcm90ZWN0ZWQgYmxvY2tDaGFpbkxhc3RIZWlnaHQ6IG51bWJlciB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcblxuICBuYW1lID0gKCkgPT4gdGhpcy5zY2FubmVyTmFtZSArICh0aGlzLnN1ZmZpeCA/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
|