@parity/product-deploy 0.8.1 → 0.8.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -230
- package/bin/bulletin-deploy +15 -1
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-KB3EII7F.js → chunk-4Y4ZBN45.js} +1 -1
- package/dist/{chunk-INVA3XGG.js → chunk-5EJ247OO.js} +88 -77
- package/dist/{chunk-IRT4A7OO.js → chunk-7GYCJPFI.js} +128 -22
- package/dist/{chunk-MXMVZU2Q.js → chunk-CSDXTU3G.js} +2 -2
- package/dist/{chunk-L2SKSKB6.js → chunk-J3NIXHZZ.js} +108 -0
- package/dist/{chunk-RW3GWDGI.js → chunk-JQ5X3VMP.js} +15 -102
- package/dist/{chunk-V5N5EYNV.js → chunk-N27JUWU2.js} +6 -3
- package/dist/{chunk-R2CJ5I5R.js → chunk-PIGHAAM2.js} +3 -3
- package/dist/{chunk-O2BFXY3Y.js → chunk-R2ORPNZC.js} +1 -1
- package/dist/{chunk-WPJADKC7.js → chunk-RRYHCOOJ.js} +75 -33
- package/dist/{chunk-CNPB4VAM.js → chunk-XFX4VODU.js} +65 -0
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +38 -1
- package/dist/deploy.js +16 -10
- package/dist/dotns.d.ts +33 -2
- package/dist/dotns.js +9 -5
- package/dist/environments.d.ts +15 -2
- package/dist/environments.js +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -11
- package/dist/manifest/publish.js +11 -11
- package/dist/manifest-fetch.d.ts +22 -1
- package/dist/manifest-fetch.js +7 -1
- package/dist/manifest-roundtrip.js +1 -1
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +10 -10
- package/dist/personhood/bootstrap.js +5 -5
- package/dist/personhood/people-client.js +5 -5
- package/dist/pool.d.ts +2 -12
- package/dist/pool.js +3 -11
- package/dist/run-state.js +1 -1
- package/dist/telemetry.js +2 -2
- package/dist/version-check.js +3 -3
- package/docs/bootstrap.md +1 -1
- package/package.json +6 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isTestnetSpecName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JQ5X3VMP.js";
|
|
4
4
|
import {
|
|
5
5
|
captureWarning,
|
|
6
6
|
markCodePath,
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
setDeploySentryTag,
|
|
9
9
|
truncateAddress,
|
|
10
10
|
withSpan
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5EJ247OO.js";
|
|
12
12
|
import {
|
|
13
13
|
validateContractAddresses
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-XFX4VODU.js";
|
|
15
15
|
import {
|
|
16
16
|
NonRetryableError
|
|
17
17
|
} from "./chunk-ZOC4GITL.js";
|
|
@@ -60,6 +60,10 @@ var FEE_FLOOR_REGISTER = ONE_PAS / 10n;
|
|
|
60
60
|
var TOP_UP_TARGET = ONE_PAS / 2n;
|
|
61
61
|
var SOURCE_BUFFER = ONE_PAS;
|
|
62
62
|
var MINIMUM_REGISTER_STORAGE_DEPOSIT = 2000000000000n;
|
|
63
|
+
var REGISTER_RENT_PRICE_WEI = 10n * 10n ** 18n;
|
|
64
|
+
function bufferedRentNative(nativeToEthRatio) {
|
|
65
|
+
return REGISTER_RENT_PRICE_WEI * 110n / (100n * nativeToEthRatio);
|
|
66
|
+
}
|
|
63
67
|
var REPROVE_FEE_ESTIMATE = ONE_PAS / 100n;
|
|
64
68
|
var REPROVE_FEE_SAFETY_MARGIN_PCT = 110n;
|
|
65
69
|
var TOP_UP_TRANSFER_TIMEOUT_MS = 6e4;
|
|
@@ -74,13 +78,13 @@ function resolveNativeTokenSymbol(envId) {
|
|
|
74
78
|
if (envId.includes("rococo")) return "ROC";
|
|
75
79
|
return "PAS";
|
|
76
80
|
}
|
|
77
|
-
function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
|
|
81
|
+
function feeFloorFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n) {
|
|
78
82
|
if (plannedAction === "already-owned-by-us") return FEE_FLOOR_OWNED;
|
|
79
|
-
return FEE_FLOOR_REGISTER + storageDeposit;
|
|
83
|
+
return FEE_FLOOR_REGISTER + storageDeposit + rentPriceNative;
|
|
80
84
|
}
|
|
81
|
-
function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT) {
|
|
85
|
+
function topUpTargetFor(plannedAction, storageDeposit = MINIMUM_REGISTER_STORAGE_DEPOSIT, rentPriceNative = 0n) {
|
|
82
86
|
if (plannedAction === "already-owned-by-us") return TOP_UP_TARGET;
|
|
83
|
-
return TOP_UP_TARGET + storageDeposit;
|
|
87
|
+
return TOP_UP_TARGET + storageDeposit + rentPriceNative;
|
|
84
88
|
}
|
|
85
89
|
var RPC_ENDPOINTS = [
|
|
86
90
|
"wss://asset-hub-paseo.dotters.network",
|
|
@@ -120,6 +124,30 @@ function classifyTxRetryDecision(err) {
|
|
|
120
124
|
if (lower.includes("transaction watcher silent")) return "retry";
|
|
121
125
|
return "abort";
|
|
122
126
|
}
|
|
127
|
+
var DOTNS_RETRY_BASE_MS = 400;
|
|
128
|
+
var DOTNS_RETRY_MAX_MS = 6e3;
|
|
129
|
+
function dotnsRetryBackoffMs(attempt, rand = Math.random) {
|
|
130
|
+
const ceil = Math.min(DOTNS_RETRY_BASE_MS * 2 ** (attempt - 1), DOTNS_RETRY_MAX_MS);
|
|
131
|
+
return Math.round(ceil * (0.5 + rand() * 0.5));
|
|
132
|
+
}
|
|
133
|
+
function makeRetryStatusFilter(sink) {
|
|
134
|
+
let buffered = false;
|
|
135
|
+
return {
|
|
136
|
+
callback: (status) => {
|
|
137
|
+
if (status === "failed") {
|
|
138
|
+
buffered = true;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
sink(status);
|
|
142
|
+
},
|
|
143
|
+
flush: () => {
|
|
144
|
+
if (buffered) sink("failed");
|
|
145
|
+
},
|
|
146
|
+
reset: () => {
|
|
147
|
+
buffered = false;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
123
151
|
var DEFAULT_MNEMONIC = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
124
152
|
var _rpcIdCounter = 0;
|
|
125
153
|
async function fetchNonceFromEndpoint(rpc, ss58Address) {
|
|
@@ -533,7 +561,13 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
533
561
|
async getEvmAddress(substrateAddress) {
|
|
534
562
|
if (isAddress(substrateAddress)) return substrateAddress;
|
|
535
563
|
const address = await this.client.apis.ReviveApi.address(substrateAddress);
|
|
536
|
-
|
|
564
|
+
const hex = convertToHexString(address);
|
|
565
|
+
if (!hex || hex === "0x") {
|
|
566
|
+
throw new Error(
|
|
567
|
+
"ReviveApi.address returned empty result \u2014 RPC node may not support pallet-revive; try a different endpoint via DOTNS_RPC"
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
return hex;
|
|
537
571
|
}
|
|
538
572
|
async performDryRunCall(originSubstrateAddress, contractAddress, value, encodedData) {
|
|
539
573
|
if (isAddress(originSubstrateAddress)) throw new Error("performDryRunCall requires SS58 Substrate address, not EVM H160 address");
|
|
@@ -554,6 +588,21 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
554
588
|
if (!result.result.isOk) return { success: false, gasConsumed: result.gasConsumed, storageDeposit: result.storageDeposit.value, gasRequired: result.gasRequired, revertData: result.result.value.data, revertFlags: result.result.value.flags };
|
|
555
589
|
return { success: true, gasConsumed: result.gasConsumed, storageDeposit: result.storageDeposit.value, gasRequired: result.gasRequired };
|
|
556
590
|
}
|
|
591
|
+
// Returns true if the address holds contract code, false if it provably does
|
|
592
|
+
// not, and null if this runtime doesn't expose the storage map (caller must
|
|
593
|
+
// not treat null as "no code"). Used to disambiguate an empty-success (`0x`)
|
|
594
|
+
// contract read: on pallet-revive, calling an address with no code succeeds
|
|
595
|
+
// with empty return data, so empty `0x` from a read almost always means the
|
|
596
|
+
// configured contract address is wrong/undeployed rather than a real "unset".
|
|
597
|
+
async hasContractCode(address) {
|
|
598
|
+
try {
|
|
599
|
+
const info = await this.client.query.Revive.AccountInfoOf.getValue(address);
|
|
600
|
+
if (info === void 0 || info === null) return false;
|
|
601
|
+
return info.account_type?.type === "Contract";
|
|
602
|
+
} catch {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
557
606
|
async checkIfAccountMapped(substrateAddress) {
|
|
558
607
|
try {
|
|
559
608
|
const evmAddress = await this.getEvmAddress(substrateAddress);
|
|
@@ -699,18 +748,23 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
699
748
|
});
|
|
700
749
|
}
|
|
701
750
|
async signAndSubmitWithRetry(buildExtrinsic, signer, statusCallback, label, opts = {}) {
|
|
751
|
+
const filter = makeRetryStatusFilter(statusCallback);
|
|
702
752
|
let lastError;
|
|
703
753
|
for (let attempt = 1; attempt <= DOTNS_TX_MAX_ATTEMPTS; attempt++) {
|
|
754
|
+
filter.reset();
|
|
704
755
|
try {
|
|
705
|
-
return await this.signAndSubmitExtrinsic(buildExtrinsic(), signer,
|
|
756
|
+
return await this.signAndSubmitExtrinsic(buildExtrinsic(), signer, filter.callback, opts);
|
|
706
757
|
} catch (e) {
|
|
707
758
|
lastError = e;
|
|
708
759
|
const decision = classifyTxRetryDecision(e);
|
|
709
760
|
if (decision === "abort" || attempt === DOTNS_TX_MAX_ATTEMPTS) break;
|
|
761
|
+
const ms = dotnsRetryBackoffMs(attempt);
|
|
710
762
|
const short = (e?.message ?? String(e)).slice(0, 80);
|
|
711
|
-
console.log(` ${label}: attempt ${attempt}/${DOTNS_TX_MAX_ATTEMPTS} failed (${short}), retrying
|
|
763
|
+
console.log(` ${label}: attempt ${attempt}/${DOTNS_TX_MAX_ATTEMPTS} failed (${short}), retrying in ${ms}ms\u2026`);
|
|
764
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
712
765
|
}
|
|
713
766
|
}
|
|
767
|
+
filter.flush();
|
|
714
768
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
715
769
|
}
|
|
716
770
|
// Dry-runs one Revive.call and returns the chain-side limits the live
|
|
@@ -987,12 +1041,16 @@ var DotNS = class {
|
|
|
987
1041
|
);
|
|
988
1042
|
console.log(` H160 Address: ${this.evmAddress}`);
|
|
989
1043
|
} catch (e) {
|
|
990
|
-
|
|
1044
|
+
const inner = e.message?.slice(0, 200) ?? String(e).slice(0, 200);
|
|
1045
|
+
const rpcHint = inner.includes("timed out") ? `; RPC: ${rpc} \u2014 retry or set DOTNS_RPC to another endpoint` : "";
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
`DotNS connect: failed to resolve EVM address from ${this.substrateAddress} via ReviveApi.address (${inner})${rpcHint}`
|
|
1048
|
+
);
|
|
991
1049
|
}
|
|
992
1050
|
setDeployAttribute("deploy.dotns.rpc_used", rpc);
|
|
993
1051
|
setDeployAttribute("deploy.dotns.evm_address", this.evmAddress);
|
|
994
1052
|
this.connected = true;
|
|
995
|
-
|
|
1053
|
+
await this.resolveNativeToEthRatio(options);
|
|
996
1054
|
try {
|
|
997
1055
|
await this.ensureMappedAccountReady(options.autoAccountMapping ?? false);
|
|
998
1056
|
} catch (e) {
|
|
@@ -1092,6 +1150,35 @@ var DotNS = class {
|
|
|
1092
1150
|
ensureConnected() {
|
|
1093
1151
|
if (!this.connected) throw new Error("Not connected. Call connect() first.");
|
|
1094
1152
|
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Resolve the authoritative nativeToEthRatio for this session.
|
|
1155
|
+
*
|
|
1156
|
+
* Priority: chain constant (Revive.NativeToEthRatio) > options.nativeToEthRatio > default.
|
|
1157
|
+
* On mismatch between the env-configured value and the chain value, logs a WARNING naming
|
|
1158
|
+
* both values and proceeds with the chain value (it is the source of truth).
|
|
1159
|
+
* On query failure, falls back to the configured/default value without throwing.
|
|
1160
|
+
*
|
|
1161
|
+
* Must be called after clientWrapper is established (i.e. inside connect()).
|
|
1162
|
+
*/
|
|
1163
|
+
async resolveNativeToEthRatio(options) {
|
|
1164
|
+
const configuredRatio = options.nativeToEthRatio ?? NATIVE_TO_ETH_RATIO;
|
|
1165
|
+
this._nativeToEthRatio = configuredRatio;
|
|
1166
|
+
if (!this.clientWrapper) return;
|
|
1167
|
+
try {
|
|
1168
|
+
const chainValue = await this.clientWrapper.client.constants.Revive.NativeToEthRatio();
|
|
1169
|
+
const chainRatio = BigInt(chainValue);
|
|
1170
|
+
if (chainRatio !== configuredRatio) {
|
|
1171
|
+
const msg = `DotNS: Revive.NativeToEthRatio from chain (${chainRatio}) differs from configured value (${configuredRatio}); using chain value`;
|
|
1172
|
+
console.warn(msg);
|
|
1173
|
+
captureWarning("nativeToEthRatio mismatch: chain value overrides env config", {
|
|
1174
|
+
chain_value: String(chainRatio),
|
|
1175
|
+
configured_value: String(configuredRatio)
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
this._nativeToEthRatio = chainRatio;
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1095
1182
|
// Returns true when the DotNS chain (Asset Hub) reports a testnet spec_name.
|
|
1096
1183
|
// Used to gate test-only behaviors like self-granting Full PoP on a Lite
|
|
1097
1184
|
// signer for a NoStatus label.
|
|
@@ -1280,7 +1367,25 @@ var DotNS = class {
|
|
|
1280
1367
|
contracts: this._contracts
|
|
1281
1368
|
}));
|
|
1282
1369
|
}
|
|
1283
|
-
|
|
1370
|
+
const rawData = callResult.result.value.data ?? "0x";
|
|
1371
|
+
if (rawData.length <= 2) {
|
|
1372
|
+
const hasCode = await this.clientWrapper.hasContractCode(contractAddress);
|
|
1373
|
+
const name = dotnsContractName(contractAddress, this._contracts);
|
|
1374
|
+
if (hasCode === false) {
|
|
1375
|
+
throw new Error(
|
|
1376
|
+
`No contract deployed at ${contractAddress} (${name}) \u2014 the dry-run call to ${functionName} returned empty success data, which on pallet-revive means the target address has no contract code. Check environments.json / --contract config for this network.`
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
if (hasCode === null) {
|
|
1380
|
+
throw new Error(
|
|
1381
|
+
`Contract call returned empty data \u2014 contract=${name} (${contractAddress}) functionName=${functionName}. Could not verify whether contract code exists at this address (runtime code-presence query failed); investigate the contract/ABI or the configured address.`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
throw new Error(
|
|
1385
|
+
`Contract call returned empty data \u2014 contract=${name} (${contractAddress}) functionName=${functionName}. The address has contract code but the call returned no bytes, which is unexpected for this read. Investigate the contract/ABI rather than masking it with a default.`
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
return decodeFunctionResult({ abi: contractAbi, functionName, data: rawData });
|
|
1284
1389
|
}
|
|
1285
1390
|
/**
|
|
1286
1391
|
* Like contractCall, but returns null when the chain replies with empty data
|
|
@@ -1314,7 +1419,7 @@ var DotNS = class {
|
|
|
1314
1419
|
}));
|
|
1315
1420
|
}
|
|
1316
1421
|
const rawData = callResult.result.value.data ?? "0x";
|
|
1317
|
-
if (rawData
|
|
1422
|
+
if (rawData.length <= 2) return null;
|
|
1318
1423
|
return decodeFunctionResult({ abi: contractAbi, functionName, data: rawData });
|
|
1319
1424
|
}
|
|
1320
1425
|
async contractTransaction(contractAddress, value, contractAbi, functionName, args = [], statusCallback = () => {
|
|
@@ -1422,7 +1527,7 @@ var DotNS = class {
|
|
|
1422
1527
|
if (txResolution.kind === TX_KIND_HASH) {
|
|
1423
1528
|
setDeployAttribute("deploy.subnode.tx", txResolution.hash);
|
|
1424
1529
|
if (txResolution.block) {
|
|
1425
|
-
setDeployAttribute("deploy.subnode.block", txResolution.block.number);
|
|
1530
|
+
setDeployAttribute("deploy.subnode.block", String(txResolution.block.number));
|
|
1426
1531
|
setDeployAttribute("deploy.subnode.block_hash", txResolution.block.hash);
|
|
1427
1532
|
console.log(` finalised @ block ${txResolution.block.number} (tx ${txResolution.hash})`);
|
|
1428
1533
|
} else {
|
|
@@ -1561,7 +1666,7 @@ var DotNS = class {
|
|
|
1561
1666
|
if (txRes.kind === TX_KIND_HASH) {
|
|
1562
1667
|
setDeployAttribute("deploy.contenthash.tx", txRes.hash);
|
|
1563
1668
|
if (txRes.block) {
|
|
1564
|
-
setDeployAttribute("deploy.contenthash.block", txRes.block.number);
|
|
1669
|
+
setDeployAttribute("deploy.contenthash.block", String(txRes.block.number));
|
|
1565
1670
|
setDeployAttribute("deploy.contenthash.block_hash", txRes.block.hash);
|
|
1566
1671
|
console.log(` finalised @ block ${txRes.block.number} (tx ${txRes.hash})`);
|
|
1567
1672
|
} else {
|
|
@@ -1979,9 +2084,7 @@ var DotNS = class {
|
|
|
1979
2084
|
const reservationInfo = await withTimeout(this.contractCall(this._contracts.POP_RULES, POP_RULES_ABI, "isBaseNameReserved", [baseName]), 3e4, "isBaseNameReserved");
|
|
1980
2085
|
const [isReserved, reservationOwner] = reservationInfo;
|
|
1981
2086
|
if (isReserved && reservationOwner.toLowerCase() !== this.evmAddress.toLowerCase()) throw new Error("Base name reserved for original Lite registrant");
|
|
1982
|
-
const
|
|
1983
|
-
const requiredStatus = typeof classificationResult[0] === "bigint" ? Number(classificationResult[0]) : classificationResult[0];
|
|
1984
|
-
const message = classificationResult[1];
|
|
2087
|
+
const { requiredStatus, message } = await this.classifyName(label);
|
|
1985
2088
|
const userStatus = await this.getUserPopStatus();
|
|
1986
2089
|
if (requiredStatus === ProofOfPersonhoodStatus.Reserved) throw new Error(message);
|
|
1987
2090
|
if (!canRegister(requiredStatus, userStatus)) {
|
|
@@ -2021,7 +2124,7 @@ var DotNS = class {
|
|
|
2021
2124
|
if (registerTxRes.kind === TX_KIND_HASH) {
|
|
2022
2125
|
setDeployAttribute("deploy.register.tx", registerTxRes.hash);
|
|
2023
2126
|
if (registerTxRes.block) {
|
|
2024
|
-
setDeployAttribute("deploy.register.block", registerTxRes.block.number);
|
|
2127
|
+
setDeployAttribute("deploy.register.block", String(registerTxRes.block.number));
|
|
2025
2128
|
setDeployAttribute("deploy.register.block_hash", registerTxRes.block.hash);
|
|
2026
2129
|
console.log(` finalised @ block ${registerTxRes.block.number} (tx ${registerTxRes.hash})`);
|
|
2027
2130
|
} else {
|
|
@@ -2271,13 +2374,14 @@ var DotNS = class {
|
|
|
2271
2374
|
// canProceed:true result with an actionable canProceed:false when even the
|
|
2272
2375
|
// top-up can't get the signer above the threshold.
|
|
2273
2376
|
async gateOnFeeBalance(candidate, signerFreeBalance, isTestnet) {
|
|
2274
|
-
const
|
|
2377
|
+
const rentPriceNative = candidate.plannedAction === "register" ? bufferedRentNative(this._nativeToEthRatio) : 0n;
|
|
2378
|
+
const feeFloor = feeFloorFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative);
|
|
2275
2379
|
let effectiveBalance = signerFreeBalance;
|
|
2276
2380
|
let toppedUp;
|
|
2277
2381
|
if (effectiveBalance < feeFloor && isTestnet) {
|
|
2278
2382
|
setDeployAttribute("deploy.dotns.signer_below_floor", "true");
|
|
2279
2383
|
console.log(` DotNS signer ${this.substrateAddress?.slice(0, 8)}... balance ${fmtPas(effectiveBalance)} PAS < ${fmtPas(feeFloor)} PAS floor \u2014 attempting testnet auto top-up...`);
|
|
2280
|
-
const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit));
|
|
2384
|
+
const result = await this.attemptTestnetTopUp(this.substrateAddress, topUpTargetFor(candidate.plannedAction, this._registerStorageDeposit, rentPriceNative));
|
|
2281
2385
|
if (result) {
|
|
2282
2386
|
console.log(` Topped up ${fmtPas(result.transferred)} PAS from ${result.source}`);
|
|
2283
2387
|
effectiveBalance += result.transferred;
|
|
@@ -2462,6 +2566,8 @@ export {
|
|
|
2462
2566
|
WS_HEARTBEAT_TIMEOUT_MS,
|
|
2463
2567
|
DOTNS_TX_MAX_ATTEMPTS,
|
|
2464
2568
|
classifyTxRetryDecision,
|
|
2569
|
+
dotnsRetryBackoffMs,
|
|
2570
|
+
makeRetryStatusFilter,
|
|
2465
2571
|
DEFAULT_MNEMONIC,
|
|
2466
2572
|
fetchNonce,
|
|
2467
2573
|
verifyNonceAdvanced,
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
classifyErrorArea,
|
|
3
3
|
isInteractive,
|
|
4
4
|
promptYesNo
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-R2ORPNZC.js";
|
|
6
6
|
import {
|
|
7
7
|
VERSION,
|
|
8
8
|
getCurrentSentryTraceId
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5EJ247OO.js";
|
|
10
10
|
|
|
11
11
|
// src/bug-report.ts
|
|
12
12
|
import { execSync, execFileSync } from "child_process";
|
|
@@ -192,10 +192,115 @@ async function fetchAcrossTiers(url, budget, start) {
|
|
|
192
192
|
}
|
|
193
193
|
return { outcome: "retryable", reason: `tiers exhausted: ${lastReason}`, attempts, bytesDownloaded };
|
|
194
194
|
}
|
|
195
|
+
var CHAIN_TIER_TIMEOUT_MS = 5e3;
|
|
196
|
+
function normalizeBitswapBytes(response) {
|
|
197
|
+
if (response instanceof Uint8Array) return response;
|
|
198
|
+
if (typeof response === "string") {
|
|
199
|
+
const hex = response.startsWith("0x") ? response.slice(2) : response;
|
|
200
|
+
if (hex.length % 2 !== 0) throw new Error(`bitswap_v1_get: odd-length hex response (${hex.length} chars)`);
|
|
201
|
+
const out = new Uint8Array(hex.length / 2);
|
|
202
|
+
for (let i = 0; i < out.length; i++) {
|
|
203
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
if (Array.isArray(response) && response.every((x) => typeof x === "number")) {
|
|
208
|
+
return new Uint8Array(response);
|
|
209
|
+
}
|
|
210
|
+
if (response instanceof ArrayBuffer) return new Uint8Array(response);
|
|
211
|
+
throw new Error(`bitswap_v1_get: unexpected response type ${typeof response}`);
|
|
212
|
+
}
|
|
213
|
+
async function chainGet(client, cid, timeoutMs) {
|
|
214
|
+
const timeoutError = new Error(`bitswap_v1_get timeout after ${timeoutMs}ms`);
|
|
215
|
+
const response = await Promise.race([
|
|
216
|
+
client._request("bitswap_v1_get", [cid]),
|
|
217
|
+
new Promise((_, reject) => {
|
|
218
|
+
const t = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
219
|
+
if (typeof t === "object" && t !== null && typeof t.unref === "function") {
|
|
220
|
+
t.unref();
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
]);
|
|
224
|
+
return normalizeBitswapBytes(response);
|
|
225
|
+
}
|
|
226
|
+
async function fetchManifestFromChain(client, rootCid, timeoutMs) {
|
|
227
|
+
let rootBytes;
|
|
228
|
+
try {
|
|
229
|
+
rootBytes = await chainGet(client, rootCid, timeoutMs);
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
let chunkCids;
|
|
234
|
+
try {
|
|
235
|
+
const rootNode = dagPB.decode(rootBytes);
|
|
236
|
+
chunkCids = (rootNode.Links ?? []).map((l) => l.Hash.toString());
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
if (chunkCids.length === 0) return null;
|
|
241
|
+
let chunk0;
|
|
242
|
+
try {
|
|
243
|
+
chunk0 = await chainGet(client, chunkCids[0], timeoutMs);
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
let manifestBytes;
|
|
248
|
+
try {
|
|
249
|
+
manifestBytes = await extractManifestFromCar(chunk0);
|
|
250
|
+
if (manifestBytes) return manifestBytes;
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
if (chunkCids.length < 2) return null;
|
|
254
|
+
let chunk1;
|
|
255
|
+
try {
|
|
256
|
+
chunk1 = await chainGet(client, chunkCids[1], timeoutMs);
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const combined = new Uint8Array(chunk0.length + chunk1.length);
|
|
261
|
+
combined.set(chunk0, 0);
|
|
262
|
+
combined.set(chunk1, chunk0.length);
|
|
263
|
+
try {
|
|
264
|
+
manifestBytes = await extractManifestFromCar(combined);
|
|
265
|
+
return manifestBytes ?? null;
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
195
270
|
async function fetchPreviousManifest(prevContenthash, options = {}) {
|
|
196
271
|
if (prevContenthash === null) return { source: "none" };
|
|
197
272
|
const local = readPersistentLocalManifest(options.domain, prevContenthash);
|
|
198
273
|
if (local) return local;
|
|
274
|
+
if (options.chainClient) {
|
|
275
|
+
const chainTimeoutMs = CHAIN_TIER_TIMEOUT_MS;
|
|
276
|
+
const chainManifestBytes = await Sentry.startSpan(
|
|
277
|
+
{ op: "manifest.fetch.chain", name: `manifest chain ${prevContenthash.slice(0, 12)}` },
|
|
278
|
+
async (span) => {
|
|
279
|
+
span.setAttribute("manifest.chain.cid", prevContenthash.slice(0, 12));
|
|
280
|
+
try {
|
|
281
|
+
const bytes = await fetchManifestFromChain(options.chainClient, prevContenthash, chainTimeoutMs);
|
|
282
|
+
span.setAttribute("manifest.chain.outcome", bytes ? "success" : "miss");
|
|
283
|
+
return bytes;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
span.setAttribute("manifest.chain.outcome", "error");
|
|
286
|
+
span.setAttribute("manifest.chain.error", e?.message ?? String(e));
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
if (chainManifestBytes) {
|
|
292
|
+
const text = new TextDecoder().decode(chainManifestBytes);
|
|
293
|
+
const parsed = parseManifest(text);
|
|
294
|
+
if (parsed.ok) {
|
|
295
|
+
return {
|
|
296
|
+
source: "embedded",
|
|
297
|
+
manifest: parsed.manifest,
|
|
298
|
+
attempts: 1,
|
|
299
|
+
bytesDownloaded: chainManifestBytes.length
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
199
304
|
const gatewayList = (options.gateways ?? (options.gateway ? [options.gateway] : [])).map((g) => g.replace(/\/$/, ""));
|
|
200
305
|
const budget = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
201
306
|
const start = Date.now();
|
|
@@ -302,6 +407,9 @@ export {
|
|
|
302
407
|
getCacheDir,
|
|
303
408
|
readPersistentLocalManifest,
|
|
304
409
|
writePersistentLocalManifest,
|
|
410
|
+
CHAIN_TIER_TIMEOUT_MS,
|
|
411
|
+
normalizeBitswapBytes,
|
|
412
|
+
fetchManifestFromChain,
|
|
305
413
|
fetchPreviousManifest,
|
|
306
414
|
extractManifestFromCar,
|
|
307
415
|
walkDagToManifest
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
setDeployAttribute
|
|
3
|
-
} from "./chunk-INVA3XGG.js";
|
|
4
|
-
|
|
5
1
|
// src/pool.ts
|
|
6
2
|
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
|
|
7
3
|
import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy } from "@polkadot-labs/hdkd-helpers";
|
|
@@ -33,14 +29,15 @@ function derivePoolAccounts(poolSize = 10, mnemonic = DEV_PHRASE) {
|
|
|
33
29
|
}
|
|
34
30
|
return accounts;
|
|
35
31
|
}
|
|
36
|
-
function isAuthorizationSufficient(auth, currentBlock, needs) {
|
|
32
|
+
async function isAuthorizationSufficient(api, address, auth, currentBlock, needs) {
|
|
37
33
|
if (auth === void 0) return false;
|
|
38
34
|
if (Number(auth.expiration ?? 0) <= currentBlock) return false;
|
|
39
35
|
if (needs) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
const ok = await api.apis.BulletinTransactionStorageApi.can_store(
|
|
37
|
+
address,
|
|
38
|
+
clampU32(needs.bytes, "needs.bytes")
|
|
39
|
+
);
|
|
40
|
+
if (!ok) return false;
|
|
44
41
|
}
|
|
45
42
|
return true;
|
|
46
43
|
}
|
|
@@ -67,46 +64,6 @@ async function fetchPoolAuthorizations(api, accounts) {
|
|
|
67
64
|
);
|
|
68
65
|
return results;
|
|
69
66
|
}
|
|
70
|
-
function computeTopUpTarget(current, needs) {
|
|
71
|
-
const minTxs = needs.txs + BigInt(TOPUP_TRANSACTIONS);
|
|
72
|
-
const minBytes = needs.bytes + TOPUP_BYTES;
|
|
73
|
-
if (current.transactions >= minTxs && current.bytes >= minBytes) return null;
|
|
74
|
-
return {
|
|
75
|
-
transactions: current.transactions > minTxs ? current.transactions : minTxs,
|
|
76
|
-
bytes: current.bytes > minBytes ? current.bytes : minBytes
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function classifyAliceTxError(err) {
|
|
80
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
81
|
-
const lower = msg.toLowerCase();
|
|
82
|
-
if (/\bstale\b/.test(lower)) return "retry";
|
|
83
|
-
if (/"type"\s*:\s*"future"|\binvalid::future\b/.test(lower)) return "retry";
|
|
84
|
-
if (lower.includes("websocket") || lower.includes("connection") || lower.includes("socket closed") || lower.includes("disconnect")) return "retry";
|
|
85
|
-
if (lower.includes("timed out") || lower.includes("timeout")) return "retry";
|
|
86
|
-
return "abort";
|
|
87
|
-
}
|
|
88
|
-
var ALICE_MAX_ATTEMPTS = 3;
|
|
89
|
-
async function submitAliceTxWithRetry(buildTx, aliceSigner, label) {
|
|
90
|
-
let lastError;
|
|
91
|
-
let attempts = 0;
|
|
92
|
-
for (let attempt = 1; attempt <= ALICE_MAX_ATTEMPTS; attempt++) {
|
|
93
|
-
attempts = attempt;
|
|
94
|
-
try {
|
|
95
|
-
const tx = buildTx();
|
|
96
|
-
const result = await tx.signAndSubmit(aliceSigner);
|
|
97
|
-
if (!result.ok) throw new Error(`${label} dispatch error`);
|
|
98
|
-
return;
|
|
99
|
-
} catch (e) {
|
|
100
|
-
lastError = e;
|
|
101
|
-
const decision = classifyAliceTxError(e);
|
|
102
|
-
if (decision === "abort" || attempt === ALICE_MAX_ATTEMPTS) break;
|
|
103
|
-
console.log(` ${label}: attempt ${attempt}/${ALICE_MAX_ATTEMPTS} failed (${String(e.message ?? e).slice(0, 80)}), retrying...`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
107
|
-
const plural = attempts === 1 ? "attempt" : "attempts";
|
|
108
|
-
throw new Error(`${label} failed after ${attempts} ${plural}: ${msg}`);
|
|
109
|
-
}
|
|
110
67
|
function isTestnetSpecName(specName) {
|
|
111
68
|
if (!specName) return false;
|
|
112
69
|
const s = specName.toLowerCase();
|
|
@@ -132,69 +89,28 @@ async function detectTestnet(api) {
|
|
|
132
89
|
function _resetTestnetCacheForTests() {
|
|
133
90
|
_testnetDetectionCache = null;
|
|
134
91
|
}
|
|
135
|
-
function aliceKeyring() {
|
|
136
|
-
const keyring = new Keyring({ type: "sr25519" });
|
|
137
|
-
const alice = keyring.addFromUri("//Alice");
|
|
138
|
-
const signer = getPolkadotSigner(alice.publicKey, "Sr25519", (data) => alice.sign(data));
|
|
139
|
-
return { alice, signer };
|
|
140
|
-
}
|
|
141
92
|
var U32_MAX = 0xFFFFFFFFn;
|
|
142
93
|
function clampU32(n, name) {
|
|
143
94
|
if (n < 0n) throw new Error(`${name} must be non-negative`);
|
|
144
95
|
if (n > U32_MAX) throw new Error(`${name} (${n}) exceeds u32 max \u2014 split the deploy into smaller batches`);
|
|
145
96
|
return Number(n);
|
|
146
97
|
}
|
|
147
|
-
function markBulletinAuthGranted() {
|
|
148
|
-
setDeployAttribute("deploy.unblock.bulletin_auth.fired", "true");
|
|
149
|
-
setDeployAttribute("deploy.unblock.bulletin_auth.granted_txs", String(TOPUP_TRANSACTIONS));
|
|
150
|
-
setDeployAttribute("deploy.unblock.bulletin_auth.granted_bytes", String(TOPUP_BYTES));
|
|
151
|
-
}
|
|
152
98
|
async function ensureAuthorized(api, address, label, needs) {
|
|
153
99
|
const [auth, currentBlock] = await Promise.all([
|
|
154
100
|
api.query.TransactionStorage.Authorizations.getValue(Enum("Account", address)),
|
|
155
101
|
api.query.System.Number.getValue()
|
|
156
102
|
]);
|
|
157
|
-
if (isAuthorizationSufficient(auth, currentBlock, needs)) return;
|
|
158
|
-
|
|
159
|
-
const {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
who
|
|
163
|
-
|
|
164
|
-
bytes: TOPUP_BYTES
|
|
165
|
-
}),
|
|
166
|
-
signer,
|
|
167
|
-
`authorize_account(${label ?? "account"})`
|
|
168
|
-
);
|
|
169
|
-
console.log(` Authorized: ${TOPUP_TRANSACTIONS} txs / ${Number(TOPUP_BYTES) / 1e6}MB`);
|
|
170
|
-
markBulletinAuthGranted();
|
|
171
|
-
}
|
|
172
|
-
async function topUpBy(api, address, needs, label) {
|
|
173
|
-
const [currentAuth, currentBlock] = await Promise.all([
|
|
174
|
-
api.query.TransactionStorage.Authorizations.getValue(Enum("Account", address)),
|
|
175
|
-
api.query.System.Number.getValue()
|
|
176
|
-
]);
|
|
177
|
-
if (isAuthorizationSufficient(currentAuth, currentBlock, needs)) {
|
|
178
|
-
const expiration = Number(currentAuth.expiration);
|
|
179
|
-
const fmtMB = (b) => (Number(b) / 1e6).toFixed(1);
|
|
180
|
-
const txsRemaining = BigInt(currentAuth.extent.transactions_allowance) - BigInt(currentAuth.extent.transactions);
|
|
181
|
-
const bytesRemaining = BigInt(currentAuth.extent.bytes_allowance) - BigInt(currentAuth.extent.bytes);
|
|
182
|
-
console.log(` Pre-auth skipped for ${label ?? "account"} (${address.slice(0, 8)}...): authorized until block ${expiration}, ${txsRemaining} txs / ${fmtMB(bytesRemaining)}MB remaining.`);
|
|
183
|
-
return;
|
|
103
|
+
if (await isAuthorizationSufficient(api, address, auth, currentBlock, needs)) return;
|
|
104
|
+
const isTestnet = await detectTestnet(api);
|
|
105
|
+
const who = `${label ?? "account"} (${address.slice(0, 8)}...)`;
|
|
106
|
+
if (isTestnet) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Bulletin storage account ${who} is not authorized (or its authorization expired). bulletin-deploy no longer self-authorizes on the Bulletin chain \u2014 request authorization for this account from the chain's authorizer (testnet faucet / personhood / pool bootstrap), then retry.`
|
|
109
|
+
);
|
|
184
110
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
await submitAliceTxWithRetry(
|
|
188
|
-
() => api.tx.TransactionStorage.authorize_account({
|
|
189
|
-
who: address,
|
|
190
|
-
transactions: clampU32(BigInt(TOPUP_TRANSACTIONS), "transactions"),
|
|
191
|
-
bytes: TOPUP_BYTES
|
|
192
|
-
}),
|
|
193
|
-
signer,
|
|
194
|
-
`topUpBy(${label ?? "account"})`
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Bulletin storage account ${who} is not authorized to store. On production the storage account must already carry its own authorization/allowance \u2014 bulletin-deploy cannot grant it.`
|
|
195
113
|
);
|
|
196
|
-
console.log(` Pre-authorized: ${TOPUP_TRANSACTIONS} txs / ${Number(TOPUP_BYTES) / 1e6}MB`);
|
|
197
|
-
markBulletinAuthGranted();
|
|
198
114
|
}
|
|
199
115
|
async function bootstrapPool(bulletinRpc, poolSize = 10, mnemonic) {
|
|
200
116
|
console.log(`Bootstrapping ${poolSize} pool accounts on ${bulletinRpc}...
|
|
@@ -259,12 +175,9 @@ export {
|
|
|
259
175
|
isAuthorizationSufficient,
|
|
260
176
|
selectAccount,
|
|
261
177
|
fetchPoolAuthorizations,
|
|
262
|
-
computeTopUpTarget,
|
|
263
|
-
classifyAliceTxError,
|
|
264
178
|
isTestnetSpecName,
|
|
265
179
|
detectTestnet,
|
|
266
180
|
_resetTestnetCacheForTests,
|
|
267
181
|
ensureAuthorized,
|
|
268
|
-
topUpBy,
|
|
269
182
|
bootstrapPool
|
|
270
183
|
};
|
|
@@ -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.8.
|
|
9
|
+
version: "0.8.2-rc.0",
|
|
10
10
|
private: false,
|
|
11
11
|
repository: {
|
|
12
12
|
type: "git",
|
|
@@ -35,6 +35,10 @@ var package_default = {
|
|
|
35
35
|
"./manifest-roundtrip": {
|
|
36
36
|
types: "./dist/manifest-roundtrip.d.ts",
|
|
37
37
|
import: "./dist/manifest-roundtrip.js"
|
|
38
|
+
},
|
|
39
|
+
"./telemetry": {
|
|
40
|
+
types: "./dist/telemetry.d.ts",
|
|
41
|
+
import: "./dist/telemetry.js"
|
|
38
42
|
}
|
|
39
43
|
},
|
|
40
44
|
files: [
|
|
@@ -47,9 +51,8 @@ var package_default = {
|
|
|
47
51
|
scripts: {
|
|
48
52
|
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 src/environments.ts src/errors.ts src/manifest.ts src/chunk-probe.ts src/manifest-embed.ts src/manifest-fetch.ts src/manifest-roundtrip.ts src/incremental-stats.ts src/chunker.ts src/personhood/encoding.ts src/personhood/hashing.ts src/personhood/constants.ts src/personhood/member-key.ts src/personhood/people-client.ts src/personhood/proof-validity.ts src/personhood/reprove.ts src/personhood/bind-personal-id.ts src/personhood/claim-pgas.ts src/personhood/bind-paid-alias.ts src/personhood/bootstrap.ts src/personhood/chain-prereqs.ts src/manifest/types.ts src/manifest/schema.ts src/manifest/byte-budget.ts src/manifest/config-load.ts src/manifest/publish.ts --format esm --dts --clean --target node22",
|
|
49
53
|
"refresh-environments": "node scripts/refresh-environments.mjs",
|
|
50
|
-
"check:watched-dependencies": "node tools/check-watched-dependencies.mjs",
|
|
51
54
|
prepare: "npm run build",
|
|
52
|
-
test: "npm run build && node --test test/test.js test/cli-help.test.js test/helpers/e2e-helpers.test.js test/environments.test.js test/refresh-environments.test.js test/
|
|
55
|
+
test: "npm run build && node --test test/test.js test/cli-help.test.js test/helpers/e2e-helpers.test.js test/environments.test.js test/refresh-environments.test.js test/chunk-sharing-report.test.js test/product-manifest.test.js test/cache-savings-totals.test.js test/error-pattern-signature.test.js test/exit-codes.test.js test/probe-env-health.test.js test/e2e-chain-calls.test.js",
|
|
53
56
|
"test:e2e": "npm run build && node --test test/e2e.test.js",
|
|
54
57
|
"test:e2e:smoke": "bash scripts/e2e-pass.sh smoke",
|
|
55
58
|
"test:e2e:pr": "bash scripts/e2e-pass.sh pr",
|
|
@@ -6,15 +6,15 @@ import {
|
|
|
6
6
|
resolveDotnsConnectOptions,
|
|
7
7
|
storeDirectory,
|
|
8
8
|
storeFile
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RRYHCOOJ.js";
|
|
10
10
|
import {
|
|
11
11
|
DotNS
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7GYCJPFI.js";
|
|
13
13
|
import {
|
|
14
14
|
getPopSelfServeConfig,
|
|
15
15
|
loadEnvironments,
|
|
16
16
|
resolveEndpoints
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-XFX4VODU.js";
|
|
18
18
|
import {
|
|
19
19
|
NonRetryableError
|
|
20
20
|
} from "./chunk-ZOC4GITL.js";
|