@totemsdk/wots-lease 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -21
- package/NOTICE +60 -0
- package/dist/axia.js +21 -25
- package/dist/certificate.js +7 -12
- package/dist/cjs/axia.d.ts +33 -0
- package/dist/cjs/axia.js +145 -0
- package/dist/cjs/certificate.d.ts +37 -0
- package/dist/cjs/certificate.js +74 -0
- package/dist/cjs/device.d.ts +18 -0
- package/dist/cjs/device.js +34 -0
- package/dist/cjs/errors.d.ts +71 -0
- package/dist/cjs/errors.js +135 -0
- package/dist/cjs/hybrid.d.ts +37 -0
- package/dist/cjs/hybrid.js +145 -0
- package/dist/cjs/index.d.ts +17 -0
- package/dist/cjs/index.js +45 -0
- package/dist/cjs/journal.d.ts +30 -0
- package/dist/cjs/journal.js +369 -0
- package/dist/cjs/local.d.ts +83 -0
- package/dist/cjs/local.js +440 -0
- package/dist/cjs/onchain.d.ts +54 -0
- package/dist/cjs/onchain.js +314 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/quorum.d.ts +75 -0
- package/dist/cjs/quorum.js +307 -0
- package/dist/cjs/replicated-backup.d.ts +30 -0
- package/dist/cjs/replicated-backup.js +52 -0
- package/dist/cjs/seed.d.ts +41 -0
- package/dist/cjs/seed.js +84 -0
- package/dist/cjs/stubs.d.ts +50 -0
- package/dist/cjs/stubs.js +125 -0
- package/dist/cjs/types.d.ts +272 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/watermark.d.ts +51 -0
- package/dist/cjs/watermark.js +328 -0
- package/dist/device.js +2 -6
- package/dist/errors.js +14 -31
- package/dist/hybrid.js +4 -8
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -43
- package/dist/journal.js +35 -38
- package/dist/local.js +29 -33
- package/dist/onchain.js +23 -27
- package/dist/quorum.js +9 -13
- package/dist/replicated-backup.d.ts +30 -0
- package/dist/replicated-backup.js +48 -0
- package/dist/seed.js +7 -11
- package/dist/stubs.js +5 -11
- package/dist/types.js +1 -2
- package/dist/watermark.js +11 -18
- package/package.json +22 -13
- package/rust/pkg/package.json +2 -2
- package/rust/pkg/wots_lease_wasm_bg.wasm +0 -0
- package/rust/pkg-node/package.json +2 -2
- package/rust/pkg-node/wots_lease_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WotsWatermarkStore — canonical v3 implementation
|
|
4
|
+
*
|
|
5
|
+
* - Reads v1 (flat usedIndices) and v2 (per-address usedIndices) automatically on load.
|
|
6
|
+
* - Writes only v3.
|
|
7
|
+
* - Enforces monotonicity: unavailable map can only grow, cursors can only advance.
|
|
8
|
+
* - Storage key unchanged ('totem_wots_watermark') to avoid storage churn.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.WotsWatermarkStore = void 0;
|
|
12
|
+
exports.flatIndex = flatIndex;
|
|
13
|
+
exports.fromFlatIndex = fromFlatIndex;
|
|
14
|
+
exports.assertValidSigningIndices = assertValidSigningIndices;
|
|
15
|
+
const core_1 = require("@totemsdk/core");
|
|
16
|
+
const errors_js_1 = require("./errors.js");
|
|
17
|
+
const STORAGE_KEY = 'totem_wots_watermark';
|
|
18
|
+
const MAX_L = 64;
|
|
19
|
+
const CAPACITY_PER_TREE = MAX_L * MAX_L * MAX_L;
|
|
20
|
+
const DEFAULT_TREE = 'default';
|
|
21
|
+
function flatIndex(idx) {
|
|
22
|
+
assertValidSigningIndices(idx);
|
|
23
|
+
return idx.addressIndex * MAX_L * MAX_L + idx.l1 * MAX_L + idx.l2;
|
|
24
|
+
}
|
|
25
|
+
function fromFlatIndex(flat) {
|
|
26
|
+
if (!Number.isSafeInteger(flat) || flat < 0 || flat >= CAPACITY_PER_TREE) {
|
|
27
|
+
throw new RangeError(`WOTS flat index must be an integer in [0, ${CAPACITY_PER_TREE - 1}]`);
|
|
28
|
+
}
|
|
29
|
+
const addressIndex = Math.floor(flat / (MAX_L * MAX_L));
|
|
30
|
+
const rem = flat % (MAX_L * MAX_L);
|
|
31
|
+
const l1 = Math.floor(rem / MAX_L);
|
|
32
|
+
const l2 = rem % MAX_L;
|
|
33
|
+
return { addressIndex, l1, l2 };
|
|
34
|
+
}
|
|
35
|
+
function assertValidSigningIndices(indices) {
|
|
36
|
+
for (const name of ['addressIndex', 'l1', 'l2']) {
|
|
37
|
+
const value = indices[name];
|
|
38
|
+
if (!Number.isSafeInteger(value) || value < 0 || value >= MAX_L) {
|
|
39
|
+
throw new RangeError(`WOTS ${name} must be an integer in [0, ${MAX_L - 1}]`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function emptyTree(treeId) {
|
|
44
|
+
return { treeId, addressCursor: 0, l1Cursor: 0, l2Cursor: 0, unavailable: {} };
|
|
45
|
+
}
|
|
46
|
+
function nextIndices(cur) {
|
|
47
|
+
let { addressIndex, l1, l2 } = cur;
|
|
48
|
+
l2++;
|
|
49
|
+
if (l2 >= MAX_L) {
|
|
50
|
+
l2 = 0;
|
|
51
|
+
l1++;
|
|
52
|
+
}
|
|
53
|
+
if (l1 >= MAX_L) {
|
|
54
|
+
l1 = 0;
|
|
55
|
+
addressIndex++;
|
|
56
|
+
}
|
|
57
|
+
if (addressIndex >= MAX_L)
|
|
58
|
+
return null;
|
|
59
|
+
return { addressIndex, l1, l2 };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Monotonic merge of two watermark states: the cursor is the further of the
|
|
63
|
+
* two and the unavailable maps are unioned, so merging never re-exposes a used
|
|
64
|
+
* index. Used to pick up writes made by another provider over the same storage
|
|
65
|
+
* (AUD-004).
|
|
66
|
+
*/
|
|
67
|
+
function mergeWatermarkState(a, b) {
|
|
68
|
+
if (!a)
|
|
69
|
+
return b;
|
|
70
|
+
const trees = { ...a.trees };
|
|
71
|
+
for (const [treeId, bt] of Object.entries(b.trees)) {
|
|
72
|
+
const at = trees[treeId];
|
|
73
|
+
if (!at) {
|
|
74
|
+
trees[treeId] = bt;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const aFlat = flatIndex({ addressIndex: at.addressCursor, l1: at.l1Cursor, l2: at.l2Cursor });
|
|
78
|
+
const bFlat = flatIndex({ addressIndex: bt.addressCursor, l1: bt.l1Cursor, l2: bt.l2Cursor });
|
|
79
|
+
const cursor = bFlat > aFlat ? bt : at;
|
|
80
|
+
const lastSync = Math.max(at.lastSyncTimestamp ?? 0, bt.lastSyncTimestamp ?? 0);
|
|
81
|
+
trees[treeId] = {
|
|
82
|
+
treeId,
|
|
83
|
+
addressCursor: cursor.addressCursor,
|
|
84
|
+
l1Cursor: cursor.l1Cursor,
|
|
85
|
+
l2Cursor: cursor.l2Cursor,
|
|
86
|
+
unavailable: { ...bt.unavailable, ...at.unavailable },
|
|
87
|
+
...(lastSync > 0 ? { lastSyncTimestamp: lastSync } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { version: 3, trees };
|
|
91
|
+
}
|
|
92
|
+
function migrateToV3(raw) {
|
|
93
|
+
if (!raw) {
|
|
94
|
+
return { version: 3, trees: {} };
|
|
95
|
+
}
|
|
96
|
+
if ('version' in raw && raw.version === 3) {
|
|
97
|
+
return raw;
|
|
98
|
+
}
|
|
99
|
+
if ('version' in raw && raw.version === 2) {
|
|
100
|
+
const v2 = raw;
|
|
101
|
+
const tree = emptyTree(DEFAULT_TREE);
|
|
102
|
+
let maxFlat = -1;
|
|
103
|
+
for (const [addrIdxStr, addrData] of Object.entries(v2.addresses)) {
|
|
104
|
+
const addressIndex = Number(addrIdxStr);
|
|
105
|
+
for (const [l1, l2] of addrData.usedIndices) {
|
|
106
|
+
const f = flatIndex({ addressIndex, l1, l2 });
|
|
107
|
+
tree.unavailable[f] = 'committed';
|
|
108
|
+
if (f > maxFlat)
|
|
109
|
+
maxFlat = f;
|
|
110
|
+
}
|
|
111
|
+
const curFlat = flatIndex({ addressIndex, l1: addrData.next_l1, l2: addrData.next_l2 });
|
|
112
|
+
if (curFlat > maxFlat)
|
|
113
|
+
maxFlat = curFlat;
|
|
114
|
+
}
|
|
115
|
+
if (maxFlat >= 0) {
|
|
116
|
+
const cur = fromFlatIndex(maxFlat);
|
|
117
|
+
tree.addressCursor = cur.addressIndex;
|
|
118
|
+
tree.l1Cursor = cur.l1;
|
|
119
|
+
tree.l2Cursor = cur.l2;
|
|
120
|
+
}
|
|
121
|
+
return { version: 3, trees: { [DEFAULT_TREE]: tree } };
|
|
122
|
+
}
|
|
123
|
+
const v1 = raw;
|
|
124
|
+
const tree = emptyTree(DEFAULT_TREE);
|
|
125
|
+
for (const [ai, l1, l2] of v1.usedIndices ?? []) {
|
|
126
|
+
const f = flatIndex({ addressIndex: ai, l1, l2 });
|
|
127
|
+
tree.unavailable[f] = 'committed';
|
|
128
|
+
}
|
|
129
|
+
tree.addressCursor = v1.next_addressIndex ?? 0;
|
|
130
|
+
tree.l1Cursor = v1.next_l1 ?? 0;
|
|
131
|
+
tree.l2Cursor = v1.next_l2 ?? 0;
|
|
132
|
+
return { version: 3, trees: { [DEFAULT_TREE]: tree } };
|
|
133
|
+
}
|
|
134
|
+
class WotsWatermarkStore {
|
|
135
|
+
constructor(storage, logger = new core_1.NoopLogger()) {
|
|
136
|
+
this.storage = storage;
|
|
137
|
+
this.state = null;
|
|
138
|
+
this._initialized = false;
|
|
139
|
+
this.logger = logger;
|
|
140
|
+
}
|
|
141
|
+
async initialize() {
|
|
142
|
+
if (this._initialized)
|
|
143
|
+
return;
|
|
144
|
+
const raw = await this.storage.get(STORAGE_KEY);
|
|
145
|
+
this.state = migrateToV3(raw);
|
|
146
|
+
if (!('version' in (raw ?? {})) || raw.version !== 3) {
|
|
147
|
+
await this.persist();
|
|
148
|
+
this.logger.info('[WotsWatermarkStore] Migrated to v3');
|
|
149
|
+
}
|
|
150
|
+
this._initialized = true;
|
|
151
|
+
}
|
|
152
|
+
ensureInit() {
|
|
153
|
+
if (!this.state)
|
|
154
|
+
throw new Error('WotsWatermarkStore not initialized — call initialize() first');
|
|
155
|
+
return this.state;
|
|
156
|
+
}
|
|
157
|
+
async persist() {
|
|
158
|
+
await this.storage.set(STORAGE_KEY, this.state);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Reload the persisted watermark and merge it monotonically into the local
|
|
162
|
+
* state. A provider that shares its storage with another provider (AUD-004)
|
|
163
|
+
* must call this inside its mutation critical section before allocating or
|
|
164
|
+
* marking, so it observes writes it did not make and never re-issues a slot
|
|
165
|
+
* another instance already took.
|
|
166
|
+
*/
|
|
167
|
+
async refresh() {
|
|
168
|
+
if (!this._initialized || !this.state)
|
|
169
|
+
return;
|
|
170
|
+
const raw = await this.storage.get(STORAGE_KEY);
|
|
171
|
+
this.state = mergeWatermarkState(this.state, migrateToV3(raw));
|
|
172
|
+
}
|
|
173
|
+
getOrCreateTree(treeId) {
|
|
174
|
+
const s = this.ensureInit();
|
|
175
|
+
if (!s.trees[treeId]) {
|
|
176
|
+
s.trees[treeId] = emptyTree(treeId);
|
|
177
|
+
}
|
|
178
|
+
return s.trees[treeId];
|
|
179
|
+
}
|
|
180
|
+
getNextIndices(treeId) {
|
|
181
|
+
const tree = this.getOrCreateTree(treeId);
|
|
182
|
+
let cur = {
|
|
183
|
+
addressIndex: tree.addressCursor,
|
|
184
|
+
l1: tree.l1Cursor,
|
|
185
|
+
l2: tree.l2Cursor,
|
|
186
|
+
};
|
|
187
|
+
let attempts = 0;
|
|
188
|
+
while (attempts < CAPACITY_PER_TREE) {
|
|
189
|
+
if (!tree.unavailable[flatIndex(cur)])
|
|
190
|
+
return cur;
|
|
191
|
+
const nxt = nextIndices(cur);
|
|
192
|
+
if (!nxt)
|
|
193
|
+
break;
|
|
194
|
+
cur = nxt;
|
|
195
|
+
attempts++;
|
|
196
|
+
}
|
|
197
|
+
throw new errors_js_1.WatermarkExhaustedError(treeId);
|
|
198
|
+
}
|
|
199
|
+
async markUnavailable(treeId, indices, reason) {
|
|
200
|
+
const tree = this.getOrCreateTree(treeId);
|
|
201
|
+
const f = flatIndex(indices);
|
|
202
|
+
tree.unavailable[f] = reason;
|
|
203
|
+
const nxt = nextIndices(indices);
|
|
204
|
+
if (nxt) {
|
|
205
|
+
const nxtFlat = flatIndex(nxt);
|
|
206
|
+
const curFlat = flatIndex({ addressIndex: tree.addressCursor, l1: tree.l1Cursor, l2: tree.l2Cursor });
|
|
207
|
+
if (nxtFlat > curFlat) {
|
|
208
|
+
tree.addressCursor = nxt.addressIndex;
|
|
209
|
+
tree.l1Cursor = nxt.l1;
|
|
210
|
+
tree.l2Cursor = nxt.l2;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
await this.persist();
|
|
214
|
+
}
|
|
215
|
+
isUnavailable(treeId, indices) {
|
|
216
|
+
const s = this.ensureInit();
|
|
217
|
+
const tree = s.trees[treeId];
|
|
218
|
+
if (!tree)
|
|
219
|
+
return false;
|
|
220
|
+
const index = flatIndex(indices);
|
|
221
|
+
const cursor = flatIndex({
|
|
222
|
+
addressIndex: tree.addressCursor,
|
|
223
|
+
l1: tree.l1Cursor,
|
|
224
|
+
l2: tree.l2Cursor,
|
|
225
|
+
});
|
|
226
|
+
return index < cursor || index in tree.unavailable;
|
|
227
|
+
}
|
|
228
|
+
async save(treeId, patch) {
|
|
229
|
+
const s = this.ensureInit();
|
|
230
|
+
const existing = s.trees[treeId] ?? emptyTree(treeId);
|
|
231
|
+
const merged = { ...existing, ...patch, treeId };
|
|
232
|
+
const existingFlat = flatIndex({ addressIndex: existing.addressCursor, l1: existing.l1Cursor, l2: existing.l2Cursor });
|
|
233
|
+
const newFlat = flatIndex({ addressIndex: merged.addressCursor, l1: merged.l1Cursor, l2: merged.l2Cursor });
|
|
234
|
+
if (newFlat < existingFlat) {
|
|
235
|
+
throw new errors_js_1.WatermarkMonotonicityError(`Cannot decrease watermark cursor for ${treeId}: ${existingFlat} → ${newFlat}`);
|
|
236
|
+
}
|
|
237
|
+
const existingUnavailCount = Object.keys(existing.unavailable).length;
|
|
238
|
+
const newUnavailCount = Object.keys(merged.unavailable).length;
|
|
239
|
+
if (newUnavailCount < existingUnavailCount) {
|
|
240
|
+
throw new errors_js_1.WatermarkMonotonicityError(`Cannot shrink unavailable map for ${treeId}: ${existingUnavailCount} → ${newUnavailCount}`);
|
|
241
|
+
}
|
|
242
|
+
s.trees[treeId] = merged;
|
|
243
|
+
await this.persist();
|
|
244
|
+
}
|
|
245
|
+
getLocalWatermark(treeId) {
|
|
246
|
+
const s = this.ensureInit();
|
|
247
|
+
const tree = s.trees[treeId] ?? emptyTree(treeId);
|
|
248
|
+
return {
|
|
249
|
+
treeId,
|
|
250
|
+
addressCursor: tree.addressCursor,
|
|
251
|
+
l1Cursor: tree.l1Cursor,
|
|
252
|
+
l2Cursor: tree.l2Cursor,
|
|
253
|
+
unavailableCount: Object.keys(tree.unavailable).length,
|
|
254
|
+
capacity: CAPACITY_PER_TREE,
|
|
255
|
+
lastSyncTimestamp: tree.lastSyncTimestamp,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Export a portable seed for a tree (RFC-013 §9). Includes the cursor and the
|
|
260
|
+
* flat indices already consumed, so it can rebuild the no-reuse state exactly.
|
|
261
|
+
*/
|
|
262
|
+
exportSeed(treeId) {
|
|
263
|
+
const s = this.ensureInit();
|
|
264
|
+
const tree = s.trees[treeId] ?? emptyTree(treeId);
|
|
265
|
+
const unavailable = Object.keys(tree.unavailable)
|
|
266
|
+
.map((k) => Number(k))
|
|
267
|
+
.filter((f) => Number.isSafeInteger(f))
|
|
268
|
+
.sort((a, b) => a - b);
|
|
269
|
+
return {
|
|
270
|
+
version: 1,
|
|
271
|
+
treeId,
|
|
272
|
+
addressCursor: tree.addressCursor,
|
|
273
|
+
l1Cursor: tree.l1Cursor,
|
|
274
|
+
l2Cursor: tree.l2Cursor,
|
|
275
|
+
unavailable,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Seed the watermark from an exported/operator/on-chain snapshot. Forward-only
|
|
280
|
+
* by default: a seed behind the current cursor raises
|
|
281
|
+
* {@link WatermarkSeedRegressionError} so a restore can never re-expose used
|
|
282
|
+
* leaves. The unavailable set is unioned, never shrunk.
|
|
283
|
+
*/
|
|
284
|
+
async seed(seed, options = {}) {
|
|
285
|
+
const s = this.ensureInit();
|
|
286
|
+
const existing = s.trees[seed.treeId] ?? emptyTree(seed.treeId);
|
|
287
|
+
const seedCursor = flatIndex({
|
|
288
|
+
addressIndex: seed.addressCursor,
|
|
289
|
+
l1: seed.l1Cursor,
|
|
290
|
+
l2: seed.l2Cursor,
|
|
291
|
+
});
|
|
292
|
+
const existingCursor = flatIndex({
|
|
293
|
+
addressIndex: existing.addressCursor,
|
|
294
|
+
l1: existing.l1Cursor,
|
|
295
|
+
l2: existing.l2Cursor,
|
|
296
|
+
});
|
|
297
|
+
if (!options.allowRegression && seedCursor < existingCursor) {
|
|
298
|
+
throw new errors_js_1.WatermarkSeedRegressionError(seed.treeId, existingCursor, seedCursor);
|
|
299
|
+
}
|
|
300
|
+
const unavailable = { ...existing.unavailable };
|
|
301
|
+
for (const flat of seed.unavailable ?? []) {
|
|
302
|
+
if (!Number.isSafeInteger(flat) || flat < 0 || flat >= CAPACITY_PER_TREE) {
|
|
303
|
+
throw new RangeError(`Seed unavailable index must be an integer in [0, ${CAPACITY_PER_TREE - 1}]`);
|
|
304
|
+
}
|
|
305
|
+
if (!(flat in unavailable))
|
|
306
|
+
unavailable[flat] = 'committed';
|
|
307
|
+
}
|
|
308
|
+
s.trees[seed.treeId] = {
|
|
309
|
+
...existing,
|
|
310
|
+
treeId: seed.treeId,
|
|
311
|
+
addressCursor: seed.addressCursor,
|
|
312
|
+
l1Cursor: seed.l1Cursor,
|
|
313
|
+
l2Cursor: seed.l2Cursor,
|
|
314
|
+
unavailable,
|
|
315
|
+
lastSyncTimestamp: Date.now(),
|
|
316
|
+
};
|
|
317
|
+
await this.persist();
|
|
318
|
+
}
|
|
319
|
+
getRawState() {
|
|
320
|
+
return this.ensureInit();
|
|
321
|
+
}
|
|
322
|
+
async clear() {
|
|
323
|
+
this.state = { version: 3, trees: {} };
|
|
324
|
+
this._initialized = false;
|
|
325
|
+
await this.storage.remove(STORAGE_KEY);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
exports.WotsWatermarkStore = WotsWatermarkStore;
|
package/dist/device.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Device keyspace partitioning — Layer 2.
|
|
4
3
|
*
|
|
@@ -11,12 +10,9 @@
|
|
|
11
10
|
* device 2 → addressIndex 16–23
|
|
12
11
|
* ...
|
|
13
12
|
*/
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.allocateDeviceRange = allocateDeviceRange;
|
|
16
|
-
exports.deviceSlotForAddressIndex = deviceSlotForAddressIndex;
|
|
17
13
|
const ADDRESSES_PER_DEVICE = 8;
|
|
18
14
|
const MAX_DEVICE_SLOTS = 8;
|
|
19
|
-
function allocateDeviceRange(params) {
|
|
15
|
+
export function allocateDeviceRange(params) {
|
|
20
16
|
if (params.deviceSlot < 0 || params.deviceSlot >= MAX_DEVICE_SLOTS) {
|
|
21
17
|
throw new RangeError(`Device slot must be 0–${MAX_DEVICE_SLOTS - 1}, got ${params.deviceSlot}`);
|
|
22
18
|
}
|
|
@@ -29,6 +25,6 @@ function allocateDeviceRange(params) {
|
|
|
29
25
|
addressCount: ADDRESSES_PER_DEVICE,
|
|
30
26
|
};
|
|
31
27
|
}
|
|
32
|
-
function deviceSlotForAddressIndex(addressIndex) {
|
|
28
|
+
export function deviceSlotForAddressIndex(addressIndex) {
|
|
33
29
|
return Math.floor(addressIndex / ADDRESSES_PER_DEVICE);
|
|
34
30
|
}
|
package/dist/errors.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DeviceRangeViolationError = exports.OnchainWatermarkError = exports.QuorumConflictError = exports.QuorumUnavailableError = exports.OnchainWatermarkNotImplementedError = exports.P2PQuorumNotImplementedError = exports.PersonalLeaseNodeNotConfiguredError = exports.IndicesUnavailableError = exports.InvalidLeaseTransitionError = exports.LeaseNotFoundError = exports.WatermarkExhaustedError = exports.WatermarkSeedRequiredError = exports.WatermarkSeedRegressionError = exports.WatermarkMonotonicityError = void 0;
|
|
4
|
-
class WatermarkMonotonicityError extends Error {
|
|
1
|
+
export class WatermarkMonotonicityError extends Error {
|
|
5
2
|
constructor(message) {
|
|
6
3
|
super(message);
|
|
7
4
|
this.name = 'WatermarkMonotonicityError';
|
|
8
5
|
}
|
|
9
6
|
}
|
|
10
|
-
exports.WatermarkMonotonicityError = WatermarkMonotonicityError;
|
|
11
7
|
/**
|
|
12
8
|
* Raised when seeding would move the local watermark cursor backwards. This is
|
|
13
9
|
* the fail-closed guard against restoring a wallet below its true high-water
|
|
14
10
|
* mark and reusing a WOTS leaf (RFC-013 §9).
|
|
15
11
|
*/
|
|
16
|
-
class WatermarkSeedRegressionError extends Error {
|
|
12
|
+
export class WatermarkSeedRegressionError extends Error {
|
|
17
13
|
constructor(treeId, currentCursor, seedCursor) {
|
|
18
14
|
super(`Refusing to seed watermark for ${treeId} below the current cursor ` +
|
|
19
15
|
`(${seedCursor} < ${currentCursor}); this would re-expose used WOTS leaves.`);
|
|
@@ -23,13 +19,12 @@ class WatermarkSeedRegressionError extends Error {
|
|
|
23
19
|
this.name = 'WatermarkSeedRegressionError';
|
|
24
20
|
}
|
|
25
21
|
}
|
|
26
|
-
exports.WatermarkSeedRegressionError = WatermarkSeedRegressionError;
|
|
27
22
|
/**
|
|
28
23
|
* Raised when a restore/import has no usable watermark source and the caller has
|
|
29
24
|
* not declared the keyspace fresh. Failing closed is required: starting at 0
|
|
30
25
|
* would reissue spent leaves (RFC-013 §9).
|
|
31
26
|
*/
|
|
32
|
-
class WatermarkSeedRequiredError extends Error {
|
|
27
|
+
export class WatermarkSeedRequiredError extends Error {
|
|
33
28
|
constructor(treeId) {
|
|
34
29
|
super(`No watermark source available for ${treeId}. Provide an on-chain cursor, an ` +
|
|
35
30
|
'exported watermark, or an operator high-water mark — or declare the keyspace fresh. ' +
|
|
@@ -38,24 +33,21 @@ class WatermarkSeedRequiredError extends Error {
|
|
|
38
33
|
this.name = 'WatermarkSeedRequiredError';
|
|
39
34
|
}
|
|
40
35
|
}
|
|
41
|
-
|
|
42
|
-
class WatermarkExhaustedError extends Error {
|
|
36
|
+
export class WatermarkExhaustedError extends Error {
|
|
43
37
|
constructor(treeId) {
|
|
44
38
|
super(`WOTS keyspace exhausted for tree: ${treeId}`);
|
|
45
39
|
this.treeId = treeId;
|
|
46
40
|
this.name = 'WatermarkExhaustedError';
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
|
-
|
|
50
|
-
class LeaseNotFoundError extends Error {
|
|
43
|
+
export class LeaseNotFoundError extends Error {
|
|
51
44
|
constructor(reservationId) {
|
|
52
45
|
super(`Lease reservation not found: ${reservationId}`);
|
|
53
46
|
this.reservationId = reservationId;
|
|
54
47
|
this.name = 'LeaseNotFoundError';
|
|
55
48
|
}
|
|
56
49
|
}
|
|
57
|
-
|
|
58
|
-
class InvalidLeaseTransitionError extends Error {
|
|
50
|
+
export class InvalidLeaseTransitionError extends Error {
|
|
59
51
|
constructor(reservationId, currentStatus, requestedStatus) {
|
|
60
52
|
super(`Cannot transition lease ${reservationId} from ${currentStatus} to ${requestedStatus}`);
|
|
61
53
|
this.reservationId = reservationId;
|
|
@@ -64,8 +56,7 @@ class InvalidLeaseTransitionError extends Error {
|
|
|
64
56
|
this.name = 'InvalidLeaseTransitionError';
|
|
65
57
|
}
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
class IndicesUnavailableError extends Error {
|
|
59
|
+
export class IndicesUnavailableError extends Error {
|
|
69
60
|
constructor(treeId, indices) {
|
|
70
61
|
super(`Indices (${indices.addressIndex}, ${indices.l1}, ${indices.l2}) are already unavailable for tree: ${treeId}`);
|
|
71
62
|
this.treeId = treeId;
|
|
@@ -73,30 +64,26 @@ class IndicesUnavailableError extends Error {
|
|
|
73
64
|
this.name = 'IndicesUnavailableError';
|
|
74
65
|
}
|
|
75
66
|
}
|
|
76
|
-
|
|
77
|
-
class PersonalLeaseNodeNotConfiguredError extends Error {
|
|
67
|
+
export class PersonalLeaseNodeNotConfiguredError extends Error {
|
|
78
68
|
constructor() {
|
|
79
69
|
super('PersonalLeaseNodeProvider requires a running lookup node. ' +
|
|
80
70
|
'Deploy @totemsdk/lookup-node and configure { nodeUrl, nodePubkey }.');
|
|
81
71
|
this.name = 'PersonalLeaseNodeNotConfiguredError';
|
|
82
72
|
}
|
|
83
73
|
}
|
|
84
|
-
|
|
85
|
-
class P2PQuorumNotImplementedError extends Error {
|
|
74
|
+
export class P2PQuorumNotImplementedError extends Error {
|
|
86
75
|
constructor() {
|
|
87
76
|
super('P2PQuorumLeaseProvider is not yet implemented.');
|
|
88
77
|
this.name = 'P2PQuorumNotImplementedError';
|
|
89
78
|
}
|
|
90
79
|
}
|
|
91
|
-
|
|
92
|
-
class OnchainWatermarkNotImplementedError extends Error {
|
|
80
|
+
export class OnchainWatermarkNotImplementedError extends Error {
|
|
93
81
|
constructor() {
|
|
94
82
|
super('OnchainWatermarkProvider is not yet implemented.');
|
|
95
83
|
this.name = 'OnchainWatermarkNotImplementedError';
|
|
96
84
|
}
|
|
97
85
|
}
|
|
98
|
-
|
|
99
|
-
class QuorumUnavailableError extends Error {
|
|
86
|
+
export class QuorumUnavailableError extends Error {
|
|
100
87
|
constructor(required, available) {
|
|
101
88
|
super(`Quorum not reached: ${available}/${required} peers attested. ` +
|
|
102
89
|
'The reservation was burned locally — retry when more peers are reachable.');
|
|
@@ -105,8 +92,7 @@ class QuorumUnavailableError extends Error {
|
|
|
105
92
|
this.name = 'QuorumUnavailableError';
|
|
106
93
|
}
|
|
107
94
|
}
|
|
108
|
-
|
|
109
|
-
class QuorumConflictError extends Error {
|
|
95
|
+
export class QuorumConflictError extends Error {
|
|
110
96
|
constructor(treeId, indices) {
|
|
111
97
|
super(`Quorum conflict for tree ${treeId} at (${indices.addressIndex}, ${indices.l1}, ${indices.l2}): ` +
|
|
112
98
|
'a peer already holds a reservation for these indices.');
|
|
@@ -115,15 +101,13 @@ class QuorumConflictError extends Error {
|
|
|
115
101
|
this.name = 'QuorumConflictError';
|
|
116
102
|
}
|
|
117
103
|
}
|
|
118
|
-
|
|
119
|
-
class OnchainWatermarkError extends Error {
|
|
104
|
+
export class OnchainWatermarkError extends Error {
|
|
120
105
|
constructor(message) {
|
|
121
106
|
super(message);
|
|
122
107
|
this.name = 'OnchainWatermarkError';
|
|
123
108
|
}
|
|
124
109
|
}
|
|
125
|
-
|
|
126
|
-
class DeviceRangeViolationError extends Error {
|
|
110
|
+
export class DeviceRangeViolationError extends Error {
|
|
127
111
|
constructor(addressIndex, allowedStart, allowedEnd) {
|
|
128
112
|
super(`Address index ${addressIndex} is outside device range [${allowedStart}, ${allowedEnd}]`);
|
|
129
113
|
this.addressIndex = addressIndex;
|
|
@@ -132,4 +116,3 @@ class DeviceRangeViolationError extends Error {
|
|
|
132
116
|
this.name = 'DeviceRangeViolationError';
|
|
133
117
|
}
|
|
134
118
|
}
|
|
135
|
-
exports.DeviceRangeViolationError = DeviceRangeViolationError;
|
package/dist/hybrid.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* HybridLeaseProvider — recommended production default.
|
|
4
3
|
*
|
|
5
4
|
* Always uses local first, then gates on node cert and/or quorum attestation
|
|
6
5
|
* for transactions above a configurable value threshold.
|
|
7
6
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const certificate_js_1 = require("./certificate.js");
|
|
11
|
-
class HybridLeaseProvider {
|
|
7
|
+
import { signCertificate, certificateSignatureVerified } from './certificate.js';
|
|
8
|
+
export class HybridLeaseProvider {
|
|
12
9
|
constructor(config) {
|
|
13
10
|
this.local = config.local;
|
|
14
11
|
this.node = config.node;
|
|
@@ -56,7 +53,7 @@ class HybridLeaseProvider {
|
|
|
56
53
|
attestations,
|
|
57
54
|
};
|
|
58
55
|
certificate.signature = this.certificateSigner
|
|
59
|
-
? await
|
|
56
|
+
? await signCertificate(this.certificateSigner, certificate)
|
|
60
57
|
: '';
|
|
61
58
|
return { ...reservation, certificate };
|
|
62
59
|
}
|
|
@@ -135,11 +132,10 @@ class HybridLeaseProvider {
|
|
|
135
132
|
return this.local.verifyLeaseCertificate(cert);
|
|
136
133
|
// Locally-assembled quorum certificates authenticate against our issuer.
|
|
137
134
|
if (cert.issuedBy === (this.certificateSigner?.name ?? 'p2p-quorum')) {
|
|
138
|
-
return
|
|
135
|
+
return certificateSignatureVerified(cert, this.certificateSigner);
|
|
139
136
|
}
|
|
140
137
|
if (this.node)
|
|
141
138
|
return this.node.verifyLeaseCertificate(cert);
|
|
142
139
|
return false;
|
|
143
140
|
}
|
|
144
141
|
}
|
|
145
|
-
exports.HybridLeaseProvider = HybridLeaseProvider;
|
package/dist/index.d.ts
CHANGED
|
@@ -13,3 +13,5 @@ export { P2PQuorumLeaseProvider as QuorumLeaseProvider } from './quorum.js';
|
|
|
13
13
|
export { OnchainWatermarkProvider as ChainWatermarkProvider } from './onchain.js';
|
|
14
14
|
export { allocateDeviceRange, deviceSlotForAddressIndex } from './device.js';
|
|
15
15
|
export { WatermarkMonotonicityError, WatermarkExhaustedError, WatermarkSeedRegressionError, WatermarkSeedRequiredError, LeaseNotFoundError, IndicesUnavailableError, PersonalLeaseNodeNotConfiguredError, P2PQuorumNotImplementedError, OnchainWatermarkNotImplementedError, QuorumUnavailableError, QuorumConflictError, OnchainWatermarkError, DeviceRangeViolationError, } from './errors.js';
|
|
16
|
+
export { ReplicatedLeaseJournalBackup } from './replicated-backup.js';
|
|
17
|
+
export type { LeaseJournalRestoreTarget } from './replicated-backup.js';
|
package/dist/index.js
CHANGED
|
@@ -1,43 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Object.defineProperty(exports, "seedLeaseWatermark", { enumerable: true, get: function () { return seed_js_1.seedLeaseWatermark; } });
|
|
14
|
-
Object.defineProperty(exports, "exportLeaseWatermark", { enumerable: true, get: function () { return seed_js_1.exportLeaseWatermark; } });
|
|
15
|
-
var axia_js_1 = require("./axia.js");
|
|
16
|
-
Object.defineProperty(exports, "AxiaLeaseProvider", { enumerable: true, get: function () { return axia_js_1.AxiaLeaseProvider; } });
|
|
17
|
-
var hybrid_js_1 = require("./hybrid.js");
|
|
18
|
-
Object.defineProperty(exports, "HybridLeaseProvider", { enumerable: true, get: function () { return hybrid_js_1.HybridLeaseProvider; } });
|
|
19
|
-
var stubs_js_1 = require("./stubs.js");
|
|
20
|
-
Object.defineProperty(exports, "PersonalLeaseNodeProvider", { enumerable: true, get: function () { return stubs_js_1.PersonalLeaseNodeProvider; } });
|
|
21
|
-
Object.defineProperty(exports, "P2PQuorumLeaseProvider", { enumerable: true, get: function () { return stubs_js_1.P2PQuorumLeaseProvider; } });
|
|
22
|
-
Object.defineProperty(exports, "OnchainWatermarkProvider", { enumerable: true, get: function () { return stubs_js_1.OnchainWatermarkProvider; } });
|
|
23
|
-
var quorum_js_1 = require("./quorum.js");
|
|
24
|
-
Object.defineProperty(exports, "QuorumLeaseProvider", { enumerable: true, get: function () { return quorum_js_1.P2PQuorumLeaseProvider; } });
|
|
25
|
-
var onchain_js_1 = require("./onchain.js");
|
|
26
|
-
Object.defineProperty(exports, "ChainWatermarkProvider", { enumerable: true, get: function () { return onchain_js_1.OnchainWatermarkProvider; } });
|
|
27
|
-
var device_js_1 = require("./device.js");
|
|
28
|
-
Object.defineProperty(exports, "allocateDeviceRange", { enumerable: true, get: function () { return device_js_1.allocateDeviceRange; } });
|
|
29
|
-
Object.defineProperty(exports, "deviceSlotForAddressIndex", { enumerable: true, get: function () { return device_js_1.deviceSlotForAddressIndex; } });
|
|
30
|
-
var errors_js_1 = require("./errors.js");
|
|
31
|
-
Object.defineProperty(exports, "WatermarkMonotonicityError", { enumerable: true, get: function () { return errors_js_1.WatermarkMonotonicityError; } });
|
|
32
|
-
Object.defineProperty(exports, "WatermarkExhaustedError", { enumerable: true, get: function () { return errors_js_1.WatermarkExhaustedError; } });
|
|
33
|
-
Object.defineProperty(exports, "WatermarkSeedRegressionError", { enumerable: true, get: function () { return errors_js_1.WatermarkSeedRegressionError; } });
|
|
34
|
-
Object.defineProperty(exports, "WatermarkSeedRequiredError", { enumerable: true, get: function () { return errors_js_1.WatermarkSeedRequiredError; } });
|
|
35
|
-
Object.defineProperty(exports, "LeaseNotFoundError", { enumerable: true, get: function () { return errors_js_1.LeaseNotFoundError; } });
|
|
36
|
-
Object.defineProperty(exports, "IndicesUnavailableError", { enumerable: true, get: function () { return errors_js_1.IndicesUnavailableError; } });
|
|
37
|
-
Object.defineProperty(exports, "PersonalLeaseNodeNotConfiguredError", { enumerable: true, get: function () { return errors_js_1.PersonalLeaseNodeNotConfiguredError; } });
|
|
38
|
-
Object.defineProperty(exports, "P2PQuorumNotImplementedError", { enumerable: true, get: function () { return errors_js_1.P2PQuorumNotImplementedError; } });
|
|
39
|
-
Object.defineProperty(exports, "OnchainWatermarkNotImplementedError", { enumerable: true, get: function () { return errors_js_1.OnchainWatermarkNotImplementedError; } });
|
|
40
|
-
Object.defineProperty(exports, "QuorumUnavailableError", { enumerable: true, get: function () { return errors_js_1.QuorumUnavailableError; } });
|
|
41
|
-
Object.defineProperty(exports, "QuorumConflictError", { enumerable: true, get: function () { return errors_js_1.QuorumConflictError; } });
|
|
42
|
-
Object.defineProperty(exports, "OnchainWatermarkError", { enumerable: true, get: function () { return errors_js_1.OnchainWatermarkError; } });
|
|
43
|
-
Object.defineProperty(exports, "DeviceRangeViolationError", { enumerable: true, get: function () { return errors_js_1.DeviceRangeViolationError; } });
|
|
1
|
+
export { WotsWatermarkStore, flatIndex, fromFlatIndex, } from './watermark.js';
|
|
2
|
+
export { LeaseJournal } from './journal.js';
|
|
3
|
+
export { LocalLeaseProvider } from './local.js';
|
|
4
|
+
export { seedLeaseWatermark, exportLeaseWatermark } from './seed.js';
|
|
5
|
+
export { AxiaLeaseProvider } from './axia.js';
|
|
6
|
+
export { HybridLeaseProvider } from './hybrid.js';
|
|
7
|
+
export { PersonalLeaseNodeProvider, P2PQuorumLeaseProvider, OnchainWatermarkProvider, } from './stubs.js';
|
|
8
|
+
export { P2PQuorumLeaseProvider as QuorumLeaseProvider } from './quorum.js';
|
|
9
|
+
export { OnchainWatermarkProvider as ChainWatermarkProvider } from './onchain.js';
|
|
10
|
+
export { allocateDeviceRange, deviceSlotForAddressIndex } from './device.js';
|
|
11
|
+
export { WatermarkMonotonicityError, WatermarkExhaustedError, WatermarkSeedRegressionError, WatermarkSeedRequiredError, LeaseNotFoundError, IndicesUnavailableError, PersonalLeaseNodeNotConfiguredError, P2PQuorumNotImplementedError, OnchainWatermarkNotImplementedError, QuorumUnavailableError, QuorumConflictError, OnchainWatermarkError, DeviceRangeViolationError, } from './errors.js';
|
|
12
|
+
export { ReplicatedLeaseJournalBackup } from './replicated-backup.js';
|