@optimystic/db-p2p 0.13.5 → 0.14.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/README.md +6 -6
- package/dist/src/cluster/block-transfer-service.d.ts +67 -8
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +101 -62
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/client.d.ts +18 -1
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +55 -39
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-error.d.ts +38 -0
- package/dist/src/cluster/cluster-error.d.ts.map +1 -0
- package/dist/src/cluster/cluster-error.js +51 -0
- package/dist/src/cluster/cluster-error.js.map +1 -0
- package/dist/src/cluster/cluster-repo.d.ts +135 -3
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +333 -58
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-cert.d.ts +63 -0
- package/dist/src/cluster/commit-cert.d.ts.map +1 -0
- package/dist/src/cluster/commit-cert.js +108 -0
- package/dist/src/cluster/commit-cert.js.map +1 -0
- package/dist/src/cluster/service.d.ts +46 -11
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +97 -33
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts +13 -0
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +34 -4
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts +59 -0
- package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts.map +1 -0
- package/dist/src/cohort-topic/bootstrap-evidence-builder.js +79 -0
- package/dist/src/cohort-topic/bootstrap-evidence-builder.js.map +1 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts +81 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts.map +1 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js +93 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js.map +1 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts +108 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts.map +1 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.js +108 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.js.map +1 -0
- package/dist/src/cohort-topic/change-bridge.d.ts +65 -0
- package/dist/src/cohort-topic/change-bridge.d.ts.map +1 -0
- package/dist/src/cohort-topic/change-bridge.js +76 -0
- package/dist/src/cohort-topic/change-bridge.js.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.d.ts +74 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.d.ts.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.js +87 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.js.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.d.ts +42 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.d.ts.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.js +57 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.js.map +1 -0
- package/dist/src/cohort-topic/fret-trust-anchor.d.ts +100 -0
- package/dist/src/cohort-topic/fret-trust-anchor.d.ts.map +1 -0
- package/dist/src/cohort-topic/fret-trust-anchor.js +115 -0
- package/dist/src/cohort-topic/fret-trust-anchor.js.map +1 -0
- package/dist/src/cohort-topic/host.d.ts +573 -0
- package/dist/src/cohort-topic/host.d.ts.map +1 -0
- package/dist/src/cohort-topic/host.js +1460 -0
- package/dist/src/cohort-topic/host.js.map +1 -0
- package/dist/src/cohort-topic/index.d.ts +14 -0
- package/dist/src/cohort-topic/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/index.js +14 -0
- package/dist/src/cohort-topic/index.js.map +1 -0
- package/dist/src/cohort-topic/membership-publish-sink.d.ts +14 -0
- package/dist/src/cohort-topic/membership-publish-sink.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership-publish-sink.js +17 -0
- package/dist/src/cohort-topic/membership-publish-sink.js.map +1 -0
- package/dist/src/cohort-topic/membership-source.d.ts +36 -0
- package/dist/src/cohort-topic/membership-source.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership-source.js +58 -0
- package/dist/src/cohort-topic/membership-source.js.map +1 -0
- package/dist/src/cohort-topic/peer-codec.d.ts +18 -0
- package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -0
- package/dist/src/cohort-topic/peer-codec.js +26 -0
- package/dist/src/cohort-topic/peer-codec.js.map +1 -0
- package/dist/src/cohort-topic/peer-sig.d.ts +52 -0
- package/dist/src/cohort-topic/peer-sig.d.ts.map +1 -0
- package/dist/src/cohort-topic/peer-sig.js +83 -0
- package/dist/src/cohort-topic/peer-sig.js.map +1 -0
- package/dist/src/cohort-topic/protocols.d.ts +49 -0
- package/dist/src/cohort-topic/protocols.d.ts.map +1 -0
- package/dist/src/cohort-topic/protocols.js +58 -0
- package/dist/src/cohort-topic/protocols.js.map +1 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.d.ts +49 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.d.ts.map +1 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.js +42 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.js.map +1 -0
- package/dist/src/cohort-topic/size-estimator.d.ts +16 -0
- package/dist/src/cohort-topic/size-estimator.d.ts.map +1 -0
- package/dist/src/cohort-topic/size-estimator.js +16 -0
- package/dist/src/cohort-topic/size-estimator.js.map +1 -0
- package/dist/src/cohort-topic/stream-util.d.ts +19 -0
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -0
- package/dist/src/cohort-topic/stream-util.js +76 -0
- package/dist/src/cohort-topic/stream-util.js.map +1 -0
- package/dist/src/cohort-topic/threshold-crypto.d.ts +94 -0
- package/dist/src/cohort-topic/threshold-crypto.d.ts.map +1 -0
- package/dist/src/cohort-topic/threshold-crypto.js +178 -0
- package/dist/src/cohort-topic/threshold-crypto.js.map +1 -0
- package/dist/src/cohort-topic/topic-router.d.ts +41 -0
- package/dist/src/cohort-topic/topic-router.d.ts.map +1 -0
- package/dist/src/cohort-topic/topic-router.js +63 -0
- package/dist/src/cohort-topic/topic-router.js.map +1 -0
- package/dist/src/dispute/cascade.d.ts +179 -0
- package/dist/src/dispute/cascade.d.ts.map +1 -0
- package/dist/src/dispute/cascade.js +315 -0
- package/dist/src/dispute/cascade.js.map +1 -0
- package/dist/src/dispute/client.d.ts +2 -1
- package/dist/src/dispute/client.d.ts.map +1 -1
- package/dist/src/dispute/client.js +12 -3
- package/dist/src/dispute/client.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +41 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +147 -21
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/dispute/index.d.ts +2 -0
- package/dist/src/dispute/index.d.ts.map +1 -1
- package/dist/src/dispute/index.js +2 -0
- package/dist/src/dispute/index.js.map +1 -1
- package/dist/src/dispute/invalidation.d.ts +266 -0
- package/dist/src/dispute/invalidation.d.ts.map +1 -0
- package/dist/src/dispute/invalidation.js +405 -0
- package/dist/src/dispute/invalidation.js.map +1 -0
- package/dist/src/dispute/types.d.ts +14 -0
- package/dist/src/dispute/types.d.ts.map +1 -1
- package/dist/src/dispute/types.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +7 -0
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +49 -5
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +45 -3
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +522 -5
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/matchmaking/aggregate-counts.d.ts +67 -0
- package/dist/src/matchmaking/aggregate-counts.d.ts.map +1 -0
- package/dist/src/matchmaking/aggregate-counts.js +60 -0
- package/dist/src/matchmaking/aggregate-counts.js.map +1 -0
- package/dist/src/matchmaking/index.d.ts +20 -0
- package/dist/src/matchmaking/index.d.ts.map +1 -0
- package/dist/src/matchmaking/index.js +20 -0
- package/dist/src/matchmaking/index.js.map +1 -0
- package/dist/src/matchmaking/module.d.ts +181 -0
- package/dist/src/matchmaking/module.d.ts.map +1 -0
- package/dist/src/matchmaking/module.js +221 -0
- package/dist/src/matchmaking/module.js.map +1 -0
- package/dist/src/matchmaking/protocols.d.ts +37 -0
- package/dist/src/matchmaking/protocols.d.ts.map +1 -0
- package/dist/src/matchmaking/protocols.js +42 -0
- package/dist/src/matchmaking/protocols.js.map +1 -0
- package/dist/src/matchmaking/provider-manager.d.ts +52 -0
- package/dist/src/matchmaking/provider-manager.d.ts.map +1 -0
- package/dist/src/matchmaking/provider-manager.js +74 -0
- package/dist/src/matchmaking/provider-manager.js.map +1 -0
- package/dist/src/matchmaking/query-handler.d.ts +34 -0
- package/dist/src/matchmaking/query-handler.d.ts.map +1 -0
- package/dist/src/matchmaking/query-handler.js +62 -0
- package/dist/src/matchmaking/query-handler.js.map +1 -0
- package/dist/src/matchmaking/query-transport.d.ts +180 -0
- package/dist/src/matchmaking/query-transport.d.ts.map +1 -0
- package/dist/src/matchmaking/query-transport.js +315 -0
- package/dist/src/matchmaking/query-transport.js.map +1 -0
- package/dist/src/matchmaking/seeker-manager.d.ts +39 -0
- package/dist/src/matchmaking/seeker-manager.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker-manager.js +49 -0
- package/dist/src/matchmaking/seeker-manager.js.map +1 -0
- package/dist/src/matchmaking/seeker-walk-client.d.ts +127 -0
- package/dist/src/matchmaking/seeker-walk-client.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker-walk-client.js +189 -0
- package/dist/src/matchmaking/seeker-walk-client.js.map +1 -0
- package/dist/src/matchmaking/traffic-validation.d.ts +128 -0
- package/dist/src/matchmaking/traffic-validation.d.ts.map +1 -0
- package/dist/src/matchmaking/traffic-validation.js +105 -0
- package/dist/src/matchmaking/traffic-validation.js.map +1 -0
- package/dist/src/network/network-manager-service.d.ts +4 -2
- package/dist/src/network/network-manager-service.d.ts.map +1 -1
- package/dist/src/network/network-manager-service.js +9 -3
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/protocol-client.d.ts +13 -0
- package/dist/src/protocol-client.d.ts.map +1 -1
- package/dist/src/protocol-client.js +74 -2
- package/dist/src/protocol-client.js.map +1 -1
- package/dist/src/reactivity/forwarder-host.d.ts +198 -0
- package/dist/src/reactivity/forwarder-host.d.ts.map +1 -0
- package/dist/src/reactivity/forwarder-host.js +355 -0
- package/dist/src/reactivity/forwarder-host.js.map +1 -0
- package/dist/src/reactivity/index.d.ts +19 -0
- package/dist/src/reactivity/index.d.ts.map +1 -0
- package/dist/src/reactivity/index.js +19 -0
- package/dist/src/reactivity/index.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts +70 -0
- package/dist/src/reactivity/notify-transport.d.ts.map +1 -0
- package/dist/src/reactivity/notify-transport.js +111 -0
- package/dist/src/reactivity/notify-transport.js.map +1 -0
- package/dist/src/reactivity/origination-manager.d.ts +110 -0
- package/dist/src/reactivity/origination-manager.d.ts.map +1 -0
- package/dist/src/reactivity/origination-manager.js +129 -0
- package/dist/src/reactivity/origination-manager.js.map +1 -0
- package/dist/src/reactivity/protocols.d.ts +43 -0
- package/dist/src/reactivity/protocols.d.ts.map +1 -0
- package/dist/src/reactivity/protocols.js +50 -0
- package/dist/src/reactivity/protocols.js.map +1 -0
- package/dist/src/reactivity/push-state-gossip.d.ts +132 -0
- package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -0
- package/dist/src/reactivity/push-state-gossip.js +231 -0
- package/dist/src/reactivity/push-state-gossip.js.map +1 -0
- package/dist/src/reactivity/recover-transport.d.ts +167 -0
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -0
- package/dist/src/reactivity/recover-transport.js +308 -0
- package/dist/src/reactivity/recover-transport.js.map +1 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.d.ts +142 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.d.ts.map +1 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.js +223 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.js.map +1 -0
- package/dist/src/reactivity/subscriber-registry.d.ts +47 -0
- package/dist/src/reactivity/subscriber-registry.d.ts.map +1 -0
- package/dist/src/reactivity/subscriber-registry.js +87 -0
- package/dist/src/reactivity/subscriber-registry.js.map +1 -0
- package/dist/src/reactivity/subscription-manager.d.ts +221 -0
- package/dist/src/reactivity/subscription-manager.d.ts.map +1 -0
- package/dist/src/reactivity/subscription-manager.js +302 -0
- package/dist/src/reactivity/subscription-manager.js.map +1 -0
- package/dist/src/reactivity/topic-bytes.d.ts +32 -0
- package/dist/src/reactivity/topic-bytes.d.ts.map +1 -0
- package/dist/src/reactivity/topic-bytes.js +34 -0
- package/dist/src/reactivity/topic-bytes.js.map +1 -0
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +51 -23
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/service.d.ts +46 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +79 -45
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/rpc-deadline.d.ts +37 -0
- package/dist/src/rpc-deadline.d.ts.map +1 -0
- package/dist/src/rpc-deadline.js +31 -0
- package/dist/src/rpc-deadline.js.map +1 -0
- package/dist/src/storage/block-storage.d.ts +2 -0
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +95 -2
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +25 -0
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +53 -2
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +193 -9
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/sync/client.d.ts +4 -1
- package/dist/src/sync/client.d.ts.map +1 -1
- package/dist/src/sync/client.js +5 -2
- package/dist/src/sync/client.js.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +219 -0
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -0
- package/dist/src/testing/cohort-topic-mesh-harness.js +467 -0
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -0
- package/dist/src/testing/matchmaking-mesh-harness.d.ts +187 -0
- package/dist/src/testing/matchmaking-mesh-harness.d.ts.map +1 -0
- package/dist/src/testing/matchmaking-mesh-harness.js +358 -0
- package/dist/src/testing/matchmaking-mesh-harness.js.map +1 -0
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +23 -1
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/dist/src/testing/reactivity-mesh-harness.d.ts +298 -0
- package/dist/src/testing/reactivity-mesh-harness.d.ts.map +1 -0
- package/dist/src/testing/reactivity-mesh-harness.js +663 -0
- package/dist/src/testing/reactivity-mesh-harness.js.map +1 -0
- package/package.json +7 -4
- package/src/cluster/block-transfer-service.ts +133 -68
- package/src/cluster/client.ts +64 -45
- package/src/cluster/cluster-error.ts +64 -0
- package/src/cluster/cluster-repo.ts +405 -55
- package/src/cluster/commit-cert.ts +139 -0
- package/src/cluster/service.ts +116 -44
- package/src/cluster/spread-on-churn.ts +328 -285
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -0
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -0
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -0
- package/src/cohort-topic/change-bridge.ts +109 -0
- package/src/cohort-topic/cohort-gossip-driver.ts +144 -0
- package/src/cohort-topic/cohort-gossip-transport.ts +74 -0
- package/src/cohort-topic/fret-trust-anchor.ts +153 -0
- package/src/cohort-topic/host.ts +2175 -0
- package/src/cohort-topic/index.ts +13 -0
- package/src/cohort-topic/membership-publish-sink.ts +20 -0
- package/src/cohort-topic/membership-source.ts +68 -0
- package/src/cohort-topic/peer-codec.ts +31 -0
- package/src/cohort-topic/peer-sig.ts +86 -0
- package/src/cohort-topic/protocols.ts +71 -0
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -0
- package/src/cohort-topic/size-estimator.ts +16 -0
- package/src/cohort-topic/stream-util.ts +87 -0
- package/src/cohort-topic/threshold-crypto.ts +239 -0
- package/src/cohort-topic/topic-router.ts +77 -0
- package/src/dispute/cascade.ts +517 -0
- package/src/dispute/client.ts +12 -2
- package/src/dispute/dispute-service.ts +171 -24
- package/src/dispute/index.ts +42 -0
- package/src/dispute/invalidation.ts +587 -0
- package/src/dispute/types.ts +14 -0
- package/src/index.ts +4 -0
- package/src/libp2p-key-network.ts +49 -6
- package/src/libp2p-node-base.ts +620 -12
- package/src/matchmaking/aggregate-counts.ts +104 -0
- package/src/matchmaking/index.ts +20 -0
- package/src/matchmaking/module.ts +363 -0
- package/src/matchmaking/protocols.ts +51 -0
- package/src/matchmaking/provider-manager.ts +95 -0
- package/src/matchmaking/query-handler.ts +88 -0
- package/src/matchmaking/query-transport.ts +483 -0
- package/src/matchmaking/seeker-manager.ts +64 -0
- package/src/matchmaking/seeker-walk-client.ts +293 -0
- package/src/matchmaking/traffic-validation.ts +195 -0
- package/src/network/network-manager-service.ts +9 -2
- package/src/protocol-client.ts +64 -3
- package/src/reactivity/forwarder-host.ts +438 -0
- package/src/reactivity/index.ts +19 -0
- package/src/reactivity/notify-transport.ts +144 -0
- package/src/reactivity/origination-manager.ts +192 -0
- package/src/reactivity/protocols.ts +61 -0
- package/src/reactivity/push-state-gossip.ts +291 -0
- package/src/reactivity/recover-transport.ts +408 -0
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -0
- package/src/reactivity/subscriber-registry.ts +96 -0
- package/src/reactivity/subscription-manager.ts +450 -0
- package/src/reactivity/topic-bytes.ts +37 -0
- package/src/repo/client.ts +54 -23
- package/src/repo/service.ts +90 -42
- package/src/rpc-deadline.ts +45 -0
- package/src/storage/block-storage.ts +106 -3
- package/src/storage/i-block-storage.ts +27 -0
- package/src/storage/storage-repo.ts +209 -13
- package/src/sync/client.ts +5 -2
- package/src/testing/cohort-topic-mesh-harness.ts +613 -0
- package/src/testing/matchmaking-mesh-harness.ts +475 -0
- package/src/testing/mesh-harness.ts +22 -2
- package/src/testing/reactivity-mesh-harness.ts +922 -0
|
@@ -1,285 +1,328 @@
|
|
|
1
|
-
import type { Startable, Libp2p } from '@libp2p/interface'
|
|
2
|
-
import type { IRepo, IPeerNetwork } from '@optimystic/db-core'
|
|
3
|
-
import { hashKey } from 'p2p-fret'
|
|
4
|
-
import type { FretService } from 'p2p-fret'
|
|
5
|
-
import { peerIdFromString } from '@libp2p/peer-id'
|
|
6
|
-
import type { PartitionDetector } from './partition-detector.js'
|
|
7
|
-
import { BlockTransferClient } from './block-transfer-service.js'
|
|
8
|
-
import { createLogger } from '../logger.js'
|
|
9
|
-
|
|
10
|
-
const log = createLogger('spread-on-churn')
|
|
11
|
-
const textEncoder = new TextEncoder()
|
|
12
|
-
|
|
13
|
-
// ── Types ────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export interface SpreadOnChurnConfig {
|
|
16
|
-
/** Enable the churn-resilient spread protocol. Default: true */
|
|
17
|
-
enabled: boolean
|
|
18
|
-
/** Number of middle-closest peers eligible to spread (d). Default: 3 */
|
|
19
|
-
spreadDistance: number
|
|
20
|
-
/** Enable dynamic d scaling based on cluster health. Default: true */
|
|
21
|
-
dynamicSpreadDistance: boolean
|
|
22
|
-
/** Cluster size ratio below which spread becomes more aggressive. Default: 0.6 */
|
|
23
|
-
healthThreshold: number
|
|
24
|
-
/** Debounce window for departure detection (ms). Default: 5000 */
|
|
25
|
-
departureDebounceMs: number
|
|
26
|
-
/** Number of peers beyond cluster boundary to target. Default: 4 */
|
|
27
|
-
expansionStep: number
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
1
|
+
import type { Startable, Libp2p } from '@libp2p/interface'
|
|
2
|
+
import type { IRepo, IPeerNetwork } from '@optimystic/db-core'
|
|
3
|
+
import { hashKey } from 'p2p-fret'
|
|
4
|
+
import type { FretService } from 'p2p-fret'
|
|
5
|
+
import { peerIdFromString } from '@libp2p/peer-id'
|
|
6
|
+
import type { PartitionDetector } from './partition-detector.js'
|
|
7
|
+
import { BlockTransferClient } from './block-transfer-service.js'
|
|
8
|
+
import { createLogger } from '../logger.js'
|
|
9
|
+
|
|
10
|
+
const log = createLogger('spread-on-churn')
|
|
11
|
+
const textEncoder = new TextEncoder()
|
|
12
|
+
|
|
13
|
+
// ── Types ────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface SpreadOnChurnConfig {
|
|
16
|
+
/** Enable the churn-resilient spread protocol. Default: true */
|
|
17
|
+
enabled: boolean
|
|
18
|
+
/** Number of middle-closest peers eligible to spread (d). Default: 3 */
|
|
19
|
+
spreadDistance: number
|
|
20
|
+
/** Enable dynamic d scaling based on cluster health. Default: true */
|
|
21
|
+
dynamicSpreadDistance: boolean
|
|
22
|
+
/** Cluster size ratio below which spread becomes more aggressive. Default: 0.6 */
|
|
23
|
+
healthThreshold: number
|
|
24
|
+
/** Debounce window for departure detection (ms). Default: 5000 */
|
|
25
|
+
departureDebounceMs: number
|
|
26
|
+
/** Number of peers beyond cluster boundary to target. Default: 4 */
|
|
27
|
+
expansionStep: number
|
|
28
|
+
/**
|
|
29
|
+
* Per-target dial deadline (ms) for churn pushes. A target that never accepts
|
|
30
|
+
* the connection fails fast instead of stalling the sequential spread pass.
|
|
31
|
+
* Default: 3000 (matches the codebase's other dial caps).
|
|
32
|
+
*/
|
|
33
|
+
pushDialTimeoutMs: number
|
|
34
|
+
/**
|
|
35
|
+
* Per-target response deadline (ms) for churn pushes. A target that dials OK but
|
|
36
|
+
* never replies (connects, then goes silent) is given up on — the push throws
|
|
37
|
+
* `ResponseTimeoutError`, is recorded as failed, and the loop continues to the
|
|
38
|
+
* next target/block rather than hanging the whole pass. Default: 10000.
|
|
39
|
+
*/
|
|
40
|
+
pushResponseTimeoutMs: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SpreadOnChurnDeps {
|
|
44
|
+
libp2p: Libp2p
|
|
45
|
+
fret: FretService
|
|
46
|
+
partitionDetector: PartitionDetector
|
|
47
|
+
repo: IRepo
|
|
48
|
+
peerNetwork: IPeerNetwork
|
|
49
|
+
clusterSize: number
|
|
50
|
+
protocolPrefix?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SpreadEvent {
|
|
54
|
+
/** Blocks that were spread */
|
|
55
|
+
spread: Array<{
|
|
56
|
+
blockId: string
|
|
57
|
+
targets: string[]
|
|
58
|
+
succeeded: string[]
|
|
59
|
+
failed: string[]
|
|
60
|
+
}>
|
|
61
|
+
/** Current effective d */
|
|
62
|
+
effectiveD: number
|
|
63
|
+
/** Timestamp of the departure that triggered this */
|
|
64
|
+
triggeredAt: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type SpreadHandler = (event: SpreadEvent) => void
|
|
68
|
+
|
|
69
|
+
// ── Defaults ─────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
const DEFAULT_CONFIG: SpreadOnChurnConfig = {
|
|
72
|
+
enabled: true,
|
|
73
|
+
spreadDistance: 3,
|
|
74
|
+
dynamicSpreadDistance: true,
|
|
75
|
+
healthThreshold: 0.6,
|
|
76
|
+
departureDebounceMs: 5000,
|
|
77
|
+
expansionStep: 4,
|
|
78
|
+
pushDialTimeoutMs: 3000,
|
|
79
|
+
pushResponseTimeoutMs: 10000,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Monitor ──────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
export class SpreadOnChurnMonitor implements Startable {
|
|
85
|
+
private running = false
|
|
86
|
+
private readonly trackedBlocks = new Set<string>()
|
|
87
|
+
private readonly handlers: SpreadHandler[] = []
|
|
88
|
+
private debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
89
|
+
private departureTimestamps: number[] = []
|
|
90
|
+
private departureTimestamp = 0
|
|
91
|
+
|
|
92
|
+
private readonly config: SpreadOnChurnConfig
|
|
93
|
+
private readonly onConnectionClose: () => void
|
|
94
|
+
|
|
95
|
+
constructor(
|
|
96
|
+
private readonly deps: SpreadOnChurnDeps,
|
|
97
|
+
config: Partial<SpreadOnChurnConfig> = {}
|
|
98
|
+
) {
|
|
99
|
+
this.config = { ...DEFAULT_CONFIG, ...config }
|
|
100
|
+
this.onConnectionClose = () => this.handleDeparture()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Startable ────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
async start(): Promise<void> {
|
|
106
|
+
if (this.running) return
|
|
107
|
+
this.running = true
|
|
108
|
+
|
|
109
|
+
this.deps.libp2p.addEventListener('connection:close', this.onConnectionClose)
|
|
110
|
+
|
|
111
|
+
log('started, tracking %d blocks', this.trackedBlocks.size)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async stop(): Promise<void> {
|
|
115
|
+
if (!this.running) return
|
|
116
|
+
this.running = false
|
|
117
|
+
|
|
118
|
+
this.deps.libp2p.removeEventListener('connection:close', this.onConnectionClose)
|
|
119
|
+
|
|
120
|
+
if (this.debounceTimer) {
|
|
121
|
+
clearTimeout(this.debounceTimer)
|
|
122
|
+
this.debounceTimer = null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
log('stopped')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Public API ───────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
onSpread(handler: SpreadHandler): void {
|
|
131
|
+
this.handlers.push(handler)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
trackBlock(blockId: string): void {
|
|
135
|
+
this.trackedBlocks.add(blockId)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
untrackBlock(blockId: string): void {
|
|
139
|
+
this.trackedBlocks.delete(blockId)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getTrackedBlockCount(): number {
|
|
143
|
+
return this.trackedBlocks.size
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Force an immediate spread check (useful for testing). */
|
|
147
|
+
async checkNow(): Promise<SpreadEvent | null> {
|
|
148
|
+
return this.performSpread(Date.now())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Internal ─────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
private handleDeparture(): void {
|
|
154
|
+
if (!this.running) return
|
|
155
|
+
if (!this.config.enabled) return
|
|
156
|
+
|
|
157
|
+
if (!this.departureTimestamp) {
|
|
158
|
+
this.departureTimestamp = Date.now()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Record for dynamic-d sliding window
|
|
162
|
+
this.departureTimestamps.push(Date.now())
|
|
163
|
+
|
|
164
|
+
if (this.debounceTimer) {
|
|
165
|
+
clearTimeout(this.debounceTimer)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.debounceTimer = setTimeout(() => {
|
|
169
|
+
this.debounceTimer = null
|
|
170
|
+
const ts = this.departureTimestamp
|
|
171
|
+
this.departureTimestamp = 0
|
|
172
|
+
if (this.running) {
|
|
173
|
+
this.performSpread(ts).catch(err => {
|
|
174
|
+
log('spread error: %O', err)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}, this.config.departureDebounceMs)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async performSpread(triggeredAt: number): Promise<SpreadEvent | null> {
|
|
181
|
+
if (!this.config.enabled) return null
|
|
182
|
+
|
|
183
|
+
if (this.deps.partitionDetector.detectPartition()) {
|
|
184
|
+
log('partition detected, suppressing spread')
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (this.trackedBlocks.size === 0) return null
|
|
189
|
+
|
|
190
|
+
const selfId = this.deps.libp2p.peerId.toString()
|
|
191
|
+
const effectiveD = this.computeEffectiveD()
|
|
192
|
+
const spreadResults: SpreadEvent['spread'] = []
|
|
193
|
+
|
|
194
|
+
for (const blockId of this.trackedBlocks) {
|
|
195
|
+
const key = textEncoder.encode(blockId)
|
|
196
|
+
const coord = await hashKey(key)
|
|
197
|
+
|
|
198
|
+
// Check eligibility: only middle peers spread
|
|
199
|
+
const rank = this.deps.fret.neighborDistance(selfId, coord, this.deps.clusterSize)
|
|
200
|
+
if (rank >= effectiveD) continue
|
|
201
|
+
|
|
202
|
+
// Get current cohort and expansion targets
|
|
203
|
+
const cohort = this.deps.fret.assembleCohort(coord, this.deps.clusterSize)
|
|
204
|
+
const cohortSet = new Set(cohort)
|
|
205
|
+
const expanded = this.deps.fret.expandCohort(
|
|
206
|
+
cohort, coord, this.config.expansionStep
|
|
207
|
+
)
|
|
208
|
+
const targets = expanded.filter(id => !cohortSet.has(id) && id !== selfId)
|
|
209
|
+
if (targets.length === 0) continue
|
|
210
|
+
|
|
211
|
+
// Read block data from local storage
|
|
212
|
+
const result = await this.deps.repo.get({ blockIds: [blockId] })
|
|
213
|
+
const blockResult = result[blockId]
|
|
214
|
+
if (!blockResult?.block) {
|
|
215
|
+
// The block has left local storage. No deletion event exists today to evict it
|
|
216
|
+
// from the tracked set, so prune here. Deleting the current element of a Set mid
|
|
217
|
+
// for...of is safe - it does not disturb the rest of the iteration. A later
|
|
218
|
+
// re-commit re-adds the block via the owned-block feed.
|
|
219
|
+
log('no-local-data block=%s (untracking)', blockId)
|
|
220
|
+
this.trackedBlocks.delete(blockId)
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const blockData = textEncoder.encode(JSON.stringify(blockResult.block))
|
|
225
|
+
|
|
226
|
+
// Carry the source's revision metadata so the replica's `latest` matches the
|
|
227
|
+
// source instead of being fabricated as rev 1 on the receiver.
|
|
228
|
+
const latest = blockResult.state?.latest
|
|
229
|
+
const blockMeta = latest
|
|
230
|
+
? { [blockId]: { rev: latest.rev, actionId: latest.actionId } }
|
|
231
|
+
: undefined
|
|
232
|
+
|
|
233
|
+
// Push to each target
|
|
234
|
+
const succeeded: string[] = []
|
|
235
|
+
const failed: string[] = []
|
|
236
|
+
|
|
237
|
+
for (const targetId of targets) {
|
|
238
|
+
try {
|
|
239
|
+
const peerId = peerIdFromString(targetId)
|
|
240
|
+
const client = new BlockTransferClient(
|
|
241
|
+
peerId,
|
|
242
|
+
this.deps.peerNetwork,
|
|
243
|
+
this.deps.protocolPrefix
|
|
244
|
+
)
|
|
245
|
+
// Bound both the dial and the response read. A target that connects but
|
|
246
|
+
// never replies would otherwise hang this sequential loop forever during
|
|
247
|
+
// churn (exactly when slow/dying peers are common); the deadline makes the
|
|
248
|
+
// push throw, which the catch below records as `failed` so the pass advances.
|
|
249
|
+
const response = await client.pushBlocks([blockId], [blockData], 'replication', blockMeta, {
|
|
250
|
+
dialTimeoutMs: this.config.pushDialTimeoutMs,
|
|
251
|
+
responseTimeoutMs: this.config.pushResponseTimeoutMs,
|
|
252
|
+
})
|
|
253
|
+
// A round-trip that does not throw still does not mean the replica
|
|
254
|
+
// landed: handlePush reports a block in `missing` when it could not
|
|
255
|
+
// parse or persist it. Treat that as a failed push so the resilience
|
|
256
|
+
// mechanism does not falsely count the new owner as holding the block.
|
|
257
|
+
if (response.missing.includes(blockId)) {
|
|
258
|
+
failed.push(targetId)
|
|
259
|
+
log('push:rejected block=%s target=%s (receiver did not persist)', blockId, targetId)
|
|
260
|
+
} else {
|
|
261
|
+
succeeded.push(targetId)
|
|
262
|
+
log('push:ok block=%s target=%s', blockId, targetId)
|
|
263
|
+
}
|
|
264
|
+
} catch (err) {
|
|
265
|
+
failed.push(targetId)
|
|
266
|
+
log('push:fail block=%s target=%s err=%s',
|
|
267
|
+
blockId, targetId, (err as Error).message)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
spreadResults.push({ blockId, targets, succeeded, failed })
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (spreadResults.length === 0) return null
|
|
275
|
+
|
|
276
|
+
const event: SpreadEvent = {
|
|
277
|
+
spread: spreadResults,
|
|
278
|
+
effectiveD,
|
|
279
|
+
triggeredAt,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.emitEvent(event)
|
|
283
|
+
return event
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private computeEffectiveD(): number {
|
|
287
|
+
const d = this.config.spreadDistance
|
|
288
|
+
if (!this.config.dynamicSpreadDistance) return d
|
|
289
|
+
|
|
290
|
+
const maxD = Math.max(d, Math.floor(this.deps.clusterSize / 2))
|
|
291
|
+
const windowMs = this.config.departureDebounceMs * 4
|
|
292
|
+
const now = Date.now()
|
|
293
|
+
|
|
294
|
+
// Prune old departure timestamps
|
|
295
|
+
this.departureTimestamps = this.departureTimestamps.filter(
|
|
296
|
+
ts => now - ts < windowMs
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
// Rapid churn: 3+ departures in window → increase d by 1
|
|
300
|
+
if (this.departureTimestamps.length >= 3) {
|
|
301
|
+
return Math.min(d + 1, maxD)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Low cluster health: observed cohort shrunk relative to expected
|
|
305
|
+
// We approximate observed cohort size from FRET diagnostics
|
|
306
|
+
const diag: any = (this.deps.fret as any).getDiagnostics?.()
|
|
307
|
+
const estimate = diag?.estimate ?? diag?.n
|
|
308
|
+
if (typeof estimate === 'number' && Number.isFinite(estimate) && estimate > 0) {
|
|
309
|
+
const ratio = estimate / this.deps.clusterSize
|
|
310
|
+
if (ratio < this.config.healthThreshold) {
|
|
311
|
+
const scaled = Math.ceil(d * (this.deps.clusterSize / estimate))
|
|
312
|
+
return Math.min(scaled, maxD)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return d
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private emitEvent(event: SpreadEvent): void {
|
|
320
|
+
for (const handler of this.handlers) {
|
|
321
|
+
try {
|
|
322
|
+
handler(event)
|
|
323
|
+
} catch (err) {
|
|
324
|
+
log('handler error: %O', err)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|