forked from Vero-protocol/vero-core-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnonce-manager.ts
More file actions
69 lines (58 loc) · 2.25 KB
/
Copy pathnonce-manager.ts
File metadata and controls
69 lines (58 loc) · 2.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* nonce-manager.ts — Atomic transaction sequence-number management.
*
* Stellar uses per-account sequence numbers. Concurrent submitters racing
* to build transactions would produce sequence collisions. This manager
* serialises reservation, auto-refreshes on network desync, and releases
* sequences back to the pool if the outer operation fails.
*/
import { RpcClient } from "./rpc-client";
export class NonceManager {
/** account → next usable sequence */
private readonly cache = new Map<string, bigint>();
/** Serialises concurrent calls per account to prevent races. */
private readonly locks = new Map<string, Promise<void>>();
constructor(private readonly rpc: RpcClient) {}
/**
* Reserve the next sequence number for `accountId`.
* The caller MUST call `release(accountId, sequence)` on submission failure.
*/
async reserve(accountId: string): Promise<bigint> {
await this.waitForLock(accountId);
let resolve!: () => void;
const lock = new Promise<void>(r => { resolve = r; });
this.locks.set(accountId, lock);
try {
const seq = await this.nextSequence(accountId);
this.cache.set(accountId, seq + 1n);
return seq;
} finally {
this.locks.delete(accountId);
resolve();
}
}
/** Return a sequence that was never submitted so it can be reused. */
release(accountId: string, sequence: bigint): void {
const current = this.cache.get(accountId);
if (current === undefined || sequence < current) {
this.cache.set(accountId, sequence);
}
}
/** Force a fresh read from the network (e.g. after fee-bump or manual tx). */
async refresh(accountId: string): Promise<void> {
this.cache.delete(accountId);
await this.nextSequence(accountId); // warms the cache
}
private async nextSequence(accountId: string): Promise<bigint> {
const cached = this.cache.get(accountId);
if (cached !== undefined) return cached;
const seq = await this.rpc.call(server => server.getAccount(accountId))
.then(a => BigInt(a.sequenceNumber()));
this.cache.set(accountId, seq + 1n);
return seq + 1n;
}
private async waitForLock(accountId: string): Promise<void> {
const existing = this.locks.get(accountId);
if (existing) await existing;
}
}