@pioneer-platform/blockbook 8.4.0 → 8.5.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 +23 -0
- package/lib/index.d.ts +25 -2
- package/lib/index.js +265 -114
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @pioneer-platform/blockbook
|
|
2
2
|
|
|
3
|
+
## 8.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Automated minor version bump for all packages
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @pioneer-platform/loggerdog@8.5.0
|
|
13
|
+
- @pioneer-platform/nodes@8.5.0
|
|
14
|
+
- @pioneer-platform/pioneer-caip@9.4.0
|
|
15
|
+
|
|
16
|
+
## 8.4.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Automated patch version bump for all packages
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @pioneer-platform/loggerdog@8.4.1
|
|
23
|
+
- @pioneer-platform/nodes@8.4.1
|
|
24
|
+
- @pioneer-platform/pioneer-caip@9.3.1
|
|
25
|
+
|
|
3
26
|
## 8.4.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
package/lib/index.d.ts
CHANGED
|
@@ -2,14 +2,36 @@ declare const TAG = " | blockbook-client | ";
|
|
|
2
2
|
declare const Blockbook: any;
|
|
3
3
|
declare const log: any;
|
|
4
4
|
declare const fakeUa: any;
|
|
5
|
+
declare const axiosLib: any;
|
|
5
6
|
declare const Axios: any;
|
|
6
7
|
declare const https: any;
|
|
7
8
|
declare const nodes: any;
|
|
8
9
|
declare const axios: any;
|
|
9
10
|
declare let NOW_NODES_API: string | undefined;
|
|
10
|
-
|
|
11
|
+
interface NodeConfig {
|
|
12
|
+
url: string;
|
|
13
|
+
priority: number;
|
|
14
|
+
avgResponseTime: number;
|
|
15
|
+
successRate: number;
|
|
16
|
+
lastChecked: number;
|
|
17
|
+
isActive: boolean;
|
|
18
|
+
consecutiveFailures: number;
|
|
19
|
+
totalRequests: number;
|
|
20
|
+
successfulRequests: number;
|
|
21
|
+
}
|
|
22
|
+
interface CoinNodes {
|
|
23
|
+
[symbol: string]: NodeConfig[];
|
|
24
|
+
}
|
|
25
|
+
declare let BLOCKBOOK_NODES: CoinNodes;
|
|
11
26
|
declare let BLOCKBOOK_SOCKETS: any;
|
|
12
|
-
declare let
|
|
27
|
+
declare let BLOCKBOOK_URLS: any;
|
|
28
|
+
declare let add_custom_node: (coin: string, url: string, priority?: number) => boolean;
|
|
29
|
+
declare let remove_node: (coin: string, url: string) => boolean;
|
|
30
|
+
declare let set_node_priority: (coin: string, url: string, priority: number) => boolean;
|
|
31
|
+
declare let get_node_stats: (coin?: string) => any;
|
|
32
|
+
declare let reorder_nodes_by_performance: (coin: string) => void;
|
|
33
|
+
declare let reorder_nodes_by_priority: (symbol: string) => void;
|
|
34
|
+
declare let update_legacy_urls: (symbol: string) => void;
|
|
13
35
|
declare let init_network: (servers?: any[]) => Promise<boolean>;
|
|
14
36
|
declare let get_fees: (coin: string) => Promise<any>;
|
|
15
37
|
declare let get_info_by_pubkey: (coin: string, pubkey: string, page?: string | undefined) => Promise<any>;
|
|
@@ -19,5 +41,6 @@ declare let get_txs_by_xpub: (coin: string, xpub: string) => Promise<any>;
|
|
|
19
41
|
declare let broadcast_transaction: (coin: string, hex: string) => Promise<any>;
|
|
20
42
|
declare let get_transaction: (coin: string, txid: string) => Promise<any>;
|
|
21
43
|
declare let get_utxos_by_xpub: (coin: string, xpub: string) => Promise<any>;
|
|
44
|
+
declare let update_node_performance: (coin: string, url: string, success: boolean, responseTime: number) => void;
|
|
22
45
|
declare let get_balance_by_xpub: (coin: string, xpub: any) => Promise<number>;
|
|
23
46
|
declare let get_node_info: () => Promise<boolean>;
|
package/lib/index.js
CHANGED
|
@@ -43,7 +43,8 @@ var TAG = " | blockbook-client | ";
|
|
|
43
43
|
var Blockbook = require('blockbook-client').Blockbook;
|
|
44
44
|
var log = require('@pioneer-platform/loggerdog')();
|
|
45
45
|
var fakeUa = require('fake-useragent');
|
|
46
|
-
var
|
|
46
|
+
var axiosLib = require('axios');
|
|
47
|
+
var Axios = axiosLib.default || axiosLib;
|
|
47
48
|
var https = require('https');
|
|
48
49
|
var nodes = require("@pioneer-platform/nodes");
|
|
49
50
|
var axios = Axios.create({
|
|
@@ -54,27 +55,29 @@ var axios = Axios.create({
|
|
|
54
55
|
});
|
|
55
56
|
// const axiosRetry = require('axios-retry');
|
|
56
57
|
var NOW_NODES_API = process.env['NOW_NODES_API'];
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
// retryDelay: (retryCount: number) => {
|
|
60
|
-
// log.error(TAG,`retry attempt: ${retryCount}`);
|
|
61
|
-
// return retryCount * 2000; // time interval between retries
|
|
62
|
-
// },
|
|
63
|
-
// retryCondition: (error: { response: { status: number; }; }) => {
|
|
64
|
-
// log.error(TAG,error)
|
|
65
|
-
// //@TODO mark node offline, and punish
|
|
66
|
-
// // if retry condition is not specified, by default idempotent requests are retried
|
|
67
|
-
// return error?.response?.status === 503;
|
|
68
|
-
// },
|
|
69
|
-
// });
|
|
70
|
-
var BLOCKBOOK_URLS = {};
|
|
58
|
+
// Enhanced node management
|
|
59
|
+
var BLOCKBOOK_NODES = {};
|
|
71
60
|
var BLOCKBOOK_SOCKETS = {};
|
|
61
|
+
// Legacy compatibility - will be populated from BLOCKBOOK_NODES
|
|
62
|
+
var BLOCKBOOK_URLS = {};
|
|
72
63
|
module.exports = {
|
|
73
64
|
init: function (servers) {
|
|
74
65
|
return init_network(servers);
|
|
75
66
|
},
|
|
76
|
-
addNode: function (coin, url) {
|
|
77
|
-
return add_custom_node(coin, url);
|
|
67
|
+
addNode: function (coin, url, priority) {
|
|
68
|
+
return add_custom_node(coin, url, priority);
|
|
69
|
+
},
|
|
70
|
+
removeNode: function (coin, url) {
|
|
71
|
+
return remove_node(coin, url);
|
|
72
|
+
},
|
|
73
|
+
setPriority: function (coin, url, priority) {
|
|
74
|
+
return set_node_priority(coin, url, priority);
|
|
75
|
+
},
|
|
76
|
+
getNodeStats: function (coin) {
|
|
77
|
+
return get_node_stats(coin);
|
|
78
|
+
},
|
|
79
|
+
reorderNodes: function (coin) {
|
|
80
|
+
return reorder_nodes_by_performance(coin);
|
|
78
81
|
},
|
|
79
82
|
getInfo: function () {
|
|
80
83
|
return get_node_info();
|
|
@@ -113,35 +116,124 @@ module.exports = {
|
|
|
113
116
|
return broadcast_transaction(coin, hex);
|
|
114
117
|
},
|
|
115
118
|
};
|
|
116
|
-
//
|
|
117
|
-
var add_custom_node = function (coin, url) {
|
|
119
|
+
// Enhanced node management functions
|
|
120
|
+
var add_custom_node = function (coin, url, priority) {
|
|
118
121
|
var symbol = coin.toUpperCase();
|
|
119
122
|
var cleanUrl = url.replace(/\/$/, ''); // Remove trailing slash
|
|
120
|
-
//
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (!BLOCKBOOK_URLS[symbol]) {
|
|
125
|
-
BLOCKBOOK_URLS[symbol] = [];
|
|
123
|
+
// Initialize if not exists
|
|
124
|
+
if (!BLOCKBOOK_NODES[symbol]) {
|
|
125
|
+
BLOCKBOOK_NODES[symbol] = [];
|
|
126
126
|
}
|
|
127
127
|
// Check for duplicates
|
|
128
|
-
|
|
128
|
+
var existingNode = BLOCKBOOK_NODES[symbol].find(function (node) { return node.url === cleanUrl; });
|
|
129
|
+
if (existingNode) {
|
|
129
130
|
log.warn("Node ".concat(cleanUrl, " already exists for ").concat(symbol));
|
|
130
131
|
return false;
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
// Determine priority
|
|
134
|
+
var defaultPriority = priority !== undefined ? priority :
|
|
135
|
+
(BLOCKBOOK_NODES[symbol].length === 0 ? 0 : Math.max.apply(Math, BLOCKBOOK_NODES[symbol].map(function (n) { return n.priority; })) + 10);
|
|
136
|
+
// Create new node config
|
|
137
|
+
var newNode = {
|
|
138
|
+
url: cleanUrl,
|
|
139
|
+
priority: defaultPriority,
|
|
140
|
+
avgResponseTime: 1000, // Default 1s
|
|
141
|
+
successRate: 100, // Optimistic start
|
|
142
|
+
lastChecked: Date.now(),
|
|
143
|
+
isActive: true,
|
|
144
|
+
consecutiveFailures: 0,
|
|
145
|
+
totalRequests: 0,
|
|
146
|
+
successfulRequests: 0
|
|
147
|
+
};
|
|
148
|
+
BLOCKBOOK_NODES[symbol].push(newNode);
|
|
149
|
+
// Sort by priority and update legacy compatibility
|
|
150
|
+
reorder_nodes_by_priority(symbol);
|
|
151
|
+
update_legacy_urls(symbol);
|
|
152
|
+
log.info("Added node for ".concat(symbol, ": ").concat(cleanUrl, " (priority: ").concat(defaultPriority, ")"));
|
|
153
|
+
log.info("".concat(symbol, " now has ").concat(BLOCKBOOK_NODES[symbol].length, " node(s)"));
|
|
154
|
+
return true;
|
|
155
|
+
};
|
|
156
|
+
var remove_node = function (coin, url) {
|
|
157
|
+
var symbol = coin.toUpperCase();
|
|
158
|
+
var cleanUrl = url.replace(/\/$/, '');
|
|
159
|
+
if (!BLOCKBOOK_NODES[symbol]) {
|
|
160
|
+
log.warn("No nodes configured for ".concat(symbol));
|
|
161
|
+
return false;
|
|
134
162
|
}
|
|
135
|
-
|
|
136
|
-
|
|
163
|
+
var initialLength = BLOCKBOOK_NODES[symbol].length;
|
|
164
|
+
BLOCKBOOK_NODES[symbol] = BLOCKBOOK_NODES[symbol].filter(function (node) { return node.url !== cleanUrl; });
|
|
165
|
+
if (BLOCKBOOK_NODES[symbol].length === initialLength) {
|
|
166
|
+
log.warn("Node ".concat(cleanUrl, " not found for ").concat(symbol));
|
|
167
|
+
return false;
|
|
137
168
|
}
|
|
138
|
-
|
|
139
|
-
log.info("".concat(symbol, "
|
|
169
|
+
update_legacy_urls(symbol);
|
|
170
|
+
log.info("Removed node for ".concat(symbol, ": ").concat(cleanUrl));
|
|
140
171
|
return true;
|
|
141
172
|
};
|
|
173
|
+
var set_node_priority = function (coin, url, priority) {
|
|
174
|
+
var symbol = coin.toUpperCase();
|
|
175
|
+
var cleanUrl = url.replace(/\/$/, '');
|
|
176
|
+
if (!BLOCKBOOK_NODES[symbol]) {
|
|
177
|
+
log.warn("No nodes configured for ".concat(symbol));
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
var node = BLOCKBOOK_NODES[symbol].find(function (n) { return n.url === cleanUrl; });
|
|
181
|
+
if (!node) {
|
|
182
|
+
log.warn("Node ".concat(cleanUrl, " not found for ").concat(symbol));
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
node.priority = priority;
|
|
186
|
+
reorder_nodes_by_priority(symbol);
|
|
187
|
+
update_legacy_urls(symbol);
|
|
188
|
+
log.info("Updated priority for ".concat(symbol, " node ").concat(cleanUrl, ": ").concat(priority));
|
|
189
|
+
return true;
|
|
190
|
+
};
|
|
191
|
+
var get_node_stats = function (coin) {
|
|
192
|
+
if (coin) {
|
|
193
|
+
var symbol = coin.toUpperCase();
|
|
194
|
+
return BLOCKBOOK_NODES[symbol] || [];
|
|
195
|
+
}
|
|
196
|
+
return BLOCKBOOK_NODES;
|
|
197
|
+
};
|
|
198
|
+
var reorder_nodes_by_performance = function (coin) {
|
|
199
|
+
var symbol = coin.toUpperCase();
|
|
200
|
+
if (!BLOCKBOOK_NODES[symbol])
|
|
201
|
+
return;
|
|
202
|
+
// Sort by: active status, success rate, response time, then priority
|
|
203
|
+
BLOCKBOOK_NODES[symbol].sort(function (a, b) {
|
|
204
|
+
if (a.isActive !== b.isActive)
|
|
205
|
+
return b.isActive ? 1 : -1;
|
|
206
|
+
if (Math.abs(a.successRate - b.successRate) > 5)
|
|
207
|
+
return b.successRate - a.successRate;
|
|
208
|
+
if (Math.abs(a.avgResponseTime - b.avgResponseTime) > 500)
|
|
209
|
+
return a.avgResponseTime - b.avgResponseTime;
|
|
210
|
+
return a.priority - b.priority;
|
|
211
|
+
});
|
|
212
|
+
update_legacy_urls(symbol);
|
|
213
|
+
};
|
|
214
|
+
var reorder_nodes_by_priority = function (symbol) {
|
|
215
|
+
if (!BLOCKBOOK_NODES[symbol])
|
|
216
|
+
return;
|
|
217
|
+
BLOCKBOOK_NODES[symbol].sort(function (a, b) { return a.priority - b.priority; });
|
|
218
|
+
};
|
|
219
|
+
var update_legacy_urls = function (symbol) {
|
|
220
|
+
if (!BLOCKBOOK_NODES[symbol])
|
|
221
|
+
return;
|
|
222
|
+
var activeNodes = BLOCKBOOK_NODES[symbol].filter(function (n) { return n.isActive; });
|
|
223
|
+
if (activeNodes.length === 0) {
|
|
224
|
+
delete BLOCKBOOK_URLS[symbol];
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (activeNodes.length === 1) {
|
|
228
|
+
BLOCKBOOK_URLS[symbol] = activeNodes[0].url;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
BLOCKBOOK_URLS[symbol] = activeNodes.map(function (n) { return n.url; });
|
|
232
|
+
}
|
|
233
|
+
};
|
|
142
234
|
var init_network = function (servers) {
|
|
143
235
|
return __awaiter(this, void 0, void 0, function () {
|
|
144
|
-
var tag, SEED_NODES, blockbooks, i, blockbook, url, e_1;
|
|
236
|
+
var tag, SEED_NODES, blockbooks, i, blockbook, symbol, priority, url, symbol, nodes_1, e_1;
|
|
145
237
|
return __generator(this, function (_a) {
|
|
146
238
|
switch (_a.label) {
|
|
147
239
|
case 0:
|
|
@@ -163,11 +255,26 @@ var init_network = function (servers) {
|
|
|
163
255
|
blockbooks = SEED_NODES;
|
|
164
256
|
}
|
|
165
257
|
log.debug(tag, "blockbooks: ", blockbooks.length);
|
|
258
|
+
// Clear existing nodes
|
|
259
|
+
BLOCKBOOK_NODES = {};
|
|
260
|
+
// Process nodes with priority assignment
|
|
166
261
|
for (i = 0; i < blockbooks.length; i++) {
|
|
167
262
|
blockbook = blockbooks[i];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
263
|
+
if (blockbook && blockbook.service) {
|
|
264
|
+
symbol = blockbook.symbol.toUpperCase();
|
|
265
|
+
priority = 50;
|
|
266
|
+
if (blockbook.service.includes('nownodes.io')) {
|
|
267
|
+
priority = 0; // Highest priority for NowNodes (fastest)
|
|
268
|
+
}
|
|
269
|
+
else if (blockbook.service.includes('zelcore.io')) {
|
|
270
|
+
priority = 10; // Lower priority for Zelcore (slower)
|
|
271
|
+
}
|
|
272
|
+
else if (blockbook.service.includes('shapeshift.com')) {
|
|
273
|
+
priority = 20; // Even lower for ShapeShift
|
|
274
|
+
}
|
|
275
|
+
// Add node with proper priority
|
|
276
|
+
add_custom_node(symbol, blockbook.service, priority);
|
|
277
|
+
}
|
|
171
278
|
if (blockbook && blockbook.websocket) {
|
|
172
279
|
url = blockbook.websocket.replace("/websocket", "");
|
|
173
280
|
url = blockbook.websocket.replace("wss://", "https://");
|
|
@@ -177,10 +284,17 @@ var init_network = function (servers) {
|
|
|
177
284
|
});
|
|
178
285
|
}
|
|
179
286
|
else {
|
|
180
|
-
log.
|
|
181
|
-
// throw Error("invalid unchained service!")
|
|
287
|
+
log.info(tag, "no websocket for: ", blockbook.symbol);
|
|
182
288
|
}
|
|
183
289
|
}
|
|
290
|
+
// Log final configuration
|
|
291
|
+
for (symbol in BLOCKBOOK_NODES) {
|
|
292
|
+
nodes_1 = BLOCKBOOK_NODES[symbol];
|
|
293
|
+
log.info(tag, "".concat(symbol, ": ").concat(nodes_1.length, " nodes configured"));
|
|
294
|
+
nodes_1.forEach(function (node, idx) {
|
|
295
|
+
log.info(tag, " ".concat(idx + 1, ". ").concat(node.url, " (priority: ").concat(node.priority, ")"));
|
|
296
|
+
});
|
|
297
|
+
}
|
|
184
298
|
log.debug(tag, "BLOCKBOOK_URLS: ", BLOCKBOOK_URLS);
|
|
185
299
|
log.debug(tag, "BLOCKBOOK_SOCKETS: ", BLOCKBOOK_SOCKETS);
|
|
186
300
|
return [2 /*return*/, true];
|
|
@@ -381,41 +495,43 @@ var get_txs_by_xpub = function (coin, xpub) {
|
|
|
381
495
|
var broadcast_transaction = function (coin, hex) {
|
|
382
496
|
return __awaiter(this, void 0, void 0, function () {
|
|
383
497
|
var tag, url, data, body, output, resp, e_7, errorMessage, statusCode, e_8;
|
|
384
|
-
var _a;
|
|
385
|
-
return __generator(this, function (
|
|
386
|
-
switch (
|
|
498
|
+
var _a, _b;
|
|
499
|
+
return __generator(this, function (_c) {
|
|
500
|
+
switch (_c.label) {
|
|
387
501
|
case 0:
|
|
388
502
|
tag = TAG + " | broadcast_transaction | ";
|
|
389
|
-
|
|
503
|
+
_c.label = 1;
|
|
390
504
|
case 1:
|
|
391
|
-
|
|
505
|
+
_c.trys.push([1, 6, , 7]);
|
|
392
506
|
url = BLOCKBOOK_URLS[coin.toUpperCase()] + "/api/v2/sendtx/";
|
|
393
507
|
data = hex;
|
|
394
508
|
body = {
|
|
395
509
|
url: url,
|
|
396
510
|
headers: {
|
|
397
|
-
'content-type': '
|
|
511
|
+
'content-type': 'text/plain',
|
|
398
512
|
'User-Agent': fakeUa()
|
|
399
513
|
},
|
|
400
514
|
method: 'POST',
|
|
401
|
-
json: false,
|
|
402
515
|
data: data,
|
|
403
516
|
};
|
|
404
517
|
output = {
|
|
405
518
|
success: false
|
|
406
519
|
};
|
|
407
520
|
resp = void 0;
|
|
408
|
-
|
|
521
|
+
_c.label = 2;
|
|
409
522
|
case 2:
|
|
410
|
-
|
|
411
|
-
return [4 /*yield*/, axios(body)
|
|
523
|
+
_c.trys.push([2, 4, , 5]);
|
|
524
|
+
return [4 /*yield*/, axios(body)
|
|
525
|
+
// Only store serializable data, not the full axios response
|
|
526
|
+
];
|
|
412
527
|
case 3:
|
|
413
|
-
resp =
|
|
414
|
-
|
|
528
|
+
resp = _c.sent();
|
|
529
|
+
// Only store serializable data, not the full axios response
|
|
530
|
+
output.txid = ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.result) || resp.data;
|
|
415
531
|
output.success = true;
|
|
416
532
|
return [3 /*break*/, 5];
|
|
417
533
|
case 4:
|
|
418
|
-
e_7 =
|
|
534
|
+
e_7 = _c.sent();
|
|
419
535
|
log.error(tag, "Broadcast error: ", e_7.message || e_7);
|
|
420
536
|
errorMessage = 'Unknown error occurred';
|
|
421
537
|
statusCode = null;
|
|
@@ -443,7 +559,7 @@ var broadcast_transaction = function (coin, hex) {
|
|
|
443
559
|
else if (e_7.request) {
|
|
444
560
|
// Request was made but no response received
|
|
445
561
|
errorMessage = 'Network error: No response received';
|
|
446
|
-
log.debug(tag, "Request config: ", (
|
|
562
|
+
log.debug(tag, "Request config: ", (_b = e_7.config) === null || _b === void 0 ? void 0 : _b.url);
|
|
447
563
|
}
|
|
448
564
|
else {
|
|
449
565
|
// Something else happened
|
|
@@ -454,7 +570,7 @@ var broadcast_transaction = function (coin, hex) {
|
|
|
454
570
|
return [3 /*break*/, 5];
|
|
455
571
|
case 5: return [2 /*return*/, output];
|
|
456
572
|
case 6:
|
|
457
|
-
e_8 =
|
|
573
|
+
e_8 = _c.sent();
|
|
458
574
|
console.error(tag, 'error: ', e_8);
|
|
459
575
|
throw e_8;
|
|
460
576
|
case 7: return [2 /*return*/];
|
|
@@ -495,88 +611,123 @@ var get_transaction = function (coin, txid) {
|
|
|
495
611
|
});
|
|
496
612
|
});
|
|
497
613
|
};
|
|
614
|
+
// Enhanced UTXO function with priority-based sequential failover
|
|
498
615
|
var get_utxos_by_xpub = function (coin, xpub) {
|
|
499
616
|
return __awaiter(this, void 0, void 0, function () {
|
|
500
|
-
var tag,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
return __generator(this, function (_b) {
|
|
504
|
-
switch (_b.label) {
|
|
617
|
+
var tag, symbol, nodes_2, activeNodes, i, node, startTime, url, body, resp, responseTime, error_1, responseTime, errorMessage, e_10;
|
|
618
|
+
return __generator(this, function (_a) {
|
|
619
|
+
switch (_a.label) {
|
|
505
620
|
case 0:
|
|
506
|
-
tag = TAG + " |
|
|
507
|
-
|
|
621
|
+
tag = TAG + " | get_utxos_by_xpub | ";
|
|
622
|
+
_a.label = 1;
|
|
508
623
|
case 1:
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (!
|
|
513
|
-
|
|
514
|
-
log.info(tag, "Querying ".concat(baseUrls.length, " nodes for ").concat(coin, "..."));
|
|
515
|
-
return [4 /*yield*/, Promise.allSettled(baseUrls.map(function (baseUrl) { return __awaiter(_this, void 0, void 0, function () {
|
|
516
|
-
var url, body, resp;
|
|
517
|
-
return __generator(this, function (_a) {
|
|
518
|
-
switch (_a.label) {
|
|
519
|
-
case 0:
|
|
520
|
-
url = baseUrl + "/api/v2/utxo/" + xpub + "?confirmed=false";
|
|
521
|
-
console.log("Querying node:", url);
|
|
522
|
-
body = {
|
|
523
|
-
method: 'GET',
|
|
524
|
-
url: url,
|
|
525
|
-
};
|
|
526
|
-
return [4 /*yield*/, axios(body)];
|
|
527
|
-
case 1:
|
|
528
|
-
resp = _a.sent();
|
|
529
|
-
return [2 /*return*/, resp.data];
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
}); }))];
|
|
533
|
-
case 2:
|
|
534
|
-
results = _b.sent();
|
|
535
|
-
allUtxos = [];
|
|
536
|
-
for (i = 0; i < results.length; i++) {
|
|
537
|
-
result = results[i];
|
|
538
|
-
if (result.status === 'fulfilled') {
|
|
539
|
-
utxos = result.value;
|
|
540
|
-
log.info(tag, "Node ".concat(i + 1, " returned ").concat(utxos.length, " UTXOs"));
|
|
541
|
-
allUtxos.push.apply(allUtxos, utxos);
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
log.warn(tag, "Node ".concat(i + 1, " failed:"), (_a = result.reason) === null || _a === void 0 ? void 0 : _a.message);
|
|
545
|
-
}
|
|
624
|
+
_a.trys.push([1, 8, , 9]);
|
|
625
|
+
symbol = coin.toUpperCase();
|
|
626
|
+
nodes_2 = BLOCKBOOK_NODES[symbol];
|
|
627
|
+
if (!nodes_2 || nodes_2.length === 0) {
|
|
628
|
+
throw new Error("No nodes configured for ".concat(symbol));
|
|
546
629
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
utxo = allUtxos_1[_i];
|
|
551
|
-
key = "".concat(utxo.txid, ":").concat(utxo.vout);
|
|
552
|
-
if (!seen.has(key)) {
|
|
553
|
-
seen.add(key);
|
|
554
|
-
uniqueUtxos.push(utxo);
|
|
555
|
-
}
|
|
630
|
+
activeNodes = nodes_2.filter(function (n) { return n.isActive; }).sort(function (a, b) { return a.priority - b.priority; });
|
|
631
|
+
if (activeNodes.length === 0) {
|
|
632
|
+
throw new Error("No active nodes available for ".concat(symbol));
|
|
556
633
|
}
|
|
557
|
-
log.info(tag, "
|
|
558
|
-
|
|
634
|
+
log.info(tag, "Attempting UTXO query for ".concat(symbol, " with ").concat(activeNodes.length, " nodes available"));
|
|
635
|
+
i = 0;
|
|
636
|
+
_a.label = 2;
|
|
637
|
+
case 2:
|
|
638
|
+
if (!(i < activeNodes.length)) return [3 /*break*/, 7];
|
|
639
|
+
node = activeNodes[i];
|
|
640
|
+
startTime = Date.now();
|
|
641
|
+
_a.label = 3;
|
|
559
642
|
case 3:
|
|
560
|
-
|
|
561
|
-
|
|
643
|
+
_a.trys.push([3, 5, , 6]);
|
|
644
|
+
log.info(tag, "Trying node ".concat(i + 1, "/").concat(activeNodes.length, ": ").concat(node.url, " (priority: ").concat(node.priority, ")"));
|
|
645
|
+
url = node.url + "/api/v2/utxo/" + xpub + "?confirmed=false";
|
|
562
646
|
body = {
|
|
563
647
|
method: 'GET',
|
|
564
648
|
url: url,
|
|
649
|
+
timeout: 15000 // 15s timeout per node
|
|
565
650
|
};
|
|
566
651
|
return [4 /*yield*/, axios(body)];
|
|
567
652
|
case 4:
|
|
568
|
-
resp =
|
|
653
|
+
resp = _a.sent();
|
|
654
|
+
responseTime = Date.now() - startTime;
|
|
655
|
+
// Update node performance metrics
|
|
656
|
+
update_node_performance(symbol, node.url, true, responseTime);
|
|
657
|
+
log.info(tag, "\u2705 Node ".concat(i + 1, " succeeded in ").concat(responseTime, "ms, returned ").concat(resp.data.length, " UTXOs"));
|
|
569
658
|
return [2 /*return*/, resp.data];
|
|
570
|
-
case 5:
|
|
659
|
+
case 5:
|
|
660
|
+
error_1 = _a.sent();
|
|
661
|
+
responseTime = Date.now() - startTime;
|
|
662
|
+
// Update node performance metrics
|
|
663
|
+
update_node_performance(symbol, node.url, false, responseTime);
|
|
664
|
+
errorMessage = error_1 instanceof Error ? error_1.message : String(error_1);
|
|
665
|
+
log.warn(tag, "\u274C Node ".concat(i + 1, " failed after ").concat(responseTime, "ms:"), errorMessage);
|
|
666
|
+
// If this is the last node, throw the error
|
|
667
|
+
if (i === activeNodes.length - 1) {
|
|
668
|
+
throw new Error("All ".concat(activeNodes.length, " nodes failed for ").concat(symbol, ". Last error: ").concat(errorMessage));
|
|
669
|
+
}
|
|
670
|
+
// Continue to next node
|
|
671
|
+
return [3 /*break*/, 6];
|
|
571
672
|
case 6:
|
|
572
|
-
|
|
673
|
+
i++;
|
|
674
|
+
return [3 /*break*/, 2];
|
|
675
|
+
case 7: return [3 /*break*/, 9];
|
|
676
|
+
case 8:
|
|
677
|
+
e_10 = _a.sent();
|
|
573
678
|
console.error(tag, e_10);
|
|
574
679
|
throw e_10;
|
|
575
|
-
case
|
|
680
|
+
case 9: return [2 /*return*/];
|
|
576
681
|
}
|
|
577
682
|
});
|
|
578
683
|
});
|
|
579
684
|
};
|
|
685
|
+
// Performance tracking function
|
|
686
|
+
var update_node_performance = function (coin, url, success, responseTime) {
|
|
687
|
+
var symbol = coin.toUpperCase();
|
|
688
|
+
var nodes = BLOCKBOOK_NODES[symbol];
|
|
689
|
+
if (!nodes)
|
|
690
|
+
return;
|
|
691
|
+
var node = nodes.find(function (n) { return n.url === url; });
|
|
692
|
+
if (!node)
|
|
693
|
+
return;
|
|
694
|
+
// Update counters
|
|
695
|
+
node.totalRequests++;
|
|
696
|
+
if (success) {
|
|
697
|
+
node.successfulRequests++;
|
|
698
|
+
node.consecutiveFailures = 0;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
node.consecutiveFailures++;
|
|
702
|
+
}
|
|
703
|
+
// Update success rate
|
|
704
|
+
node.successRate = (node.successfulRequests / node.totalRequests) * 100;
|
|
705
|
+
// Update average response time (exponential moving average)
|
|
706
|
+
var alpha = 0.3; // Weight for new sample
|
|
707
|
+
node.avgResponseTime = (alpha * responseTime) + ((1 - alpha) * node.avgResponseTime);
|
|
708
|
+
// Update health status
|
|
709
|
+
var wasActive = node.isActive;
|
|
710
|
+
// Deactivate if too many consecutive failures or very low success rate
|
|
711
|
+
if (node.consecutiveFailures >= 3 || (node.totalRequests >= 10 && node.successRate < 50)) {
|
|
712
|
+
node.isActive = false;
|
|
713
|
+
if (wasActive) {
|
|
714
|
+
log.warn("Deactivated node ".concat(url, " for ").concat(symbol, " (failures: ").concat(node.consecutiveFailures, ", success rate: ").concat(node.successRate.toFixed(1), "%)"));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// Reactivate if it was down but now working
|
|
718
|
+
if (!node.isActive && success && node.consecutiveFailures === 0) {
|
|
719
|
+
node.isActive = true;
|
|
720
|
+
log.info("Reactivated node ".concat(url, " for ").concat(symbol));
|
|
721
|
+
}
|
|
722
|
+
node.lastChecked = Date.now();
|
|
723
|
+
// Update legacy compatibility
|
|
724
|
+
update_legacy_urls(symbol);
|
|
725
|
+
// Auto-reorder nodes based on performance every 10 requests
|
|
726
|
+
if (node.totalRequests % 10 === 0) {
|
|
727
|
+
reorder_nodes_by_performance(symbol);
|
|
728
|
+
log.info("Auto-reordered ".concat(symbol, " nodes based on performance"));
|
|
729
|
+
}
|
|
730
|
+
};
|
|
580
731
|
var get_balance_by_xpub = function (coin, xpub) {
|
|
581
732
|
return __awaiter(this, void 0, void 0, function () {
|
|
582
733
|
var tag, output, balance, i, uxto, e_11;
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pioneer-platform/blockbook",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.0",
|
|
4
4
|
"main": "./lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "pnpm run build && node __tests__/test-module.js",
|
|
8
|
+
"start": "pnpm run build:live",
|
|
9
|
+
"build": "tsc -p .",
|
|
10
|
+
"prepublish": "tsc -p .",
|
|
11
|
+
"build:live": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
|
|
12
|
+
},
|
|
6
13
|
"dependencies": {
|
|
7
|
-
"@pioneer-platform/loggerdog": "^8.
|
|
8
|
-
"@pioneer-platform/nodes": "^8.
|
|
9
|
-
"@pioneer-platform/pioneer-caip": "^9.
|
|
14
|
+
"@pioneer-platform/loggerdog": "^8.5.0",
|
|
15
|
+
"@pioneer-platform/nodes": "^8.5.0",
|
|
16
|
+
"@pioneer-platform/pioneer-caip": "^9.4.0",
|
|
10
17
|
"@types/request-promise-native": "^1.0.17",
|
|
11
18
|
"axiom": "^0.1.6",
|
|
12
19
|
"axios": "^1.6.0",
|
|
@@ -24,12 +31,5 @@
|
|
|
24
31
|
"keepkey"
|
|
25
32
|
],
|
|
26
33
|
"author": "highlander",
|
|
27
|
-
"license": "MIT"
|
|
28
|
-
"scripts": {
|
|
29
|
-
"test": "pnpm run build && node __tests__/test-module.js",
|
|
30
|
-
"start": "pnpm run build:live",
|
|
31
|
-
"build": "tsc -p .",
|
|
32
|
-
"prepublish": "tsc -p .",
|
|
33
|
-
"build:live": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
|
|
34
|
-
}
|
|
34
|
+
"license": "MIT"
|
|
35
35
|
}
|