@totemsdk/core 1.0.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/Streamable.d.ts +268 -0
- package/dist/Streamable.js +416 -0
- package/dist/adapters/index.d.ts +190 -0
- package/dist/adapters/index.js +107 -0
- package/dist/adapters/types.d.ts +149 -0
- package/dist/adapters/types.js +67 -0
- package/dist/binio.d.ts +9 -0
- package/dist/binio.js +30 -0
- package/dist/bip39.d.ts +91 -0
- package/dist/bip39.js +417 -0
- package/dist/derive.d.ts +2 -0
- package/dist/derive.js +9 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +53 -0
- package/dist/javaStreamables.d.ts +233 -0
- package/dist/javaStreamables.js +332 -0
- package/dist/lease/LeaseMonitor.d.ts +44 -0
- package/dist/lease/LeaseMonitor.js +107 -0
- package/dist/lease/LeaseStore.d.ts +57 -0
- package/dist/lease/LeaseStore.js +144 -0
- package/dist/lease/WatermarkStore.d.ts +61 -0
- package/dist/lease/WatermarkStore.js +202 -0
- package/dist/lease/index.d.ts +14 -0
- package/dist/lease/index.js +13 -0
- package/dist/lease-client.d.ts +31 -0
- package/dist/lease-client.js +39 -0
- package/dist/minima32.d.ts +18 -0
- package/dist/minima32.js +112 -0
- package/dist/minimaWireSerializer.d.ts +60 -0
- package/dist/minimaWireSerializer.js +71 -0
- package/dist/mmr.d.ts +156 -0
- package/dist/mmr.js +342 -0
- package/dist/mx.d.ts +1 -0
- package/dist/mx.js +1 -0
- package/dist/params.d.ts +29 -0
- package/dist/params.js +16 -0
- package/dist/script.d.ts +7 -0
- package/dist/script.js +22 -0
- package/dist/scripts/contract-helpers.d.ts +252 -0
- package/dist/scripts/contract-helpers.js +394 -0
- package/dist/scripts/dapp-types.d.ts +119 -0
- package/dist/scripts/dapp-types.js +72 -0
- package/dist/scripts/index.d.ts +4 -0
- package/dist/scripts/index.js +4 -0
- package/dist/scripts/types.d.ts +81 -0
- package/dist/scripts/types.js +145 -0
- package/dist/scripts/witness-serializer.d.ts +29 -0
- package/dist/scripts/witness-serializer.js +288 -0
- package/dist/transaction.d.ts +106 -0
- package/dist/transaction.js +284 -0
- package/dist/treekey.d.ts +383 -0
- package/dist/treekey.js +762 -0
- package/dist/tx/TransactionLifecycle.d.ts +55 -0
- package/dist/tx/TransactionLifecycle.js +189 -0
- package/dist/tx/TransactionReceiptStore.d.ts +36 -0
- package/dist/tx/TransactionReceiptStore.js +85 -0
- package/dist/tx/TransactionService.d.ts +50 -0
- package/dist/tx/TransactionService.js +178 -0
- package/dist/tx/index.d.ts +13 -0
- package/dist/tx/index.js +12 -0
- package/dist/tx/types.d.ts +114 -0
- package/dist/tx/types.js +5 -0
- package/dist/util.d.ts +1 -0
- package/dist/util.js +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +35 -0
- package/dist/verify.d.ts +18 -0
- package/dist/verify.js +162 -0
- package/dist/version.d.ts +12 -0
- package/dist/version.js +12 -0
- package/dist/wots.d.ts +232 -0
- package/dist/wots.js +538 -0
- package/package.json +65 -0
- package/src/Streamable.d.ts +265 -0
- package/src/Streamable.js +431 -0
- package/src/Streamable.ts +567 -0
- package/src/__tests__/Streamable.parity.test.ts +422 -0
- package/src/__tests__/perAddressDerivation.parity.test.ts +152 -0
- package/src/__tests__/wots-parity.test.ts +289 -0
- package/src/adapters/index.d.ts +190 -0
- package/src/adapters/index.js +117 -0
- package/src/adapters/index.ts +301 -0
- package/src/adapters/types.ts +235 -0
- package/src/addr.oracle.test.ts +25 -0
- package/src/address.test.ts +55 -0
- package/src/binio.ts +21 -0
- package/src/bip39.d.ts +91 -0
- package/src/bip39.js +427 -0
- package/src/bip39.ts +432 -0
- package/src/derive.d.ts +2 -0
- package/src/derive.js +13 -0
- package/src/derive.ts +11 -0
- package/src/index.d.ts +26 -0
- package/src/index.js +128 -0
- package/src/index.test.ts +5 -0
- package/src/index.ts +206 -0
- package/src/javaStreamables.d.ts +233 -0
- package/src/javaStreamables.js +328 -0
- package/src/javaStreamables.ts +403 -0
- package/src/lease/LeaseMonitor.d.ts +44 -0
- package/src/lease/LeaseMonitor.js +111 -0
- package/src/lease/LeaseMonitor.ts +157 -0
- package/src/lease/LeaseStore.d.ts +57 -0
- package/src/lease/LeaseStore.js +148 -0
- package/src/lease/LeaseStore.ts +203 -0
- package/src/lease/WatermarkStore.d.ts +61 -0
- package/src/lease/WatermarkStore.js +206 -0
- package/src/lease/WatermarkStore.ts +270 -0
- package/src/lease/index.d.ts +14 -0
- package/src/lease/index.js +22 -0
- package/src/lease/index.ts +41 -0
- package/src/lease-client.d.ts +31 -0
- package/src/lease-client.js +44 -0
- package/src/lease-client.ts +69 -0
- package/src/minima32.d.ts +10 -0
- package/src/minima32.js +113 -0
- package/src/minima32.test.ts +11 -0
- package/src/minima32.ts +127 -0
- package/src/minimaWireSerializer.d.ts +189 -0
- package/src/minimaWireSerializer.js +311 -0
- package/src/minimaWireSerializer.test.ts +466 -0
- package/src/minimaWireSerializer.ts +117 -0
- package/src/mmr.d.ts +153 -0
- package/src/mmr.js +351 -0
- package/src/mmr.oracle.test.ts +28 -0
- package/src/mmr.ts +429 -0
- package/src/mx.test.ts +25 -0
- package/src/mx.ts +1 -0
- package/src/params.d.ts +29 -0
- package/src/params.d.ts.map +1 -0
- package/src/params.js +20 -0
- package/src/params.js.map +1 -0
- package/src/params.ts +44 -0
- package/src/script.d.ts +7 -0
- package/src/script.js +27 -0
- package/src/script.ts +24 -0
- package/src/scripts/contract-helpers.ts +609 -0
- package/src/scripts/dapp-types.ts +240 -0
- package/src/scripts/index.ts +22 -0
- package/src/scripts/types.ts +247 -0
- package/src/scripts/witness-serializer.ts +384 -0
- package/src/transaction.ts +430 -0
- package/src/treekey.d.ts +383 -0
- package/src/treekey.js +778 -0
- package/src/treekey.parity.test.ts +459 -0
- package/src/treekey.ts +979 -0
- package/src/tx/TransactionLifecycle.d.ts +55 -0
- package/src/tx/TransactionLifecycle.js +188 -0
- package/src/tx/TransactionLifecycle.ts +280 -0
- package/src/tx/TransactionReceiptStore.d.ts +36 -0
- package/src/tx/TransactionReceiptStore.js +89 -0
- package/src/tx/TransactionReceiptStore.ts +112 -0
- package/src/tx/TransactionService.d.ts +31 -0
- package/src/tx/TransactionService.js +140 -0
- package/src/tx/TransactionService.ts +250 -0
- package/src/tx/index.d.ts +13 -0
- package/src/tx/index.js +20 -0
- package/src/tx/index.ts +44 -0
- package/src/tx/types.d.ts +80 -0
- package/src/tx/types.js +6 -0
- package/src/tx/types.ts +127 -0
- package/src/util.d.ts +1 -0
- package/src/util.js +5 -0
- package/src/util.ts +1 -0
- package/src/utils.d.ts +8 -0
- package/src/utils.js +42 -0
- package/src/utils.ts +38 -0
- package/src/verify.ts +209 -0
- package/src/version.ts +13 -0
- package/src/wots.d.ts +224 -0
- package/src/wots.d.ts.map +1 -0
- package/src/wots.js +559 -0
- package/src/wots.js.map +1 -0
- package/src/wots.ts +636 -0
- package/test-vectors.json +45 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module LeaseMonitor
|
|
3
|
+
* Monitors lease expiry and triggers renewal/cleanup actions
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Automatic expiry detection with configurable thresholds
|
|
7
|
+
* - Event-based notifications for expiring leases
|
|
8
|
+
* - Adaptive monitoring interval based on shortest TTL
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { TimerAdapter, LoggerAdapter } from '../adapters/index.js';
|
|
12
|
+
import { DefaultTimerAdapter, NoopLogger, type TimerHandle } from '../adapters/index.js';
|
|
13
|
+
import type { LeaseStore, StoredLease } from './LeaseStore.js';
|
|
14
|
+
|
|
15
|
+
export interface LeaseExpiryEvent {
|
|
16
|
+
leaseId: string;
|
|
17
|
+
expiresAt: number;
|
|
18
|
+
remainingMs: number;
|
|
19
|
+
lease: StoredLease;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type LeaseExpiryCallback = (event: LeaseExpiryEvent) => void;
|
|
23
|
+
|
|
24
|
+
export interface LeaseMonitorConfig {
|
|
25
|
+
defaultIntervalMs?: number;
|
|
26
|
+
expiryThresholdMs?: number;
|
|
27
|
+
minIntervalMs?: number;
|
|
28
|
+
maxIntervalMs?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_CONFIG: Required<LeaseMonitorConfig> = {
|
|
32
|
+
defaultIntervalMs: 5000,
|
|
33
|
+
expiryThresholdMs: 5000,
|
|
34
|
+
minIntervalMs: 1000,
|
|
35
|
+
maxIntervalMs: 30000,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class LeaseMonitor {
|
|
39
|
+
private readonly leaseStore: LeaseStore;
|
|
40
|
+
private readonly timer: TimerAdapter;
|
|
41
|
+
private readonly logger: LoggerAdapter;
|
|
42
|
+
private readonly config: Required<LeaseMonitorConfig>;
|
|
43
|
+
|
|
44
|
+
private timerHandle: TimerHandle | null = null;
|
|
45
|
+
private isRunning = false;
|
|
46
|
+
private expiryCallbacks = new Set<LeaseExpiryCallback>();
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
leaseStore: LeaseStore,
|
|
50
|
+
timer: TimerAdapter = new DefaultTimerAdapter(),
|
|
51
|
+
logger: LoggerAdapter = new NoopLogger(),
|
|
52
|
+
config: LeaseMonitorConfig = {}
|
|
53
|
+
) {
|
|
54
|
+
this.leaseStore = leaseStore;
|
|
55
|
+
this.timer = timer;
|
|
56
|
+
this.logger = logger;
|
|
57
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
start(): void {
|
|
61
|
+
if (this.isRunning) {
|
|
62
|
+
this.logger.debug('LeaseMonitor already running');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.isRunning = true;
|
|
67
|
+
this.scheduleNextCheck();
|
|
68
|
+
this.logger.debug('LeaseMonitor started');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
stop(): void {
|
|
72
|
+
if (!this.isRunning) return;
|
|
73
|
+
|
|
74
|
+
this.isRunning = false;
|
|
75
|
+
if (this.timerHandle !== null) {
|
|
76
|
+
this.timer.clearTimeout(this.timerHandle);
|
|
77
|
+
this.timerHandle = null;
|
|
78
|
+
}
|
|
79
|
+
this.logger.debug('LeaseMonitor stopped');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
isActive(): boolean {
|
|
83
|
+
return this.isRunning;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
onExpirySoon(callback: LeaseExpiryCallback): () => void {
|
|
87
|
+
this.expiryCallbacks.add(callback);
|
|
88
|
+
return () => this.expiryCallbacks.delete(callback);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
removeAllListeners(): void {
|
|
92
|
+
this.expiryCallbacks.clear();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async checkNow(): Promise<LeaseExpiryEvent[]> {
|
|
96
|
+
const now = this.timer.now();
|
|
97
|
+
const expiringSoon = this.leaseStore.getExpiringSoon(this.config.expiryThresholdMs);
|
|
98
|
+
|
|
99
|
+
const events: LeaseExpiryEvent[] = expiringSoon.map(lease => ({
|
|
100
|
+
leaseId: lease.leaseId,
|
|
101
|
+
expiresAt: lease.expiresAt,
|
|
102
|
+
remainingMs: Math.max(0, lease.expiresAt - now),
|
|
103
|
+
lease,
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
for (const event of events) {
|
|
107
|
+
this.notifyExpirySoon(event);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await this.leaseStore.cleanupExpired();
|
|
111
|
+
|
|
112
|
+
return events;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private scheduleNextCheck(): void {
|
|
116
|
+
if (!this.isRunning) return;
|
|
117
|
+
|
|
118
|
+
const interval = this.calculateInterval();
|
|
119
|
+
this.timerHandle = this.timer.setTimeout(() => {
|
|
120
|
+
this.runCheck();
|
|
121
|
+
}, interval);
|
|
122
|
+
|
|
123
|
+
this.logger.debug(`Next lease check scheduled in ${interval}ms`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async runCheck(): Promise<void> {
|
|
127
|
+
if (!this.isRunning) return;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await this.checkNow();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.error('Error during lease check:', error);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.scheduleNextCheck();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private calculateInterval(): number {
|
|
139
|
+
const storeInterval = this.leaseStore.calculateMonitoringInterval();
|
|
140
|
+
const interval = Math.max(
|
|
141
|
+
this.config.minIntervalMs,
|
|
142
|
+
Math.min(storeInterval, this.config.maxIntervalMs)
|
|
143
|
+
);
|
|
144
|
+
return interval;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private notifyExpirySoon(event: LeaseExpiryEvent): void {
|
|
148
|
+
this.logger.debug(`Lease expiring soon: ${event.leaseId}, remaining: ${event.remainingMs}ms`);
|
|
149
|
+
this.expiryCallbacks.forEach(callback => {
|
|
150
|
+
try {
|
|
151
|
+
callback(event);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
this.logger.error('Error in expiry callback:', error);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module LeaseStore
|
|
3
|
+
* WOTS lease lifecycle management with platform-agnostic storage
|
|
4
|
+
*
|
|
5
|
+
* Uses in-memory cache backed by persistent storage.
|
|
6
|
+
* Pattern:
|
|
7
|
+
* - initialize() loads from storage into memory cache
|
|
8
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
9
|
+
* - Mutators update memory cache AND persist to storage
|
|
10
|
+
* - Caller MUST call initialize() before using sync getters
|
|
11
|
+
*/
|
|
12
|
+
import type { StorageAdapter, LoggerAdapter } from '../adapters';
|
|
13
|
+
export interface WotsIndices {
|
|
14
|
+
addressIndex: number;
|
|
15
|
+
l1: number;
|
|
16
|
+
l2: number;
|
|
17
|
+
}
|
|
18
|
+
export type LeaseStatus = 'pending' | 'active' | 'expired' | 'finalized' | 'cancelled';
|
|
19
|
+
export interface StoredLease {
|
|
20
|
+
leaseId: string;
|
|
21
|
+
leaseToken: string;
|
|
22
|
+
indices: WotsIndices;
|
|
23
|
+
expiresAt: number;
|
|
24
|
+
status: LeaseStatus;
|
|
25
|
+
createdAt: number;
|
|
26
|
+
txId?: string;
|
|
27
|
+
leaseTTL: number;
|
|
28
|
+
}
|
|
29
|
+
export interface LeaseStoreConfig {
|
|
30
|
+
storageKey?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class LeaseStore {
|
|
33
|
+
private leases;
|
|
34
|
+
private _initialized;
|
|
35
|
+
private readonly storageKey;
|
|
36
|
+
private readonly storage;
|
|
37
|
+
private readonly logger;
|
|
38
|
+
constructor(storage: StorageAdapter, logger?: LoggerAdapter, config?: LeaseStoreConfig);
|
|
39
|
+
initialize(): Promise<void>;
|
|
40
|
+
isInitialized(): boolean;
|
|
41
|
+
private load;
|
|
42
|
+
private persist;
|
|
43
|
+
save(lease: StoredLease): Promise<void>;
|
|
44
|
+
get(leaseId: string): StoredLease | undefined;
|
|
45
|
+
getByToken(leaseToken: string): StoredLease | undefined;
|
|
46
|
+
getAll(): StoredLease[];
|
|
47
|
+
getActive(): StoredLease[];
|
|
48
|
+
getExpiringSoon(thresholdMs?: number): StoredLease[];
|
|
49
|
+
delete(leaseId: string): Promise<boolean>;
|
|
50
|
+
deleteByToken(leaseToken: string): Promise<boolean>;
|
|
51
|
+
updateStatus(leaseId: string, status: LeaseStatus): Promise<void>;
|
|
52
|
+
cleanupExpired(): Promise<number>;
|
|
53
|
+
clear(): Promise<void>;
|
|
54
|
+
getMinimumTTL(): number | null;
|
|
55
|
+
calculateMonitoringInterval(): number;
|
|
56
|
+
private flattenIndex;
|
|
57
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module LeaseStore
|
|
4
|
+
* WOTS lease lifecycle management with platform-agnostic storage
|
|
5
|
+
*
|
|
6
|
+
* Uses in-memory cache backed by persistent storage.
|
|
7
|
+
* Pattern:
|
|
8
|
+
* - initialize() loads from storage into memory cache
|
|
9
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
10
|
+
* - Mutators update memory cache AND persist to storage
|
|
11
|
+
* - Caller MUST call initialize() before using sync getters
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.LeaseStore = void 0;
|
|
15
|
+
const adapters_1 = require("../adapters");
|
|
16
|
+
const DEFAULT_STORAGE_KEY = 'totem_wots_leases';
|
|
17
|
+
class LeaseStore {
|
|
18
|
+
constructor(storage, logger = new adapters_1.NoopLogger(), config = {}) {
|
|
19
|
+
this.leases = new Map();
|
|
20
|
+
this._initialized = false;
|
|
21
|
+
this.storage = storage;
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
24
|
+
}
|
|
25
|
+
async initialize() {
|
|
26
|
+
if (this._initialized)
|
|
27
|
+
return;
|
|
28
|
+
await this.load();
|
|
29
|
+
await this.cleanupExpired();
|
|
30
|
+
this._initialized = true;
|
|
31
|
+
}
|
|
32
|
+
isInitialized() {
|
|
33
|
+
return this._initialized;
|
|
34
|
+
}
|
|
35
|
+
async load() {
|
|
36
|
+
try {
|
|
37
|
+
const leasesArray = await this.storage.get(this.storageKey);
|
|
38
|
+
if (leasesArray) {
|
|
39
|
+
this.leases = new Map(leasesArray.map(lease => [lease.leaseId, lease]));
|
|
40
|
+
this.logger.debug(`Loaded ${this.leases.size} leases from storage`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
this.logger.error('Failed to load leases:', error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async persist() {
|
|
48
|
+
try {
|
|
49
|
+
const leasesArray = Array.from(this.leases.values());
|
|
50
|
+
await this.storage.set(this.storageKey, leasesArray);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.logger.error('Failed to persist leases:', error);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async save(lease) {
|
|
58
|
+
const flatIndex = this.flattenIndex(lease.indices);
|
|
59
|
+
this.logger.debug(`Storing lease: ${lease.leaseId}`);
|
|
60
|
+
this.logger.debug(` indices: (addressIndex=${lease.indices.addressIndex}, l1=${lease.indices.l1}, l2=${lease.indices.l2}) [${flatIndex}/262,144]`);
|
|
61
|
+
this.logger.debug(` status: ${lease.status}, TTL: ${lease.leaseTTL}ms`);
|
|
62
|
+
this.leases.set(lease.leaseId, lease);
|
|
63
|
+
await this.persist();
|
|
64
|
+
}
|
|
65
|
+
get(leaseId) {
|
|
66
|
+
return this.leases.get(leaseId);
|
|
67
|
+
}
|
|
68
|
+
getByToken(leaseToken) {
|
|
69
|
+
return Array.from(this.leases.values()).find(lease => lease.leaseToken === leaseToken);
|
|
70
|
+
}
|
|
71
|
+
getAll() {
|
|
72
|
+
return Array.from(this.leases.values());
|
|
73
|
+
}
|
|
74
|
+
getActive() {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
return this.getAll().filter(lease => lease.status === 'active' && lease.expiresAt > now);
|
|
77
|
+
}
|
|
78
|
+
getExpiringSoon(thresholdMs = 5000) {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
return this.getAll().filter(lease => lease.status === 'active' &&
|
|
81
|
+
lease.expiresAt > now &&
|
|
82
|
+
lease.expiresAt <= now + thresholdMs);
|
|
83
|
+
}
|
|
84
|
+
async delete(leaseId) {
|
|
85
|
+
const lease = this.leases.get(leaseId);
|
|
86
|
+
if (lease) {
|
|
87
|
+
this.logger.debug(`Deleting lease: ${leaseId}, final status: ${lease.status}`);
|
|
88
|
+
}
|
|
89
|
+
const existed = this.leases.delete(leaseId);
|
|
90
|
+
if (existed) {
|
|
91
|
+
await this.persist();
|
|
92
|
+
}
|
|
93
|
+
return existed;
|
|
94
|
+
}
|
|
95
|
+
async deleteByToken(leaseToken) {
|
|
96
|
+
const lease = this.getByToken(leaseToken);
|
|
97
|
+
if (lease) {
|
|
98
|
+
return this.delete(lease.leaseId);
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
async updateStatus(leaseId, status) {
|
|
103
|
+
const lease = this.leases.get(leaseId);
|
|
104
|
+
if (lease) {
|
|
105
|
+
const oldStatus = lease.status;
|
|
106
|
+
this.logger.debug(`Lease status change: ${leaseId} ${oldStatus} → ${status}`);
|
|
107
|
+
lease.status = status;
|
|
108
|
+
await this.persist();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async cleanupExpired() {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const expiredLeaseIds = [];
|
|
114
|
+
for (const [leaseId, lease] of this.leases.entries()) {
|
|
115
|
+
if (lease.expiresAt <= now && lease.status !== 'finalized') {
|
|
116
|
+
lease.status = 'expired';
|
|
117
|
+
expiredLeaseIds.push(leaseId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (expiredLeaseIds.length > 0) {
|
|
121
|
+
this.logger.debug(`Marked ${expiredLeaseIds.length} leases as expired`);
|
|
122
|
+
await this.persist();
|
|
123
|
+
}
|
|
124
|
+
return expiredLeaseIds.length;
|
|
125
|
+
}
|
|
126
|
+
async clear() {
|
|
127
|
+
this.leases.clear();
|
|
128
|
+
await this.storage.remove(this.storageKey);
|
|
129
|
+
this.logger.debug('Cleared all leases');
|
|
130
|
+
}
|
|
131
|
+
getMinimumTTL() {
|
|
132
|
+
const activeLeases = this.getActive();
|
|
133
|
+
if (activeLeases.length === 0)
|
|
134
|
+
return null;
|
|
135
|
+
return Math.min(...activeLeases.map(lease => lease.leaseTTL));
|
|
136
|
+
}
|
|
137
|
+
calculateMonitoringInterval() {
|
|
138
|
+
const minTTL = this.getMinimumTTL();
|
|
139
|
+
if (minTTL === null)
|
|
140
|
+
return 5000;
|
|
141
|
+
const calculatedInterval = Math.min(minTTL / 4, 5000);
|
|
142
|
+
return Math.max(calculatedInterval, 1000);
|
|
143
|
+
}
|
|
144
|
+
flattenIndex(indices) {
|
|
145
|
+
return (indices.addressIndex * 64 * 64) + (indices.l1 * 64) + indices.l2;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.LeaseStore = LeaseStore;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module LeaseStore
|
|
3
|
+
* WOTS lease lifecycle management with platform-agnostic storage
|
|
4
|
+
*
|
|
5
|
+
* Uses in-memory cache backed by persistent storage.
|
|
6
|
+
* Pattern:
|
|
7
|
+
* - initialize() loads from storage into memory cache
|
|
8
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
9
|
+
* - Mutators update memory cache AND persist to storage
|
|
10
|
+
* - Caller MUST call initialize() before using sync getters
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { StorageAdapter, LoggerAdapter } from '../adapters/index.js';
|
|
14
|
+
import { NoopLogger } from '../adapters/index.js';
|
|
15
|
+
|
|
16
|
+
export interface WotsIndices {
|
|
17
|
+
addressIndex: number;
|
|
18
|
+
l1: number;
|
|
19
|
+
l2: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type LeaseStatus = 'pending' | 'active' | 'expired' | 'finalized' | 'cancelled';
|
|
23
|
+
|
|
24
|
+
export interface StoredLease {
|
|
25
|
+
leaseId: string;
|
|
26
|
+
leaseToken: string;
|
|
27
|
+
indices: WotsIndices;
|
|
28
|
+
expiresAt: number;
|
|
29
|
+
status: LeaseStatus;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
txId?: string;
|
|
32
|
+
leaseTTL: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LeaseStoreConfig {
|
|
36
|
+
storageKey?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DEFAULT_STORAGE_KEY = 'totem_wots_leases';
|
|
40
|
+
|
|
41
|
+
export class LeaseStore {
|
|
42
|
+
private leases: Map<string, StoredLease> = new Map();
|
|
43
|
+
private _initialized = false;
|
|
44
|
+
private readonly storageKey: string;
|
|
45
|
+
private readonly storage: StorageAdapter;
|
|
46
|
+
private readonly logger: LoggerAdapter;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
storage: StorageAdapter,
|
|
50
|
+
logger: LoggerAdapter = new NoopLogger(),
|
|
51
|
+
config: LeaseStoreConfig = {}
|
|
52
|
+
) {
|
|
53
|
+
this.storage = storage;
|
|
54
|
+
this.logger = logger;
|
|
55
|
+
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async initialize(): Promise<void> {
|
|
59
|
+
if (this._initialized) return;
|
|
60
|
+
|
|
61
|
+
await this.load();
|
|
62
|
+
await this.cleanupExpired();
|
|
63
|
+
this._initialized = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isInitialized(): boolean {
|
|
67
|
+
return this._initialized;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async load(): Promise<void> {
|
|
71
|
+
try {
|
|
72
|
+
const leasesArray = await this.storage.get<StoredLease[]>(this.storageKey);
|
|
73
|
+
if (leasesArray) {
|
|
74
|
+
this.leases = new Map(leasesArray.map(lease => [lease.leaseId, lease]));
|
|
75
|
+
this.logger.debug(`Loaded ${this.leases.size} leases from storage`);
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.logger.error('Failed to load leases:', error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async persist(): Promise<void> {
|
|
83
|
+
try {
|
|
84
|
+
const leasesArray = Array.from(this.leases.values());
|
|
85
|
+
await this.storage.set(this.storageKey, leasesArray);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.logger.error('Failed to persist leases:', error);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async save(lease: StoredLease): Promise<void> {
|
|
93
|
+
const flatIndex = this.flattenIndex(lease.indices);
|
|
94
|
+
this.logger.debug(`Storing lease: ${lease.leaseId}`);
|
|
95
|
+
this.logger.debug(` indices: (addressIndex=${lease.indices.addressIndex}, l1=${lease.indices.l1}, l2=${lease.indices.l2}) [${flatIndex}/262,144]`);
|
|
96
|
+
this.logger.debug(` status: ${lease.status}, TTL: ${lease.leaseTTL}ms`);
|
|
97
|
+
|
|
98
|
+
this.leases.set(lease.leaseId, lease);
|
|
99
|
+
await this.persist();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get(leaseId: string): StoredLease | undefined {
|
|
103
|
+
return this.leases.get(leaseId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getByToken(leaseToken: string): StoredLease | undefined {
|
|
107
|
+
return Array.from(this.leases.values()).find(
|
|
108
|
+
lease => lease.leaseToken === leaseToken
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getAll(): StoredLease[] {
|
|
113
|
+
return Array.from(this.leases.values());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getActive(): StoredLease[] {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
return this.getAll().filter(lease =>
|
|
119
|
+
lease.status === 'active' && lease.expiresAt > now
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getExpiringSoon(thresholdMs: number = 5000): StoredLease[] {
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
return this.getAll().filter(lease =>
|
|
126
|
+
lease.status === 'active' &&
|
|
127
|
+
lease.expiresAt > now &&
|
|
128
|
+
lease.expiresAt <= now + thresholdMs
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async delete(leaseId: string): Promise<boolean> {
|
|
133
|
+
const lease = this.leases.get(leaseId);
|
|
134
|
+
if (lease) {
|
|
135
|
+
this.logger.debug(`Deleting lease: ${leaseId}, final status: ${lease.status}`);
|
|
136
|
+
}
|
|
137
|
+
const existed = this.leases.delete(leaseId);
|
|
138
|
+
if (existed) {
|
|
139
|
+
await this.persist();
|
|
140
|
+
}
|
|
141
|
+
return existed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async deleteByToken(leaseToken: string): Promise<boolean> {
|
|
145
|
+
const lease = this.getByToken(leaseToken);
|
|
146
|
+
if (lease) {
|
|
147
|
+
return this.delete(lease.leaseId);
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async updateStatus(leaseId: string, status: LeaseStatus): Promise<void> {
|
|
153
|
+
const lease = this.leases.get(leaseId);
|
|
154
|
+
if (lease) {
|
|
155
|
+
const oldStatus = lease.status;
|
|
156
|
+
this.logger.debug(`Lease status change: ${leaseId} ${oldStatus} → ${status}`);
|
|
157
|
+
lease.status = status;
|
|
158
|
+
await this.persist();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async cleanupExpired(): Promise<number> {
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
const expiredLeaseIds: string[] = [];
|
|
165
|
+
|
|
166
|
+
for (const [leaseId, lease] of this.leases.entries()) {
|
|
167
|
+
if (lease.expiresAt <= now && lease.status !== 'finalized') {
|
|
168
|
+
lease.status = 'expired';
|
|
169
|
+
expiredLeaseIds.push(leaseId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (expiredLeaseIds.length > 0) {
|
|
174
|
+
this.logger.debug(`Marked ${expiredLeaseIds.length} leases as expired`);
|
|
175
|
+
await this.persist();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return expiredLeaseIds.length;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async clear(): Promise<void> {
|
|
182
|
+
this.leases.clear();
|
|
183
|
+
await this.storage.remove(this.storageKey);
|
|
184
|
+
this.logger.debug('Cleared all leases');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getMinimumTTL(): number | null {
|
|
188
|
+
const activeLeases = this.getActive();
|
|
189
|
+
if (activeLeases.length === 0) return null;
|
|
190
|
+
return Math.min(...activeLeases.map(lease => lease.leaseTTL));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
calculateMonitoringInterval(): number {
|
|
194
|
+
const minTTL = this.getMinimumTTL();
|
|
195
|
+
if (minTTL === null) return 5000;
|
|
196
|
+
const calculatedInterval = Math.min(minTTL / 4, 5000);
|
|
197
|
+
return Math.max(calculatedInterval, 1000);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private flattenIndex(indices: WotsIndices): number {
|
|
201
|
+
return (indices.addressIndex * 64 * 64) + (indices.l1 * 64) + indices.l2;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module WatermarkStore
|
|
3
|
+
* WOTS index tracking with server synchronization
|
|
4
|
+
*
|
|
5
|
+
* Uses in-memory cache backed by persistent storage.
|
|
6
|
+
* Pattern:
|
|
7
|
+
* - initialize() loads from storage into memory cache (or creates default)
|
|
8
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
9
|
+
* - Mutators update memory cache AND persist to storage
|
|
10
|
+
* - Caller MUST call initialize() before using sync getters
|
|
11
|
+
*/
|
|
12
|
+
import type { StorageAdapter, LoggerAdapter } from '../adapters';
|
|
13
|
+
export interface WotsIndices {
|
|
14
|
+
addressIndex: number;
|
|
15
|
+
l1: number;
|
|
16
|
+
l2: number;
|
|
17
|
+
}
|
|
18
|
+
export interface WatermarkState {
|
|
19
|
+
next_addressIndex: number;
|
|
20
|
+
next_l1: number;
|
|
21
|
+
next_l2: number;
|
|
22
|
+
usedIndices: Array<[number, number, number]>;
|
|
23
|
+
lastSyncTimestamp?: number;
|
|
24
|
+
serverWatermark?: WotsIndices;
|
|
25
|
+
}
|
|
26
|
+
export interface SyncResult {
|
|
27
|
+
updated: boolean;
|
|
28
|
+
drift: number;
|
|
29
|
+
hasConflict: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface WatermarkStoreConfig {
|
|
32
|
+
storageKey?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare class WatermarkStore {
|
|
35
|
+
private state;
|
|
36
|
+
private _initialized;
|
|
37
|
+
private readonly storageKey;
|
|
38
|
+
private readonly storage;
|
|
39
|
+
private readonly logger;
|
|
40
|
+
constructor(storage: StorageAdapter, logger?: LoggerAdapter, config?: WatermarkStoreConfig);
|
|
41
|
+
initialize(): Promise<WatermarkState>;
|
|
42
|
+
isInitialized(): boolean;
|
|
43
|
+
load(): Promise<WatermarkState | null>;
|
|
44
|
+
save(watermark: WatermarkState): Promise<void>;
|
|
45
|
+
clear(): Promise<void>;
|
|
46
|
+
getCurrent(): WatermarkState | null;
|
|
47
|
+
getNextIndices(): WotsIndices | null;
|
|
48
|
+
isExhausted(): boolean;
|
|
49
|
+
hasAvailableIndices(): boolean;
|
|
50
|
+
markUsed(indices: WotsIndices): Promise<void>;
|
|
51
|
+
advanceWatermark(indices: WotsIndices): Promise<void>;
|
|
52
|
+
updateFromServer(serverWatermark: WotsIndices): Promise<SyncResult>;
|
|
53
|
+
getUsageStats(): {
|
|
54
|
+
used: number;
|
|
55
|
+
total: number;
|
|
56
|
+
percentage: number;
|
|
57
|
+
};
|
|
58
|
+
private calculateNextIndices;
|
|
59
|
+
private shouldUseServerWatermark;
|
|
60
|
+
private flattenIndex;
|
|
61
|
+
}
|