@bitsocial/pubsub-voting 0.1.1 → 0.1.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.
|
@@ -6,7 +6,7 @@ import type { VoteCrdt } from "../../crdt/types.js";
|
|
|
6
6
|
import { type VerdictCache } from "../../verify/cache.js";
|
|
7
7
|
import { type RootChaser } from "../chase.js";
|
|
8
8
|
import type { PubsubService, VoteTransport } from "../types.js";
|
|
9
|
-
import { type RootRecord } from "../messages.js";
|
|
9
|
+
import { type FetchRootRecord, type RootRecord } from "../messages.js";
|
|
10
10
|
/** The gossipsub peer-score methods used for assertions — on the concrete class, not the interface. */
|
|
11
11
|
interface ScoreOps {
|
|
12
12
|
getScore(peer: string): number;
|
|
@@ -41,7 +41,13 @@ export interface VoteNode {
|
|
|
41
41
|
/** Replace the injected verifier (e.g. a rejecter, or a slow one, for a specific assertion). */
|
|
42
42
|
setVerifier(verify: (bundle: VotesBundle) => Promise<BundleVerdict>): void;
|
|
43
43
|
/** Encode this node's current winner-set to a checkpoint (blocks written to its blockstore). */
|
|
44
|
-
checkpointRootRecord(): Promise<
|
|
44
|
+
checkpointRootRecord(): Promise<FetchRootRecord>;
|
|
45
|
+
/**
|
|
46
|
+
* Pull `from`'s root record over the REAL libp2p fetch protocol — the cold-start pull a
|
|
47
|
+
* joiner makes before chasing (voter.ts `#fetchRootWithRetry`). `undefined` when the peer
|
|
48
|
+
* serves nothing for the key.
|
|
49
|
+
*/
|
|
50
|
+
fetchRootRecord(from: VoteNode): Promise<FetchRootRecord | undefined>;
|
|
45
51
|
/** Publish this node's own root record on the topic (a heartbeat). */
|
|
46
52
|
publishOwnRoot(): Promise<void>;
|
|
47
53
|
/** Seed a bundle straight into this node's state (store block + CRDT merge), no network. */
|
|
@@ -17,7 +17,7 @@ import { encodeCheckpoint } from "../../checkpoint/codec.js";
|
|
|
17
17
|
import { makeGossipGate } from "../gossip-validator.js";
|
|
18
18
|
import { makeRootChaser, toChaseSession } from "../chase.js";
|
|
19
19
|
import { makeVoteTransport } from "../transport.js";
|
|
20
|
-
import { decodeVoteMessage, maxBundleMessageBytes, MAX_ROOT_MESSAGE_BYTES, ROOT_RECORD_VERSION } from "../messages.js";
|
|
20
|
+
import { decodeVoteMessage, decodeRootRecord, encodeRootRecord, maxBundleMessageBytes, rootFetchKey, MAX_ROOT_MESSAGE_BYTES, ROOT_RECORD_VERSION } from "../messages.js";
|
|
21
21
|
/**
|
|
22
22
|
* Test harness for the two-node gossipsub integration test. It stands up ONE real libp2p +
|
|
23
23
|
* Helia node carrying `@libp2p/gossipsub` (>= 15.0.23, the CVE-2026-46679 floor) and
|
|
@@ -117,16 +117,30 @@ export async function makeVoteNode(topic, options = {}) {
|
|
|
117
117
|
let matchedOwnRoot = false;
|
|
118
118
|
async function checkpointRootRecord() {
|
|
119
119
|
const winners = crdt.current(CURRENT_BUCKET);
|
|
120
|
-
const { root, blocks } = await encodeCheckpoint(winners);
|
|
120
|
+
const { root, chunks, blocks } = await encodeCheckpoint(winners);
|
|
121
121
|
for (const block of blocks)
|
|
122
122
|
await blockstore.put(block.cid, block.bytes);
|
|
123
123
|
return {
|
|
124
124
|
version: ROOT_RECORD_VERSION,
|
|
125
125
|
root,
|
|
126
|
+
chunks,
|
|
126
127
|
count: winners.length,
|
|
127
128
|
sizeBytes: blocks.reduce((total, block) => total + block.bytes.length, 0)
|
|
128
129
|
};
|
|
129
130
|
}
|
|
131
|
+
// Mirror the production fetch responder (voter.ts `#rootLookup`): answer `<topic>/root` with
|
|
132
|
+
// this node's current root record, encoded on demand — the surface a cold joiner pulls
|
|
133
|
+
// before chasing. `@libp2p/fetch` hands the lookup the requested key as raw bytes.
|
|
134
|
+
libp2p.services.fetch.registerLookupFunction(topic, async (keyBytes) => {
|
|
135
|
+
if (new TextDecoder().decode(keyBytes) !== rootFetchKey(topic))
|
|
136
|
+
return undefined;
|
|
137
|
+
try {
|
|
138
|
+
return encodeRootRecord(await checkpointRootRecord());
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
130
144
|
const openedSessions = [];
|
|
131
145
|
const chaseLimit = pLimit(2);
|
|
132
146
|
const chaser = makeRootChaser({
|
|
@@ -228,6 +242,10 @@ export async function makeVoteNode(topic, options = {}) {
|
|
|
228
242
|
verifyImpl = verify;
|
|
229
243
|
},
|
|
230
244
|
checkpointRootRecord,
|
|
245
|
+
fetchRootRecord: async (from) => {
|
|
246
|
+
const bytes = await libp2p.services.fetch.fetch(from.libp2p.peerId, rootFetchKey(topic));
|
|
247
|
+
return bytes == null ? undefined : decodeRootRecord(bytes);
|
|
248
|
+
},
|
|
231
249
|
publishOwnRoot: async () => {
|
|
232
250
|
await transport.publishRootRecord(await checkpointRootRecord());
|
|
233
251
|
},
|