bulletin-deploy 0.7.24 → 0.7.25-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/assets/environments.json +2 -0
- package/bin/bulletin-deploy +10 -1
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-4V6HI3TA.js → chunk-4HFR7OYX.js} +1 -1
- package/dist/{chunk-N6AK5PSE.js → chunk-5BYKEV2D.js} +2 -2
- package/dist/{chunk-LEYQOOWC.js → chunk-BO22ZDUF.js} +15 -10
- package/dist/{chunk-5VEZVLDN.js → chunk-DJYVUPAF.js} +1 -1
- package/dist/{chunk-5MRZ3V4A.js → chunk-JOHTUV2D.js} +99 -2
- package/dist/{chunk-K6RIDRB7.js → chunk-LE62ANFY.js} +2 -2
- package/dist/{chunk-R6NLVFXP.js → chunk-O3S64AGV.js} +94 -41
- package/dist/{chunk-RZOZQ2AP.js → chunk-ODVRROGV.js} +114 -89
- package/dist/{chunk-5JHQZDWQ.js → chunk-Q7JW7TGW.js} +20 -1
- package/dist/{chunk-L7KXFYLH.js → chunk-Z2ZZKED3.js} +35 -6
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +18 -2
- package/dist/deploy.js +10 -10
- package/dist/dotns.d.ts +7 -8
- package/dist/dotns.js +9 -5
- package/dist/environments.d.ts +23 -1
- package/dist/environments.js +7 -3
- package/dist/incremental-stats.d.ts +2 -0
- package/dist/incremental-stats.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +15 -11
- package/dist/manifest-fetch.d.ts +10 -1
- package/dist/manifest-fetch.js +8 -3
- package/dist/manifest-roundtrip.js +2 -1
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +10 -10
- package/dist/personhood/bootstrap.js +4 -4
- package/dist/personhood/people-client.js +4 -4
- package/dist/run-state.js +1 -1
- package/dist/telemetry.js +2 -2
- package/dist/version-check.d.ts +4 -2
- package/dist/version-check.js +7 -3
- package/package.json +3 -3
package/assets/environments.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"ipfs": "https://previewnet.substrate.dev/ipfs/",
|
|
10
10
|
"autoAccountMapping": true,
|
|
11
11
|
"bulletinAuthorizeV2": true,
|
|
12
|
+
"registerStorageDeposit": 2000000000000,
|
|
12
13
|
"contracts": {
|
|
13
14
|
"DOTNS_PROTOCOL_REGISTRY": "0xc07A2F24387DA27283CD87b9F24573b74C9e0c9b",
|
|
14
15
|
"DOTNS_REGISTRAR": "0x6c40817cdb96Ab57A4d9E9fa21D0eEa8307BDDE8",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"autoAccountMapping": true,
|
|
57
58
|
"bulletinAuthorizeV2": true,
|
|
58
59
|
"nativeToEthRatio": 100000000,
|
|
60
|
+
"registerStorageDeposit": 2000000000000,
|
|
59
61
|
"popSelfServe": {
|
|
60
62
|
"sudoEnvLabel": "Next V2",
|
|
61
63
|
"faucetUrl": "https://faucet.polkadot.io/?parachain=1500",
|
package/bin/bulletin-deploy
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { deploy, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, NonRetryableError, EXIT_CODE_NO_RETRY, isConnectionError } from "../dist/deploy.js";
|
|
4
4
|
import { VERSION, setDeployAttribute, captureWarning, closeTelemetry, setRunStateActive, markRelaunchOomHintShown } from "../dist/telemetry.js";
|
|
5
|
-
import { handleFailedDeploy, preReleaseWarning, checkNodeVersion } from "../dist/version-check.js";
|
|
5
|
+
import { handleFailedDeploy, handlePreflightVersionCheck, fetchVersionInfo, preReleaseWarning, checkNodeVersion } from "../dist/version-check.js";
|
|
6
6
|
import { setDeployContext, installLogCapture, buildCliFlagsSummary } from "../dist/bug-report.js";
|
|
7
7
|
import { loadRunState, writeRunState, shouldSkipStaleWarning, shouldShowOomHint, probablyOomRssMb } from "../dist/run-state.js";
|
|
8
8
|
import { loadEnvironments, listEnvironments, formatEnvironmentTable, DEFAULT_ENV_ID } from "../dist/environments.js";
|
|
@@ -113,6 +113,11 @@ Options:
|
|
|
113
113
|
const rcWarning = preReleaseWarning(VERSION);
|
|
114
114
|
if (rcWarning) console.error(rcWarning);
|
|
115
115
|
|
|
116
|
+
// Fire in background immediately; await just before deploy() to stay non-blocking during flag-parse.
|
|
117
|
+
const _versionCheckPromise = process.env.BULLETIN_DEPLOY_UPDATE_CHECK !== "0"
|
|
118
|
+
? fetchVersionInfo()
|
|
119
|
+
: Promise.resolve(null);
|
|
120
|
+
|
|
116
121
|
// ── Crash capture (issue #154) ───────────────────────────────────
|
|
117
122
|
// Only wire crash capture for actual deploy runs — skip for --help / --version
|
|
118
123
|
// (which exit above).
|
|
@@ -251,6 +256,10 @@ try {
|
|
|
251
256
|
ci,
|
|
252
257
|
});
|
|
253
258
|
|
|
259
|
+
// Await the background version check started at startup.
|
|
260
|
+
const _versionInfo = await _versionCheckPromise;
|
|
261
|
+
if (handlePreflightVersionCheck(_versionInfo) === "abort") process.exit(1);
|
|
262
|
+
|
|
254
263
|
const result = await deploy(buildDir, domain, {
|
|
255
264
|
mnemonic: flags.mnemonic,
|
|
256
265
|
derivationPath: flags.derivationPath,
|
package/dist/bug-report.js
CHANGED
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
offerBugReport,
|
|
10
10
|
scrubSecrets,
|
|
11
11
|
setDeployContext
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-LE62ANFY.js";
|
|
13
|
+
import "./chunk-Z2ZZKED3.js";
|
|
14
|
+
import "./chunk-DJYVUPAF.js";
|
|
15
|
+
import "./chunk-5BYKEV2D.js";
|
|
16
16
|
export {
|
|
17
17
|
buildCliFlagsSummary,
|
|
18
18
|
buildLabels,
|
|
@@ -6,7 +6,7 @@ import * as path from "path";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "bulletin-deploy",
|
|
9
|
-
version: "0.7.
|
|
9
|
+
version: "0.7.25-rc.1",
|
|
10
10
|
private: false,
|
|
11
11
|
repository: {
|
|
12
12
|
type: "git",
|
|
@@ -77,7 +77,7 @@ var package_default = {
|
|
|
77
77
|
"@types/node": "^22.0.0",
|
|
78
78
|
tsup: "^8.5.0",
|
|
79
79
|
typescript: "^5.9.3",
|
|
80
|
-
ws: "^8.20.
|
|
80
|
+
ws: "^8.20.1"
|
|
81
81
|
},
|
|
82
82
|
minimumVersion: "0.5.6",
|
|
83
83
|
engines: {
|
|
@@ -25,6 +25,7 @@ function computeStats(input) {
|
|
|
25
25
|
recycledCids: recycled.length,
|
|
26
26
|
retentionPeriodBlocks: input.retentionPeriodBlocks,
|
|
27
27
|
bytesProbePresent: input.bytesProbePresent,
|
|
28
|
+
bytesProbeAbsent: input.bytesProbeAbsent ?? 0,
|
|
28
29
|
bytesSkipped: input.bytesSkipped,
|
|
29
30
|
bytesUploaded: input.bytesUploaded,
|
|
30
31
|
chunksTotal: input.chunksTotal,
|
|
@@ -64,6 +65,7 @@ function telemetryAttributes(s) {
|
|
|
64
65
|
"deploy.cache.chunks_uploaded": String(s.chunksUploaded),
|
|
65
66
|
"deploy.cache.chunks_skipped": String(s.chunksSkipped),
|
|
66
67
|
"deploy.cache.bytes_probe_present": String(s.bytesProbePresent),
|
|
68
|
+
"deploy.cache.bytes_probe_absent": String(s.bytesProbeAbsent),
|
|
67
69
|
"deploy.cache.bytes_skipped": String(s.bytesSkipped),
|
|
68
70
|
"deploy.cache.bytes_uploaded": String(s.bytesUploaded),
|
|
69
71
|
"deploy.cache.car_bytes": String(s.carBytes),
|
|
@@ -84,8 +86,9 @@ function fmtKb(bytes) {
|
|
|
84
86
|
}
|
|
85
87
|
function renderSummary(s) {
|
|
86
88
|
const lines = [];
|
|
89
|
+
const attemptsWord = s.manifestFetchAttempts === 1 ? "attempt" : "attempts";
|
|
87
90
|
if (s.manifestSource === "heuristic_fallback") {
|
|
88
|
-
lines.push(` \u26A0 Previous manifest fetch failed after ${s.manifestFetchAttempts}
|
|
91
|
+
lines.push(` \u26A0 Previous manifest fetch failed after ${s.manifestFetchAttempts} ${attemptsWord} (gateway timeout).`);
|
|
89
92
|
lines.push(` Using heuristic classification \u2014 hit rate may be lower this run.`);
|
|
90
93
|
lines.push(` Subsequent deploys recover automatically.`);
|
|
91
94
|
lines.push("");
|
|
@@ -94,15 +97,15 @@ function renderSummary(s) {
|
|
|
94
97
|
if (s.manifestSource === "none") {
|
|
95
98
|
lines.push(` Manifest: first deploy (no previous manifest)`);
|
|
96
99
|
} else if (s.manifestSource === "embedded") {
|
|
97
|
-
const attemptsStr = `${s.manifestFetchAttempts} attempt${s.manifestFetchAttempts === 1 ? "" : "s"}`;
|
|
98
100
|
const sizeStr = s.manifestBytes > 0 ? `, ${fmtKb(s.manifestBytes)} KB Range hit` : "";
|
|
99
|
-
lines.push(` Manifest: embedded (${
|
|
101
|
+
lines.push(` Manifest: embedded (${s.manifestFetchAttempts} ${attemptsWord}${sizeStr})`);
|
|
100
102
|
} else {
|
|
101
|
-
lines.push(` Manifest: heuristic_fallback (${s.manifestFetchAttempts}
|
|
103
|
+
lines.push(` Manifest: heuristic_fallback (${s.manifestFetchAttempts} ${attemptsWord})`);
|
|
102
104
|
}
|
|
103
105
|
if (s.filesTotal > 0 && s.manifestSource !== "none") {
|
|
104
106
|
const pct = s.filesTotal === 0 ? 0 : Math.round(s.filesStable / s.filesTotal * 100);
|
|
105
|
-
|
|
107
|
+
const heuristicNote = s.manifestSource === "heuristic_fallback" ? " (heuristic estimate)" : "";
|
|
108
|
+
lines.push(` Files: ${s.filesStable} unchanged, ${s.filesVolatile} changed (${pct} % stable)${heuristicNote}`);
|
|
106
109
|
}
|
|
107
110
|
if (s.probedTotal > 0) {
|
|
108
111
|
let probeFailedStr = "";
|
|
@@ -115,7 +118,7 @@ function renderSummary(s) {
|
|
|
115
118
|
}
|
|
116
119
|
lines.push(` Probed: ${s.probedTotal} chunks \u2192 ${s.probePresent} cached, ${s.probeAbsent} to upload${probeFailedStr}`);
|
|
117
120
|
}
|
|
118
|
-
if (s.recycledCids > 0) {
|
|
121
|
+
if (s.recycledCids > 0 && s.manifestSource === "embedded") {
|
|
119
122
|
lines.push(` Recycled: ${s.recycledCids} CIDs found on-chain that weren't in the previous manifest`);
|
|
120
123
|
}
|
|
121
124
|
if (s.tier2FallbackCount > 0) {
|
|
@@ -123,10 +126,12 @@ function renderSummary(s) {
|
|
|
123
126
|
lines.push(` Verify: ${s.tier2VerifiedCount}/${s.tier2FallbackCount} via-fallback chunks confirmed on chain${inconclusiveStr}`);
|
|
124
127
|
}
|
|
125
128
|
lines.push(` CAR sections: manifest ${fmtKb(s.section0Bytes)} KB \xB7 stable ${fmtMb(s.section1Bytes)} MB \xB7 volatile ${fmtMb(s.section2Bytes)} MB`);
|
|
126
|
-
if (s.chunksUploaded > 0
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
129
|
+
if (s.chunksUploaded > 0) {
|
|
130
|
+
if (s.bytesSkipped > 0) {
|
|
131
|
+
lines.push(` Upload: ${fmtMb(s.bytesUploaded)} MB across ${s.chunksUploaded} chunks (vs ${fmtMb(s.carBytes)} MB if full deploy)`);
|
|
132
|
+
} else {
|
|
133
|
+
lines.push(` Upload: ${fmtMb(s.bytesUploaded)} MB across ${s.chunksUploaded} chunks`);
|
|
134
|
+
}
|
|
130
135
|
}
|
|
131
136
|
if (s.estimatedSecondsSaved > 0 || s.bytesSkipped > 0) {
|
|
132
137
|
lines.push(` Saved: ~${s.estimatedSecondsSaved} s and ${fmtMb(s.bytesSkipped)} MB`);
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MANIFEST_DIR,
|
|
3
3
|
MANIFEST_FILENAME,
|
|
4
|
+
MANIFEST_PATH,
|
|
4
5
|
parseManifest
|
|
5
6
|
} from "./chunk-S7EM5VMW.js";
|
|
7
|
+
import {
|
|
8
|
+
mirrorUrl,
|
|
9
|
+
normalizeDomainFilename,
|
|
10
|
+
resolveOwnerRepo
|
|
11
|
+
} from "./chunk-HOTQDYHD.js";
|
|
6
12
|
|
|
7
13
|
// src/manifest-fetch.ts
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
8
16
|
import { CarReader } from "@ipld/car/reader";
|
|
9
17
|
import * as dagPB from "@ipld/dag-pb";
|
|
10
18
|
import { CID } from "multiformats/cid";
|
|
11
19
|
var DEFAULT_GATEWAY = "https://paseo-ipfs.polkadot.io";
|
|
12
20
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
21
|
+
var SIDECAR_FILENAME = ".last_deploy_cid";
|
|
13
22
|
var RANGE_TIERS = [
|
|
14
23
|
"bytes=0-4095",
|
|
15
24
|
"bytes=0-65535",
|
|
@@ -17,6 +26,67 @@ var RANGE_TIERS = [
|
|
|
17
26
|
void 0
|
|
18
27
|
// full body
|
|
19
28
|
];
|
|
29
|
+
function readLocalManifest(buildDir, prevContenthash) {
|
|
30
|
+
const sidePath = path.join(buildDir, MANIFEST_DIR, SIDECAR_FILENAME);
|
|
31
|
+
try {
|
|
32
|
+
const lastCid = fs.readFileSync(sidePath, "utf8").trim();
|
|
33
|
+
if (lastCid !== prevContenthash) return null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const manifestPath = path.join(buildDir, MANIFEST_PATH);
|
|
38
|
+
let text;
|
|
39
|
+
try {
|
|
40
|
+
text = fs.readFileSync(manifestPath, "utf8");
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const parsed = parseManifest(text);
|
|
45
|
+
if (!parsed.ok) return null;
|
|
46
|
+
return {
|
|
47
|
+
source: "embedded",
|
|
48
|
+
manifest: parsed.manifest,
|
|
49
|
+
attempts: 0,
|
|
50
|
+
bytesDownloaded: text.length
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function fetchFromGitHubPages(domain, prevContenthash, options, start) {
|
|
54
|
+
let ownerRepo;
|
|
55
|
+
try {
|
|
56
|
+
ownerRepo = resolveOwnerRepo(process.cwd());
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (!ownerRepo) return null;
|
|
61
|
+
const { owner, repo } = ownerRepo;
|
|
62
|
+
let domainFilename;
|
|
63
|
+
try {
|
|
64
|
+
domainFilename = normalizeDomainFilename(domain);
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const carUrl = mirrorUrl(owner, repo, domainFilename);
|
|
69
|
+
const sidecarUrl = carUrl.replace(/\.car$/, ".json");
|
|
70
|
+
const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
71
|
+
try {
|
|
72
|
+
const sidecarBudget = Math.min(2e3, budget - (Date.now() - start));
|
|
73
|
+
if (sidecarBudget <= 0) return null;
|
|
74
|
+
const ctrl = new AbortController();
|
|
75
|
+
const timer = setTimeout(() => ctrl.abort(), sidecarBudget);
|
|
76
|
+
let sidecarRes;
|
|
77
|
+
try {
|
|
78
|
+
sidecarRes = await fetch(sidecarUrl, { signal: ctrl.signal });
|
|
79
|
+
} finally {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
}
|
|
82
|
+
if (!sidecarRes.ok) return null;
|
|
83
|
+
const sidecar = await sidecarRes.json();
|
|
84
|
+
if (sidecar.cid !== prevContenthash) return null;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return fetchAcrossTiers(carUrl, budget, start);
|
|
89
|
+
}
|
|
20
90
|
async function fetchAcrossTiers(url, budget, start) {
|
|
21
91
|
let lastReason = "unknown";
|
|
22
92
|
let attempts = 0;
|
|
@@ -91,9 +161,34 @@ async function fetchAcrossTiers(url, budget, start) {
|
|
|
91
161
|
}
|
|
92
162
|
async function fetchPreviousManifest(prevContenthash, options = {}) {
|
|
93
163
|
if (prevContenthash === null) return { source: "none" };
|
|
94
|
-
|
|
95
|
-
|
|
164
|
+
if (options.buildDir) {
|
|
165
|
+
const local = readLocalManifest(options.buildDir, prevContenthash);
|
|
166
|
+
if (local) return local;
|
|
167
|
+
}
|
|
96
168
|
const start = Date.now();
|
|
169
|
+
const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
170
|
+
if (options.domain) {
|
|
171
|
+
const ghResult = await fetchFromGitHubPages(options.domain, prevContenthash, options, start);
|
|
172
|
+
if (ghResult) {
|
|
173
|
+
if (ghResult.outcome === "success") {
|
|
174
|
+
return {
|
|
175
|
+
source: "embedded",
|
|
176
|
+
manifest: ghResult.manifest,
|
|
177
|
+
attempts: ghResult.attempts,
|
|
178
|
+
bytesDownloaded: ghResult.bytesDownloaded
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (ghResult.outcome === "404" || ghResult.outcome === "parse_error") {
|
|
182
|
+
return {
|
|
183
|
+
source: "heuristic_fallback",
|
|
184
|
+
reason: ghResult.outcome === "404" ? "gh-pages 404" : ghResult.reason,
|
|
185
|
+
attempts: ghResult.attempts,
|
|
186
|
+
bytesDownloaded: ghResult.bytesDownloaded
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const gatewayList = (options.gateways ?? (options.gateway ? [options.gateway] : [DEFAULT_GATEWAY])).map((g) => g.replace(/\/$/, ""));
|
|
97
192
|
let lastReason = "unknown";
|
|
98
193
|
let totalAttempts = 0;
|
|
99
194
|
let bytesDownloaded = 0;
|
|
@@ -166,6 +261,8 @@ async function extractManifestFromCar(carBytes) {
|
|
|
166
261
|
export {
|
|
167
262
|
DEFAULT_GATEWAY,
|
|
168
263
|
DEFAULT_TIMEOUT_MS,
|
|
264
|
+
SIDECAR_FILENAME,
|
|
265
|
+
readLocalManifest,
|
|
169
266
|
fetchPreviousManifest,
|
|
170
267
|
extractManifestFromCar
|
|
171
268
|
};
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
classifyErrorArea,
|
|
3
3
|
isInteractive,
|
|
4
4
|
promptYesNo
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Z2ZZKED3.js";
|
|
6
6
|
import {
|
|
7
7
|
VERSION,
|
|
8
8
|
getCurrentSentryTraceId
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DJYVUPAF.js";
|
|
10
10
|
|
|
11
11
|
// src/bug-report.ts
|
|
12
12
|
import { execSync, execFileSync } from "child_process";
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
setDeploySentryTag,
|
|
8
8
|
truncateAddress,
|
|
9
9
|
withSpan
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DJYVUPAF.js";
|
|
11
|
+
import {
|
|
12
|
+
validateContractAddresses
|
|
13
|
+
} from "./chunk-Q7JW7TGW.js";
|
|
11
14
|
|
|
12
15
|
// src/dotns.ts
|
|
13
16
|
import crypto from "crypto";
|
|
@@ -37,6 +40,7 @@ var FEE_FLOOR_OWNED = ONE_PAS / 100n;
|
|
|
37
40
|
var FEE_FLOOR_REGISTER = ONE_PAS / 10n;
|
|
38
41
|
var TOP_UP_TARGET = ONE_PAS / 2n;
|
|
39
42
|
var SOURCE_BUFFER = ONE_PAS;
|
|
43
|
+
var MINIMUM_REGISTER_STORAGE_DEPOSIT = 2000000000000n;
|
|
40
44
|
var REPROVE_FEE_ESTIMATE = ONE_PAS / 100n;
|
|
41
45
|
var REPROVE_FEE_SAFETY_MARGIN_PCT = 110n;
|
|
42
46
|
var TOP_UP_TRANSFER_TIMEOUT_MS = 6e4;
|
|
@@ -51,8 +55,13 @@ function resolveNativeTokenSymbol(envId) {
|
|
|
51
55
|
if (envId.includes("rococo")) return "ROC";
|
|
52
56
|
return "PAS";
|
|
53
57
|
}
|
|
54
|
-
function feeFloorFor(plannedAction) {
|
|
55
|
-
|
|
58
|
+
function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
|
|
59
|
+
if (plannedAction === "already-owned-by-us") return FEE_FLOOR_OWNED;
|
|
60
|
+
return FEE_FLOOR_REGISTER + storageDeposit;
|
|
61
|
+
}
|
|
62
|
+
function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
|
|
63
|
+
if (plannedAction === "already-owned-by-us") return TOP_UP_TARGET;
|
|
64
|
+
return TOP_UP_TARGET + storageDeposit;
|
|
56
65
|
}
|
|
57
66
|
var RPC_ENDPOINTS = [
|
|
58
67
|
"wss://asset-hub-paseo.dotters.network",
|
|
@@ -145,6 +154,7 @@ var DOTNS_REGISTRAR_CONTROLLER_ABI = [
|
|
|
145
154
|
{ inputs: [{ name: "registration", type: "tuple", components: [{ name: "label", type: "string" }, { name: "owner", type: "address" }, { name: "secret", type: "bytes32" }, { name: "reserved", type: "bool" }] }], name: "makeCommitment", outputs: [{ name: "", type: "bytes32" }], stateMutability: "view", type: "function" },
|
|
146
155
|
{ inputs: [{ name: "commitment", type: "bytes32" }], name: "commit", outputs: [], stateMutability: "nonpayable", type: "function" },
|
|
147
156
|
{ inputs: [], name: "minCommitmentAge", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
|
|
157
|
+
{ inputs: [], name: "maxCommitmentAge", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
|
|
148
158
|
{ inputs: [{ name: "commitment", type: "bytes32" }], name: "commitments", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
|
|
149
159
|
{ inputs: [{ name: "registration", type: "tuple", components: [{ name: "label", type: "string" }, { name: "owner", type: "address" }, { name: "secret", type: "bytes32" }, { name: "reserved", type: "bool" }] }], name: "register", outputs: [], stateMutability: "payable", type: "function" }
|
|
150
160
|
];
|
|
@@ -156,7 +166,6 @@ var POP_RULES_ABI = [
|
|
|
156
166
|
{ inputs: [{ name: "name", type: "string" }], name: "price", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function" },
|
|
157
167
|
{ inputs: [{ name: "name", type: "string" }, { name: "userAddress", type: "address" }], name: "priceWithCheck", outputs: [{ name: "metadata", type: "tuple", components: [{ name: "price", type: "uint256" }, { name: "status", type: "uint8" }, { name: "userStatus", type: "uint8" }, { name: "message", type: "string" }] }], stateMutability: "view", type: "function" },
|
|
158
168
|
{ inputs: [{ name: "name", type: "string" }, { name: "userAddress", type: "address" }], name: "priceWithoutCheck", outputs: [{ name: "metadata", type: "tuple", components: [{ name: "price", type: "uint256" }, { name: "status", type: "uint8" }, { name: "userStatus", type: "uint8" }, { name: "message", type: "string" }] }], stateMutability: "view", type: "function" },
|
|
159
|
-
{ inputs: [{ name: "status", type: "uint8" }], name: "setUserPopStatus", outputs: [], stateMutability: "nonpayable", type: "function" },
|
|
160
169
|
{ inputs: [{ name: "name", type: "string" }], name: "isBaseNameReserved", outputs: [{ name: "isReserved", type: "bool" }, { name: "reservationOwner", type: "address" }, { name: "expiryTimestamp", type: "uint64" }], stateMutability: "view", type: "function" }
|
|
161
170
|
];
|
|
162
171
|
var PERSONHOOD_ABI = [
|
|
@@ -268,14 +277,27 @@ function formatContractDryRunFailure(gasEstimate, context) {
|
|
|
268
277
|
const revertData = gasEstimate.revertData;
|
|
269
278
|
const isBareRevert = (revertData === void 0 || revertData.trim() === "0x") && gasEstimate.revertFlags === 1n;
|
|
270
279
|
if (isBareRevert && BARE_REVERT_DIAGNOSTIC_FUNCTIONS.has(functionName)) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
if (functionName === "register") {
|
|
281
|
+
lines.push(
|
|
282
|
+
` diagnostic: bare-revert (empty 0x) during register. Most likely cause: insufficient signer balance for storage deposit.`,
|
|
283
|
+
` A fresh TLD register() requires sufficient free balance to cover the chain's storage deposit (typically 200+ PAS).`,
|
|
284
|
+
` Other possible causes:`,
|
|
285
|
+
` 1. PoP status changed between preflight and registration (race condition).`,
|
|
286
|
+
` 2. Commitment timing: the revealed commitment is still too new or already expired.`,
|
|
287
|
+
` 3. Label was registered by someone else between preflight and register.`,
|
|
288
|
+
` To reproduce in isolation: \`node tools/dotns-dry-run.mjs <label>\``,
|
|
289
|
+
` To rule out a mapping issue: add --fresh (a brand-new unmapped origin) \u2014 if --fresh reverts but the mapped one doesn't, it's a mapping bug.`
|
|
290
|
+
);
|
|
291
|
+
} else {
|
|
292
|
+
lines.push(
|
|
293
|
+
` diagnostic: bare-revert (empty 0x). Account mapping was verified at connect time, so the cause is likely:`,
|
|
294
|
+
` 1. PoP status changed between preflight and registration (race condition).`,
|
|
295
|
+
` 2. Commitment timing: the revealed commitment is still too new or already expired.`,
|
|
296
|
+
` 3. Label was registered by someone else between preflight and register.`,
|
|
297
|
+
` To reproduce in isolation: \`node tools/dotns-dry-run.mjs <label>\``,
|
|
298
|
+
` To rule out a mapping issue: add --fresh (a brand-new unmapped origin) \u2014 if --fresh reverts but the mapped one doesn't, it's a mapping bug.`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
279
301
|
}
|
|
280
302
|
return lines.join("\n");
|
|
281
303
|
}
|
|
@@ -348,6 +370,9 @@ function validateDomainLabel(label) {
|
|
|
348
370
|
function isCommitmentMature(chainNowSeconds, commitTimestampSeconds, minimumAgeSeconds) {
|
|
349
371
|
return chainNowSeconds > commitTimestampSeconds + minimumAgeSeconds;
|
|
350
372
|
}
|
|
373
|
+
function isCommitmentTimingBarerevert(msg) {
|
|
374
|
+
return /bare-revert.*\(empty 0x\)/i.test(msg) || /commitment.*too new.*expired/i.test(msg) || /expired.*commitment/i.test(msg);
|
|
375
|
+
}
|
|
351
376
|
function classifyDotnsLabel(label) {
|
|
352
377
|
const totalLength = label.length;
|
|
353
378
|
const trailingDigits = countTrailingDigits(label);
|
|
@@ -385,21 +410,6 @@ function exampleNoStatusLabel(label) {
|
|
|
385
410
|
const base = stripTrailingDigits(validateDomainLabel(label)).replace(/[^a-z0-9-]/g, "x");
|
|
386
411
|
return `${base.padEnd(9, "x").slice(0, 9)}00.dot`;
|
|
387
412
|
}
|
|
388
|
-
function simulateUserStatus(currentStatus, requiredStatus, options) {
|
|
389
|
-
const canSelfAttest = options.canSelfAttest ?? true;
|
|
390
|
-
const max = (a, b) => a > b ? a : b;
|
|
391
|
-
if (options.explicitStatus !== void 0) {
|
|
392
|
-
if (options.isTestnet && canSelfAttest) return max(currentStatus, options.explicitStatus);
|
|
393
|
-
return currentStatus;
|
|
394
|
-
}
|
|
395
|
-
if (requiredStatus === ProofOfPersonhoodStatus.NoStatus && currentStatus === ProofOfPersonhoodStatus.ProofOfPersonhoodLite && options.isTestnet && canSelfAttest) {
|
|
396
|
-
return ProofOfPersonhoodStatus.ProofOfPersonhoodFull;
|
|
397
|
-
}
|
|
398
|
-
if (requiredStatus !== ProofOfPersonhoodStatus.NoStatus && options.isTestnet && canSelfAttest) {
|
|
399
|
-
return max(currentStatus, requiredStatus);
|
|
400
|
-
}
|
|
401
|
-
return currentStatus;
|
|
402
|
-
}
|
|
403
413
|
function parseDomainName(input) {
|
|
404
414
|
const name = input.replace(/\.dot$/, "");
|
|
405
415
|
const parts = name.split(".");
|
|
@@ -707,6 +717,7 @@ var DotNS = class {
|
|
|
707
717
|
_nativeToEthRatio = NATIVE_TO_ETH_RATIO;
|
|
708
718
|
_environmentId = null;
|
|
709
719
|
_popSelfServe = null;
|
|
720
|
+
_registerStorageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT;
|
|
710
721
|
// Test-only seam: consumed once by classifyAliasAccountState() then cleared.
|
|
711
722
|
// Mirrors the __setDeployRootSpanForTest / __setSentryForTest pattern.
|
|
712
723
|
_classifyOverrideForTest = null;
|
|
@@ -742,6 +753,7 @@ var DotNS = class {
|
|
|
742
753
|
this.assetHubEndpoints = options.assetHubEndpoints;
|
|
743
754
|
}
|
|
744
755
|
if (options.contracts && Object.keys(options.contracts).length > 0) {
|
|
756
|
+
validateContractAddresses(options.contracts, options.environmentId ?? "unknown");
|
|
745
757
|
this._contracts = { ...CONTRACTS, ...options.contracts };
|
|
746
758
|
}
|
|
747
759
|
if (options.environmentId) {
|
|
@@ -750,6 +762,9 @@ var DotNS = class {
|
|
|
750
762
|
if (options.popSelfServe !== void 0) {
|
|
751
763
|
this._popSelfServe = options.popSelfServe ?? null;
|
|
752
764
|
}
|
|
765
|
+
if (options.registerStorageDeposit !== void 0) {
|
|
766
|
+
this._registerStorageDeposit = options.registerStorageDeposit;
|
|
767
|
+
}
|
|
753
768
|
const rpc = options.rpc || process.env.DOTNS_RPC || this.assetHubEndpoints[0];
|
|
754
769
|
this.rpc = rpc;
|
|
755
770
|
this._usesExternalSigner = Boolean(options.signer && options.signerAddress);
|
|
@@ -1136,9 +1151,6 @@ var DotNS = class {
|
|
|
1136
1151
|
);
|
|
1137
1152
|
}
|
|
1138
1153
|
}
|
|
1139
|
-
async setUserPopStatus(_status) {
|
|
1140
|
-
throw new Error("DotNS self-attestation is no longer available. Personhood status is read from the Personhood precompile; contact the DotNS team for whitelisting / status changes.");
|
|
1141
|
-
}
|
|
1142
1154
|
async checkSubdomainOwnership(sublabel, parentLabel) {
|
|
1143
1155
|
this.ensureConnected();
|
|
1144
1156
|
if (!this.clientWrapper) return { owned: false, owner: null };
|
|
@@ -1183,9 +1195,20 @@ var DotNS = class {
|
|
|
1183
1195
|
}
|
|
1184
1196
|
if (!ipfsCid) throw new Error(`setContenthash: cannot decode contenthash ${contenthashHex} to an IPFS CID`);
|
|
1185
1197
|
console.log(` Setting contenthash: ${ipfsCid}`);
|
|
1198
|
+
const expected = contenthashHex.toLowerCase();
|
|
1199
|
+
try {
|
|
1200
|
+
const current = (await this.getContenthash(domainName) || "0x").toLowerCase();
|
|
1201
|
+
if (current === expected) {
|
|
1202
|
+
console.log(` Contenthash already set: ${ipfsCid} \u2014 skipping tx`);
|
|
1203
|
+
setDeployAttribute("deploy.dotns.contenthash_unchanged", "true");
|
|
1204
|
+
return { node };
|
|
1205
|
+
}
|
|
1206
|
+
} catch (_) {
|
|
1207
|
+
}
|
|
1208
|
+
setDeployAttribute("deploy.dotns.contenthash_unchanged", "false");
|
|
1186
1209
|
const txHash = await this.contractTransaction(this._contracts.DOTNS_CONTENT_RESOLVER, 0n, DOTNS_CONTENT_RESOLVER_ABI, "setContenthash", [node, contenthashHex], (s) => console.log(` ${s}`), { useNoncePolling: true });
|
|
1187
1210
|
console.log(` Tx: ${txHash}`);
|
|
1188
|
-
|
|
1211
|
+
let txResolution = txHash.startsWith("nonce-advanced:") ? "nonce-advanced" : "finalized";
|
|
1189
1212
|
const MAX_CHAIN_WAIT_SECONDS = 90;
|
|
1190
1213
|
const POLL_INTERVAL_MS = 2e3;
|
|
1191
1214
|
const startChainMs = Number(await this.clientWrapper.client.query.Timestamp.Now.getValue());
|
|
@@ -1199,6 +1222,8 @@ var DotNS = class {
|
|
|
1199
1222
|
console.log(` Awaiting finalization (chain time +${Math.floor(chainElapsed)}s / ${MAX_CHAIN_WAIT_SECONDS}s)...`);
|
|
1200
1223
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1201
1224
|
}
|
|
1225
|
+
if (onChain !== expected) txResolution = "timeout";
|
|
1226
|
+
setDeployAttribute("deploy.dotns.tx_resolution", txResolution);
|
|
1202
1227
|
if (onChain !== expected) {
|
|
1203
1228
|
throw new Error(
|
|
1204
1229
|
`Post-deploy verification failed for ${domainName}.dot: on-chain contenthash is ${onChain}, not the ${expected} we just wrote. The setContenthash tx may have silently failed, or another party overwrote the domain. Re-run the deploy to retry.`
|
|
@@ -1300,16 +1325,19 @@ var DotNS = class {
|
|
|
1300
1325
|
const POLL_INTERVAL_MS = 3e3;
|
|
1301
1326
|
console.log(`
|
|
1302
1327
|
Reading minimum commitment age...`);
|
|
1303
|
-
const [minimumAge, initialCommitTimestamp] = await Promise.all([
|
|
1328
|
+
const [minimumAge, maximumAge, initialCommitTimestamp] = await Promise.all([
|
|
1304
1329
|
withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "minCommitmentAge", []), 3e4, "minCommitmentAge"),
|
|
1330
|
+
withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "maxCommitmentAge", []), 3e4, "maxCommitmentAge"),
|
|
1305
1331
|
withTimeout(this.contractCall(this._contracts.DOTNS_REGISTRAR_CONTROLLER, DOTNS_REGISTRAR_CONTROLLER_ABI, "commitments", [commitment]), 3e4, "commitments")
|
|
1306
1332
|
]);
|
|
1307
1333
|
const minimumAgeSeconds = typeof minimumAge === "bigint" ? Number(minimumAge) : minimumAge;
|
|
1334
|
+
const maximumAgeSeconds = typeof maximumAge === "bigint" ? Number(maximumAge) : maximumAge ?? 86400;
|
|
1308
1335
|
const commitTimestamp = typeof initialCommitTimestamp === "bigint" ? Number(initialCommitTimestamp) : initialCommitTimestamp;
|
|
1309
1336
|
if (commitTimestamp === 0) {
|
|
1310
1337
|
throw new Error("Commitment not found on-chain. It may not have been included in a block yet.");
|
|
1311
1338
|
}
|
|
1312
|
-
console.log(` Minimum commitment age: ${minimumAgeSeconds}s`);
|
|
1339
|
+
console.log(` Minimum commitment age: ${minimumAgeSeconds}s, maximum: ${maximumAgeSeconds}s`);
|
|
1340
|
+
console.log(` Commitment valid window: ${commitTimestamp + minimumAgeSeconds} \u2013 ${commitTimestamp + maximumAgeSeconds}`);
|
|
1313
1341
|
console.log(` Commitment stored on-chain (timestamp: ${commitTimestamp})`);
|
|
1314
1342
|
console.log(` Waiting for on-chain block.timestamp > ${commitTimestamp + minimumAgeSeconds}...`);
|
|
1315
1343
|
const pollDeadline = Date.now() + POLL_TIMEOUT_MS;
|
|
@@ -1318,6 +1346,17 @@ var DotNS = class {
|
|
|
1318
1346
|
const chainNowSeconds = Math.floor(Number(nowMs) / 1e3);
|
|
1319
1347
|
if (isCommitmentMature(chainNowSeconds, commitTimestamp, minimumAgeSeconds)) {
|
|
1320
1348
|
console.log(` Commitment age requirement met (chain.now=${chainNowSeconds}, target>${commitTimestamp + minimumAgeSeconds})`);
|
|
1349
|
+
console.log(` Buffering ${POLL_INTERVAL_MS / 1e3}s for block propagation (guard against node lag after maturity)...`);
|
|
1350
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
1351
|
+
const nowAfterBuffer = Math.floor(Number(await this.clientWrapper.client.query.Timestamp.Now.getValue()) / 1e3);
|
|
1352
|
+
const expiresAt = commitTimestamp + maximumAgeSeconds;
|
|
1353
|
+
const remainingSecs = expiresAt - nowAfterBuffer;
|
|
1354
|
+
if (remainingSecs <= 0) {
|
|
1355
|
+
throw new Error(`Commitment has expired (chain.now=${nowAfterBuffer}, expired at=${expiresAt}). A fresh commit cycle is needed.`);
|
|
1356
|
+
}
|
|
1357
|
+
if (remainingSecs < 30) {
|
|
1358
|
+
console.log(` Warning: commitment expires in ${remainingSecs}s \u2014 proceeding immediately.`);
|
|
1359
|
+
}
|
|
1321
1360
|
return;
|
|
1322
1361
|
}
|
|
1323
1362
|
const remaining = Math.ceil((pollDeadline - Date.now()) / 1e3);
|
|
@@ -1645,13 +1684,13 @@ var DotNS = class {
|
|
|
1645
1684
|
// canProceed:true result with an actionable canProceed:false when even the
|
|
1646
1685
|
// top-up can't get the signer above the threshold.
|
|
1647
1686
|
async gateOnFeeBalance(candidate, signerFreeBalance, isTestnet) {
|
|
1648
|
-
const feeFloor = feeFloorFor(candidate.plannedAction);
|
|
1687
|
+
const feeFloor = feeFloorFor(candidate.plannedAction, this._registerStorageDeposit);
|
|
1649
1688
|
let effectiveBalance = signerFreeBalance;
|
|
1650
1689
|
let toppedUp;
|
|
1651
1690
|
if (effectiveBalance < feeFloor && isTestnet) {
|
|
1652
1691
|
setDeployAttribute("deploy.dotns.signer_below_floor", "true");
|
|
1653
1692
|
console.log(` DotNS signer ${this.substrateAddress?.slice(0, 8)}... balance ${fmtPas(effectiveBalance)} PAS < ${fmtPas(feeFloor)} PAS floor \u2014 attempting testnet auto top-up...`);
|
|
1654
|
-
const result = await this.attemptTestnetTopUp(this.substrateAddress,
|
|
1693
|
+
const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit));
|
|
1655
1694
|
if (result) {
|
|
1656
1695
|
console.log(` Topped up ${fmtPas(result.transferred)} PAS from ${result.source}`);
|
|
1657
1696
|
effectiveBalance += result.transferred;
|
|
@@ -1728,11 +1767,24 @@ var DotNS = class {
|
|
|
1728
1767
|
if (!canRegister(requiredStatus, userStatus, trailingDigitCount)) {
|
|
1729
1768
|
rejectIneligible(requiredStatus, userStatus);
|
|
1730
1769
|
}
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1770
|
+
const doCommitAndRegister = async () => {
|
|
1771
|
+
const { commitment, registration } = await this.generateCommitment(label, reverse);
|
|
1772
|
+
await withSpan("deploy.dotns.submit-commitment", "2a-i. submit-commitment", {}, () => this.submitCommitment(commitment));
|
|
1773
|
+
await withSpan("deploy.dotns.wait-commitment-age", "2a-ii. wait-commitment-age", {}, () => this.waitForCommitmentAge(commitment));
|
|
1774
|
+
const pricing = await withSpan("deploy.dotns.price-validation", "2a-iii. price-validation", {}, () => this.getPriceAndValidate(label));
|
|
1775
|
+
await withSpan("deploy.dotns.finalize-registration", "2a-iv. finalize-registration", {}, () => this.finalizeRegistration(registration, pricing.priceWei));
|
|
1776
|
+
};
|
|
1777
|
+
try {
|
|
1778
|
+
await doCommitAndRegister();
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
const msg = err.message ?? "";
|
|
1781
|
+
if (!isCommitmentTimingBarerevert(msg)) throw err;
|
|
1782
|
+
console.log(`
|
|
1783
|
+
Register bare-reverted (commitment timing race \u2014 node saw a block where commitment was too new or expired).`);
|
|
1784
|
+
console.log(` Retrying with a fresh commitment. This usually resolves in one block.
|
|
1785
|
+
`);
|
|
1786
|
+
await doCommitAndRegister();
|
|
1787
|
+
}
|
|
1736
1788
|
await this.verifyOwnership(label);
|
|
1737
1789
|
console.log(`
|
|
1738
1790
|
Registration complete!`);
|
|
@@ -1810,6 +1862,7 @@ var DotNS = class {
|
|
|
1810
1862
|
var dotns = new DotNS();
|
|
1811
1863
|
|
|
1812
1864
|
export {
|
|
1865
|
+
MINIMUM_REGISTER_STORAGE_DEPOSIT,
|
|
1813
1866
|
fmtPas,
|
|
1814
1867
|
feeFloorFor,
|
|
1815
1868
|
RPC_ENDPOINTS,
|
|
@@ -1838,9 +1891,9 @@ export {
|
|
|
1838
1891
|
sanitizeDomainLabel,
|
|
1839
1892
|
validateDomainLabel,
|
|
1840
1893
|
isCommitmentMature,
|
|
1894
|
+
isCommitmentTimingBarerevert,
|
|
1841
1895
|
classifyDotnsLabel,
|
|
1842
1896
|
canRegister,
|
|
1843
|
-
simulateUserStatus,
|
|
1844
1897
|
parseDomainName,
|
|
1845
1898
|
parseProofOfPersonhoodStatus,
|
|
1846
1899
|
popStatusName,
|