@serfab/cadre-core 0.12.0 → 1.0.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 (155) hide show
  1. package/README.md +23 -8
  2. package/dist/bootstrap-peer-store-file.js +2 -2
  3. package/dist/bootstrap-peer-store.d.ts +32 -12
  4. package/dist/bootstrap-peer-store.js +41 -15
  5. package/dist/bootstrap-peer-store.js.map +1 -1
  6. package/dist/cadre-node.d.ts +883 -102
  7. package/dist/cadre-node.js +1800 -209
  8. package/dist/cadre-node.js.map +1 -1
  9. package/dist/control-authorization.d.ts +2 -2
  10. package/dist/control-authorization.js +1 -0
  11. package/dist/control-authorization.js.map +1 -1
  12. package/dist/control-cohort.d.ts +13 -26
  13. package/dist/control-cohort.js +0 -26
  14. package/dist/control-cohort.js.map +1 -1
  15. package/dist/control-database.d.ts +318 -44
  16. package/dist/control-database.js +516 -74
  17. package/dist/control-database.js.map +1 -1
  18. package/dist/control-read-retry.d.ts +40 -11
  19. package/dist/control-read-retry.js +48 -15
  20. package/dist/control-read-retry.js.map +1 -1
  21. package/dist/control-retry.d.ts +58 -3
  22. package/dist/control-retry.js +58 -3
  23. package/dist/control-retry.js.map +1 -1
  24. package/dist/control-schema.d.ts +1 -1
  25. package/dist/control-schema.js +133 -21
  26. package/dist/control-schema.js.map +1 -1
  27. package/dist/control-write-retry.d.ts +80 -11
  28. package/dist/control-write-retry.js +168 -29
  29. package/dist/control-write-retry.js.map +1 -1
  30. package/dist/enrolled-machine-store-file.d.ts +13 -0
  31. package/dist/enrolled-machine-store-file.js +41 -0
  32. package/dist/enrolled-machine-store-file.js.map +1 -0
  33. package/dist/enrolled-machine-store.d.ts +81 -0
  34. package/dist/enrolled-machine-store.js +225 -0
  35. package/dist/enrolled-machine-store.js.map +1 -0
  36. package/dist/file-durable-slot.js +5 -3
  37. package/dist/file-durable-slot.js.map +1 -1
  38. package/dist/index.d.ts +17 -8
  39. package/dist/index.js +54 -9
  40. package/dist/index.js.map +1 -1
  41. package/dist/membership-connection-gater.d.ts +15 -1
  42. package/dist/membership-connection-gater.js +9 -3
  43. package/dist/membership-connection-gater.js.map +1 -1
  44. package/dist/node-local-snapshot.d.ts +21 -7
  45. package/dist/node-local-snapshot.js +30 -9
  46. package/dist/node-local-snapshot.js.map +1 -1
  47. package/dist/peer-dial.d.ts +143 -0
  48. package/dist/peer-dial.js +176 -0
  49. package/dist/peer-dial.js.map +1 -0
  50. package/dist/{strand-backfill.d.ts → peer-join-backfill.d.ts} +49 -20
  51. package/dist/{strand-backfill.js → peer-join-backfill.js} +133 -52
  52. package/dist/peer-join-backfill.js.map +1 -0
  53. package/dist/relay-addrs.d.ts +160 -49
  54. package/dist/relay-addrs.js +287 -60
  55. package/dist/relay-addrs.js.map +1 -1
  56. package/dist/relay-reservation.d.ts +71 -3
  57. package/dist/relay-reservation.js +116 -8
  58. package/dist/relay-reservation.js.map +1 -1
  59. package/dist/seed-bootstrap.d.ts +30 -1
  60. package/dist/seed-bootstrap.js +62 -17
  61. package/dist/seed-bootstrap.js.map +1 -1
  62. package/dist/storage-scope.d.ts +62 -0
  63. package/dist/storage-scope.js +75 -0
  64. package/dist/storage-scope.js.map +1 -0
  65. package/dist/strand-database.d.ts +41 -11
  66. package/dist/strand-database.js +48 -18
  67. package/dist/strand-database.js.map +1 -1
  68. package/dist/strand-first-sync-gate.d.ts +152 -0
  69. package/dist/strand-first-sync-gate.js +208 -0
  70. package/dist/strand-first-sync-gate.js.map +1 -0
  71. package/dist/strand-formation-manager.d.ts +99 -5
  72. package/dist/strand-formation-manager.js +119 -8
  73. package/dist/strand-formation-manager.js.map +1 -1
  74. package/dist/strand-formation-protocol.d.ts +97 -3
  75. package/dist/strand-formation-protocol.js +108 -2
  76. package/dist/strand-formation-protocol.js.map +1 -1
  77. package/dist/strand-instance-manager.d.ts +389 -19
  78. package/dist/strand-instance-manager.js +678 -65
  79. package/dist/strand-instance-manager.js.map +1 -1
  80. package/dist/strand-member-key.d.ts +20 -12
  81. package/dist/strand-member-key.js +20 -12
  82. package/dist/strand-member-key.js.map +1 -1
  83. package/dist/strand-member-registry.js +3 -0
  84. package/dist/strand-member-registry.js.map +1 -1
  85. package/dist/strand-membership-reconciler.d.ts +361 -0
  86. package/dist/strand-membership-reconciler.js +548 -0
  87. package/dist/strand-membership-reconciler.js.map +1 -0
  88. package/dist/strand-membership-writer.d.ts +143 -11
  89. package/dist/strand-membership-writer.js +419 -135
  90. package/dist/strand-membership-writer.js.map +1 -1
  91. package/dist/strand-network-config.d.ts +116 -0
  92. package/dist/strand-network-config.js +192 -0
  93. package/dist/strand-network-config.js.map +1 -0
  94. package/dist/strand-revocation-enforcer.d.ts +392 -0
  95. package/dist/strand-revocation-enforcer.js +533 -0
  96. package/dist/strand-revocation-enforcer.js.map +1 -0
  97. package/dist/strand-solicitation.d.ts +17 -1
  98. package/dist/strand-solicitation.js +13 -2
  99. package/dist/strand-solicitation.js.map +1 -1
  100. package/dist/strand-wake-protocol.d.ts +11 -7
  101. package/dist/strand-wake-protocol.js +20 -52
  102. package/dist/strand-wake-protocol.js.map +1 -1
  103. package/dist/strand-watcher.js +12 -1
  104. package/dist/strand-watcher.js.map +1 -1
  105. package/dist/timed-step.d.ts +12 -0
  106. package/dist/timed-step.js +28 -0
  107. package/dist/timed-step.js.map +1 -0
  108. package/dist/timeout-scheduler.d.ts +19 -0
  109. package/dist/timeout-scheduler.js +22 -0
  110. package/dist/timeout-scheduler.js.map +1 -0
  111. package/dist/types.d.ts +472 -46
  112. package/dist/types.js +7 -1
  113. package/dist/types.js.map +1 -1
  114. package/package.json +16 -8
  115. package/src/bootstrap-peer-store-file.ts +2 -2
  116. package/src/bootstrap-peer-store.ts +69 -27
  117. package/src/cadre-node.ts +1926 -228
  118. package/src/control-authorization.ts +2 -1
  119. package/src/control-cohort.ts +122 -130
  120. package/src/control-database.ts +586 -74
  121. package/src/control-read-retry.ts +202 -167
  122. package/src/control-retry.ts +297 -190
  123. package/src/control-schema.ts +133 -21
  124. package/src/control-write-retry.ts +525 -383
  125. package/src/enrolled-machine-store-file.ts +43 -0
  126. package/src/enrolled-machine-store.ts +280 -0
  127. package/src/file-durable-slot.ts +5 -3
  128. package/src/index.ts +134 -15
  129. package/src/membership-connection-gater.ts +9 -3
  130. package/src/node-local-snapshot.ts +38 -9
  131. package/src/peer-addr-book.ts +251 -250
  132. package/src/peer-dial.ts +215 -0
  133. package/src/{strand-backfill.ts → peer-join-backfill.ts} +478 -389
  134. package/src/relay-addrs.ts +458 -218
  135. package/src/relay-reservation.ts +951 -828
  136. package/src/seed-bootstrap.ts +76 -18
  137. package/src/storage-scope.ts +78 -0
  138. package/src/strand-database.ts +61 -21
  139. package/src/strand-first-sync-gate.ts +258 -0
  140. package/src/strand-formation-manager.ts +162 -8
  141. package/src/strand-formation-protocol.ts +850 -692
  142. package/src/strand-instance-manager.ts +877 -74
  143. package/src/strand-member-key.ts +20 -12
  144. package/src/strand-member-registry.ts +3 -0
  145. package/src/strand-membership-reconciler.ts +680 -0
  146. package/src/strand-membership-writer.ts +550 -190
  147. package/src/strand-network-config.ts +250 -0
  148. package/src/strand-revocation-enforcer.ts +653 -0
  149. package/src/strand-solicitation.ts +499 -471
  150. package/src/strand-wake-protocol.ts +21 -66
  151. package/src/strand-watcher.ts +325 -314
  152. package/src/timed-step.ts +28 -0
  153. package/src/timeout-scheduler.ts +28 -0
  154. package/src/types.ts +487 -52
  155. package/dist/strand-backfill.js.map +0 -1
@@ -6,7 +6,13 @@
6
6
  * - the trusted-owner anchor (`trusted-owner-store.ts`),
7
7
  * - the cold-start bootstrap-peer store (`bootstrap-peer-store.ts`).
8
8
  *
9
- * Both are the same mechanism with a different payload: a
9
+ * A THIRD node-local record — the enrolled-machine count
10
+ * (`enrolled-machine-store.ts`) — reuses {@link DurableSlot} but deliberately
11
+ * NOT this module's snapshot machinery: it is one scalar rather than an entry
12
+ * map, and it must cold-start rather than throw on an unreadable slot. Its
13
+ * module comment carries the reasoning; do not fold it in here.
14
+ *
15
+ * Both records here are the same mechanism with a different payload: a
10
16
  * `{ version, partyId, <entries> }` envelope, snapshot-written whole on every
11
17
  * change, loaded under one fail-safe-but-not-fail-silent policy. The only part
12
18
  * that differs per platform is WHERE the bytes are kept — Node writes a file,
@@ -41,9 +47,9 @@ export class NodeLocalSnapshot {
41
47
  this.spec = spec;
42
48
  this.entries = entries;
43
49
  /**
44
- * Serialises persists: every {@link put} snapshot-writes the full entry set,
45
- * so chaining writes keeps them ordered and the last landed snapshot
46
- * complete.
50
+ * Serialises persists: every write ({@link put}, {@link remove}) snapshot-writes
51
+ * the full entry set, so chaining writes keeps them ordered and the last landed
52
+ * snapshot complete.
47
53
  */
48
54
  this.writeChain = Promise.resolve();
49
55
  }
@@ -69,14 +75,14 @@ export class NodeLocalSnapshot {
69
75
  has(key) {
70
76
  return this.entries.has(key);
71
77
  }
72
- /** Fresh copy of the keys — a snapshot decoupled from later {@link put} calls. */
78
+ /** Fresh copy of the keys — a snapshot decoupled from later {@link put} / {@link remove} calls. */
73
79
  keySnapshot() {
74
80
  return new Set(this.entries.keys());
75
81
  }
76
82
  /**
77
83
  * Fresh copy of the `key -> entry` map — a snapshot decoupled from later
78
- * {@link put} calls. The entries themselves need no copy: {@link put}
79
- * REPLACES an entry rather than mutating it in place.
84
+ * {@link put} / {@link remove} calls. The entries themselves need no copy:
85
+ * {@link put} REPLACES an entry rather than mutating it in place.
80
86
  */
81
87
  entrySnapshot() {
82
88
  return new Map(this.entries);
@@ -86,12 +92,27 @@ export class NodeLocalSnapshot {
86
92
  * synchronous caller may consult the store the moment this returns), then
87
93
  * the full snapshot is persisted. A persist failure rejects the returned
88
94
  * promise but leaves the entry in memory — this session's decision stands,
89
- * and any later successful {@link put} re-lands the complete set.
95
+ * and any later successful write re-lands the complete set.
90
96
  */
91
97
  put(key, entry) {
92
98
  this.entries.set(key, entry);
99
+ return this.queuePersist();
100
+ }
101
+ /**
102
+ * Remove an entry, with {@link put}'s contract: the in-memory map updates
103
+ * SYNCHRONOUSLY, then the full snapshot is persisted. Removing an absent key
104
+ * changes nothing, so it writes nothing.
105
+ */
106
+ remove(key) {
107
+ if (!this.entries.delete(key)) {
108
+ return Promise.resolve();
109
+ }
110
+ return this.queuePersist();
111
+ }
112
+ /** Chain a full-snapshot write behind every earlier one (see {@link put}). */
113
+ queuePersist() {
93
114
  const persist = this.writeChain.then(() => this.persistSnapshot());
94
- // The chain itself must survive a failed persist (the next put retries the
115
+ // The chain itself must survive a failed persist (the next write retries the
95
116
  // full snapshot); the caller still observes the rejection via `persist`.
96
117
  this.writeChain = persist.catch((error) => {
97
118
  log('%s: persist failed for party %s: %o', this.spec.label, this.partyId, error);
@@ -1 +1 @@
1
- {"version":3,"file":"node-local-snapshot.js","sourceRoot":"","sources":["../src/node-local-snapshot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-local-snapshot.js","sourceRoot":"","sources":["../src/node-local-snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Dialing one peer from a list of candidate addresses, so that an address that
3
+ * never answers cannot use up the time the others needed.
4
+ *
5
+ * libp2p's own multi-address dial (`libp2p.dial(addrs)`, libp2p 3.1.3's
6
+ * `DialQueue.dialPeer`) tries the addresses ONE AT A TIME under ONE signal, with
7
+ * no time limit per address. An address whose connection attempt is silently
8
+ * dropped — a firewall, a subnet the dialer cannot route to, a virtual adapter's
9
+ * address a Windows host reports alongside its real ones — holds that dial until
10
+ * the whole signal expires, and every address sorted after it is never tried. It
11
+ * also sorts the list itself (`defaultAddressSorter`), putting loopback addresses
12
+ * last, behind exactly the LAN addresses most likely to be dropped. A phone
13
+ * borrowing a node from a cadre-host whose only reachable address was the
14
+ * forwarded loopback one never connected for this reason.
15
+ *
16
+ * So the addresses are dialed here, each as its own `dial()` with its own time
17
+ * limit ({@link PeerDialBudget.perAddressMs}), inside a limit for the whole peer
18
+ * ({@link PeerDialBudget.totalMs}). One address per call also means libp2p's
19
+ * sorter has nothing to reorder, so the order is chosen here
20
+ * ({@link directBeforeRelayed}).
21
+ *
22
+ * Dependency-free apart from `control-stream.ts`, so any module may import it
23
+ * without creating an import cycle.
24
+ */
25
+ import type { Connection } from '@libp2p/interface';
26
+ import type { Multiaddr } from '@multiformats/multiaddr';
27
+ /**
28
+ * Default limit on ONE address's dial attempt, in ms.
29
+ *
30
+ * It has to cover the slowest address that should succeed, because an attempt
31
+ * that needs longer fails the same way on every retry. That is a relayed dial on
32
+ * a mobile link: connect to the relay (transport, encryption and multiplexer
33
+ * handshakes, when no relay connection is open yet), open the circuit, then run
34
+ * both handshakes again end to end through the relay at roughly twice the
35
+ * round-trip time. At a few hundred ms per round trip that is several seconds.
36
+ * A direct dial that works is far quicker — a phone's WebSocket dial to a node
37
+ * forwarded over `adb reverse` measured 1.6 s end to end. 8 s covers the relayed
38
+ * case with some margin while still abandoning a silently dropped address
39
+ * sooner than libp2p's own 10 s dial timeout would.
40
+ *
41
+ * NOTE: a `/p2p-circuit/webrtc` address also negotiates a WebRTC session after
42
+ * the relay leg. If such dials are ever seen failing at exactly this limit, raise
43
+ * it (or `network.controlCohort.perAddressDialTimeoutMs` on the affected nodes)
44
+ * rather than the per-peer limit, which cannot help an address that is itself
45
+ * cut off.
46
+ *
47
+ * Override per node with `network.controlCohort.perAddressDialTimeoutMs`.
48
+ */
49
+ export declare const DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS = 8000;
50
+ /**
51
+ * Default limit on dialing ONE peer — every candidate address together — in ms.
52
+ *
53
+ * Without it, an offline peer costs (address count × up to
54
+ * {@link DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS}), a number that
55
+ * depends on how many addresses the peer happens to report. Callers dial peers
56
+ * one after another (the control-cohort reconcile pass dials each sibling in
57
+ * turn), so that cost also delays every later peer. With the limit, a reconcile
58
+ * pass costs at most (dialed siblings) × this.
59
+ *
60
+ * Sized to fit a typical list with dead addresses ahead of the one that works:
61
+ * a lent cadre-host node reports two to four addresses a phone can dial
62
+ * (loopback and LAN WebSocket addresses, plus any virtual adapters). Three
63
+ * silently dropped addresses cost 3 × 8 s = 24 s, leaving 6 s for the working
64
+ * address after them — well over the 1.6 s the phone measured. A peer whose
65
+ * working address sits behind four or more dropped ones does not fit, and fails
66
+ * each pass the same way; if that shows up, the remedy is ordering (try the
67
+ * address that last connected first), not a larger number here.
68
+ *
69
+ * Override per node with `network.controlCohort.dialTimeoutMs` — tests that
70
+ * drive dead addresses on purpose set it low so a pass's duration is a chosen
71
+ * number rather than a transitive libp2p default stretched by machine load.
72
+ */
73
+ export declare const DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS = 30000;
74
+ /** Time limits for dialing one peer from a list of candidate addresses. */
75
+ export interface PeerDialBudget {
76
+ /** Limit on one address's attempt, in ms. */
77
+ perAddressMs: number;
78
+ /** Limit on the whole peer — every address together — in ms. */
79
+ totalMs: number;
80
+ }
81
+ /** {@link PeerDialBudget} built from the two defaults above. */
82
+ export declare const DEFAULT_PEER_DIAL_BUDGET: Readonly<PeerDialBudget>;
83
+ /** The slice of a libp2p node {@link dialPeerAddrs} uses. */
84
+ export interface AddrDialer {
85
+ dial(addr: Multiaddr, options: {
86
+ signal: AbortSignal;
87
+ }): Promise<Connection>;
88
+ }
89
+ /**
90
+ * Dial one peer from `addrs`, one address per `dial()` call, and return the
91
+ * first connection that forms. Direct addresses are tried before relayed ones
92
+ * ({@link directBeforeRelayed}); otherwise the given order is kept.
93
+ *
94
+ * Addresses naming a transport this node lacks are not filtered out first: a
95
+ * one-address `dial()` rejects those at once (`NoValidAddressesError`), so they
96
+ * cost a log line, not time.
97
+ *
98
+ * Throws when no address connects — see {@link tryAddrsInTurn} for the error.
99
+ *
100
+ * @param label Names the dial in timeout messages and logs, e.g.
101
+ * `reconcileControlCohort dial of sibling <peerId>`; each attempt reads
102
+ * `<label> via <addr>`.
103
+ */
104
+ export declare function dialPeerAddrs(dialer: AddrDialer, addrs: readonly Multiaddr[], budget: PeerDialBudget, label: string): Promise<Connection>;
105
+ /**
106
+ * Run `attempt` against each address in the given order until one succeeds, and
107
+ * return its result.
108
+ *
109
+ * Each attempt is limited to `budget.perAddressMs`, or to whatever remains of
110
+ * `budget.totalMs` when that is less. The attempt's signal is aborted when its
111
+ * limit passes, so a dial in progress is cancelled rather than left running.
112
+ * Addresses the total leaves no time for are reported as not tried.
113
+ *
114
+ * Throws when every address fails. With one address the error is that attempt's
115
+ * own, unchanged. With several it is one error naming each address and why it
116
+ * failed, in order, with the first failure as `cause` — so the report shows the
117
+ * address that mattered, not only whichever happened to be tried last.
118
+ *
119
+ * Between attempts this waits for one macrotask (`setTimeout(0)`). libp2p's dial
120
+ * queue lets a new `dial()` join any queued dial job for the same peer id
121
+ * WITHOUT checking whether that job has already finished, and an aborted job
122
+ * leaves the queue a few microtasks after its callers are rejected. Timing out
123
+ * one address and immediately dialing the next for the same peer therefore
124
+ * joins the dying job, which rejects the new dial with `AbortError` before it
125
+ * touches the network — the next address is never tried. Seen against libp2p
126
+ * 3.1.3; `test/peer-dial.spec.ts` dials real nodes so an upgrade that changes it
127
+ * shows up there. All of the queue's cleanup runs as microtasks, so it has
128
+ * finished by the time a macrotask runs.
129
+ */
130
+ export declare function tryAddrsInTurn<T>(addrs: readonly Multiaddr[], budget: PeerDialBudget, label: string, attempt: (addr: Multiaddr, signal: AbortSignal, attemptMs: number) => Promise<T>): Promise<T>;
131
+ /**
132
+ * `addrs` with every circuit-relay address (one containing `/p2p-circuit`) moved
133
+ * after every direct one, each group keeping its given order.
134
+ *
135
+ * The one part of libp2p's own ordering worth keeping: a relayed connection is
136
+ * slower and limited in how much data and time it may carry, so a direct
137
+ * address is always worth trying first. libp2p's other rules are deliberately
138
+ * dropped — it puts loopback addresses last and public ones before private ones,
139
+ * which is how a forwarded loopback address ended up behind addresses that
140
+ * never answer. With a per-address limit, trying a dead address first costs a
141
+ * bounded amount of time, so the caller's order is the better guide.
142
+ */
143
+ export declare function directBeforeRelayed(addrs: readonly Multiaddr[]): Multiaddr[];
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Dialing one peer from a list of candidate addresses, so that an address that
3
+ * never answers cannot use up the time the others needed.
4
+ *
5
+ * libp2p's own multi-address dial (`libp2p.dial(addrs)`, libp2p 3.1.3's
6
+ * `DialQueue.dialPeer`) tries the addresses ONE AT A TIME under ONE signal, with
7
+ * no time limit per address. An address whose connection attempt is silently
8
+ * dropped — a firewall, a subnet the dialer cannot route to, a virtual adapter's
9
+ * address a Windows host reports alongside its real ones — holds that dial until
10
+ * the whole signal expires, and every address sorted after it is never tried. It
11
+ * also sorts the list itself (`defaultAddressSorter`), putting loopback addresses
12
+ * last, behind exactly the LAN addresses most likely to be dropped. A phone
13
+ * borrowing a node from a cadre-host whose only reachable address was the
14
+ * forwarded loopback one never connected for this reason.
15
+ *
16
+ * So the addresses are dialed here, each as its own `dial()` with its own time
17
+ * limit ({@link PeerDialBudget.perAddressMs}), inside a limit for the whole peer
18
+ * ({@link PeerDialBudget.totalMs}). One address per call also means libp2p's
19
+ * sorter has nothing to reorder, so the order is chosen here
20
+ * ({@link directBeforeRelayed}).
21
+ *
22
+ * Dependency-free apart from `control-stream.ts`, so any module may import it
23
+ * without creating an import cycle.
24
+ */
25
+ import debug from 'debug';
26
+ import { withDeadline } from './control-stream.js';
27
+ const log = debug('sereus:cadre:peer-dial');
28
+ /**
29
+ * Default limit on ONE address's dial attempt, in ms.
30
+ *
31
+ * It has to cover the slowest address that should succeed, because an attempt
32
+ * that needs longer fails the same way on every retry. That is a relayed dial on
33
+ * a mobile link: connect to the relay (transport, encryption and multiplexer
34
+ * handshakes, when no relay connection is open yet), open the circuit, then run
35
+ * both handshakes again end to end through the relay at roughly twice the
36
+ * round-trip time. At a few hundred ms per round trip that is several seconds.
37
+ * A direct dial that works is far quicker — a phone's WebSocket dial to a node
38
+ * forwarded over `adb reverse` measured 1.6 s end to end. 8 s covers the relayed
39
+ * case with some margin while still abandoning a silently dropped address
40
+ * sooner than libp2p's own 10 s dial timeout would.
41
+ *
42
+ * NOTE: a `/p2p-circuit/webrtc` address also negotiates a WebRTC session after
43
+ * the relay leg. If such dials are ever seen failing at exactly this limit, raise
44
+ * it (or `network.controlCohort.perAddressDialTimeoutMs` on the affected nodes)
45
+ * rather than the per-peer limit, which cannot help an address that is itself
46
+ * cut off.
47
+ *
48
+ * Override per node with `network.controlCohort.perAddressDialTimeoutMs`.
49
+ */
50
+ export const DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS = 8000;
51
+ /**
52
+ * Default limit on dialing ONE peer — every candidate address together — in ms.
53
+ *
54
+ * Without it, an offline peer costs (address count × up to
55
+ * {@link DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS}), a number that
56
+ * depends on how many addresses the peer happens to report. Callers dial peers
57
+ * one after another (the control-cohort reconcile pass dials each sibling in
58
+ * turn), so that cost also delays every later peer. With the limit, a reconcile
59
+ * pass costs at most (dialed siblings) × this.
60
+ *
61
+ * Sized to fit a typical list with dead addresses ahead of the one that works:
62
+ * a lent cadre-host node reports two to four addresses a phone can dial
63
+ * (loopback and LAN WebSocket addresses, plus any virtual adapters). Three
64
+ * silently dropped addresses cost 3 × 8 s = 24 s, leaving 6 s for the working
65
+ * address after them — well over the 1.6 s the phone measured. A peer whose
66
+ * working address sits behind four or more dropped ones does not fit, and fails
67
+ * each pass the same way; if that shows up, the remedy is ordering (try the
68
+ * address that last connected first), not a larger number here.
69
+ *
70
+ * Override per node with `network.controlCohort.dialTimeoutMs` — tests that
71
+ * drive dead addresses on purpose set it low so a pass's duration is a chosen
72
+ * number rather than a transitive libp2p default stretched by machine load.
73
+ */
74
+ export const DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS = 30000;
75
+ /** {@link PeerDialBudget} built from the two defaults above. */
76
+ export const DEFAULT_PEER_DIAL_BUDGET = {
77
+ perAddressMs: DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS,
78
+ totalMs: DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS,
79
+ };
80
+ /**
81
+ * Dial one peer from `addrs`, one address per `dial()` call, and return the
82
+ * first connection that forms. Direct addresses are tried before relayed ones
83
+ * ({@link directBeforeRelayed}); otherwise the given order is kept.
84
+ *
85
+ * Addresses naming a transport this node lacks are not filtered out first: a
86
+ * one-address `dial()` rejects those at once (`NoValidAddressesError`), so they
87
+ * cost a log line, not time.
88
+ *
89
+ * Throws when no address connects — see {@link tryAddrsInTurn} for the error.
90
+ *
91
+ * @param label Names the dial in timeout messages and logs, e.g.
92
+ * `reconcileControlCohort dial of sibling <peerId>`; each attempt reads
93
+ * `<label> via <addr>`.
94
+ */
95
+ export function dialPeerAddrs(dialer, addrs, budget, label) {
96
+ return tryAddrsInTurn(directBeforeRelayed(addrs), budget, label, (addr, signal) => dialer.dial(addr, { signal }));
97
+ }
98
+ /**
99
+ * Run `attempt` against each address in the given order until one succeeds, and
100
+ * return its result.
101
+ *
102
+ * Each attempt is limited to `budget.perAddressMs`, or to whatever remains of
103
+ * `budget.totalMs` when that is less. The attempt's signal is aborted when its
104
+ * limit passes, so a dial in progress is cancelled rather than left running.
105
+ * Addresses the total leaves no time for are reported as not tried.
106
+ *
107
+ * Throws when every address fails. With one address the error is that attempt's
108
+ * own, unchanged. With several it is one error naming each address and why it
109
+ * failed, in order, with the first failure as `cause` — so the report shows the
110
+ * address that mattered, not only whichever happened to be tried last.
111
+ *
112
+ * Between attempts this waits for one macrotask (`setTimeout(0)`). libp2p's dial
113
+ * queue lets a new `dial()` join any queued dial job for the same peer id
114
+ * WITHOUT checking whether that job has already finished, and an aborted job
115
+ * leaves the queue a few microtasks after its callers are rejected. Timing out
116
+ * one address and immediately dialing the next for the same peer therefore
117
+ * joins the dying job, which rejects the new dial with `AbortError` before it
118
+ * touches the network — the next address is never tried. Seen against libp2p
119
+ * 3.1.3; `test/peer-dial.spec.ts` dials real nodes so an upgrade that changes it
120
+ * shows up there. All of the queue's cleanup runs as microtasks, so it has
121
+ * finished by the time a macrotask runs.
122
+ */
123
+ export async function tryAddrsInTurn(addrs, budget, label, attempt) {
124
+ const deadline = Date.now() + budget.totalMs;
125
+ const failures = [];
126
+ for (const addr of addrs) {
127
+ const remaining = deadline - Date.now();
128
+ if (remaining <= 0) {
129
+ failures.push({ addr, error: new Error(`not tried — the ${budget.totalMs}ms budget for ${label} was spent on earlier addresses`) });
130
+ continue;
131
+ }
132
+ if (failures.length > 0) {
133
+ await nextMacrotask();
134
+ }
135
+ const attemptMs = Math.min(budget.perAddressMs, remaining);
136
+ try {
137
+ return await withDeadline(attemptMs, `${label} via ${addr.toString()}`, (signal) => attempt(addr, signal, attemptMs));
138
+ }
139
+ catch (err) {
140
+ const error = err instanceof Error ? err : new Error(String(err));
141
+ failures.push({ addr, error });
142
+ log('%s via %s failed: %o', label, addr.toString(), error);
143
+ }
144
+ }
145
+ throw allAttemptsFailed(label, failures);
146
+ }
147
+ /**
148
+ * `addrs` with every circuit-relay address (one containing `/p2p-circuit`) moved
149
+ * after every direct one, each group keeping its given order.
150
+ *
151
+ * The one part of libp2p's own ordering worth keeping: a relayed connection is
152
+ * slower and limited in how much data and time it may carry, so a direct
153
+ * address is always worth trying first. libp2p's other rules are deliberately
154
+ * dropped — it puts loopback addresses last and public ones before private ones,
155
+ * which is how a forwarded loopback address ended up behind addresses that
156
+ * never answer. With a per-address limit, trying a dead address first costs a
157
+ * bounded amount of time, so the caller's order is the better guide.
158
+ */
159
+ export function directBeforeRelayed(addrs) {
160
+ const relayed = (addr) => addr.getComponents().some((c) => c.name === 'p2p-circuit');
161
+ return [...addrs.filter((addr) => !relayed(addr)), ...addrs.filter(relayed)];
162
+ }
163
+ function allAttemptsFailed(label, failures) {
164
+ if (failures.length === 0) {
165
+ return new Error(`${label}: no candidate addresses`);
166
+ }
167
+ if (failures.length === 1) {
168
+ return failures[0].error;
169
+ }
170
+ const detail = failures.map((f) => `${f.addr.toString()} — ${f.error.message}`).join('; ');
171
+ return new Error(`${label} failed for all ${failures.length} candidate addresses: ${detail}`, { cause: failures[0].error });
172
+ }
173
+ function nextMacrotask() {
174
+ return new Promise((resolve) => setTimeout(resolve, 0));
175
+ }
176
+ //# sourceMappingURL=peer-dial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-dial.js","sourceRoot":"","sources":["../src/peer-dial.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,8 @@ import { BlockTransferClient, type IRawStorage } from '@optimystic/db-p2p';
8
8
  * changes it or starts exporting it.
9
9
  */
10
10
  export declare const MAX_BLOCK_MESSAGE_BYTES: number;
11
- /** Tuning for the per-peer strand catch-up. Every field optional; defaults in {@link DEFAULT_STRAND_BACKFILL}. */
12
- export interface StrandBackfillConfig {
11
+ /** Tuning for the per-peer catch-up. Every field optional; defaults in {@link DEFAULT_PEER_JOIN_BACKFILL}. */
12
+ export interface PeerJoinBackfillConfig {
13
13
  /** Default true. False disables the catch-up entirely (the pre-existing behaviour). */
14
14
  enabled?: boolean;
15
15
  /** Settle time after a connection opens before catching that peer up, ms. Default 1000. */
@@ -25,32 +25,39 @@ export interface StrandBackfillConfig {
25
25
  /** Per-push response deadline, ms. Default 10_000 (matches SpreadOnChurnMonitor). */
26
26
  responseTimeoutMs?: number;
27
27
  }
28
- /** The resolved defaults every {@link StrandBackfill} starts from. */
29
- export declare const DEFAULT_STRAND_BACKFILL: Required<StrandBackfillConfig>;
28
+ /** The resolved defaults every {@link PeerJoinBackfill} starts from. */
29
+ export declare const DEFAULT_PEER_JOIN_BACKFILL: Required<PeerJoinBackfillConfig>;
30
30
  /**
31
31
  * The one capability the catch-up needs from a transfer client. Structural (rather than
32
32
  * the concrete `BlockTransferClient`) so unit tests can capture pushes without dialing
33
- * libp2p — see {@link StrandBackfillDeps.createPushClient}.
33
+ * libp2p — see {@link PeerJoinBackfillDeps.createPushClient}.
34
34
  */
35
- export type StrandBackfillPushClient = Pick<BlockTransferClient, 'pushBlocks'>;
36
- export interface StrandBackfillDeps {
37
- strandId: string;
38
- /** The STRAND's libp2p node (not the control node) — source of connection events and peer ids. */
35
+ export type PeerJoinBackfillPushClient = Pick<BlockTransferClient, 'pushBlocks'>;
36
+ export interface PeerJoinBackfillDeps {
37
+ /** Log tag naming which network this catch-up serves (a strand id, or `control-<partyId>`). */
38
+ label: string;
39
+ /** The libp2p node of the network being caught up — source of connection events and peer ids. */
39
40
  libp2p: Libp2p;
40
41
  /** `node.keyNetwork`, the IPeerNetwork BlockTransferClient dials through. */
41
42
  peerNetwork: IPeerNetwork;
42
- /** This strand's own raw block store — the same instance handed to the libp2p node. */
43
+ /** This network's own raw block store — the same instance handed to the libp2p node. */
43
44
  storage: IRawStorage;
44
- /** Must equal the prefix the receiver registered its handler under: `/optimystic/strand-<strandId>`. */
45
+ /** Must equal the prefix the receiver registered its handler under: `/optimystic/<networkName>`. */
45
46
  protocolPrefix: string;
47
+ /**
48
+ * Membership gate, consulted at PUSH time (fails closed on a throw). Absent = push to
49
+ * every connected peer — correct for strand networks, NEVER for the control network;
50
+ * see the module comment for why the strand no-gate argument does not carry over.
51
+ */
52
+ authorizePeer?: (peerId: string) => Promise<boolean>;
46
53
  /**
47
54
  * Test seam: build the per-peer push client. Defaults to a real
48
55
  * `BlockTransferClient` over {@link peerNetwork} + {@link protocolPrefix}.
49
56
  */
50
- createPushClient?: (peerId: PeerId) => StrandBackfillPushClient;
57
+ createPushClient?: (peerId: PeerId) => PeerJoinBackfillPushClient;
51
58
  }
52
59
  /** What one peer's catch-up actually did. Returned for tests and logged at the end of each run. */
53
- export interface StrandBackfillResult {
60
+ export interface PeerJoinBackfillResult {
54
61
  /** Blocks offered: had a committed `latest` AND materialized content locally. */
55
62
  offered: number;
56
63
  /** Blocks the remote reported it persisted. */
@@ -65,19 +72,21 @@ export interface StrandBackfillResult {
65
72
  capped: number;
66
73
  /** Skipped: a single block whose wire size alone exceeds {@link MAX_BLOCK_MESSAGE_BYTES}. */
67
74
  oversized: string[];
75
+ /** True when the `authorizePeer` gate refused (or threw) — nothing was pushed, peer not memoized. */
76
+ denied: boolean;
68
77
  }
69
78
  /**
70
- * Copies every block in one strand's own raw store to each peer its libp2p node connects
71
- * to, so a peer that joined the strand after blocks were committed still ends up holding
79
+ * Copies every block in one network's own raw store to each peer its libp2p node connects
80
+ * to, so a peer that joined the network after blocks were committed still ends up holding
72
81
  * them physically (the receiver persists each push via `saveReplicatedBlock`, which is
73
82
  * monotonic and idempotent — crossing pushes from both ends cannot regress a revision).
74
83
  *
75
- * Best-effort throughout: nothing here throws into a libp2p event handler or into
76
- * `buildStrandRuntime`; a failed chunk is logged and the run continues. A peer is marked
84
+ * Best-effort throughout: nothing here throws into a libp2p event handler or into a
85
+ * runtime bring-up; a failed chunk is logged and the run continues. A peer is marked
77
86
  * fully caught up ONLY after a run with no thrown chunk and an empty `missing` list, so
78
87
  * the next `connection:open` from a peer whose catch-up failed retries it.
79
88
  */
80
- export declare class StrandBackfill {
89
+ export declare class PeerJoinBackfill {
81
90
  private readonly deps;
82
91
  private readonly config;
83
92
  private readonly createPushClient;
@@ -87,15 +96,35 @@ export declare class StrandBackfill {
87
96
  private readonly done;
88
97
  /** Peers with a catch-up currently running (suppresses concurrent duplicates). */
89
98
  private readonly inFlight;
99
+ /**
100
+ * Peers whose (re-)schedule arrived while their OWN run was in flight, replayed once
101
+ * that run finishes. Load-bearing for the gated path: the gate's authorization check is
102
+ * a control-database read, so the window between it and the run's end is wide enough for
103
+ * the very membership commit that would have authorized the peer to land inside it —
104
+ * dropping that schedule (rather than deferring it) leaves the denied peer waiting for a
105
+ * reconnect, which is exactly what the re-arm exists to avoid.
106
+ */
107
+ private readonly rearmAfterFlight;
90
108
  /** Pending per-peer debounce timers, cleared on stop. */
91
109
  private readonly timers;
92
110
  private readonly onConnectionOpen;
93
111
  private loggedNoListBlockIds;
94
- constructor(deps: StrandBackfillDeps, config?: StrandBackfillConfig);
112
+ constructor(deps: PeerJoinBackfillDeps, config?: PeerJoinBackfillConfig);
95
113
  /** Subscribe to connection:open AND schedule a catch-up for peers already connected. */
96
114
  start(): void;
97
115
  /** Unsubscribe, clear timers; in-flight runs observe the stopped flag and bail. */
98
116
  stop(): void;
117
+ /**
118
+ * (Re-)schedule a debounced catch-up for every currently-connected peer not yet caught
119
+ * up. Idempotent and cheap (caught-up peers are skipped before any timer is set, and a
120
+ * peer whose run is in flight is deferred to the end of that run rather than dropped).
121
+ * Driven by {@link start}, and — on a gated network — by the embedder whenever
122
+ * membership changes, so a peer whose first pass was denied (connected before it was
123
+ * authorized: the production join order) is retried without waiting for a reconnect.
124
+ * Returns how many distinct peers were considered (for the start() log) — a peer holding
125
+ * several connections is one peer, and is scheduled once.
126
+ */
127
+ scheduleConnectedPeers(): number;
99
128
  /** Debounced entry point for connection churn: one run per peer per settle window. */
100
129
  private schedulePeer;
101
130
  /**
@@ -103,7 +132,7 @@ export declare class StrandBackfill {
103
132
  * all-zero result without pushing when the peer is already caught up, already being
104
133
  * caught up, or this backfill is stopped.
105
134
  */
106
- catchUpPeer(peerId: PeerId): Promise<StrandBackfillResult>;
135
+ catchUpPeer(peerId: PeerId): Promise<PeerJoinBackfillResult>;
107
136
  /** The actual copy. `clean` = every chunk pushed and the remote persisted every block. */
108
137
  private runCatchUp;
109
138
  }