@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<RootRecord>;
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitsocial/pubsub-voting",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Trustless pubsub voting over a shared libp2p/Helia node.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",