@my-packages-changeit/cardano-scanner 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +138 -0
- package/README.md +38 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +5 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/interfaces/blockFrost.d.ts +9 -0
- package/dist/interfaces/blockFrost.d.ts.map +1 -0
- package/dist/interfaces/blockFrost.js +6 -0
- package/dist/interfaces/index.d.ts +19 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +2 -0
- package/dist/interfaces/koios.d.ts +26 -0
- package/dist/interfaces/koios.d.ts.map +1 -0
- package/dist/interfaces/koios.js +2 -0
- package/dist/network/blockfrost.d.ts +26 -0
- package/dist/network/blockfrost.d.ts.map +1 -0
- package/dist/network/blockfrost.js +76 -0
- package/dist/network/koios.d.ts +26 -0
- package/dist/network/koios.d.ts.map +1 -0
- package/dist/network/koios.js +90 -0
- package/dist/scanner/blockfrost.d.ts +7 -0
- package/dist/scanner/blockfrost.d.ts.map +1 -0
- package/dist/scanner/blockfrost.js +8 -0
- package/dist/scanner/koios.d.ts +7 -0
- package/dist/scanner/koios.d.ts.map +1 -0
- package/dist/scanner/koios.js +8 -0
- package/dist/scanner/ogmios.d.ts +51 -0
- package/dist/scanner/ogmios.d.ts.map +1 -0
- package/dist/scanner/ogmios.js +173 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ChainSynchronizationClient } from '@cardano-ogmios/client/dist/ChainSynchronization';
|
|
2
|
+
import { Point, Transaction } from '@cardano-ogmios/schema';
|
|
3
|
+
import { AbstractLogger } from '@rosen-bridge/abstract-logger';
|
|
4
|
+
import { WebSocketScanner } from '@rosen-bridge/abstract-scanner';
|
|
5
|
+
import { CardanoOgmiosConfig } from '../interfaces';
|
|
6
|
+
declare class CardanoOgmiosScanner extends WebSocketScanner<Transaction> {
|
|
7
|
+
client: ChainSynchronizationClient;
|
|
8
|
+
initPoint: Point;
|
|
9
|
+
host: string;
|
|
10
|
+
port: number;
|
|
11
|
+
useTls: boolean;
|
|
12
|
+
private connected;
|
|
13
|
+
private stopped;
|
|
14
|
+
private reconnectionConfig;
|
|
15
|
+
constructor(config: CardanoOgmiosConfig, logger?: AbstractLogger);
|
|
16
|
+
/**
|
|
17
|
+
* process block when a block forked library call this function
|
|
18
|
+
* @param response: forked block
|
|
19
|
+
* @param requestNext: a function to tell node to send next block
|
|
20
|
+
*/
|
|
21
|
+
private rollBackward;
|
|
22
|
+
/**
|
|
23
|
+
* Step forward when new block arrived ogmios-client call this function
|
|
24
|
+
* @param response: new block
|
|
25
|
+
* @param requestNext: tell library that this block proceeds. pass next block when available
|
|
26
|
+
*/
|
|
27
|
+
private rollForward;
|
|
28
|
+
/**
|
|
29
|
+
* find intersect between stored blocks and blockchain.
|
|
30
|
+
* @param context: blockchain context
|
|
31
|
+
*/
|
|
32
|
+
private findIntersection;
|
|
33
|
+
/**
|
|
34
|
+
* Handles ogmios connection closure
|
|
35
|
+
*/
|
|
36
|
+
private connectionCloseHandler;
|
|
37
|
+
/**
|
|
38
|
+
* start scanner. first find fork point. fork all blocks and request updates from node.
|
|
39
|
+
*/
|
|
40
|
+
start: () => Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* stop ws connection to node.
|
|
43
|
+
*/
|
|
44
|
+
stop: () => Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* @returns The connection status of ogmios scanner
|
|
47
|
+
*/
|
|
48
|
+
getConnectionStatus: () => boolean;
|
|
49
|
+
}
|
|
50
|
+
export { CardanoOgmiosScanner };
|
|
51
|
+
//# sourceMappingURL=ogmios.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ogmios.d.ts","sourceRoot":"","sources":["../../lib/scanner/ogmios.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,0BAA0B,EAE3B,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EAGL,KAAK,EAGL,WAAW,EACZ,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAEL,gBAAgB,EACjB,MAAM,gCAAgC,CAAC;AAQxC,OAAO,EAAE,mBAAmB,EAA4B,MAAM,eAAe,CAAC;AAY9E,cAAM,oBAAqB,SAAQ,gBAAgB,CAAC,WAAW,CAAC;IAC9D,MAAM,EAAE,0BAA0B,CAAC;IACnC,SAAS,EAAE,KAAK,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,kBAAkB,CAA2B;gBAEzC,MAAM,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,cAAc;IAoBhE;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAsBlB;IAEF;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAuBjB;IAEF;;;OAGG;IACH,OAAO,CAAC,gBAAgB,CAkCtB;IAEF;;OAEG;IACH,OAAO,CAAC,sBAAsB,CA6B5B;IAEF;;OAEG;IACH,KAAK,QAAa,OAAO,CAAC,IAAI,CAAC,CAwB7B;IAEF;;OAEG;IACH,IAAI,QAAa,OAAO,CAAC,IAAI,CAAC,CAG5B;IAEF;;OAEG;IACH,mBAAmB,gBAAwB;CAC5C;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createChainSynchronizationClient, } from '@cardano-ogmios/client';
|
|
2
|
+
import { createInteractionContext } from '@cardano-ogmios/client';
|
|
3
|
+
import { findIntersection, } from '@cardano-ogmios/client/dist/ChainSynchronization';
|
|
4
|
+
import { ExponentialBackoff, handleWhen, retry } from 'cockatiel';
|
|
5
|
+
import { BlockDbAction, WebSocketScanner, } from '@rosen-bridge/abstract-scanner';
|
|
6
|
+
import { RECONNECTION_INITIAL_DELAY, RECONNECTION_MAX_DELAY, SLOT_SHELLY_NUMBER, RECONNECTION_MAX_ATTEMPTS, } from '../constants';
|
|
7
|
+
class CardanoOgmiosScanner extends WebSocketScanner {
|
|
8
|
+
client;
|
|
9
|
+
initPoint;
|
|
10
|
+
host;
|
|
11
|
+
port;
|
|
12
|
+
useTls;
|
|
13
|
+
connected;
|
|
14
|
+
stopped;
|
|
15
|
+
reconnectionConfig;
|
|
16
|
+
constructor(config, logger) {
|
|
17
|
+
super('cardano', logger, config.maxTryBlock, config.suffix);
|
|
18
|
+
this.action = new BlockDbAction(config.dataSource, this.name(), logger);
|
|
19
|
+
this.host = config.nodeHostOrIp;
|
|
20
|
+
this.port = config.nodePort;
|
|
21
|
+
this.useTls = config.useTls ?? false;
|
|
22
|
+
this.connected = false;
|
|
23
|
+
this.initPoint = {
|
|
24
|
+
id: config.initialHash,
|
|
25
|
+
slot: config.initialSlot,
|
|
26
|
+
};
|
|
27
|
+
this.reconnectionConfig = {
|
|
28
|
+
initialDelay: config.reconnectionConfig?.initialDelay ?? RECONNECTION_INITIAL_DELAY,
|
|
29
|
+
maxDelay: config.reconnectionConfig?.maxDelay ?? RECONNECTION_MAX_DELAY,
|
|
30
|
+
maxAttempts: config.reconnectionConfig?.maxAttempts ?? RECONNECTION_MAX_ATTEMPTS,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* process block when a block forked library call this function
|
|
35
|
+
* @param response: forked block
|
|
36
|
+
* @param requestNext: a function to tell node to send next block
|
|
37
|
+
*/
|
|
38
|
+
rollBackward = async (response, requestNext) => {
|
|
39
|
+
const hash = response.point.id;
|
|
40
|
+
const savedBlock = await this.action.getBlockWithHash(hash);
|
|
41
|
+
this.logger.debug(`Rolling backward to height ${savedBlock?.height} in scanner ${this.name()}`);
|
|
42
|
+
if (savedBlock) {
|
|
43
|
+
const block = {
|
|
44
|
+
hash: savedBlock.hash,
|
|
45
|
+
height: savedBlock.height,
|
|
46
|
+
parentHash: savedBlock.parentHash,
|
|
47
|
+
extra: savedBlock.extra,
|
|
48
|
+
timestamp: savedBlock.timestamp,
|
|
49
|
+
};
|
|
50
|
+
await this.stepBackward(block);
|
|
51
|
+
}
|
|
52
|
+
requestNext();
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Step forward when new block arrived ogmios-client call this function
|
|
56
|
+
* @param response: new block
|
|
57
|
+
* @param requestNext: tell library that this block proceeds. pass next block when available
|
|
58
|
+
*/
|
|
59
|
+
rollForward = async (response, requestNext) => {
|
|
60
|
+
if (response.block.type === 'praos') {
|
|
61
|
+
const praosBlock = response.block;
|
|
62
|
+
this.logger.debug(`Queueing new block at height ${praosBlock.height} in scanner ${this.name()}`);
|
|
63
|
+
const block = {
|
|
64
|
+
hash: praosBlock.id,
|
|
65
|
+
height: praosBlock.height,
|
|
66
|
+
parentHash: praosBlock.ancestor,
|
|
67
|
+
extra: `${praosBlock.slot}`,
|
|
68
|
+
// Caution: In case of a hard fork and change in slot duration, this must change!
|
|
69
|
+
timestamp: praosBlock.slot + SLOT_SHELLY_NUMBER,
|
|
70
|
+
};
|
|
71
|
+
if (praosBlock.transactions)
|
|
72
|
+
await this.stepForward(block, praosBlock.transactions);
|
|
73
|
+
}
|
|
74
|
+
requestNext();
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* find intersect between stored blocks and blockchain.
|
|
78
|
+
* @param context: blockchain context
|
|
79
|
+
*/
|
|
80
|
+
findIntersection = async (context) => {
|
|
81
|
+
let count = 1;
|
|
82
|
+
let skip = 0;
|
|
83
|
+
while (count !== 0) {
|
|
84
|
+
try {
|
|
85
|
+
const release = await this.mutex.acquire();
|
|
86
|
+
const blocks = await this.action.getLastSavedBlocks(skip, count);
|
|
87
|
+
release();
|
|
88
|
+
if (blocks.length === 0)
|
|
89
|
+
count = 0;
|
|
90
|
+
const points = blocks.length > 0
|
|
91
|
+
? blocks.map((item) => ({
|
|
92
|
+
slot: item.extra ? parseInt(item.extra) : 0,
|
|
93
|
+
id: item.hash,
|
|
94
|
+
}))
|
|
95
|
+
: [this.initPoint];
|
|
96
|
+
const intersect = await findIntersection(context, points);
|
|
97
|
+
const intersectPoint = intersect.intersection;
|
|
98
|
+
let height = 0;
|
|
99
|
+
if (blocks.length) {
|
|
100
|
+
const foundedBlock = blocks.find((item) => (item.hash = intersectPoint.id));
|
|
101
|
+
if (foundedBlock) {
|
|
102
|
+
height = foundedBlock.height;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { point: intersect.intersection, height: height };
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
skip += count;
|
|
109
|
+
count *= 2;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Handles ogmios connection closure
|
|
116
|
+
*/
|
|
117
|
+
connectionCloseHandler = async () => {
|
|
118
|
+
this.connected = false;
|
|
119
|
+
this.logger.warn('Ogmios connection closed');
|
|
120
|
+
if (this.stopped)
|
|
121
|
+
return;
|
|
122
|
+
const retryPolicy = retry(handleWhen((error) => {
|
|
123
|
+
this.logger.warn(`An error occurred while reconnecting to ogmios client: ${error}`);
|
|
124
|
+
return true;
|
|
125
|
+
}), {
|
|
126
|
+
maxAttempts: this.reconnectionConfig.maxAttempts,
|
|
127
|
+
backoff: new ExponentialBackoff({
|
|
128
|
+
maxDelay: this.reconnectionConfig.maxDelay,
|
|
129
|
+
initialDelay: this.reconnectionConfig.initialDelay,
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
let trial = 0;
|
|
133
|
+
retryPolicy.execute(async () => {
|
|
134
|
+
trial++;
|
|
135
|
+
this.logger.debug(`Retrying to connect to ogmios client in trial step #${trial}`);
|
|
136
|
+
return await this.start();
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* start scanner. first find fork point. fork all blocks and request updates from node.
|
|
141
|
+
*/
|
|
142
|
+
start = async () => {
|
|
143
|
+
this.stopped = false;
|
|
144
|
+
const context = await createInteractionContext((err) => this.logger.error(`${err}`), this.connectionCloseHandler, { connection: { port: this.port, host: this.host, tls: this.useTls } });
|
|
145
|
+
const intersect = await this.findIntersection(context);
|
|
146
|
+
if (intersect) {
|
|
147
|
+
// find intersect then start from that point
|
|
148
|
+
this.client = await createChainSynchronizationClient(context, {
|
|
149
|
+
rollBackward: this.rollBackward,
|
|
150
|
+
rollForward: this.rollForward,
|
|
151
|
+
}, { sequential: true });
|
|
152
|
+
this.connected = true;
|
|
153
|
+
await this.forkBlock(intersect.height + 1);
|
|
154
|
+
await this.client.resume([intersect.point], 2);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
throw Error('Can not start scanner. initial block is invalid');
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* stop ws connection to node.
|
|
162
|
+
*/
|
|
163
|
+
stop = async () => {
|
|
164
|
+
this.stopped = true;
|
|
165
|
+
await this.client.shutdown();
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* @returns The connection status of ogmios scanner
|
|
169
|
+
*/
|
|
170
|
+
getConnectionStatus = () => this.connected;
|
|
171
|
+
}
|
|
172
|
+
export { CardanoOgmiosScanner };
|
|
173
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2dtaW9zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vbGliL3NjYW5uZXIvb2dtaW9zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCxnQ0FBZ0MsR0FFakMsTUFBTSx3QkFBd0IsQ0FBQztBQUNoQyxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNsRSxPQUFPLEVBRUwsZ0JBQWdCLEdBQ2pCLE1BQU0sa0RBQWtELENBQUM7QUFTMUQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFHbEUsT0FBTyxFQUNMLGFBQWEsRUFDYixnQkFBZ0IsR0FDakIsTUFBTSxnQ0FBZ0MsQ0FBQztBQUV4QyxPQUFPLEVBQ0wsMEJBQTBCLEVBQzFCLHNCQUFzQixFQUN0QixrQkFBa0IsRUFDbEIseUJBQXlCLEdBQzFCLE1BQU0sY0FBYyxDQUFDO0FBYXRCLE1BQU0sb0JBQXFCLFNBQVEsZ0JBQTZCO0lBQzlELE1BQU0sQ0FBNkI7SUFDbkMsU0FBUyxDQUFRO0lBQ2pCLElBQUksQ0FBUztJQUNiLElBQUksQ0FBUztJQUNiLE1BQU0sQ0FBVTtJQUNSLFNBQVMsQ0FBVTtJQUNuQixPQUFPLENBQVU7SUFDakIsa0JBQWtCLENBQTJCO0lBRXJELFlBQVksTUFBMkIsRUFBRSxNQUF1QjtRQUM5RCxLQUFLLENBQUMsU0FBUyxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsV0FBVyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUM1RCxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksYUFBYSxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBRSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3hFLElBQUksQ0FBQyxJQUFJLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQztRQUNoQyxJQUFJLENBQUMsSUFBSSxHQUFHLE1BQU0sQ0FB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QUM7WUFDTixNQUFNLEtBQUssQ0FBQyxpREFBaUQsQ0FBQyxDQUFDO1FBQ2pFLENBQUM7SUFDSCxDQUFDLENBQUM7SUFFRjs7T0FFRztJQUNILElBQUksR0FBRyxLQUFLLElBQW1CLEVBQUU7UUFDL0IsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFDcEIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQy9CLENBQUMsQ0FBQztJQUVGOztPQUVHO0lBQ0gsbUJBQW1CLEdBQUcsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztDQUM1QztBQUVELE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgY3JlYXRlQ2hhaW5TeW5jaHJvbml6YXRpb25DbGllbnQsXG4gIEludGVyYWN0aW9uQ29udGV4dCxcbn0gZnJvbSAnQGNhcmRhbm8tb2dtaW9zL2NsaWVudCc7XG5pbXBvcnQgeyBjcmVhdGVJbnRlcmFjdGlvbkNvbnRleHQgfSBmcm9tICdAY2FyZGFuby1vZ21pb3MvY2xpZW50JztcbmltcG9ydCB7XG4gIENoYWluU3luY2hyb25pemF0aW9uQ2xpZW50LFxuICBmaW5kSW50ZXJzZWN0aW9uLFxufSBmcm9tICdAY2FyZGFuby1vZ21pb3MvY2xpZW50L2Rpc3QvQ2hhaW5TeW5jaHJvbml6YXRpb24nO1xuaW1wb3J0IHtcbiAgQmxvY2ssXG4gIEJsb2NrUHJhb3MsXG4gIFBvaW50LFxuICBQb2ludE9yT3JpZ2luLFxuICBUaXBPck9yaWdpbixcbiAgVHJhbnNhY3Rpb24sXG59IGZyb20gJ0BjYXJkYW5vLW9nbWlvcy9zY2hlbWEnO1xuaW1wb3J0IHsgRXhwb25lbnRpYWxCYWNrb2ZmLCBoYW5kbGVXaGVuLCByZXRyeSB9IGZyb20gJ2NvY2thdGllbCc7XG5cbmltcG9ydCB7IEFic3RyYWN0TG9nZ2VyIH0gZnJvbSAnQHJvc2VuLWJyaWRnZS9hYnN0cmFjdC1sb2dnZXInO1xuaW1wb3J0IHtcbiAgQmxvY2tEYkFjdGlvbixcbiAgV2ViU29ja2V0U2Nhbm5lcixcbn0gZnJvbSAnQHJvc2VuLWJyaWRnZS9hYnN0cmFjdC1zY2FubmVyJztcblxuaW1wb3J0IHtcbiAgUkVDT05ORUNUSU9OX0lOSVRJQUxfREVMQVksXG4gIFJFQ09OTkVDVElPTl9NQVhfREVMQVksXG4gIFNMT1RfU0hFTExZX05VTUJFUixcbiAgUkVDT05ORUNUSU9OX01BWF9BVFRFTVBUUyxcbn0gZnJvbSAnLi4vY29uc3RhbnRzJztcbmltcG9ydCB7IENhcmRhbm9PZ21pb3NDb25maWcsIE9nbWlvc1JlY29ubmVjdGlvbkNvbmZpZyB9IGZyb20gJy4uL2ludGVyZmFjZXMnO1xuXG5pbnRlcmZhY2UgQmFja3dhcmRSZXNwb25zZSB7XG4gIHBvaW50OiBQb2ludE9yT3JpZ2luO1xuICB0aXA6IFRpcE9yT3JpZ2luO1xufVxuXG5pbnRlcmZhY2UgRm9yd2FyZFJlc3BvbnNlIHtcbiAgYmxvY2s6IEJsb2NrO1xuICB0aXA6IFRpcE9yT3JpZ2luO1xufVxuXG5jbGFzcyBDYXJkYW5vT2dtaW9zU2Nhbm5lciBleHRlbmRzIFdlYlNvY2tldFNjYW5uZXI8VHJhbnNhY3Rpb24+IHtcbiAgY2xpZW50OiBDaGFpblN5bmNocm9uaXphdGlvbkNsaWVudDtcbiAgaW5pdFBvaW50OiBQb2ludDtcbiAgaG9zdDogc3RyaW5nO1xuICBwb3J0OiBudW1iZXI7XG4gIHVzZVRsczogYm9vbGVhbjtcbiAgcHJpdmF0ZSBjb25uZWN0ZWQ6IGJvb2xlYW47XG4gIHByaXZhdGUgc3RvcHBlZDogYm9vbGVhbjtcbiAgcHJpdmF0ZSByZWNvbm5lY3Rpb25Db25maWc6IE9nbWlvc1JlY29ubmVjdGlvbkNvbmZpZztcblxuICBjb25zdHJ1Y3Rvcihjb25maWc6IENhcmRhbm9PZ21pb3NDb25maWcsIGxvZ2dlcj86IEFic3RyYWN0TG9nZ2VyKSB7XG4gICAgc3VwZXIoJ2NhcmRhbm8nLCBsb2dnZXIsIGNvbmZpZy5tYXhUcnlCbG9jaywgY29uZmlnLnN1ZmZpeCk7XG4gICAgdGhpcy5hY3Rpb24gPSBuZXcgQmxvY2tEYkFjdGlvbihjb25maWcuZGF0YVNvdXJjZSwgdGhpcy5uYW1lKCksIGxvZ2dlcik7XG4gICAgdGhpcy5ob3N0ID0gY29uZmlnLm5vZGVIb3N0T3JJcDtcbiAgICB0aGlzLnBvcnQgPSBjb25maWcubm9kZVBvcnQ7XG4gICAgdGhpcy51c2VUbHMgPSBjb25maWcudXNlVGxzID8/IGZhbHNlO1xuICAgIHRoaXMuY29ubmVjdGVkID0gZmFsc2U7XG4gICAgdGhpcy5pbml0UG9pbnQgPSB7XG4gICAgICBpZDogY29uZmlnLmluaXRpYWxIYXNoLFxuICAgICAgc2xvdDogY29uZmlnLmluaXRpYWxTbG90LFxuICAgIH07XG4gICAgdGhpcy5yZWNvbm5lY3Rpb25Db25maWcgPSB7XG4gICAgICBpbml0aWFsRGVsYXk6XG4gICAgICAgIGNvbmZpZy5yZWNvbm5lY3Rpb25Db25maWc/LmluaXRpYWxEZWxheSA/PyBSRUNPTk5FQ1RJT05fSU5JVElBTF9ERUxBWSxcbiAgICAgIG1heERlbGF5OiBjb25maWcucmVjb25uZWN0aW9uQ29uZmlnPy5tYXhEZWxheSA/PyBSRUNPTk5FQ1RJT05fTUFYX0RFTEFZLFxuICAgICAgbWF4QXR0ZW1wdHM6XG4gICAgICAgIGNvbmZpZy5yZWNvbm5lY3Rpb25Db25maWc/Lm1heEF0dGVtcHRzID8/IFJFQ09OTkVDVElPTl9NQVhfQVRURU1QVFMsXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBwcm9jZXNzIGJsb2NrIHdoZW4gYSBibG9jayBmb3JrZWQgbGlicmFyeSBjYWxsIHRoaXMgZnVuY3Rpb25cbiAgICogQHBhcmFtIHJlc3BvbnNlOiBmb3JrZWQgYmxvY2tcbiAgICogQHBhcmFtIHJlcXVlc3ROZXh0OiBhIGZ1bmN0aW9uIHRvIHRlbGwgbm9kZSB0byBzZW5kIG5leHQgYmxvY2tcbiAgICovXG4gIHByaXZhdGUgcm9sbEJhY2t3YXJkID0gYXN5bmMgKFxuICAgIHJlc3BvbnNlOiBCYWNrd2FyZFJlc3BvbnNlLFxuICAgIHJlcXVlc3ROZXh0OiAoKSA9PiB2b2lkLFxuICApID0+IHtcbiAgICBjb25zdCBoYXNoID0gKHJlc3BvbnNlLnBvaW50IGFzIFBvaW50KS5pZDtcbiAgICBjb25zdCBzYXZlZEJsb2NrID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0QmxvY2tXaXRoSGFzaChoYXNoKTtcbiAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgIGBSb2xsaW5nIGJhY2t3YXJkIHRvIGhlaWdodCAke1xuICAgICAgICBzYXZlZEJsb2NrPy5oZWlnaHRcbiAgICAgIH0gaW4gc2Nhbm5lciAke3RoaXMubmFtZSgpfWAsXG4gICAgKTtcbiAgICBpZiAoc2F2ZWRCbG9jaykge1xuICAgICAgY29uc3QgYmxvY2sgPSB7XG4gICAgICAgIGhhc2g6IHNhdmVkQmxvY2suaGFzaCxcbiAgICAgICAgaGVpZ2h0OiBzYXZlZEJsb2NrLmhlaWdodCxcbiAgICAgICAgcGFyZW50SGFzaDogc2F2ZWRCbG9jay5wYXJlbnRIYXNoLFxuICAgICAgICBleHRyYTogc2F2ZWRCbG9jay5leHRyYSxcbiAgICAgICAgdGltZXN0YW1wOiBzYXZlZEJsb2NrLnRpbWVzdGFtcCxcbiAgICAgIH07XG4gICAgICBhd2FpdCB0aGlzLnN0ZXBCYWNrd2FyZChibG9jayk7XG4gICAgfVxuICAgIHJlcXVlc3ROZXh0KCk7XG4gIH07XG5cbiAgLyoqXG4gICAqIFN0ZXAgZm9yd2FyZCB3aGVuIG5ldyBibG9jayBhcnJpdmVkIG9nbWlvcy1jbGllbnQgY2FsbCB0aGlzIGZ1bmN0aW9uXG4gICAqIEBwYXJhbSByZXNwb25zZTogbmV3IGJsb2NrXG4gICAqIEBwYXJhbSByZXF1ZXN0TmV4dDogdGVsbCBsaWJyYXJ5IHRoYXQgdGhpcyBibG9jayBwcm9jZWVkcy4gcGFzcyBuZXh0IGJsb2NrIHdoZW4gYXZhaWxhYmxlXG4gICAqL1xuICBwcml2YXRlIHJvbGxGb3J3YXJkID0gYXN5bmMgKFxuICAgIHJlc3BvbnNlOiBGb3J3YXJkUmVzcG9uc2UsXG4gICAgcmVxdWVzdE5leHQ6ICgpID0+IHZvaWQsXG4gICkgPT4ge1xuICAgIGlmIChyZXNwb25zZS5ibG9jay50eXBlID09PSAncHJhb3MnKSB7XG4gICAgICBjb25zdCBwcmFvc0Jsb2NrID0gcmVzcG9uc2UuYmxvY2sgYXMgQmxvY2tQcmFvcztcbiAgICAgIHRoaXMubG9nZ2VyLmRlYnVnKFxuICAgICAgICBgUXVldWVpbmcgbmV3IGJsb2NrIGF0IGhlaWdodCAke1xuICAgICAgICAgIHByYW9zQmxvY2suaGVpZ2h0XG4gICAgICAgIH0gaW4gc2Nhbm5lciAke3RoaXMubmFtZSgpfWAsXG4gICAgICApO1xuICAgICAgY29uc3QgYmxvY2sgPSB7XG4gICAgICAgIGhhc2g6IHByYW9zQmxvY2suaWQsXG4gICAgICAgIGhlaWdodDogcHJhb3NCbG9jay5oZWlnaHQsXG4gICAgICAgIHBhcmVudEhhc2g6IHByYW9zQmxvY2suYW5jZXN0b3IsXG4gICAgICAgIGV4dHJhOiBgJHtwcmFvc0Jsb2NrLnNsb3R9YCxcbiAgICAgICAgLy8gQ2F1dGlvbjogSW4gY2FzZSBvZiBhIGhhcmQgZm9yayBhbmQgY2hhbmdlIGluIHNsb3QgZHVyYXRpb24sIHRoaXMgbXVzdCBjaGFuZ2UhXG4gICAgICAgIHRpbWVzdGFtcDogcHJhb3NCbG9jay5zbG90ICsgU0xPVF9TSEVMTFlfTlVNQkVSLFxuICAgICAgfTtcbiAgICAgIGlmIChwcmFvc0Jsb2NrLnRyYW5zYWN0aW9ucylcbiAgICAgICAgYXdhaXQgdGhpcy5zdGVwRm9yd2FyZChibG9jaywgcHJhb3NCbG9jay50cmFuc2FjdGlvbnMpO1xuICAgIH1cbiAgICByZXF1ZXN0TmV4dCgpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBmaW5kIGludGVyc2VjdCBiZXR3ZWVuIHN0b3JlZCBibG9ja3MgYW5kIGJsb2NrY2hhaW4uXG4gICAqIEBwYXJhbSBjb250ZXh0OiBibG9ja2NoYWluIGNvbnRleHRcbiAgICovXG4gIHByaXZhdGUgZmluZEludGVyc2VjdGlvbiA9IGFzeW5jIChjb250ZXh0OiBJbnRlcmFjdGlvbkNvbnRleHQpID0+IHtcbiAgICBsZXQgY291bnQgPSAxO1xuICAgIGxldCBza2lwID0gMDtcbiAgICB3aGlsZSAoY291bnQgIT09IDApIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJlbGVhc2UgPSBhd2FpdCB0aGlzLm11dGV4LmFjcXVpcmUoKTtcbiAgICAgICAgY29uc3QgYmxvY2tzID0gYXdhaXQgdGhpcy5hY3Rpb24uZ2V0TGFzdFNhdmVkQmxvY2tzKHNraXAsIGNvdW50KTtcbiAgICAgICAgcmVsZWFzZSgpO1xuICAgICAgICBpZiAoYmxvY2tzLmxlbmd0aCA9PT0gMCkgY291bnQgPSAwO1xuICAgICAgICBjb25zdCBwb2ludHMgPVxuICAgICAgICAgIGJsb2Nrcy5sZW5ndGggPiAwXG4gICAgICAgICAgICA/IGJsb2Nrcy5tYXAoKGl0ZW0pID0+ICh7XG4gICAgICAgICAgICAgICAgc2xvdDogaXRlbS5leHRyYSA/IHBhcnNlSW50KGl0ZW0uZXh0cmEpIDogMCxcbiAgICAgICAgICAgICAgICBpZDogaXRlbS5oYXNoLFxuICAgICAgICAgICAgICB9KSlcbiAgICAgICAgICAgIDogW3RoaXMuaW5pdFBvaW50XTtcbiAgICAgICAgY29uc3QgaW50ZXJzZWN0ID0gYXdhaXQgZmluZEludGVyc2VjdGlvbihjb250ZXh0LCBwb2ludHMpO1xuICAgICAgICBjb25zdCBpbnRlcnNlY3RQb2ludCA9IGludGVyc2VjdC5pbnRlcnNlY3Rpb24gYXMgUG9pbnQ7XG4gICAgICAgIGxldCBoZWlnaHQgPSAwO1xuICAgICAgICBpZiAoYmxvY2tzLmxlbmd0aCkge1xuICAgICAgICAgIGNvbnN0IGZvdW5kZWRCbG9jayA9IGJsb2Nrcy5maW5kKFxuICAgICAgICAgICAgKGl0ZW0pID0+IChpdGVtLmhhc2ggPSBpbnRlcnNlY3RQb2ludC5pZCksXG4gICAgICAgICAgKTtcbiAgICAgICAgICBpZiAoZm91bmRlZEJsb2NrKSB7XG4gICAgICAgICAgICBoZWlnaHQgPSBmb3VuZGVkQmxvY2suaGVpZ2h0O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4geyBwb2ludDogaW50ZXJzZWN0LmludGVyc2VjdGlvbiBhcyBQb2ludCwgaGVpZ2h0OiBoZWlnaHQgfTtcbiAgICAgIH0gY2F0Y2gge1xuICAgICAgICBza2lwICs9IGNvdW50O1xuICAgICAgICBjb3VudCAqPSAyO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9O1xuXG4gIC8qKlxuICAgKiBIYW5kbGVzIG9nbWlvcyBjb25uZWN0aW9uIGNsb3N1cmVcbiAgICovXG4gIHByaXZhdGUgY29ubmVjdGlvbkNsb3NlSGFuZGxlciA9IGFzeW5jICgpID0+IHtcbiAgICB0aGlzLmNvbm5lY3RlZCA9IGZhbHNlO1xuICAgIHRoaXMubG9nZ2VyLndhcm4oJ09nbWlvcyBjb25uZWN0aW9uIGNsb3NlZCcpO1xuICAgIGlmICh0aGlzLnN0b3BwZWQpIHJldHVybjtcblxuICAgIGNvbnN0IHJldHJ5UG9saWN5ID0gcmV0cnkoXG4gICAgICBoYW5kbGVXaGVuKChlcnJvcjogRXJyb3IpID0+IHtcbiAgICAgICAgdGhpcy5sb2dnZXIud2FybihcbiAgICAgICAgICBgQW4gZXJyb3Igb2NjdXJyZWQgd2hpbGUgcmVjb25uZWN0aW5nIHRvIG9nbWlvcyBjbGllbnQ6ICR7ZXJyb3J9YCxcbiAgICAgICAgKTtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9KSxcbiAgICAgIHtcbiAgICAgICAgbWF4QXR0ZW1wdHM6IHRoaXMucmVjb25uZWN0aW9uQ29uZmlnLm1heEF0dGVtcHRzLFxuICAgICAgICBiYWNrb2ZmOiBuZXcgRXhwb25lbnRpYWxCYWNrb2ZmKHtcbiAgICAgICAgICBtYXhEZWxheTogdGhpcy5yZWNvbm5lY3Rpb25Db25maWcubWF4RGVsYXksXG4gICAgICAgICAgaW5pdGlhbERlbGF5OiB0aGlzLnJlY29ubmVjdGlvbkNvbmZpZy5pbml0aWFsRGVsYXksXG4gICAgICAgIH0pLFxuICAgICAgfSxcbiAgICApO1xuXG4gICAgbGV0IHRyaWFsID0gMDtcbiAgICByZXRyeVBvbGljeS5leGVjdXRlKGFzeW5jICgpID0+IHtcbiAgICAgIHRyaWFsKys7XG4gICAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgICAgYFJldHJ5aW5nIHRvIGNvbm5lY3QgdG8gb2dtaW9zIGNsaWVudCBpbiB0cmlhbCBzdGVwICMke3RyaWFsfWAsXG4gICAgICApO1xuICAgICAgcmV0dXJuIGF3YWl0IHRoaXMuc3RhcnQoKTtcbiAgICB9KTtcbiAgfTtcblxuICAvKipcbiAgICogc3RhcnQgc2Nhbm5lci4gZmlyc3QgZmluZCBmb3JrIHBvaW50LiBmb3JrIGFsbCBibG9ja3MgYW5kIHJlcXVlc3QgdXBkYXRlcyBmcm9tIG5vZGUuXG4gICAqL1xuICBzdGFydCA9IGFzeW5jICgpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICB0aGlzLnN0b3BwZWQgPSBmYWxzZTtcbiAgICBjb25zdCBjb250ZXh0OiBJbnRlcmFjdGlvbkNvbnRleHQgPSBhd2FpdCBjcmVhdGVJbnRlcmFjdGlvbkNvbnRleHQoXG4gICAgICAoZXJyKSA9PiB0aGlzLmxvZ2dlci5lcnJvcihgJHtlcnJ9YCksXG4gICAgICB0aGlzLmNvbm5lY3Rpb25DbG9zZUhhbmRsZXIsXG4gICAgICB7IGNvbm5lY3Rpb246IHsgcG9ydDogdGhpcy5wb3J0LCBob3N0OiB0aGlzLmhvc3QsIHRsczogdGhpcy51c2VUbHMgfSB9LFxuICAgICk7XG4gICAgY29uc3QgaW50ZXJzZWN0ID0gYXdhaXQgdGhpcy5maW5kSW50ZXJzZWN0aW9uKGNvbnRleHQpO1xuICAgIGlmIChpbnRlcnNlY3QpIHtcbiAgICAgIC8vIGZpbmQgaW50ZXJzZWN0IHRoZW4gc3RhcnQgZnJvbSB0aGF0IHBvaW50XG4gICAgICB0aGlzLmNsaWVudCA9IGF3YWl0IGNyZWF0ZUNoYWluU3luY2hyb25pemF0aW9uQ2xpZW50KFxuICAgICAgICBjb250ZXh0LFxuICAgICAgICB7XG4gICAgICAgICAgcm9sbEJhY2t3YXJkOiB0aGlzLnJvbGxCYWNrd2FyZCxcbiAgICAgICAgICByb2xsRm9yd2FyZDogdGhpcy5yb2xsRm9yd2FyZCxcbiAgICAgICAgfSxcbiAgICAgICAgeyBzZXF1ZW50aWFsOiB0cnVlIH0sXG4gICAgICApO1xuICAgICAgdGhpcy5jb25uZWN0ZWQgPSB0cnVlO1xuICAgICAgYXdhaXQgdGhpcy5mb3JrQmxvY2soaW50ZXJzZWN0LmhlaWdodCArIDEpO1xuICAgICAgYXdhaXQgdGhpcy5jbGllbnQucmVzdW1lKFtpbnRlcnNlY3QucG9pbnRdLCAyKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgRXJyb3IoJ0NhbiBub3Qgc3RhcnQgc2Nhbm5lci4gaW5pdGlhbCBibG9jayBpcyBpbnZhbGlkJyk7XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBzdG9wIHdzIGNvbm5lY3Rpb24gdG8gbm9kZS5cbiAgICovXG4gIHN0b3AgPSBhc3luYyAoKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gICAgdGhpcy5zdG9wcGVkID0gdHJ1ZTtcbiAgICBhd2FpdCB0aGlzLmNsaWVudC5zaHV0ZG93bigpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBAcmV0dXJucyBUaGUgY29ubmVjdGlvbiBzdGF0dXMgb2Ygb2dtaW9zIHNjYW5uZXJcbiAgICovXG4gIGdldENvbm5lY3Rpb25TdGF0dXMgPSAoKSA9PiB0aGlzLmNvbm5lY3RlZDtcbn1cblxuZXhwb3J0IHsgQ2FyZGFub09nbWlvc1NjYW5uZXIgfTtcbiJdfQ==
|