@parity/product-deploy 0.11.0 → 0.12.0-rc.2

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 (44) hide show
  1. package/bin/bulletin-bootstrap +23 -1
  2. package/dist/auth-config.js +3 -3
  3. package/dist/bug-report.js +4 -4
  4. package/dist/{chunk-SES7CGNY.js → chunk-DDC6NKE3.js} +1 -1
  5. package/dist/{chunk-TE2YKRYY.js → chunk-F4O42MUQ.js} +2 -2
  6. package/dist/{chunk-HLXRVFKL.js → chunk-FGCBIUMT.js} +38 -1
  7. package/dist/{chunk-HTZGBUZA.js → chunk-IR5BHZ2S.js} +3 -3
  8. package/dist/{chunk-7RYEYWYO.js → chunk-JGS6QR5X.js} +1 -1
  9. package/dist/{chunk-53BU4X2R.js → chunk-LRJTODL2.js} +12 -2
  10. package/dist/{chunk-UUJFLJKY.js → chunk-N7KXNIH4.js} +158 -61
  11. package/dist/{chunk-JWMHCW74.js → chunk-NRSIBGBP.js} +1 -1
  12. package/dist/{chunk-O4I37F5K.js → chunk-S62H6TWE.js} +55 -12
  13. package/dist/{chunk-7E33R3OQ.js → chunk-UPA5PEQA.js} +1 -1
  14. package/dist/{chunk-QDHLTSEO.js → chunk-WVVRMGKN.js} +2 -2
  15. package/dist/{chunk-MRQPJLPS.js → chunk-XVQICFTB.js} +22 -1
  16. package/dist/chunk-probe.d.ts +43 -1
  17. package/dist/chunk-probe.js +11 -5
  18. package/dist/commands/login.js +11 -11
  19. package/dist/commands/logout.js +4 -4
  20. package/dist/commands/transfer.js +4 -4
  21. package/dist/commands/whoami.js +3 -3
  22. package/dist/deploy-actors.js +6 -6
  23. package/dist/deploy.d.ts +45 -1
  24. package/dist/deploy.js +17 -11
  25. package/dist/dotns.d.ts +33 -1
  26. package/dist/dotns.js +14 -4
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +18 -12
  29. package/dist/manifest/publish.js +12 -12
  30. package/dist/memory-report.js +2 -2
  31. package/dist/merkle.js +11 -11
  32. package/dist/personhood/bootstrap.js +4 -4
  33. package/dist/personhood/people-client.js +4 -4
  34. package/dist/personhood/reprove.js +19 -2
  35. package/dist/pool.d.ts +11 -1
  36. package/dist/pool.js +9 -1
  37. package/dist/run-state.js +1 -1
  38. package/dist/sss-allowance-cache.js +4 -4
  39. package/dist/storage-signer.d.ts +28 -3
  40. package/dist/storage-signer.js +13 -11
  41. package/dist/telemetry.js +2 -2
  42. package/dist/version-check.js +3 -3
  43. package/package.json +1 -1
  44. package/tools/release-retry-wrapper.mjs +6 -0
@@ -4,7 +4,7 @@ import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE } from "../dist/deploy.js";
7
- import { bootstrapPool } from "../dist/pool.js";
7
+ import { bootstrapPool, BULLETIN_BLOCKS_PER_DAY, isAutoReauthorizeAllowed } from "../dist/pool.js";
8
8
  import { VERSION } from "../dist/telemetry.js";
9
9
 
10
10
  const args = process.argv.slice(2);
@@ -16,6 +16,7 @@ for (let i = 0; i < args.length; i++) {
16
16
  else if (args[i] === "--authorizer") { flags.authorizer = args[++i]; }
17
17
  else if (args[i] === "--rpc") { flags.rpc = args[++i]; }
18
18
  else if (args[i] === "--env") { flags.env = args[++i]; }
19
+ else if (args[i] === "--pre-check") { flags.preCheck = true; }
19
20
  else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
20
21
  else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
21
22
  else {
@@ -52,11 +53,22 @@ Options:
52
53
  --rpc wss://... Bulletin RPC endpoint (or set BULLETIN_RPC env var)
53
54
  --env <id> Load environment by id from environments.json
54
55
  --pool-size N Number of pool accounts to check/initialize (default: 10)
56
+ --pre-check E2E pre-flight mode (#1059): renew any account expiring
57
+ within 24h, not just already-expired ones. Requires
58
+ --env, and is hard-gated to environments where
59
+ environments.json has network != "mainnet" and
60
+ bulletinAutoAuthorize: true — refuses to write
61
+ otherwise, even with --authorizer supplied.
55
62
  --version Show version
56
63
  --help Show this help`);
57
64
  process.exit(0);
58
65
  }
59
66
 
67
+ if (flags.preCheck && !flags.env) {
68
+ console.error("Error: --pre-check requires --env <id>");
69
+ process.exit(1);
70
+ }
71
+
60
72
  let rpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
61
73
  const poolSize = flags.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
62
74
  const mnemonic = flags.mnemonic ?? process.env.BULLETIN_POOL_MNEMONIC ?? process.env.MNEMONIC;
@@ -94,6 +106,16 @@ if (flags.env) {
94
106
  }
95
107
 
96
108
  bootstrapOpts.bulletinAuthorizeV2 = envEntry.bulletinAuthorizeV2 ?? false;
109
+
110
+ if (flags.preCheck) {
111
+ // #1059: widen the "needs authorization" threshold to 24h-before-expiry,
112
+ // and hard-gate the actual write to environments cleared for it. This is
113
+ // a config-driven (environments.json) gate, not a CLI flag override —
114
+ // --pre-check can never itself grant write access on an env that isn't
115
+ // eligible.
116
+ bootstrapOpts.reauthBufferBlocks = BULLETIN_BLOCKS_PER_DAY;
117
+ bootstrapOpts.allowAutoReauthorize = isAutoReauthorizeAllowed(envEntry);
118
+ }
97
119
  }
98
120
 
99
121
  await bootstrapPool(rpc, poolSize, mnemonic, bootstrapOpts);
@@ -9,10 +9,10 @@ import {
9
9
  getPeopleChainEndpoints,
10
10
  hasPersistedSession,
11
11
  resolveBulletinEndpoints
12
- } from "./chunk-JWMHCW74.js";
12
+ } from "./chunk-NRSIBGBP.js";
13
13
  import "./chunk-TSPERKUS.js";
14
- import "./chunk-53BU4X2R.js";
15
- import "./chunk-SES7CGNY.js";
14
+ import "./chunk-LRJTODL2.js";
15
+ import "./chunk-DDC6NKE3.js";
16
16
  import "./chunk-JSYQ3JQS.js";
17
17
  import "./chunk-ZOC4GITL.js";
18
18
  export {
@@ -10,10 +10,10 @@ import {
10
10
  offerBugReport,
11
11
  scrubSecrets,
12
12
  setDeployContext
13
- } from "./chunk-HTZGBUZA.js";
14
- import "./chunk-7E33R3OQ.js";
15
- import "./chunk-53BU4X2R.js";
16
- import "./chunk-SES7CGNY.js";
13
+ } from "./chunk-IR5BHZ2S.js";
14
+ import "./chunk-UPA5PEQA.js";
15
+ import "./chunk-LRJTODL2.js";
16
+ import "./chunk-DDC6NKE3.js";
17
17
  export {
18
18
  buildCliFlagsSummary,
19
19
  buildLabels,
@@ -6,7 +6,7 @@ import * as path from "path";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@parity/product-deploy",
9
- version: "0.11.0",
9
+ version: "0.12.0-rc.2",
10
10
  private: false,
11
11
  repository: {
12
12
  type: "git",
@@ -7,10 +7,10 @@ import {
7
7
  resolveDotnsConnectOptions,
8
8
  storeDirectory,
9
9
  storeFile
10
- } from "./chunk-UUJFLJKY.js";
10
+ } from "./chunk-N7KXNIH4.js";
11
11
  import {
12
12
  DotNS
13
- } from "./chunk-O4I37F5K.js";
13
+ } from "./chunk-S62H6TWE.js";
14
14
  import {
15
15
  getPopSelfServeConfig,
16
16
  loadEnvironments,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  captureWarning
3
- } from "./chunk-53BU4X2R.js";
3
+ } from "./chunk-LRJTODL2.js";
4
4
 
5
5
  // src/chunk-probe.ts
6
6
  import { Twox128, Blake2128Concat, decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
@@ -166,6 +166,40 @@ async function probeChunks(cids, options) {
166
166
  }
167
167
  return results;
168
168
  }
169
+ function classifyFinalityGap(missingAtFinalized, bestBlockResults) {
170
+ const presentAtBest = new Set(
171
+ bestBlockResults.filter((r) => r.present === true).map((r) => r.cid)
172
+ );
173
+ const lagging = [];
174
+ const reallyMissing = [];
175
+ for (const cid of missingAtFinalized) {
176
+ if (presentAtBest.has(cid)) lagging.push(cid);
177
+ else reallyMissing.push(cid);
178
+ }
179
+ return { reallyMissing, lagging };
180
+ }
181
+ async function probeFinalityGap(missingAtFinalized, options) {
182
+ if (missingAtFinalized.length === 0) return { reallyMissing: [], lagging: [] };
183
+ let bestBlockResults = await probeChunks(missingAtFinalized, { ...options, atFinalized: false });
184
+ const indeterminate = bestBlockResults.filter((r) => r.present === null).map((r) => r.cid);
185
+ if (indeterminate.length > 0) {
186
+ const retry = await probeChunks(indeterminate, { ...options, atFinalized: false });
187
+ const retryByCid = new Map(retry.map((r) => [r.cid, r]));
188
+ bestBlockResults = bestBlockResults.map((r) => r.present === null ? retryByCid.get(r.cid) ?? r : r);
189
+ }
190
+ return classifyFinalityGap(missingAtFinalized, bestBlockResults);
191
+ }
192
+ async function getBestBlockNumber(client) {
193
+ try {
194
+ const header = await client._request("chain_getHeader", []);
195
+ const hex = header?.number;
196
+ if (typeof hex !== "string") return null;
197
+ const n = parseInt(hex, 16);
198
+ return Number.isFinite(n) ? n : null;
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
169
203
  function _resetProbeSession() {
170
204
  _metadataChecked = false;
171
205
  _crossValidated = false;
@@ -180,6 +214,9 @@ export {
180
214
  ChainProbeCrossValidationError,
181
215
  _decodeStorageValue,
182
216
  probeChunks,
217
+ classifyFinalityGap,
218
+ probeFinalityGap,
219
+ getBestBlockNumber,
183
220
  _resetProbeSession,
184
221
  _bypassMetadataCheckForTest
185
222
  };
@@ -2,15 +2,15 @@ import {
2
2
  classifyErrorArea,
3
3
  isInteractive,
4
4
  promptYesNo
5
- } from "./chunk-7E33R3OQ.js";
5
+ } from "./chunk-UPA5PEQA.js";
6
6
  import {
7
7
  VERSION,
8
8
  getCurrentSentryTraceId,
9
9
  resolveIssueRepoSlug
10
- } from "./chunk-53BU4X2R.js";
10
+ } from "./chunk-LRJTODL2.js";
11
11
  import {
12
12
  package_default
13
- } from "./chunk-SES7CGNY.js";
13
+ } from "./chunk-DDC6NKE3.js";
14
14
 
15
15
  // src/bug-report.ts
16
16
  import { execSync, execFileSync } from "child_process";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-GRPLHUYC.js";
4
4
  import {
5
5
  DOT_DAPP_ID
6
- } from "./chunk-JWMHCW74.js";
6
+ } from "./chunk-NRSIBGBP.js";
7
7
 
8
8
  // src/sss-allowance-cache.ts
9
9
  import { mkdir, readFile, writeFile, unlink } from "fs/promises";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  package_default,
3
3
  writeRunState
4
- } from "./chunk-SES7CGNY.js";
4
+ } from "./chunk-DDC6NKE3.js";
5
5
 
6
6
  // src/memory-report.ts
7
7
  import * as fs2 from "fs";
@@ -241,7 +241,11 @@ var DEPLOY_SEED_STORAGE = {
241
241
  };
242
242
  var DEPLOY_SEED_PROBE = {
243
243
  "deploy.probe.finality_miss_count": 0,
244
- "deploy.probe.finality_miss_reupload_count": 0
244
+ "deploy.probe.finality_miss_reupload_count": 0,
245
+ // #1049: chunks present at best-block but not yet at finalised head.
246
+ // These are NEVER re-uploaded — only tracked so we can see finality lag
247
+ // without conflating it with genuinely-dropped (re-uploaded) chunks.
248
+ "deploy.probe.finality_lagging_count": 0
245
249
  };
246
250
  var DEPLOY_SEED_POOL = {
247
251
  "deploy.pool.eligible_count": 0,
@@ -337,6 +341,12 @@ var ERROR_KIND_RULES = [
337
341
  [/requires ProofOfPersonhood(?:Full|Lite|Light),\s*but this signer is NoStatus/i, "naming.pop_required"],
338
342
  [/requires NoStatus,\s*but this signer is ProofOfPersonhood/i, "naming.nostatus_required"],
339
343
  [/Cannot decode zero data.*with ABI parameters/i, "naming.contract_unavailable"],
344
+ // Issue #1060: contractCall's own empty-`0x`-data guard (src/dotns.ts, #729) throws
345
+ // an actionable wrapper instead of letting the raw viem message above reach a
346
+ // caller. Same failure family (a DotNS contract read came back empty) — classify
347
+ // it the same way instead of letting it fall into 'unknown'.
348
+ [/No contract deployed at .+ returned empty success data/i, "naming.contract_unavailable"],
349
+ [/Contract call returned empty data — contract=/i, "naming.contract_unavailable"],
340
350
  [/Domain\s+\S+\.dot\s+is already owned by\s+0x[a-fA-F0-9]+/i, "naming.already_owned"],
341
351
  [/Cannot deploy\s+[\w.-]+\.dot:\s*parent\s+[\w.-]+\.dot\s+is owned by/i, "naming.subdomain_orphan"],
342
352
  [/Post-deploy verification failed for .+: on-chain contenthash is /i, "verify.contenthash_mismatch"],
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  preflightSssAllowance
3
- } from "./chunk-7RYEYWYO.js";
3
+ } from "./chunk-JGS6QR5X.js";
4
4
  import {
5
5
  statementSigningAccount
6
6
  } from "./chunk-GRPLHUYC.js";
@@ -31,16 +31,18 @@ import {
31
31
  } from "./chunk-S7EM5VMW.js";
32
32
  import {
33
33
  setDeployContext
34
- } from "./chunk-HTZGBUZA.js";
34
+ } from "./chunk-IR5BHZ2S.js";
35
35
  import {
36
- probeChunks
37
- } from "./chunk-HLXRVFKL.js";
36
+ getBestBlockNumber,
37
+ probeChunks,
38
+ probeFinalityGap
39
+ } from "./chunk-FGCBIUMT.js";
38
40
  import {
39
41
  packSection
40
42
  } from "./chunk-C2TS5MER.js";
41
43
  import {
42
44
  resolveStorageSigner
43
- } from "./chunk-QDHLTSEO.js";
45
+ } from "./chunk-WVVRMGKN.js";
44
46
  import {
45
47
  createSlotAccountSigner,
46
48
  requestResourceAllocation
@@ -49,7 +51,7 @@ import {
49
51
  STALE_SESSION_MESSAGE,
50
52
  getPeopleChainEndpoints,
51
53
  hasPersistedSession
52
- } from "./chunk-JWMHCW74.js";
54
+ } from "./chunk-NRSIBGBP.js";
53
55
  import {
54
56
  CLI_NAME
55
57
  } from "./chunk-TSPERKUS.js";
@@ -62,7 +64,7 @@ import {
62
64
  parseDomainName,
63
65
  popStatusName,
64
66
  verifyNonceAdvanced
65
- } from "./chunk-O4I37F5K.js";
67
+ } from "./chunk-S62H6TWE.js";
66
68
  import {
67
69
  derivePoolAccounts,
68
70
  detectTestnet,
@@ -70,7 +72,7 @@ import {
70
72
  fetchPoolAuthorizations,
71
73
  isAuthorizationSufficient,
72
74
  selectAccount
73
- } from "./chunk-MRQPJLPS.js";
75
+ } from "./chunk-XVQICFTB.js";
74
76
  import {
75
77
  VERSION,
76
78
  captureWarning,
@@ -84,7 +86,7 @@ import {
84
86
  truncateAddress,
85
87
  withDeploySpan,
86
88
  withSpan
87
- } from "./chunk-53BU4X2R.js";
89
+ } from "./chunk-LRJTODL2.js";
88
90
  import {
89
91
  DEFAULT_ENV_ID,
90
92
  getPopSelfServeConfig,
@@ -339,27 +341,58 @@ async function waitForBulletinAuthorization(ss58, opts = {}) {
339
341
  client.destroy();
340
342
  }
341
343
  }
342
- async function getSlotSignerProvider(signer, ss58) {
343
- const primary = BULLETIN_ENDPOINTS[0];
344
- console.log(` Connecting to Bulletin (slot signer): ${primary}`);
345
- const client = createPolkadotClient(getWsProvider(
346
- BULLETIN_ENDPOINTS,
347
- { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
348
- ));
349
- const unsafeApi = client.getUnsafeApi();
350
- const [auth, currentBlock] = await Promise.all([
351
- unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
352
- client.getFinalizedBlock()
353
- ]);
354
- const result = isBulletinAuthActive(auth, currentBlock.number);
355
- if (!result.active) {
356
- client.destroy();
357
- throw new BulletinSlotAuthError(result.reason, ss58, result.expiration);
344
+ async function withTransientRetry(attempt, opts = {}) {
345
+ const { retries = 2, delayMs = 1e3 } = opts;
346
+ const debug = Boolean(process.env.DOT_DEBUG);
347
+ let lastErr;
348
+ for (let i = 0; i <= retries; i++) {
349
+ try {
350
+ return await attempt();
351
+ } catch (e) {
352
+ if (e instanceof BulletinSlotAuthError) throw e;
353
+ lastErr = e;
354
+ if (i < retries) {
355
+ if (debug) {
356
+ console.error(
357
+ `[slot-signer] transient error (attempt ${i + 1}/${retries + 1}): ${e instanceof Error ? e.message : String(e)}`
358
+ );
359
+ }
360
+ await new Promise((r) => setTimeout(r, delayMs));
361
+ }
362
+ }
358
363
  }
359
- console.log(` Using slot signer: ${ss58} (authorized until block ${result.expiration})`);
360
- setDeployAttribute("deploy.signer.mode", "slot");
361
- setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
362
- return { client, unsafeApi, signer, ss58 };
364
+ throw lastErr;
365
+ }
366
+ async function getSlotSignerProvider(signer, ss58) {
367
+ return withTransientRetry(async () => {
368
+ const primary = BULLETIN_ENDPOINTS[0];
369
+ console.log(` Connecting to Bulletin (slot signer): ${primary}`);
370
+ const client = createPolkadotClient(getWsProvider(
371
+ BULLETIN_ENDPOINTS,
372
+ { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
373
+ ));
374
+ const unsafeApi = client.getUnsafeApi();
375
+ let auth;
376
+ let currentBlock;
377
+ try {
378
+ [auth, currentBlock] = await Promise.all([
379
+ unsafeApi.query.TransactionStorage.Authorizations.getValue(Enum("Account", ss58)),
380
+ client.getFinalizedBlock()
381
+ ]);
382
+ } catch (e) {
383
+ client.destroy();
384
+ throw e;
385
+ }
386
+ const result = isBulletinAuthActive(auth, currentBlock.number);
387
+ if (!result.active) {
388
+ client.destroy();
389
+ throw new BulletinSlotAuthError(result.reason, ss58, result.expiration);
390
+ }
391
+ console.log(` Using slot signer: ${ss58} (authorized until block ${result.expiration})`);
392
+ setDeployAttribute("deploy.signer.mode", "slot");
393
+ setDeployAttribute("deploy.signer.address", truncateAddress(ss58));
394
+ return { client, unsafeApi, signer, ss58 };
395
+ });
363
396
  }
364
397
 
365
398
  // src/deploy.ts
@@ -400,15 +433,28 @@ var MAX_RECONNECTIONS = parseInt(process.env.BULLETIN_MAX_RECONNECTIONS ?? "3",
400
433
  var CHUNK_TIMEOUT_MS = parseInt(process.env.BULLETIN_CHUNK_TIMEOUT_MS ?? "180000", 10);
401
434
  var CHUNK_MORTALITY_PERIOD = (() => {
402
435
  const v = parseInt(process.env.BULLETIN_CHUNK_MORTALITY_PERIOD ?? "", 10);
403
- return Number.isFinite(v) && v > 0 ? v : 16;
436
+ return Number.isFinite(v) && v > 0 ? v : 64;
404
437
  })();
438
+ var CHUNK_LIVENESS_MAX_WAIT_MS = parseInt(process.env.BULLETIN_CHUNK_LIVENESS_MAX_WAIT_MS ?? "60000", 10);
439
+ var CHUNK_LIVENESS_POLL_MS = 5e3;
405
440
  var RETRY_BASE_DELAY_MS = 2e3;
406
441
  var RETRY_MAX_DELAY_MS = 15e3;
407
442
  var WS_HEARTBEAT_TIMEOUT_MS = 3e5;
408
- var GRANDPA_NATURAL_WAIT_MS = parseInt(process.env.BULLETIN_GRANDPA_NATURAL_WAIT_MS ?? "90000", 10);
443
+ var GRANDPA_NATURAL_WAIT_MS = parseInt(process.env.BULLETIN_GRANDPA_NATURAL_WAIT_MS ?? "210000", 10);
409
444
  var GRANDPA_REUPLOAD_POLL_MS = 5e3;
410
- var GRANDPA_REUPLOAD_TIMEOUT_MS = 12e4;
445
+ var GRANDPA_REUPLOAD_TIMEOUT_MS = parseInt(process.env.BULLETIN_GRANDPA_REUPLOAD_TIMEOUT_MS ?? "120000", 10);
411
446
  var GRANDPA_REUPLOAD_MAX_ROUNDS = 3;
447
+ var GRANDPA_LAGGING_WAIT_MS = parseInt(process.env.BULLETIN_GRANDPA_LAGGING_WAIT_MS ?? "90000", 10);
448
+ async function pollUntilFinalized(cids, timeoutMs, client) {
449
+ const start = Date.now();
450
+ while (Date.now() - start < timeoutMs && cids.size > 0) {
451
+ await new Promise((r) => setTimeout(r, GRANDPA_REUPLOAD_POLL_MS));
452
+ const poll = await probeChunks([...cids], { client, atFinalized: true });
453
+ for (const r of poll) {
454
+ if (r.present === true) cids.delete(r.cid);
455
+ }
456
+ }
457
+ }
412
458
  var RETRY_BUDGET_MAX_EVENTS = parseInt(process.env.BULLETIN_RETRY_BUDGET_MAX ?? "5", 10);
413
459
  var RETRY_BUDGET_WINDOW_MS = parseInt(process.env.BULLETIN_RETRY_BUDGET_WINDOW_MS ?? "30000", 10);
414
460
  function retryBudgetExhausted(history, maxEvents, windowMs, now = Date.now()) {
@@ -621,6 +667,12 @@ function formatTransferModeStorageSignerLine(workerAddress) {
621
667
  function formatTransferModeDotnsLine(alreadyOwned, dotName, recipient) {
622
668
  return alreadyOwned ? ` DotNS: you already own ${dotName} \u2014 content update needs your phone signature (no transfer)` : ` DotNS: will register ${dotName} and transfer it to your account ${recipient}`;
623
669
  }
670
+ function describeSlotFallbackReason(e) {
671
+ if (e instanceof BulletinSlotAuthError) {
672
+ return e.reason === "expired" && e.expiration != null ? `expired at block ${e.expiration}` : "no on-chain authorization found";
673
+ }
674
+ return e instanceof Error ? e.message : String(e);
675
+ }
624
676
  function selectStorageReconnect(options) {
625
677
  if (options.storageSigner && options.storageSignerAddress) {
626
678
  let useSlot = true;
@@ -631,12 +683,8 @@ function selectStorageReconnect(options) {
631
683
  } catch (e) {
632
684
  useSlot = false;
633
685
  setDeployAttribute("deploy.signer.mode", "pool-fallback");
634
- let reason;
635
- if (e instanceof BulletinSlotAuthError) {
636
- reason = e.reason === "expired" && e.expiration != null ? `expired at block ${e.expiration}` : "no on-chain authorization found";
637
- } else {
638
- reason = e instanceof Error ? e.message : String(e);
639
- }
686
+ const reason = describeSlotFallbackReason(e);
687
+ setDeployAttribute("deploy.signer.fallback_reason", reason);
640
688
  console.warn(
641
689
  `\u26A0 Bulletin allowance slot not usable: ${reason}
642
690
  Falling back to the shared pool account for storage (fine on testnet).
@@ -713,6 +761,26 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
713
761
  });
714
762
  });
715
763
  }
764
+ function reconcileTimedOutChunk(opts) {
765
+ const { originalNonce, currentNonce, nonceHeuristicValid, cidPresentAtBest } = opts;
766
+ if (cidPresentAtBest === true) return true;
767
+ if (nonceHeuristicValid && originalNonce !== void 0 && originalNonce < currentNonce) return true;
768
+ return false;
769
+ }
770
+ async function waitForChainLiveness(client, lastHeight, timeoutMs, pollMs = CHUNK_LIVENESS_POLL_MS) {
771
+ if (lastHeight == null) return getBestBlockNumber(client);
772
+ const deadline = Date.now() + timeoutMs;
773
+ let height = lastHeight;
774
+ while (Date.now() < deadline) {
775
+ const h = await getBestBlockNumber(client);
776
+ if (h == null) return height;
777
+ height = h;
778
+ if (height > lastHeight) return height;
779
+ await new Promise((r) => setTimeout(r, pollMs));
780
+ }
781
+ return height;
782
+ }
783
+ var __waitForChainLivenessForTest = waitForChainLiveness;
716
784
  async function storeChunk(unsafeApi, signer, chunkBytes, nonce, ss58, opts = {}) {
717
785
  const cid = createCID(chunkBytes, CID_CONFIG.codec, CID_CONFIG.hashCode);
718
786
  const tx = unsafeApi.tx.TransactionStorage.store_with_cid_config({ cid: { codec: BigInt(CID_CONFIG.codec), hashing: toHashingEnum(CID_CONFIG.hashCode) }, data: chunkBytes });
@@ -1062,8 +1130,14 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
1062
1130
  try {
1063
1131
  const currentNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
1064
1132
  const originalNonce = assignedNonces.get(fail.index);
1065
- if (!perRetryChanged && originalNonce !== void 0 && originalNonce < currentNonce) {
1066
- console.log(` Chunk ${fail.index}: nonce ${originalNonce} consumed (current=${currentNonce}), treating as included`);
1133
+ let cidPresentAtBest = null;
1134
+ try {
1135
+ const [probe] = await probeChunks([failCid.toString()], { client });
1136
+ cidPresentAtBest = probe.present === true ? true : null;
1137
+ } catch {
1138
+ }
1139
+ if (reconcileTimedOutChunk({ originalNonce, currentNonce, nonceHeuristicValid: !perRetryChanged, cidPresentAtBest })) {
1140
+ console.log(` Chunk ${fail.index}: reconcile found it already included (nonce ${originalNonce}\u2192${currentNonce}${cidPresentAtBest ? ", CID present at best-block" : ""}) \u2014 skipping resubmit`);
1067
1141
  stored[fail.index] = { cid: createCID(fail.chunkData, CID_CONFIG.codec, 18), len: fail.chunkData.length, viaFallback: true };
1068
1142
  nonceAdvanceIndices.add(fail.index);
1069
1143
  assignedNonces.delete(fail.index);
@@ -1071,6 +1145,11 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
1071
1145
  retried = true;
1072
1146
  break;
1073
1147
  }
1148
+ const heightBefore = await getBestBlockNumber(client);
1149
+ const heightAfter = await waitForChainLiveness(client, heightBefore, CHUNK_LIVENESS_MAX_WAIT_MS);
1150
+ if (heightBefore != null && heightAfter != null && heightAfter <= heightBefore) {
1151
+ console.log(` Chunk ${fail.index}: chain still frozen at block ${heightBefore} after ${(CHUNK_LIVENESS_MAX_WAIT_MS / 1e3).toFixed(0)}s wait \u2014 resubmitting anyway`);
1152
+ }
1074
1153
  const retryNonce = originalNonce ?? currentNonce;
1075
1154
  const result2 = await storeChunk(unsafeApi, signer, fail.chunkData, retryNonce, ss58, { fetchNonce: fetchNonceOverride });
1076
1155
  stored[fail.index] = result2;
@@ -1662,23 +1741,27 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1662
1741
  let missingCids = new Set(finalityResults.filter((r) => r.present === false).map((r) => r.cid));
1663
1742
  setDeployAttribute("deploy.probe.finality_miss_count", missingCids.size);
1664
1743
  let reuploadCount = 0;
1744
+ let laggingFinalityCount = 0;
1665
1745
  if (missingCids.size === 0) {
1666
1746
  console.log(` \u2713 All ${grandpaCids.length} chunks finalised`);
1667
1747
  } else {
1668
1748
  console.log(` ${missingCids.size} chunks not yet finalised \u2014 waiting up to ${GRANDPA_NATURAL_WAIT_MS / 1e3}s for natural finalisation`);
1669
1749
  for (const cid of missingCids) console.log(` ${cid.slice(0, 20)}\u2026`);
1670
1750
  const waitStart = Date.now();
1671
- while (Date.now() - waitStart < GRANDPA_NATURAL_WAIT_MS && missingCids.size > 0) {
1672
- await new Promise((r) => setTimeout(r, GRANDPA_REUPLOAD_POLL_MS));
1673
- const poll = await probeChunks([...missingCids], { client: phaseALiveProvider.client, atFinalized: true });
1674
- for (const r of poll) {
1675
- if (r.present === true) missingCids.delete(r.cid);
1676
- }
1677
- }
1751
+ await pollUntilFinalized(missingCids, GRANDPA_NATURAL_WAIT_MS, phaseALiveProvider.client);
1678
1752
  if (missingCids.size === 0) {
1679
1753
  const elapsed = Math.round((Date.now() - waitStart) / 1e3);
1680
1754
  console.log(` \u2713 All ${grandpaCids.length} chunks finalised (waited ${elapsed}s)`);
1681
1755
  } else {
1756
+ const stillMissing = [...missingCids];
1757
+ const { reallyMissing, lagging } = await probeFinalityGap(stillMissing, { client: phaseALiveProvider.client });
1758
+ const laggingCids = new Set(lagging);
1759
+ laggingFinalityCount = laggingCids.size;
1760
+ missingCids = new Set(reallyMissing);
1761
+ setDeployAttribute("deploy.probe.finality_lagging_count", laggingFinalityCount);
1762
+ if (laggingCids.size > 0) {
1763
+ console.log(` ${laggingCids.size} chunk(s) present in best-block but finality-lagging \u2014 will NOT re-upload, waiting for GRANDPA (bounded)`);
1764
+ }
1682
1765
  const rootHashCode = 18;
1683
1766
  const rootChunkLinks = phaseB.chunks.map((c) => ({
1684
1767
  cid: createCID(c, CID_CONFIG.codec, rootHashCode),
@@ -1737,14 +1820,7 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1737
1820
  }
1738
1821
  throw e;
1739
1822
  }
1740
- const reuploadStart = Date.now();
1741
- while (Date.now() - reuploadStart < GRANDPA_REUPLOAD_TIMEOUT_MS && missingCids.size > 0) {
1742
- await new Promise((r) => setTimeout(r, GRANDPA_REUPLOAD_POLL_MS));
1743
- const poll = await probeChunks([...missingCids], { client: phaseALiveProvider.client, atFinalized: true });
1744
- for (const r of poll) {
1745
- if (r.present === true) missingCids.delete(r.cid);
1746
- }
1747
- }
1823
+ await pollUntilFinalized(missingCids, GRANDPA_REUPLOAD_TIMEOUT_MS, phaseALiveProvider.client);
1748
1824
  }
1749
1825
  if (missingCids.size > 0) {
1750
1826
  const stuck = [...missingCids][0];
@@ -1752,7 +1828,21 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1752
1828
  `Deploy verification failed: ${missingCids.size} chunk(s) not finalised after ${GRANDPA_REUPLOAD_MAX_ROUNDS} re-upload round(s) (first: ${stuck.slice(0, 20)}\u2026). The chain may have dropped chunks due to a persistent fork. Re-run deploy.`
1753
1829
  );
1754
1830
  }
1755
- console.log(` \u2713 All ${grandpaCids.length} chunks finalised after re-upload`);
1831
+ if (reuploadCount > 0) {
1832
+ console.log(` \u2713 All ${grandpaCids.length - laggingFinalityCount} chunks finalised after re-upload`);
1833
+ } else {
1834
+ console.log(` \u2713 No chunks genuinely missing from best-block \u2014 skipped re-upload entirely`);
1835
+ }
1836
+ if (laggingCids.size > 0) {
1837
+ await pollUntilFinalized(laggingCids, GRANDPA_LAGGING_WAIT_MS, phaseALiveProvider.client);
1838
+ if (laggingCids.size === 0) {
1839
+ console.log(` \u2713 finality-lagging chunk(s) caught up`);
1840
+ } else {
1841
+ console.warn(
1842
+ ` \u26A0 ${laggingCids.size} chunk(s) still not finalised after extended wait, but confirmed present in best-block \u2014 deploy succeeds; finality pending asynchronously`
1843
+ );
1844
+ }
1845
+ }
1756
1846
  }
1757
1847
  }
1758
1848
  setDeployAttribute("deploy.probe.finality_miss_reupload_count", reuploadCount);
@@ -1833,11 +1923,14 @@ async function storeDirectoryV2(directoryPath, opts = {}) {
1833
1923
  console.log(` Final root check: ${storageCid}`);
1834
1924
  const rootProbe = await probeChunks([storageCid], { client: phaseALiveProvider.client, atFinalized: true });
1835
1925
  if (rootProbe[0]?.present === false) {
1836
- throw new Error(
1837
- `Deploy verification failed: DAG-PB root ${storageCid.slice(0, 20)}\u2026 not finalised. The chain may have evicted the root extrinsic. Re-run deploy.`
1838
- );
1839
- }
1840
- if (rootProbe[0]?.present === true) {
1926
+ const { reallyMissing } = await probeFinalityGap([storageCid], { client: phaseALiveProvider.client });
1927
+ if (reallyMissing.length > 0) {
1928
+ throw new Error(
1929
+ `Deploy verification failed: DAG-PB root ${storageCid.slice(0, 20)}\u2026 not finalised and not present in best-block. The chain may have evicted the root extrinsic. Re-run deploy.`
1930
+ );
1931
+ }
1932
+ console.log(` Root confirmed present in best-block (finality-lagging, #1049) \u2014 treating as success.`);
1933
+ } else if (rootProbe[0]?.present === true) {
1841
1934
  console.log(` \u2713 Root finalised on chain`);
1842
1935
  } else {
1843
1936
  console.log(` Root re-check inconclusive (RPC error) \u2014 GRANDPA probe above already verified; continuing.`);
@@ -3008,6 +3101,7 @@ export {
3008
3101
  isBulletinAuthActive,
3009
3102
  pollUntilBulletinAuthorized,
3010
3103
  waitForBulletinAuthorization,
3104
+ withTransientRetry,
3011
3105
  getSlotSignerProvider,
3012
3106
  friendlyChainError,
3013
3107
  DEFAULT_BULLETIN_RPC,
@@ -3039,6 +3133,9 @@ export {
3039
3133
  formatStorageSignerLine,
3040
3134
  formatTransferModeStorageSignerLine,
3041
3135
  formatTransferModeDotnsLine,
3136
+ describeSlotFallbackReason,
3137
+ reconcileTimedOutChunk,
3138
+ __waitForChainLivenessForTest,
3042
3139
  storeFile,
3043
3140
  __assignDenseNoncesForTest,
3044
3141
  storeChunkedContent,
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-TSPERKUS.js";
4
4
  import {
5
5
  VERSION
6
- } from "./chunk-53BU4X2R.js";
6
+ } from "./chunk-LRJTODL2.js";
7
7
  import {
8
8
  loadEnvironments
9
9
  } from "./chunk-JSYQ3JQS.js";