@parity/product-deploy 0.7.28-rc.0 → 0.7.28
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/assets/environments.json +0 -2
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-KJH2T5TQ.js → chunk-37M4NISG.js} +1 -1
- package/dist/{chunk-43HLT335.js → chunk-64KTKZ7H.js} +1 -1
- package/dist/{chunk-DNXH4QTI.js → chunk-6Y3XJGV7.js} +112 -16
- package/dist/{chunk-QTZNULSH.js → chunk-7NKOV5SU.js} +1 -1
- package/dist/{chunk-OITUIM2E.js → chunk-HZKRLQLG.js} +0 -3
- package/dist/{chunk-KHVTYIIX.js → chunk-IW3X2MJF.js} +2 -0
- package/dist/chunk-L2SKSKB6.js +308 -0
- package/dist/{chunk-MFTODIIT.js → chunk-LFKP64TQ.js} +13 -7
- package/dist/{chunk-ADNBLFDP.js → chunk-THZU3FZU.js} +2 -2
- package/dist/{chunk-P6CHOMN3.js → chunk-TPDF24MG.js} +25 -36
- package/dist/{chunk-QMYW3D6E.js → chunk-Y7XKC43A.js} +54 -100
- package/dist/{chunk-NF2FL4ZO.js → chunk-ZJDGVUN3.js} +3 -3
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +1 -2
- package/dist/deploy.js +11 -11
- package/dist/dotns.d.ts +10 -1
- package/dist/dotns.js +9 -5
- package/dist/environments.d.ts +0 -2
- package/dist/environments.js +1 -1
- package/dist/incremental-stats.d.ts +2 -0
- package/dist/incremental-stats.js +1 -1
- package/dist/index.js +12 -12
- package/dist/manifest/publish.js +12 -12
- package/dist/manifest-fetch.d.ts +2 -1
- package/dist/manifest-fetch.js +1 -1
- package/dist/manifest-roundtrip.js +1 -1
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +11 -11
- package/dist/personhood/bootstrap.js +5 -5
- package/dist/personhood/people-client.js +5 -5
- package/dist/pool.d.ts +6 -13
- package/dist/pool.js +3 -1
- package/dist/run-state.js +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.js +2 -2
- package/dist/version-check.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-FZWJV5AD.js +0 -231
package/assets/environments.json
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"backend": "https://polkadot-app-stg.parity.io/",
|
|
9
9
|
"ipfs": "https://previewnet.substrate.dev",
|
|
10
10
|
"autoAccountMapping": true,
|
|
11
|
-
"bulletinAuthorizeV2": true,
|
|
12
11
|
"registerStorageDeposit": 2000000000000,
|
|
13
12
|
"contracts": {
|
|
14
13
|
"DOTNS_PROTOCOL_REGISTRY": "0xc07A2F24387DA27283CD87b9F24573b74C9e0c9b",
|
|
@@ -55,7 +54,6 @@
|
|
|
55
54
|
"ipfs": "https://paseo-bulletin-next-ipfs.polkadot.io",
|
|
56
55
|
"docsUrl": "https://sre.teleport.parity.io/environments/paseo-next/",
|
|
57
56
|
"autoAccountMapping": true,
|
|
58
|
-
"bulletinAuthorizeV2": true,
|
|
59
57
|
"nativeToEthRatio": 100000000,
|
|
60
58
|
"registerStorageDeposit": 2000000000000,
|
|
61
59
|
"popSelfServe": {
|
package/dist/bug-report.js
CHANGED
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
offerBugReport,
|
|
10
10
|
scrubSecrets,
|
|
11
11
|
setDeployContext
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-THZU3FZU.js";
|
|
13
|
+
import "./chunk-64KTKZ7H.js";
|
|
14
|
+
import "./chunk-LFKP64TQ.js";
|
|
15
|
+
import "./chunk-37M4NISG.js";
|
|
16
16
|
export {
|
|
17
17
|
buildCliFlagsSummary,
|
|
18
18
|
buildLabels,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isTestnetSpecName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Y7XKC43A.js";
|
|
4
4
|
import {
|
|
5
5
|
captureWarning,
|
|
6
6
|
setDeployAttribute,
|
|
7
7
|
setDeploySentryTag,
|
|
8
8
|
truncateAddress,
|
|
9
9
|
withSpan
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-LFKP64TQ.js";
|
|
11
11
|
import {
|
|
12
12
|
validateContractAddresses
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-HZKRLQLG.js";
|
|
14
14
|
import {
|
|
15
15
|
NonRetryableError
|
|
16
16
|
} from "./chunk-ZOC4GITL.js";
|
|
@@ -93,7 +93,8 @@ var CONNECTION_TIMEOUT_MS = 3e4;
|
|
|
93
93
|
var OPERATION_TIMEOUT_MS = 3e5;
|
|
94
94
|
var TX_TIMEOUT_MS = 9e4;
|
|
95
95
|
var TX_CHAIN_TIME_BUDGET_MS = 18e4;
|
|
96
|
-
var TX_WALL_CLOCK_CEILING_MS =
|
|
96
|
+
var TX_WALL_CLOCK_CEILING_MS = 24e4;
|
|
97
|
+
var TX_NO_PROGRESS_MS = 9e4;
|
|
97
98
|
var WS_HEARTBEAT_TIMEOUT_MS = 3e5;
|
|
98
99
|
var DOTNS_TX_MAX_ATTEMPTS = 3;
|
|
99
100
|
function classifyTxRetryDecision(err) {
|
|
@@ -104,6 +105,7 @@ function classifyTxRetryDecision(err) {
|
|
|
104
105
|
if (/"type"\s*:\s*"future"|\binvalid::future\b/.test(lower)) return "retry";
|
|
105
106
|
if (lower.includes("websocket") || lower.includes("connection") || lower.includes("socket closed") || lower.includes("disconnect")) return "retry";
|
|
106
107
|
if (lower.includes("timed out") || lower.includes("timeout")) return "retry";
|
|
108
|
+
if (lower.includes("transaction watcher silent")) return "retry";
|
|
107
109
|
return "abort";
|
|
108
110
|
}
|
|
109
111
|
var DEFAULT_MNEMONIC = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
@@ -400,8 +402,8 @@ function sanitizeDomainLabel(label) {
|
|
|
400
402
|
function validateDomainLabel(label, opts = {}) {
|
|
401
403
|
if (!/^[a-z0-9-]{3,}$/.test(label)) throw new Error("Invalid domain label: must contain only lowercase letters, digits, and hyphens, min 3 chars");
|
|
402
404
|
if (label.startsWith("-") || label.endsWith("-")) throw new Error("Invalid domain label: cannot start or end with hyphen");
|
|
403
|
-
const sanitized = sanitizeDomainLabel(label);
|
|
404
|
-
if (/-\d+$/.test(sanitized)) {
|
|
405
|
+
const sanitized = opts.skipSanitize ? label : sanitizeDomainLabel(label);
|
|
406
|
+
if (!opts.skipSanitize && /-\d+$/.test(sanitized)) {
|
|
405
407
|
const baseWithHyphen = sanitized.replace(/\d+$/, "");
|
|
406
408
|
const dropHyphen = sanitized.replace(/-(\d+)$/, "$1");
|
|
407
409
|
const insertSegment = sanitized.replace(/-(\d+)$/, "-pr$1");
|
|
@@ -471,7 +473,7 @@ function parseDomainName(input) {
|
|
|
471
473
|
return { isSubdomain: false, label: sanitized, sublabel: null, parentLabel: null, fullName: `${sanitized}.dot` };
|
|
472
474
|
}
|
|
473
475
|
if (parts.length === 2) {
|
|
474
|
-
const sanitizedSub = validateDomainLabel(parts[0], { checkReserved: false });
|
|
476
|
+
const sanitizedSub = validateDomainLabel(parts[0], { checkReserved: false, skipSanitize: true });
|
|
475
477
|
const sanitizedParent = validateDomainLabel(parts[1]);
|
|
476
478
|
const fullLabel = `${sanitizedSub}.${sanitizedParent}`;
|
|
477
479
|
return { isSubdomain: true, label: fullLabel, sublabel: sanitizedSub, parentLabel: sanitizedParent, fullName: `${fullLabel}.dot` };
|
|
@@ -578,6 +580,8 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
578
580
|
}
|
|
579
581
|
};
|
|
580
582
|
const startWallClockMs = Date.now();
|
|
583
|
+
let lastEventAt = Date.now();
|
|
584
|
+
let lastEventType = "(none)";
|
|
581
585
|
let startChainTimeMs = null;
|
|
582
586
|
const poll = async () => {
|
|
583
587
|
if (settled) return;
|
|
@@ -612,6 +616,12 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
612
616
|
finish(reject)(new Error(`Transaction not included after ${Math.floor(chainElapsedMs / 1e3)}s of chain progress (budget=${TX_CHAIN_TIME_BUDGET_MS / 1e3}s)`));
|
|
613
617
|
return;
|
|
614
618
|
}
|
|
619
|
+
const silentMs = Date.now() - lastEventAt;
|
|
620
|
+
if (silentMs > TX_NO_PROGRESS_MS) {
|
|
621
|
+
statusCallback("failed");
|
|
622
|
+
finish(reject)(new Error(`transaction watcher silent for ${Math.floor(silentMs / 1e3)}s after ${lastEventType}`));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
615
625
|
} catch {
|
|
616
626
|
}
|
|
617
627
|
if (!settled) deadlinePoller = setTimeout(poll, 6e3);
|
|
@@ -620,6 +630,8 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
620
630
|
try {
|
|
621
631
|
sub = extrinsic.signSubmitAndWatch(signer, { mortality: { mortal: true, period: 256 } }).subscribe({
|
|
622
632
|
next: (event) => {
|
|
633
|
+
lastEventAt = Date.now();
|
|
634
|
+
lastEventType = event.type;
|
|
623
635
|
const transactionHash = event.txHash?.toString();
|
|
624
636
|
switch (event.type) {
|
|
625
637
|
case "signed":
|
|
@@ -634,7 +646,7 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
634
646
|
case "finalized": {
|
|
635
647
|
if (event.dispatchError || event.ok === false) {
|
|
636
648
|
statusCallback("failed");
|
|
637
|
-
finish(reject)(new Error(`Transaction failed: ${event.dispatchError
|
|
649
|
+
finish(reject)(new Error(`Transaction failed: ${formatDispatchError(event.dispatchError)}`));
|
|
638
650
|
return;
|
|
639
651
|
}
|
|
640
652
|
const block = event.block ? { hash: String(event.block.hash), number: Number(event.block.number) } : void 0;
|
|
@@ -721,7 +733,12 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
721
733
|
} catch {
|
|
722
734
|
}
|
|
723
735
|
}
|
|
724
|
-
const prep = await
|
|
736
|
+
const prep = await withSpan(
|
|
737
|
+
"chain.dry_run",
|
|
738
|
+
`dry-run ${functionName ?? "Revive.call"}`,
|
|
739
|
+
{ "chain.function_name": functionName ?? "Revive.call" },
|
|
740
|
+
() => this.dryRunReviveCall(contractAddress, value, encodedData, signerSubstrateAddress, { functionName, args, contracts })
|
|
741
|
+
);
|
|
725
742
|
const buildExtrinsic = () => this.client.tx.Revive.call({ dest: contractAddress, value, weight_limit: prep.weight_limit, storage_deposit_limit: prep.storage_deposit_limit, data: Binary.fromHex(encodedData) });
|
|
726
743
|
let nonceFallback;
|
|
727
744
|
if (useNoncePolling) {
|
|
@@ -731,7 +748,12 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
731
748
|
} catch {
|
|
732
749
|
}
|
|
733
750
|
}
|
|
734
|
-
return await
|
|
751
|
+
return await withSpan(
|
|
752
|
+
"chain.tx.submit",
|
|
753
|
+
`sign+submit ${functionName ?? "Revive.call"}`,
|
|
754
|
+
{ "chain.function_name": functionName ?? "Revive.call", "chain.use_nonce_polling": Boolean(useNoncePolling) },
|
|
755
|
+
() => this.signAndSubmitWithRetry(buildExtrinsic, signer, statusCallback, "Revive.call", { nonceFallback, verifyEffect })
|
|
756
|
+
);
|
|
735
757
|
}
|
|
736
758
|
// Dry-runs each call individually, then wraps them in a single
|
|
737
759
|
// Utility.batch_all extrinsic. batch_all is atomic over inner calls — only
|
|
@@ -740,7 +762,12 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
740
762
|
async submitBatchedTransactions(calls, signerSubstrateAddress, signer, statusCallback) {
|
|
741
763
|
if (calls.length === 0) throw new Error("submitBatchedTransactions: at least one call required");
|
|
742
764
|
await this.ensureAccountMapped(signerSubstrateAddress, signer);
|
|
743
|
-
const preps = await
|
|
765
|
+
const preps = await withSpan(
|
|
766
|
+
"chain.dry_run",
|
|
767
|
+
"dry-run Utility.batch_all",
|
|
768
|
+
{ "chain.function_name": "Utility.batch_all" },
|
|
769
|
+
() => Promise.all(calls.map((c) => this.dryRunReviveCall(c.contractAddress, c.value, c.encodedData, signerSubstrateAddress, { functionName: c.functionName, args: c.args })))
|
|
770
|
+
);
|
|
744
771
|
const buildExtrinsic = () => {
|
|
745
772
|
const inners = calls.map((c, i) => this.client.tx.Revive.call({
|
|
746
773
|
dest: c.contractAddress,
|
|
@@ -751,9 +778,24 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
751
778
|
}).decodedCall);
|
|
752
779
|
return this.client.tx.Utility.batch_all({ calls: inners });
|
|
753
780
|
};
|
|
754
|
-
return await
|
|
781
|
+
return await withSpan(
|
|
782
|
+
"chain.tx.submit",
|
|
783
|
+
"sign+submit Utility.batch_all",
|
|
784
|
+
{ "chain.function_name": "Utility.batch_all" },
|
|
785
|
+
() => this.signAndSubmitWithRetry(buildExtrinsic, signer, statusCallback, "Utility.batch_all")
|
|
786
|
+
);
|
|
755
787
|
}
|
|
756
788
|
};
|
|
789
|
+
function formatDispatchError(err) {
|
|
790
|
+
if (err === void 0 || err === null) return "dispatch error";
|
|
791
|
+
if (typeof err === "string") return err;
|
|
792
|
+
try {
|
|
793
|
+
const out = JSON.stringify(err, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
794
|
+
return typeof out === "string" ? out : "dispatch error";
|
|
795
|
+
} catch {
|
|
796
|
+
return String(err);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
757
799
|
function logTxResolution(res) {
|
|
758
800
|
setDeployAttribute(ATTR_TX_RESOLUTION_KIND, res.kind);
|
|
759
801
|
if (res.kind === TX_KIND_HASH) {
|
|
@@ -1319,13 +1361,39 @@ var DotNS = class {
|
|
|
1319
1361
|
const parentNode = namehash(`${parentLabel}.dot`);
|
|
1320
1362
|
const subnodeNode = namehash(`${sublabel}.${parentLabel}.dot`);
|
|
1321
1363
|
const subnodeRecord = { parentNode, subLabel: sublabel, parentLabel, owner: this.evmAddress };
|
|
1364
|
+
const MAX_VERIFY_CHAIN_SECONDS = 30;
|
|
1365
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
1366
|
+
const verifyEffect = async () => {
|
|
1367
|
+
const wrapper = this.clientWrapper;
|
|
1368
|
+
if (!this.connected || !wrapper) return false;
|
|
1369
|
+
const startChainMs = Number(await wrapper.client.query.Timestamp.Now.getValue());
|
|
1370
|
+
let lastPrintedElapsed = -1;
|
|
1371
|
+
while (true) {
|
|
1372
|
+
const liveWrapper = this.clientWrapper;
|
|
1373
|
+
if (!this.connected || !liveWrapper) return false;
|
|
1374
|
+
const [ownership, nowChainMs] = await Promise.all([
|
|
1375
|
+
this.checkSubdomainOwnership(sublabel, parentLabel),
|
|
1376
|
+
liveWrapper.client.query.Timestamp.Now.getValue().then(Number)
|
|
1377
|
+
]);
|
|
1378
|
+
if (ownership.owned) return true;
|
|
1379
|
+
const chainElapsed = (nowChainMs - startChainMs) / 1e3;
|
|
1380
|
+
if (chainElapsed >= MAX_VERIFY_CHAIN_SECONDS) return false;
|
|
1381
|
+
const floored = Math.floor(chainElapsed);
|
|
1382
|
+
if (floored > lastPrintedElapsed) {
|
|
1383
|
+
console.log(` Awaiting subnode finalization [verifyEffect] (chain time +${floored}s / ${MAX_VERIFY_CHAIN_SECONDS}s)...`);
|
|
1384
|
+
lastPrintedElapsed = floored;
|
|
1385
|
+
}
|
|
1386
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1322
1389
|
const txResolution = await this.submitBatchedContractCalls(
|
|
1323
1390
|
[
|
|
1324
1391
|
{ contractAddress: this._contracts.DOTNS_REGISTRY, abi: DOTNS_REGISTRY_ABI, functionName: "setSubnodeOwner", args: [subnodeRecord] },
|
|
1325
1392
|
{ contractAddress: this._contracts.DOTNS_REGISTRY, abi: DOTNS_REGISTRY_ABI, functionName: "setResolver", args: [subnodeNode, this._contracts.DOTNS_CONTENT_RESOLVER] }
|
|
1326
1393
|
],
|
|
1327
1394
|
(s) => console.log(` ${s}`),
|
|
1328
|
-
`Utility.batch_all (register ${sublabel}.${parentLabel}.dot)
|
|
1395
|
+
`Utility.batch_all (register ${sublabel}.${parentLabel}.dot)`,
|
|
1396
|
+
{ verifyEffect }
|
|
1329
1397
|
);
|
|
1330
1398
|
logTxResolution(txResolution);
|
|
1331
1399
|
if (txResolution.kind === TX_KIND_HASH) {
|
|
@@ -1353,7 +1421,7 @@ var DotNS = class {
|
|
|
1353
1421
|
* budget for every subsequent call, on the assumption sibling
|
|
1354
1422
|
* registry/resolver writes are similarly sized.
|
|
1355
1423
|
*/
|
|
1356
|
-
async submitBatchedContractCalls(calls, statusCallback, label) {
|
|
1424
|
+
async submitBatchedContractCalls(calls, statusCallback, label, { verifyEffect } = {}) {
|
|
1357
1425
|
this.ensureConnected();
|
|
1358
1426
|
if (!this.clientWrapper) throw new Error(`${label}: polkadot-api client not available`);
|
|
1359
1427
|
if (calls.length === 0) throw new Error(`${label}: at least one inner call required`);
|
|
@@ -1402,7 +1470,7 @@ var DotNS = class {
|
|
|
1402
1470
|
return client.tx.Utility.batch_all({ calls: inner.map((c) => c.decodedCall) });
|
|
1403
1471
|
};
|
|
1404
1472
|
return await withTimeout(
|
|
1405
|
-
this.clientWrapper.signAndSubmitWithRetry(buildBatch, this.signer, statusCallback, label),
|
|
1473
|
+
this.clientWrapper.signAndSubmitWithRetry(buildBatch, this.signer, statusCallback, label, { verifyEffect }),
|
|
1406
1474
|
OPERATION_TIMEOUT_MS,
|
|
1407
1475
|
label
|
|
1408
1476
|
);
|
|
@@ -1527,7 +1595,33 @@ var DotNS = class {
|
|
|
1527
1595
|
this.ensureConnected();
|
|
1528
1596
|
console.log(` Setting text[${key}]: ${value}`);
|
|
1529
1597
|
const node = namehash(`${domainName}.dot`);
|
|
1530
|
-
const
|
|
1598
|
+
const MAX_VERIFY_CHAIN_SECONDS = 30;
|
|
1599
|
+
const TEXT_POLL_INTERVAL_MS = 2e3;
|
|
1600
|
+
const verifyEffect = async () => {
|
|
1601
|
+
const wrapper = this.clientWrapper;
|
|
1602
|
+
if (!this.connected || !wrapper) return false;
|
|
1603
|
+
const startChainMs2 = Number(await wrapper.client.query.Timestamp.Now.getValue());
|
|
1604
|
+
let lastPrintedElapsed2 = -1;
|
|
1605
|
+
while (true) {
|
|
1606
|
+
const liveWrapper = this.clientWrapper;
|
|
1607
|
+
if (!this.connected || !liveWrapper) return false;
|
|
1608
|
+
const [onChainRaw, nowChainMs] = await Promise.all([
|
|
1609
|
+
this.contractCallNullable(this._contracts.DOTNS_CONTENT_RESOLVER, DOTNS_TEXT_RESOLVER_ABI, "text", [node, key]),
|
|
1610
|
+
liveWrapper.client.query.Timestamp.Now.getValue().then(Number)
|
|
1611
|
+
]);
|
|
1612
|
+
const onChain = onChainRaw ?? "";
|
|
1613
|
+
if (onChain === value) return true;
|
|
1614
|
+
const chainElapsed = (nowChainMs - startChainMs2) / 1e3;
|
|
1615
|
+
if (chainElapsed >= MAX_VERIFY_CHAIN_SECONDS) return false;
|
|
1616
|
+
const floored = Math.floor(chainElapsed);
|
|
1617
|
+
if (floored > lastPrintedElapsed2) {
|
|
1618
|
+
console.log(` Awaiting text finalization [verifyEffect] (chain time +${floored}s / ${MAX_VERIFY_CHAIN_SECONDS}s)...`);
|
|
1619
|
+
lastPrintedElapsed2 = floored;
|
|
1620
|
+
}
|
|
1621
|
+
await new Promise((r) => setTimeout(r, TEXT_POLL_INTERVAL_MS));
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
const textTxRes = await this.contractTransaction(this._contracts.DOTNS_CONTENT_RESOLVER, 0n, DOTNS_TEXT_RESOLVER_ABI, "setText", [node, key, value], (s) => console.log(` ${s}`), { useNoncePolling: true, verifyEffect });
|
|
1531
1625
|
logTxResolution(textTxRes);
|
|
1532
1626
|
const MAX_CHAIN_WAIT_SECONDS = 90;
|
|
1533
1627
|
const POLL_INTERVAL_MS = 2e3;
|
|
@@ -2302,6 +2396,7 @@ export {
|
|
|
2302
2396
|
TX_TIMEOUT_MS,
|
|
2303
2397
|
TX_CHAIN_TIME_BUDGET_MS,
|
|
2304
2398
|
TX_WALL_CLOCK_CEILING_MS,
|
|
2399
|
+
TX_NO_PROGRESS_MS,
|
|
2305
2400
|
WS_HEARTBEAT_TIMEOUT_MS,
|
|
2306
2401
|
DOTNS_TX_MAX_ATTEMPTS,
|
|
2307
2402
|
classifyTxRetryDecision,
|
|
@@ -2329,6 +2424,7 @@ export {
|
|
|
2329
2424
|
parseDomainName,
|
|
2330
2425
|
parseProofOfPersonhoodStatus,
|
|
2331
2426
|
popStatusName,
|
|
2427
|
+
formatDispatchError,
|
|
2332
2428
|
formatPersonhoodRemediation,
|
|
2333
2429
|
formatPopShortfallReason,
|
|
2334
2430
|
DotNS,
|
|
@@ -17,7 +17,6 @@ var environments_default = {
|
|
|
17
17
|
backend: "https://polkadot-app-stg.parity.io/",
|
|
18
18
|
ipfs: "https://previewnet.substrate.dev",
|
|
19
19
|
autoAccountMapping: true,
|
|
20
|
-
bulletinAuthorizeV2: true,
|
|
21
20
|
registerStorageDeposit: 2e12,
|
|
22
21
|
contracts: {
|
|
23
22
|
DOTNS_PROTOCOL_REGISTRY: "0xc07A2F24387DA27283CD87b9F24573b74C9e0c9b",
|
|
@@ -64,7 +63,6 @@ var environments_default = {
|
|
|
64
63
|
ipfs: "https://paseo-bulletin-next-ipfs.polkadot.io",
|
|
65
64
|
docsUrl: "https://sre.teleport.parity.io/environments/paseo-next/",
|
|
66
65
|
autoAccountMapping: true,
|
|
67
|
-
bulletinAuthorizeV2: true,
|
|
68
66
|
nativeToEthRatio: 1e8,
|
|
69
67
|
registerStorageDeposit: 2e12,
|
|
70
68
|
popSelfServe: {
|
|
@@ -471,7 +469,6 @@ function resolveEndpoints(doc, envId) {
|
|
|
471
469
|
envName: env.name,
|
|
472
470
|
ipfs: env.ipfs,
|
|
473
471
|
autoAccountMapping: env.autoAccountMapping ?? false,
|
|
474
|
-
bulletinAuthorizeV2: env.bulletinAuthorizeV2 ?? false,
|
|
475
472
|
contracts: env.contracts ?? {},
|
|
476
473
|
nativeToEthRatio: BigInt(env.nativeToEthRatio ?? 1e6),
|
|
477
474
|
...env.registerStorageDeposit !== void 0 ? { registerStorageDeposit: BigInt(env.registerStorageDeposit) } : {}
|
|
@@ -10,6 +10,7 @@ function computeStats(input) {
|
|
|
10
10
|
return {
|
|
11
11
|
manifestSource: input.manifestSource,
|
|
12
12
|
manifestFetchAttempts: input.manifestFetchAttempts,
|
|
13
|
+
manifestFetchReason: input.manifestFetchReason,
|
|
13
14
|
manifestBytes: input.manifestBytes ?? 0,
|
|
14
15
|
framework: input.framework,
|
|
15
16
|
filesTotal: input.filesTotal,
|
|
@@ -46,6 +47,7 @@ function telemetryAttributes(s) {
|
|
|
46
47
|
return {
|
|
47
48
|
"deploy.cache.manifest_source": s.manifestSource,
|
|
48
49
|
"deploy.cache.manifest_fetch_attempts": String(s.manifestFetchAttempts),
|
|
50
|
+
"deploy.cache.manifest_fetch_reason": s.manifestFetchReason ?? "",
|
|
49
51
|
"deploy.cache.manifest_bytes": String(s.manifestBytes),
|
|
50
52
|
"deploy.cache.framework": s.framework ?? "",
|
|
51
53
|
"deploy.cache.hit_rate": String(Math.round(hitRate * 1e3) / 1e3),
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MANIFEST_DIR,
|
|
3
|
+
MANIFEST_FILENAME,
|
|
4
|
+
parseManifest
|
|
5
|
+
} from "./chunk-S7EM5VMW.js";
|
|
6
|
+
|
|
7
|
+
// src/manifest-fetch.ts
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
import * as Sentry from "@sentry/node";
|
|
12
|
+
import { CarReader } from "@ipld/car/reader";
|
|
13
|
+
import * as dagPB from "@ipld/dag-pb";
|
|
14
|
+
import { CID } from "multiformats/cid";
|
|
15
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
16
|
+
var SIDECAR_FILENAME = ".last_deploy_cid";
|
|
17
|
+
var RANGE_TIERS = [
|
|
18
|
+
"bytes=0-4095",
|
|
19
|
+
"bytes=0-65535",
|
|
20
|
+
"bytes=0-1048575",
|
|
21
|
+
void 0
|
|
22
|
+
// full body
|
|
23
|
+
];
|
|
24
|
+
function getCacheDir() {
|
|
25
|
+
const override = process.env.BULLETIN_DEPLOY_CACHE_DIR;
|
|
26
|
+
if (override) return path.join(override, "manifests");
|
|
27
|
+
if (process.platform === "win32") return null;
|
|
28
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
29
|
+
if (xdg) return path.join(xdg, "bulletin-deploy", "manifests");
|
|
30
|
+
return path.join(os.homedir(), ".cache", "bulletin-deploy", "manifests");
|
|
31
|
+
}
|
|
32
|
+
function readPersistentLocalManifest(domain, prevContenthash) {
|
|
33
|
+
if (!domain) return null;
|
|
34
|
+
const cacheDir = getCacheDir();
|
|
35
|
+
if (!cacheDir) return null;
|
|
36
|
+
const cidPath = path.join(cacheDir, `${domain}.cid`);
|
|
37
|
+
const manifestPath = path.join(cacheDir, `${domain}.json`);
|
|
38
|
+
let storedCid;
|
|
39
|
+
try {
|
|
40
|
+
storedCid = fs.readFileSync(cidPath, "utf8").trim();
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (storedCid !== prevContenthash) return null;
|
|
45
|
+
let text;
|
|
46
|
+
try {
|
|
47
|
+
text = fs.readFileSync(manifestPath, "utf8");
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const parsed = parseManifest(text);
|
|
52
|
+
if (!parsed.ok) return null;
|
|
53
|
+
return {
|
|
54
|
+
source: "embedded",
|
|
55
|
+
manifest: parsed.manifest,
|
|
56
|
+
attempts: 0,
|
|
57
|
+
bytesDownloaded: text.length
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function writePersistentLocalManifest(domain, storageCid, manifestJson) {
|
|
61
|
+
const cacheDir = getCacheDir();
|
|
62
|
+
if (!cacheDir) return;
|
|
63
|
+
try {
|
|
64
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
65
|
+
const cidPath = path.join(cacheDir, `${domain}.cid`);
|
|
66
|
+
const manifestPath = path.join(cacheDir, `${domain}.json`);
|
|
67
|
+
const cidTmp = `${cidPath}.${process.pid}.tmp`;
|
|
68
|
+
const manifestTmp = `${manifestPath}.${process.pid}.tmp`;
|
|
69
|
+
fs.writeFileSync(cidTmp, storageCid);
|
|
70
|
+
fs.renameSync(cidTmp, cidPath);
|
|
71
|
+
fs.writeFileSync(manifestTmp, manifestJson);
|
|
72
|
+
fs.renameSync(manifestTmp, manifestPath);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function fetchOneTier(url, tierIndex, budgetRemaining) {
|
|
77
|
+
const rangeHeader = RANGE_TIERS[tierIndex];
|
|
78
|
+
const rangeLabel = rangeHeader != null ? rangeHeader.replace("bytes=", "") : "full";
|
|
79
|
+
const isFullBody = rangeHeader === void 0;
|
|
80
|
+
return Sentry.startSpan(
|
|
81
|
+
{ op: "manifest.fetch.tier", name: `tier ${tierIndex} ${rangeLabel}` },
|
|
82
|
+
async (span) => {
|
|
83
|
+
span.setAttribute("manifest.tier.index", String(tierIndex));
|
|
84
|
+
span.setAttribute("manifest.tier.range", rangeLabel);
|
|
85
|
+
span.setAttribute("manifest.tier.http_status", "");
|
|
86
|
+
span.setAttribute("manifest.tier.bytes", "0");
|
|
87
|
+
span.setAttribute("manifest.tier.wait_ms", "0");
|
|
88
|
+
span.setAttribute("manifest.tier.read_ms", "0");
|
|
89
|
+
span.setAttribute("manifest.tier.error", "");
|
|
90
|
+
const headers = {};
|
|
91
|
+
if (rangeHeader !== void 0) headers.Range = rangeHeader;
|
|
92
|
+
let res;
|
|
93
|
+
const fetchStart = Date.now();
|
|
94
|
+
try {
|
|
95
|
+
const ctrl = new AbortController();
|
|
96
|
+
const timer = setTimeout(() => ctrl.abort(), Math.max(100, budgetRemaining));
|
|
97
|
+
try {
|
|
98
|
+
res = await fetch(url, { headers, signal: ctrl.signal });
|
|
99
|
+
} finally {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
const isAbort = e?.name === "AbortError";
|
|
104
|
+
const outcome = isAbort ? "timeout" : "network_error";
|
|
105
|
+
const msg = e?.message ?? String(e);
|
|
106
|
+
span.setAttribute("manifest.tier.outcome", outcome);
|
|
107
|
+
span.setAttribute("manifest.tier.wait_ms", String(Date.now() - fetchStart));
|
|
108
|
+
span.setAttribute("manifest.tier.error", msg);
|
|
109
|
+
return { outcome, reason: `${outcome}: ${msg}`, bytes: 0 };
|
|
110
|
+
}
|
|
111
|
+
const waitMs = Date.now() - fetchStart;
|
|
112
|
+
span.setAttribute("manifest.tier.wait_ms", String(waitMs));
|
|
113
|
+
span.setAttribute("manifest.tier.http_status", String(res.status));
|
|
114
|
+
if (res.status === 404) {
|
|
115
|
+
span.setAttribute("manifest.tier.outcome", "http_404");
|
|
116
|
+
return { outcome: "http_404", reason: "gateway 404", bytes: 0 };
|
|
117
|
+
}
|
|
118
|
+
if (res.status !== 200 && res.status !== 206) {
|
|
119
|
+
const outcome = `http_${res.status}`;
|
|
120
|
+
span.setAttribute("manifest.tier.outcome", outcome);
|
|
121
|
+
span.setAttribute("manifest.tier.error", `HTTP ${res.status}`);
|
|
122
|
+
return { outcome, reason: `gateway HTTP ${res.status}`, bytes: 0 };
|
|
123
|
+
}
|
|
124
|
+
let carBytes;
|
|
125
|
+
const readStart = Date.now();
|
|
126
|
+
try {
|
|
127
|
+
const buf = await res.arrayBuffer();
|
|
128
|
+
carBytes = new Uint8Array(buf);
|
|
129
|
+
span.setAttribute("manifest.tier.bytes", String(carBytes.length));
|
|
130
|
+
span.setAttribute("manifest.tier.read_ms", String(Date.now() - readStart));
|
|
131
|
+
} catch (e) {
|
|
132
|
+
span.setAttribute("manifest.tier.read_ms", String(Date.now() - readStart));
|
|
133
|
+
const msg = `body read error: ${e?.message ?? e}`;
|
|
134
|
+
span.setAttribute("manifest.tier.outcome", "body_read_error");
|
|
135
|
+
span.setAttribute("manifest.tier.error", msg);
|
|
136
|
+
return { outcome: "body_read_error", reason: msg, bytes: 0 };
|
|
137
|
+
}
|
|
138
|
+
let manifestBytes;
|
|
139
|
+
try {
|
|
140
|
+
manifestBytes = await extractManifestFromCar(carBytes);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
const msg = `CAR parse error: ${e?.message ?? e}`;
|
|
143
|
+
const outcome = isFullBody ? "car_parse_error" : "car_truncated";
|
|
144
|
+
span.setAttribute("manifest.tier.outcome", outcome);
|
|
145
|
+
span.setAttribute("manifest.tier.error", msg);
|
|
146
|
+
return { outcome, reason: msg, bytes: carBytes.length };
|
|
147
|
+
}
|
|
148
|
+
if (!manifestBytes) {
|
|
149
|
+
const outcome = isFullBody ? "manifest_missing" : "manifest_not_in_slice";
|
|
150
|
+
const msg = isFullBody ? "no .bulletin-deploy/manifest.json in deployed DAG" : `manifest not in slice tier ${tierIndex}`;
|
|
151
|
+
span.setAttribute("manifest.tier.outcome", outcome);
|
|
152
|
+
span.setAttribute("manifest.tier.error", msg);
|
|
153
|
+
return { outcome, reason: msg, bytes: carBytes.length };
|
|
154
|
+
}
|
|
155
|
+
const text = new TextDecoder().decode(manifestBytes);
|
|
156
|
+
const parsed = parseManifest(text);
|
|
157
|
+
if (parsed.ok) {
|
|
158
|
+
span.setAttribute("manifest.tier.outcome", "success");
|
|
159
|
+
return { outcome: "success", reason: "", bytes: carBytes.length, manifest: parsed.manifest };
|
|
160
|
+
}
|
|
161
|
+
span.setAttribute("manifest.tier.outcome", "manifest_parse_error");
|
|
162
|
+
span.setAttribute("manifest.tier.error", parsed.error);
|
|
163
|
+
return { outcome: "manifest_parse_error", reason: parsed.error, bytes: carBytes.length };
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
async function fetchAcrossTiers(url, budget, start) {
|
|
168
|
+
let lastReason = "unknown";
|
|
169
|
+
let attempts = 0;
|
|
170
|
+
let bytesDownloaded = 0;
|
|
171
|
+
for (let tier = 0; tier < RANGE_TIERS.length; tier++) {
|
|
172
|
+
const elapsed = Date.now() - start;
|
|
173
|
+
if (elapsed > budget) {
|
|
174
|
+
return { outcome: "retryable", reason: `budget exceeded: ${lastReason}`, attempts, bytesDownloaded };
|
|
175
|
+
}
|
|
176
|
+
attempts++;
|
|
177
|
+
const result = await fetchOneTier(url, tier, budget - elapsed);
|
|
178
|
+
bytesDownloaded += result.bytes;
|
|
179
|
+
if (result.outcome === "success") {
|
|
180
|
+
return { outcome: "success", manifest: result.manifest, attempts, bytesDownloaded };
|
|
181
|
+
}
|
|
182
|
+
if (result.outcome === "http_404") {
|
|
183
|
+
return { outcome: "404", attempts, bytesDownloaded };
|
|
184
|
+
}
|
|
185
|
+
if (result.outcome === "car_parse_error" || result.outcome === "manifest_missing" || result.outcome === "manifest_parse_error") {
|
|
186
|
+
return { outcome: "parse_error", reason: result.reason, attempts, bytesDownloaded };
|
|
187
|
+
}
|
|
188
|
+
if (result.outcome === "body_read_error" && tier === RANGE_TIERS.length - 1) {
|
|
189
|
+
return { outcome: "parse_error", reason: result.reason, attempts, bytesDownloaded };
|
|
190
|
+
}
|
|
191
|
+
lastReason = result.reason;
|
|
192
|
+
}
|
|
193
|
+
return { outcome: "retryable", reason: `tiers exhausted: ${lastReason}`, attempts, bytesDownloaded };
|
|
194
|
+
}
|
|
195
|
+
async function fetchPreviousManifest(prevContenthash, options = {}) {
|
|
196
|
+
if (prevContenthash === null) return { source: "none" };
|
|
197
|
+
const local = readPersistentLocalManifest(options.domain, prevContenthash);
|
|
198
|
+
if (local) return local;
|
|
199
|
+
const gatewayList = (options.gateways ?? (options.gateway ? [options.gateway] : [])).map((g) => g.replace(/\/$/, ""));
|
|
200
|
+
const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
201
|
+
const start = Date.now();
|
|
202
|
+
let lastReason = "unknown";
|
|
203
|
+
let totalAttempts = 0;
|
|
204
|
+
let bytesDownloaded = 0;
|
|
205
|
+
for (const gatewayRaw of gatewayList) {
|
|
206
|
+
const gateway = gatewayRaw.replace(/\/ipfs\/?$/, "");
|
|
207
|
+
const url = `${gateway}/ipfs/${prevContenthash}`;
|
|
208
|
+
const gatewayStart = Date.now();
|
|
209
|
+
const tierResult = await Sentry.startSpan(
|
|
210
|
+
{
|
|
211
|
+
op: "manifest.fetch",
|
|
212
|
+
name: `manifest fetch ${prevContenthash.slice(0, 12)}`,
|
|
213
|
+
attributes: {
|
|
214
|
+
"manifest.fetch.gateway": gateway,
|
|
215
|
+
"manifest.fetch.cid": prevContenthash.slice(0, 12),
|
|
216
|
+
"manifest.fetch.budget_ms": String(budget)
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
async (span) => {
|
|
220
|
+
const result = await fetchAcrossTiers(url, budget, start);
|
|
221
|
+
span.setAttribute("manifest.fetch.outcome", result.outcome);
|
|
222
|
+
span.setAttribute("manifest.fetch.attempts", String(result.attempts));
|
|
223
|
+
span.setAttribute("manifest.fetch.bytes", String(result.bytesDownloaded));
|
|
224
|
+
span.setAttribute("manifest.fetch.elapsed_ms", String(Date.now() - gatewayStart));
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
if (tierResult.outcome === "success") {
|
|
229
|
+
return {
|
|
230
|
+
source: "embedded",
|
|
231
|
+
manifest: tierResult.manifest,
|
|
232
|
+
attempts: totalAttempts + tierResult.attempts,
|
|
233
|
+
bytesDownloaded: bytesDownloaded + tierResult.bytesDownloaded
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (tierResult.outcome === "404" || tierResult.outcome === "parse_error") {
|
|
237
|
+
return {
|
|
238
|
+
source: "heuristic_fallback",
|
|
239
|
+
reason: tierResult.outcome === "404" ? "gateway 404" : tierResult.reason,
|
|
240
|
+
attempts: totalAttempts + tierResult.attempts,
|
|
241
|
+
bytesDownloaded: bytesDownloaded + tierResult.bytesDownloaded
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
lastReason = tierResult.reason;
|
|
245
|
+
totalAttempts += tierResult.attempts;
|
|
246
|
+
bytesDownloaded += tierResult.bytesDownloaded;
|
|
247
|
+
}
|
|
248
|
+
return { source: "heuristic_fallback", reason: `all gateways exhausted: ${lastReason}`, attempts: totalAttempts, bytesDownloaded };
|
|
249
|
+
}
|
|
250
|
+
async function extractManifestFromCar(carBytes) {
|
|
251
|
+
const reader = await CarReader.fromBytes(carBytes);
|
|
252
|
+
const roots = await reader.getRoots();
|
|
253
|
+
if (roots.length === 0) return null;
|
|
254
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
255
|
+
for await (const { cid, bytes } of reader.blocks()) {
|
|
256
|
+
blocks.set(cid.toString(), bytes);
|
|
257
|
+
}
|
|
258
|
+
return walkDagToManifest(blocks, roots[0].toString());
|
|
259
|
+
}
|
|
260
|
+
async function walkDagToManifest(blocks, rootCid) {
|
|
261
|
+
const rootBytes = blocks.get(rootCid);
|
|
262
|
+
if (!rootBytes) return null;
|
|
263
|
+
const rootNode = dagPB.decode(rootBytes);
|
|
264
|
+
const bdLink = (rootNode.Links ?? []).find((l) => l.Name === MANIFEST_DIR);
|
|
265
|
+
if (!bdLink) return null;
|
|
266
|
+
const bdBytes = blocks.get(bdLink.Hash.toString());
|
|
267
|
+
if (!bdBytes) return null;
|
|
268
|
+
const bdNode = dagPB.decode(bdBytes);
|
|
269
|
+
const manLink = (bdNode.Links ?? []).find((l) => l.Name === MANIFEST_FILENAME);
|
|
270
|
+
if (!manLink) return null;
|
|
271
|
+
const manCidStr = manLink.Hash.toString();
|
|
272
|
+
const manCid = CID.parse(manCidStr);
|
|
273
|
+
const manBytes = blocks.get(manCidStr);
|
|
274
|
+
if (!manBytes) return null;
|
|
275
|
+
if (manCid.code === 85) {
|
|
276
|
+
return manBytes;
|
|
277
|
+
}
|
|
278
|
+
if (manCid.code === 112) {
|
|
279
|
+
const node = dagPB.decode(manBytes);
|
|
280
|
+
const parts = [];
|
|
281
|
+
let total = 0;
|
|
282
|
+
for (const link of node.Links ?? []) {
|
|
283
|
+
const leafBytes = blocks.get(link.Hash.toString());
|
|
284
|
+
if (!leafBytes) return null;
|
|
285
|
+
parts.push(leafBytes);
|
|
286
|
+
total += leafBytes.length;
|
|
287
|
+
}
|
|
288
|
+
const out = new Uint8Array(total);
|
|
289
|
+
let pos = 0;
|
|
290
|
+
for (const part of parts) {
|
|
291
|
+
out.set(part, pos);
|
|
292
|
+
pos += part.length;
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export {
|
|
300
|
+
DEFAULT_TIMEOUT_MS,
|
|
301
|
+
SIDECAR_FILENAME,
|
|
302
|
+
getCacheDir,
|
|
303
|
+
readPersistentLocalManifest,
|
|
304
|
+
writePersistentLocalManifest,
|
|
305
|
+
fetchPreviousManifest,
|
|
306
|
+
extractManifestFromCar,
|
|
307
|
+
walkDagToManifest
|
|
308
|
+
};
|