@optimystic/db-p2p 0.22.0 → 0.24.1
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/dist/src/cluster/client.d.ts +10 -0
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +30 -1
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +39 -14
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +226 -119
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/service.d.ts +32 -1
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +43 -2
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cohort-topic/host.js +34 -11
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +37 -7
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +77 -19
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +9 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +88 -2
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +134 -28
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +25 -1
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/logger.d.ts +17 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +19 -2
- package/dist/src/logger.js.map +1 -1
- package/dist/src/matchmaking/query-transport.js +3 -3
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/owned-block-seed.d.ts +6 -3
- package/dist/src/owned-block-seed.d.ts.map +1 -1
- package/dist/src/owned-block-seed.js +16 -3
- package/dist/src/owned-block-seed.js.map +1 -1
- package/dist/src/peer-address-book.d.ts +72 -0
- package/dist/src/peer-address-book.d.ts.map +1 -0
- package/dist/src/peer-address-book.js +123 -0
- package/dist/src/peer-address-book.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts +4 -4
- package/dist/src/reactivity/notify-transport.js +6 -6
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -2
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/reactivity/recover-transport.d.ts +6 -2
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
- package/dist/src/reactivity/recover-transport.js +7 -3
- package/dist/src/reactivity/recover-transport.js.map +1 -1
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +11 -2
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +30 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +95 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +62 -9
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +242 -73
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/rn.d.ts +3 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +3 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +83 -0
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
- package/dist/src/storage/cached-raw-storage.js +152 -0
- package/dist/src/storage/cached-raw-storage.js.map +1 -0
- package/dist/src/storage/cached-store-driver.d.ts +186 -0
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
- package/dist/src/storage/cached-store-driver.js +775 -0
- package/dist/src/storage/cached-store-driver.js.map +1 -0
- package/dist/src/storage/i-raw-storage.d.ts +12 -5
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +234 -0
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
- package/dist/src/storage/shared-cache-pool.js +354 -0
- package/dist/src/storage/shared-cache-pool.js.map +1 -0
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +35 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +668 -668
- package/src/cluster/block-transfer.ts +424 -424
- package/src/cluster/client.ts +119 -88
- package/src/cluster/cluster-error.ts +64 -64
- package/src/cluster/cluster-policy.ts +203 -203
- package/src/cluster/cluster-repo.ts +242 -122
- package/src/cluster/cluster-size-coupling.ts +45 -45
- package/src/cluster/commit-cert.ts +139 -139
- package/src/cluster/i-transaction-state-store.ts +43 -43
- package/src/cluster/memory-transaction-state-store.ts +56 -56
- package/src/cluster/peer-key-binding.ts +37 -37
- package/src/cluster/persistent-transaction-state-store.ts +92 -92
- package/src/cluster/quorum-restore.ts +223 -223
- package/src/cluster/reconcile-block.ts +203 -203
- package/src/cluster/service.ts +293 -241
- package/src/cluster/supermajority-coupling.ts +37 -37
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
- package/src/cohort-topic/change-bridge.ts +109 -109
- package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
- package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
- package/src/cohort-topic/fret-trust-anchor.ts +153 -153
- package/src/cohort-topic/host.ts +42 -11
- package/src/cohort-topic/index.ts +13 -13
- package/src/cohort-topic/membership-publish-sink.ts +20 -20
- package/src/cohort-topic/membership-source.ts +68 -68
- package/src/cohort-topic/peer-codec.ts +31 -31
- package/src/cohort-topic/peer-sig.ts +86 -86
- package/src/cohort-topic/protocols.ts +71 -71
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
- package/src/cohort-topic/size-estimator.ts +16 -16
- package/src/cohort-topic/stream-util.ts +79 -19
- package/src/cohort-topic/threshold-crypto.ts +239 -239
- package/src/cohort-topic/topic-router.ts +77 -77
- package/src/dispute/arbitrator-selection.ts +138 -138
- package/src/dispute/cascade.ts +524 -524
- package/src/dispute/dispute-service.ts +11 -5
- package/src/dispute/invalidation.ts +625 -625
- package/src/inbound-authorization.ts +190 -190
- package/src/index.ts +52 -49
- package/src/libp2p-key-network.ts +1120 -990
- package/src/libp2p-node-base.ts +1675 -1651
- package/src/libp2p-node-rn.ts +30 -30
- package/src/libp2p-node.ts +36 -36
- package/src/logger.ts +19 -2
- package/src/matchmaking/aggregate-counts.ts +104 -104
- package/src/matchmaking/index.ts +20 -20
- package/src/matchmaking/module.ts +363 -363
- package/src/matchmaking/protocols.ts +51 -51
- package/src/matchmaking/provider-manager.ts +95 -95
- package/src/matchmaking/query-handler.ts +88 -88
- package/src/matchmaking/query-transport.ts +3 -3
- package/src/matchmaking/seeker-manager.ts +64 -64
- package/src/matchmaking/seeker-walk-client.ts +293 -293
- package/src/matchmaking/traffic-validation.ts +195 -195
- package/src/optimystic-node.ts +36 -36
- package/src/owned-block-seed.ts +53 -40
- package/src/peer-address-book.ts +149 -0
- package/src/protocol-limits.ts +33 -33
- package/src/reactivity/forwarder-host.ts +438 -438
- package/src/reactivity/index.ts +19 -19
- package/src/reactivity/notify-transport.ts +144 -144
- package/src/reactivity/origination-manager.ts +192 -192
- package/src/reactivity/protocols.ts +61 -61
- package/src/reactivity/push-state-gossip.ts +291 -291
- package/src/reactivity/recover-transport.ts +7 -3
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
- package/src/reactivity/subscriber-registry.ts +96 -96
- package/src/reactivity/subscription-manager.ts +450 -450
- package/src/reactivity/topic-bytes.ts +37 -37
- package/src/repo/client.ts +12 -2
- package/src/repo/cluster-coordinator.ts +99 -3
- package/src/repo/coordinator-repo.ts +281 -74
- package/src/repo/types.ts +7 -7
- package/src/rn.ts +39 -36
- package/src/rpc-deadline.ts +45 -45
- package/src/storage/arachnode-partition.ts +74 -74
- package/src/storage/cached-raw-storage.ts +180 -0
- package/src/storage/cached-store-driver.ts +859 -0
- package/src/storage/i-kv-store.ts +8 -8
- package/src/storage/i-raw-storage.ts +12 -5
- package/src/storage/kv-raw-storage.ts +135 -135
- package/src/storage/memory-kv-store.ts +28 -28
- package/src/storage/memory-storage.ts +25 -25
- package/src/storage/memory-store-driver.ts +157 -157
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +80 -80
- package/src/storage/ring-selector.ts +317 -317
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/shared-cache-pool.ts +452 -0
- package/src/storage/storage-repo.ts +1014 -1014
- package/src/testing/cohort-topic-mesh-harness.ts +673 -663
- package/src/testing/index.ts +8 -8
- package/src/testing/matchmaking-mesh-harness.ts +475 -475
- package/src/testing/raw-storage-conformance.ts +453 -417
- package/src/testing/reactivity-mesh-harness.ts +922 -922
- package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
- package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
- package/dist/src/storage/restoration-coordinator-v2.js +0 -172
- package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cohort-topic peer-id ↔ bytes bridge.
|
|
3
|
-
*
|
|
4
|
-
* db-core's substrate logic references peers as opaque `Uint8Array` ids ({@link PeerRef}`.id`); FRET +
|
|
5
|
-
* libp2p reference them as `PeerId` / peer-id strings. The cohort-member ids the substrate carries on
|
|
6
|
-
* the wire (a reply's `primary` / `backups` / `cohortMembers`) must round-trip back to a *dialable*
|
|
7
|
-
* libp2p peer, so this module pins one unambiguous encoding for them: the UTF-8 bytes of the canonical
|
|
8
|
-
* peer-id string. (The participant's own addressing coordinate `P` is a separate ring-coord value — see
|
|
9
|
-
* the host — so this encoding never needs to feed the ring hash.)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { peerIdFromString } from "@libp2p/peer-id";
|
|
13
|
-
import type { PeerId } from "@libp2p/interface";
|
|
14
|
-
|
|
15
|
-
const utf8Encoder = new TextEncoder();
|
|
16
|
-
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
17
|
-
|
|
18
|
-
/** A cohort-member id as the substrate's opaque bytes: UTF-8 of the canonical peer-id string. */
|
|
19
|
-
export function peerIdToBytes(peerId: PeerId | string): Uint8Array {
|
|
20
|
-
return utf8Encoder.encode(typeof peerId === "string" ? peerId : peerId.toString());
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Reverse of {@link peerIdToBytes}: decode member bytes back to the canonical peer-id string. */
|
|
24
|
-
export function bytesToPeerIdString(bytes: Uint8Array): string {
|
|
25
|
-
return utf8Decoder.decode(bytes);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Reverse of {@link peerIdToBytes}: decode member bytes back to a dialable {@link PeerId}. */
|
|
29
|
-
export function bytesToPeerId(bytes: Uint8Array): PeerId {
|
|
30
|
-
return peerIdFromString(bytesToPeerIdString(bytes));
|
|
31
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic peer-id ↔ bytes bridge.
|
|
3
|
+
*
|
|
4
|
+
* db-core's substrate logic references peers as opaque `Uint8Array` ids ({@link PeerRef}`.id`); FRET +
|
|
5
|
+
* libp2p reference them as `PeerId` / peer-id strings. The cohort-member ids the substrate carries on
|
|
6
|
+
* the wire (a reply's `primary` / `backups` / `cohortMembers`) must round-trip back to a *dialable*
|
|
7
|
+
* libp2p peer, so this module pins one unambiguous encoding for them: the UTF-8 bytes of the canonical
|
|
8
|
+
* peer-id string. (The participant's own addressing coordinate `P` is a separate ring-coord value — see
|
|
9
|
+
* the host — so this encoding never needs to feed the ring hash.)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { peerIdFromString } from "@libp2p/peer-id";
|
|
13
|
+
import type { PeerId } from "@libp2p/interface";
|
|
14
|
+
|
|
15
|
+
const utf8Encoder = new TextEncoder();
|
|
16
|
+
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
17
|
+
|
|
18
|
+
/** A cohort-member id as the substrate's opaque bytes: UTF-8 of the canonical peer-id string. */
|
|
19
|
+
export function peerIdToBytes(peerId: PeerId | string): Uint8Array {
|
|
20
|
+
return utf8Encoder.encode(typeof peerId === "string" ? peerId : peerId.toString());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Reverse of {@link peerIdToBytes}: decode member bytes back to the canonical peer-id string. */
|
|
24
|
+
export function bytesToPeerIdString(bytes: Uint8Array): string {
|
|
25
|
+
return utf8Decoder.decode(bytes);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Reverse of {@link peerIdToBytes}: decode member bytes back to a dialable {@link PeerId}. */
|
|
29
|
+
export function bytesToPeerId(bytes: Uint8Array): PeerId {
|
|
30
|
+
return peerIdFromString(bytesToPeerIdString(bytes));
|
|
31
|
+
}
|
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cohort-topic shared Ed25519 sign/verify primitive (libp2p peer keys).
|
|
3
|
-
*
|
|
4
|
-
* Mirrors the established peer-key signing pattern in `cluster-repo.ts` / `dispute-service.ts`
|
|
5
|
-
* (`PrivateKey.sign` → base64url; verify against the peer's public key), specialized for the
|
|
6
|
-
* cohort-topic substrate:
|
|
7
|
-
*
|
|
8
|
-
* - **Signing** uses the node's libp2p `PrivateKey.sign` (async — fine on the participant outbound
|
|
9
|
-
* path) and is the seam the threshold-assembly ticket reuses for per-member partial signatures.
|
|
10
|
-
* - **Verification is SYNCHRONOUS**, because the db-core verify ports (e.g. the participant-signature
|
|
11
|
-
* predicates and `ICohortThresholdCrypto.verify`) are synchronous. libp2p's
|
|
12
|
-
* `publicKeyFromRaw(raw).verify` is async, so this verifies with `@noble/curves/ed25519` directly
|
|
13
|
-
* over the raw 32-byte key. noble's `ed25519.verify` (ZIP215) is a strict superset of the RFC8032
|
|
14
|
-
* signatures the libp2p signer (Node/WebCrypto Ed25519) produces, so it accepts them.
|
|
15
|
-
*
|
|
16
|
-
* The signer id on the cohort wire is the UTF-8 bytes of the peer-id string (see `peer-codec.ts`).
|
|
17
|
-
* For an Ed25519 identity the public key is embedded in the identity multihash, so no network lookup
|
|
18
|
-
* is needed — `peerIdFromString(str).publicKey.raw` yields the raw key. Verification is **total**: any
|
|
19
|
-
* non-Ed25519 id, missing key, or malformed input returns `false` (it never throws). The cohort-topic
|
|
20
|
-
* substrate therefore assumes Ed25519 identities (the libp2p default; `generateKeyPair('Ed25519')`).
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { PrivateKey } from "@libp2p/interface";
|
|
24
|
-
import { peerIdFromString } from "@libp2p/peer-id";
|
|
25
|
-
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
26
|
-
import { bytesToPeerIdString } from "./peer-codec.js";
|
|
27
|
-
|
|
28
|
-
/** Sign `payload` with the node's libp2p peer key. Async — libp2p `PrivateKey.sign` is async. */
|
|
29
|
-
export async function signPeer(privateKey: PrivateKey, payload: Uint8Array): Promise<Uint8Array> {
|
|
30
|
-
return privateKey.sign(payload);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Sign `payload` with a libp2p Ed25519 peer key **synchronously** — the mirror of the synchronous
|
|
35
|
-
* {@link verifyPeerSig}. Use this where the signer seam is synchronous and the async {@link signPeer}
|
|
36
|
-
* cannot be awaited: e.g. the reactivity recover `signBackfill` / `signResume` `(unsigned) => string`
|
|
37
|
-
* ports, whose db-core driver builds the unsigned image internally (so a pre-signed value is impossible)
|
|
38
|
-
* and whose seam shape is `=> string`, not `=> Promise<string>`.
|
|
39
|
-
*
|
|
40
|
-
* libp2p's own `PrivateKey.sign` is async (Node / WebCrypto Ed25519), so this signs directly with
|
|
41
|
-
* `@noble/curves/ed25519` over the raw 32-byte seed. libp2p stores an Ed25519 private key as 64 bytes
|
|
42
|
-
* (`raw` = 32-byte seed ‖ 32-byte public key); noble's `ed25519.sign(message, secretKey)` takes the
|
|
43
|
-
* 32-byte seed. noble's signatures are RFC8032-compliant and deterministic, so both verify paths accept
|
|
44
|
-
* them: the libp2p `publicKeyFromRaw(raw).verify` (Node / WebCrypto) and the synchronous
|
|
45
|
-
* {@link verifyPeerSig} (noble, ZIP215) — symmetric with the verify direction's cross-acceptance, and
|
|
46
|
-
* byte-identical to what the async {@link signPeer} would produce for the same key + payload.
|
|
47
|
-
*
|
|
48
|
-
* Assumes an Ed25519 identity (the cohort-topic substrate's standing assumption). Throws on a
|
|
49
|
-
* non-Ed25519 key or an unexpected raw length — a programming error on the node's own key, never
|
|
50
|
-
* attacker-reachable input (the verify side, by contrast, is total and returns `false`).
|
|
51
|
-
*/
|
|
52
|
-
export function signPeerSig(privateKey: PrivateKey, payload: Uint8Array): Uint8Array {
|
|
53
|
-
if (privateKey.type !== "Ed25519") {
|
|
54
|
-
throw new Error(`signPeerSig: expected an Ed25519 private key, got "${privateKey.type}"`);
|
|
55
|
-
}
|
|
56
|
-
const raw = privateKey.raw;
|
|
57
|
-
// 64-byte libp2p form = seed ‖ public key; accept a bare 32-byte seed too.
|
|
58
|
-
const seed = raw.length === 64 ? raw.subarray(0, 32) : raw;
|
|
59
|
-
if (seed.length !== 32) {
|
|
60
|
-
throw new Error(`signPeerSig: expected a 32- or 64-byte Ed25519 raw key, got ${raw.length} bytes`);
|
|
61
|
-
}
|
|
62
|
-
return ed25519.sign(payload, seed);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Verify an Ed25519 peer-key signature synchronously. `signer` is the cohort-wire member id — the
|
|
67
|
-
* UTF-8 bytes of the peer-id string (see {@link bytesToPeerIdString}) — or that peer-id string
|
|
68
|
-
* directly. Returns `false` (never throws) on a non-Ed25519 id, a missing/short key, a malformed
|
|
69
|
-
* signature, or any decode failure.
|
|
70
|
-
*/
|
|
71
|
-
export function verifyPeerSig(signer: string | Uint8Array, payload: Uint8Array, sig: Uint8Array): boolean {
|
|
72
|
-
try {
|
|
73
|
-
const peerIdStr = typeof signer === "string" ? signer : bytesToPeerIdString(signer);
|
|
74
|
-
const peerId = peerIdFromString(peerIdStr);
|
|
75
|
-
if (peerId.type !== "Ed25519" || peerId.publicKey === undefined) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
const rawPub = peerId.publicKey.raw;
|
|
79
|
-
if (rawPub.length !== 32) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
return ed25519.verify(sig, payload, rawPub);
|
|
83
|
-
} catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic shared Ed25519 sign/verify primitive (libp2p peer keys).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the established peer-key signing pattern in `cluster-repo.ts` / `dispute-service.ts`
|
|
5
|
+
* (`PrivateKey.sign` → base64url; verify against the peer's public key), specialized for the
|
|
6
|
+
* cohort-topic substrate:
|
|
7
|
+
*
|
|
8
|
+
* - **Signing** uses the node's libp2p `PrivateKey.sign` (async — fine on the participant outbound
|
|
9
|
+
* path) and is the seam the threshold-assembly ticket reuses for per-member partial signatures.
|
|
10
|
+
* - **Verification is SYNCHRONOUS**, because the db-core verify ports (e.g. the participant-signature
|
|
11
|
+
* predicates and `ICohortThresholdCrypto.verify`) are synchronous. libp2p's
|
|
12
|
+
* `publicKeyFromRaw(raw).verify` is async, so this verifies with `@noble/curves/ed25519` directly
|
|
13
|
+
* over the raw 32-byte key. noble's `ed25519.verify` (ZIP215) is a strict superset of the RFC8032
|
|
14
|
+
* signatures the libp2p signer (Node/WebCrypto Ed25519) produces, so it accepts them.
|
|
15
|
+
*
|
|
16
|
+
* The signer id on the cohort wire is the UTF-8 bytes of the peer-id string (see `peer-codec.ts`).
|
|
17
|
+
* For an Ed25519 identity the public key is embedded in the identity multihash, so no network lookup
|
|
18
|
+
* is needed — `peerIdFromString(str).publicKey.raw` yields the raw key. Verification is **total**: any
|
|
19
|
+
* non-Ed25519 id, missing key, or malformed input returns `false` (it never throws). The cohort-topic
|
|
20
|
+
* substrate therefore assumes Ed25519 identities (the libp2p default; `generateKeyPair('Ed25519')`).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { PrivateKey } from "@libp2p/interface";
|
|
24
|
+
import { peerIdFromString } from "@libp2p/peer-id";
|
|
25
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
26
|
+
import { bytesToPeerIdString } from "./peer-codec.js";
|
|
27
|
+
|
|
28
|
+
/** Sign `payload` with the node's libp2p peer key. Async — libp2p `PrivateKey.sign` is async. */
|
|
29
|
+
export async function signPeer(privateKey: PrivateKey, payload: Uint8Array): Promise<Uint8Array> {
|
|
30
|
+
return privateKey.sign(payload);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sign `payload` with a libp2p Ed25519 peer key **synchronously** — the mirror of the synchronous
|
|
35
|
+
* {@link verifyPeerSig}. Use this where the signer seam is synchronous and the async {@link signPeer}
|
|
36
|
+
* cannot be awaited: e.g. the reactivity recover `signBackfill` / `signResume` `(unsigned) => string`
|
|
37
|
+
* ports, whose db-core driver builds the unsigned image internally (so a pre-signed value is impossible)
|
|
38
|
+
* and whose seam shape is `=> string`, not `=> Promise<string>`.
|
|
39
|
+
*
|
|
40
|
+
* libp2p's own `PrivateKey.sign` is async (Node / WebCrypto Ed25519), so this signs directly with
|
|
41
|
+
* `@noble/curves/ed25519` over the raw 32-byte seed. libp2p stores an Ed25519 private key as 64 bytes
|
|
42
|
+
* (`raw` = 32-byte seed ‖ 32-byte public key); noble's `ed25519.sign(message, secretKey)` takes the
|
|
43
|
+
* 32-byte seed. noble's signatures are RFC8032-compliant and deterministic, so both verify paths accept
|
|
44
|
+
* them: the libp2p `publicKeyFromRaw(raw).verify` (Node / WebCrypto) and the synchronous
|
|
45
|
+
* {@link verifyPeerSig} (noble, ZIP215) — symmetric with the verify direction's cross-acceptance, and
|
|
46
|
+
* byte-identical to what the async {@link signPeer} would produce for the same key + payload.
|
|
47
|
+
*
|
|
48
|
+
* Assumes an Ed25519 identity (the cohort-topic substrate's standing assumption). Throws on a
|
|
49
|
+
* non-Ed25519 key or an unexpected raw length — a programming error on the node's own key, never
|
|
50
|
+
* attacker-reachable input (the verify side, by contrast, is total and returns `false`).
|
|
51
|
+
*/
|
|
52
|
+
export function signPeerSig(privateKey: PrivateKey, payload: Uint8Array): Uint8Array {
|
|
53
|
+
if (privateKey.type !== "Ed25519") {
|
|
54
|
+
throw new Error(`signPeerSig: expected an Ed25519 private key, got "${privateKey.type}"`);
|
|
55
|
+
}
|
|
56
|
+
const raw = privateKey.raw;
|
|
57
|
+
// 64-byte libp2p form = seed ‖ public key; accept a bare 32-byte seed too.
|
|
58
|
+
const seed = raw.length === 64 ? raw.subarray(0, 32) : raw;
|
|
59
|
+
if (seed.length !== 32) {
|
|
60
|
+
throw new Error(`signPeerSig: expected a 32- or 64-byte Ed25519 raw key, got ${raw.length} bytes`);
|
|
61
|
+
}
|
|
62
|
+
return ed25519.sign(payload, seed);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Verify an Ed25519 peer-key signature synchronously. `signer` is the cohort-wire member id — the
|
|
67
|
+
* UTF-8 bytes of the peer-id string (see {@link bytesToPeerIdString}) — or that peer-id string
|
|
68
|
+
* directly. Returns `false` (never throws) on a non-Ed25519 id, a missing/short key, a malformed
|
|
69
|
+
* signature, or any decode failure.
|
|
70
|
+
*/
|
|
71
|
+
export function verifyPeerSig(signer: string | Uint8Array, payload: Uint8Array, sig: Uint8Array): boolean {
|
|
72
|
+
try {
|
|
73
|
+
const peerIdStr = typeof signer === "string" ? signer : bytesToPeerIdString(signer);
|
|
74
|
+
const peerId = peerIdFromString(peerIdStr);
|
|
75
|
+
if (peerId.type !== "Ed25519" || peerId.publicKey === undefined) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const rawPub = peerId.publicKey.raw;
|
|
79
|
+
if (rawPub.length !== 32) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return ed25519.verify(sig, payload, rawPub);
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cohort-topic libp2p protocol IDs (`docs/cohort-topic.md` §FRET integration L432-460).
|
|
3
|
-
*
|
|
4
|
-
* Five protocols ride the same FRET/libp2p node:
|
|
5
|
-
*
|
|
6
|
-
* - `register` — Register, renew, re-attach (`RegisterV1` / `RenewV1`, routed via `RouteAndMaybeAct`).
|
|
7
|
-
* - `cohort-gossip` — record replication, willingness, load barometers (`CohortGossipV1`).
|
|
8
|
-
* - `promote` — threshold-signed promotion / demotion notices.
|
|
9
|
-
* - `membership` — membership certificates (`MembershipCertV1`).
|
|
10
|
-
* - `sign` — intra-cohort per-member endorsement for `k − x` threshold-signature assembly
|
|
11
|
-
* (`SignRequestV1` / `SignReplyV1`); a member collecting a threshold signature dials
|
|
12
|
-
* each cohort member here and concatenates the returned per-member Ed25519 sigs.
|
|
13
|
-
*
|
|
14
|
-
* The default (network-agnostic) IDs match the doc verbatim; {@link makeCohortTopicProtocols}
|
|
15
|
-
* mirrors FRET's `makeProtocols(networkName)` so a named network namespaces its cohort-topic
|
|
16
|
-
* protocols the same way FRET namespaces its routing protocols.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/** Base path for the cohort-topic protocol family. */
|
|
20
|
-
export const COHORT_TOPIC_BASE = "/optimystic/cohort-topic/1.0.0" as const;
|
|
21
|
-
|
|
22
|
-
/** `RegisterV1` / `RenewV1` — register, renew, re-attach. */
|
|
23
|
-
export const PROTOCOL_COHORT_REGISTER = `${COHORT_TOPIC_BASE}/register` as const;
|
|
24
|
-
/** `CohortGossipV1` — record replication, willingness, load barometers. */
|
|
25
|
-
export const PROTOCOL_COHORT_GOSSIP = `${COHORT_TOPIC_BASE}/cohort-gossip` as const;
|
|
26
|
-
/** Threshold-signed promotion / demotion notices. */
|
|
27
|
-
export const PROTOCOL_COHORT_PROMOTE = `${COHORT_TOPIC_BASE}/promote` as const;
|
|
28
|
-
/** `MembershipCertV1` — membership certificates. */
|
|
29
|
-
export const PROTOCOL_COHORT_MEMBERSHIP = `${COHORT_TOPIC_BASE}/membership` as const;
|
|
30
|
-
/** `SignRequestV1` / `SignReplyV1` — intra-cohort per-member endorsement for threshold-signature assembly. */
|
|
31
|
-
export const PROTOCOL_COHORT_SIGN = `${COHORT_TOPIC_BASE}/sign` as const;
|
|
32
|
-
|
|
33
|
-
/** The five cohort-topic protocol IDs in registration order. */
|
|
34
|
-
export interface CohortTopicProtocols {
|
|
35
|
-
readonly register: string;
|
|
36
|
-
readonly gossip: string;
|
|
37
|
-
readonly promote: string;
|
|
38
|
-
readonly membership: string;
|
|
39
|
-
readonly sign: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Default (network-agnostic) protocol IDs, matching `docs/cohort-topic.md` §FRET integration. */
|
|
43
|
-
export const DEFAULT_COHORT_TOPIC_PROTOCOLS: CohortTopicProtocols = {
|
|
44
|
-
register: PROTOCOL_COHORT_REGISTER,
|
|
45
|
-
gossip: PROTOCOL_COHORT_GOSSIP,
|
|
46
|
-
promote: PROTOCOL_COHORT_PROMOTE,
|
|
47
|
-
membership: PROTOCOL_COHORT_MEMBERSHIP,
|
|
48
|
-
sign: PROTOCOL_COHORT_SIGN,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Namespaced cohort-topic protocol IDs for `networkName` (mirrors FRET's `makeProtocols`, which
|
|
53
|
-
* inserts the network segment even for `"default"` → `/optimystic/default/...`). Note this does NOT
|
|
54
|
-
* equal {@link DEFAULT_COHORT_TOPIC_PROTOCOLS}: the canonical, network-agnostic IDs omit the segment
|
|
55
|
-
* entirely (`/optimystic/cohort-topic/1.0.0/...`); use those unless you need per-network namespacing.
|
|
56
|
-
*/
|
|
57
|
-
export function makeCohortTopicProtocols(networkName = "default"): CohortTopicProtocols {
|
|
58
|
-
const base = `/optimystic/${networkName}/cohort-topic/1.0.0`;
|
|
59
|
-
return {
|
|
60
|
-
register: `${base}/register`,
|
|
61
|
-
gossip: `${base}/cohort-gossip`,
|
|
62
|
-
promote: `${base}/promote`,
|
|
63
|
-
membership: `${base}/membership`,
|
|
64
|
-
sign: `${base}/sign`,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** All five protocol IDs as an array (for `node.handle` / `unhandle` over the set). */
|
|
69
|
-
export function cohortTopicProtocolList(p: CohortTopicProtocols): string[] {
|
|
70
|
-
return [p.register, p.gossip, p.promote, p.membership, p.sign];
|
|
71
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic libp2p protocol IDs (`docs/cohort-topic.md` §FRET integration L432-460).
|
|
3
|
+
*
|
|
4
|
+
* Five protocols ride the same FRET/libp2p node:
|
|
5
|
+
*
|
|
6
|
+
* - `register` — Register, renew, re-attach (`RegisterV1` / `RenewV1`, routed via `RouteAndMaybeAct`).
|
|
7
|
+
* - `cohort-gossip` — record replication, willingness, load barometers (`CohortGossipV1`).
|
|
8
|
+
* - `promote` — threshold-signed promotion / demotion notices.
|
|
9
|
+
* - `membership` — membership certificates (`MembershipCertV1`).
|
|
10
|
+
* - `sign` — intra-cohort per-member endorsement for `k − x` threshold-signature assembly
|
|
11
|
+
* (`SignRequestV1` / `SignReplyV1`); a member collecting a threshold signature dials
|
|
12
|
+
* each cohort member here and concatenates the returned per-member Ed25519 sigs.
|
|
13
|
+
*
|
|
14
|
+
* The default (network-agnostic) IDs match the doc verbatim; {@link makeCohortTopicProtocols}
|
|
15
|
+
* mirrors FRET's `makeProtocols(networkName)` so a named network namespaces its cohort-topic
|
|
16
|
+
* protocols the same way FRET namespaces its routing protocols.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Base path for the cohort-topic protocol family. */
|
|
20
|
+
export const COHORT_TOPIC_BASE = "/optimystic/cohort-topic/1.0.0" as const;
|
|
21
|
+
|
|
22
|
+
/** `RegisterV1` / `RenewV1` — register, renew, re-attach. */
|
|
23
|
+
export const PROTOCOL_COHORT_REGISTER = `${COHORT_TOPIC_BASE}/register` as const;
|
|
24
|
+
/** `CohortGossipV1` — record replication, willingness, load barometers. */
|
|
25
|
+
export const PROTOCOL_COHORT_GOSSIP = `${COHORT_TOPIC_BASE}/cohort-gossip` as const;
|
|
26
|
+
/** Threshold-signed promotion / demotion notices. */
|
|
27
|
+
export const PROTOCOL_COHORT_PROMOTE = `${COHORT_TOPIC_BASE}/promote` as const;
|
|
28
|
+
/** `MembershipCertV1` — membership certificates. */
|
|
29
|
+
export const PROTOCOL_COHORT_MEMBERSHIP = `${COHORT_TOPIC_BASE}/membership` as const;
|
|
30
|
+
/** `SignRequestV1` / `SignReplyV1` — intra-cohort per-member endorsement for threshold-signature assembly. */
|
|
31
|
+
export const PROTOCOL_COHORT_SIGN = `${COHORT_TOPIC_BASE}/sign` as const;
|
|
32
|
+
|
|
33
|
+
/** The five cohort-topic protocol IDs in registration order. */
|
|
34
|
+
export interface CohortTopicProtocols {
|
|
35
|
+
readonly register: string;
|
|
36
|
+
readonly gossip: string;
|
|
37
|
+
readonly promote: string;
|
|
38
|
+
readonly membership: string;
|
|
39
|
+
readonly sign: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Default (network-agnostic) protocol IDs, matching `docs/cohort-topic.md` §FRET integration. */
|
|
43
|
+
export const DEFAULT_COHORT_TOPIC_PROTOCOLS: CohortTopicProtocols = {
|
|
44
|
+
register: PROTOCOL_COHORT_REGISTER,
|
|
45
|
+
gossip: PROTOCOL_COHORT_GOSSIP,
|
|
46
|
+
promote: PROTOCOL_COHORT_PROMOTE,
|
|
47
|
+
membership: PROTOCOL_COHORT_MEMBERSHIP,
|
|
48
|
+
sign: PROTOCOL_COHORT_SIGN,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Namespaced cohort-topic protocol IDs for `networkName` (mirrors FRET's `makeProtocols`, which
|
|
53
|
+
* inserts the network segment even for `"default"` → `/optimystic/default/...`). Note this does NOT
|
|
54
|
+
* equal {@link DEFAULT_COHORT_TOPIC_PROTOCOLS}: the canonical, network-agnostic IDs omit the segment
|
|
55
|
+
* entirely (`/optimystic/cohort-topic/1.0.0/...`); use those unless you need per-network namespacing.
|
|
56
|
+
*/
|
|
57
|
+
export function makeCohortTopicProtocols(networkName = "default"): CohortTopicProtocols {
|
|
58
|
+
const base = `/optimystic/${networkName}/cohort-topic/1.0.0`;
|
|
59
|
+
return {
|
|
60
|
+
register: `${base}/register`,
|
|
61
|
+
gossip: `${base}/cohort-gossip`,
|
|
62
|
+
promote: `${base}/promote`,
|
|
63
|
+
membership: `${base}/membership`,
|
|
64
|
+
sign: `${base}/sign`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** All five protocol IDs as an array (for `node.handle` / `unhandle` over the set). */
|
|
69
|
+
export function cohortTopicProtocolList(p: CohortTopicProtocols): string[] {
|
|
70
|
+
return [p.register, p.gossip, p.promote, p.membership, p.sign];
|
|
71
|
+
}
|
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reactivity origination membership gate (`docs/reactivity.md` §Anchor, `docs/internals.md`
|
|
3
|
-
* §Cohort-Topic Origination Bridge).
|
|
4
|
-
*
|
|
5
|
-
* Builds the `selfIsCohortMember(event)` predicate the change-notifier bridge gates each member commit
|
|
6
|
-
* on: this node is responsible for a collection's reactivity-topic fan-out iff it sits in the FRET
|
|
7
|
-
* cohort around `coord_0(H(currentTailId ‖ "reactivity"))`. The tail comes from `event.tailId` (the seam
|
|
8
|
-
* ticket put the committed `CommitRequest.tailId` on the change event), so the decision is synchronous —
|
|
9
|
-
* no async storage read, no per-collection registry. A tail-less event (a read-driven promotion) is
|
|
10
|
-
* never a member; those never originate and are cert-gated out downstream anyway.
|
|
11
|
-
*
|
|
12
|
-
* Extracted from the node assembly so the encoding + coord derivation are unit-testable in isolation and
|
|
13
|
-
* reusable by the future reactivity origination wiring.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
createReactivityTopicAnchor,
|
|
18
|
-
createTierAddressing,
|
|
19
|
-
createRingHash,
|
|
20
|
-
type ReactivityTopicAnchor,
|
|
21
|
-
type TierAddressing,
|
|
22
|
-
type CollectionChangeEvent,
|
|
23
|
-
} from "@optimystic/db-core";
|
|
24
|
-
import type { FretService } from "p2p-fret";
|
|
25
|
-
|
|
26
|
-
// The pinned `BlockId` → raw tail-bytes encoding now lives in the reactivity surface so the origination
|
|
27
|
-
// gate (here) and the subscriber-side subscribe factory import the SAME function — see `reactivity/
|
|
28
|
-
// topic-bytes.ts` for the load-bearing rationale. Re-exported so this module's existing callers (tests,
|
|
29
|
-
// node wiring) keep importing `reactivityTailBytes` from the gate.
|
|
30
|
-
export { reactivityTailBytes } from "../reactivity/topic-bytes.js";
|
|
31
|
-
import { reactivityTailBytes } from "../reactivity/topic-bytes.js";
|
|
32
|
-
|
|
33
|
-
/** Construction inputs for {@link createReactivitySelfMembershipGate}. */
|
|
34
|
-
export interface ReactivitySelfMembershipGateDeps {
|
|
35
|
-
/** FRET membership read — the cohort assembler around a served coord (returns peer-id strings). */
|
|
36
|
-
readonly fret: Pick<FretService, "assembleCohort">;
|
|
37
|
-
/** This node's peer-id string, compared against the assembled cohort. */
|
|
38
|
-
readonly selfPeerId: string;
|
|
39
|
-
/**
|
|
40
|
-
* Requested cohort size `wantK`. MUST equal the cohort-topic host's `wantK`, so the gate checks the
|
|
41
|
-
* exact cohort the host serves; a mismatch silently breaks origination.
|
|
42
|
-
*/
|
|
43
|
-
readonly wantK: number;
|
|
44
|
-
/**
|
|
45
|
-
* Reactivity topic anchor. Default db-core's {@link createReactivityTopicAnchor} (256-bit SHA-256),
|
|
46
|
-
* byte-identical to the host's internal `new RingHash()` and the subscriber-side anchor.
|
|
47
|
-
*/
|
|
48
|
-
readonly anchor?: ReactivityTopicAnchor;
|
|
49
|
-
/**
|
|
50
|
-
* Tier addressing. Default `createTierAddressing(createRingHash())`. `coord_0` is fan-out independent
|
|
51
|
-
* (`H(0x00 ‖ topicId)`), so the default fan-out is irrelevant to the tier-0 reactivity coord.
|
|
52
|
-
*/
|
|
53
|
-
readonly addressing?: TierAddressing;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Build the synchronous `selfIsCohortMember(event)` gate for {@link attachCohortChangeBridge}.
|
|
58
|
-
*
|
|
59
|
-
* Returns `false` for a tail-less event; otherwise resolves `coord_0(H(event.tailId ‖ "reactivity"))`
|
|
60
|
-
* via the shared anchor + tier addressing and returns whether `fret.assembleCohort(coord, wantK)`
|
|
61
|
-
* includes this node's peer id. Pure aside from the live FRET read; membership churn between commits is
|
|
62
|
-
* fine because reactivity is hint-only.
|
|
63
|
-
*/
|
|
64
|
-
export function createReactivitySelfMembershipGate(
|
|
65
|
-
deps: ReactivitySelfMembershipGateDeps,
|
|
66
|
-
): (event: CollectionChangeEvent) => boolean {
|
|
67
|
-
const anchor = deps.anchor ?? createReactivityTopicAnchor();
|
|
68
|
-
const addressing = deps.addressing ?? createTierAddressing(createRingHash());
|
|
69
|
-
return (event: CollectionChangeEvent): boolean => {
|
|
70
|
-
if (event.tailId === undefined) {
|
|
71
|
-
return false; // promotion / tail-less event never originates
|
|
72
|
-
}
|
|
73
|
-
const topicId = anchor.topicId(reactivityTailBytes(event.tailId));
|
|
74
|
-
const coord = addressing.coord0(topicId);
|
|
75
|
-
return deps.fret.assembleCohort(coord, deps.wantK).includes(deps.selfPeerId);
|
|
76
|
-
};
|
|
77
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity origination membership gate (`docs/reactivity.md` §Anchor, `docs/internals.md`
|
|
3
|
+
* §Cohort-Topic Origination Bridge).
|
|
4
|
+
*
|
|
5
|
+
* Builds the `selfIsCohortMember(event)` predicate the change-notifier bridge gates each member commit
|
|
6
|
+
* on: this node is responsible for a collection's reactivity-topic fan-out iff it sits in the FRET
|
|
7
|
+
* cohort around `coord_0(H(currentTailId ‖ "reactivity"))`. The tail comes from `event.tailId` (the seam
|
|
8
|
+
* ticket put the committed `CommitRequest.tailId` on the change event), so the decision is synchronous —
|
|
9
|
+
* no async storage read, no per-collection registry. A tail-less event (a read-driven promotion) is
|
|
10
|
+
* never a member; those never originate and are cert-gated out downstream anyway.
|
|
11
|
+
*
|
|
12
|
+
* Extracted from the node assembly so the encoding + coord derivation are unit-testable in isolation and
|
|
13
|
+
* reusable by the future reactivity origination wiring.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createReactivityTopicAnchor,
|
|
18
|
+
createTierAddressing,
|
|
19
|
+
createRingHash,
|
|
20
|
+
type ReactivityTopicAnchor,
|
|
21
|
+
type TierAddressing,
|
|
22
|
+
type CollectionChangeEvent,
|
|
23
|
+
} from "@optimystic/db-core";
|
|
24
|
+
import type { FretService } from "p2p-fret";
|
|
25
|
+
|
|
26
|
+
// The pinned `BlockId` → raw tail-bytes encoding now lives in the reactivity surface so the origination
|
|
27
|
+
// gate (here) and the subscriber-side subscribe factory import the SAME function — see `reactivity/
|
|
28
|
+
// topic-bytes.ts` for the load-bearing rationale. Re-exported so this module's existing callers (tests,
|
|
29
|
+
// node wiring) keep importing `reactivityTailBytes` from the gate.
|
|
30
|
+
export { reactivityTailBytes } from "../reactivity/topic-bytes.js";
|
|
31
|
+
import { reactivityTailBytes } from "../reactivity/topic-bytes.js";
|
|
32
|
+
|
|
33
|
+
/** Construction inputs for {@link createReactivitySelfMembershipGate}. */
|
|
34
|
+
export interface ReactivitySelfMembershipGateDeps {
|
|
35
|
+
/** FRET membership read — the cohort assembler around a served coord (returns peer-id strings). */
|
|
36
|
+
readonly fret: Pick<FretService, "assembleCohort">;
|
|
37
|
+
/** This node's peer-id string, compared against the assembled cohort. */
|
|
38
|
+
readonly selfPeerId: string;
|
|
39
|
+
/**
|
|
40
|
+
* Requested cohort size `wantK`. MUST equal the cohort-topic host's `wantK`, so the gate checks the
|
|
41
|
+
* exact cohort the host serves; a mismatch silently breaks origination.
|
|
42
|
+
*/
|
|
43
|
+
readonly wantK: number;
|
|
44
|
+
/**
|
|
45
|
+
* Reactivity topic anchor. Default db-core's {@link createReactivityTopicAnchor} (256-bit SHA-256),
|
|
46
|
+
* byte-identical to the host's internal `new RingHash()` and the subscriber-side anchor.
|
|
47
|
+
*/
|
|
48
|
+
readonly anchor?: ReactivityTopicAnchor;
|
|
49
|
+
/**
|
|
50
|
+
* Tier addressing. Default `createTierAddressing(createRingHash())`. `coord_0` is fan-out independent
|
|
51
|
+
* (`H(0x00 ‖ topicId)`), so the default fan-out is irrelevant to the tier-0 reactivity coord.
|
|
52
|
+
*/
|
|
53
|
+
readonly addressing?: TierAddressing;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the synchronous `selfIsCohortMember(event)` gate for {@link attachCohortChangeBridge}.
|
|
58
|
+
*
|
|
59
|
+
* Returns `false` for a tail-less event; otherwise resolves `coord_0(H(event.tailId ‖ "reactivity"))`
|
|
60
|
+
* via the shared anchor + tier addressing and returns whether `fret.assembleCohort(coord, wantK)`
|
|
61
|
+
* includes this node's peer id. Pure aside from the live FRET read; membership churn between commits is
|
|
62
|
+
* fine because reactivity is hint-only.
|
|
63
|
+
*/
|
|
64
|
+
export function createReactivitySelfMembershipGate(
|
|
65
|
+
deps: ReactivitySelfMembershipGateDeps,
|
|
66
|
+
): (event: CollectionChangeEvent) => boolean {
|
|
67
|
+
const anchor = deps.anchor ?? createReactivityTopicAnchor();
|
|
68
|
+
const addressing = deps.addressing ?? createTierAddressing(createRingHash());
|
|
69
|
+
return (event: CollectionChangeEvent): boolean => {
|
|
70
|
+
if (event.tailId === undefined) {
|
|
71
|
+
return false; // promotion / tail-less event never originates
|
|
72
|
+
}
|
|
73
|
+
const topicId = anchor.topicId(reactivityTailBytes(event.tailId));
|
|
74
|
+
const coord = addressing.coord0(topicId);
|
|
75
|
+
return deps.fret.assembleCohort(coord, deps.wantK).includes(deps.selfPeerId);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type { ISizeEstimator } from "@optimystic/db-core";
|
|
2
|
-
import type { FretService } from "p2p-fret";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* FRET-backed {@link ISizeEstimator}: feeds `d_max` from FRET's network-size estimate
|
|
6
|
-
* (`FretService.getNetworkSizeEstimate`). A clean, total binding — db-core's `d_max` computer applies
|
|
7
|
-
* the low-confidence clamp itself, so this adapter only forwards FRET's `(size_estimate, confidence)`.
|
|
8
|
-
*/
|
|
9
|
-
export class FretSizeEstimator implements ISizeEstimator {
|
|
10
|
-
constructor(private readonly fret: FretService) {}
|
|
11
|
-
|
|
12
|
-
estimate(): { nEst: number; confidence: number } {
|
|
13
|
-
const { size_estimate, confidence } = this.fret.getNetworkSizeEstimate();
|
|
14
|
-
return { nEst: size_estimate, confidence };
|
|
15
|
-
}
|
|
16
|
-
}
|
|
1
|
+
import type { ISizeEstimator } from "@optimystic/db-core";
|
|
2
|
+
import type { FretService } from "p2p-fret";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* FRET-backed {@link ISizeEstimator}: feeds `d_max` from FRET's network-size estimate
|
|
6
|
+
* (`FretService.getNetworkSizeEstimate`). A clean, total binding — db-core's `d_max` computer applies
|
|
7
|
+
* the low-confidence clamp itself, so this adapter only forwards FRET's `(size_estimate, confidence)`.
|
|
8
|
+
*/
|
|
9
|
+
export class FretSizeEstimator implements ISizeEstimator {
|
|
10
|
+
constructor(private readonly fret: FretService) {}
|
|
11
|
+
|
|
12
|
+
estimate(): { nEst: number; confidence: number } {
|
|
13
|
+
const { size_estimate, confidence } = this.fret.getNetworkSizeEstimate();
|
|
14
|
+
return { nEst: size_estimate, confidence };
|
|
15
|
+
}
|
|
16
|
+
}
|