@optimystic/db-p2p 0.24.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/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 +25 -11
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +31 -19
- package/dist/src/cohort-topic/stream-util.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/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/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/package.json +3 -3
- package/src/cohort-topic/host.ts +2932 -2901
- package/src/cohort-topic/stream-util.ts +147 -135
- package/src/matchmaking/query-transport.ts +492 -492
- package/src/reactivity/notify-transport.ts +6 -6
- package/src/reactivity/push-state-gossip.ts +2 -2
- package/src/reactivity/recover-transport.ts +412 -408
- package/src/testing/cohort-topic-mesh-harness.ts +20 -10
|
@@ -1,135 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single-frame libp2p stream helpers, shared by cohort-topic, matchmaking, and reactivity.
|
|
3
|
-
*
|
|
4
|
-
* These protocols exchange a single
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* NOTE:
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* `
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Single-frame libp2p stream helpers, shared by cohort-topic, matchmaking, and reactivity.
|
|
3
|
+
*
|
|
4
|
+
* These protocols exchange a single varint-length-prefixed frame each way, via FRET's exported
|
|
5
|
+
* `sendFramed` / `readFramed` pair — the varint prefix is what delimits the frame on the wire
|
|
6
|
+
* (the db-core codec's own internal length prefix travels *inside* the framed body and plays no
|
|
7
|
+
* part in stream delimiting). Request/response is one framed send + one framed read, matching
|
|
8
|
+
* FRET's own four RPC protocols and the `it-length-prefixed` framing the rest of `db-p2p`
|
|
9
|
+
* (`protocol-client.ts`, the cluster/repo/sync/dispute services) already uses, keeping the stream
|
|
10
|
+
* lifecycle (open → send → close-write → read → close) consistent across all protocol families.
|
|
11
|
+
*
|
|
12
|
+
* NOTE: every `sendFramed` here discards its boolean result (`false` = "write accepted, transport
|
|
13
|
+
* buffer now full"), so these helpers apply no backpressure. Harmless while each protocol writes
|
|
14
|
+
* exactly one bounded frame per stream and then closes; if a caller ever writes repeatedly on one
|
|
15
|
+
* stream, honor the flag by awaiting the stream's `'drain'` event the way FRET's `rpcRequest` does.
|
|
16
|
+
*
|
|
17
|
+
* {@link openStream} deliberately duplicates FRET's `rpc/protocols.ts#openRpcStream` connection
|
|
18
|
+
* selection. FRET now exports `openRpcStream`, but the accepted tradeoff on
|
|
19
|
+
* {@link STREAM_OPTIONS} is why the local copy stays: FRET's helper pins `negotiateFully: false`,
|
|
20
|
+
* which this module deliberately does not set — swapping would silently reverse that decision.
|
|
21
|
+
* Same applies to `libp2p-key-network.ts#connect`, which is a third copy.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { Libp2p } from "libp2p";
|
|
25
|
+
import type { Connection, PeerId, Stream } from "@libp2p/interface";
|
|
26
|
+
import { readFramed, sendFramed } from "p2p-fret";
|
|
27
|
+
|
|
28
|
+
/** Default per-frame ceiling — matches FRET's 512 KiB maybe-act bound. */
|
|
29
|
+
export const DEFAULT_STREAM_MAX_BYTES = 512 * 1024;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* libp2p refuses to open a protocol stream over a *limited* (circuit-relay) connection unless the
|
|
33
|
+
* caller opts in, so every peer reachable only through a relay — the steady state for browsers and
|
|
34
|
+
* NATed peers — depends on this flag. It is a harmless no-op on a direct connection.
|
|
35
|
+
*
|
|
36
|
+
* NOTE: accepted tradeoff — FRET's `openRpcStream` and `libp2p-key-network.ts#connect` also set
|
|
37
|
+
* `negotiateFully: false`; deliberately not set here. It saves a round trip but defers an
|
|
38
|
+
* unsupported-protocol failure from stream-open to the first read, which would turn
|
|
39
|
+
* {@link sendOneWay} against a peer lacking the protocol into a silent no-op. Revisit if
|
|
40
|
+
* stream-open latency shows up in a profile.
|
|
41
|
+
*/
|
|
42
|
+
const STREAM_OPTIONS = { runOnLimitedConnection: true } as const;
|
|
43
|
+
|
|
44
|
+
/** True for a circuit-relay ("limited") connection: libp2p stamps one with per-circuit `limits`;
|
|
45
|
+
* sniffing `/p2p-circuit` in the multiaddr covers transports that leave `limits` unpopulated. */
|
|
46
|
+
function isLimitedConnection(c: Connection): boolean {
|
|
47
|
+
if ((c as { limits?: unknown }).limits != null) return true;
|
|
48
|
+
return c.remoteAddr?.toString?.().includes("/p2p-circuit") ?? false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Open `protocol` to `peer`, reusing a healthy existing connection when there is one.
|
|
53
|
+
*
|
|
54
|
+
* Skips connections libp2p has not yet evicted from its index but that are no longer open, and
|
|
55
|
+
* prefers a direct connection over a relayed one — a relayed connection can be reset once the
|
|
56
|
+
* relay's per-circuit cap or reservation lapses, and after DCUtR upgrades a link to direct both
|
|
57
|
+
* briefly coexist. Falls back to the relayed connection when it is the only open path.
|
|
58
|
+
*/
|
|
59
|
+
async function openStream(node: Libp2p, peer: PeerId, protocol: string): Promise<Stream> {
|
|
60
|
+
const open = node.getConnections(peer).filter(c => c?.status === "open" && typeof c?.newStream === "function");
|
|
61
|
+
const chosen = open.find(c => !isLimitedConnection(c)) ?? open[0];
|
|
62
|
+
return chosen ? await chosen.newStream([protocol], STREAM_OPTIONS) : await node.dialProtocol(peer, [protocol], STREAM_OPTIONS);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Open `protocol` to `peer`, send `frame`, and read the bounded reply frame.
|
|
67
|
+
*
|
|
68
|
+
* NOTE: takes no `AbortSignal`, so a caller cannot set its own deadline. Bounded today anyway —
|
|
69
|
+
* `readFramed` self-times-out at 5s and `dialProtocol` falls back to libp2p's default dial
|
|
70
|
+
* timeout (~30s) — so an unresponsive peer is slow, not hung. If a caller ever needs a tighter
|
|
71
|
+
* deadline (`membership-source.fetch` walks candidate peers *sequentially*, so its worst case is
|
|
72
|
+
* peers × dial-timeout), thread a signal through {@link openStream} the way
|
|
73
|
+
* `libp2p-key-network.ts#connect` does.
|
|
74
|
+
*/
|
|
75
|
+
export async function requestResponse(
|
|
76
|
+
node: Libp2p,
|
|
77
|
+
peer: PeerId,
|
|
78
|
+
protocol: string,
|
|
79
|
+
frame: Uint8Array,
|
|
80
|
+
maxBytes = DEFAULT_STREAM_MAX_BYTES,
|
|
81
|
+
): Promise<Uint8Array> {
|
|
82
|
+
let stream: Stream | undefined;
|
|
83
|
+
try {
|
|
84
|
+
stream = await openStream(node, peer, protocol);
|
|
85
|
+
sendFramed(stream, frame);
|
|
86
|
+
await stream.close();
|
|
87
|
+
return await readFramed(stream, maxBytes);
|
|
88
|
+
} finally {
|
|
89
|
+
if (stream != null) {
|
|
90
|
+
try {
|
|
91
|
+
await stream.close();
|
|
92
|
+
} catch {
|
|
93
|
+
/* already closed */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Open `protocol` to `peer` and send `frame` without awaiting a reply (fire-and-forget gossip). */
|
|
100
|
+
export async function sendOneWay(node: Libp2p, peer: PeerId, protocol: string, frame: Uint8Array): Promise<void> {
|
|
101
|
+
let stream: Stream | undefined;
|
|
102
|
+
try {
|
|
103
|
+
stream = await openStream(node, peer, protocol);
|
|
104
|
+
sendFramed(stream, frame);
|
|
105
|
+
await stream.close();
|
|
106
|
+
} finally {
|
|
107
|
+
if (stream != null) {
|
|
108
|
+
try {
|
|
109
|
+
await stream.close();
|
|
110
|
+
} catch {
|
|
111
|
+
/* already closed */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Register a request/response handler for `protocol`: read one bounded frame, reply with one frame.
|
|
119
|
+
*
|
|
120
|
+
* A `handle` that returns `undefined` (a drop, a gate rejection, no serving engine) replies with an
|
|
121
|
+
* explicit **zero-length frame** rather than silence: `readFramed` treats end-of-stream as a
|
|
122
|
+
* truncation error, so "no reply" must travel in-band — the dialer's {@link requestResponse}
|
|
123
|
+
* resolves it as empty bytes, exactly what the old read-to-EOF returned for a silent close.
|
|
124
|
+
*/
|
|
125
|
+
export function handleRequestResponse(
|
|
126
|
+
node: Libp2p,
|
|
127
|
+
protocol: string,
|
|
128
|
+
handle: (frame: Uint8Array, from: PeerId) => Promise<Uint8Array | undefined>,
|
|
129
|
+
maxBytes = DEFAULT_STREAM_MAX_BYTES,
|
|
130
|
+
): void {
|
|
131
|
+
void node.handle(protocol, (stream: Stream, connection: Connection) => {
|
|
132
|
+
void (async (): Promise<void> => {
|
|
133
|
+
try {
|
|
134
|
+
const frame = await readFramed(stream, maxBytes);
|
|
135
|
+
const reply = await handle(frame, connection.remotePeer);
|
|
136
|
+
sendFramed(stream, reply ?? new Uint8Array(0));
|
|
137
|
+
await stream.close();
|
|
138
|
+
} catch {
|
|
139
|
+
try {
|
|
140
|
+
stream.abort(new Error("cohort-topic stream handler error"));
|
|
141
|
+
} catch {
|
|
142
|
+
/* already aborted */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
});
|
|
147
|
+
}
|