@bitsocial/pubsub-voting 0.2.1 → 0.3.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/README.md +42 -6
- package/dist/client/voter.js +48 -35
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/rules/cache.d.ts +107 -0
- package/dist/rules/cache.js +160 -0
- package/dist/rules/erc20-balance.js +7 -3
- package/dist/rules/erc5192-min-balance.d.ts +17 -9
- package/dist/rules/erc5192-min-balance.js +158 -36
- package/dist/rules/erc721-min-balance.js +35 -10
- package/dist/rules/nft-balance.d.ts +24 -6
- package/dist/rules/nft-balance.js +16 -13
- package/dist/rules/types.d.ts +82 -16
- package/dist/tally/tally.d.ts +14 -0
- package/dist/tally/tally.js +16 -2
- package/dist/verify/background.d.ts +42 -11
- package/dist/verify/background.js +91 -37
- package/dist/verify/bundle.d.ts +23 -9
- package/dist/verify/bundle.js +33 -19
- package/dist/verify/gate-grace.d.ts +32 -0
- package/dist/verify/gate-grace.js +32 -0
- package/package.json +2 -1
- package/dist/verify/gate-result-cache.d.ts +0 -65
- package/dist/verify/gate-result-cache.js +0 -91
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
const memKeyFor = (wallet, sampleBlock) => `${wallet.toLowerCase()}:${sampleBlock}`;
|
|
2
|
-
/**
|
|
3
|
-
* An in-memory {@link GateResultCache} bounded to `maxEntries` with FIFO eviction. Eviction is
|
|
4
|
-
* safe because a score is deterministic — an evicted entry only ever costs a re-read, never a
|
|
5
|
-
* wrong answer; without a bound, a flood of fresh wallets is a memory-exhaustion vector (see
|
|
6
|
-
* DESIGN.md "Can valid votes clog the topic?").
|
|
7
|
-
*/
|
|
8
|
-
export function makeGateResultCache(maxEntries = 4096) {
|
|
9
|
-
const byKey = new Map();
|
|
10
|
-
const order = [];
|
|
11
|
-
return {
|
|
12
|
-
get: async (wallet, sampleBlock) => byKey.get(memKeyFor(wallet, sampleBlock)),
|
|
13
|
-
set: (wallet, sampleBlock, score) => {
|
|
14
|
-
const k = memKeyFor(wallet, sampleBlock);
|
|
15
|
-
if (byKey.has(k))
|
|
16
|
-
return; // idempotent: never refresh position or overwrite a pinned score
|
|
17
|
-
byKey.set(k, score);
|
|
18
|
-
order.push(k);
|
|
19
|
-
if (order.length > maxEntries) {
|
|
20
|
-
const evicted = order.shift();
|
|
21
|
-
if (evicted !== undefined)
|
|
22
|
-
byKey.delete(evicted);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
/** The persistent gate store's key. `ruleHash` disambiguates: the shared store spans every
|
|
28
|
-
* contest on the voter, and one wallet can hold different scores under different gate rules
|
|
29
|
-
* (or the same rule at different chainIds). Same score under the same rule is what lets two
|
|
30
|
-
* contests over one gate (a 5chan-style directory) share each other's reads. */
|
|
31
|
-
const storeKeyFor = (ruleHash, wallet, sampleBlock) => `${ruleHash}:${wallet.toLowerCase()}:${sampleBlock}`;
|
|
32
|
-
/**
|
|
33
|
-
* A {@link GateResultCache} layered over the voter's persistent store: an in-memory FIFO front
|
|
34
|
-
* (the hot path — steady-state gossip hits it synchronously) with read-through to the store on
|
|
35
|
-
* a miss and fire-and-forget write-through on `set`. Scores travel as decimal strings (JSON has
|
|
36
|
-
* no bigint). A broken store read or write degrades to a live chain read — never an error into
|
|
37
|
-
* the verify pipeline — because everything here is a pure function of pinned historical state.
|
|
38
|
-
*/
|
|
39
|
-
export function makePersistentGateResultCache(opts) {
|
|
40
|
-
const { store, ruleHash } = opts;
|
|
41
|
-
const mem = makeGateResultCache(opts.maxMemEntries);
|
|
42
|
-
return {
|
|
43
|
-
async get(wallet, sampleBlock) {
|
|
44
|
-
const cached = await mem.get(wallet, sampleBlock);
|
|
45
|
-
if (cached !== undefined)
|
|
46
|
-
return cached;
|
|
47
|
-
let persisted;
|
|
48
|
-
try {
|
|
49
|
-
persisted = await store.getItem(storeKeyFor(ruleHash, wallet, sampleBlock));
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
if (typeof persisted !== "string" || !/^\d+$/.test(persisted))
|
|
55
|
-
return undefined;
|
|
56
|
-
const score = BigInt(persisted);
|
|
57
|
-
mem.set(wallet, sampleBlock, score);
|
|
58
|
-
return score;
|
|
59
|
-
},
|
|
60
|
-
set(wallet, sampleBlock, score) {
|
|
61
|
-
mem.set(wallet, sampleBlock, score);
|
|
62
|
-
void store.setItem(storeKeyFor(ruleHash, wallet, sampleBlock), score.toString()).catch(() => {
|
|
63
|
-
// a failed persist costs a future re-read, never a wrong answer
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Deterministic expiry purge for one rule's persisted gate results — better than LRU here
|
|
70
|
-
* because staleness is *provable*: a score at bucket B's sample block is only ever consulted
|
|
71
|
-
* while bundles from B are admissible (within `voteExpiryBuckets` of head), so anything older
|
|
72
|
-
* than the oldest admissible sample block can never be read again. Run per contest whenever a
|
|
73
|
-
* head read advances the expiry boundary (see the engine's `#maybePurgeGateResults`); the
|
|
74
|
-
* store's LRU bound stays as the backstop for rules never purged.
|
|
75
|
-
*/
|
|
76
|
-
export async function purgeExpiredGateResults(opts) {
|
|
77
|
-
const prefix = `${opts.ruleHash}:`;
|
|
78
|
-
try {
|
|
79
|
-
for (const key of await opts.store.keys()) {
|
|
80
|
-
if (!key.startsWith(prefix))
|
|
81
|
-
continue;
|
|
82
|
-
const sampleBlock = Number(key.slice(key.lastIndexOf(":") + 1));
|
|
83
|
-
if (Number.isFinite(sampleBlock) && sampleBlock < opts.oldestSampleBlock) {
|
|
84
|
-
await opts.store.removeItem(key);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
// purge is best-effort; the store's LRU bound is the correctness-free backstop
|
|
90
|
-
}
|
|
91
|
-
}
|