@pioneer-platform/blockbook 8.12.8 → 8.12.10
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/.turbo/turbo-build.log +1 -2
- package/CHANGELOG.md +20 -0
- package/lib/index.js +131 -81
- package/package.json +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
[0m[2m[35m$[0m [2m[1mtsc -p .[0m
|
|
1
|
+
$ tsc -p .
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @pioneer-platform/blockbook
|
|
2
2
|
|
|
3
|
+
## 8.12.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- CRITICAL FIX: Prevent node deactivation from transaction validation errors
|
|
8
|
+
|
|
9
|
+
Fixed critical bug where blockbook nodes were incorrectly marked as inactive when broadcast transactions failed due to transaction validation errors (e.g., "min relay fee not met"). These validation errors represent successful node responses with invalid transactions, not node failures.
|
|
10
|
+
|
|
11
|
+
Changes:
|
|
12
|
+
|
|
13
|
+
- Modified broadcast error handling to distinguish between transaction validation errors and actual node failures
|
|
14
|
+
- Added hardcoded Digibyte fees (100-150 sat/byte) to meet minimum relay requirements
|
|
15
|
+
- Nodes are now only penalized for real connection/network/server errors
|
|
16
|
+
|
|
17
|
+
## 8.12.9
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- fix(blockbook): Add dgub to xpub conversion for DGB
|
|
22
|
+
|
|
3
23
|
## 8.12.8
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/lib/index.js
CHANGED
|
@@ -514,7 +514,7 @@ var get_txs_by_xpub = function (coin, xpub) {
|
|
|
514
514
|
};
|
|
515
515
|
var broadcast_transaction = function (coin, hex) {
|
|
516
516
|
return __awaiter(this, void 0, void 0, function () {
|
|
517
|
-
var tag, symbol, nodes_2, activeNodes, MAX_RETRIES, RETRY_DELAY_MS_1, allErrors, retry,
|
|
517
|
+
var tag, symbol, nodes_2, activeNodes, MAX_RETRIES, RETRY_DELAY_MS_1, allErrors, retry, _loop_1, i, state_1, errorSummary, e_7;
|
|
518
518
|
var _a, _b;
|
|
519
519
|
return __generator(this, function (_c) {
|
|
520
520
|
switch (_c.label) {
|
|
@@ -522,7 +522,7 @@ var broadcast_transaction = function (coin, hex) {
|
|
|
522
522
|
tag = TAG + " | broadcast_transaction | ";
|
|
523
523
|
_c.label = 1;
|
|
524
524
|
case 1:
|
|
525
|
-
_c.trys.push([1,
|
|
525
|
+
_c.trys.push([1, 10, , 11]);
|
|
526
526
|
symbol = coin.toUpperCase();
|
|
527
527
|
nodes_2 = BLOCKBOOK_NODES[symbol];
|
|
528
528
|
if (!nodes_2 || nodes_2.length === 0) {
|
|
@@ -539,95 +539,126 @@ var broadcast_transaction = function (coin, hex) {
|
|
|
539
539
|
retry = 0;
|
|
540
540
|
_c.label = 2;
|
|
541
541
|
case 2:
|
|
542
|
-
if (!(retry < MAX_RETRIES)) return [3 /*break*/,
|
|
542
|
+
if (!(retry < MAX_RETRIES)) return [3 /*break*/, 9];
|
|
543
543
|
log.info(tag, "Broadcast attempt ".concat(retry + 1, "/").concat(MAX_RETRIES));
|
|
544
|
+
_loop_1 = function (i) {
|
|
545
|
+
var node, startTime, url, body, resp, responseTime, txid, error_1, responseTime, errorMessage_1, statusCode, isNodeFailure, txValidationErrors, attemptInfo;
|
|
546
|
+
return __generator(this, function (_d) {
|
|
547
|
+
switch (_d.label) {
|
|
548
|
+
case 0:
|
|
549
|
+
node = activeNodes[i];
|
|
550
|
+
startTime = Date.now();
|
|
551
|
+
_d.label = 1;
|
|
552
|
+
case 1:
|
|
553
|
+
_d.trys.push([1, 3, , 4]);
|
|
554
|
+
log.info(tag, "Trying node ".concat(i + 1, "/").concat(activeNodes.length, ": ").concat(node.url, " (priority: ").concat(node.priority, ") [attempt ").concat(retry + 1, "]"));
|
|
555
|
+
url = node.url + "/api/v2/sendtx/";
|
|
556
|
+
body = {
|
|
557
|
+
url: url,
|
|
558
|
+
headers: {
|
|
559
|
+
'content-type': 'text/plain',
|
|
560
|
+
'User-Agent': fakeUa()
|
|
561
|
+
},
|
|
562
|
+
method: 'POST',
|
|
563
|
+
data: hex,
|
|
564
|
+
timeout: 15000 // 15s timeout per node
|
|
565
|
+
};
|
|
566
|
+
return [4 /*yield*/, axios(body)];
|
|
567
|
+
case 2:
|
|
568
|
+
resp = _d.sent();
|
|
569
|
+
responseTime = Date.now() - startTime;
|
|
570
|
+
// Update node performance metrics
|
|
571
|
+
update_node_performance(symbol, node.url, true, responseTime);
|
|
572
|
+
txid = ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.result) || resp.data;
|
|
573
|
+
log.info(tag, "\u2705 Broadcast succeeded via node ".concat(i + 1, " in ").concat(responseTime, "ms on attempt ").concat(retry + 1, ". TXID: ").concat(txid));
|
|
574
|
+
return [2 /*return*/, { value: {
|
|
575
|
+
success: true,
|
|
576
|
+
txid: txid
|
|
577
|
+
} }];
|
|
578
|
+
case 3:
|
|
579
|
+
error_1 = _d.sent();
|
|
580
|
+
responseTime = Date.now() - startTime;
|
|
581
|
+
errorMessage_1 = 'Unknown error occurred';
|
|
582
|
+
statusCode = null;
|
|
583
|
+
isNodeFailure = true;
|
|
584
|
+
if (error_1.response) {
|
|
585
|
+
statusCode = error_1.response.status;
|
|
586
|
+
log.error(tag, "Node ".concat(i + 1, " HTTP Status: "), statusCode);
|
|
587
|
+
log.error(tag, "Node ".concat(i + 1, " Response headers: "), error_1.response.headers);
|
|
588
|
+
if (error_1.response.data) {
|
|
589
|
+
log.error(tag, "Node ".concat(i + 1, " Response data: "), error_1.response.data);
|
|
590
|
+
if (error_1.response.data.error) {
|
|
591
|
+
errorMessage_1 = error_1.response.data.error;
|
|
592
|
+
}
|
|
593
|
+
else if (typeof error_1.response.data === 'string') {
|
|
594
|
+
errorMessage_1 = error_1.response.data;
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
errorMessage_1 = "HTTP ".concat(statusCode, ": ").concat(error_1.response.statusText || 'Request failed');
|
|
598
|
+
log.error(tag, "Node ".concat(i + 1, " Full response object: "), JSON.stringify(error_1.response.data));
|
|
599
|
+
}
|
|
600
|
+
txValidationErrors = [
|
|
601
|
+
'min relay fee',
|
|
602
|
+
'insufficient',
|
|
603
|
+
'bad-txns',
|
|
604
|
+
'txn-mempool-conflict',
|
|
605
|
+
'dust',
|
|
606
|
+
'absurdly-high-fee',
|
|
607
|
+
'mandatory-script-verify-flag-failed',
|
|
608
|
+
'non-final',
|
|
609
|
+
'too-long-mempool-chain'
|
|
610
|
+
];
|
|
611
|
+
if (txValidationErrors.some(function (pattern) { return errorMessage_1.toLowerCase().includes(pattern); })) {
|
|
612
|
+
isNodeFailure = false;
|
|
613
|
+
log.warn(tag, "\u26A0\uFE0F Transaction validation error (NOT node failure): ".concat(errorMessage_1));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
errorMessage_1 = "HTTP ".concat(statusCode, ": ").concat(error_1.response.statusText || 'Request failed');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else if (error_1.request) {
|
|
621
|
+
errorMessage_1 = 'Network error: No response received';
|
|
622
|
+
log.error(tag, "Node ".concat(i + 1, " Request config: "), (_b = error_1.config) === null || _b === void 0 ? void 0 : _b.url);
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
errorMessage_1 = error_1.message || 'Request setup error';
|
|
626
|
+
}
|
|
627
|
+
// Update node performance metrics ONLY if this was an actual node failure
|
|
628
|
+
// Don't penalize nodes for transaction validation errors
|
|
629
|
+
update_node_performance(symbol, node.url, !isNodeFailure, responseTime);
|
|
630
|
+
attemptInfo = "[Node ".concat(i + 1, ", Attempt ").concat(retry + 1, "/").concat(MAX_RETRIES, "]");
|
|
631
|
+
log.error(tag, "\u274C ".concat(attemptInfo, " failed after ").concat(responseTime, "ms: ").concat(errorMessage_1));
|
|
632
|
+
allErrors.push("".concat(attemptInfo, " ").concat(errorMessage_1));
|
|
633
|
+
return [2 /*return*/, "continue"];
|
|
634
|
+
case 4: return [2 /*return*/];
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
};
|
|
544
638
|
i = 0;
|
|
545
639
|
_c.label = 3;
|
|
546
640
|
case 3:
|
|
547
|
-
if (!(i < activeNodes.length)) return [3 /*break*/,
|
|
548
|
-
|
|
549
|
-
startTime = Date.now();
|
|
550
|
-
_c.label = 4;
|
|
641
|
+
if (!(i < activeNodes.length)) return [3 /*break*/, 6];
|
|
642
|
+
return [5 /*yield**/, _loop_1(i)];
|
|
551
643
|
case 4:
|
|
552
|
-
_c.
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
url: url,
|
|
557
|
-
headers: {
|
|
558
|
-
'content-type': 'text/plain',
|
|
559
|
-
'User-Agent': fakeUa()
|
|
560
|
-
},
|
|
561
|
-
method: 'POST',
|
|
562
|
-
data: hex,
|
|
563
|
-
timeout: 15000 // 15s timeout per node
|
|
564
|
-
};
|
|
565
|
-
return [4 /*yield*/, axios(body)];
|
|
644
|
+
state_1 = _c.sent();
|
|
645
|
+
if (typeof state_1 === "object")
|
|
646
|
+
return [2 /*return*/, state_1.value];
|
|
647
|
+
_c.label = 5;
|
|
566
648
|
case 5:
|
|
567
|
-
resp = _c.sent();
|
|
568
|
-
responseTime = Date.now() - startTime;
|
|
569
|
-
// Update node performance metrics
|
|
570
|
-
update_node_performance(symbol, node.url, true, responseTime);
|
|
571
|
-
txid = ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.result) || resp.data;
|
|
572
|
-
log.info(tag, "\u2705 Broadcast succeeded via node ".concat(i + 1, " in ").concat(responseTime, "ms on attempt ").concat(retry + 1, ". TXID: ").concat(txid));
|
|
573
|
-
return [2 /*return*/, {
|
|
574
|
-
success: true,
|
|
575
|
-
txid: txid
|
|
576
|
-
}];
|
|
577
|
-
case 6:
|
|
578
|
-
error_1 = _c.sent();
|
|
579
|
-
responseTime = Date.now() - startTime;
|
|
580
|
-
// Update node performance metrics
|
|
581
|
-
update_node_performance(symbol, node.url, false, responseTime);
|
|
582
|
-
errorMessage = 'Unknown error occurred';
|
|
583
|
-
statusCode = null;
|
|
584
|
-
if (error_1.response) {
|
|
585
|
-
statusCode = error_1.response.status;
|
|
586
|
-
log.error(tag, "Node ".concat(i + 1, " HTTP Status: "), statusCode);
|
|
587
|
-
log.error(tag, "Node ".concat(i + 1, " Response headers: "), error_1.response.headers);
|
|
588
|
-
if (error_1.response.data) {
|
|
589
|
-
log.error(tag, "Node ".concat(i + 1, " Response data: "), error_1.response.data);
|
|
590
|
-
if (error_1.response.data.error) {
|
|
591
|
-
errorMessage = error_1.response.data.error;
|
|
592
|
-
}
|
|
593
|
-
else if (typeof error_1.response.data === 'string') {
|
|
594
|
-
errorMessage = error_1.response.data;
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
errorMessage = "HTTP ".concat(statusCode, ": ").concat(error_1.response.statusText || 'Request failed');
|
|
598
|
-
log.error(tag, "Node ".concat(i + 1, " Full response object: "), JSON.stringify(error_1.response.data));
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
errorMessage = "HTTP ".concat(statusCode, ": ").concat(error_1.response.statusText || 'Request failed');
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
else if (error_1.request) {
|
|
606
|
-
errorMessage = 'Network error: No response received';
|
|
607
|
-
log.error(tag, "Node ".concat(i + 1, " Request config: "), (_b = error_1.config) === null || _b === void 0 ? void 0 : _b.url);
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
errorMessage = error_1.message || 'Request setup error';
|
|
611
|
-
}
|
|
612
|
-
attemptInfo = "[Node ".concat(i + 1, ", Attempt ").concat(retry + 1, "/").concat(MAX_RETRIES, "]");
|
|
613
|
-
log.error(tag, "\u274C ".concat(attemptInfo, " failed after ").concat(responseTime, "ms: ").concat(errorMessage));
|
|
614
|
-
allErrors.push("".concat(attemptInfo, " ").concat(errorMessage));
|
|
615
|
-
// Continue to next node in this retry round
|
|
616
|
-
return [3 /*break*/, 7];
|
|
617
|
-
case 7:
|
|
618
649
|
i++;
|
|
619
650
|
return [3 /*break*/, 3];
|
|
620
|
-
case
|
|
621
|
-
if (!(retry < MAX_RETRIES - 1)) return [3 /*break*/,
|
|
651
|
+
case 6:
|
|
652
|
+
if (!(retry < MAX_RETRIES - 1)) return [3 /*break*/, 8];
|
|
622
653
|
log.info(tag, "All nodes failed in attempt ".concat(retry + 1, ". Waiting ").concat(RETRY_DELAY_MS_1, "ms before retry..."));
|
|
623
654
|
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, RETRY_DELAY_MS_1); })];
|
|
624
|
-
case
|
|
655
|
+
case 7:
|
|
625
656
|
_c.sent();
|
|
626
|
-
_c.label =
|
|
627
|
-
case
|
|
657
|
+
_c.label = 8;
|
|
658
|
+
case 8:
|
|
628
659
|
retry++;
|
|
629
660
|
return [3 /*break*/, 2];
|
|
630
|
-
case
|
|
661
|
+
case 9:
|
|
631
662
|
errorSummary = "All ".concat(activeNodes.length, " nodes failed after ").concat(MAX_RETRIES, " attempts each. Errors:\n").concat(allErrors.join('\n'));
|
|
632
663
|
log.error(tag, errorSummary);
|
|
633
664
|
return [2 /*return*/, {
|
|
@@ -635,11 +666,11 @@ var broadcast_transaction = function (coin, hex) {
|
|
|
635
666
|
error: errorSummary,
|
|
636
667
|
statusCode: 500
|
|
637
668
|
}];
|
|
638
|
-
case
|
|
669
|
+
case 10:
|
|
639
670
|
e_7 = _c.sent();
|
|
640
671
|
console.error(tag, 'error: ', e_7);
|
|
641
672
|
throw e_7;
|
|
642
|
-
case
|
|
673
|
+
case 11: return [2 /*return*/];
|
|
643
674
|
}
|
|
644
675
|
});
|
|
645
676
|
});
|
|
@@ -680,7 +711,7 @@ var get_transaction = function (coin, txid) {
|
|
|
680
711
|
// Enhanced UTXO function with priority-based sequential failover
|
|
681
712
|
var get_utxos_by_xpub = function (coin, xpub) {
|
|
682
713
|
return __awaiter(this, void 0, void 0, function () {
|
|
683
|
-
var tag, symbol, isBitcoin, nodes_3, activeNodes, i, node, startTime, url, body, resp, responseTime, error_2, responseTime, errorMessage, e_9;
|
|
714
|
+
var tag, symbol, isBitcoin, b58, data, payload, xpubPrefix, convertedData, convertedXpub, nodes_3, activeNodes, i, node, startTime, url, body, resp, responseTime, error_2, responseTime, errorMessage, e_9;
|
|
684
715
|
var _a;
|
|
685
716
|
return __generator(this, function (_b) {
|
|
686
717
|
switch (_b.label) {
|
|
@@ -691,6 +722,25 @@ var get_utxos_by_xpub = function (coin, xpub) {
|
|
|
691
722
|
_b.label = 1;
|
|
692
723
|
case 1:
|
|
693
724
|
_b.trys.push([1, 8, , 9]);
|
|
725
|
+
// Convert dgub to xpub for DGB (NowNodes blockbook doesn't accept dgub format)
|
|
726
|
+
if (symbol === 'DGB' && xpub.startsWith('dgub')) {
|
|
727
|
+
b58 = require('bs58');
|
|
728
|
+
try {
|
|
729
|
+
data = b58.decode(xpub);
|
|
730
|
+
payload = data.slice(4);
|
|
731
|
+
xpubPrefix = Buffer.from('0488b21e', 'hex');
|
|
732
|
+
convertedData = Buffer.concat([xpubPrefix, payload]);
|
|
733
|
+
convertedXpub = b58.encode(convertedData);
|
|
734
|
+
log.info(tag, "Converting DGB dgub to xpub format for blockbook compatibility");
|
|
735
|
+
log.info(tag, "Original: ".concat(xpub.substring(0, 20), "..."));
|
|
736
|
+
log.info(tag, "Converted: ".concat(convertedXpub.substring(0, 20), "..."));
|
|
737
|
+
xpub = convertedXpub;
|
|
738
|
+
}
|
|
739
|
+
catch (conversionError) {
|
|
740
|
+
log.error(tag, 'Failed to convert dgub to xpub:', conversionError);
|
|
741
|
+
// Continue with original xpub and let blockbook handle it
|
|
742
|
+
}
|
|
743
|
+
}
|
|
694
744
|
nodes_3 = BLOCKBOOK_NODES[symbol];
|
|
695
745
|
if (isBitcoin)
|
|
696
746
|
log.info(tag, '🔍 [BITCOIN BLOCKBOOK] Starting UTXO query for xpub:', xpub.substring(0, 20) + '...');
|