@optimystic/db-p2p 1.0.0-beta.3 → 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 +126 -6
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +378 -50
- 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 +77 -5
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +131 -20
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +238 -122
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +625 -472
- 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 +422 -56
- 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 +145 -19
- package/src/repo/coordinator-repo.ts +652 -505
- 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/libp2p-node-base.ts
CHANGED
|
@@ -18,10 +18,13 @@ import { BlockStorage } from './storage/block-storage.js';
|
|
|
18
18
|
import { MemoryRawStorage } from './storage/memory-storage.js';
|
|
19
19
|
import { withReadCache, type ResolvedReadCache } from './storage/with-read-cache.js';
|
|
20
20
|
import type { IRawStorage } from './storage/i-raw-storage.js';
|
|
21
|
+
import type { IKVStore } from './storage/i-kv-store.js';
|
|
22
|
+
import { MemoryKVStore } from './storage/memory-kv-store.js';
|
|
23
|
+
import { KvUnderReplicationLedger } from './repo/kv-under-replication-ledger.js';
|
|
21
24
|
import { latestClaimFromArchive, servableProof, type ArchiveServingRepo } from './storage/block-archive.js';
|
|
22
25
|
import { createServedRepoProxy } from './repo/served-repo-proxy.js';
|
|
23
26
|
import { seedOwnedBlocksFromStorage } from './owned-block-seed.js';
|
|
24
|
-
import { clusterMember, type ReconcileBlockCallback, type CommitCertificateSink, type DeriveExpectedClusterCallback } from './cluster/cluster-repo.js';
|
|
27
|
+
import { clusterMember, type ReconcileBlockCallback, type CommitCertificateSink, type CommittedHoldersSink, type DeriveExpectedClusterCallback } from './cluster/cluster-repo.js';
|
|
25
28
|
import { createReconcileBlock } from './cluster/reconcile-block.js';
|
|
26
29
|
import { resolveClusterPolicy, type ClusterPolicyOptions } from './cluster/cluster-policy.js';
|
|
27
30
|
import { assertClusterSizeCoupling } from './cluster/cluster-size-coupling.js';
|
|
@@ -34,8 +37,11 @@ import { ClusterClient } from './cluster/client.js';
|
|
|
34
37
|
import type { IRepo, ICluster, ITransactionValidator, BlockId, IBlockChangeNotifier } from '@optimystic/db-core';
|
|
35
38
|
import type { ITransactionStateStore } from './cluster/i-transaction-state-store.js';
|
|
36
39
|
import { networkManagerService, type NetworkManagerService } from './network/network-manager-service.js';
|
|
40
|
+
import { assertCircuitRelayTransport, assertRelayAddrsAdvertisable, planRelayListenAddrs, superviseRelayReservations } from './network/relay-reservation.js';
|
|
37
41
|
import type { SpreadOnChurnConfig, SpreadOnChurnMonitor } from './cluster/spread-on-churn.js';
|
|
38
42
|
import { BlockTransferCoordinator } from './cluster/block-transfer.js';
|
|
43
|
+
import { pushBlockToPeers } from './cluster/block-transfer-service.js';
|
|
44
|
+
import { UnderReplicationDrain, type UnderReplicationDrainConfig } from './repo/under-replication-drain.js';
|
|
39
45
|
import type { RebalanceMonitorConfig } from './cluster/rebalance-monitor.js';
|
|
40
46
|
import { fretService, Libp2pFretService } from 'p2p-fret';
|
|
41
47
|
import { syncService } from './sync/service.js';
|
|
@@ -85,6 +91,7 @@ import {
|
|
|
85
91
|
type PushStateInit,
|
|
86
92
|
type NotificationVerifier,
|
|
87
93
|
} from '@optimystic/db-core';
|
|
94
|
+
import { routingKeyForBlock } from '@optimystic/db-core';
|
|
88
95
|
import { PartitionDetector } from './cluster/partition-detector.js';
|
|
89
96
|
import { assertSuperMajorityCoupling } from './cluster/supermajority-coupling.js';
|
|
90
97
|
import { createLogger } from './logger.js';
|
|
@@ -154,6 +161,9 @@ const wiringLog = createLogger('node-wiring');
|
|
|
154
161
|
*/
|
|
155
162
|
export type RawStorageProvider = IRawStorage | (() => IRawStorage);
|
|
156
163
|
|
|
164
|
+
/** Factory function or instance for the node's durable key-value store; see {@link NodeOptions.kvStore}. */
|
|
165
|
+
export type KVStoreProvider = IKVStore | (() => IKVStore);
|
|
166
|
+
|
|
157
167
|
/**
|
|
158
168
|
* `ClusterPolicyOptions` is intersected in, not restated: `resolveClusterPolicy` consumes those
|
|
159
169
|
* fields structurally, so a second copy of the shape here would let a newly added knob compile and
|
|
@@ -198,7 +208,22 @@ export type NodeOptions = ClusterPolicyOptions & {
|
|
|
198
208
|
relayServerInit?: CircuitRelayServerInit;
|
|
199
209
|
/** Storage provider - either an IRawStorage instance or a factory function. Defaults to MemoryRawStorage if not provided. See {@link RawStorageProvider} for the ownership rule. */
|
|
200
210
|
storage?: RawStorageProvider;
|
|
201
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Durable key-value store for node-local state that must survive a restart — today, the
|
|
213
|
+
* under-replication ledger (which blocks this node acknowledged below full replication, and who
|
|
214
|
+
* is still missing each). An instance or a factory, the same shape as {@link NodeOptions.storage}.
|
|
215
|
+
* Absent → an in-memory store, with a `node-wiring` warning: the ledger still works, but what it
|
|
216
|
+
* records is lost on restart. `FileKVStore` from `@optimystic/db-p2p-storage-fs` may share
|
|
217
|
+
* `FileRawStorage`'s base path; see its README.
|
|
218
|
+
*/
|
|
219
|
+
kvStore?: KVStoreProvider;
|
|
220
|
+
/**
|
|
221
|
+
* Override libp2p listen multiaddrs. An entry naming a relay (`<relay address>/p2p/<relay id>/p2p-circuit`)
|
|
222
|
+
* is not handed to libp2p as written: the node listens on a bare `/p2p-circuit` and keeps the
|
|
223
|
+
* reservation on that relay itself (`superviseRelayReservation` in `network/relay-reservation.ts`).
|
|
224
|
+
* Such an entry needs a circuit-relay transport and cannot be combined with {@link NodeOptions.announceAddrs},
|
|
225
|
+
* which would hide the circuit address; both are rejected before anything starts.
|
|
226
|
+
*/
|
|
202
227
|
listenAddrs?: string[];
|
|
203
228
|
/**
|
|
204
229
|
* Multiaddrs to advertise INSTEAD OF the listen addrs. For a node behind a NAT / reverse proxy /
|
|
@@ -253,6 +278,18 @@ export type NodeOptions = ClusterPolicyOptions & {
|
|
|
253
278
|
*/
|
|
254
279
|
rebalance?: Partial<RebalanceMonitorConfig> & { enabled?: boolean };
|
|
255
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Under-replication drain tuning. The drain sends the copies the under-replication ledger says
|
|
283
|
+
* this node still owes — blocks it acknowledged below full replication — to the missing members
|
|
284
|
+
* as they become reachable, and fires `onBlockDurabilityReached` on the node's `storageRepo` when
|
|
285
|
+
* a block finally has every copy. It needs only the key network and the node's own storage, so it
|
|
286
|
+
* runs whether or not arachnode/FRET rebalancing is enabled. Absent -> enabled with defaults (see
|
|
287
|
+
* `UnderReplicationDrainConfig`). Set { enabled: false } to leave the ledger unsent on this node.
|
|
288
|
+
* The push deadlines are separate because they bound the wire, not the pass: `pushDialTimeoutMs`
|
|
289
|
+
* (default 3000) and `pushResponseTimeoutMs` (default 10000), the same caps spread-on-churn uses.
|
|
290
|
+
*/
|
|
291
|
+
underReplicationDrain?: Partial<UnderReplicationDrainConfig> & { enabled?: boolean; pushDialTimeoutMs?: number; pushResponseTimeoutMs?: number };
|
|
292
|
+
|
|
256
293
|
/** Transaction validator for cluster consensus */
|
|
257
294
|
validator?: ITransactionValidator;
|
|
258
295
|
|
|
@@ -394,6 +431,24 @@ function resolveStorage(provider: RawStorageProvider | undefined, networkName: s
|
|
|
394
431
|
return withReadCache(storage, `node:${networkName}`);
|
|
395
432
|
}
|
|
396
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Resolve the node's durable key-value store, beside {@link resolveStorage}. The memory fallback is
|
|
436
|
+
* LOUD on purpose: a silent one would look like the under-replication ledger works and then fail the
|
|
437
|
+
* one case it exists for — a shortfall recorded before a restart being there after it.
|
|
438
|
+
*
|
|
439
|
+
* NOTE: with a key-value store always present, `PersistentTransactionStateStore` is now
|
|
440
|
+
* constructible here from the same instance. Wiring it changes commit-retry recovery behaviour and
|
|
441
|
+
* belongs to `feat-long-lived-pend-completes-as-members-appear`, not to this resolver;
|
|
442
|
+
* `NodeOptions.transactionStateStore` stays the only way it is supplied.
|
|
443
|
+
*/
|
|
444
|
+
function resolveKvStore(provider: KVStoreProvider | undefined): IKVStore {
|
|
445
|
+
if (provider !== undefined) {
|
|
446
|
+
return typeof provider === 'function' ? provider() : provider;
|
|
447
|
+
}
|
|
448
|
+
wiringLog('WARN: no kvStore supplied — using an in-memory store. The under-replication ledger (blocks this node acknowledged before every cohort member held them) will NOT survive a restart, so copies owed at shutdown are never sent. Supply NodeOptions.kvStore (e.g. FileKVStore) to keep it.');
|
|
449
|
+
return new MemoryKVStore();
|
|
450
|
+
}
|
|
451
|
+
|
|
397
452
|
/**
|
|
398
453
|
* Resolve the full FRET engine the cohort-topic host needs.
|
|
399
454
|
*
|
|
@@ -435,6 +490,8 @@ export async function createLibp2pNodeBase(
|
|
|
435
490
|
}
|
|
436
491
|
): Promise<OptimysticNode> {
|
|
437
492
|
const { storage: rawStorage, lease } = resolveStorage(options.storage, options.networkName);
|
|
493
|
+
const kvStore = resolveKvStore(options.kvStore);
|
|
494
|
+
const underReplicationLedger = new KvUnderReplicationLedger(kvStore);
|
|
438
495
|
|
|
439
496
|
// Create placeholder restore callback (will be replaced after node starts)
|
|
440
497
|
let restoreCallback: RestoreCallback = async (_blockId, _rev?) => {
|
|
@@ -487,7 +544,26 @@ export async function createLibp2pNodeBase(
|
|
|
487
544
|
|
|
488
545
|
const nodePrivateKey = options.privateKey ?? await generateKeyPair('Ed25519');
|
|
489
546
|
|
|
490
|
-
|
|
547
|
+
// A listen address naming a relay (`<relay>/p2p/<id>/p2p-circuit`, the only shape a phone or browser
|
|
548
|
+
// can take) is handed to libp2p as a bare `/p2p-circuit` and the relay is supervised by this node
|
|
549
|
+
// instead, so the reservation survives relay restarts, dropped relay connections and libp2p's own
|
|
550
|
+
// renewal. See `network/relay-reservation.ts` for why the configured shape cannot recover.
|
|
551
|
+
// NOTE: accepted tradeoff — a bare `/p2p-circuit` listener also turns on libp2p's relay discovery,
|
|
552
|
+
// whose topology handler can reserve on ANY connected peer serving the relay hop protocol (every
|
|
553
|
+
// Optimystic node started with `relay: true` does), and that reservation takes the pending slot
|
|
554
|
+
// meant for the named relay. The node then stays reachable, but through that other relay, so a
|
|
555
|
+
// partner that enrolled its circuit address through the named relay cannot dial it until that
|
|
556
|
+
// reservation drops; the supervisor reports it as `relay-reservation:slot-taken` and retries at its
|
|
557
|
+
// backoff cap. It happens only when a relay-only node is directly connected to a second relay server,
|
|
558
|
+
// and Sereus runs with the same behaviour. Revisit on a deployment that connects relay-only nodes to
|
|
559
|
+
// more than one relay server, or on the libp2p upgrade below.
|
|
560
|
+
// NOTE: circuit-relay-v2 >= 4.2 (needs @libp2p/interface ^3.3) refreshes reservations in place and
|
|
561
|
+
// re-applies a configured reservation for its own relay; when the libp2p line moves, revisit keeping
|
|
562
|
+
// the host's configured address and re-requesting with 'configured', which drops the rewrite and the
|
|
563
|
+
// discovery side effect.
|
|
564
|
+
const relayPlan = planRelayListenAddrs(options.listenAddrs ?? defaults.listenAddrs);
|
|
565
|
+
assertRelayAddrsAdvertisable(relayPlan.supervisedRelays, options.announceAddrs);
|
|
566
|
+
const listenAddrs = relayPlan.listenAddrs;
|
|
491
567
|
const transports = options.transports ?? defaults.transports;
|
|
492
568
|
|
|
493
569
|
// --- cohort-topic substrate activation (opt-in; default off → today's bare behavior, zero cost) ---
|
|
@@ -544,13 +620,14 @@ export async function createLibp2pNodeBase(
|
|
|
544
620
|
transports,
|
|
545
621
|
connectionEncrypters: [noise()],
|
|
546
622
|
streamMuxers: [yamux()],
|
|
547
|
-
// Narrow cast confined to the `services` field
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
623
|
+
// Narrow cast confined to the `services` field, so the rest of `libp2pOptions` stays typed as
|
|
624
|
+
// `Libp2pInit`: `@libp2p/dcutr` and `@libp2p/autonat` are typed against `@libp2p/interface` 3.2.x
|
|
625
|
+
// while `libp2p` resolves 3.1.x, and the two minors disagree on `Uint8Array<ArrayBuffer>` vs
|
|
626
|
+
// `<ArrayBufferLike>` — a type-level artifact of the minor split, not a runtime mismatch.
|
|
627
|
+
// NOTE: accepted tradeoff — raising this package's `@libp2p/interface` pin to ^3.2 to drop the cast
|
|
628
|
+
// was declined: the 3.1/3.2 split is deliberate (scripts/shared-majors.cjs), and `libp2p` and its
|
|
629
|
+
// core services resolve 3.1.0 regardless, so the cast would only move. Revisit when that libp2p
|
|
630
|
+
// release line itself declares `@libp2p/interface@^3.2` or later.
|
|
554
631
|
services: ({
|
|
555
632
|
// `@libp2p/identify` is the ONE service here whose protocol id it builds itself:
|
|
556
633
|
// `Identify`/`IdentifyPush` both emit `/${protocolPrefix}/id[/push]/1.0.0`, always
|
|
@@ -643,15 +720,14 @@ export async function createLibp2pNodeBase(
|
|
|
643
720
|
responsibilityK: options.responsibilityK ?? 1,
|
|
644
721
|
...inboundAuthorization
|
|
645
722
|
});
|
|
646
|
-
// RepoService.checkRedirect needs the running node (
|
|
647
|
-
// responsible
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
// coordinator's findCluster(encode(blockId)) — same cohort, no spurious redirect.
|
|
723
|
+
// RepoService.checkRedirect needs the running node (its `keyNetwork` attachment for
|
|
724
|
+
// the responsible set, self id for the membership check, connection addrs for
|
|
725
|
+
// redirect targets). The libp2p components.libp2p proxy does NOT reliably resolve
|
|
726
|
+
// from inside a service at request time, so the node is injected explicitly
|
|
727
|
+
// post-construction via setLibp2p(node) below — the same mechanism networkManager/fret
|
|
728
|
+
// use — rather than forwarded here. checkRedirect asks the node's own key network for
|
|
729
|
+
// the block's routing key (`routingKeyForBlock`), the same call the writer and the
|
|
730
|
+
// coordinator make — one rule, so a correctly routed request is never redirected.
|
|
655
731
|
return serviceFactory({
|
|
656
732
|
registrar: components.registrar,
|
|
657
733
|
repo: repoProxy
|
|
@@ -712,6 +788,10 @@ export async function createLibp2pNodeBase(
|
|
|
712
788
|
capacity: 2048,
|
|
713
789
|
profile: options.fretProfile ?? ((options.bootstrapNodes?.length ?? 0) > 0 ? 'core' : 'edge'),
|
|
714
790
|
networkName: options.networkName,
|
|
791
|
+
// NOTE: FRET's `seedFromBootstraps` seeds the FIRST `/p2p/` id of each entry, so a partner's circuit
|
|
792
|
+
// address (`<relay>/p2p/<relay>/p2p-circuit/p2p/<partner>`) seeds the relay, not the partner. Harmless
|
|
793
|
+
// today: the partner is learned once its connection opens, and the relay is classified foreign. If a
|
|
794
|
+
// phone ever fails to find a partner it was given only as a circuit bootstrap, fix FRET to take the last id.
|
|
715
795
|
bootstraps: options.bootstrapNodes ?? []
|
|
716
796
|
});
|
|
717
797
|
const svc = svcFactory(components) as Libp2pFretService;
|
|
@@ -768,12 +848,18 @@ export async function createLibp2pNodeBase(
|
|
|
768
848
|
const wired = node.services as unknown as WiredServices;
|
|
769
849
|
wired.fret.setLibp2p(node);
|
|
770
850
|
wired.networkManager.setLibp2p(node);
|
|
771
|
-
// RepoService.checkRedirect resolves the network
|
|
851
|
+
// RepoService.checkRedirect resolves the key network / self id / connection
|
|
772
852
|
// addrs through this injected node (the components.libp2p proxy is unreliable
|
|
773
853
|
// from inside a service at request time). Done before start() so the protocol
|
|
774
|
-
// handler is live with a resolvable node from its first request
|
|
854
|
+
// handler is live with a resolvable node from its first request; the `keyNetwork`
|
|
855
|
+
// attachment itself arrives after start(), and until then checkRedirect handles locally.
|
|
775
856
|
wired.repo.setLibp2p(node);
|
|
776
857
|
|
|
858
|
+
// A relay-naming listen address with no circuit-relay transport can never be reserved on. Checked
|
|
859
|
+
// here, before start(), so the caller sees the omission by name rather than libp2p's generic
|
|
860
|
+
// unsupported-listen-address error for the bare `/p2p-circuit` the plan substituted.
|
|
861
|
+
assertCircuitRelayTransport(node, relayPlan.supervisedRelays);
|
|
862
|
+
|
|
777
863
|
await node.start();
|
|
778
864
|
|
|
779
865
|
// Everything from here to the `return` runs against an ALREADY STARTED node (open transports,
|
|
@@ -782,6 +868,24 @@ export async function createLibp2pNodeBase(
|
|
|
782
868
|
// for the caller and enough to block the port for the next start attempt. So the whole post-start
|
|
783
869
|
// body rolls back: see the `catch` at the bottom of this function.
|
|
784
870
|
try {
|
|
871
|
+
// Keep every relay-naming listen address's reservation alive (see `relayPlan` above). Started
|
|
872
|
+
// first, with its stop wrapper installed before anything is awaited, so the rollback `catch`
|
|
873
|
+
// below stops it too. Awaiting the first drives keeps today's startup contract: a relay that
|
|
874
|
+
// cannot be reserved rejects node creation, naming the relay, exactly as libp2p's own
|
|
875
|
+
// `listen()` on the configured shape did. A host that would rather start offline and let the
|
|
876
|
+
// supervisor recover is a separate decision, not an option here.
|
|
877
|
+
if (relayPlan.supervisedRelays.length > 0) {
|
|
878
|
+
const relaySupervisors = superviseRelayReservations(node, relayPlan.supervisedRelays);
|
|
879
|
+
const previousStop = node.stop.bind(node);
|
|
880
|
+
node.stop = async () => {
|
|
881
|
+
try {
|
|
882
|
+
relaySupervisors.stop();
|
|
883
|
+
} finally {
|
|
884
|
+
await previousStop();
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
await relaySupervisors.awaitFirstDrives();
|
|
888
|
+
}
|
|
785
889
|
|
|
786
890
|
// Initialize peer reputation service
|
|
787
891
|
const reputation = new PeerReputationService();
|
|
@@ -867,7 +971,7 @@ export async function createLibp2pNodeBase(
|
|
|
867
971
|
// voting, so a self-shrunk minority-partition set cannot be voted into super-majority (see cluster-repo
|
|
868
972
|
// admitMembership). No FRET ⇒ confidence 0 ⇒ the gate fails closed for any downsize.
|
|
869
973
|
const deriveExpectedCluster: DeriveExpectedClusterCallback = async (blockId) => {
|
|
870
|
-
const peers = await keyNetwork.findCluster(
|
|
974
|
+
const peers = await keyNetwork.findCluster(routingKeyForBlock(blockId));
|
|
871
975
|
let confidence = 0;
|
|
872
976
|
if (fretSvc) {
|
|
873
977
|
try {
|
|
@@ -879,6 +983,13 @@ export async function createLibp2pNodeBase(
|
|
|
879
983
|
return { peers: peers ?? {}, confidence };
|
|
880
984
|
};
|
|
881
985
|
|
|
986
|
+
// Who holds each commit this node holds, for the rebalance monitor (so it does not push freshly
|
|
987
|
+
// committed blocks back to the members that stored them). Late-bound: the member and coordinator
|
|
988
|
+
// are built here, the monitor much further down inside the arachnode gate — and not at all when
|
|
989
|
+
// rebalance or FRET is off, in which case reports go nowhere.
|
|
990
|
+
let committedHoldersTarget: CommittedHoldersSink | undefined;
|
|
991
|
+
const onCommittedHolders: CommittedHoldersSink = (committed) => committedHoldersTarget?.(committed);
|
|
992
|
+
|
|
882
993
|
clusterImpl = clusterMember({
|
|
883
994
|
storageRepo,
|
|
884
995
|
peerNetwork: keyNetwork,
|
|
@@ -893,6 +1004,7 @@ export async function createLibp2pNodeBase(
|
|
|
893
1004
|
stateStore: options.transactionStateStore,
|
|
894
1005
|
reconcileBlock,
|
|
895
1006
|
onCommitCertificate,
|
|
1007
|
+
onCommittedHolders,
|
|
896
1008
|
deriveExpectedCluster
|
|
897
1009
|
// `recomputeArbitratorSet` (invalidation layer-2) is intentionally NOT wired here yet: a live FRET
|
|
898
1010
|
// recompute needs a churn-tolerance window so it does not false-reject legitimate certificates from
|
|
@@ -993,7 +1105,11 @@ export async function createLibp2pNodeBase(
|
|
|
993
1105
|
// funnel. `clusterLatestCallback` alone can only tell the reader WHICH revision the cohort
|
|
994
1106
|
// holds; this is what moves the bytes. Only reached once a corroborated revision exists, so a
|
|
995
1107
|
// genuinely absent block still costs no archive fetch.
|
|
996
|
-
acquireBlockFromCohort: reconcileBlock
|
|
1108
|
+
acquireBlockFromCohort: reconcileBlock,
|
|
1109
|
+
// Records who is still missing each block of a commit acknowledged below `full`. Nothing
|
|
1110
|
+
// drains it yet: sending those copies is `under-replication-drain-and-full-replication-event`.
|
|
1111
|
+
underReplicationLedger,
|
|
1112
|
+
onCommittedHolders
|
|
997
1113
|
});
|
|
998
1114
|
|
|
999
1115
|
// Fail-fast coupling: the cluster member (what accepts a super-majority as sufficient) and the
|
|
@@ -1102,6 +1218,47 @@ export async function createLibp2pNodeBase(
|
|
|
1102
1218
|
};
|
|
1103
1219
|
}
|
|
1104
1220
|
|
|
1221
|
+
// --- Under-replication drain: send the copies the ledger says this node still owes ---
|
|
1222
|
+
// The coordinator repo above records, per block it acknowledged below `full`, who is still
|
|
1223
|
+
// missing a copy; this pushes those copies as the missing members become reachable and fires
|
|
1224
|
+
// the full-replication event on storageRepo when a block has every copy. Deliberately OUTSIDE
|
|
1225
|
+
// the arachnode `if (fret)` gate below: it needs the key network (cohort re-resolution for an
|
|
1226
|
+
// entry that could not name its members), the node's own store and dialer, and libp2p's
|
|
1227
|
+
// connection events — not the fretAdapter or the restoration coordinator. It overlaps the
|
|
1228
|
+
// rebalance growth arm and spread-on-churn on purpose: all three may push the same block to
|
|
1229
|
+
// the same peer, and the receiver is idempotent, so no cross-monitor coordination is added.
|
|
1230
|
+
// The sink it emits through is one method wide, so it can reach nothing else on the repo.
|
|
1231
|
+
let underReplicationDrain: UnderReplicationDrain | undefined;
|
|
1232
|
+
if ((options.underReplicationDrain?.enabled ?? true) !== false) {
|
|
1233
|
+
const { enabled: _enabled, pushDialTimeoutMs, pushResponseTimeoutMs, ...drainConfig } = options.underReplicationDrain ?? {};
|
|
1234
|
+
underReplicationDrain = new UnderReplicationDrain({
|
|
1235
|
+
libp2p: node,
|
|
1236
|
+
ledger: underReplicationLedger,
|
|
1237
|
+
keyNetwork,
|
|
1238
|
+
partitionDetector,
|
|
1239
|
+
protocolPrefix,
|
|
1240
|
+
pushBlock: (blockId, peerIds) => pushBlockToPeers(storageRepo, keyNetwork, blockId, peerIds, {
|
|
1241
|
+
reason: 'replication',
|
|
1242
|
+
protocolPrefix,
|
|
1243
|
+
dialTimeoutMs: pushDialTimeoutMs ?? 3000,
|
|
1244
|
+
responseTimeoutMs: pushResponseTimeoutMs ?? 10_000
|
|
1245
|
+
}),
|
|
1246
|
+
emit: (event) => storageRepo.emitBlockDurabilityReached(event)
|
|
1247
|
+
}, drainConfig);
|
|
1248
|
+
await underReplicationDrain.start();
|
|
1249
|
+
const previousStop = node.stop.bind(node);
|
|
1250
|
+
node.stop = async () => {
|
|
1251
|
+
try {
|
|
1252
|
+
await underReplicationDrain!.stop();
|
|
1253
|
+
} finally {
|
|
1254
|
+
await previousStop();
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
// Expose for tests/diagnostics (mirrors node.spreadOnChurnMonitor / node.rebalanceMonitor).
|
|
1259
|
+
(node as any).underReplicationDrain = underReplicationDrain;
|
|
1260
|
+
(node as any).underReplicationLedger = underReplicationLedger;
|
|
1261
|
+
|
|
1105
1262
|
// Initialize Arachnode ring membership and restoration
|
|
1106
1263
|
const enableArachnode = options.arachnode?.enableRingZulu ?? true;
|
|
1107
1264
|
if (enableArachnode) {
|
|
@@ -1203,9 +1360,11 @@ export async function createLibp2pNodeBase(
|
|
|
1203
1360
|
partitionDetector,
|
|
1204
1361
|
fretAdapter,
|
|
1205
1362
|
ownedBlocks,
|
|
1363
|
+
keyNetwork,
|
|
1206
1364
|
options.rebalance,
|
|
1207
1365
|
);
|
|
1208
1366
|
await rebalanceMonitor.start();
|
|
1367
|
+
committedHoldersTarget = (committed) => rebalanceMonitor.recordCommittedHolders(committed);
|
|
1209
1368
|
|
|
1210
1369
|
// onRebalance fires synchronously from the monitor's debounced check; the coordinator's
|
|
1211
1370
|
// reaction (pull gained / push lost, each partition-guarded) is async, so hop it off the
|
|
@@ -1648,8 +1807,8 @@ export async function createLibp2pNodeBase(
|
|
|
1648
1807
|
return undefined; // tail-less (read-driven promotion) never originates (the gate also returns first)
|
|
1649
1808
|
}
|
|
1650
1809
|
return {
|
|
1651
|
-
// MUST reuse the gate's `reactivityTailBytes` (utf8
|
|
1652
|
-
//
|
|
1810
|
+
// MUST reuse the gate's `reactivityTailBytes` (raw utf8, the tail's routing key), never
|
|
1811
|
+
// a pre-hashed digest — else origination derives a different coord than subscribers resolve.
|
|
1653
1812
|
tailId: reactivityTailBytes(event.tailId),
|
|
1654
1813
|
deltaMaxBytes: reactivityPolicy.deltaMaxBytes,
|
|
1655
1814
|
// rotationHint stays undefined on a live node: the successor tail id is not knowable at the
|
|
@@ -1720,8 +1879,8 @@ export async function createLibp2pNodeBase(
|
|
|
1720
1879
|
});
|
|
1721
1880
|
|
|
1722
1881
|
// Inbound serve handler: decode (bounded) → verify the dialing peer's signature → freshness/replay gate →
|
|
1723
|
-
// resolve the live PushState off the forwarder host → serveBackfill/serveResume → reply (
|
|
1724
|
-
// failure; the
|
|
1882
|
+
// resolve the live PushState off the forwarder host → serveBackfill/serveResume → reply (a decline on any
|
|
1883
|
+
// failure; the subscriber's transport tries the next cohort member). One node-level replay guard is shared
|
|
1725
1884
|
// across all recover requests — a plain pruned-on-access map, so no new timer to tear down.
|
|
1726
1885
|
registerRecoverHandler(node, reactivityProtocols.recover, {
|
|
1727
1886
|
pushStateFor: forwarderHost.pushStateFor.bind(forwarderHost),
|
package/src/libp2p-node-rn.ts
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createLibp2pNodeBase,
|
|
3
|
-
type Libp2pTransports,
|
|
4
|
-
type NodeOptions,
|
|
5
|
-
type RawStorageProvider,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
createLibp2pNodeBase,
|
|
3
|
+
type Libp2pTransports,
|
|
4
|
+
type NodeOptions,
|
|
5
|
+
type RawStorageProvider,
|
|
6
|
+
type KVStoreProvider,
|
|
7
|
+
} from './libp2p-node-base.js';
|
|
8
|
+
import type { OptimysticNode } from './optimystic-node.js';
|
|
9
|
+
|
|
10
|
+
export type { Libp2pTransports, NodeOptions, RawStorageProvider, KVStoreProvider };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* React Native-friendly libp2p node factory.
|
|
14
|
+
*
|
|
15
|
+
* This entrypoint intentionally does not import Node-only transports (like `@libp2p/tcp`).
|
|
16
|
+
* Callers must provide `options.transports` (and typically `options.listenAddrs`).
|
|
17
|
+
*/
|
|
18
|
+
export async function createLibp2pNode(options: NodeOptions): Promise<OptimysticNode> {
|
|
19
|
+
const transports = options.transports;
|
|
20
|
+
if (!transports || transports.length === 0) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'createLibp2pNode (RN) requires options.transports. ' +
|
|
23
|
+
'Provide an RN-compatible transport (e.g. WebSockets) and any required listenAddrs.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return await createLibp2pNodeBase(options, {
|
|
28
|
+
listenAddrs: [],
|
|
29
|
+
transports,
|
|
30
|
+
});
|
|
31
|
+
}
|
package/src/libp2p-node.ts
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
|
-
import { tcp } from '@libp2p/tcp';
|
|
2
|
-
import { webSockets } from '@libp2p/websockets';
|
|
3
|
-
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
|
|
4
|
-
import {
|
|
5
|
-
createLibp2pNodeBase,
|
|
6
|
-
type Libp2pTransports,
|
|
7
|
-
type NodeOptions,
|
|
8
|
-
type RawStorageProvider,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
1
|
+
import { tcp } from '@libp2p/tcp';
|
|
2
|
+
import { webSockets } from '@libp2p/websockets';
|
|
3
|
+
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
|
|
4
|
+
import {
|
|
5
|
+
createLibp2pNodeBase,
|
|
6
|
+
type Libp2pTransports,
|
|
7
|
+
type NodeOptions,
|
|
8
|
+
type RawStorageProvider,
|
|
9
|
+
type KVStoreProvider,
|
|
10
|
+
} from './libp2p-node-base.js';
|
|
11
|
+
import type { OptimysticNode } from './optimystic-node.js';
|
|
12
|
+
|
|
13
|
+
export type { Libp2pTransports, NodeOptions, RawStorageProvider, KVStoreProvider };
|
|
14
|
+
|
|
15
|
+
export async function createLibp2pNode(options: NodeOptions): Promise<OptimysticNode> {
|
|
16
|
+
const port = options.port ?? 0;
|
|
17
|
+
const wsHost = options.wsHost ?? '0.0.0.0';
|
|
18
|
+
|
|
19
|
+
const defaultTransports: Libp2pTransports = [];
|
|
20
|
+
const defaultListenAddrs: string[] = [];
|
|
21
|
+
|
|
22
|
+
if (!options.disableTcp) {
|
|
23
|
+
defaultTransports.push(tcp());
|
|
24
|
+
defaultListenAddrs.push(`/ip4/0.0.0.0/tcp/${port}`);
|
|
25
|
+
}
|
|
26
|
+
if (options.wsPort !== undefined) {
|
|
27
|
+
defaultTransports.push(webSockets());
|
|
28
|
+
defaultListenAddrs.push(`/ip4/${wsHost}/tcp/${options.wsPort}/ws`);
|
|
29
|
+
}
|
|
30
|
+
// Always include the relay transport so this node can dial through relays
|
|
31
|
+
defaultTransports.push(circuitRelayTransport());
|
|
32
|
+
|
|
33
|
+
return await createLibp2pNodeBase(options, {
|
|
34
|
+
listenAddrs: defaultListenAddrs,
|
|
35
|
+
transports: defaultTransports,
|
|
36
|
+
});
|
|
37
|
+
}
|
package/src/logger.ts
CHANGED
|
@@ -113,7 +113,12 @@ function formatError(v: Error, indent = ''): string {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
function isAggregateError(err?: any): err is AggregateError {
|
|
116
|
-
|
|
116
|
+
// `typeof` guards the bare identifier: if Hermes (React Native's JS engine) doesn't
|
|
117
|
+
// define a global `AggregateError`, `err instanceof AggregateError` would throw a
|
|
118
|
+
// ReferenceError right here on the error-formatting path — exactly where a thrown
|
|
119
|
+
// exception would otherwise go unreported. `typeof` never throws for an undeclared
|
|
120
|
+
// global, so this falls straight through to the duck-typed check either way.
|
|
121
|
+
return (typeof AggregateError !== 'undefined' && err instanceof AggregateError) || (err?.name === 'AggregateError' && Array.isArray(err.errors))
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
function printError(err: Error, indent = ''): string {
|
|
@@ -28,10 +28,9 @@
|
|
|
28
28
|
* milestone); a *serving* tier-`d ≥ 1` query is gated on the promotion follow-ons, exactly as the seeker
|
|
29
29
|
* walk is mock-tier-tagged-unimplemented. So this resolves the tier-0 engine for `coord_0(topicId)` only.
|
|
30
30
|
*
|
|
31
|
-
* **No-engine = no
|
|
32
|
-
* **no reply
|
|
33
|
-
* a DoS amplifier). The seeker maps a no-
|
|
34
|
-
* that mapping).
|
|
31
|
+
* **No-engine = no result (anti-DoS).** A query for a topic this node holds no serving engine for gets
|
|
32
|
+
* **no result** (a zero-length reply) — the handler never instantiates a `CoordEngine` from an inbound query
|
|
33
|
+
* (that would be a DoS amplifier). The seeker maps a no-result to a benign empty advisory result.
|
|
35
34
|
*
|
|
36
35
|
* **Per-coord scoping.** The reply is built from exactly the `coord_0(topicId)` engine's store, nothing
|
|
37
36
|
* cross-coord — matching how the register handler recomputes a served coord per frame. The serve side does
|
|
@@ -79,7 +78,7 @@ import type { CoordRegistry } from "../cohort-topic/host.js";
|
|
|
79
78
|
import { peerIdToBytes } from "../cohort-topic/peer-codec.js";
|
|
80
79
|
import { signPeer, verifyPeerSig } from "../cohort-topic/peer-sig.js";
|
|
81
80
|
import { FretSizeEstimator } from "../cohort-topic/size-estimator.js";
|
|
82
|
-
import { handleRequestResponse, requestResponse, DEFAULT_STREAM_MAX_BYTES } from "../cohort-topic/stream-util.js";
|
|
81
|
+
import { handleRequestResponse, requestResponse, requireReply, DEFAULT_STREAM_MAX_BYTES } from "../cohort-topic/stream-util.js";
|
|
83
82
|
import { DEFAULT_COHORT_TOPIC_PROTOCOLS } from "../cohort-topic/protocols.js";
|
|
84
83
|
import { handleMatchmakingQuery } from "./query-handler.js";
|
|
85
84
|
import { PROTOCOL_MATCHMAKING_QUERY, DEFAULT_MATCHMAKING_PROTOCOLS } from "./protocols.js";
|
|
@@ -339,9 +338,9 @@ export function createLibp2pMatchmakingTransport(deps: Libp2pMatchmakingTranspor
|
|
|
339
338
|
}
|
|
340
339
|
try {
|
|
341
340
|
const frame = await requestResponse(node, peerIdFromString(primary), queryProtocol, encodeQueryV1(q, maxBytes), maxBytes);
|
|
342
|
-
// The serve handler
|
|
343
|
-
// failure) to a benign empty reply so SeekerWalkClient.collect keeps walking rather than throwing.
|
|
344
|
-
return frame
|
|
341
|
+
// The serve handler has no result for a topic it does not serve (`undefined`); map it (and any
|
|
342
|
+
// decode/dial failure) to a benign empty reply so SeekerWalkClient.collect keeps walking rather than throwing.
|
|
343
|
+
return frame === undefined ? emptyReply() : decodeQueryReplyV1(frame, maxBytes);
|
|
345
344
|
} catch (err) {
|
|
346
345
|
log("matchmaking query: dial/decode failed for primary %s (empty reply): %o", primary, err);
|
|
347
346
|
return emptyReply();
|
|
@@ -390,7 +389,8 @@ export function createLibp2pMatchmakingTransport(deps: Libp2pMatchmakingTranspor
|
|
|
390
389
|
throw new Error("matchmaking register: FRET routed the cohort primary to self; provide deps.selfServe.register");
|
|
391
390
|
}
|
|
392
391
|
const frame = await requestResponse(node, peerIdFromString(primary), registerProtocol, encodeCohortMessage(reg, maxBytes), maxBytes);
|
|
393
|
-
|
|
392
|
+
// The `/register` responder always writes a frame, so a no-result reply is a non-conforming peer: reject.
|
|
393
|
+
return validateRegisterReplyV1(decodeCohortMessage(requireReply(frame, "matchmaking register"), maxBytes));
|
|
394
394
|
};
|
|
395
395
|
|
|
396
396
|
/** Map a `RegisterReplyV1` to the walk's {@link SeekerProbeReply} (pass `result`; copy traffic/targetTier). */
|