bulletin-deploy 0.7.21 → 0.7.22-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/dist/bug-report.js +4 -4
- package/dist/{chunk-2VNGK2MU.js → chunk-3LGLMHQI.js} +26 -3
- package/dist/{chunk-VTTN4BX7.js → chunk-4YO5SOAY.js} +151 -2
- package/dist/chunk-74ETPOKH.js +284 -0
- package/dist/chunk-A5IQ5MKO.js +207 -0
- package/dist/chunk-EJ5TNGAY.js +24 -0
- package/dist/{chunk-BFXHVC23.js → chunk-HEUS42MX.js} +21 -17
- package/dist/{chunk-ZY6QLNKQ.js → chunk-KTOZL74I.js} +1 -1
- package/dist/{chunk-MJTQOXBC.js → chunk-LEYQOOWC.js} +3 -2
- package/dist/{chunk-G2P5UIPX.js → chunk-LZO2QX4T.js} +4 -3
- package/dist/chunk-QHOZEY5X.js +231 -0
- package/dist/{chunk-RJAFD4LO.js → chunk-SL7LV4DS.js} +1 -1
- package/dist/chunk-T7EEVWNU.js +32 -0
- package/dist/{chunk-SA37SLYF.js → chunk-TA3NCL6U.js} +2 -2
- package/dist/chunk-UPWEOGLQ.js +37 -0
- package/dist/{chunk-4TS6R26J.js → chunk-WVCIU6WM.js} +22 -9
- package/dist/chunk-ZYVGHDMU.js +117 -0
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +4 -2
- package/dist/deploy.js +9 -9
- package/dist/dotns.d.ts +74 -1
- package/dist/dotns.js +8 -4
- package/dist/incremental-stats.d.ts +3 -1
- package/dist/incremental-stats.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -9
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +9 -9
- package/dist/personhood/bind-paid-alias.d.ts +43 -0
- package/dist/personhood/bind-paid-alias.js +10 -0
- package/dist/personhood/bind-personal-id.d.ts +55 -0
- package/dist/personhood/bind-personal-id.js +12 -0
- package/dist/personhood/bootstrap.d.ts +85 -0
- package/dist/personhood/bootstrap.js +245 -0
- package/dist/personhood/claim-pgas.d.ts +61 -0
- package/dist/personhood/claim-pgas.js +12 -0
- package/dist/personhood/constants.d.ts +23 -0
- package/dist/personhood/constants.js +22 -0
- package/dist/personhood/encoding.d.ts +49 -0
- package/dist/personhood/encoding.js +24 -0
- package/dist/personhood/hashing.d.ts +4 -0
- package/dist/personhood/hashing.js +8 -0
- package/dist/personhood/member-key.d.ts +12 -0
- package/dist/personhood/member-key.js +10 -0
- package/dist/personhood/people-client.d.ts +14 -0
- package/dist/personhood/people-client.js +48 -0
- package/dist/personhood/reprove.d.ts +43 -0
- package/dist/personhood/reprove.js +225 -0
- package/dist/pool.d.ts +7 -2
- package/dist/pool.js +3 -1
- package/dist/run-state.js +1 -1
- package/dist/telemetry.d.ts +7 -1
- package/dist/telemetry.js +8 -2
- package/dist/version-check.js +3 -3
- package/docs/e2e-bootstrap.md +36 -10
- package/package.json +4 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PolkadotClient } from 'polkadot-api';
|
|
2
|
+
|
|
3
|
+
interface PeopleClientResult {
|
|
4
|
+
client: PolkadotClient;
|
|
5
|
+
unsafeApi: ReturnType<PolkadotClient["getUnsafeApi"]>;
|
|
6
|
+
disconnect: () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Open a People-chain polkadot-api client for the given environment.
|
|
10
|
+
* The caller must call `disconnect()` when done to release the WS connection.
|
|
11
|
+
*/
|
|
12
|
+
declare function connectPeopleClient(environmentId: string): Promise<PeopleClientResult>;
|
|
13
|
+
|
|
14
|
+
export { type PeopleClientResult, connectPeopleClient };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WS_HEARTBEAT_TIMEOUT_MS
|
|
3
|
+
} from "../chunk-4YO5SOAY.js";
|
|
4
|
+
import "../chunk-WVCIU6WM.js";
|
|
5
|
+
import "../chunk-3LGLMHQI.js";
|
|
6
|
+
import "../chunk-LZO2QX4T.js";
|
|
7
|
+
import {
|
|
8
|
+
loadEnvironments
|
|
9
|
+
} from "../chunk-F36C363Y.js";
|
|
10
|
+
import "../chunk-ZOC4GITL.js";
|
|
11
|
+
|
|
12
|
+
// src/personhood/people-client.ts
|
|
13
|
+
import { createClient } from "polkadot-api";
|
|
14
|
+
import { getWsProvider } from "polkadot-api/ws";
|
|
15
|
+
async function connectPeopleClient(environmentId) {
|
|
16
|
+
const { doc } = await loadEnvironments();
|
|
17
|
+
const peopleChain = doc.chains.find((c) => c.id === "people");
|
|
18
|
+
if (!peopleChain) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`environments.json has no 'people' chain entry for env '${environmentId}'`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const entry = peopleChain.endpoints[environmentId];
|
|
24
|
+
if (!entry) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`No People-chain endpoint for environment '${environmentId}'. Bootstrap is only available on paseo-next-v2.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const rawWss = entry.wss;
|
|
30
|
+
const wss = Array.isArray(rawWss) ? rawWss[0] : rawWss;
|
|
31
|
+
const client = createClient(
|
|
32
|
+
getWsProvider(wss, { heartbeatTimeout: WS_HEARTBEAT_TIMEOUT_MS })
|
|
33
|
+
);
|
|
34
|
+
const unsafeApi = client.getUnsafeApi();
|
|
35
|
+
return {
|
|
36
|
+
client,
|
|
37
|
+
unsafeApi,
|
|
38
|
+
disconnect: () => {
|
|
39
|
+
try {
|
|
40
|
+
client.destroy();
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
connectPeopleClient
|
|
48
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SS58String, PolkadotSigner } from 'polkadot-api';
|
|
2
|
+
|
|
3
|
+
type ReproveAliasErrorKind = "NoStoredAlias" | "NotPaid" | "RingRootNotFound" | "RevisionNotAdvanced" | "AliasMismatch" | "NotARecognizedPerson" | "BadProof" | "DispatchError" | "RpcError" | "ClientError" | "Unknown";
|
|
4
|
+
declare class ReproveAliasError extends Error {
|
|
5
|
+
readonly kind: ReproveAliasErrorKind;
|
|
6
|
+
readonly dispatchError?: unknown;
|
|
7
|
+
constructor(message: string, options?: ErrorOptions & {
|
|
8
|
+
kind?: ReproveAliasErrorKind;
|
|
9
|
+
dispatchError?: unknown;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
type RingExponent = 9 | 10 | 14;
|
|
13
|
+
interface BuildRingProofInput {
|
|
14
|
+
ringExponent: RingExponent;
|
|
15
|
+
members: Uint8Array;
|
|
16
|
+
context: Uint8Array;
|
|
17
|
+
msg: Uint8Array;
|
|
18
|
+
}
|
|
19
|
+
type BuildRingProof = (input: BuildRingProofInput) => Promise<{
|
|
20
|
+
proof: Uint8Array;
|
|
21
|
+
alias: Uint8Array;
|
|
22
|
+
}>;
|
|
23
|
+
interface ReproveAliasProgress {
|
|
24
|
+
onBroadcasted?: () => void;
|
|
25
|
+
onBestBlock?: (blockHash: string) => void;
|
|
26
|
+
}
|
|
27
|
+
interface ReproveAliasParams {
|
|
28
|
+
peopleUnsafeApi: unknown;
|
|
29
|
+
ahUnsafeApi: unknown;
|
|
30
|
+
account: SS58String;
|
|
31
|
+
memberKey: Uint8Array;
|
|
32
|
+
signCall: PolkadotSigner;
|
|
33
|
+
buildRingProof: BuildRingProof;
|
|
34
|
+
progress?: ReproveAliasProgress;
|
|
35
|
+
}
|
|
36
|
+
interface ReproveAliasResult {
|
|
37
|
+
blockHash: string;
|
|
38
|
+
oldRevision: number;
|
|
39
|
+
newRevision: number;
|
|
40
|
+
}
|
|
41
|
+
declare const reproveAliasToAccount: ({ peopleUnsafeApi, ahUnsafeApi, account, memberKey, signCall, buildRingProof, progress, }: ReproveAliasParams) => Promise<ReproveAliasResult>;
|
|
42
|
+
|
|
43
|
+
export { type BuildRingProof, type BuildRingProofInput, ReproveAliasError, type ReproveAliasErrorKind, type ReproveAliasParams, type ReproveAliasProgress, type ReproveAliasResult, type RingExponent, reproveAliasToAccount };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
blake2_256,
|
|
3
|
+
bytesToHex,
|
|
4
|
+
concatBytes,
|
|
5
|
+
encodeMembers,
|
|
6
|
+
hexToBytes
|
|
7
|
+
} from "../chunk-ZYVGHDMU.js";
|
|
8
|
+
import {
|
|
9
|
+
PAID_PROOF_TAG,
|
|
10
|
+
PEOPLE_MEMBER_IDENTIFIER_HEX,
|
|
11
|
+
PROOF_BYTES
|
|
12
|
+
} from "../chunk-T7EEVWNU.js";
|
|
13
|
+
|
|
14
|
+
// src/personhood/reprove.ts
|
|
15
|
+
import { getSs58AddressInfo } from "polkadot-api";
|
|
16
|
+
var ReproveAliasError = class extends Error {
|
|
17
|
+
kind;
|
|
18
|
+
dispatchError;
|
|
19
|
+
constructor(message, options = {}) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.name = "ReproveAliasError";
|
|
22
|
+
this.kind = options.kind ?? "Unknown";
|
|
23
|
+
this.dispatchError = options.dispatchError;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var reproveAliasToAccount = async ({
|
|
27
|
+
peopleUnsafeApi,
|
|
28
|
+
ahUnsafeApi,
|
|
29
|
+
account,
|
|
30
|
+
memberKey,
|
|
31
|
+
signCall,
|
|
32
|
+
buildRingProof,
|
|
33
|
+
progress
|
|
34
|
+
}) => {
|
|
35
|
+
const people = peopleUnsafeApi;
|
|
36
|
+
const ah = ahUnsafeApi;
|
|
37
|
+
if (memberKey.length !== 32) {
|
|
38
|
+
throw new ReproveAliasError("memberKey must be 32 bytes", {
|
|
39
|
+
kind: "ClientError"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const stored = await ah.query.AliasAccounts.AccountToAlias.getValue(
|
|
43
|
+
account,
|
|
44
|
+
{ at: "best" }
|
|
45
|
+
);
|
|
46
|
+
if (!stored) {
|
|
47
|
+
throw new ReproveAliasError(
|
|
48
|
+
"no AccountToAlias row for this account \u2014 bind first",
|
|
49
|
+
{ kind: "NoStoredAlias" }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (!stored.paid) {
|
|
53
|
+
throw new ReproveAliasError(
|
|
54
|
+
"AccountToAlias row is not paid \u2014 reprove_alias_account is only for paid aliases",
|
|
55
|
+
{ kind: "NotPaid" }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const contextBytes = hexToBytes(stored.ca.context);
|
|
59
|
+
const storedRevision = stored.revision;
|
|
60
|
+
const storedRing = stored.ring;
|
|
61
|
+
const storedAliasHex = stored.ca.alias.toLowerCase();
|
|
62
|
+
const position = await people.query.Members.Members.getValue(
|
|
63
|
+
PEOPLE_MEMBER_IDENTIFIER_HEX,
|
|
64
|
+
bytesToHex(memberKey),
|
|
65
|
+
{ at: "best" }
|
|
66
|
+
);
|
|
67
|
+
if (!position || position.type !== "Included") {
|
|
68
|
+
throw new ReproveAliasError(
|
|
69
|
+
`member position is '${position?.type ?? "absent"}', expected 'Included'`,
|
|
70
|
+
{ kind: "NotARecognizedPerson" }
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const ringIndex = position.value.ring_index;
|
|
74
|
+
const allEntries = await people.query.Members.RingKeys.getEntries({
|
|
75
|
+
at: "best"
|
|
76
|
+
});
|
|
77
|
+
const pages = [];
|
|
78
|
+
const identHex = PEOPLE_MEMBER_IDENTIFIER_HEX.toLowerCase();
|
|
79
|
+
for (const entry of allEntries) {
|
|
80
|
+
if (entry.keyArgs[0].toLowerCase() !== identHex) continue;
|
|
81
|
+
if (Number(entry.keyArgs[1]) !== ringIndex) continue;
|
|
82
|
+
pages.push([Number(entry.keyArgs[2]), [...entry.value]]);
|
|
83
|
+
}
|
|
84
|
+
pages.sort((a, b) => a[0] - b[0]);
|
|
85
|
+
const ringKeys = pages.flatMap(([, ks]) => ks);
|
|
86
|
+
if (ringKeys.length === 0) {
|
|
87
|
+
throw new ReproveAliasError("ring has no members on People", {
|
|
88
|
+
kind: "ClientError"
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const membersBytes = encodeMembers(ringKeys.map((k) => hexToBytes(k)));
|
|
92
|
+
const ringExp = await ah.constants.AliasAccounts.PeopleRingExponent();
|
|
93
|
+
const ringExponent = ringExp.type === "R2e9" ? 9 : ringExp.type === "R2e10" ? 10 : 14;
|
|
94
|
+
const ringRoots = await ah.query.MembersSubscriber.RingRoots.getValue(
|
|
95
|
+
stored.collection,
|
|
96
|
+
ringIndex,
|
|
97
|
+
{ at: "best" }
|
|
98
|
+
);
|
|
99
|
+
if (!ringRoots || ringRoots.length === 0) {
|
|
100
|
+
throw new ReproveAliasError(
|
|
101
|
+
"AH has no RingRoots for this (collection, ring_index)",
|
|
102
|
+
{ kind: "RingRootNotFound" }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const latest = ringRoots[ringRoots.length - 1];
|
|
106
|
+
const newRevision = latest.revision;
|
|
107
|
+
if (newRevision <= storedRevision && ringIndex === storedRing) {
|
|
108
|
+
throw new ReproveAliasError(
|
|
109
|
+
`latest revision ${newRevision} is not strictly greater than stored ${storedRevision} on the same ring`,
|
|
110
|
+
{ kind: "RevisionNotAdvanced" }
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const ss58Info = getSs58AddressInfo(account);
|
|
114
|
+
if (!ss58Info.isValid) {
|
|
115
|
+
throw new ReproveAliasError(`invalid SS58: ${account}`, {
|
|
116
|
+
kind: "ClientError"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const paidMsg = blake2_256(concatBytes(PAID_PROOF_TAG, ss58Info.publicKey));
|
|
120
|
+
const { proof, alias } = await buildRingProof({
|
|
121
|
+
ringExponent,
|
|
122
|
+
members: membersBytes,
|
|
123
|
+
context: contextBytes,
|
|
124
|
+
msg: paidMsg
|
|
125
|
+
});
|
|
126
|
+
if (proof.length !== PROOF_BYTES) {
|
|
127
|
+
throw new ReproveAliasError(
|
|
128
|
+
`ring proof must be ${PROOF_BYTES} bytes, got ${proof.length}`,
|
|
129
|
+
{ kind: "ClientError" }
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (bytesToHex(alias).toLowerCase() !== storedAliasHex) {
|
|
133
|
+
throw new ReproveAliasError(
|
|
134
|
+
`regenerated alias ${bytesToHex(alias)} differs from stored ${storedAliasHex} \u2014 would fail ReproveMismatch`,
|
|
135
|
+
{ kind: "AliasMismatch" }
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const tx = ah.tx.AliasAccounts.reprove_alias_account({
|
|
139
|
+
proof: bytesToHex(proof),
|
|
140
|
+
ring_index: ringIndex,
|
|
141
|
+
ring_revision: newRevision
|
|
142
|
+
});
|
|
143
|
+
const blockHash = await new Promise((resolve, reject) => {
|
|
144
|
+
let settled = false;
|
|
145
|
+
const fail = (err) => {
|
|
146
|
+
if (settled) return;
|
|
147
|
+
settled = true;
|
|
148
|
+
reject(err);
|
|
149
|
+
};
|
|
150
|
+
const succeed = (h) => {
|
|
151
|
+
if (settled) return;
|
|
152
|
+
settled = true;
|
|
153
|
+
resolve(h);
|
|
154
|
+
};
|
|
155
|
+
tx.signSubmitAndWatch(signCall).subscribe({
|
|
156
|
+
next: (event) => {
|
|
157
|
+
if (settled) return;
|
|
158
|
+
const ev = event;
|
|
159
|
+
if (ev.type === "broadcasted") {
|
|
160
|
+
progress?.onBroadcasted?.();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (ev.type === "txBestBlocksState" && ev.found) {
|
|
164
|
+
if (ev.ok === false) {
|
|
165
|
+
fail(
|
|
166
|
+
new ReproveAliasError(
|
|
167
|
+
"reprove_alias_account dispatched but failed in-block",
|
|
168
|
+
{
|
|
169
|
+
kind: narrowDispatchError(ev.dispatchError),
|
|
170
|
+
dispatchError: ev.dispatchError
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (ev.block) progress?.onBestBlock?.(ev.block.hash);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (ev.type === "finalized") {
|
|
180
|
+
if (ev.ok === false) {
|
|
181
|
+
fail(
|
|
182
|
+
new ReproveAliasError(
|
|
183
|
+
"reprove_alias_account failed at finalization",
|
|
184
|
+
{
|
|
185
|
+
kind: narrowDispatchError(ev.dispatchError),
|
|
186
|
+
dispatchError: ev.dispatchError
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (ev.block) succeed(ev.block.hash);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
error: (err) => {
|
|
196
|
+
fail(
|
|
197
|
+
err instanceof ReproveAliasError ? err : new ReproveAliasError(
|
|
198
|
+
err instanceof Error ? `RPC rejected extrinsic: ${err.message}` : "RPC error during submitAndWatch",
|
|
199
|
+
{ cause: err, kind: "RpcError" }
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
return { blockHash, oldRevision: storedRevision, newRevision };
|
|
206
|
+
};
|
|
207
|
+
var narrowDispatchError = (dispatchError) => {
|
|
208
|
+
if (typeof dispatchError === "object" && dispatchError !== null && "type" in dispatchError) {
|
|
209
|
+
const d = dispatchError;
|
|
210
|
+
if (d.type === "Module" && typeof d.value === "object" && d.value !== null) {
|
|
211
|
+
const v = d.value;
|
|
212
|
+
if (v.type === "AliasAccounts") {
|
|
213
|
+
if (v.value?.type === "BadProof") return "BadProof";
|
|
214
|
+
if (v.value?.type === "ReproveMismatch") return "AliasMismatch";
|
|
215
|
+
if (v.value?.type === "AliasAccountAlreadySet")
|
|
216
|
+
return "RevisionNotAdvanced";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return "DispatchError";
|
|
221
|
+
};
|
|
222
|
+
export {
|
|
223
|
+
ReproveAliasError,
|
|
224
|
+
reproveAliasToAccount
|
|
225
|
+
};
|
package/dist/pool.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ interface PoolAuthorization extends PoolAccount {
|
|
|
14
14
|
expiration: number;
|
|
15
15
|
}
|
|
16
16
|
declare function derivePoolAccounts(poolSize?: number, mnemonic?: string): PoolAccount[];
|
|
17
|
+
interface AuthorizationCheck {
|
|
18
|
+
needs?: AuthorizationNeeds;
|
|
19
|
+
bulletinAuthorizeV2?: boolean;
|
|
20
|
+
}
|
|
21
|
+
declare function isAuthorizationSufficient(auth: any, currentBlock: number, check?: AuthorizationCheck): boolean;
|
|
17
22
|
declare function selectAccount(authorizations: PoolAuthorization[], random?: () => number, currentBlock?: number): PoolAuthorization | null;
|
|
18
23
|
declare function fetchPoolAuthorizations(api: any, accounts: PoolAccount[]): Promise<PoolAuthorization[]>;
|
|
19
24
|
interface AuthorizationNeeds {
|
|
@@ -32,11 +37,11 @@ declare function classifyAliceTxError(err: unknown): TxRetryDecision;
|
|
|
32
37
|
declare function isTestnetSpecName(specName: string | undefined | null): boolean;
|
|
33
38
|
declare function detectTestnet(api: any): Promise<boolean>;
|
|
34
39
|
declare function _resetTestnetCacheForTests(): void;
|
|
35
|
-
declare function ensureAuthorized(api: any, address: string, label?: string, bulletinAuthorizeV2?: boolean): Promise<void>;
|
|
40
|
+
declare function ensureAuthorized(api: any, address: string, label?: string, bulletinAuthorizeV2?: boolean, needs?: AuthorizationNeeds): Promise<void>;
|
|
36
41
|
declare function topUpBy(api: any, address: string, needs: AuthorizationNeeds, label?: string, bulletinAuthorizeV2?: boolean): Promise<void>;
|
|
37
42
|
interface BootstrapOptions {
|
|
38
43
|
bulletinAuthorizeV2?: boolean;
|
|
39
44
|
}
|
|
40
45
|
declare function bootstrapPool(bulletinRpc: string, poolSize?: number, mnemonic?: string, opts?: BootstrapOptions): Promise<void>;
|
|
41
46
|
|
|
42
|
-
export { type AuthorizationNeeds, type BootstrapOptions, type PoolAccount, type PoolAuthorization, type TxRetryDecision, _resetTestnetCacheForTests, bootstrapPool, classifyAliceTxError, computeTopUpTarget, derivePoolAccounts, detectTestnet, ensureAuthorized, fetchPoolAuthorizations, formatPasBalance, isTestnetSpecName, selectAccount, topUpBy };
|
|
47
|
+
export { type AuthorizationCheck, type AuthorizationNeeds, type BootstrapOptions, type PoolAccount, type PoolAuthorization, type TxRetryDecision, _resetTestnetCacheForTests, bootstrapPool, classifyAliceTxError, computeTopUpTarget, derivePoolAccounts, detectTestnet, ensureAuthorized, fetchPoolAuthorizations, formatPasBalance, isAuthorizationSufficient, isTestnetSpecName, selectAccount, topUpBy };
|
package/dist/pool.js
CHANGED
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
ensureAuthorized,
|
|
9
9
|
fetchPoolAuthorizations,
|
|
10
10
|
formatPasBalance,
|
|
11
|
+
isAuthorizationSufficient,
|
|
11
12
|
isTestnetSpecName,
|
|
12
13
|
selectAccount,
|
|
13
14
|
topUpBy
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-WVCIU6WM.js";
|
|
15
16
|
export {
|
|
16
17
|
_resetTestnetCacheForTests,
|
|
17
18
|
bootstrapPool,
|
|
@@ -22,6 +23,7 @@ export {
|
|
|
22
23
|
ensureAuthorized,
|
|
23
24
|
fetchPoolAuthorizations,
|
|
24
25
|
formatPasBalance,
|
|
26
|
+
isAuthorizationSufficient,
|
|
25
27
|
isTestnetSpecName,
|
|
26
28
|
selectAccount,
|
|
27
29
|
topUpBy
|
package/dist/run-state.js
CHANGED
package/dist/telemetry.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import * as _sentry_node from '@sentry/node';
|
|
1
2
|
import { DeployContextForReport } from './memory-report.js';
|
|
2
3
|
import 'node:v8';
|
|
3
4
|
|
|
4
5
|
declare const VERSION: string;
|
|
6
|
+
type SentryModule = typeof _sentry_node | null;
|
|
5
7
|
interface InternalContextSignals {
|
|
6
8
|
githubRepository?: string;
|
|
7
9
|
runnerName?: string;
|
|
@@ -27,6 +29,9 @@ type DeployErrorCategory = 'user' | 'environment' | 'internal' | 'unknown';
|
|
|
27
29
|
declare function classifyDeployError(msg: string): DeployErrorCategory;
|
|
28
30
|
declare function classifySadReason(message: string): string;
|
|
29
31
|
declare function computeDeployOutcome(errorCategory: DeployErrorCategory | null, isSad: boolean, sadReason: string): string;
|
|
32
|
+
type DeployErrorKind = 'contract-revert' | 'chain-timeout' | 'nonce-stale' | 'connection' | 'unknown';
|
|
33
|
+
declare function classifyErrorKind(msg: string): DeployErrorKind;
|
|
34
|
+
declare function sanitizeErrorMessage(msg: string): string;
|
|
30
35
|
declare function withSpan<T>(op: string, description: string, attributes: Record<string, string | number | boolean | undefined>, fn: () => T | Promise<T>): Promise<T>;
|
|
31
36
|
declare function sampleMemory(stage: string): void;
|
|
32
37
|
declare function withDeploySpan<T>(domain: string, fn: () => T | Promise<T>): Promise<T>;
|
|
@@ -35,9 +40,10 @@ declare function setDeployReportContext(patch: Partial<DeployContextForReport> &
|
|
|
35
40
|
}): void;
|
|
36
41
|
declare function setDeployAttribute(key: string, value: string | number | boolean): void;
|
|
37
42
|
declare function __setDeployRootSpanForTest(span: any | null): void;
|
|
43
|
+
declare function __setSentryForTest(stub: any): SentryModule;
|
|
38
44
|
declare function getCurrentSentryTraceId(): string | undefined;
|
|
39
45
|
declare function setDeploySentryTag(key: string, value: string): void;
|
|
40
46
|
declare function captureWarning(message: string, context?: Record<string, unknown>): void;
|
|
41
47
|
declare function flush(): Promise<void>;
|
|
42
48
|
|
|
43
|
-
export { type DeployErrorCategory, type InternalContextSignals, VERSION, __setDeployRootSpanForTest, captureWarning, classifyDeployError, classifySadReason, closeTelemetry, computeDeployOutcome, flush, getCurrentSentryTraceId, getDeployAttributes, initTelemetry, isExpectedError, isInternalContext, isInternalContextFromSignals, markRelaunchOomHintShown, resolveRepo, resolveRunner, resolveRunnerType, sampleMemory, sanitizeBranch, sanitizeRepo, scrubPaths, setDeployAttribute, setDeployReportContext, setDeploySentryTag, setRunStateActive, truncateAddress, withDeploySpan, withSpan };
|
|
49
|
+
export { type DeployErrorCategory, type DeployErrorKind, type InternalContextSignals, VERSION, __setDeployRootSpanForTest, __setSentryForTest, captureWarning, classifyDeployError, classifyErrorKind, classifySadReason, closeTelemetry, computeDeployOutcome, flush, getCurrentSentryTraceId, getDeployAttributes, initTelemetry, isExpectedError, isInternalContext, isInternalContextFromSignals, markRelaunchOomHintShown, resolveRepo, resolveRunner, resolveRunnerType, sampleMemory, sanitizeBranch, sanitizeErrorMessage, sanitizeRepo, scrubPaths, setDeployAttribute, setDeployReportContext, setDeploySentryTag, setRunStateActive, truncateAddress, withDeploySpan, withSpan };
|
package/dist/telemetry.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION,
|
|
3
3
|
__setDeployRootSpanForTest,
|
|
4
|
+
__setSentryForTest,
|
|
4
5
|
captureWarning,
|
|
5
6
|
classifyDeployError,
|
|
7
|
+
classifyErrorKind,
|
|
6
8
|
classifySadReason,
|
|
7
9
|
closeTelemetry,
|
|
8
10
|
computeDeployOutcome,
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
resolveRunnerType,
|
|
20
22
|
sampleMemory,
|
|
21
23
|
sanitizeBranch,
|
|
24
|
+
sanitizeErrorMessage,
|
|
22
25
|
sanitizeRepo,
|
|
23
26
|
scrubPaths,
|
|
24
27
|
setDeployAttribute,
|
|
@@ -28,13 +31,15 @@ import {
|
|
|
28
31
|
truncateAddress,
|
|
29
32
|
withDeploySpan,
|
|
30
33
|
withSpan
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
34
|
+
} from "./chunk-3LGLMHQI.js";
|
|
35
|
+
import "./chunk-LZO2QX4T.js";
|
|
33
36
|
export {
|
|
34
37
|
VERSION,
|
|
35
38
|
__setDeployRootSpanForTest,
|
|
39
|
+
__setSentryForTest,
|
|
36
40
|
captureWarning,
|
|
37
41
|
classifyDeployError,
|
|
42
|
+
classifyErrorKind,
|
|
38
43
|
classifySadReason,
|
|
39
44
|
closeTelemetry,
|
|
40
45
|
computeDeployOutcome,
|
|
@@ -51,6 +56,7 @@ export {
|
|
|
51
56
|
resolveRunnerType,
|
|
52
57
|
sampleMemory,
|
|
53
58
|
sanitizeBranch,
|
|
59
|
+
sanitizeErrorMessage,
|
|
54
60
|
sanitizeRepo,
|
|
55
61
|
scrubPaths,
|
|
56
62
|
setDeployAttribute,
|
package/dist/version-check.js
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
isPreReleaseVersion,
|
|
10
10
|
preReleaseWarning,
|
|
11
11
|
promptYesNo
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-SL7LV4DS.js";
|
|
13
|
+
import "./chunk-3LGLMHQI.js";
|
|
14
|
+
import "./chunk-LZO2QX4T.js";
|
|
15
15
|
export {
|
|
16
16
|
assessVersion,
|
|
17
17
|
checkNodeVersion,
|
package/docs/e2e-bootstrap.md
CHANGED
|
@@ -41,7 +41,11 @@ Expected output: Bob's SS58, his H160 (`0x41dccbd49b26c50d34355ed86ff0fa9e489d1e
|
|
|
41
41
|
|
|
42
42
|
The S3 negative scenario asserts that `bulletin-deploy` refuses to deploy to a domain owned by a different account (exit 78 with transfer guidance). Have Bob register the label with a DotNS registration tool outside `bulletin-deploy` — no Alice intermediary, no Bulletin content needed (S3 never reads content).
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
```bash
|
|
45
|
+
node tools/register-test-fixture-paseo-next-v2.mjs e2eowned
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Expected: `e2eowned.dot` is owned by Bob's H160 `0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01`. The `e2eowned.dot` label requires PoP Full status and a mature commitment (the tool waits 30s after the minimum commitment age). The tool is idempotent: it exits 0 immediately if Bob already owns it, and transfers the label from Alice back to Bob if fixture drift is detected.
|
|
45
49
|
|
|
46
50
|
If S3 ever fails because `e2eowned.dot` was transferred back to Alice by mistake, re-run this step to restore Bob's ownership.
|
|
47
51
|
|
|
@@ -51,7 +55,9 @@ If S3 ever fails because `e2eowned.dot` was transferred back to Alice by mistake
|
|
|
51
55
|
|
|
52
56
|
Paseo Next v2 uses a separate Asset Hub (`wss://paseo-asset-hub-next-rpc.polkadot.io`) and Bulletin chain (`wss://paseo-bulletin-next-rpc.polkadot.io`) with different contract addresses. The `map_account` extrinsic does not exist on this chain — account mapping is triggered automatically when an account submits its first on-chain transaction.
|
|
53
57
|
|
|
54
|
-
> **Note on PoP grants:** The paseo-next-v2 `POP_RULES` contract (`0x2002C1c15b88632Ad01c7770f6EbE1Ca05c8472E`) is **not permissionless** — `setUserPopStatus` can only be called by the contract owner (`0x4a519c30da0ec16aa9a73c26ea6ca6f701cce099`). CI
|
|
58
|
+
> **Note on PoP grants:** The paseo-next-v2 `POP_RULES` contract (`0x2002C1c15b88632Ad01c7770f6EbE1Ca05c8472E`) is **not permissionless** — `setUserPopStatus` can only be called by the contract owner (`0x4a519c30da0ec16aa9a73c26ea6ca6f701cce099`). `bulletin-deploy` itself cannot upgrade a signer; the chain team flips the Personhood precompile out of band. CI scenarios pick labels via `pickStableLabel`/`pickDirectLabel`/`pickIncLabel`/`pickRotLabel`, which auto-select between a PoP-Full base name (e.g. `e2epool.dot`) and a NoStatus fallback (e.g. `e2epoolns01.dot`) based on what `Personhood.personhoodStatus(<signer>)` returns at test start. The setup below covers both modes.
|
|
59
|
+
|
|
60
|
+
> **⚠ Testnet wipes reset everything.** When paseo-next-v2 is reset (which happens periodically), Alice's Personhood precompile status drops to NoStatus *and* every `e2e*.dot` registration is gone. Re-run the relevant steps below after each wipe — there is no on-chain self-recovery. Issue #381 traces back to exactly this: Alice's status came back as Full but `e2epool.dot` was unregistered, so `setContenthash` reverted with `ERC721NonexistentToken`. As long as ownership stays in lockstep with Alice's PoP grade (Full ↔ PoP-Full labels registered; NoStatus ↔ NoStatus labels auto-register on first deploy), the nightly stays green.
|
|
55
61
|
|
|
56
62
|
### Prerequisites
|
|
57
63
|
|
|
@@ -68,13 +74,23 @@ This grants each pool account `TransactionStorage` quota on Bulletin Next. Alice
|
|
|
68
74
|
|
|
69
75
|
### 2. Verify Alice PoP status
|
|
70
76
|
|
|
71
|
-
Alice does not need PoP Full for the current v2 CI labels. They are deliberately shaped as NoStatus labels, for example `e2epoolns01.dot` (base length >= 9 with exactly two trailing digits).
|
|
72
|
-
|
|
73
77
|
```bash
|
|
74
78
|
node tools/check-pop-status.mjs --env paseo-next-v2
|
|
75
79
|
```
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
What you see decides which labels need pre-registration:
|
|
82
|
+
|
|
83
|
+
- **`NoStatus (0)`** → no extra work. The tests pick the NoStatus fallback labels (`e2epoolns01`, `e2edirect01`, `e2eincpool01`, `e2erotpool01`) which auto-register on first deploy because their shape (base length ≥ 9 with two trailing digits) bypasses the `Requires Full personhood verification` gate.
|
|
84
|
+
- **`ProofOfPersonhoodFull (2)`** → the chain team has flipped Alice on the Personhood precompile. The tests will now pick the PoP-Full stable labels (`e2epool`, `e2edirect`, `e2einc`, `e2erot`), and those **must already be registered to Alice** before the matrix runs. Register them once:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Pre-built fixture is fine — it's the contenthash, not the content, that the tests overwrite.
|
|
88
|
+
for label in e2epool e2edirect e2einc e2erot; do
|
|
89
|
+
node bin/bulletin-deploy test/fixtures/e2e-spa "${label}.dot" --env paseo-next-v2 --js-merkle
|
|
90
|
+
done
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Skipping this leaves `setContenthash` reverting with `ERC721NonexistentToken` on whichever PoP-Full label the scenario picks (issue #381).
|
|
78
94
|
|
|
79
95
|
### 3. Fund Bob and trigger his account mapping
|
|
80
96
|
|
|
@@ -91,18 +107,27 @@ Expected output: Bob's SS58, his H160 (`0x41dccbd49b26c50d34355ed86ff0fa9e489d1e
|
|
|
91
107
|
|
|
92
108
|
### 4. Verify Bob PoP status
|
|
93
109
|
|
|
94
|
-
Bob
|
|
110
|
+
Bob owns both `e2eownedns02.dot` (NoStatus, used by S3 when Alice is NoStatus) and `e2eowned.dot` (PoP-Full, used by S3 when the Personhood precompile has flipped Alice to Full). The NoStatus branch needs no admin help; the PoP-Full branch needed Bob to register it back when the chain team granted him Full status once. After that one-shot registration the label persists until expiry, so Bob can stay NoStatus going forward — ownership and PoP-grade are decoupled after registration.
|
|
95
111
|
|
|
96
112
|
### 5. Register `e2eownedns02.dot` directly as Bob
|
|
97
113
|
|
|
98
|
-
Register the domain using the dedicated tool
|
|
114
|
+
Register the domain using the dedicated tool:
|
|
99
115
|
|
|
100
116
|
```bash
|
|
101
|
-
node tools/register-
|
|
117
|
+
node tools/register-test-fixture-paseo-next-v2.mjs e2eownedns02
|
|
102
118
|
```
|
|
103
119
|
|
|
104
120
|
Expected: `e2eownedns02.dot` is owned by Bob (`0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01`).
|
|
105
|
-
The
|
|
121
|
+
The tool is idempotent: it leaves Bob-owned state alone and transfers the label back to Bob if a failed S3 fixture run accidentally left it owned by Alice.
|
|
122
|
+
|
|
123
|
+
### 6. Ensure Bob owns `e2eowned.dot` on paseo-next-v2
|
|
124
|
+
|
|
125
|
+
S3 picks `e2eowned.dot` when Alice is PoP-Full at test start. If the label is unregistered, Alice's deploy gets routed through the full register flow and her own H160 lands as owner — at which point every subsequent S3 run on the Full path "succeeds" cleanly (`exit 0`) instead of being rejected (`exit 78`), and the test fails. Bob must own this label.
|
|
126
|
+
|
|
127
|
+
If Bob already owns it (`ownerOf` on `DOTNS_REGISTRAR` returns `0x41dccbd…`), skip. Otherwise:
|
|
128
|
+
|
|
129
|
+
- **If nobody owns it yet:** registration needs Bob to hold Full PoP. Coordinate with the chain team to flip Bob's Personhood precompile to Full, then run `node tools/register-e2eowned-paseo-next-v2.mjs` (the script targets Bob via `//Bob`). After registration Bob can drop back to NoStatus.
|
|
130
|
+
- **If Alice (or anyone other than Bob) is squatting:** the owner can call `transferFrom(<current>, Bob, tokenId)` on `DOTNS_REGISTRAR`. ERC721 transfer is unconditional on the recipient (no PoP check) and doesn't need Bob's signer. `tools/transfer-e2eowned-to-bob.mjs` does this for Alice → Bob in one shot (was added to recover from a botched PR-CI run on 2026-05-17, see issue #381 thread).
|
|
106
131
|
|
|
107
132
|
---
|
|
108
133
|
|
|
@@ -121,8 +146,9 @@ E2E=1 E2E_SIGNER=pool E2E_MERKLE=js E2E_SCENARIO=s1 \
|
|
|
121
146
|
# Paseo Next v2
|
|
122
147
|
E2E=1 E2E_SIGNER=pool E2E_MERKLE=js E2E_SCENARIO=s1 \
|
|
123
148
|
BULLETIN_RPC=wss://paseo-bulletin-next-rpc.polkadot.io \
|
|
124
|
-
|
|
149
|
+
BULLETIN_DEPLOY_ENV=paseo-next-v2 \
|
|
125
150
|
npm run test:e2e
|
|
151
|
+
# Previously named DOTNS_ENV; kept as deprecated alias for one release.
|
|
126
152
|
```
|
|
127
153
|
|
|
128
154
|
Vary `E2E_SCENARIO` (`s1`, `s2`, `s3`), `E2E_SIGNER` (`pool`, `direct`), and `E2E_MERKLE` (`js`, `kubo`) to cover the full CI matrix.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bulletin-deploy",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.22-rc.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"assets"
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
|
-
"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/mirror.ts --format esm --dts --clean --target node22",
|
|
33
|
+
"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/mirror.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/reprove.ts src/personhood/bind-personal-id.ts src/personhood/claim-pgas.ts src/personhood/bind-paid-alias.ts src/personhood/bootstrap.ts --format esm --dts --clean --target node22",
|
|
34
34
|
"refresh-environments": "node scripts/refresh-environments.mjs",
|
|
35
35
|
"check:watched-dependencies": "node tools/check-watched-dependencies.mjs",
|
|
36
36
|
"prepare": "npm run build",
|
|
37
|
-
"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/watched-dependencies.test.js",
|
|
37
|
+
"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/watched-dependencies.test.js test/chunk-sharing-report.test.js",
|
|
38
38
|
"test:e2e": "npm run build && node --test test/e2e.test.js",
|
|
39
39
|
"test:e2e:smoke": "bash scripts/e2e-pass.sh smoke",
|
|
40
40
|
"test:e2e:pr": "bash scripts/e2e-pass.sh pr",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"ipfs-unixfs-importer": "^16.1.4",
|
|
57
57
|
"multiformats": "^13.4.1",
|
|
58
58
|
"polkadot-api": "^2.1.3",
|
|
59
|
+
"verifiablejs": "^1.2.0",
|
|
59
60
|
"viem": "^2.30.5"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|