@optimystic/db-core 0.21.0 → 0.24.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.
Files changed (185) hide show
  1. package/README.md +336 -336
  2. package/dist/src/btree/btree.d.ts +2 -1
  3. package/dist/src/btree/btree.d.ts.map +1 -1
  4. package/dist/src/btree/btree.js +1 -1
  5. package/dist/src/btree/btree.js.map +1 -1
  6. package/dist/src/chain/chain.d.ts +1 -1
  7. package/dist/src/chain/chain.d.ts.map +1 -1
  8. package/dist/src/chain/chain.js +1 -1
  9. package/dist/src/chain/chain.js.map +1 -1
  10. package/dist/src/cluster/structs.d.ts +39 -1
  11. package/dist/src/cluster/structs.d.ts.map +1 -1
  12. package/dist/src/cluster/structs.js +24 -0
  13. package/dist/src/cluster/structs.js.map +1 -1
  14. package/dist/src/collection/collection.d.ts +20 -1
  15. package/dist/src/collection/collection.d.ts.map +1 -1
  16. package/dist/src/collection/collection.js +31 -4
  17. package/dist/src/collection/collection.js.map +1 -1
  18. package/dist/src/collections/diary/diary.d.ts.map +1 -1
  19. package/dist/src/collections/diary/diary.js +2 -1
  20. package/dist/src/collections/diary/diary.js.map +1 -1
  21. package/dist/src/collections/diary/struct.js +1 -1
  22. package/dist/src/collections/diary/struct.js.map +1 -1
  23. package/dist/src/collections/tree/collection-trunk.js +1 -1
  24. package/dist/src/collections/tree/collection-trunk.js.map +1 -1
  25. package/dist/src/collections/tree/struct.d.ts +1 -1
  26. package/dist/src/collections/tree/struct.d.ts.map +1 -1
  27. package/dist/src/collections/tree/struct.js +2 -1
  28. package/dist/src/collections/tree/struct.js.map +1 -1
  29. package/dist/src/collections/tree/tree.d.ts +5 -0
  30. package/dist/src/collections/tree/tree.d.ts.map +1 -1
  31. package/dist/src/collections/tree/tree.js +7 -0
  32. package/dist/src/collections/tree/tree.js.map +1 -1
  33. package/dist/src/log/log.d.ts +1 -1
  34. package/dist/src/log/log.d.ts.map +1 -1
  35. package/dist/src/log/log.js +2 -2
  36. package/dist/src/log/log.js.map +1 -1
  37. package/dist/src/network/i-peer-network.d.ts +16 -0
  38. package/dist/src/network/i-peer-network.d.ts.map +1 -1
  39. package/dist/src/network/struct.d.ts +39 -2
  40. package/dist/src/network/struct.d.ts.map +1 -1
  41. package/dist/src/network/struct.js +18 -0
  42. package/dist/src/network/struct.js.map +1 -1
  43. package/dist/src/testing/test-transactor.d.ts +95 -8
  44. package/dist/src/testing/test-transactor.d.ts.map +1 -1
  45. package/dist/src/testing/test-transactor.js +133 -12
  46. package/dist/src/testing/test-transactor.js.map +1 -1
  47. package/dist/src/transaction/coordinator.d.ts.map +1 -1
  48. package/dist/src/transaction/coordinator.js +2 -1
  49. package/dist/src/transaction/coordinator.js.map +1 -1
  50. package/dist/src/transaction/transaction.d.ts +1 -1
  51. package/dist/src/transaction/transaction.js +1 -1
  52. package/dist/src/transactor/network-transactor.d.ts.map +1 -1
  53. package/dist/src/transactor/network-transactor.js +57 -18
  54. package/dist/src/transactor/network-transactor.js.map +1 -1
  55. package/dist/src/transactor/transactor-source.d.ts.map +1 -1
  56. package/dist/src/transactor/transactor-source.js +25 -2
  57. package/dist/src/transactor/transactor-source.js.map +1 -1
  58. package/dist/src/transform/cache-source.js +1 -1
  59. package/dist/src/transform/cache-source.js.map +1 -1
  60. package/dist/src/transform/helpers.d.ts +6 -1
  61. package/dist/src/transform/helpers.d.ts.map +1 -1
  62. package/dist/src/transform/helpers.js +7 -6
  63. package/dist/src/transform/helpers.js.map +1 -1
  64. package/dist/src/transform/tracker.d.ts.map +1 -1
  65. package/dist/src/transform/tracker.js +2 -1
  66. package/dist/src/transform/tracker.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/btree/btree.ts +2 -1
  69. package/src/chain/chain.ts +2 -1
  70. package/src/cluster/membership.ts +85 -85
  71. package/src/cluster/structs.ts +43 -4
  72. package/src/cohort-topic/addressing.ts +120 -120
  73. package/src/cohort-topic/antidos/bootstrap-evidence-envelope.ts +253 -253
  74. package/src/cohort-topic/antidos/bootstrap-evidence.ts +106 -106
  75. package/src/cohort-topic/antidos/index.ts +5 -5
  76. package/src/cohort-topic/antidos/rate-limiter.ts +210 -210
  77. package/src/cohort-topic/antidos/replay-guard.ts +146 -146
  78. package/src/cohort-topic/antidos/topic-budget.ts +160 -160
  79. package/src/cohort-topic/antiflood/index.ts +2 -2
  80. package/src/cohort-topic/antiflood/invariants.ts +108 -108
  81. package/src/cohort-topic/antiflood/jitter.ts +117 -117
  82. package/src/cohort-topic/coldstart.ts +237 -237
  83. package/src/cohort-topic/dmax.ts +88 -88
  84. package/src/cohort-topic/gossip/bus.ts +254 -254
  85. package/src/cohort-topic/gossip/index.ts +3 -3
  86. package/src/cohort-topic/gossip/records.ts +45 -45
  87. package/src/cohort-topic/gossip/view.ts +91 -91
  88. package/src/cohort-topic/index.ts +20 -20
  89. package/src/cohort-topic/load/barometer.ts +134 -134
  90. package/src/cohort-topic/load/index.ts +1 -1
  91. package/src/cohort-topic/member-engine.ts +430 -430
  92. package/src/cohort-topic/membership/index.ts +3 -3
  93. package/src/cohort-topic/membership/publisher.ts +163 -163
  94. package/src/cohort-topic/membership/source.ts +41 -41
  95. package/src/cohort-topic/membership/verifier.ts +461 -461
  96. package/src/cohort-topic/ports.ts +157 -157
  97. package/src/cohort-topic/promotion.ts +405 -405
  98. package/src/cohort-topic/registration/bytes.ts +37 -37
  99. package/src/cohort-topic/registration/handoff.ts +154 -154
  100. package/src/cohort-topic/registration/index.ts +6 -6
  101. package/src/cohort-topic/registration/renewal.ts +495 -495
  102. package/src/cohort-topic/registration/sharding.ts +61 -61
  103. package/src/cohort-topic/registration/store.ts +81 -81
  104. package/src/cohort-topic/registration/types.ts +91 -91
  105. package/src/cohort-topic/ring-hash.ts +50 -50
  106. package/src/cohort-topic/service.ts +416 -416
  107. package/src/cohort-topic/sig/index.ts +2 -2
  108. package/src/cohort-topic/sig/payloads.ts +59 -59
  109. package/src/cohort-topic/sig/threshold.ts +64 -64
  110. package/src/cohort-topic/tiers.ts +74 -74
  111. package/src/cohort-topic/traffic.ts +233 -233
  112. package/src/cohort-topic/walk.ts +326 -326
  113. package/src/cohort-topic/willingness.ts +237 -237
  114. package/src/cohort-topic/wire/codec.ts +216 -216
  115. package/src/cohort-topic/wire/index.ts +18 -18
  116. package/src/cohort-topic/wire/payloads.ts +126 -126
  117. package/src/cohort-topic/wire/primitives.ts +188 -188
  118. package/src/cohort-topic/wire/types.ts +475 -475
  119. package/src/cohort-topic/wire/validate.ts +512 -512
  120. package/src/collection/collection-type-registry.ts +37 -37
  121. package/src/collection/collection.ts +32 -4
  122. package/src/collections/diary/diary.ts +68 -67
  123. package/src/collections/diary/struct.ts +1 -1
  124. package/src/collections/tree/collection-trunk.ts +1 -1
  125. package/src/collections/tree/readme.md +4 -0
  126. package/src/collections/tree/struct.ts +3 -1
  127. package/src/collections/tree/tree.ts +320 -312
  128. package/src/log/log.ts +2 -2
  129. package/src/matchmaking/capability-filter.ts +45 -45
  130. package/src/matchmaking/config.ts +98 -98
  131. package/src/matchmaking/index.ts +21 -21
  132. package/src/matchmaking/multi-cohort-seeker.ts +234 -234
  133. package/src/matchmaking/provider.ts +123 -123
  134. package/src/matchmaking/query-eval.ts +105 -105
  135. package/src/matchmaking/seeker-walk.ts +127 -127
  136. package/src/matchmaking/seeker.ts +86 -86
  137. package/src/matchmaking/topic-anchor.ts +90 -90
  138. package/src/matchmaking/voting-quorum.ts +394 -394
  139. package/src/matchmaking/wire.ts +603 -603
  140. package/src/network/i-peer-network.ts +17 -0
  141. package/src/network/stale-failure.ts +43 -43
  142. package/src/network/struct.ts +41 -2
  143. package/src/network/types.ts +37 -37
  144. package/src/reactivity/backfill.ts +220 -220
  145. package/src/reactivity/backpressure.ts +191 -191
  146. package/src/reactivity/checkpoint.ts +308 -308
  147. package/src/reactivity/config.ts +172 -172
  148. package/src/reactivity/dedupe.ts +132 -132
  149. package/src/reactivity/forwarder.ts +87 -87
  150. package/src/reactivity/index.ts +34 -34
  151. package/src/reactivity/notification.ts +123 -123
  152. package/src/reactivity/policy.ts +79 -79
  153. package/src/reactivity/push-state.ts +310 -310
  154. package/src/reactivity/recover.ts +153 -153
  155. package/src/reactivity/replay-buffer.ts +141 -141
  156. package/src/reactivity/resume.ts +549 -549
  157. package/src/reactivity/rotation.ts +415 -415
  158. package/src/reactivity/subscriber.ts +132 -132
  159. package/src/reactivity/subscription.ts +66 -66
  160. package/src/reactivity/topic-anchor.ts +71 -71
  161. package/src/reactivity/verify.ts +73 -73
  162. package/src/reactivity/wire-validate.ts +13 -13
  163. package/src/reactivity/wire.ts +224 -224
  164. package/src/testing/async-wait.ts +65 -65
  165. package/src/testing/index.ts +2 -2
  166. package/src/testing/test-transactor.ts +638 -489
  167. package/src/transaction/coordinator.ts +2 -1
  168. package/src/transaction/errors.ts +91 -91
  169. package/src/transaction/operations-hash.ts +196 -196
  170. package/src/transaction/read-dependency-collector.ts +78 -78
  171. package/src/transaction/transaction.ts +1 -1
  172. package/src/transactor/change-notifier.ts +80 -80
  173. package/src/transactor/index.ts +5 -5
  174. package/src/transactor/network-transactor.ts +58 -19
  175. package/src/transactor/transactor-source.ts +25 -2
  176. package/src/transform/atomic-proxy.ts +92 -92
  177. package/src/transform/cache-source.ts +1 -1
  178. package/src/transform/helpers.ts +159 -158
  179. package/src/transform/tracker.ts +2 -1
  180. package/src/utility/backoff.ts +95 -95
  181. package/src/utility/batch-coordinator.ts +191 -191
  182. package/dist/src/transaction/context.d.ts +0 -60
  183. package/dist/src/transaction/context.d.ts.map +0 -1
  184. package/dist/src/transaction/context.js +0 -91
  185. package/dist/src/transaction/context.js.map +0 -1
@@ -1,9 +1,48 @@
1
1
  import type { RepoMessage } from "../network/repo-protocol.js";
2
2
 
3
- export type Signature = {
4
- type: 'approve' | 'reject';
5
- signature: string;
6
- rejectReason?: string;
3
+ /**
4
+ * One member's vote on a cluster transaction, in either the promise or the commit map.
5
+ *
6
+ * A discriminated union rather than one shape with optional fields, so each vote kind carries
7
+ * exactly its own payload: a `conflict` without its `conflictWith` (or a stray `rejectReason` on an
8
+ * `approve`) does not typecheck. Every variant's extra field is folded into the signed payload
9
+ * ({@link clusterVoteSigningPayload}), so none of them can be altered in transit.
10
+ */
11
+ export type Signature =
12
+ | { type: 'approve'; signature: string }
13
+ | { type: 'reject'; signature: string; rejectReason?: string }
14
+ /**
15
+ * This member refuses the transaction *for now*: it holds a conflicting transaction that won
16
+ * the deterministic race (`resolveRace`). Retryable — NOT a validity judgement, and never
17
+ * counted toward the permanent-rejection threshold. `conflictWith` is the winning
18
+ * transaction's messageHash: structured, signed, and readable without parsing prose.
19
+ */
20
+ | { type: 'conflict'; signature: string; conflictWith: string };
21
+
22
+ /**
23
+ * The exact bytes a vote signature covers: `<hash>:<type>[:<extra>]`, where `extra` is the variant's
24
+ * own payload — a reject's `rejectReason`, a conflict's `conflictWith`, nothing for an approve.
25
+ * Folding the extra in is what makes it integrity-protected in transit rather than free-floating
26
+ * prose.
27
+ *
28
+ * Producers and verifiers must both build the preimage here. It lives beside {@link Signature}
29
+ * rather than in either consumer because a second copy that forgets a variant does not fail loudly:
30
+ * it reports an honest vote as an invalid signature. (The dispute path once carried such a copy.)
31
+ *
32
+ * "Cluster" in the name distinguishes these consensus votes from the dispute subsystem's
33
+ * arbitration votes, which have their own unrelated preimage (`dispute/invalidation.ts`).
34
+ */
35
+ export function clusterVoteSigningPayload(hash: string, type: Signature['type'], extra?: string): Uint8Array {
36
+ return new TextEncoder().encode(hash + ':' + type + (extra ? ':' + extra : ''));
37
+ }
38
+
39
+ /** Verifier-side {@link clusterVoteSigningPayload}: reads each variant's signed extra off the vote itself. */
40
+ export function clusterVoteVerificationPayload(hash: string, signature: Signature): Uint8Array {
41
+ switch (signature.type) {
42
+ case 'reject': return clusterVoteSigningPayload(hash, 'reject', signature.rejectReason);
43
+ case 'conflict': return clusterVoteSigningPayload(hash, 'conflict', signature.conflictWith);
44
+ default: return clusterVoteSigningPayload(hash, signature.type);
45
+ }
7
46
  }
8
47
 
9
48
  export type ClusterPeers = {
@@ -1,120 +1,120 @@
1
- /**
2
- * Cohort-topic substrate — tier addressing (`coord_d`).
3
- *
4
- * Transcribed from `docs/cohort-topic.md` §Tier addressing:
5
- *
6
- * ```
7
- * coord_0(_, topicId) = H(0x00 ‖ topicId)
8
- * coord_d(P, topicId) = H(d ‖ prefix(H(P), d·log₂F) ‖ topicId) for d ≥ 1
9
- * ```
10
- *
11
- * `P` is ring-hashed before the prefix so the shard input is uniformly distributed across
12
- * participants (the raw peer-id string bytes share a near-constant `12D3Koo…` prefix that would
13
- * collapse all tier-`d` shards to one coordinate). The wire field `participantCoord` keeps the
14
- * unmodified peer id so the Ed25519 key remains recoverable; the ring-hash is applied only inside
15
- * the addressing math.
16
- *
17
- * `H` is the injected {@link IRingHash} (db-core's own SHA-256 truncated to the ring width — **not**
18
- * a FRET import). `prefix(P, n)` is the `n` most-significant bits of peer id `P`, left-padded if
19
- * shorter. `F` is the fan-out (default 16, `log₂F = 4`); tier `d` has exactly `F^d` coordinates.
20
- *
21
- * The db-p2p binding is responsible for ensuring the coord byte layout (ring width) matches FRET's
22
- * `RING_BITS` so the routing keys produced here line up with FRET's ring on the wire.
23
- */
24
-
25
- import type { IRingHash, RingCoord } from "./ports.js";
26
-
27
- /** Tier-addressing surface — derives the ring coordinate for tier `d` of a topic. */
28
- export interface TierAddressing {
29
- /** Fan-out per tier (default 16). */
30
- readonly F: number;
31
- /** Tier-0 root coordinate: `H(0x00 ‖ topicId)`. Peer-independent. */
32
- coord0(topicId: Uint8Array): RingCoord;
33
- /** Tier-`d` coordinate for `d ≥ 1`: `H(d ‖ prefix(H(P), d·log₂F) ‖ topicId)` where `H(P)` is the ring-hash of `peerId`. */
34
- coordD(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord;
35
- /** Dispatches `d === 0` to {@link coord0}, otherwise to {@link coordD}. */
36
- coord(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord;
37
- }
38
-
39
- /** Default fan-out per tier (`log₂16 = 4`). */
40
- export const DEFAULT_FANOUT = 16;
41
-
42
- /**
43
- * `prefix(P, n)` — the `n` most-significant bits of `P`, packed MSB-first into `⌈n/8⌉` bytes with
44
- * the trailing partial byte's unused low bits zeroed. If `P` carries fewer than `n` bits it is
45
- * **left-padded**: the high `(n − bits(P))` positions are zero and `P`'s bits occupy the low end, so
46
- * the result is always exactly `n` bits wide. `n === 0` yields an empty array (the tier-0 case).
47
- *
48
- * Exported for tests; not part of the public substrate surface.
49
- */
50
- export function prefixBits(peerId: Uint8Array, n: number): Uint8Array {
51
- if (!Number.isInteger(n) || n < 0) {
52
- throw new RangeError(`prefix bit count must be a non-negative integer, got ${n}`);
53
- }
54
- const outBytes = Math.ceil(n / 8);
55
- const out = new Uint8Array(outBytes);
56
- const available = peerId.length * 8;
57
- const pad = Math.max(0, n - available);
58
- for (let j = 0; j < n; j++) {
59
- // Result bit j (MSB-first). High `pad` positions are zero (left-pad); the rest map to P.
60
- if (j < pad) continue;
61
- const srcBit = j - pad;
62
- const srcByte = peerId[srcBit >>> 3]!;
63
- const bit = (srcByte >>> (7 - (srcBit & 7))) & 1;
64
- if (bit) out[j >>> 3]! |= 1 << (7 - (j & 7));
65
- }
66
- return out;
67
- }
68
-
69
- /** Implements {@link TierAddressing} over an injected {@link IRingHash}. */
70
- export class HashTierAddressing implements TierAddressing {
71
- public readonly F: number;
72
- private readonly log2F: number;
73
-
74
- constructor(private readonly hash: IRingHash, F: number = DEFAULT_FANOUT) {
75
- if (!Number.isInteger(F) || F < 2) {
76
- throw new RangeError(`fan-out F must be an integer ≥ 2, got ${F}`);
77
- }
78
- const log2F = Math.log2(F);
79
- if (!Number.isInteger(log2F)) {
80
- throw new RangeError(`fan-out F must be a power of two, got ${F}`);
81
- }
82
- this.F = F;
83
- this.log2F = log2F;
84
- }
85
-
86
- coord0(topicId: Uint8Array): RingCoord {
87
- // H(0x00 ‖ topicId)
88
- const input = new Uint8Array(1 + topicId.length);
89
- input[0] = 0x00;
90
- input.set(topicId, 1);
91
- return this.hash.H(input);
92
- }
93
-
94
- coordD(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord {
95
- if (!Number.isInteger(d) || d < 1) {
96
- throw new RangeError(`coordD requires an integer tier d ≥ 1, got ${d}`);
97
- }
98
- if (d > 255) {
99
- throw new RangeError(`tier d must fit in one byte (≤ 255), got ${d}`);
100
- }
101
- // H(d ‖ prefix(H(P), d·log₂F) ‖ topicId) — ring-hash P first so the shard input is uniform
102
- // NOTE: re-hashes peerId on every coordD call; a walk over a tier ladder recomputes H(self) per
103
- // tier. Negligible today (walk steps are network-bound); if coord becomes hot, cache H(peerId).
104
- const prefix = prefixBits(this.hash.H(peerId), d * this.log2F);
105
- const input = new Uint8Array(1 + prefix.length + topicId.length);
106
- input[0] = d;
107
- input.set(prefix, 1);
108
- input.set(topicId, 1 + prefix.length);
109
- return this.hash.H(input);
110
- }
111
-
112
- coord(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord {
113
- return d === 0 ? this.coord0(topicId) : this.coordD(d, peerId, topicId);
114
- }
115
- }
116
-
117
- /** Convenience factory mirroring the db-p2p adapter construction style. */
118
- export function createTierAddressing(hash: IRingHash, F: number = DEFAULT_FANOUT): TierAddressing {
119
- return new HashTierAddressing(hash, F);
120
- }
1
+ /**
2
+ * Cohort-topic substrate — tier addressing (`coord_d`).
3
+ *
4
+ * Transcribed from `docs/cohort-topic.md` §Tier addressing:
5
+ *
6
+ * ```
7
+ * coord_0(_, topicId) = H(0x00 ‖ topicId)
8
+ * coord_d(P, topicId) = H(d ‖ prefix(H(P), d·log₂F) ‖ topicId) for d ≥ 1
9
+ * ```
10
+ *
11
+ * `P` is ring-hashed before the prefix so the shard input is uniformly distributed across
12
+ * participants (the raw peer-id string bytes share a near-constant `12D3Koo…` prefix that would
13
+ * collapse all tier-`d` shards to one coordinate). The wire field `participantCoord` keeps the
14
+ * unmodified peer id so the Ed25519 key remains recoverable; the ring-hash is applied only inside
15
+ * the addressing math.
16
+ *
17
+ * `H` is the injected {@link IRingHash} (db-core's own SHA-256 truncated to the ring width — **not**
18
+ * a FRET import). `prefix(P, n)` is the `n` most-significant bits of peer id `P`, left-padded if
19
+ * shorter. `F` is the fan-out (default 16, `log₂F = 4`); tier `d` has exactly `F^d` coordinates.
20
+ *
21
+ * The db-p2p binding is responsible for ensuring the coord byte layout (ring width) matches FRET's
22
+ * `RING_BITS` so the routing keys produced here line up with FRET's ring on the wire.
23
+ */
24
+
25
+ import type { IRingHash, RingCoord } from "./ports.js";
26
+
27
+ /** Tier-addressing surface — derives the ring coordinate for tier `d` of a topic. */
28
+ export interface TierAddressing {
29
+ /** Fan-out per tier (default 16). */
30
+ readonly F: number;
31
+ /** Tier-0 root coordinate: `H(0x00 ‖ topicId)`. Peer-independent. */
32
+ coord0(topicId: Uint8Array): RingCoord;
33
+ /** Tier-`d` coordinate for `d ≥ 1`: `H(d ‖ prefix(H(P), d·log₂F) ‖ topicId)` where `H(P)` is the ring-hash of `peerId`. */
34
+ coordD(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord;
35
+ /** Dispatches `d === 0` to {@link coord0}, otherwise to {@link coordD}. */
36
+ coord(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord;
37
+ }
38
+
39
+ /** Default fan-out per tier (`log₂16 = 4`). */
40
+ export const DEFAULT_FANOUT = 16;
41
+
42
+ /**
43
+ * `prefix(P, n)` — the `n` most-significant bits of `P`, packed MSB-first into `⌈n/8⌉` bytes with
44
+ * the trailing partial byte's unused low bits zeroed. If `P` carries fewer than `n` bits it is
45
+ * **left-padded**: the high `(n − bits(P))` positions are zero and `P`'s bits occupy the low end, so
46
+ * the result is always exactly `n` bits wide. `n === 0` yields an empty array (the tier-0 case).
47
+ *
48
+ * Exported for tests; not part of the public substrate surface.
49
+ */
50
+ export function prefixBits(peerId: Uint8Array, n: number): Uint8Array {
51
+ if (!Number.isInteger(n) || n < 0) {
52
+ throw new RangeError(`prefix bit count must be a non-negative integer, got ${n}`);
53
+ }
54
+ const outBytes = Math.ceil(n / 8);
55
+ const out = new Uint8Array(outBytes);
56
+ const available = peerId.length * 8;
57
+ const pad = Math.max(0, n - available);
58
+ for (let j = 0; j < n; j++) {
59
+ // Result bit j (MSB-first). High `pad` positions are zero (left-pad); the rest map to P.
60
+ if (j < pad) continue;
61
+ const srcBit = j - pad;
62
+ const srcByte = peerId[srcBit >>> 3]!;
63
+ const bit = (srcByte >>> (7 - (srcBit & 7))) & 1;
64
+ if (bit) out[j >>> 3]! |= 1 << (7 - (j & 7));
65
+ }
66
+ return out;
67
+ }
68
+
69
+ /** Implements {@link TierAddressing} over an injected {@link IRingHash}. */
70
+ export class HashTierAddressing implements TierAddressing {
71
+ public readonly F: number;
72
+ private readonly log2F: number;
73
+
74
+ constructor(private readonly hash: IRingHash, F: number = DEFAULT_FANOUT) {
75
+ if (!Number.isInteger(F) || F < 2) {
76
+ throw new RangeError(`fan-out F must be an integer ≥ 2, got ${F}`);
77
+ }
78
+ const log2F = Math.log2(F);
79
+ if (!Number.isInteger(log2F)) {
80
+ throw new RangeError(`fan-out F must be a power of two, got ${F}`);
81
+ }
82
+ this.F = F;
83
+ this.log2F = log2F;
84
+ }
85
+
86
+ coord0(topicId: Uint8Array): RingCoord {
87
+ // H(0x00 ‖ topicId)
88
+ const input = new Uint8Array(1 + topicId.length);
89
+ input[0] = 0x00;
90
+ input.set(topicId, 1);
91
+ return this.hash.H(input);
92
+ }
93
+
94
+ coordD(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord {
95
+ if (!Number.isInteger(d) || d < 1) {
96
+ throw new RangeError(`coordD requires an integer tier d ≥ 1, got ${d}`);
97
+ }
98
+ if (d > 255) {
99
+ throw new RangeError(`tier d must fit in one byte (≤ 255), got ${d}`);
100
+ }
101
+ // H(d ‖ prefix(H(P), d·log₂F) ‖ topicId) — ring-hash P first so the shard input is uniform
102
+ // NOTE: re-hashes peerId on every coordD call; a walk over a tier ladder recomputes H(self) per
103
+ // tier. Negligible today (walk steps are network-bound); if coord becomes hot, cache H(peerId).
104
+ const prefix = prefixBits(this.hash.H(peerId), d * this.log2F);
105
+ const input = new Uint8Array(1 + prefix.length + topicId.length);
106
+ input[0] = d;
107
+ input.set(prefix, 1);
108
+ input.set(topicId, 1 + prefix.length);
109
+ return this.hash.H(input);
110
+ }
111
+
112
+ coord(d: number, peerId: Uint8Array, topicId: Uint8Array): RingCoord {
113
+ return d === 0 ? this.coord0(topicId) : this.coordD(d, peerId, topicId);
114
+ }
115
+ }
116
+
117
+ /** Convenience factory mirroring the db-p2p adapter construction style. */
118
+ export function createTierAddressing(hash: IRingHash, F: number = DEFAULT_FANOUT): TierAddressing {
119
+ return new HashTierAddressing(hash, F);
120
+ }