@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.
Files changed (39) hide show
  1. package/README.md +2 -230
  2. package/assets/environments.json +6 -0
  3. package/bin/bulletin-deploy +15 -1
  4. package/dist/bug-report.js +4 -4
  5. package/dist/{chunk-IRT4A7OO.js → chunk-2GBSYVK2.js} +139 -31
  6. package/dist/{chunk-CNPB4VAM.js → chunk-5K3RI5C2.js} +71 -0
  7. package/dist/{chunk-O2BFXY3Y.js → chunk-EJWZGSHD.js} +1 -1
  8. package/dist/{chunk-MXMVZU2Q.js → chunk-H64ZLWW2.js} +2 -2
  9. package/dist/{chunk-WPJADKC7.js → chunk-HPPLVGGC.js} +73 -42
  10. package/dist/{chunk-L2SKSKB6.js → chunk-J3NIXHZZ.js} +108 -0
  11. package/dist/{chunk-RW3GWDGI.js → chunk-JQ5X3VMP.js} +15 -102
  12. package/dist/{chunk-V5N5EYNV.js → chunk-NJPBXF5Z.js} +6 -3
  13. package/dist/{chunk-INVA3XGG.js → chunk-RIAMPAL2.js} +88 -77
  14. package/dist/{chunk-R2CJ5I5R.js → chunk-TQC3S6NP.js} +3 -3
  15. package/dist/{chunk-KB3EII7F.js → chunk-WSBDIHFZ.js} +1 -1
  16. package/dist/chunk-probe.js +3 -3
  17. package/dist/deploy.d.ts +38 -1
  18. package/dist/deploy.js +16 -10
  19. package/dist/dotns.d.ts +33 -2
  20. package/dist/dotns.js +9 -5
  21. package/dist/environments.d.ts +17 -4
  22. package/dist/environments.js +3 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +13 -11
  25. package/dist/manifest/publish.js +11 -11
  26. package/dist/manifest-fetch.d.ts +22 -1
  27. package/dist/manifest-fetch.js +7 -1
  28. package/dist/manifest-roundtrip.js +1 -1
  29. package/dist/memory-report.js +2 -2
  30. package/dist/merkle.js +10 -10
  31. package/dist/personhood/bootstrap.js +5 -5
  32. package/dist/personhood/people-client.js +5 -5
  33. package/dist/pool.d.ts +2 -12
  34. package/dist/pool.js +3 -11
  35. package/dist/run-state.js +1 -1
  36. package/dist/telemetry.js +2 -2
  37. package/dist/version-check.js +3 -3
  38. package/docs/bootstrap.md +1 -1
  39. 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,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-INVA3XGG.js";
3
+ } from "./chunk-RIAMPAL2.js";
4
4
 
5
5
  // src/version-check.ts
6
6
  import { execSync, execFileSync } from "child_process";
@@ -2,11 +2,11 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-O2BFXY3Y.js";
5
+ } from "./chunk-EJWZGSHD.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId
9
- } from "./chunk-INVA3XGG.js";
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-L2SKSKB6.js";
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-MXMVZU2Q.js";
23
+ } from "./chunk-H64ZLWW2.js";
24
24
  import {
25
25
  probeChunks
26
- } from "./chunk-KB3EII7F.js";
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-IRT4A7OO.js";
38
+ } from "./chunk-2GBSYVK2.js";
39
39
  import {
40
40
  derivePoolAccounts,
41
41
  detectTestnet,
42
42
  ensureAuthorized,
43
43
  fetchPoolAuthorizations,
44
- selectAccount,
45
- topUpBy
46
- } from "./chunk-RW3GWDGI.js";
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-INVA3XGG.js";
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-CNPB4VAM.js";
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
- Chunks: ${chunks.length}`);
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 txsRemaining = uploadAuth ? BigInt(uploadAuth.extent.transactions_allowance) - BigInt(uploadAuth.extent.transactions) : 0n;
512
- const bytesRemaining = uploadAuth ? BigInt(uploadAuth.extent.bytes_allowance) - BigInt(uploadAuth.extent.bytes) : 0n;
513
- const isAuthorized = uploadAuth !== void 0 && Number(uploadAuth.expiration ?? 0) > currentBlockNum;
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 [isTestnet, estimated] = await Promise.all([
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: https://${name}.dot.li`);
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} chunks (${section0Chunks.length}+${section1Chunks.length}+${section2Chunks.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 txsRemaining = BigInt(auth.extent.transactions_allowance) - BigInt(auth.extent.transactions);
41
- const bytesRemaining = BigInt(auth.extent.bytes_allowance) - BigInt(auth.extent.bytes);
42
- if (txsRemaining < needs.txs) return false;
43
- if (bytesRemaining < needs.bytes) return false;
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
- console.log(` Auto-authorizing ${label ?? "account"} (${address.slice(0, 8)}...)...`);
159
- const { signer } = aliceKeyring();
160
- await submitAliceTxWithRetry(
161
- () => api.tx.TransactionStorage.authorize_account({
162
- who: address,
163
- transactions: clampU32(BigInt(TOPUP_TRANSACTIONS), "transactions"),
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
- const { signer } = aliceKeyring();
186
- console.log(` Pre-authorizing ${label ?? "account"} (${address.slice(0, 8)}...): granting ${TOPUP_TRANSACTIONS} txs / ${Number(TOPUP_BYTES) / 1e6}MB...`);
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
  };