bulletin-deploy 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/bin/bulletin-deploy +94 -1
- package/dist/bug-report.js +4 -3
- package/dist/{chunk-4UNQGG3O.js → chunk-CQ753LDA.js} +1 -1
- package/dist/{chunk-4FUUYJP2.js → chunk-DHQ3JGF4.js} +67 -75
- package/dist/{chunk-VJLTIZ6S.js → chunk-SVKCVNXD.js} +126 -68
- package/dist/chunk-UJP2PZGU.js +155 -0
- package/dist/chunk-YREZFNCC.js +869 -0
- package/dist/{chunk-LMSMDKVU.js → chunk-YYULF2JX.js} +2 -2
- package/dist/deploy.d.ts +4 -2
- package/dist/deploy.js +8 -5
- package/dist/dotns.d.ts +25 -39
- package/dist/dotns.js +11 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +27 -7
- package/dist/memory-report.js +2 -1
- package/dist/run-state.d.ts +22 -0
- package/dist/run-state.js +21 -0
- package/dist/telemetry.d.ts +5 -1
- package/dist/telemetry.js +8 -1
- package/dist/version-check.js +3 -2
- package/package.json +3 -2
- package/dist/chunk-NEV6WTYM.js +0 -999
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
setDeployContext
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YYULF2JX.js";
|
|
4
4
|
import {
|
|
5
5
|
DotNS,
|
|
6
6
|
TX_TIMEOUT_MS,
|
|
7
7
|
fetchNonce,
|
|
8
|
+
parseDomainName,
|
|
8
9
|
popStatusName,
|
|
9
|
-
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
verifyNonceAdvanced
|
|
11
|
+
} from "./chunk-YREZFNCC.js";
|
|
11
12
|
import {
|
|
12
13
|
MirrorSkipped,
|
|
13
14
|
mirrorToGitHubPages,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
truncateAddress,
|
|
27
28
|
withDeploySpan,
|
|
28
29
|
withSpan
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-DHQ3JGF4.js";
|
|
30
31
|
import {
|
|
31
32
|
merkleizeJS
|
|
32
33
|
} from "./chunk-B7GUYYAN.js";
|
|
@@ -48,7 +49,7 @@ import { sha256 } from "@noble/hashes/sha256";
|
|
|
48
49
|
import { blake2b } from "@noble/hashes/blake2b";
|
|
49
50
|
import { createClient as createPolkadotClient, Enum } from "polkadot-api";
|
|
50
51
|
import { Binary } from "@polkadot-api/substrate-bindings";
|
|
51
|
-
import { getWsProvider } from "polkadot-api/ws-provider";
|
|
52
|
+
import { getWsProvider, WsEvent } from "polkadot-api/ws-provider";
|
|
52
53
|
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
|
|
53
54
|
import { CID } from "multiformats/cid";
|
|
54
55
|
import { create as createMultihash } from "multiformats/hashes/digest";
|
|
@@ -75,12 +76,23 @@ function friendlyChainError(msg) {
|
|
|
75
76
|
}
|
|
76
77
|
var DEFAULT_BULLETIN_RPC = "wss://paseo-bulletin-rpc.polkadot.io";
|
|
77
78
|
var DEFAULT_POOL_SIZE = 10;
|
|
78
|
-
var
|
|
79
|
+
var BULLETIN_ENDPOINTS = [DEFAULT_BULLETIN_RPC];
|
|
79
80
|
var POOL_SIZE = DEFAULT_POOL_SIZE;
|
|
81
|
+
var _deployRpcFailedOver = false;
|
|
82
|
+
function makeBulletinStatusHandler(primary) {
|
|
83
|
+
return (s) => {
|
|
84
|
+
if (s.type === WsEvent.CONNECTED && s.uri !== primary) {
|
|
85
|
+
_deployRpcFailedOver = true;
|
|
86
|
+
setDeployAttribute("deploy.rpc.failed_over", "true");
|
|
87
|
+
captureWarning("Bulletin RPC failover", { from: primary, to: s.uri });
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
80
91
|
var CHUNK_SIZE = 2 * 1024 * 1024;
|
|
81
92
|
var MAX_FILE_SIZE = 8 * 1024 * 1024;
|
|
82
93
|
var MAX_RECONNECTIONS = parseInt(process.env.BULLETIN_MAX_RECONNECTIONS ?? "3", 10);
|
|
83
94
|
var CHUNK_TIMEOUT_MS = parseInt(process.env.BULLETIN_CHUNK_TIMEOUT_MS ?? "180000", 10);
|
|
95
|
+
var CHUNK_MORTALITY_PERIOD = 16;
|
|
84
96
|
var RETRY_BASE_DELAY_MS = 2e3;
|
|
85
97
|
var RETRY_MAX_DELAY_MS = 15e3;
|
|
86
98
|
var WS_HEARTBEAT_TIMEOUT_MS = 3e5;
|
|
@@ -147,8 +159,12 @@ function toHashingEnum(mhCode) {
|
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
async function getProvider() {
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
const primary = BULLETIN_ENDPOINTS[0];
|
|
163
|
+
console.log(` Connecting to Bulletin: ${primary}`);
|
|
164
|
+
const client = createPolkadotClient(withPolkadotSdkCompat(getWsProvider(
|
|
165
|
+
BULLETIN_ENDPOINTS,
|
|
166
|
+
{ heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
|
|
167
|
+
)));
|
|
152
168
|
const unsafeApi = client.getUnsafeApi();
|
|
153
169
|
try {
|
|
154
170
|
await cryptoWaitReady();
|
|
@@ -159,10 +175,10 @@ async function getProvider() {
|
|
|
159
175
|
if (!selected) {
|
|
160
176
|
const best = authorizations.reduce((a, b) => a.transactions > b.transactions ? a : b);
|
|
161
177
|
console.log(` All pool accounts low on capacity, auto-authorizing account ${best.index}...`);
|
|
162
|
-
await ensureAuthorized(unsafeApi, best.address,
|
|
178
|
+
await ensureAuthorized(unsafeApi, best.address, BULLETIN_ENDPOINTS[0], `pool account ${best.index}`);
|
|
163
179
|
selected = best;
|
|
164
180
|
} else {
|
|
165
|
-
await ensureAuthorized(unsafeApi, selected.address,
|
|
181
|
+
await ensureAuthorized(unsafeApi, selected.address, BULLETIN_ENDPOINTS[0], `pool account ${selected.index}`);
|
|
166
182
|
}
|
|
167
183
|
console.log(` Using pool account ${selected.index}: ${selected.address}`);
|
|
168
184
|
setDeployAttribute("deploy.signer.mode", "pool");
|
|
@@ -175,8 +191,12 @@ async function getProvider() {
|
|
|
175
191
|
}
|
|
176
192
|
}
|
|
177
193
|
async function getDirectProvider(mnemonic, derivationPath = "") {
|
|
178
|
-
|
|
179
|
-
|
|
194
|
+
const primary = BULLETIN_ENDPOINTS[0];
|
|
195
|
+
console.log(` Connecting to Bulletin: ${primary}`);
|
|
196
|
+
const client = createPolkadotClient(withPolkadotSdkCompat(getWsProvider(
|
|
197
|
+
BULLETIN_ENDPOINTS,
|
|
198
|
+
{ heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS, onStatusChanged: makeBulletinStatusHandler(primary) }
|
|
199
|
+
)));
|
|
180
200
|
const unsafeApi = client.getUnsafeApi();
|
|
181
201
|
const { signer, ss58 } = deriveRootSigner(mnemonic, derivationPath);
|
|
182
202
|
console.log(` Using direct signer: ${ss58}${derivationPath ? ` (path: ${derivationPath})` : ""}`);
|
|
@@ -195,8 +215,9 @@ async function getDirectProvider(mnemonic, derivationPath = "") {
|
|
|
195
215
|
return { client, unsafeApi, signer, ss58 };
|
|
196
216
|
}
|
|
197
217
|
var MAX_BEST_CHAIN_DROPS = 5;
|
|
198
|
-
function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction", rpc, senderSS58, expectedNonce, timeoutMs } = {}) {
|
|
218
|
+
function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction", rpc, senderSS58, expectedNonce, timeoutMs, fetchNonce: fetchNonceOverride } = {}) {
|
|
199
219
|
const timeout = timeoutMs ?? TX_TIMEOUT_MS;
|
|
220
|
+
const _fetchNonce = fetchNonceOverride ?? fetchNonce;
|
|
200
221
|
return new Promise((resolve2, reject) => {
|
|
201
222
|
let settled = false;
|
|
202
223
|
let sub;
|
|
@@ -211,14 +232,15 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
|
|
|
211
232
|
}
|
|
212
233
|
fn(...args);
|
|
213
234
|
};
|
|
214
|
-
const tryNonceFallback = async () => {
|
|
235
|
+
const tryNonceFallback = async (subscriptionErrored) => {
|
|
215
236
|
if (!rpc || !senderSS58 || expectedNonce == null) return false;
|
|
216
237
|
try {
|
|
217
|
-
const
|
|
238
|
+
const endpoints = Array.isArray(rpc) ? rpc : [rpc];
|
|
239
|
+
const verified = await verifyNonceAdvanced(endpoints, senderSS58, expectedNonce);
|
|
218
240
|
if (settled) return true;
|
|
219
|
-
if (
|
|
220
|
-
console.log(` ${label}: nonce advanced
|
|
221
|
-
settle(resolve2)({ value: onSuccess(), viaFallback: true });
|
|
241
|
+
if (verified.advanced) {
|
|
242
|
+
console.log(` ${label}: nonce advanced past ${expectedNonce} (witnessed by ${verified.witnessRpc}), tx was included`);
|
|
243
|
+
settle(resolve2)({ value: onSuccess(), viaFallback: true, subscriptionError: subscriptionErrored });
|
|
222
244
|
return true;
|
|
223
245
|
}
|
|
224
246
|
} catch (e) {
|
|
@@ -229,7 +251,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
|
|
|
229
251
|
};
|
|
230
252
|
const timer = setTimeout(async () => {
|
|
231
253
|
if (settled) return;
|
|
232
|
-
if (await tryNonceFallback()) return;
|
|
254
|
+
if (await tryNonceFallback(false)) return;
|
|
233
255
|
settle(reject)(new Error(`${label} timed out after ${timeout / 1e3}s waiting for block confirmation`));
|
|
234
256
|
}, timeout);
|
|
235
257
|
sub = tx.signSubmitAndWatch(signer, txOpts).subscribe({
|
|
@@ -237,7 +259,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
|
|
|
237
259
|
if (event.type === "txBestBlocksState") {
|
|
238
260
|
if (event.found) {
|
|
239
261
|
if (event.ok) {
|
|
240
|
-
settle(resolve2)({ value: onSuccess(event), viaFallback: false });
|
|
262
|
+
settle(resolve2)({ value: onSuccess(event), viaFallback: false, subscriptionError: false });
|
|
241
263
|
} else {
|
|
242
264
|
settle(reject)(new Error(`${label} dispatch error`));
|
|
243
265
|
}
|
|
@@ -245,7 +267,7 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
|
|
|
245
267
|
dropCount++;
|
|
246
268
|
if (dropCount >= MAX_BEST_CHAIN_DROPS) {
|
|
247
269
|
console.log(` ${label}: tx dropped ${dropCount} times, checking nonce...`);
|
|
248
|
-
if (await tryNonceFallback()) return;
|
|
270
|
+
if (await tryNonceFallback(true)) return;
|
|
249
271
|
settle(reject)(new Error(`${label} tx dropped from best chain ${dropCount} times`));
|
|
250
272
|
} else {
|
|
251
273
|
console.log(` ${label}: tx dropped from best chain (${dropCount}/${MAX_BEST_CHAIN_DROPS}), waiting...`);
|
|
@@ -260,16 +282,16 @@ function watchTransaction(tx, signer, txOpts, onSuccess, { label = "transaction"
|
|
|
260
282
|
});
|
|
261
283
|
});
|
|
262
284
|
}
|
|
263
|
-
async function storeChunk(unsafeApi, signer, chunkBytes, nonce, ss58) {
|
|
285
|
+
async function storeChunk(unsafeApi, signer, chunkBytes, nonce, ss58, opts = {}) {
|
|
264
286
|
const hashCode = 18;
|
|
265
287
|
const cid = createCID(chunkBytes, CID_CONFIG.codec, hashCode);
|
|
266
288
|
const tx = unsafeApi.tx.TransactionStorage.store_with_cid_config({ cid: { codec: BigInt(CID_CONFIG.codec), hashing: toHashingEnum(hashCode) }, data: Binary.fromBytes(chunkBytes) });
|
|
267
|
-
const txOpts = { mortality: { mortal: true, period:
|
|
268
|
-
const { value, viaFallback } = await watchTransaction(tx, signer, txOpts, () => {
|
|
289
|
+
const txOpts = { mortality: { mortal: true, period: CHUNK_MORTALITY_PERIOD }, nonce };
|
|
290
|
+
const { value, viaFallback, subscriptionError } = await watchTransaction(tx, signer, txOpts, () => {
|
|
269
291
|
console.log(` CID: ${cid.toString()}`);
|
|
270
292
|
return { cid, len: chunkBytes.length };
|
|
271
|
-
}, { label: `chunk(nonce:${nonce})`, rpc:
|
|
272
|
-
return { ...value, viaFallback };
|
|
293
|
+
}, { label: `chunk(nonce:${nonce})`, rpc: BULLETIN_ENDPOINTS, senderSS58: ss58, expectedNonce: nonce, timeoutMs: CHUNK_TIMEOUT_MS, fetchNonce: opts.fetchNonce });
|
|
294
|
+
return { ...value, viaFallback, subscriptionError };
|
|
273
295
|
}
|
|
274
296
|
async function storeFile(contentBytes, { client: existingClient, unsafeApi: existingApi, signer: existingSigner } = {}) {
|
|
275
297
|
console.log(`
|
|
@@ -305,7 +327,8 @@ async function storeFile(contentBytes, { client: existingClient, unsafeApi: exis
|
|
|
305
327
|
throw e;
|
|
306
328
|
}
|
|
307
329
|
}
|
|
308
|
-
async function storeChunkedContent(chunks, { client: existingClient, unsafeApi: existingApi, signer: existingSigner, ss58: existingSS58, reconnect } = {}) {
|
|
330
|
+
async function storeChunkedContent(chunks, { client: existingClient, unsafeApi: existingApi, signer: existingSigner, ss58: existingSS58, reconnect, fetchNonce: fetchNonceOverride } = {}) {
|
|
331
|
+
const _fetchNonce = fetchNonceOverride ?? fetchNonce;
|
|
309
332
|
console.log(`
|
|
310
333
|
Chunks: ${chunks.length}`);
|
|
311
334
|
const totalBytes = chunks.reduce((s, c) => s + c.length, 0);
|
|
@@ -337,7 +360,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
337
360
|
Account has insufficient authorization for this upload (need ${requiredTxs} txs / ${(totalBytes / 1e6).toFixed(1)}MB, have ${txsRemaining} txs / ${Number(bytesRemaining) / 1e6}MB)`);
|
|
338
361
|
console.log(` Attempting to re-authorize with Alice...`);
|
|
339
362
|
try {
|
|
340
|
-
await ensureAuthorized(unsafeApi, ss58,
|
|
363
|
+
await ensureAuthorized(unsafeApi, ss58, BULLETIN_ENDPOINTS[0], void 0, { txs: requiredTxs, bytes: requiredBytes });
|
|
341
364
|
console.log(` Re-authorization successful`);
|
|
342
365
|
} catch (e) {
|
|
343
366
|
throw new NonRetryableError(`Account ${ss58} has insufficient Bulletin authorization quota and auto-authorization via Alice failed (${e.message}). Authorize the account on-chain.`);
|
|
@@ -368,7 +391,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
368
391
|
sampleMemory(`reconnect_${reconnectionsUsed}_after`);
|
|
369
392
|
}
|
|
370
393
|
try {
|
|
371
|
-
let startNonce = await
|
|
394
|
+
let startNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
|
|
372
395
|
console.log(` Starting nonce: ${startNonce}`);
|
|
373
396
|
const BATCH_SIZE = 2;
|
|
374
397
|
const MAX_CHUNK_RETRIES = 3;
|
|
@@ -394,7 +417,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
394
417
|
}
|
|
395
418
|
const nonce = assignedNonces.get(i);
|
|
396
419
|
console.log(` [${i + 1}/${chunks.length}] ${(chunkData.length / 1024 / 1024).toFixed(2)} MB (nonce: ${nonce})`);
|
|
397
|
-
return storeChunk(unsafeApi, signer, chunkData, nonce, ss58);
|
|
420
|
+
return storeChunk(unsafeApi, signer, chunkData, nonce, ss58, { fetchNonce: fetchNonceOverride });
|
|
398
421
|
});
|
|
399
422
|
const results = await Promise.allSettled(batchPromises);
|
|
400
423
|
results.forEach((r, j) => {
|
|
@@ -404,11 +427,11 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
404
427
|
}
|
|
405
428
|
});
|
|
406
429
|
const failures = results.map((r, j) => r.status === "rejected" ? { index: batchIndices[j], chunkData: batchChunks[j], error: r.reason } : null).filter(Boolean);
|
|
407
|
-
const
|
|
408
|
-
const needsReconnect = failures.some((f) => isConnectionError(f.error)) ||
|
|
430
|
+
const subscriptionErrored = results.some((r) => r.status === "fulfilled" && r.value.subscriptionError);
|
|
431
|
+
const needsReconnect = failures.some((f) => isConnectionError(f.error)) || subscriptionErrored;
|
|
409
432
|
if (needsReconnect && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
|
|
410
433
|
await doReconnect();
|
|
411
|
-
const currentNonce = await
|
|
434
|
+
const currentNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
|
|
412
435
|
for (const idx of batchIndices) {
|
|
413
436
|
const chunkNonce = assignedNonces.get(idx);
|
|
414
437
|
if (chunkNonce !== void 0 && chunkNonce < currentNonce && stored[idx] === null) {
|
|
@@ -439,7 +462,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
439
462
|
}
|
|
440
463
|
}
|
|
441
464
|
try {
|
|
442
|
-
const currentNonce = await
|
|
465
|
+
const currentNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
|
|
443
466
|
const originalNonce = assignedNonces.get(fail.index);
|
|
444
467
|
if (originalNonce !== void 0 && originalNonce < currentNonce) {
|
|
445
468
|
console.log(` Chunk ${fail.index + 1}: nonce ${originalNonce} consumed (current=${currentNonce}), treating as included`);
|
|
@@ -449,7 +472,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
449
472
|
break;
|
|
450
473
|
}
|
|
451
474
|
const retryNonce = originalNonce ?? currentNonce;
|
|
452
|
-
const result2 = await storeChunk(unsafeApi, signer, fail.chunkData, retryNonce, ss58);
|
|
475
|
+
const result2 = await storeChunk(unsafeApi, signer, fail.chunkData, retryNonce, ss58, { fetchNonce: fetchNonceOverride });
|
|
453
476
|
stored[fail.index] = result2;
|
|
454
477
|
assignedNonces.delete(fail.index);
|
|
455
478
|
retried = true;
|
|
@@ -495,7 +518,7 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
495
518
|
const MAX_ROOT_RETRIES = 3;
|
|
496
519
|
let result;
|
|
497
520
|
for (let rootAttempt = 1; rootAttempt <= MAX_ROOT_RETRIES; rootAttempt++) {
|
|
498
|
-
const rootNonce = await
|
|
521
|
+
const rootNonce = await _fetchNonce(BULLETIN_ENDPOINTS, ss58);
|
|
499
522
|
console.log(` Storing root node (nonce: ${rootNonce})...`);
|
|
500
523
|
const rootTx = unsafeApi.tx.TransactionStorage.store_with_cid_config({ cid: { codec: BigInt(112), hashing: toHashingEnum(hashCode) }, data: Binary.fromBytes(dagBytes) });
|
|
501
524
|
const rootTxOpts = { mortality: { mortal: true, period: 256 }, nonce: rootNonce };
|
|
@@ -504,9 +527,9 @@ async function storeChunkedContent(chunks, { client: existingClient, unsafeApi:
|
|
|
504
527
|
console.log(` Root CID: ${rootCid.toString()}
|
|
505
528
|
`);
|
|
506
529
|
return rootCid.toString();
|
|
507
|
-
}, { label: "root-node", rpc:
|
|
530
|
+
}, { label: "root-node", rpc: BULLETIN_ENDPOINTS, senderSS58: ss58, expectedNonce: rootNonce, timeoutMs: CHUNK_TIMEOUT_MS, fetchNonce: fetchNonceOverride });
|
|
508
531
|
result = watchResult.value;
|
|
509
|
-
if (watchResult.
|
|
532
|
+
if (watchResult.subscriptionError && reconnect && reconnectionsUsed < MAX_RECONNECTIONS) {
|
|
510
533
|
await doReconnect();
|
|
511
534
|
}
|
|
512
535
|
break;
|
|
@@ -665,12 +688,14 @@ async function estimateUploadBytes(content) {
|
|
|
665
688
|
}
|
|
666
689
|
}
|
|
667
690
|
async function deploy(content, domainName = null, options = {}) {
|
|
668
|
-
|
|
691
|
+
const userRpc = options.rpc ?? process.env.BULLETIN_RPC;
|
|
692
|
+
BULLETIN_ENDPOINTS = userRpc ? [userRpc, ...[DEFAULT_BULLETIN_RPC].filter((e) => e !== userRpc)] : [DEFAULT_BULLETIN_RPC];
|
|
693
|
+
_deployRpcFailedOver = false;
|
|
669
694
|
POOL_SIZE = options.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
|
|
670
695
|
initTelemetry();
|
|
671
696
|
const randomSuffix = Math.floor(Math.random() * 100).toString().padStart(2, "0");
|
|
672
|
-
const
|
|
673
|
-
const name =
|
|
697
|
+
const parsed = domainName ? parseDomainName(domainName) : null;
|
|
698
|
+
const name = parsed ? parsed.label : `test-domain-${Date.now().toString(36)}${randomSuffix}`;
|
|
674
699
|
return withDeploySpan(name, async () => {
|
|
675
700
|
const deployTag = options.tag ?? process.env.DEPLOY_TAG;
|
|
676
701
|
if (deployTag) {
|
|
@@ -714,26 +739,43 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
714
739
|
);
|
|
715
740
|
}
|
|
716
741
|
console.log(` Account: mapped`);
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
742
|
+
if (parsed?.isSubdomain) {
|
|
743
|
+
try {
|
|
744
|
+
const { owned: subOwned } = await preflight.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
|
|
745
|
+
if (!subOwned) {
|
|
746
|
+
const { owned: parentOwned, owner: parentOwner } = await preflight.checkOwnership(parsed.parentLabel);
|
|
747
|
+
if (!parentOwned) {
|
|
748
|
+
throw new NonRetryableError(
|
|
749
|
+
`Cannot deploy ${parsed.fullName}: parent ${parsed.parentLabel}.dot is owned by ${parentOwner ?? "no one"}, not by this signer.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
} finally {
|
|
754
|
+
preflight.disconnect();
|
|
755
|
+
}
|
|
756
|
+
console.log(` Mode: subdomain (parent ${parsed.parentLabel}.dot owned by signer)`);
|
|
757
|
+
} else {
|
|
758
|
+
let dotnsPreflight;
|
|
759
|
+
try {
|
|
760
|
+
dotnsPreflight = await preflight.preflight(name);
|
|
761
|
+
} finally {
|
|
762
|
+
preflight.disconnect();
|
|
763
|
+
}
|
|
764
|
+
console.log(` DotNS: ${name}.dot classifies as ${popStatusName(dotnsPreflight.classification.status)}`);
|
|
765
|
+
if (dotnsPreflight.canProceed) {
|
|
766
|
+
const fromName = popStatusName(dotnsPreflight.userStatus);
|
|
767
|
+
if (dotnsPreflight.needsPopUpgrade && dotnsPreflight.targetPopStatus !== void 0) {
|
|
768
|
+
console.log(` PoP: ${fromName} \u2192 will upgrade to ${popStatusName(dotnsPreflight.targetPopStatus)}`);
|
|
769
|
+
} else {
|
|
770
|
+
console.log(` PoP: ${fromName} (no upgrade required)`);
|
|
771
|
+
}
|
|
772
|
+
console.log(` Domain: ${dotnsPreflight.plannedAction === "already-owned-by-us" ? "owned by you" : "available"}`);
|
|
773
|
+
}
|
|
774
|
+
if (!dotnsPreflight.canProceed) {
|
|
775
|
+
throw new NonRetryableError(
|
|
776
|
+
dotnsPreflight.reason ?? "DotNS preflight rejected the deploy; please check the label and signer."
|
|
777
|
+
);
|
|
730
778
|
}
|
|
731
|
-
console.log(` Domain: ${dotnsPreflight.plannedAction === "already-owned-by-us" ? "owned by you" : "available"}`);
|
|
732
|
-
}
|
|
733
|
-
if (!dotnsPreflight.canProceed) {
|
|
734
|
-
throw new NonRetryableError(
|
|
735
|
-
dotnsPreflight.reason ?? "DotNS preflight rejected the deploy; please check the label and signer."
|
|
736
|
-
);
|
|
737
779
|
}
|
|
738
780
|
provider = await reconnect();
|
|
739
781
|
const providerWithReconnect = { ...provider, reconnect };
|
|
@@ -745,7 +787,7 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
745
787
|
const uploadBytes = Math.ceil(estimated * 1.2);
|
|
746
788
|
const chunksNeeded = Math.max(1, Math.ceil(uploadBytes / CHUNK_SIZE));
|
|
747
789
|
const needs = { txs: BigInt(chunksNeeded + 2), bytes: BigInt(uploadBytes) };
|
|
748
|
-
await topUpBy(provider.ss58,
|
|
790
|
+
await topUpBy(provider.ss58, BULLETIN_ENDPOINTS[0], needs, "uploader");
|
|
749
791
|
}
|
|
750
792
|
console.log("\n" + "=".repeat(60));
|
|
751
793
|
console.log("Storage");
|
|
@@ -791,7 +833,7 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
791
833
|
carBytes,
|
|
792
834
|
cid: predictedCid,
|
|
793
835
|
toolVersion: VERSION,
|
|
794
|
-
bulletinRpc:
|
|
836
|
+
bulletinRpc: BULLETIN_ENDPOINTS[0],
|
|
795
837
|
encrypted: Boolean(options.password)
|
|
796
838
|
}).catch((err) => err instanceof Error ? err : new Error(String(err)));
|
|
797
839
|
}
|
|
@@ -844,17 +886,31 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
844
886
|
console.log("\n" + "=".repeat(60));
|
|
845
887
|
console.log("DotNS");
|
|
846
888
|
console.log("=".repeat(60));
|
|
847
|
-
await withSpan("deploy.dotns", "2. dotns", { "deploy.domain": name }, async () => {
|
|
889
|
+
await withSpan("deploy.dotns", "2. dotns", { "deploy.domain": name, "deploy.subdomain": String(parsed?.isSubdomain ?? false) }, async () => {
|
|
848
890
|
const dotns = new DotNS();
|
|
849
891
|
await dotns.connect(
|
|
850
892
|
options.signer && options.signerAddress ? { signer: options.signer, signerAddress: options.signerAddress } : options.mnemonic ? { mnemonic: options.mnemonic, derivationPath: options.derivationPath } : {}
|
|
851
893
|
);
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
894
|
+
if (parsed?.isSubdomain) {
|
|
895
|
+
const { owned, owner } = await dotns.checkSubdomainOwnership(parsed.sublabel, parsed.parentLabel);
|
|
896
|
+
if (owned) {
|
|
897
|
+
console.log(` Status: Already owned`);
|
|
898
|
+
} else if (owner) {
|
|
899
|
+
throw new Error(`Subdomain ${parsed.fullName} is owned by ${owner}, not ${dotns.evmAddress}`);
|
|
900
|
+
} else {
|
|
901
|
+
const parentOwnership = await dotns.checkOwnership(parsed.parentLabel);
|
|
902
|
+
if (!parentOwnership.owned) throw new Error(`You must own ${parsed.parentLabel}.dot to register subdomains under it`);
|
|
903
|
+
console.log(` Status: Registering subdomain...`);
|
|
904
|
+
await dotns.registerSubdomain(parsed.sublabel, parsed.parentLabel);
|
|
905
|
+
}
|
|
855
906
|
} else {
|
|
856
|
-
|
|
857
|
-
|
|
907
|
+
const { owned } = await dotns.checkOwnership(name);
|
|
908
|
+
if (owned) {
|
|
909
|
+
console.log(` Status: Already owned`);
|
|
910
|
+
} else {
|
|
911
|
+
console.log(` Status: Registering...`);
|
|
912
|
+
await dotns.register(name);
|
|
913
|
+
}
|
|
858
914
|
}
|
|
859
915
|
const contenthashHex = `0x${encodeContenthash(cid)}`;
|
|
860
916
|
await dotns.setContenthash(name, contenthashHex);
|
|
@@ -908,6 +964,7 @@ async function deploy(content, domainName = null, options = {}) {
|
|
|
908
964
|
console.log("\n" + "=".repeat(60) + "\n");
|
|
909
965
|
return { domainName: name, fullDomain: `${name}.dot`, cid, ipfsCid };
|
|
910
966
|
} finally {
|
|
967
|
+
if (_deployRpcFailedOver) setDeployAttribute("deploy.rpc.failed_over", "true");
|
|
911
968
|
provider?.client.destroy();
|
|
912
969
|
}
|
|
913
970
|
});
|
|
@@ -919,6 +976,7 @@ export {
|
|
|
919
976
|
friendlyChainError,
|
|
920
977
|
DEFAULT_BULLETIN_RPC,
|
|
921
978
|
DEFAULT_POOL_SIZE,
|
|
979
|
+
CHUNK_MORTALITY_PERIOD,
|
|
922
980
|
isConnectionError,
|
|
923
981
|
deriveRootSigner,
|
|
924
982
|
createCID,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/run-state.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
|
|
6
|
+
// package.json
|
|
7
|
+
var package_default = {
|
|
8
|
+
name: "bulletin-deploy",
|
|
9
|
+
version: "0.7.4",
|
|
10
|
+
private: false,
|
|
11
|
+
repository: {
|
|
12
|
+
type: "git",
|
|
13
|
+
url: "https://github.com/paritytech/bulletin-deploy.git"
|
|
14
|
+
},
|
|
15
|
+
publishConfig: {
|
|
16
|
+
registry: "https://registry.npmjs.org",
|
|
17
|
+
access: "public"
|
|
18
|
+
},
|
|
19
|
+
type: "module",
|
|
20
|
+
main: "./dist/index.js",
|
|
21
|
+
types: "./dist/index.d.ts",
|
|
22
|
+
bin: {
|
|
23
|
+
"bulletin-deploy": "./bin/bulletin-deploy"
|
|
24
|
+
},
|
|
25
|
+
exports: {
|
|
26
|
+
".": {
|
|
27
|
+
types: "./dist/index.d.ts",
|
|
28
|
+
import: "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
files: [
|
|
32
|
+
"dist",
|
|
33
|
+
"bin"
|
|
34
|
+
],
|
|
35
|
+
scripts: {
|
|
36
|
+
build: "tsup src/index.ts src/deploy.ts src/dotns.ts src/pool.ts src/telemetry.ts src/memory-report.ts src/merkle.ts src/gh-pages-mirror.ts src/version-check.ts src/bug-report.ts src/run-state.ts --format esm --dts --clean --target node22",
|
|
37
|
+
prepare: "npm run build",
|
|
38
|
+
test: "npm run build && node --test test/test.js test/pool.test.js test/helpers/e2e-helpers.test.js",
|
|
39
|
+
"test:e2e": "npm run build && node --test test/e2e.test.js",
|
|
40
|
+
"test:e2e:smoke": "bash scripts/e2e-pass.sh smoke",
|
|
41
|
+
"test:e2e:pr": "bash scripts/e2e-pass.sh pr",
|
|
42
|
+
"test:e2e:nightly": "bash scripts/e2e-pass.sh nightly",
|
|
43
|
+
benchmark: "npm run build && node benchmark.js"
|
|
44
|
+
},
|
|
45
|
+
dependencies: {
|
|
46
|
+
"@ipld/car": "^5.4.3",
|
|
47
|
+
"@ipld/dag-pb": "^4.1.3",
|
|
48
|
+
"@noble/hashes": "^1.7.2",
|
|
49
|
+
"@parity/dotns-cli": "0.5.6",
|
|
50
|
+
"@polkadot-api/substrate-bindings": "^0.16.5",
|
|
51
|
+
"@polkadot-labs/hdkd": "^0.0.25",
|
|
52
|
+
"@polkadot-labs/hdkd-helpers": "^0.0.26",
|
|
53
|
+
"@polkadot/keyring": "^13.0.0",
|
|
54
|
+
"@polkadot/util-crypto": "^13.0.0",
|
|
55
|
+
"@sentry/node": "^9.14.0",
|
|
56
|
+
"ipfs-unixfs": "^11.2.0",
|
|
57
|
+
"ipfs-unixfs-importer": "^16.1.4",
|
|
58
|
+
multiformats: "^13.4.1",
|
|
59
|
+
"polkadot-api": "^1.23.1",
|
|
60
|
+
viem: "^2.30.5"
|
|
61
|
+
},
|
|
62
|
+
devDependencies: {
|
|
63
|
+
"@types/node": "^22.0.0",
|
|
64
|
+
tsup: "^8.5.0",
|
|
65
|
+
typescript: "^5.9.3"
|
|
66
|
+
},
|
|
67
|
+
minimumVersion: "0.5.6",
|
|
68
|
+
engines: {
|
|
69
|
+
node: ">=22"
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/run-state.ts
|
|
74
|
+
var VERSION = package_default.version;
|
|
75
|
+
function resolveStateDir() {
|
|
76
|
+
if (process.platform === "darwin") {
|
|
77
|
+
return path.join(os.homedir(), "Library", "Application Support", "bulletin-deploy");
|
|
78
|
+
}
|
|
79
|
+
if (process.platform === "win32") {
|
|
80
|
+
const base2 = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
|
|
81
|
+
return path.join(base2, "bulletin-deploy");
|
|
82
|
+
}
|
|
83
|
+
const base = process.env.XDG_STATE_HOME && process.env.XDG_STATE_HOME.length > 0 ? process.env.XDG_STATE_HOME : path.join(os.homedir(), ".local", "state");
|
|
84
|
+
return path.join(base, "bulletin-deploy");
|
|
85
|
+
}
|
|
86
|
+
function stateFilePath() {
|
|
87
|
+
return path.join(resolveStateDir(), "last-run.json");
|
|
88
|
+
}
|
|
89
|
+
function loadRunState() {
|
|
90
|
+
try {
|
|
91
|
+
const raw = fs.readFileSync(stateFilePath(), "utf-8");
|
|
92
|
+
const parsed = JSON.parse(raw);
|
|
93
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
94
|
+
return parsed;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function writeRunState(patch) {
|
|
100
|
+
try {
|
|
101
|
+
const dir = resolveStateDir();
|
|
102
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
103
|
+
const file = stateFilePath();
|
|
104
|
+
let existing = {};
|
|
105
|
+
try {
|
|
106
|
+
const raw = fs.readFileSync(file, "utf-8");
|
|
107
|
+
const parsed = JSON.parse(raw);
|
|
108
|
+
if (parsed && typeof parsed === "object") existing = parsed;
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
const merged = { ...existing, ...patch };
|
|
112
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
113
|
+
fs.writeFileSync(tmp, JSON.stringify(merged), { encoding: "utf-8" });
|
|
114
|
+
fs.renameSync(tmp, file);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function isPidAlive(pid) {
|
|
119
|
+
try {
|
|
120
|
+
process.kill(pid, 0);
|
|
121
|
+
return true;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const code = err.code;
|
|
124
|
+
if (code === "EPERM") return true;
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function shouldSkipStaleWarning(prev) {
|
|
129
|
+
if (prev.pid && isPidAlive(prev.pid)) return true;
|
|
130
|
+
if (prev.toolVersion !== VERSION) return true;
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
function probablyOomRssMb(override) {
|
|
134
|
+
if (typeof override === "number" && Number.isFinite(override)) return override;
|
|
135
|
+
const env = process.env.BULLETIN_DEPLOY_OOM_HINT_RSS_MB;
|
|
136
|
+
const parsed = env != null ? Number(env) : NaN;
|
|
137
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
138
|
+
return 1800;
|
|
139
|
+
}
|
|
140
|
+
function shouldShowOomHint(prev) {
|
|
141
|
+
if (prev.lastPeakRssMb == null) return false;
|
|
142
|
+
return prev.lastPeakRssMb >= probablyOomRssMb();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
package_default,
|
|
147
|
+
VERSION,
|
|
148
|
+
resolveStateDir,
|
|
149
|
+
stateFilePath,
|
|
150
|
+
loadRunState,
|
|
151
|
+
writeRunState,
|
|
152
|
+
shouldSkipStaleWarning,
|
|
153
|
+
probablyOomRssMb,
|
|
154
|
+
shouldShowOomHint
|
|
155
|
+
};
|