@parity/product-deploy 0.8.1 → 0.8.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -230
- package/assets/environments.json +6 -0
- package/bin/bulletin-deploy +15 -1
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-IRT4A7OO.js → chunk-2GBSYVK2.js} +139 -31
- package/dist/{chunk-CNPB4VAM.js → chunk-5K3RI5C2.js} +71 -0
- package/dist/{chunk-O2BFXY3Y.js → chunk-EJWZGSHD.js} +1 -1
- package/dist/{chunk-MXMVZU2Q.js → chunk-H64ZLWW2.js} +2 -2
- package/dist/{chunk-WPJADKC7.js → chunk-HPPLVGGC.js} +73 -42
- package/dist/{chunk-L2SKSKB6.js → chunk-J3NIXHZZ.js} +108 -0
- package/dist/{chunk-RW3GWDGI.js → chunk-JQ5X3VMP.js} +15 -102
- package/dist/{chunk-V5N5EYNV.js → chunk-NJPBXF5Z.js} +6 -3
- package/dist/{chunk-INVA3XGG.js → chunk-RIAMPAL2.js} +88 -77
- package/dist/{chunk-R2CJ5I5R.js → chunk-TQC3S6NP.js} +3 -3
- package/dist/{chunk-KB3EII7F.js → chunk-WSBDIHFZ.js} +1 -1
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +38 -1
- package/dist/deploy.js +16 -10
- package/dist/dotns.d.ts +33 -2
- package/dist/dotns.js +9 -5
- package/dist/environments.d.ts +17 -4
- package/dist/environments.js +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -11
- package/dist/manifest/publish.js +11 -11
- package/dist/manifest-fetch.d.ts +22 -1
- package/dist/manifest-fetch.js +7 -1
- package/dist/manifest-roundtrip.js +1 -1
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +10 -10
- package/dist/personhood/bootstrap.js +5 -5
- package/dist/personhood/people-client.js +5 -5
- package/dist/pool.d.ts +2 -12
- package/dist/pool.js +3 -11
- package/dist/run-state.js +1 -1
- package/dist/telemetry.js +2 -2
- package/dist/version-check.js +3 -3
- package/docs/bootstrap.md +1 -1
- package/package.json +6 -3
|
@@ -20,6 +20,12 @@ var environments_default = {
|
|
|
20
20
|
autoAccountMapping: true,
|
|
21
21
|
nativeToEthRatio: 1e8,
|
|
22
22
|
registerStorageDeposit: 2e12,
|
|
23
|
+
popSelfServe: {
|
|
24
|
+
sudoEnvLabel: "Preview",
|
|
25
|
+
personhoodFaucetUrl: "https://sudo.personhood.dev/personhood-faucet",
|
|
26
|
+
dotnsBootstrapUrl: "https://sudo.personhood.dev/dotns-bootstrap",
|
|
27
|
+
stateAwareGuidance: true
|
|
28
|
+
},
|
|
23
29
|
contracts: {
|
|
24
30
|
DOTNS_PROTOCOL_REGISTRY: "0x984F17a9077808F4B7e127F76806A1D59546B5B6",
|
|
25
31
|
DOTNS_REGISTRAR: "0x061273AeF34e8ab9Ca08E199d7440E2639Fc2088",
|
|
@@ -453,6 +459,34 @@ function isValidDoc(value) {
|
|
|
453
459
|
const v = value;
|
|
454
460
|
return Array.isArray(v.environments) && Array.isArray(v.chains);
|
|
455
461
|
}
|
|
462
|
+
function deepMergeEnvironments(base, override) {
|
|
463
|
+
const mergedEnvironments = mergeById(base.environments, override.environments ?? []);
|
|
464
|
+
const mergedChains = mergeById(base.chains, override.chains ?? []);
|
|
465
|
+
return { environments: mergedEnvironments, chains: mergedChains };
|
|
466
|
+
}
|
|
467
|
+
function mergeById(base, overrides) {
|
|
468
|
+
const result = base.map((b) => {
|
|
469
|
+
const ov = overrides.find((o) => o.id === b.id);
|
|
470
|
+
return ov ? mergeObjects(b, ov) : b;
|
|
471
|
+
});
|
|
472
|
+
for (const ov of overrides) {
|
|
473
|
+
if (!base.find((b) => b.id === ov.id)) result.push(ov);
|
|
474
|
+
}
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
function mergeObjects(base, override) {
|
|
478
|
+
const result = { ...base };
|
|
479
|
+
for (const key of Object.keys(override)) {
|
|
480
|
+
const ov = override[key];
|
|
481
|
+
const bs = base[key];
|
|
482
|
+
if (ov !== null && typeof ov === "object" && !Array.isArray(ov) && bs !== null && typeof bs === "object" && !Array.isArray(bs)) {
|
|
483
|
+
result[key] = { ...bs, ...ov };
|
|
484
|
+
} else {
|
|
485
|
+
result[key] = ov;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return result;
|
|
489
|
+
}
|
|
456
490
|
async function readBundled(bundledPath, capture) {
|
|
457
491
|
try {
|
|
458
492
|
const raw = await fs.readFile(bundledPath, "utf8");
|
|
@@ -468,6 +502,42 @@ async function loadEnvironments(opts = {}) {
|
|
|
468
502
|
const warn = opts.warn ?? ((msg) => console.error(msg));
|
|
469
503
|
const capture = opts.capture ?? (() => {
|
|
470
504
|
});
|
|
505
|
+
const userFilePath = opts.userFilePath ?? process.env.BULLETIN_DEPLOY_ENV_FILE;
|
|
506
|
+
if (userFilePath) {
|
|
507
|
+
let raw;
|
|
508
|
+
try {
|
|
509
|
+
raw = await fs.readFile(userFilePath, "utf8");
|
|
510
|
+
} catch (err) {
|
|
511
|
+
throw new NonRetryableError(
|
|
512
|
+
`--environment-file: cannot read "${userFilePath}": ${err?.message ?? err}`
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
let userDoc;
|
|
516
|
+
try {
|
|
517
|
+
userDoc = JSON.parse(raw);
|
|
518
|
+
} catch (err) {
|
|
519
|
+
throw new NonRetryableError(
|
|
520
|
+
`--environment-file: "${userFilePath}" is not valid JSON: ${err?.message ?? err}`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
const base = await readBundled(bundledPath, capture);
|
|
524
|
+
const baseDoc = base ?? (isValidDoc(environments_default) ? environments_default : HARDCODED_FALLBACK);
|
|
525
|
+
const partial = userDoc && typeof userDoc === "object" && !Array.isArray(userDoc) ? userDoc : {};
|
|
526
|
+
const merged = deepMergeEnvironments(baseDoc, partial);
|
|
527
|
+
warn(
|
|
528
|
+
`bulletin-deploy: Using user-supplied environment file "${userFilePath}" \u2014 values are NOT validated against chain; you own correctness.`
|
|
529
|
+
);
|
|
530
|
+
for (const env of merged.environments) {
|
|
531
|
+
if (env.contracts && Object.keys(env.contracts).length > 0) {
|
|
532
|
+
try {
|
|
533
|
+
validateContractAddresses(env.contracts, env.id);
|
|
534
|
+
} catch (err) {
|
|
535
|
+
warn(`bulletin-deploy: Warning: ${err?.message ?? err}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return { doc: merged, source: "file" };
|
|
540
|
+
}
|
|
471
541
|
const bundled = await readBundled(bundledPath, capture);
|
|
472
542
|
if (bundled) return { doc: bundled, source: "bundled" };
|
|
473
543
|
if (isValidDoc(environments_default)) {
|
|
@@ -584,6 +654,7 @@ export {
|
|
|
584
654
|
defaultBundledPath,
|
|
585
655
|
isValidContractAddress,
|
|
586
656
|
validateContractAddresses,
|
|
657
|
+
deepMergeEnvironments,
|
|
587
658
|
loadEnvironments,
|
|
588
659
|
resolveEndpoints,
|
|
589
660
|
getPopSelfServeConfig,
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
classifyErrorArea,
|
|
3
3
|
isInteractive,
|
|
4
4
|
promptYesNo
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EJWZGSHD.js";
|
|
6
6
|
import {
|
|
7
7
|
VERSION,
|
|
8
8
|
getCurrentSentryTraceId
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RIAMPAL2.js";
|
|
10
10
|
|
|
11
11
|
// src/bug-report.ts
|
|
12
12
|
import { execSync, execFileSync } from "child_process";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
extractManifestFromCar,
|
|
12
12
|
fetchPreviousManifest,
|
|
13
13
|
writePersistentLocalManifest
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-J3NIXHZZ.js";
|
|
15
15
|
import {
|
|
16
16
|
MANIFEST_PATH,
|
|
17
17
|
MANIFEST_VERSION,
|
|
@@ -20,10 +20,10 @@ import {
|
|
|
20
20
|
} from "./chunk-S7EM5VMW.js";
|
|
21
21
|
import {
|
|
22
22
|
setDeployContext
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-H64ZLWW2.js";
|
|
24
24
|
import {
|
|
25
25
|
probeChunks
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-WSBDIHFZ.js";
|
|
27
27
|
import {
|
|
28
28
|
packSection
|
|
29
29
|
} from "./chunk-C2TS5MER.js";
|
|
@@ -35,15 +35,15 @@ import {
|
|
|
35
35
|
parseDomainName,
|
|
36
36
|
popStatusName,
|
|
37
37
|
verifyNonceAdvanced
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-2GBSYVK2.js";
|
|
39
39
|
import {
|
|
40
40
|
derivePoolAccounts,
|
|
41
41
|
detectTestnet,
|
|
42
42
|
ensureAuthorized,
|
|
43
43
|
fetchPoolAuthorizations,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
} from "./chunk-
|
|
44
|
+
isAuthorizationSufficient,
|
|
45
|
+
selectAccount
|
|
46
|
+
} from "./chunk-JQ5X3VMP.js";
|
|
47
47
|
import {
|
|
48
48
|
VERSION,
|
|
49
49
|
captureWarning,
|
|
@@ -57,13 +57,13 @@ import {
|
|
|
57
57
|
truncateAddress,
|
|
58
58
|
withDeploySpan,
|
|
59
59
|
withSpan
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-RIAMPAL2.js";
|
|
61
61
|
import {
|
|
62
62
|
DEFAULT_ENV_ID,
|
|
63
63
|
getPopSelfServeConfig,
|
|
64
64
|
loadEnvironments,
|
|
65
65
|
resolveEndpoints
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-5K3RI5C2.js";
|
|
67
67
|
import {
|
|
68
68
|
NonRetryableError
|
|
69
69
|
} from "./chunk-ZOC4GITL.js";
|
|
@@ -449,7 +449,7 @@ var __assignDenseNoncesForTest = assignDenseNonces;
|
|
|
449
449
|
async function storeChunkedContent(chunks, { client: existingClient, unsafeApi: existingApi, signer: existingSigner, ss58: existingSS58, reconnect, fetchNonce: fetchNonceOverride, skipCids, probeFailedCids, gateway: providerGateway, trustedCids, skipRootStore } = {}) {
|
|
450
450
|
const _fetchNonce = fetchNonceOverride ?? fetchNonce;
|
|
451
451
|
console.log(`
|
|
452
|
-
|
|
452
|
+
Data chunks: ${chunks.length}`);
|
|
453
453
|
const totalBytes = chunks.reduce((s, c) => s + c.length, 0);
|
|
454
454
|
console.log(` Total: ${(totalBytes / 1024).toFixed(2)} KB`);
|
|
455
455
|
let client, unsafeApi, signer, ss58;
|
|
@@ -490,8 +490,6 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
|
-
const requiredTxs = BigInt(chunks.length + 1);
|
|
494
|
-
const requiredBytes = BigInt(totalBytes);
|
|
495
493
|
const readUploadAuthorization = () => Promise.all([
|
|
496
494
|
unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
|
|
497
495
|
unsafeApi.query.System.Number.getValue()
|
|
@@ -508,21 +506,9 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
508
506
|
throw e;
|
|
509
507
|
}
|
|
510
508
|
}
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (!isAuthorized || txsRemaining < requiredTxs || bytesRemaining < requiredBytes) {
|
|
515
|
-
const txsRemainingDisplay = txsRemaining < 0n ? 0n : txsRemaining;
|
|
516
|
-
const bytesRemainingDisplay = bytesRemaining < 0n ? 0n : bytesRemaining;
|
|
517
|
-
console.log(`
|
|
518
|
-
Account needs re-authorization (authorized=${isAuthorized}, need ${requiredTxs} txs / ${(totalBytes / 1e6).toFixed(1)}MB, have ${txsRemainingDisplay} txs / ${(Number(bytesRemainingDisplay) / 1e6).toFixed(2)}MB)`);
|
|
519
|
-
console.log(` Attempting to re-authorize with Alice...`);
|
|
520
|
-
try {
|
|
521
|
-
await ensureAuthorized(unsafeApi, ss58, void 0, { txs: requiredTxs, bytes: requiredBytes });
|
|
522
|
-
console.log(` Re-authorization successful`);
|
|
523
|
-
} catch (e) {
|
|
524
|
-
throw new NonRetryableError(`Account ${ss58} has insufficient Bulletin authorization and auto-authorization via Alice failed (${e.message}). Authorize the account on-chain.`);
|
|
525
|
-
}
|
|
509
|
+
const sufficient = await isAuthorizationSufficient(unsafeApi, ss58, uploadAuth, currentBlockNum);
|
|
510
|
+
if (!sufficient) {
|
|
511
|
+
throw new NonRetryableError(`Account ${ss58} has no active Bulletin authorization (missing or expired). Request authorization on-chain (testnet faucet / personhood / pool bootstrap), then retry.`);
|
|
526
512
|
}
|
|
527
513
|
let reconnectionsUsed = 0;
|
|
528
514
|
const recoveryHistory = [];
|
|
@@ -583,7 +569,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
583
569
|
const MAX_CHUNK_RETRIES = 3;
|
|
584
570
|
const MAX_REPROBE_RETRIES = 3;
|
|
585
571
|
console.log(`
|
|
586
|
-
Submitting ${chunks.length} chunks in batches of up to ${BATCH_SIZE_INITIAL}...`);
|
|
572
|
+
Submitting ${chunks.length} data chunks in batches of up to ${BATCH_SIZE_INITIAL}...`);
|
|
587
573
|
const stored = new Array(chunks.length).fill(null);
|
|
588
574
|
let tier2Verified = 0;
|
|
589
575
|
let tier2Inconclusive = 0;
|
|
@@ -875,7 +861,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
875
861
|
if (uploadReceipt) {
|
|
876
862
|
setDeployAttribute("bulletin.upload.tx_hash", uploadReceipt.txHash);
|
|
877
863
|
setDeployAttribute("bulletin.upload.block_hash", uploadReceipt.blockHash);
|
|
878
|
-
setDeployAttribute("bulletin.upload.block_number", uploadReceipt.blockNumber);
|
|
864
|
+
setDeployAttribute("bulletin.upload.block_number", String(uploadReceipt.blockNumber));
|
|
879
865
|
console.log(` Storage upload finalised @ block ${uploadReceipt.blockNumber} (tx ${uploadReceipt.txHash})`);
|
|
880
866
|
}
|
|
881
867
|
if (wsHaltDetected && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
|
|
@@ -1109,7 +1095,8 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
|
|
|
1109
1095
|
sampleMemory("storage_start");
|
|
1110
1096
|
const fetched = await fetchPreviousManifest(prevContenthash, {
|
|
1111
1097
|
gateway,
|
|
1112
|
-
domain: opts.domain
|
|
1098
|
+
domain: opts.domain,
|
|
1099
|
+
chainClient: opts.provider?.client
|
|
1113
1100
|
});
|
|
1114
1101
|
const prevManifest = fetched.source === "embedded" ? fetched.manifest : null;
|
|
1115
1102
|
console.log(` Manifest fetch: ${fetched.source}${fetched.source !== "none" ? ` (${fetched.attempts} attempt${fetched.attempts === 1 ? "" : "s"})` : ""}`);
|
|
@@ -1577,6 +1564,45 @@ async function unpublish(domainName, options = {}) {
|
|
|
1577
1564
|
}
|
|
1578
1565
|
}
|
|
1579
1566
|
}
|
|
1567
|
+
function browserUrlFor(name, envId) {
|
|
1568
|
+
const base = `https://${name}.dot.li`;
|
|
1569
|
+
return envId === "preview" ? `${base}?network=previewnet` : base;
|
|
1570
|
+
}
|
|
1571
|
+
function interpretBitswapResult(outcome) {
|
|
1572
|
+
if (outcome.ok) {
|
|
1573
|
+
return { retrievable: true, errorVariant: "none" };
|
|
1574
|
+
}
|
|
1575
|
+
const err = outcome.error;
|
|
1576
|
+
if (err != null && typeof err === "object" && "code" in err && err.code === -32810) {
|
|
1577
|
+
return { retrievable: false, errorVariant: "not_found" };
|
|
1578
|
+
}
|
|
1579
|
+
if (err instanceof Error && err.message === "p2p_probe_timeout") {
|
|
1580
|
+
return { retrievable: false, errorVariant: "timeout" };
|
|
1581
|
+
}
|
|
1582
|
+
return { retrievable: false, errorVariant: "error" };
|
|
1583
|
+
}
|
|
1584
|
+
async function probeP2pRetrieval(client, cid, timeoutMs = 3e3) {
|
|
1585
|
+
const t0 = Date.now();
|
|
1586
|
+
let outcome;
|
|
1587
|
+
try {
|
|
1588
|
+
const timeoutError = new Error("p2p_probe_timeout");
|
|
1589
|
+
const response = await Promise.race([
|
|
1590
|
+
client._request("bitswap_v1_get", [cid]),
|
|
1591
|
+
new Promise((_, reject) => {
|
|
1592
|
+
const t = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
1593
|
+
if (typeof t === "object" && t !== null && typeof t.unref === "function") {
|
|
1594
|
+
t.unref();
|
|
1595
|
+
}
|
|
1596
|
+
})
|
|
1597
|
+
]);
|
|
1598
|
+
outcome = { ok: true, response };
|
|
1599
|
+
} catch (err) {
|
|
1600
|
+
outcome = { ok: false, error: err };
|
|
1601
|
+
}
|
|
1602
|
+
const durationMs = Date.now() - t0;
|
|
1603
|
+
const { retrievable, errorVariant } = interpretBitswapResult(outcome);
|
|
1604
|
+
return { retrievable, errorVariant, durationMs };
|
|
1605
|
+
}
|
|
1580
1606
|
async function deploy(content, domainName = null, options = {}) {
|
|
1581
1607
|
if (options.signer && options.signerAddress && options.mnemonic) {
|
|
1582
1608
|
throw new NonRetryableError("Pass either a mnemonic or an external signer, not both \u2014 they identify the signing account and only one can win.");
|
|
@@ -1708,17 +1734,8 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
1708
1734
|
}
|
|
1709
1735
|
provider = await reconnect();
|
|
1710
1736
|
const providerWithReconnect = { ...provider, reconnect };
|
|
1711
|
-
const
|
|
1712
|
-
detectTestnet(provider.unsafeApi),
|
|
1713
|
-
estimateUploadBytes(content)
|
|
1714
|
-
]);
|
|
1737
|
+
const isTestnet = await detectTestnet(provider.unsafeApi);
|
|
1715
1738
|
setDeployAttribute("deploy.is_testnet", isTestnet ? "true" : "false");
|
|
1716
|
-
if (isTestnet && estimated != null) {
|
|
1717
|
-
const uploadBytes = Math.ceil(estimated * 1.2);
|
|
1718
|
-
const chunksNeeded = Math.max(1, Math.ceil(uploadBytes / CHUNK_SIZE));
|
|
1719
|
-
const needs = { txs: BigInt(chunksNeeded + 2), bytes: BigInt(uploadBytes) };
|
|
1720
|
-
await topUpBy(provider.unsafeApi, provider.ss58, needs, "uploader");
|
|
1721
|
-
}
|
|
1722
1739
|
console.log("\n" + "=".repeat(60));
|
|
1723
1740
|
console.log("Storage");
|
|
1724
1741
|
console.log("=".repeat(60));
|
|
@@ -1928,6 +1945,17 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
1928
1945
|
}
|
|
1929
1946
|
dotns.disconnect();
|
|
1930
1947
|
});
|
|
1948
|
+
await withSpan("deploy.p2p-check", "3. p2p-check", { "deploy.domain": name }, async () => {
|
|
1949
|
+
const probe = await probeP2pRetrieval(provider.client, cid);
|
|
1950
|
+
setDeployAttribute("deploy.p2p.retrievable", probe.retrievable ? "true" : "false");
|
|
1951
|
+
setDeployAttribute("deploy.p2p.check_ms", String(probe.durationMs));
|
|
1952
|
+
setDeployAttribute("deploy.p2p.error_variant", probe.errorVariant);
|
|
1953
|
+
if (probe.retrievable) {
|
|
1954
|
+
console.log(` P2P retrieval: \u2713 (${probe.durationMs}ms)`);
|
|
1955
|
+
} else {
|
|
1956
|
+
console.log(` P2P retrieval: \u26A0 not yet retrievable (${probe.errorVariant}, ${probe.durationMs}ms)`);
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1931
1959
|
if (options.ghPagesMirror) {
|
|
1932
1960
|
console.log("\n" + "=".repeat(60));
|
|
1933
1961
|
console.log("Final checks");
|
|
@@ -1967,7 +1995,7 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
1967
1995
|
console.log("=".repeat(60));
|
|
1968
1996
|
console.log("\n\u{1F53A} Polkadot Triangle");
|
|
1969
1997
|
console.log(` - Polkadot Desktop: ${name}.dot`);
|
|
1970
|
-
console.log(` - Polkadot Browser:
|
|
1998
|
+
console.log(` - Polkadot Browser: ${browserUrlFor(name, envId)}`);
|
|
1971
1999
|
console.log("\n" + "=".repeat(60) + "\n");
|
|
1972
2000
|
return { domainName: name, fullDomain: `${name}.dot`, cid, ipfsCid };
|
|
1973
2001
|
} finally {
|
|
@@ -2289,7 +2317,7 @@ async function buildOrderedCar(options) {
|
|
|
2289
2317
|
const s1Bytes = section1Chunks.reduce((s, b) => s + b.length, 0);
|
|
2290
2318
|
const s2Bytes = section2Chunks.reduce((s, b) => s + b.length, 0);
|
|
2291
2319
|
console.log(
|
|
2292
|
-
` CAR (3-section): ${(carBytes.length / 1024 / 1024).toFixed(2)} MB (s0=${section0Bytes.length}B s1=${s1Bytes}B s2=${s2Bytes}B), ${allChunks.length}
|
|
2320
|
+
` CAR (3-section): ${(carBytes.length / 1024 / 1024).toFixed(2)} MB (s0=${section0Bytes.length}B s1=${s1Bytes}B s2=${s2Bytes}B), ${allChunks.length} frames (${section0Chunks.length} header + ${section1Chunks.length} data + ${section2Chunks.length} manifest)`
|
|
2293
2321
|
);
|
|
2294
2322
|
return {
|
|
2295
2323
|
carBytes,
|
|
@@ -2407,5 +2435,8 @@ export {
|
|
|
2407
2435
|
estimateUploadBytes,
|
|
2408
2436
|
assertSubdomainOwnerMatchesSigner,
|
|
2409
2437
|
unpublish,
|
|
2438
|
+
browserUrlFor,
|
|
2439
|
+
interpretBitswapResult,
|
|
2440
|
+
probeP2pRetrieval,
|
|
2410
2441
|
deploy
|
|
2411
2442
|
};
|
|
@@ -192,10 +192,115 @@ async function fetchAcrossTiers(url, budget, start) {
|
|
|
192
192
|
}
|
|
193
193
|
return { outcome: "retryable", reason: `tiers exhausted: ${lastReason}`, attempts, bytesDownloaded };
|
|
194
194
|
}
|
|
195
|
+
var CHAIN_TIER_TIMEOUT_MS = 5e3;
|
|
196
|
+
function normalizeBitswapBytes(response) {
|
|
197
|
+
if (response instanceof Uint8Array) return response;
|
|
198
|
+
if (typeof response === "string") {
|
|
199
|
+
const hex = response.startsWith("0x") ? response.slice(2) : response;
|
|
200
|
+
if (hex.length % 2 !== 0) throw new Error(`bitswap_v1_get: odd-length hex response (${hex.length} chars)`);
|
|
201
|
+
const out = new Uint8Array(hex.length / 2);
|
|
202
|
+
for (let i = 0; i < out.length; i++) {
|
|
203
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
if (Array.isArray(response) && response.every((x) => typeof x === "number")) {
|
|
208
|
+
return new Uint8Array(response);
|
|
209
|
+
}
|
|
210
|
+
if (response instanceof ArrayBuffer) return new Uint8Array(response);
|
|
211
|
+
throw new Error(`bitswap_v1_get: unexpected response type ${typeof response}`);
|
|
212
|
+
}
|
|
213
|
+
async function chainGet(client, cid, timeoutMs) {
|
|
214
|
+
const timeoutError = new Error(`bitswap_v1_get timeout after ${timeoutMs}ms`);
|
|
215
|
+
const response = await Promise.race([
|
|
216
|
+
client._request("bitswap_v1_get", [cid]),
|
|
217
|
+
new Promise((_, reject) => {
|
|
218
|
+
const t = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
219
|
+
if (typeof t === "object" && t !== null && typeof t.unref === "function") {
|
|
220
|
+
t.unref();
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
]);
|
|
224
|
+
return normalizeBitswapBytes(response);
|
|
225
|
+
}
|
|
226
|
+
async function fetchManifestFromChain(client, rootCid, timeoutMs) {
|
|
227
|
+
let rootBytes;
|
|
228
|
+
try {
|
|
229
|
+
rootBytes = await chainGet(client, rootCid, timeoutMs);
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
let chunkCids;
|
|
234
|
+
try {
|
|
235
|
+
const rootNode = dagPB.decode(rootBytes);
|
|
236
|
+
chunkCids = (rootNode.Links ?? []).map((l) => l.Hash.toString());
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
if (chunkCids.length === 0) return null;
|
|
241
|
+
let chunk0;
|
|
242
|
+
try {
|
|
243
|
+
chunk0 = await chainGet(client, chunkCids[0], timeoutMs);
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
let manifestBytes;
|
|
248
|
+
try {
|
|
249
|
+
manifestBytes = await extractManifestFromCar(chunk0);
|
|
250
|
+
if (manifestBytes) return manifestBytes;
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
if (chunkCids.length < 2) return null;
|
|
254
|
+
let chunk1;
|
|
255
|
+
try {
|
|
256
|
+
chunk1 = await chainGet(client, chunkCids[1], timeoutMs);
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const combined = new Uint8Array(chunk0.length + chunk1.length);
|
|
261
|
+
combined.set(chunk0, 0);
|
|
262
|
+
combined.set(chunk1, chunk0.length);
|
|
263
|
+
try {
|
|
264
|
+
manifestBytes = await extractManifestFromCar(combined);
|
|
265
|
+
return manifestBytes ?? null;
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
195
270
|
async function fetchPreviousManifest(prevContenthash, options = {}) {
|
|
196
271
|
if (prevContenthash === null) return { source: "none" };
|
|
197
272
|
const local = readPersistentLocalManifest(options.domain, prevContenthash);
|
|
198
273
|
if (local) return local;
|
|
274
|
+
if (options.chainClient) {
|
|
275
|
+
const chainTimeoutMs = CHAIN_TIER_TIMEOUT_MS;
|
|
276
|
+
const chainManifestBytes = await Sentry.startSpan(
|
|
277
|
+
{ op: "manifest.fetch.chain", name: `manifest chain ${prevContenthash.slice(0, 12)}` },
|
|
278
|
+
async (span) => {
|
|
279
|
+
span.setAttribute("manifest.chain.cid", prevContenthash.slice(0, 12));
|
|
280
|
+
try {
|
|
281
|
+
const bytes = await fetchManifestFromChain(options.chainClient, prevContenthash, chainTimeoutMs);
|
|
282
|
+
span.setAttribute("manifest.chain.outcome", bytes ? "success" : "miss");
|
|
283
|
+
return bytes;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
span.setAttribute("manifest.chain.outcome", "error");
|
|
286
|
+
span.setAttribute("manifest.chain.error", e?.message ?? String(e));
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
if (chainManifestBytes) {
|
|
292
|
+
const text = new TextDecoder().decode(chainManifestBytes);
|
|
293
|
+
const parsed = parseManifest(text);
|
|
294
|
+
if (parsed.ok) {
|
|
295
|
+
return {
|
|
296
|
+
source: "embedded",
|
|
297
|
+
manifest: parsed.manifest,
|
|
298
|
+
attempts: 1,
|
|
299
|
+
bytesDownloaded: chainManifestBytes.length
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
199
304
|
const gatewayList = (options.gateways ?? (options.gateway ? [options.gateway] : [])).map((g) => g.replace(/\/$/, ""));
|
|
200
305
|
const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
201
306
|
const start = Date.now();
|
|
@@ -302,6 +407,9 @@ export {
|
|
|
302
407
|
getCacheDir,
|
|
303
408
|
readPersistentLocalManifest,
|
|
304
409
|
writePersistentLocalManifest,
|
|
410
|
+
CHAIN_TIER_TIMEOUT_MS,
|
|
411
|
+
normalizeBitswapBytes,
|
|
412
|
+
fetchManifestFromChain,
|
|
305
413
|
fetchPreviousManifest,
|
|
306
414
|
extractManifestFromCar,
|
|
307
415
|
walkDagToManifest
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
setDeployAttribute
|
|
3
|
-
} from "./chunk-INVA3XGG.js";
|
|
4
|
-
|
|
5
1
|
// src/pool.ts
|
|
6
2
|
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
|
|
7
3
|
import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy } from "@polkadot-labs/hdkd-helpers";
|
|
@@ -33,14 +29,15 @@ function derivePoolAccounts(poolSize = 10, mnemonic = DEV_PHRASE) {
|
|
|
33
29
|
}
|
|
34
30
|
return accounts;
|
|
35
31
|
}
|
|
36
|
-
function isAuthorizationSufficient(auth, currentBlock, needs) {
|
|
32
|
+
async function isAuthorizationSufficient(api, address, auth, currentBlock, needs) {
|
|
37
33
|
if (auth === void 0) return false;
|
|
38
34
|
if (Number(auth.expiration ?? 0) <= currentBlock) return false;
|
|
39
35
|
if (needs) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
const ok = await api.apis.BulletinTransactionStorageApi.can_store(
|
|
37
|
+
address,
|
|
38
|
+
clampU32(needs.bytes, "needs.bytes")
|
|
39
|
+
);
|
|
40
|
+
if (!ok) return false;
|
|
44
41
|
}
|
|
45
42
|
return true;
|
|
46
43
|
}
|
|
@@ -67,46 +64,6 @@ async function fetchPoolAuthorizations(api, accounts) {
|
|
|
67
64
|
);
|
|
68
65
|
return results;
|
|
69
66
|
}
|
|
70
|
-
function computeTopUpTarget(current, needs) {
|
|
71
|
-
const minTxs = needs.txs + BigInt(TOPUP_TRANSACTIONS);
|
|
72
|
-
const minBytes = needs.bytes + TOPUP_BYTES;
|
|
73
|
-
if (current.transactions >= minTxs && current.bytes >= minBytes) return null;
|
|
74
|
-
return {
|
|
75
|
-
transactions: current.transactions > minTxs ? current.transactions : minTxs,
|
|
76
|
-
bytes: current.bytes > minBytes ? current.bytes : minBytes
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function classifyAliceTxError(err) {
|
|
80
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
81
|
-
const lower = msg.toLowerCase();
|
|
82
|
-
if (/\bstale\b/.test(lower)) return "retry";
|
|
83
|
-
if (/"type"\s*:\s*"future"|\binvalid::future\b/.test(lower)) return "retry";
|
|
84
|
-
if (lower.includes("websocket") || lower.includes("connection") || lower.includes("socket closed") || lower.includes("disconnect")) return "retry";
|
|
85
|
-
if (lower.includes("timed out") || lower.includes("timeout")) return "retry";
|
|
86
|
-
return "abort";
|
|
87
|
-
}
|
|
88
|
-
var ALICE_MAX_ATTEMPTS = 3;
|
|
89
|
-
async function submitAliceTxWithRetry(buildTx, aliceSigner, label) {
|
|
90
|
-
let lastError;
|
|
91
|
-
let attempts = 0;
|
|
92
|
-
for (let attempt = 1; attempt <= ALICE_MAX_ATTEMPTS; attempt++) {
|
|
93
|
-
attempts = attempt;
|
|
94
|
-
try {
|
|
95
|
-
const tx = buildTx();
|
|
96
|
-
const result = await tx.signAndSubmit(aliceSigner);
|
|
97
|
-
if (!result.ok) throw new Error(`${label} dispatch error`);
|
|
98
|
-
return;
|
|
99
|
-
} catch (e) {
|
|
100
|
-
lastError = e;
|
|
101
|
-
const decision = classifyAliceTxError(e);
|
|
102
|
-
if (decision === "abort" || attempt === ALICE_MAX_ATTEMPTS) break;
|
|
103
|
-
console.log(` ${label}: attempt ${attempt}/${ALICE_MAX_ATTEMPTS} failed (${String(e.message ?? e).slice(0, 80)}), retrying...`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
107
|
-
const plural = attempts === 1 ? "attempt" : "attempts";
|
|
108
|
-
throw new Error(`${label} failed after ${attempts} ${plural}: ${msg}`);
|
|
109
|
-
}
|
|
110
67
|
function isTestnetSpecName(specName) {
|
|
111
68
|
if (!specName) return false;
|
|
112
69
|
const s = specName.toLowerCase();
|
|
@@ -132,69 +89,28 @@ async function detectTestnet(api) {
|
|
|
132
89
|
function _resetTestnetCacheForTests() {
|
|
133
90
|
_testnetDetectionCache = null;
|
|
134
91
|
}
|
|
135
|
-
function aliceKeyring() {
|
|
136
|
-
const keyring = new Keyring({ type: "sr25519" });
|
|
137
|
-
const alice = keyring.addFromUri("//Alice");
|
|
138
|
-
const signer = getPolkadotSigner(alice.publicKey, "Sr25519", (data) => alice.sign(data));
|
|
139
|
-
return { alice, signer };
|
|
140
|
-
}
|
|
141
92
|
var U32_MAX = 0xFFFFFFFFn;
|
|
142
93
|
function clampU32(n, name) {
|
|
143
94
|
if (n < 0n) throw new Error(`${name} must be non-negative`);
|
|
144
95
|
if (n > U32_MAX) throw new Error(`${name} (${n}) exceeds u32 max \u2014 split the deploy into smaller batches`);
|
|
145
96
|
return Number(n);
|
|
146
97
|
}
|
|
147
|
-
function markBulletinAuthGranted() {
|
|
148
|
-
setDeployAttribute("deploy.unblock.bulletin_auth.fired", "true");
|
|
149
|
-
setDeployAttribute("deploy.unblock.bulletin_auth.granted_txs", String(TOPUP_TRANSACTIONS));
|
|
150
|
-
setDeployAttribute("deploy.unblock.bulletin_auth.granted_bytes", String(TOPUP_BYTES));
|
|
151
|
-
}
|
|
152
98
|
async function ensureAuthorized(api, address, label, needs) {
|
|
153
99
|
const [auth, currentBlock] = await Promise.all([
|
|
154
100
|
api.query.TransactionStorage.Authorizations.getValue(Enum("Account", address)),
|
|
155
101
|
api.query.System.Number.getValue()
|
|
156
102
|
]);
|
|
157
|
-
if (isAuthorizationSufficient(auth, currentBlock, needs)) return;
|
|
158
|
-
|
|
159
|
-
const {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
who
|
|
163
|
-
|
|
164
|
-
bytes: TOPUP_BYTES
|
|
165
|
-
}),
|
|
166
|
-
signer,
|
|
167
|
-
`authorize_account(${label ?? "account"})`
|
|
168
|
-
);
|
|
169
|
-
console.log(` Authorized: ${TOPUP_TRANSACTIONS} txs / ${Number(TOPUP_BYTES) / 1e6}MB`);
|
|
170
|
-
markBulletinAuthGranted();
|
|
171
|
-
}
|
|
172
|
-
async function topUpBy(api, address, needs, label) {
|
|
173
|
-
const [currentAuth, currentBlock] = await Promise.all([
|
|
174
|
-
api.query.TransactionStorage.Authorizations.getValue(Enum("Account", address)),
|
|
175
|
-
api.query.System.Number.getValue()
|
|
176
|
-
]);
|
|
177
|
-
if (isAuthorizationSufficient(currentAuth, currentBlock, needs)) {
|
|
178
|
-
const expiration = Number(currentAuth.expiration);
|
|
179
|
-
const fmtMB = (b) => (Number(b) / 1e6).toFixed(1);
|
|
180
|
-
const txsRemaining = BigInt(currentAuth.extent.transactions_allowance) - BigInt(currentAuth.extent.transactions);
|
|
181
|
-
const bytesRemaining = BigInt(currentAuth.extent.bytes_allowance) - BigInt(currentAuth.extent.bytes);
|
|
182
|
-
console.log(` Pre-auth skipped for ${label ?? "account"} (${address.slice(0, 8)}...): authorized until block ${expiration}, ${txsRemaining} txs / ${fmtMB(bytesRemaining)}MB remaining.`);
|
|
183
|
-
return;
|
|
103
|
+
if (await isAuthorizationSufficient(api, address, auth, currentBlock, needs)) return;
|
|
104
|
+
const isTestnet = await detectTestnet(api);
|
|
105
|
+
const who = `${label ?? "account"} (${address.slice(0, 8)}...)`;
|
|
106
|
+
if (isTestnet) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Bulletin storage account ${who} is not authorized (or its authorization expired). bulletin-deploy no longer self-authorizes on the Bulletin chain \u2014 request authorization for this account from the chain's authorizer (testnet faucet / personhood / pool bootstrap), then retry.`
|
|
109
|
+
);
|
|
184
110
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
await submitAliceTxWithRetry(
|
|
188
|
-
() => api.tx.TransactionStorage.authorize_account({
|
|
189
|
-
who: address,
|
|
190
|
-
transactions: clampU32(BigInt(TOPUP_TRANSACTIONS), "transactions"),
|
|
191
|
-
bytes: TOPUP_BYTES
|
|
192
|
-
}),
|
|
193
|
-
signer,
|
|
194
|
-
`topUpBy(${label ?? "account"})`
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Bulletin storage account ${who} is not authorized to store. On production the storage account must already carry its own authorization/allowance \u2014 bulletin-deploy cannot grant it.`
|
|
195
113
|
);
|
|
196
|
-
console.log(` Pre-authorized: ${TOPUP_TRANSACTIONS} txs / ${Number(TOPUP_BYTES) / 1e6}MB`);
|
|
197
|
-
markBulletinAuthGranted();
|
|
198
114
|
}
|
|
199
115
|
async function bootstrapPool(bulletinRpc, poolSize = 10, mnemonic) {
|
|
200
116
|
console.log(`Bootstrapping ${poolSize} pool accounts on ${bulletinRpc}...
|
|
@@ -259,12 +175,9 @@ export {
|
|
|
259
175
|
isAuthorizationSufficient,
|
|
260
176
|
selectAccount,
|
|
261
177
|
fetchPoolAuthorizations,
|
|
262
|
-
computeTopUpTarget,
|
|
263
|
-
classifyAliceTxError,
|
|
264
178
|
isTestnetSpecName,
|
|
265
179
|
detectTestnet,
|
|
266
180
|
_resetTestnetCacheForTests,
|
|
267
181
|
ensureAuthorized,
|
|
268
|
-
topUpBy,
|
|
269
182
|
bootstrapPool
|
|
270
183
|
};
|