@optimystic/db-p2p 1.0.0-beta.3 → 1.1.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 +155 -9
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +446 -58
- 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 +234 -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 +6 -1
- 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 +96 -11
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +164 -25
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +240 -122
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +627 -473
- 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 +122 -5
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +245 -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 +77 -11
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +226 -100
- 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 +493 -65
- 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 -1260
- 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 +6 -1
- 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 +183 -25
- package/src/repo/coordinator-repo.ts +656 -506
- 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 +280 -72
- package/src/storage/struct.ts +58 -0
- package/src/testing/mesh-harness.ts +293 -112
package/src/cluster/client.ts
CHANGED
|
@@ -1,119 +1,115 @@
|
|
|
1
|
-
import type { PeerId, IPeerNetwork, ICluster, ClusterRecord } from '@optimystic/db-core';
|
|
2
|
-
import {
|
|
3
|
-
import { ProtocolClient } from '../protocol-client.js';
|
|
4
|
-
import { peerIdFromString } from '@libp2p/peer-id';
|
|
5
|
-
import { isClusterErrorEnvelope, clusterErrorFromEnvelope } from './cluster-error.js';
|
|
6
|
-
import type { RedirectPayload } from '../repo/redirect.js';
|
|
7
|
-
import { mergeRecordPeerAddresses } from '../peer-address-book.js';
|
|
8
|
-
import { createLogger } from '../logger.js';
|
|
9
|
-
import { withRpcDeadlineDefaults, type RpcDeadlineOptions } from '../rpc-deadline.js';
|
|
10
|
-
import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
|
|
11
|
-
|
|
12
|
-
const log = createLogger('cluster-client');
|
|
13
|
-
|
|
14
|
-
export class ClusterClient extends ProtocolClient implements ICluster {
|
|
15
|
-
private constructor(peerId: PeerId, peerNetwork: IPeerNetwork, readonly protocolPrefix?: string) {
|
|
16
|
-
super(peerId, peerNetwork);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Create a new client instance */
|
|
20
|
-
public static create(peerId: PeerId, peerNetwork: IPeerNetwork, protocolPrefix?: string): ClusterClient {
|
|
21
|
-
return new ClusterClient(peerId, peerNetwork, protocolPrefix);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async update(record: ClusterRecord, hop: number = 0, options?: RpcDeadlineOptions): Promise<ClusterRecord> {
|
|
25
|
-
const message = {
|
|
26
|
-
operation: 'update',
|
|
27
|
-
record
|
|
28
|
-
};
|
|
29
|
-
// Every node registers the cluster service under its network-prefixed
|
|
30
|
-
// protocol id (`/optimystic/<network>/cluster/1.0.0`); the bare
|
|
31
|
-
// `/db-p2p/cluster/1.0.0` is never registered, so there is no legacy
|
|
32
|
-
// fallback to attempt — a single dial whose error propagates directly.
|
|
33
|
-
const protocol = (this.protocolPrefix ?? '/db-p2p') + '/cluster/1.0.0';
|
|
34
|
-
// Apply the client-level dial/response deadline so a silent cluster peer
|
|
35
|
-
// can't hang the coordinator forever; an explicit caller override wins.
|
|
36
|
-
// Response is a ClusterRecord (peer set + signatures + small metadata) → control cap.
|
|
37
|
-
const response = await this.processMessage<unknown>(message, protocol, { ...withRpcDeadlineDefaults(options), maxDataLength: MAX_CONTROL_MESSAGE_BYTES });
|
|
38
|
-
|
|
39
|
-
// A member that threw inside `update` replies with a structured error
|
|
40
|
-
// envelope rather than aborting the stream; rethrow the server's real
|
|
41
|
-
// error (preserving name/code) so the coordinator sees the true cause.
|
|
42
|
-
if (isClusterErrorEnvelope(response)) {
|
|
43
|
-
throw clusterErrorFromEnvelope(response);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Type the redirect branch against the payload the service actually produces
|
|
47
|
-
// (`RedirectPayload`), not a hand-written narrower shape — the old `{ id: string }`
|
|
48
|
-
// literal is what structurally discarded the `addrs` the sender took the trouble to embed.
|
|
49
|
-
const redirectResponse = response as Partial<RedirectPayload>;
|
|
50
|
-
if (redirectResponse?.redirect?.peers?.length) {
|
|
51
|
-
if (hop >= 2) {
|
|
52
|
-
throw new Error('Redirect loop detected in ClusterClient (max hops reached)')
|
|
53
|
-
}
|
|
54
|
-
const currentIdStr = this.peerId.toString()
|
|
55
|
-
const next = redirectResponse.redirect.peers.find((p) => p.id !== currentIdStr) ?? redirectResponse.redirect.peers[0]!
|
|
56
|
-
const nextId = peerIdFromString(next.id)
|
|
57
|
-
if (next.id === currentIdStr) {
|
|
58
|
-
throw new Error('Redirect loop detected in ClusterClient (same peer)')
|
|
59
|
-
}
|
|
60
|
-
// Learn the redirect target's addresses BEFORE dialing it: the redirect is the only
|
|
61
|
-
// notice we get that this peer matters, and if it is relay-only we have no address
|
|
62
|
-
// for it at all. Merging after the dial would help only some later hop.
|
|
63
|
-
this.peerNetwork.recordPeerAddresses?.(nextId, next.addrs ?? [])
|
|
64
|
-
|
|
65
|
-
const nextClient = ClusterClient.create(nextId, this.peerNetwork, this.protocolPrefix)
|
|
66
|
-
// Thread the caller's *original* options through the redirect hop (the
|
|
67
|
-
// recursive call re-applies its own defaults) so the deadline survives a redirect.
|
|
68
|
-
return await nextClient.update(record, hop + 1, options)
|
|
69
|
-
}
|
|
70
|
-
const updated = response as ClusterRecord;
|
|
71
|
-
// The member's reply carries the cohort's peer map too, and it may name addresses this
|
|
72
|
-
// node lacks — the coordinator's own half of the same exchange the service handles inbound.
|
|
73
|
-
this.recordRecordPeerAddresses(updated)
|
|
74
|
-
return updated;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Offer every address a cluster record carries for its cohort to the dialer's address book.
|
|
79
|
-
*
|
|
80
|
-
* Shares its traversal — and its cap on how many peers one record may introduce — with the
|
|
81
|
-
* inbound side (`ClusterService.learnPeerAddresses`); validation, the per-peer cap, and the
|
|
82
|
-
* trust boundary live in the implementation behind `recordPeerAddresses`. No self-filter is
|
|
83
|
-
* passed: `this.peerId` is the peer we DIALED, not this node, and only the sink knows the
|
|
84
|
-
* local node's identity.
|
|
85
|
-
*/
|
|
86
|
-
private recordRecordPeerAddresses(record: ClusterRecord): void {
|
|
87
|
-
const sink = this.peerNetwork.recordPeerAddresses
|
|
88
|
-
if (!sink) return
|
|
89
|
-
mergeRecordPeerAddresses(record?.peers, (pid, addrs) => sink.call(this.peerNetwork, pid, addrs), log)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Record a coordinator-affinity hint for the block this record's op is
|
|
94
|
-
* coordinated on, so a follow-up op can dial `peerId` directly.
|
|
95
|
-
*
|
|
96
|
-
* The op lives at `record.message.operations[0]` — `record.message` is a
|
|
97
|
-
* RepoMessage `{ operations: [...] }`, NOT a bare `{ commit }`/`{ pend }`.
|
|
98
|
-
* Reading `record.message.commit`/`.pend` (the old code) always saw
|
|
99
|
-
* `undefined`, so this hint was dead code and never recorded anything.
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* (
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const pn: any = this.peerNetwork as any
|
|
117
|
-
if (typeof pn?.recordCoordinator === 'function') pn.recordCoordinator(kbytes, peerId)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
1
|
+
import type { PeerId, IPeerNetwork, ICluster, ClusterRecord } from '@optimystic/db-core';
|
|
2
|
+
import { blockIdsForTransforms } from '@optimystic/db-core';
|
|
3
|
+
import { ProtocolClient } from '../protocol-client.js';
|
|
4
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
5
|
+
import { isClusterErrorEnvelope, clusterErrorFromEnvelope } from './cluster-error.js';
|
|
6
|
+
import type { RedirectPayload } from '../repo/redirect.js';
|
|
7
|
+
import { mergeRecordPeerAddresses } from '../peer-address-book.js';
|
|
8
|
+
import { createLogger } from '../logger.js';
|
|
9
|
+
import { withRpcDeadlineDefaults, type RpcDeadlineOptions } from '../rpc-deadline.js';
|
|
10
|
+
import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
|
|
11
|
+
|
|
12
|
+
const log = createLogger('cluster-client');
|
|
13
|
+
|
|
14
|
+
export class ClusterClient extends ProtocolClient implements ICluster {
|
|
15
|
+
private constructor(peerId: PeerId, peerNetwork: IPeerNetwork, readonly protocolPrefix?: string) {
|
|
16
|
+
super(peerId, peerNetwork);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Create a new client instance */
|
|
20
|
+
public static create(peerId: PeerId, peerNetwork: IPeerNetwork, protocolPrefix?: string): ClusterClient {
|
|
21
|
+
return new ClusterClient(peerId, peerNetwork, protocolPrefix);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async update(record: ClusterRecord, hop: number = 0, options?: RpcDeadlineOptions): Promise<ClusterRecord> {
|
|
25
|
+
const message = {
|
|
26
|
+
operation: 'update',
|
|
27
|
+
record
|
|
28
|
+
};
|
|
29
|
+
// Every node registers the cluster service under its network-prefixed
|
|
30
|
+
// protocol id (`/optimystic/<network>/cluster/1.0.0`); the bare
|
|
31
|
+
// `/db-p2p/cluster/1.0.0` is never registered, so there is no legacy
|
|
32
|
+
// fallback to attempt — a single dial whose error propagates directly.
|
|
33
|
+
const protocol = (this.protocolPrefix ?? '/db-p2p') + '/cluster/1.0.0';
|
|
34
|
+
// Apply the client-level dial/response deadline so a silent cluster peer
|
|
35
|
+
// can't hang the coordinator forever; an explicit caller override wins.
|
|
36
|
+
// Response is a ClusterRecord (peer set + signatures + small metadata) → control cap.
|
|
37
|
+
const response = await this.processMessage<unknown>(message, protocol, { ...withRpcDeadlineDefaults(options), maxDataLength: MAX_CONTROL_MESSAGE_BYTES });
|
|
38
|
+
|
|
39
|
+
// A member that threw inside `update` replies with a structured error
|
|
40
|
+
// envelope rather than aborting the stream; rethrow the server's real
|
|
41
|
+
// error (preserving name/code) so the coordinator sees the true cause.
|
|
42
|
+
if (isClusterErrorEnvelope(response)) {
|
|
43
|
+
throw clusterErrorFromEnvelope(response);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Type the redirect branch against the payload the service actually produces
|
|
47
|
+
// (`RedirectPayload`), not a hand-written narrower shape — the old `{ id: string }`
|
|
48
|
+
// literal is what structurally discarded the `addrs` the sender took the trouble to embed.
|
|
49
|
+
const redirectResponse = response as Partial<RedirectPayload>;
|
|
50
|
+
if (redirectResponse?.redirect?.peers?.length) {
|
|
51
|
+
if (hop >= 2) {
|
|
52
|
+
throw new Error('Redirect loop detected in ClusterClient (max hops reached)')
|
|
53
|
+
}
|
|
54
|
+
const currentIdStr = this.peerId.toString()
|
|
55
|
+
const next = redirectResponse.redirect.peers.find((p) => p.id !== currentIdStr) ?? redirectResponse.redirect.peers[0]!
|
|
56
|
+
const nextId = peerIdFromString(next.id)
|
|
57
|
+
if (next.id === currentIdStr) {
|
|
58
|
+
throw new Error('Redirect loop detected in ClusterClient (same peer)')
|
|
59
|
+
}
|
|
60
|
+
// Learn the redirect target's addresses BEFORE dialing it: the redirect is the only
|
|
61
|
+
// notice we get that this peer matters, and if it is relay-only we have no address
|
|
62
|
+
// for it at all. Merging after the dial would help only some later hop.
|
|
63
|
+
this.peerNetwork.recordPeerAddresses?.(nextId, next.addrs ?? [])
|
|
64
|
+
this.recordCoordinatorForRecordIfSupported(record, nextId)
|
|
65
|
+
const nextClient = ClusterClient.create(nextId, this.peerNetwork, this.protocolPrefix)
|
|
66
|
+
// Thread the caller's *original* options through the redirect hop (the
|
|
67
|
+
// recursive call re-applies its own defaults) so the deadline survives a redirect.
|
|
68
|
+
return await nextClient.update(record, hop + 1, options)
|
|
69
|
+
}
|
|
70
|
+
const updated = response as ClusterRecord;
|
|
71
|
+
// The member's reply carries the cohort's peer map too, and it may name addresses this
|
|
72
|
+
// node lacks — the coordinator's own half of the same exchange the service handles inbound.
|
|
73
|
+
this.recordRecordPeerAddresses(updated)
|
|
74
|
+
return updated;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Offer every address a cluster record carries for its cohort to the dialer's address book.
|
|
79
|
+
*
|
|
80
|
+
* Shares its traversal — and its cap on how many peers one record may introduce — with the
|
|
81
|
+
* inbound side (`ClusterService.learnPeerAddresses`); validation, the per-peer cap, and the
|
|
82
|
+
* trust boundary live in the implementation behind `recordPeerAddresses`. No self-filter is
|
|
83
|
+
* passed: `this.peerId` is the peer we DIALED, not this node, and only the sink knows the
|
|
84
|
+
* local node's identity.
|
|
85
|
+
*/
|
|
86
|
+
private recordRecordPeerAddresses(record: ClusterRecord): void {
|
|
87
|
+
const sink = this.peerNetwork.recordPeerAddresses
|
|
88
|
+
if (!sink) return
|
|
89
|
+
mergeRecordPeerAddresses(record?.peers, (pid, addrs) => sink.call(this.peerNetwork, pid, addrs), log)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Record a coordinator-affinity hint for the block this record's op is
|
|
94
|
+
* coordinated on, so a follow-up op can dial `peerId` directly.
|
|
95
|
+
*
|
|
96
|
+
* The op lives at `record.message.operations[0]` — `record.message` is a
|
|
97
|
+
* RepoMessage `{ operations: [...] }`, NOT a bare `{ commit }`/`{ pend }`.
|
|
98
|
+
* Reading `record.message.commit`/`.pend` (the old code) always saw
|
|
99
|
+
* `undefined`, so this hint was dead code and never recorded anything.
|
|
100
|
+
*
|
|
101
|
+
* Commit anchors on blockIds[0] (where CoordinatorRepo runs consensus +
|
|
102
|
+
* verifyResponsibility), not tailId. Pend anchors on a real block id
|
|
103
|
+
* (blockIdsForTransforms), not a structural transforms field name.
|
|
104
|
+
* ClusterClient only ever carries commit/pend.
|
|
105
|
+
*/
|
|
106
|
+
private recordCoordinatorForRecordIfSupported(record: ClusterRecord, peerId: PeerId): void {
|
|
107
|
+
const op = record.message.operations[0]
|
|
108
|
+
if (!op) return
|
|
109
|
+
let id: string | undefined
|
|
110
|
+
if ('commit' in op) id = op.commit.blockIds[0]
|
|
111
|
+
else if ('pend' in op) id = blockIdsForTransforms(op.pend.transforms)[0]
|
|
112
|
+
if (id == null) return
|
|
113
|
+
this.recordCoordinatorHint(id, peerId)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -150,7 +150,8 @@ export interface ClusterPolicyOptions {
|
|
|
150
150
|
repairCorroborationClusterSize?: number;
|
|
151
151
|
/**
|
|
152
152
|
* What a validator-configured member does with a pend that carries no `validation` payload
|
|
153
|
-
* (nothing to re-execute —
|
|
153
|
+
* (nothing to re-execute — a single-collection `Collection.sync` pend, or the Quereus
|
|
154
|
+
* adapter's legacy multi-tree commit, which pends through an unvalidated coordinator). Default 'accept';
|
|
154
155
|
* see {@link UnvalidatablePendPolicy}.
|
|
155
156
|
*/
|
|
156
157
|
unvalidatablePendPolicy?: UnvalidatablePendPolicy;
|