@optimystic/db-p2p 1.0.0-beta.2 → 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.
- package/dist/src/cluster/block-transfer-service.d.ts +73 -1
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +70 -3
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +16 -1
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +73 -72
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/client.d.ts +4 -6
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +8 -13
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-policy.d.ts +2 -1
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +172 -17
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +553 -149
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-proof.js +2 -2
- package/dist/src/cluster/race-resolution.d.ts +51 -7
- package/dist/src/cluster/race-resolution.d.ts.map +1 -1
- package/dist/src/cluster/race-resolution.js +71 -7
- package/dist/src/cluster/race-resolution.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts +67 -5
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +120 -26
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +34 -47
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/host.d.ts.map +1 -1
- package/dist/src/cohort-topic/host.js +8 -6
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/membership-source.js +1 -1
- package/dist/src/cohort-topic/membership-source.js.map +1 -1
- package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -1
- package/dist/src/cohort-topic/peer-codec.js +5 -2
- package/dist/src/cohort-topic/peer-codec.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +24 -3
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +36 -3
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/cohort-topic/topic-router.d.ts +9 -0
- package/dist/src/cohort-topic/topic-router.d.ts.map +1 -1
- package/dist/src/cohort-topic/topic-router.js +12 -2
- package/dist/src/cohort-topic/topic-router.js.map +1 -1
- package/dist/src/dispute/client.d.ts.map +1 -1
- package/dist/src/dispute/client.js +15 -3
- package/dist/src/dispute/client.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +78 -17
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +241 -147
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +35 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +151 -26
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/libp2p-node-rn.d.ts +2 -2
- package/dist/src/libp2p-node-rn.d.ts.map +1 -1
- package/dist/src/libp2p-node-rn.js.map +1 -1
- package/dist/src/libp2p-node.d.ts +2 -2
- package/dist/src/libp2p-node.d.ts.map +1 -1
- package/dist/src/libp2p-node.js.map +1 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +19 -7
- package/dist/src/logger.js.map +1 -1
- package/dist/src/matchmaking/query-transport.d.ts +3 -4
- package/dist/src/matchmaking/query-transport.d.ts.map +1 -1
- package/dist/src/matchmaking/query-transport.js +9 -9
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/network/network-manager-service.d.ts +10 -5
- package/dist/src/network/network-manager-service.d.ts.map +1 -1
- package/dist/src/network/network-manager-service.js +11 -2
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/network/relay-reservation.d.ts +170 -0
- package/dist/src/network/relay-reservation.d.ts.map +1 -0
- package/dist/src/network/relay-reservation.js +527 -0
- package/dist/src/network/relay-reservation.js.map +1 -0
- package/dist/src/pend-validation.d.ts +3 -2
- package/dist/src/pend-validation.d.ts.map +1 -1
- package/dist/src/pend-validation.js +3 -2
- package/dist/src/pend-validation.js.map +1 -1
- package/dist/src/protocol-client.d.ts +8 -1
- package/dist/src/protocol-client.d.ts.map +1 -1
- package/dist/src/protocol-client.js +11 -0
- package/dist/src/protocol-client.js.map +1 -1
- package/dist/src/reactivity/forwarder-host.d.ts +2 -1
- package/dist/src/reactivity/forwarder-host.d.ts.map +1 -1
- package/dist/src/reactivity/forwarder-host.js +2 -1
- package/dist/src/reactivity/forwarder-host.js.map +1 -1
- package/dist/src/reactivity/origination-manager.d.ts +3 -3
- package/dist/src/reactivity/origination-manager.js +3 -3
- package/dist/src/reactivity/recover-transport.d.ts +22 -14
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
- package/dist/src/reactivity/recover-transport.js +32 -19
- package/dist/src/reactivity/recover-transport.js.map +1 -1
- package/dist/src/reactivity/subscription-manager.d.ts +2 -2
- package/dist/src/reactivity/topic-bytes.d.ts +6 -7
- package/dist/src/reactivity/topic-bytes.d.ts.map +1 -1
- package/dist/src/reactivity/topic-bytes.js +8 -9
- package/dist/src/reactivity/topic-bytes.js.map +1 -1
- package/dist/src/repo/client.d.ts +5 -2
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +49 -40
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +119 -5
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +181 -29
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +259 -76
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +716 -328
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/i-under-replication-ledger.d.ts +87 -0
- package/dist/src/repo/i-under-replication-ledger.d.ts.map +1 -0
- package/dist/src/repo/i-under-replication-ledger.js +2 -0
- package/dist/src/repo/i-under-replication-ledger.js.map +1 -0
- package/dist/src/repo/kv-under-replication-ledger.d.ts +91 -0
- package/dist/src/repo/kv-under-replication-ledger.d.ts.map +1 -0
- package/dist/src/repo/kv-under-replication-ledger.js +287 -0
- package/dist/src/repo/kv-under-replication-ledger.js.map +1 -0
- package/dist/src/repo/responsibility.d.ts +37 -0
- package/dist/src/repo/responsibility.d.ts.map +1 -0
- package/dist/src/repo/responsibility.js +37 -0
- package/dist/src/repo/responsibility.js.map +1 -0
- package/dist/src/repo/service.d.ts +44 -15
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +64 -27
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/repo/stuck-reservation.d.ts +127 -0
- package/dist/src/repo/stuck-reservation.d.ts.map +1 -0
- package/dist/src/repo/stuck-reservation.js +179 -0
- package/dist/src/repo/stuck-reservation.js.map +1 -0
- package/dist/src/repo/under-replication-drain.d.ts +208 -0
- package/dist/src/repo/under-replication-drain.d.ts.map +1 -0
- package/dist/src/repo/under-replication-drain.js +461 -0
- package/dist/src/repo/under-replication-drain.js.map +1 -0
- package/dist/src/rn.d.ts +5 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +5 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/block-latch.d.ts +3 -2
- package/dist/src/storage/block-latch.d.ts.map +1 -1
- package/dist/src/storage/block-latch.js +7 -4
- package/dist/src/storage/block-latch.js.map +1 -1
- package/dist/src/storage/block-storage.d.ts +45 -3
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +156 -9
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +57 -5
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-block-storage.js.map +1 -1
- package/dist/src/storage/pending-claim.d.ts +167 -0
- package/dist/src/storage/pending-claim.d.ts.map +1 -0
- package/dist/src/storage/pending-claim.js +156 -0
- package/dist/src/storage/pending-claim.js.map +1 -0
- package/dist/src/storage/raw-store-codec.d.ts +13 -0
- package/dist/src/storage/raw-store-codec.d.ts.map +1 -1
- package/dist/src/storage/raw-store-codec.js +24 -3
- package/dist/src/storage/raw-store-codec.js.map +1 -1
- package/dist/src/storage/restoration-coordinator.d.ts.map +1 -1
- package/dist/src/storage/restoration-coordinator.js +2 -1
- package/dist/src/storage/restoration-coordinator.js.map +1 -1
- package/dist/src/storage/ring-shift-coordinator.d.ts.map +1 -1
- package/dist/src/storage/ring-shift-coordinator.js +3 -3
- package/dist/src/storage/ring-shift-coordinator.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +137 -5
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +273 -68
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/storage/struct.d.ts +58 -0
- package/dist/src/storage/struct.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +92 -11
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +246 -103
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +27 -3
- package/src/cluster/block-transfer-service.ts +132 -4
- package/src/cluster/block-transfer.ts +79 -84
- package/src/cluster/client.ts +115 -119
- package/src/cluster/cluster-policy.ts +2 -1
- package/src/cluster/cluster-repo.ts +3115 -2671
- package/src/cluster/commit-proof.ts +2 -2
- package/src/cluster/race-resolution.ts +76 -7
- package/src/cluster/rebalance-monitor.ts +677 -539
- package/src/cluster/spread-on-churn.ts +34 -54
- package/src/cohort-topic/host.ts +8 -6
- package/src/cohort-topic/membership-source.ts +1 -1
- package/src/cohort-topic/peer-codec.ts +34 -31
- package/src/cohort-topic/stream-util.ts +39 -4
- package/src/cohort-topic/topic-router.ts +12 -2
- package/src/dispute/client.ts +17 -7
- package/src/index.ts +64 -59
- package/src/libp2p-key-network.ts +1377 -1253
- package/src/libp2p-node-base.ts +186 -27
- package/src/libp2p-node-rn.ts +31 -30
- package/src/libp2p-node.ts +37 -36
- package/src/logger.ts +20 -7
- package/src/matchmaking/query-transport.ts +9 -9
- package/src/network/network-manager-service.ts +16 -5
- package/src/network/relay-reservation.ts +636 -0
- package/src/pend-validation.ts +3 -2
- package/src/protocol-client.ts +13 -1
- package/src/reactivity/forwarder-host.ts +2 -1
- package/src/reactivity/origination-manager.ts +3 -3
- package/src/reactivity/recover-transport.ts +39 -22
- package/src/reactivity/subscription-manager.ts +2 -2
- package/src/reactivity/topic-bytes.ts +35 -37
- package/src/repo/client.ts +172 -173
- package/src/repo/cluster-coordinator.ts +1296 -1113
- package/src/repo/coordinator-repo.ts +3087 -2687
- package/src/repo/i-under-replication-ledger.ts +89 -0
- package/src/repo/kv-under-replication-ledger.ts +302 -0
- package/src/repo/responsibility.ts +48 -0
- package/src/repo/service.ts +76 -39
- package/src/repo/stuck-reservation.ts +224 -0
- package/src/repo/under-replication-drain.ts +582 -0
- package/src/rn.ts +74 -69
- package/src/storage/block-latch.ts +7 -4
- package/src/storage/block-storage.ts +161 -10
- package/src/storage/i-block-storage.ts +60 -5
- package/src/storage/pending-claim.ts +200 -0
- package/src/storage/raw-store-codec.ts +26 -3
- package/src/storage/restoration-coordinator.ts +212 -211
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/storage-repo.ts +311 -73
- package/src/storage/struct.ts +58 -0
- package/src/testing/mesh-harness.ts +329 -115
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { Startable, Libp2p } from '@libp2p/interface'
|
|
2
2
|
import type { IPeerNetwork } from '@optimystic/db-core'
|
|
3
|
+
import { routingKeyForBlock } from '@optimystic/db-core'
|
|
3
4
|
import { hashKey } from 'p2p-fret'
|
|
4
5
|
import type { FretService } from 'p2p-fret'
|
|
5
|
-
import { peerIdFromString } from '@libp2p/peer-id'
|
|
6
6
|
import type { PartitionDetector } from './partition-detector.js'
|
|
7
7
|
import type { ProofRetainingRepo } from '../storage/block-archive.js'
|
|
8
|
-
import {
|
|
8
|
+
import { pushBlockToPeers } from './block-transfer-service.js'
|
|
9
9
|
import { createLogger } from '../logger.js'
|
|
10
10
|
|
|
11
11
|
const log = createLogger('spread-on-churn')
|
|
12
|
-
const textEncoder = new TextEncoder()
|
|
13
12
|
|
|
14
13
|
// ── Types ────────────────────────────────────────────────────────────
|
|
15
14
|
|
|
@@ -208,7 +207,7 @@ export class SpreadOnChurnMonitor implements Startable {
|
|
|
208
207
|
const spreadResults: SpreadEvent['spread'] = []
|
|
209
208
|
|
|
210
209
|
for (const blockId of this.trackedBlocks) {
|
|
211
|
-
const key =
|
|
210
|
+
const key = routingKeyForBlock(blockId)
|
|
212
211
|
const coord = await hashKey(key)
|
|
213
212
|
|
|
214
213
|
// Check eligibility: only middle peers spread
|
|
@@ -224,18 +223,33 @@ export class SpreadOnChurnMonitor implements Startable {
|
|
|
224
223
|
const targets = expanded.filter(id => !cohortSet.has(id) && id !== selfId)
|
|
225
224
|
if (targets.length === 0) continue
|
|
226
225
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
// The shared read-certify-push loop, to every target. Both per-target deadlines are set:
|
|
227
|
+
// a target that connects but never replies would otherwise hang this sequential loop
|
|
228
|
+
// forever during churn (exactly when slow/dying peers are common). A receiver running
|
|
229
|
+
// the default `requirePushCertificate` rejects an uncertified push, so a block this node
|
|
230
|
+
// holds no proof for simply fails to spread (logged by the receiver as
|
|
231
|
+
// `push:reject-uncertified`) rather than being planted.
|
|
232
|
+
//
|
|
233
|
+
// NOTE: no memory of earlier spreads — every debounced departure re-pushes every eligible block
|
|
234
|
+
// to every expansion target, including targets a previous pass already confirmed. Targets are
|
|
235
|
+
// only peers OUTSIDE the cohort, so a deployment no larger than its cohort (a two-member pair)
|
|
236
|
+
// spreads nothing. If churn on a larger network shows the same (block, target) pushed pass
|
|
237
|
+
// after pass, remember confirmed targets per block the way RebalanceMonitor's `cohortPeers` does.
|
|
238
|
+
const outcome = await pushBlockToPeers(this.deps.repo, this.deps.peerNetwork, blockId, targets, {
|
|
239
|
+
reason: 'replication',
|
|
240
|
+
protocolPrefix: this.deps.protocolPrefix,
|
|
241
|
+
dialTimeoutMs: this.config.pushDialTimeoutMs,
|
|
242
|
+
responseTimeoutMs: this.config.pushResponseTimeoutMs,
|
|
243
|
+
})
|
|
244
|
+
if (outcome.status === 'unavailable') {
|
|
231
245
|
// The repo could not work out whether it still holds this block (unmaterializable
|
|
232
246
|
// history / failed restore). Untracking on that answer would silently drop the block
|
|
233
247
|
// from the spread set on a guess, and only a later re-commit would put it back — so
|
|
234
248
|
// keep it tracked and let the next sweep (or a heal) settle it.
|
|
235
|
-
log('unavailable block=%s reason=%s (keeping tracked)', blockId,
|
|
249
|
+
log('unavailable block=%s reason=%s (keeping tracked)', blockId, outcome.reason)
|
|
236
250
|
continue
|
|
237
251
|
}
|
|
238
|
-
if (
|
|
252
|
+
if (outcome.status === 'no-local-data') {
|
|
239
253
|
// The block has left local storage. No deletion event exists today to evict it
|
|
240
254
|
// from the tracked set, so prune here. Deleting the current element of a Set mid
|
|
241
255
|
// for...of is safe - it does not disturb the rest of the iteration. A later
|
|
@@ -245,50 +259,16 @@ export class SpreadOnChurnMonitor implements Startable {
|
|
|
245
259
|
continue
|
|
246
260
|
}
|
|
247
261
|
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const succeeded: string[] = []
|
|
259
|
-
const failed: string[] = []
|
|
260
|
-
|
|
261
|
-
for (const targetId of targets) {
|
|
262
|
-
try {
|
|
263
|
-
const peerId = peerIdFromString(targetId)
|
|
264
|
-
const client = new BlockTransferClient(
|
|
265
|
-
peerId,
|
|
266
|
-
this.deps.peerNetwork,
|
|
267
|
-
this.deps.protocolPrefix
|
|
268
|
-
)
|
|
269
|
-
// Bound both the dial and the response read. A target that connects but
|
|
270
|
-
// never replies would otherwise hang this sequential loop forever during
|
|
271
|
-
// churn (exactly when slow/dying peers are common); the deadline makes the
|
|
272
|
-
// push throw, which the catch below records as `failed` so the pass advances.
|
|
273
|
-
const response = await client.pushBlocks([blockId], [blockData], 'replication', certification, {
|
|
274
|
-
dialTimeoutMs: this.config.pushDialTimeoutMs,
|
|
275
|
-
responseTimeoutMs: this.config.pushResponseTimeoutMs,
|
|
276
|
-
})
|
|
277
|
-
// A round-trip that does not throw still does not mean the replica
|
|
278
|
-
// landed: handlePush reports a block in `missing` when it could not
|
|
279
|
-
// parse or persist it. Treat that as a failed push so the resilience
|
|
280
|
-
// mechanism does not falsely count the new owner as holding the block.
|
|
281
|
-
if (response.missing.includes(blockId)) {
|
|
282
|
-
failed.push(targetId)
|
|
283
|
-
log('push:rejected block=%s target=%s (receiver did not persist)', blockId, targetId)
|
|
284
|
-
} else {
|
|
285
|
-
succeeded.push(targetId)
|
|
286
|
-
log('push:ok block=%s target=%s', blockId, targetId)
|
|
287
|
-
}
|
|
288
|
-
} catch (err) {
|
|
289
|
-
failed.push(targetId)
|
|
290
|
-
log('push:fail block=%s target=%s err=%s',
|
|
291
|
-
blockId, targetId, (err as Error).message)
|
|
262
|
+
const succeeded = outcome.confirmed
|
|
263
|
+
const failed = outcome.refusals.map(refusal => refusal.peerId)
|
|
264
|
+
for (const targetId of succeeded) {
|
|
265
|
+
log('push:ok block=%s target=%s', blockId, targetId)
|
|
266
|
+
}
|
|
267
|
+
for (const refusal of outcome.refusals) {
|
|
268
|
+
if (refusal.reason === 'rejected') {
|
|
269
|
+
log('push:rejected block=%s target=%s (receiver did not persist)', blockId, refusal.peerId)
|
|
270
|
+
} else {
|
|
271
|
+
log('push:fail block=%s target=%s err=%s', blockId, refusal.peerId, refusal.error)
|
|
292
272
|
}
|
|
293
273
|
}
|
|
294
274
|
|
package/src/cohort-topic/host.ts
CHANGED
|
@@ -167,7 +167,7 @@ import { createPoWVerifier, createReputationVerifier, type BootstrapReputationVi
|
|
|
167
167
|
import { createParentReferenceVerifier, createDefaultParentTopicView, type BootstrapParentTopicView } from "./bootstrap-parent-reference.js";
|
|
168
168
|
import { createBootstrapEvidenceBuilder } from "./bootstrap-evidence-builder.js";
|
|
169
169
|
import { DEFAULT_COHORT_TOPIC_PROTOCOLS, cohortTopicProtocolList, type CohortTopicProtocols } from "./protocols.js";
|
|
170
|
-
import { requestResponse, DEFAULT_STREAM_MAX_BYTES } from "./stream-util.js";
|
|
170
|
+
import { requestResponse, requireReply, DEFAULT_STREAM_MAX_BYTES } from "./stream-util.js";
|
|
171
171
|
import { createLogger } from "../logger.js";
|
|
172
172
|
|
|
173
173
|
const log = createLogger("cohort-topic");
|
|
@@ -697,10 +697,11 @@ export async function createCohortTopicHost(node: Libp2p, fret: FretService, opt
|
|
|
697
697
|
// The real k − x assembly lives in each CoordEngine's own threshold signer (constructed per coord).
|
|
698
698
|
const verifyingSigner = createCohortSigner(createVerifyOnlyThresholdCrypto(), minSigs);
|
|
699
699
|
|
|
700
|
-
// Collect one cohort member's `/sign` endorsement over the new fifth protocol.
|
|
700
|
+
// Collect one cohort member's `/sign` endorsement over the new fifth protocol. The `/sign` responder always
|
|
701
|
+
// writes a frame, so a no-result reply is a non-conforming peer: reject, and `collectFrom` counts no signature.
|
|
701
702
|
const dialSign = async (peerIdStr: string, request: SignRequestV1): Promise<SignReplyV1> => {
|
|
702
703
|
const reply = await requestResponse(node, peerIdFromString(peerIdStr), protocols.sign, encodeCohortMessage(request, maxBytes), maxBytes);
|
|
703
|
-
return validateSignReplyV1(decodeCohortMessage(reply, maxBytes));
|
|
704
|
+
return validateSignReplyV1(decodeCohortMessage(requireReply(reply, "cohort sign"), maxBytes));
|
|
704
705
|
};
|
|
705
706
|
|
|
706
707
|
/** FRET assembly around `coord`: self prepended + deduped; epoch = H(sorted member join). */
|
|
@@ -3175,9 +3176,10 @@ async function registerCohortTopicProtocols(
|
|
|
3175
3176
|
*
|
|
3176
3177
|
* `handle` cannot return `undefined`, and that is the point. The dialer's `readFramed` treats bare
|
|
3177
3178
|
* end-of-stream as a truncation error rather than an empty reply, so a reply-reading protocol
|
|
3178
|
-
* (register, membership, sign) must always put a frame on the wire — an empty one for "no result"
|
|
3179
|
-
*
|
|
3180
|
-
* where a later edit cannot silently
|
|
3179
|
+
* (register, membership, sign) must always put a frame on the wire — an empty one for "no result",
|
|
3180
|
+
* which the dialer's `requestResponse` resolves as `undefined`. Making that a separate constructor
|
|
3181
|
+
* from {@link makeOneWayHandler} puts the invariant in the type, where a later edit cannot silently
|
|
3182
|
+
* opt out of it. (`stream-util.ts#handleRequestResponse` takes
|
|
3181
3183
|
* the other route for the same reason: every consumer of it is a reply-reading protocol, so it maps
|
|
3182
3184
|
* a handler's `undefined` onto an explicit zero-length frame itself.)
|
|
3183
3185
|
*/
|
|
@@ -50,7 +50,7 @@ export class FretMembershipSource implements IMembershipSource {
|
|
|
50
50
|
for (const peerStr of this.resolver.cohortPeers(coord, this.wants)) {
|
|
51
51
|
try {
|
|
52
52
|
const reply = await requestResponse(this.node, peerIdFromString(peerStr), this.membershipProtocol, request, this.maxBytes);
|
|
53
|
-
if (reply
|
|
53
|
+
if (reply !== undefined) {
|
|
54
54
|
this.cache(coord, reply);
|
|
55
55
|
return reply;
|
|
56
56
|
}
|
|
@@ -1,31 +1,34 @@
|
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
// Built on first use, not at module load: Hermes (React Native) has no native `TextDecoder`, so a
|
|
17
|
+
// module-scope construction fails the import itself whenever it runs ahead of the host's polyfill.
|
|
18
|
+
let utf8DecoderInstance: TextDecoder | undefined;
|
|
19
|
+
const utf8Decoder = (): TextDecoder => utf8DecoderInstance ??= new TextDecoder("utf-8", { fatal: true });
|
|
20
|
+
|
|
21
|
+
/** A cohort-member id as the substrate's opaque bytes: UTF-8 of the canonical peer-id string. */
|
|
22
|
+
export function peerIdToBytes(peerId: PeerId | string): Uint8Array {
|
|
23
|
+
return utf8Encoder.encode(typeof peerId === "string" ? peerId : peerId.toString());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Reverse of {@link peerIdToBytes}: decode member bytes back to the canonical peer-id string. */
|
|
27
|
+
export function bytesToPeerIdString(bytes: Uint8Array): string {
|
|
28
|
+
return utf8Decoder().decode(bytes);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Reverse of {@link peerIdToBytes}: decode member bytes back to a dialable {@link PeerId}. */
|
|
32
|
+
export function bytesToPeerId(bytes: Uint8Array): PeerId {
|
|
33
|
+
return peerIdFromString(bytesToPeerIdString(bytes));
|
|
34
|
+
}
|
|
@@ -34,6 +34,13 @@ export const DEFAULT_STREAM_MAX_BYTES = 512 * 1024;
|
|
|
34
34
|
/**
|
|
35
35
|
* Open `protocol` to `peer`, send `frame`, and read the bounded reply frame.
|
|
36
36
|
*
|
|
37
|
+
* Resolves `undefined` when the peer replied with a **zero-length frame** — the in-band "I have no
|
|
38
|
+
* result" signal {@link handleRequestResponse} sends for a handler that returned nothing. A returned
|
|
39
|
+
* `Uint8Array` is therefore never empty, and every caller must decide what "no result" means for its
|
|
40
|
+
* protocol: fall through to another peer, map it to a benign default, or {@link requireReply} when the
|
|
41
|
+
* protocol always carries a reply. Genuine failures still reject — a dial failure, a stream the peer
|
|
42
|
+
* aborted (`FrameTruncationError`), an over-ceiling reply (`PayloadTooLargeError`).
|
|
43
|
+
*
|
|
37
44
|
* NOTE: takes no `AbortSignal`, so a caller cannot set its own deadline. Bounded today anyway —
|
|
38
45
|
* `readFramed` self-times-out at 5s and `dialProtocol` falls back to libp2p's default dial
|
|
39
46
|
* timeout (~30s) — so an unresponsive peer is slow, not hung. If a caller ever needs a tighter
|
|
@@ -47,14 +54,18 @@ export async function requestResponse(
|
|
|
47
54
|
protocol: string,
|
|
48
55
|
frame: Uint8Array,
|
|
49
56
|
maxBytes = DEFAULT_STREAM_MAX_BYTES,
|
|
50
|
-
): Promise<Uint8Array> {
|
|
57
|
+
): Promise<Uint8Array | undefined> {
|
|
51
58
|
let stream: Stream | undefined;
|
|
52
59
|
try {
|
|
53
60
|
// `negotiateFully` deliberately omitted — see the accepted tradeoff in the module docblock.
|
|
54
61
|
stream = await openProtocolStream(node, peer, protocol);
|
|
55
62
|
sendFramed(stream, frame);
|
|
56
63
|
await stream.close();
|
|
57
|
-
|
|
64
|
+
const reply = await readFramed(stream, maxBytes);
|
|
65
|
+
// NOTE: every zero-length reply maps to "no result". Safe while no protocol has a meaningful empty reply
|
|
66
|
+
// (cohort frames carry an inner length prefix; query/recover replies are encoded objects). If one ever
|
|
67
|
+
// needs to answer "empty" as data, it must send a non-empty envelope rather than bare empty bytes.
|
|
68
|
+
return reply.length === 0 ? undefined : reply;
|
|
58
69
|
} finally {
|
|
59
70
|
if (stream != null) {
|
|
60
71
|
try {
|
|
@@ -66,6 +77,25 @@ export async function requestResponse(
|
|
|
66
77
|
}
|
|
67
78
|
}
|
|
68
79
|
|
|
80
|
+
/** Thrown when a request/response peer replied with a zero-length frame where the protocol requires a reply. */
|
|
81
|
+
export class NoResultReplyError extends Error {
|
|
82
|
+
constructor(context: string) {
|
|
83
|
+
super(`${context}: peer replied with no result (zero-length reply frame)`);
|
|
84
|
+
this.name = "NoResultReplyError";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Unwrap a {@link requestResponse} reply for a protocol whose responder always carries a frame; throw
|
|
90
|
+
* {@link NoResultReplyError} (naming `context`) when the peer had no result.
|
|
91
|
+
*/
|
|
92
|
+
export function requireReply(reply: Uint8Array | undefined, context: string): Uint8Array {
|
|
93
|
+
if (reply === undefined) {
|
|
94
|
+
throw new NoResultReplyError(context);
|
|
95
|
+
}
|
|
96
|
+
return reply;
|
|
97
|
+
}
|
|
98
|
+
|
|
69
99
|
/** Open `protocol` to `peer` and send `frame` without awaiting a reply (fire-and-forget gossip). */
|
|
70
100
|
export async function sendOneWay(node: Libp2p, peer: PeerId, protocol: string, frame: Uint8Array): Promise<void> {
|
|
71
101
|
let stream: Stream | undefined;
|
|
@@ -90,8 +120,13 @@ export async function sendOneWay(node: Libp2p, peer: PeerId, protocol: string, f
|
|
|
90
120
|
*
|
|
91
121
|
* A `handle` that returns `undefined` (a drop, a gate rejection, no serving engine) replies with an
|
|
92
122
|
* explicit **zero-length frame** rather than silence: `readFramed` treats end-of-stream as a
|
|
93
|
-
* truncation error, so "no
|
|
94
|
-
* resolves it as
|
|
123
|
+
* truncation error, so "no result" must travel in-band — the dialer's {@link requestResponse}
|
|
124
|
+
* resolves it as `undefined`. Returning `new Uint8Array(0)` puts the same zero-length frame on the
|
|
125
|
+
* wire, so it is the same signal (host.ts's membership responder says "no certificate" that way).
|
|
126
|
+
* A `handle` that throws aborts the stream instead, which the dialer sees as a rejection.
|
|
127
|
+
*
|
|
128
|
+
* Only the reply direction carries "no result": the request frame `handle` receives is always bytes,
|
|
129
|
+
* and an empty request arrives as a zero-length `Uint8Array`.
|
|
95
130
|
*/
|
|
96
131
|
export function handleRequestResponse(
|
|
97
132
|
node: Libp2p,
|
|
@@ -3,7 +3,7 @@ import { bytesToB64url, b64urlToBytes, encodeCohortMessage } from "@optimystic/d
|
|
|
3
3
|
import type { Libp2p } from "libp2p";
|
|
4
4
|
import type { FretService, RouteAndMaybeActV1, NearAnchorV1 } from "p2p-fret";
|
|
5
5
|
import { bytesToPeerId } from "./peer-codec.js";
|
|
6
|
-
import { requestResponse, DEFAULT_STREAM_MAX_BYTES } from "./stream-util.js";
|
|
6
|
+
import { requestResponse, requireReply, DEFAULT_STREAM_MAX_BYTES, type NoResultReplyError } from "./stream-util.js";
|
|
7
7
|
import { PROTOCOL_COHORT_REGISTER } from "./protocols.js";
|
|
8
8
|
|
|
9
9
|
/** A FRET `routeAct` result carrying a cohort reply. */
|
|
@@ -70,8 +70,18 @@ export class FretTopicRouter implements ITopicRouter {
|
|
|
70
70
|
return encodeCohortMessage({ v: 1, result: "no_state" }, this.maxBytes);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Dial `member`'s `/register` directly and return its encoded reply.
|
|
75
|
+
*
|
|
76
|
+
* The `/register` responder always writes a frame, so a no-result (zero-length) reply only comes from a
|
|
77
|
+
* non-conforming peer; it rejects with {@link NoResultReplyError}, reaching the caller exactly as a dial
|
|
78
|
+
* failure does (renewal counts a failed ping; the walk's direct dial propagates the rejection). db-core's {@link ITopicRouter.dialMember} port deliberately stays
|
|
79
|
+
* `Promise<Uint8Array>` rather than widening to `| undefined`: that would ripple through the walk and
|
|
80
|
+
* renewal decision logic for a state only a misbehaving peer can produce, so the decision is made here.
|
|
81
|
+
*/
|
|
73
82
|
async dialMember(member: PeerRef, activity: Uint8Array): Promise<Uint8Array> {
|
|
74
83
|
const peer = bytesToPeerId(member.id);
|
|
75
|
-
|
|
84
|
+
const reply = await requestResponse(this.node, peer, this.registerProtocol, activity, this.maxBytes);
|
|
85
|
+
return requireReply(reply, "cohort-topic register dial");
|
|
76
86
|
}
|
|
77
87
|
}
|
package/src/dispute/client.ts
CHANGED
|
@@ -29,13 +29,23 @@ export class DisputeClient extends ProtocolClient {
|
|
|
29
29
|
// arbitrator" behavior. Also apply the default dial cap so a challenge to an
|
|
30
30
|
// unreachable arbitrator fails the dial fast even when no `timeoutMs` is given;
|
|
31
31
|
// this does not alter the response semantics (still bounded only by the signal).
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
38
|
-
|
|
32
|
+
//
|
|
33
|
+
// Explicit AbortController + timer rather than `AbortSignal.timeout`, which Hermes
|
|
34
|
+
// (React Native's JS engine) does not provide — same pattern as `dialRelay` in
|
|
35
|
+
// network/relay-reservation.ts. The timer is cleared on every exit path below so a
|
|
36
|
+
// finished call never holds a timer (and the node it references) alive.
|
|
37
|
+
const controller = timeoutMs ? new AbortController() : undefined;
|
|
38
|
+
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
|
|
39
|
+
try {
|
|
40
|
+
const response = await this.processMessage<{ type: 'vote'; vote: ArbitrationVote }>(
|
|
41
|
+
message,
|
|
42
|
+
this.protocol,
|
|
43
|
+
{ signal: controller?.signal, dialTimeoutMs: DEFAULT_DIAL_TIMEOUT_MS, maxDataLength: MAX_CONTROL_MESSAGE_BYTES }
|
|
44
|
+
);
|
|
45
|
+
return response.vote;
|
|
46
|
+
} finally {
|
|
47
|
+
if (timer) clearTimeout(timer);
|
|
48
|
+
}
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
/** Send a resolution to a peer (broadcast) */
|
package/src/index.ts
CHANGED
|
@@ -1,59 +1,64 @@
|
|
|
1
|
-
export * from "./cluster/client.js";
|
|
2
|
-
export * from "./cluster/client-signature-verifier.js";
|
|
3
|
-
export * from "./cluster/cluster-repo.js";
|
|
4
|
-
export * from "./cluster/cluster-policy.js";
|
|
5
|
-
export * from "./cluster/commit-cert.js";
|
|
6
|
-
export * from "./cluster/commit-proof.js";
|
|
7
|
-
export * from "./cluster/service.js";
|
|
8
|
-
export * from "./cluster/rebalance-monitor.js";
|
|
9
|
-
export * from "./cluster/spread-on-churn.js";
|
|
10
|
-
export * from "./cluster/block-transfer.js";
|
|
11
|
-
export * from "./cluster/block-transfer-service.js";
|
|
12
|
-
export * from "./inbound-authorization.js";
|
|
13
|
-
export * from "./protocol-client.js";
|
|
14
|
-
export * from "./repo/client.js";
|
|
15
|
-
export * from "./repo/cluster-coordinator.js";
|
|
16
|
-
export * from "./repo/coordinator-repo.js";
|
|
17
|
-
export * from "./repo/
|
|
18
|
-
export * from "./repo/
|
|
19
|
-
export * from "./
|
|
20
|
-
export * from "./
|
|
21
|
-
export * from "./
|
|
22
|
-
export * from "./
|
|
23
|
-
export * from "./storage/
|
|
24
|
-
export * from "./storage/
|
|
25
|
-
export * from "./storage/
|
|
26
|
-
export * from "./storage/
|
|
27
|
-
export * from "./storage/
|
|
28
|
-
export * from "./storage/
|
|
29
|
-
export * from "./storage/
|
|
30
|
-
export * from "./storage/
|
|
31
|
-
export * from "./storage/
|
|
32
|
-
export * from "./storage/
|
|
33
|
-
export * from "./storage/
|
|
34
|
-
export * from "./storage/storage
|
|
35
|
-
export * from "./storage/
|
|
36
|
-
export * from "./storage/
|
|
37
|
-
export * from "./storage/
|
|
38
|
-
export * from "./storage/
|
|
39
|
-
export * from "./
|
|
40
|
-
export * from "./
|
|
41
|
-
export * from "./
|
|
42
|
-
export * from "./
|
|
43
|
-
export * from "./
|
|
44
|
-
export * from "./
|
|
45
|
-
export * from "./
|
|
46
|
-
export * from "./
|
|
47
|
-
export * from "./
|
|
48
|
-
export * from "./
|
|
49
|
-
export * from "./
|
|
50
|
-
export * from "./
|
|
51
|
-
export * from "./
|
|
52
|
-
export * from "./
|
|
53
|
-
export * from "./
|
|
54
|
-
export * from "./
|
|
55
|
-
export * from "./
|
|
56
|
-
export * from "./
|
|
57
|
-
export * from "./
|
|
58
|
-
export * from "./
|
|
59
|
-
export * from "./
|
|
1
|
+
export * from "./cluster/client.js";
|
|
2
|
+
export * from "./cluster/client-signature-verifier.js";
|
|
3
|
+
export * from "./cluster/cluster-repo.js";
|
|
4
|
+
export * from "./cluster/cluster-policy.js";
|
|
5
|
+
export * from "./cluster/commit-cert.js";
|
|
6
|
+
export * from "./cluster/commit-proof.js";
|
|
7
|
+
export * from "./cluster/service.js";
|
|
8
|
+
export * from "./cluster/rebalance-monitor.js";
|
|
9
|
+
export * from "./cluster/spread-on-churn.js";
|
|
10
|
+
export * from "./cluster/block-transfer.js";
|
|
11
|
+
export * from "./cluster/block-transfer-service.js";
|
|
12
|
+
export * from "./inbound-authorization.js";
|
|
13
|
+
export * from "./protocol-client.js";
|
|
14
|
+
export * from "./repo/client.js";
|
|
15
|
+
export * from "./repo/cluster-coordinator.js";
|
|
16
|
+
export * from "./repo/coordinator-repo.js";
|
|
17
|
+
export * from "./repo/i-under-replication-ledger.js";
|
|
18
|
+
export * from "./repo/kv-under-replication-ledger.js";
|
|
19
|
+
export * from "./repo/under-replication-drain.js";
|
|
20
|
+
export * from "./repo/responsibility.js";
|
|
21
|
+
export * from "./repo/served-repo-proxy.js";
|
|
22
|
+
export * from "./repo/service.js";
|
|
23
|
+
export * from "./storage/block-storage.js";
|
|
24
|
+
export * from "./storage/block-latch.js";
|
|
25
|
+
export * from "./storage/store-identity.js";
|
|
26
|
+
export * from "./storage/raw-store-driver.js";
|
|
27
|
+
export * from "./storage/kv-raw-storage.js";
|
|
28
|
+
export * from "./storage/shared-cache-pool.js";
|
|
29
|
+
export * from "./storage/cached-store-driver.js";
|
|
30
|
+
export * from "./storage/cached-raw-storage.js";
|
|
31
|
+
export * from "./storage/with-read-cache.js";
|
|
32
|
+
export * from "./storage/memory-store-driver.js";
|
|
33
|
+
export * from "./storage/memory-storage.js";
|
|
34
|
+
export * from "./storage/i-block-storage.js";
|
|
35
|
+
export * from "./storage/i-raw-storage.js";
|
|
36
|
+
export * from "./storage/struct.js";
|
|
37
|
+
export * from "./storage/pending-claim.js";
|
|
38
|
+
export * from "./storage/block-archive.js";
|
|
39
|
+
export * from "./storage/storage-repo.js";
|
|
40
|
+
export * from "./storage/restoration-coordinator.js";
|
|
41
|
+
export * from "./storage/ring-selector.js";
|
|
42
|
+
export * from "./storage/storage-monitor.js";
|
|
43
|
+
export * from "./storage/arachnode-fret-adapter.js";
|
|
44
|
+
export * from "./sync/protocol.js";
|
|
45
|
+
export * from "./sync/client.js";
|
|
46
|
+
export * from "./sync/service.js";
|
|
47
|
+
export * from "./it-utility.js";
|
|
48
|
+
export * from "./libp2p-key-network.js";
|
|
49
|
+
export * from "./libp2p-node.js";
|
|
50
|
+
export * from "./optimystic-node.js";
|
|
51
|
+
export * from "./routing/responsibility.js";
|
|
52
|
+
export * from "./routing/libp2p-known-peers.js";
|
|
53
|
+
export * from "./network/network-manager-service.js";
|
|
54
|
+
export * from "./network/get-network-manager.js";
|
|
55
|
+
export * from "./reputation/index.js";
|
|
56
|
+
export * from "./dispute/index.js";
|
|
57
|
+
export * from "./cohort-topic/index.js";
|
|
58
|
+
export * from "./matchmaking/index.js";
|
|
59
|
+
export * from "./reactivity/index.js";
|
|
60
|
+
export * from "./cluster/i-transaction-state-store.js";
|
|
61
|
+
export * from "./cluster/memory-transaction-state-store.js";
|
|
62
|
+
export * from "./cluster/persistent-transaction-state-store.js";
|
|
63
|
+
export * from "./storage/i-kv-store.js";
|
|
64
|
+
export * from "./storage/memory-kv-store.js";
|