Skip to content

Commit 2268dd9

Browse files
committed
sdk: add getRound caching with explicit invalidation
1 parent e432c49 commit 2268dd9

2 files changed

Lines changed: 83 additions & 0 deletions

File tree

packages/sdk/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,18 @@ cached for later calls. A mismatch throws `SubRosaNetworkMismatchError` before
2323
simulation, signing, or submission, with the conflicting values and a suggested
2424
fix. Contract IDs do not encode a Stellar network, so copying a `C...` address
2525
between Testnet and Mainnet requires updating all three configuration values.
26+
27+
## Caching
28+
29+
You can optionally configure a short-lived cache for `getRound` and `getConfig` reads by providing a `cacheTtl` (in milliseconds):
30+
31+
```ts
32+
const client = new SubRosaClient({
33+
// ... other config
34+
cacheTtl: 5000, // 5 seconds
35+
});
36+
```
37+
38+
This reduces avoidable RPC load and latency when dashboards or keeper watch loops re-read the same round repeatedly.
39+
40+
**Tradeoffs**: Reads may be stale for up to `cacheTtl` milliseconds if another client updates the round. However, the client automatically invalidates its own cache for a round when it successfully executes a state-changing write operation (e.g., `commit`, `reveal`, `settle`) for that round.

packages/sdk/src/client.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,71 @@ describe("SubRosaClient external submitter failures", () => {
222222
});
223223
});
224224
});
225+
226+
describe("SubRosaClient caching", () => {
227+
it("caches getRound reads and invalidates on writes", async () => {
228+
let contractCalls = 0;
229+
const client = new SubRosaClient({
230+
...BASE_CONFIG,
231+
publicKey: PUBLIC_KEY,
232+
cacheTtl: 60_000,
233+
});
234+
235+
Object.defineProperty(client.contract, "get_round", {
236+
configurable: true,
237+
value: async () => {
238+
contractCalls += 1;
239+
return { result: { unwrap: () => ({ round_number: contractCalls }) } };
240+
},
241+
});
242+
243+
Object.defineProperty(client.contract, "void", {
244+
configurable: true,
245+
value: async () => ({
246+
async signAndSend() {
247+
return { result: { unwrap: () => {} } };
248+
},
249+
}),
250+
});
251+
252+
const r1 = await client.getRound(1);
253+
assert.equal((r1 as any).round_number, 1);
254+
assert.equal(contractCalls, 1);
255+
256+
const r2 = await client.getRound(1);
257+
assert.equal((r2 as any).round_number, 1);
258+
assert.equal(contractCalls, 1, "should use cached result");
259+
260+
await client.void(1);
261+
262+
const r3 = await client.getRound(1);
263+
assert.equal((r3 as any).round_number, 2);
264+
assert.equal(contractCalls, 2, "should fetch new result after invalidation");
265+
});
266+
267+
it("caches getConfig reads", async () => {
268+
let contractCalls = 0;
269+
const client = new SubRosaClient({
270+
...BASE_CONFIG,
271+
publicKey: PUBLIC_KEY,
272+
cacheTtl: 60_000,
273+
});
274+
275+
Object.defineProperty(client.contract, "get_config", {
276+
configurable: true,
277+
value: async () => {
278+
contractCalls += 1;
279+
return { result: { unwrap: () => ({ version: contractCalls }) } };
280+
},
281+
});
282+
283+
const c1 = await client.getConfig();
284+
assert.equal((c1 as any).version, 1);
285+
assert.equal(contractCalls, 1);
286+
287+
const c2 = await client.getConfig();
288+
assert.equal((c2 as any).version, 1);
289+
assert.equal(contractCalls, 1, "should use cached config result");
290+
});
291+
});
292+

0 commit comments

Comments
 (0)