@optimystic/db-p2p 0.13.4 → 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 +8 -1
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +67 -8
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +57 -3
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +523 -6
- 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 +25 -0
- package/dist/src/protocol-client.d.ts.map +1 -1
- package/dist/src/protocol-client.js +126 -4
- 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 -19
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +9 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +65 -22
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +20 -0
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +101 -16
- package/dist/src/repo/coordinator-repo.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 +67 -9
- package/src/libp2p-node-base.ts +633 -14
- 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 +119 -5
- 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 -19
- package/src/repo/cluster-coordinator.ts +66 -26
- package/src/repo/coordinator-repo.ts +99 -15
- 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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createLogger } from "../logger.js";
|
|
2
|
+
const log = createLogger('cohort-change-bridge');
|
|
3
|
+
/**
|
|
4
|
+
* The reactivity/matchmaking **origination point**: bridge the local single-node change-notifier
|
|
5
|
+
* primitive into the networked cohort-topic substrate.
|
|
6
|
+
*
|
|
7
|
+
* On EVERY commit landing on this node (via the catch-all {@link ChangeBridgeSource.onAnyCollectionChange}
|
|
8
|
+
* feed), if this node is a cohort member for the collection's reactivity topic, the bridge hands the
|
|
9
|
+
* `CollectionChangeEvent` plus the pass-through {@link CommitCert} to `service.onLocalCommit` —
|
|
10
|
+
* reactivity reuses the commit cert's threshold signature directly and never re-signs. A commit on a
|
|
11
|
+
* non-member node (no fan-out responsibility) or one for which no cert is retained (nothing
|
|
12
|
+
* authoritative to forward) is a no-op. A throwing downstream hook is isolated + logged so
|
|
13
|
+
* origination can never break the commit (matching the {@link IBlockChangeNotifier} listener contract).
|
|
14
|
+
*
|
|
15
|
+
* The returned value IS an {@link IBlockChangeNotifier}: it is what `network-transactor` takes as its
|
|
16
|
+
* `localChangeNotifier`, so per-collection {@link IBlockChangeNotifier.onCollectionChange} subscribers
|
|
17
|
+
* (e.g. the Quereus reactive-watch vtab) keep working — those subscriptions delegate straight to
|
|
18
|
+
* `source`. Origination runs independently on the catch-all feed.
|
|
19
|
+
*
|
|
20
|
+
* `makeCohortTopicChangeNotifier` discards the catch-all unsubscribe (node-lifetime by intent) for the
|
|
21
|
+
* unit tests; the node assembly uses {@link attachCohortChangeBridge}, which returns the teardown.
|
|
22
|
+
*/
|
|
23
|
+
export function makeCohortTopicChangeNotifier(deps) {
|
|
24
|
+
return buildCohortTopicChangeBridge(deps).notifier;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Shared wiring behind both entry points: subscribe the origination handler to the source's catch-all
|
|
28
|
+
* commit feed and build the decorating {@link IBlockChangeNotifier}. Returns the notifier plus the
|
|
29
|
+
* idempotent teardown for that catch-all subscription (the underlying `onAnyCollectionChange` unsub is
|
|
30
|
+
* itself idempotent), so the node assembly can release it on node stop alongside `host.stop()`.
|
|
31
|
+
*/
|
|
32
|
+
function buildCohortTopicChangeBridge(deps) {
|
|
33
|
+
const unsubscribe = deps.source.onAnyCollectionChange((event) => originate(deps, event));
|
|
34
|
+
const notifier = {
|
|
35
|
+
onCollectionChange: (collectionId, listener) => deps.source.onCollectionChange(collectionId, listener),
|
|
36
|
+
};
|
|
37
|
+
return { notifier, unsubscribe };
|
|
38
|
+
}
|
|
39
|
+
/** Run the membership gate, cert extraction, and origination hook for one change event, isolating throws. */
|
|
40
|
+
function originate(deps, event) {
|
|
41
|
+
try {
|
|
42
|
+
if (!deps.selfIsCohortMember(event)) {
|
|
43
|
+
return; // not responsible for this topic's fan-out
|
|
44
|
+
}
|
|
45
|
+
const hook = deps.service.onLocalCommit;
|
|
46
|
+
if (!hook) {
|
|
47
|
+
return; // no reactivity/matchmaking consumer has attached an origination handler yet
|
|
48
|
+
}
|
|
49
|
+
const commitCert = deps.extractCommitCert(event);
|
|
50
|
+
if (!commitCert) {
|
|
51
|
+
return; // nothing authoritative to forward; never fabricate an unsigned cert (extractor logs why)
|
|
52
|
+
}
|
|
53
|
+
hook(event, commitCert);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
log('origination hook threw for collection=%s rev=%d: %o', event.collectionId, event.rev, err);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Wire the cohort-topic origination bridge as `node`'s `blockChangeNotifier` (the value the
|
|
61
|
+
* `NetworkTransactor` consumes as its `localChangeNotifier`). Call this from the node assembly once a
|
|
62
|
+
* {@link CohortTopicService} is running on the node, passing the node's `StorageRepo` as `source` and
|
|
63
|
+
* the membership + cert-extraction seams.
|
|
64
|
+
*
|
|
65
|
+
* Returns the installed `notifier` plus an idempotent `unsubscribe` that tears down the catch-all
|
|
66
|
+
* origination subscription — the node assembly calls it on node stop (alongside `host.stop()`) so the
|
|
67
|
+
* origination feed is released before the node's transports close. Tearing it down stops further
|
|
68
|
+
* origination but leaves the per-collection `onCollectionChange` delegation intact (those subscriptions
|
|
69
|
+
* live on the `source` `StorageRepo`, not on the bridge).
|
|
70
|
+
*/
|
|
71
|
+
export function attachCohortChangeBridge(node, deps) {
|
|
72
|
+
const bridge = buildCohortTopicChangeBridge(deps);
|
|
73
|
+
node.blockChangeNotifier = bridge.notifier;
|
|
74
|
+
return bridge;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=change-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-bridge.js","sourceRoot":"","sources":["../../../src/cohort-topic/change-bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AA4BjD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,6BAA6B,CAAC,IAAmC;IAChF,OAAO,4BAA4B,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACH,SAAS,4BAA4B,CAAC,IAAmC;IACxE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACzF,MAAM,QAAQ,GAAyB;QACtC,kBAAkB,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAgB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,YAAY,EAAE,QAAQ,CAAC;KACpH,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,6GAA6G;AAC7G,SAAS,SAAS,CAAC,IAAmC,EAAE,KAA4B;IACnF,IAAI,CAAC;QACJ,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;YACrC,OAAO,CAAC,2CAA2C;QACpD,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;QACxC,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,OAAO,CAAC,6EAA6E;QACtF,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,UAAU,EAAE,CAAC;YACjB,OAAO,CAAC,0FAA0F;QACnG,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,GAAG,CAAC,qDAAqD,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAChG,CAAC;AACF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CACvC,IAAoD,EACpD,IAAmC;IAEnC,MAAM,MAAM,GAAG,4BAA4B,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC3C,OAAO,MAAM,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic gossip-cadence driver helpers (host-side, gap 5).
|
|
3
|
+
*
|
|
4
|
+
* The {@link import("./host.js").CohortTopicHost} owns a single repeating timer; on each tick it drives
|
|
5
|
+
* every live `CoordEngine`'s gossip round. The per-round work this module factors out — assembling a
|
|
6
|
+
* `CohortGossipV1` and accumulating/draining the registration-record deltas the renewal `touch`/`evicted`
|
|
7
|
+
* hooks emit — is split here so it is unit-testable without the timer or libp2p.
|
|
8
|
+
*
|
|
9
|
+
* db-p2p (not db-core) owns the cadence because the round interval, the peer-key signature on the gossip
|
|
10
|
+
* envelope, and the per-coord inbound routing all live at the FRET/libp2p boundary; db-core supplies the
|
|
11
|
+
* pure substrate (`selfWillingnessBits`, `toCohortTopicSummary`, `toGossipRecord`, the bus merge).
|
|
12
|
+
*/
|
|
13
|
+
import { type CohortGossipV1, type CohortTopicSummary, type GossipRecordRefV1, type GossipRecordV1, type LoadBarometerState, type NodeProfile, type RegistrationRecord } from "@optimystic/db-core";
|
|
14
|
+
/**
|
|
15
|
+
* Default gossip-round cadence in ms. There is no dedicated `gossip_round` constant in
|
|
16
|
+
* `docs/cohort-topic.md` §Configuration, so this is a derived, injectable default on the order of one
|
|
17
|
+
* round — a few seconds, aligned with the traffic window / ping cadence (`ping_interval = ttl/3` is
|
|
18
|
+
* 30 s Core, so a sub-round of that). The membership refresh (`T_membership_refresh`, 5 min) and the
|
|
19
|
+
* demotion hysteresis (`T_demote`, 5 min) are gated by elapsed-time **inside** their modules, so the
|
|
20
|
+
* driver can tick fast and let those modules decide when to act.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_GOSSIP_INTERVAL_MS = 5000;
|
|
23
|
+
/**
|
|
24
|
+
* Per-`CoordEngine` queue of registration-record deltas accumulated between gossip rounds. The renewal
|
|
25
|
+
* cohort side calls {@link PendingDeltas.touch} on every served ping/re-attach and
|
|
26
|
+
* {@link PendingDeltas.evicted} on every TTL sweep; the next round {@link PendingDeltas.drain}s the
|
|
27
|
+
* batch into the gossip frame and clears it — one broadcast per round rather than one per ping.
|
|
28
|
+
*/
|
|
29
|
+
export interface PendingDeltas {
|
|
30
|
+
/** Upsert a fresh/touched record (keyed by `(topicId, participantId)`; last write wins on `lastPing`). */
|
|
31
|
+
touch(rec: RegistrationRecord): void;
|
|
32
|
+
/** Queue an eviction ref and drop any pending record for the same key (a stale record can't also re-advertise). */
|
|
33
|
+
evicted(rec: RegistrationRecord): void;
|
|
34
|
+
/** True iff nothing is queued. */
|
|
35
|
+
isEmpty(): boolean;
|
|
36
|
+
/** Drain the queue into wire-shaped deltas, clearing it. */
|
|
37
|
+
drain(): {
|
|
38
|
+
records: GossipRecordV1[];
|
|
39
|
+
evicted: GossipRecordRefV1[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Build an empty {@link PendingDeltas} queue. */
|
|
43
|
+
export declare function createPendingDeltas(): PendingDeltas;
|
|
44
|
+
/** Inputs to {@link buildCohortGossip} — the per-round signals already gathered by the engine. */
|
|
45
|
+
export interface GossipFrameInputs {
|
|
46
|
+
/** This member's dialable id, base64url (the `fromMember` / view key). */
|
|
47
|
+
readonly fromMember: string;
|
|
48
|
+
/** This cohort's served coord, base64url (the inbound routing key). */
|
|
49
|
+
readonly coord: string;
|
|
50
|
+
/** This cohort's epoch, base64url. */
|
|
51
|
+
readonly cohortEpoch: string;
|
|
52
|
+
/** This node's tier profile (the `∧` half of the willingness vector). */
|
|
53
|
+
readonly profile: NodeProfile;
|
|
54
|
+
/** This member's load barometer (load buckets + the load-shed half of willingness). */
|
|
55
|
+
readonly barometer: LoadBarometerState;
|
|
56
|
+
/** Cohort-wide observation window (seconds) for the rate fields in `topicSummaries`. */
|
|
57
|
+
readonly windowSeconds: number;
|
|
58
|
+
/** Per-resident-topic summaries (own published counts), already frozen for this round. */
|
|
59
|
+
readonly topicSummaries: CohortTopicSummary[];
|
|
60
|
+
/** Drained fresh/touched record deltas for this round. */
|
|
61
|
+
readonly records: GossipRecordV1[];
|
|
62
|
+
/** Drained eviction refs for this round. */
|
|
63
|
+
readonly evicted: GossipRecordRefV1[];
|
|
64
|
+
/** Round timestamp, unix ms. */
|
|
65
|
+
readonly timestamp: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Assemble one round's {@link CohortGossipV1}, or `undefined` when this engine is **idle** — no resident
|
|
69
|
+
* topics and no pending deltas, so it has nothing for siblings and the host skips the broadcast (idle
|
|
70
|
+
* empty engines cost no gossip). `willingnessBits` is `profile ∧ load` ({@link selfWillingnessBits}); the
|
|
71
|
+
* `signature` slot is left empty for the host's peer-key signer to fill before broadcast.
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildCohortGossip(i: GossipFrameInputs): CohortGossipV1 | undefined;
|
|
74
|
+
//# sourceMappingURL=cohort-gossip-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cohort-gossip-driver.d.ts","sourceRoot":"","sources":["../../../src/cohort-topic/cohort-gossip-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAKN,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,OAAQ,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,0GAA0G;IAC1G,KAAK,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACrC,mHAAmH;IACnH,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACvC,kCAAkC;IAClC,OAAO,IAAI,OAAO,CAAC;IACnB,4DAA4D;IAC5D,KAAK,IAAI;QAAE,OAAO,EAAE,cAAc,EAAE,CAAC;QAAC,OAAO,EAAE,iBAAiB,EAAE,CAAA;KAAE,CAAC;CACrE;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,IAAI,aAAa,CAiCnD;AAED,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IACjC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,uFAAuF;IACvF,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,wFAAwF;IACxF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,0FAA0F;IAC1F,QAAQ,CAAC,cAAc,EAAE,kBAAkB,EAAE,CAAC;IAC9C,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IACtC,gCAAgC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,iBAAiB,GAAG,cAAc,GAAG,SAAS,CAuBlF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic gossip-cadence driver helpers (host-side, gap 5).
|
|
3
|
+
*
|
|
4
|
+
* The {@link import("./host.js").CohortTopicHost} owns a single repeating timer; on each tick it drives
|
|
5
|
+
* every live `CoordEngine`'s gossip round. The per-round work this module factors out — assembling a
|
|
6
|
+
* `CohortGossipV1` and accumulating/draining the registration-record deltas the renewal `touch`/`evicted`
|
|
7
|
+
* hooks emit — is split here so it is unit-testable without the timer or libp2p.
|
|
8
|
+
*
|
|
9
|
+
* db-p2p (not db-core) owns the cadence because the round interval, the peer-key signature on the gossip
|
|
10
|
+
* envelope, and the per-coord inbound routing all live at the FRET/libp2p boundary; db-core supplies the
|
|
11
|
+
* pure substrate (`selfWillingnessBits`, `toCohortTopicSummary`, `toGossipRecord`, the bus merge).
|
|
12
|
+
*/
|
|
13
|
+
import { bytesToB64url, toGossipRecord, willingnessBitsHex, selfWillingnessBits, } from "@optimystic/db-core";
|
|
14
|
+
/**
|
|
15
|
+
* Default gossip-round cadence in ms. There is no dedicated `gossip_round` constant in
|
|
16
|
+
* `docs/cohort-topic.md` §Configuration, so this is a derived, injectable default on the order of one
|
|
17
|
+
* round — a few seconds, aligned with the traffic window / ping cadence (`ping_interval = ttl/3` is
|
|
18
|
+
* 30 s Core, so a sub-round of that). The membership refresh (`T_membership_refresh`, 5 min) and the
|
|
19
|
+
* demotion hysteresis (`T_demote`, 5 min) are gated by elapsed-time **inside** their modules, so the
|
|
20
|
+
* driver can tick fast and let those modules decide when to act.
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_GOSSIP_INTERVAL_MS = 5_000;
|
|
23
|
+
/** Build an empty {@link PendingDeltas} queue. */
|
|
24
|
+
export function createPendingDeltas() {
|
|
25
|
+
const records = new Map();
|
|
26
|
+
const evicted = new Map();
|
|
27
|
+
const keyOf = (topicId, participantId) => `${bytesToB64url(topicId)}|${bytesToB64url(participantId)}`;
|
|
28
|
+
return {
|
|
29
|
+
touch(rec) {
|
|
30
|
+
const key = keyOf(rec.topicId, rec.participantId);
|
|
31
|
+
const held = records.get(key);
|
|
32
|
+
// Last-writer-wins on lastPing: never let an older touch shadow a newer one queued the same round.
|
|
33
|
+
if (held === undefined || rec.lastPing >= held.lastPing) {
|
|
34
|
+
records.set(key, rec);
|
|
35
|
+
}
|
|
36
|
+
evicted.delete(key); // a live touch supersedes a pending eviction (eviction-vs-late-touch)
|
|
37
|
+
},
|
|
38
|
+
evicted(rec) {
|
|
39
|
+
const key = keyOf(rec.topicId, rec.participantId);
|
|
40
|
+
evicted.set(key, { topicId: bytesToB64url(rec.topicId), participantId: bytesToB64url(rec.participantId) });
|
|
41
|
+
records.delete(key);
|
|
42
|
+
},
|
|
43
|
+
isEmpty() {
|
|
44
|
+
return records.size === 0 && evicted.size === 0;
|
|
45
|
+
},
|
|
46
|
+
drain() {
|
|
47
|
+
const out = {
|
|
48
|
+
records: [...records.values()].map(toGossipRecord),
|
|
49
|
+
evicted: [...evicted.values()],
|
|
50
|
+
};
|
|
51
|
+
records.clear();
|
|
52
|
+
evicted.clear();
|
|
53
|
+
return out;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Assemble one round's {@link CohortGossipV1}, or `undefined` when this engine is **idle** — no resident
|
|
59
|
+
* topics and no pending deltas, so it has nothing for siblings and the host skips the broadcast (idle
|
|
60
|
+
* empty engines cost no gossip). `willingnessBits` is `profile ∧ load` ({@link selfWillingnessBits}); the
|
|
61
|
+
* `signature` slot is left empty for the host's peer-key signer to fill before broadcast.
|
|
62
|
+
*/
|
|
63
|
+
export function buildCohortGossip(i) {
|
|
64
|
+
if (i.topicSummaries.length === 0 && i.records.length === 0 && i.evicted.length === 0) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const g = {
|
|
68
|
+
v: 1,
|
|
69
|
+
fromMember: i.fromMember,
|
|
70
|
+
coord: i.coord,
|
|
71
|
+
cohortEpoch: i.cohortEpoch,
|
|
72
|
+
willingnessBits: willingnessBitsHex(selfWillingnessBits(i.profile, i.barometer)),
|
|
73
|
+
loadBuckets: i.barometer.loadBuckets(),
|
|
74
|
+
windowSeconds: i.windowSeconds,
|
|
75
|
+
topicSummaries: i.topicSummaries,
|
|
76
|
+
timestamp: i.timestamp,
|
|
77
|
+
signature: "",
|
|
78
|
+
};
|
|
79
|
+
if (i.records.length > 0) {
|
|
80
|
+
g.records = i.records;
|
|
81
|
+
}
|
|
82
|
+
if (i.evicted.length > 0) {
|
|
83
|
+
g.evicted = i.evicted;
|
|
84
|
+
}
|
|
85
|
+
return g;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=cohort-gossip-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cohort-gossip-driver.js","sourceRoot":"","sources":["../../../src/cohort-topic/cohort-gossip-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACN,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,mBAAmB,GAQnB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,KAAK,CAAC;AAmBhD,kDAAkD;AAClD,MAAM,UAAU,mBAAmB;IAClC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8B,CAAC;IACtD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;IACrD,MAAM,KAAK,GAAG,CAAC,OAAmB,EAAE,aAAyB,EAAU,EAAE,CACxE,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;IAC7D,OAAO;QACN,KAAK,CAAC,GAAuB;YAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;YAClD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,mGAAmG;YACnG,IAAI,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACzD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACvB,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,sEAAsE;QAC5F,CAAC;QACD,OAAO,CAAC,GAAuB;YAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;YAC3G,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,OAAO;YACN,OAAO,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;QACjD,CAAC;QACD,KAAK;YACJ,MAAM,GAAG,GAAG;gBACX,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC;gBAClD,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;aAC9B,CAAC;YACF,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO,GAAG,CAAC;QACZ,CAAC;KACD,CAAC;AACH,CAAC;AA0BD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAoB;IACrD,IAAI,CAAC,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvF,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,CAAC,GAAmB;QACzB,CAAC,EAAE,CAAC;QACJ,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,eAAe,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;QAChF,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;QACtC,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,cAAc,EAAE,CAAC,CAAC,cAAc;QAChC,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,EAAE;KACb,CAAC;IACF,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,OAAO,CAAC,CAAC;AACV,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ICohortGossipTransport, PeerRef, RingCoord } from "@optimystic/db-core";
|
|
2
|
+
import type { Libp2p } from "libp2p";
|
|
3
|
+
/** Resolves the cohort owning a coord to its libp2p peer-id strings (FRET two-sided assembly). */
|
|
4
|
+
export interface CohortPeerResolver {
|
|
5
|
+
/** Peer-id strings of the cohort owning `coord`, up to `wants` members. */
|
|
6
|
+
cohortPeers(coord: RingCoord, wants: number): string[];
|
|
7
|
+
}
|
|
8
|
+
export interface FretCohortGossipTransportOptions {
|
|
9
|
+
readonly gossipProtocol?: string;
|
|
10
|
+
/** Cohort fan-out for a broadcast. Default 16. */
|
|
11
|
+
readonly wants?: number;
|
|
12
|
+
/** This node's peer-id string — excluded from its own broadcasts. */
|
|
13
|
+
readonly selfPeerId: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* FRET-backed {@link ICohortGossipTransport}: intra-cohort gossip over
|
|
17
|
+
* `/optimystic/cohort-topic/1.0.0/cohort-gossip`. `broadcast` fans a frame out to the FRET-assembled
|
|
18
|
+
* cohort for `coord` (fire-and-forget, self excluded); inbound frames arrive through the host's gossip
|
|
19
|
+
* protocol handler, which calls {@link deliver}. Subscribers registered via `onMessage` see every
|
|
20
|
+
* delivered frame.
|
|
21
|
+
*/
|
|
22
|
+
export declare class FretCohortGossipTransport implements ICohortGossipTransport {
|
|
23
|
+
private readonly node;
|
|
24
|
+
private readonly resolver;
|
|
25
|
+
private readonly handlers;
|
|
26
|
+
private readonly gossipProtocol;
|
|
27
|
+
private readonly wants;
|
|
28
|
+
private readonly selfPeerId;
|
|
29
|
+
constructor(node: Libp2p, resolver: CohortPeerResolver, options: FretCohortGossipTransportOptions);
|
|
30
|
+
broadcast(coord: RingCoord, msg: Uint8Array): void;
|
|
31
|
+
/**
|
|
32
|
+
* Fan `msg` out to the FRET-assembled cohort for `coord` over an arbitrary one-way `protocol` (self
|
|
33
|
+
* excluded, fire-and-forget). Reuses the cohort peer resolution so the `promote` protocol's
|
|
34
|
+
* promotion/demotion notice broadcast shares the gossip transport's wiring; a single unreachable
|
|
35
|
+
* member is recovered by the cohort's next convergence round, so per-peer failures are swallowed.
|
|
36
|
+
*/
|
|
37
|
+
broadcastOver(protocol: string, coord: RingCoord, msg: Uint8Array): void;
|
|
38
|
+
onMessage(handler: (from: PeerRef, msg: Uint8Array) => void): () => void;
|
|
39
|
+
/** Feed an inbound gossip frame (called by the host's `/cohort-gossip` protocol handler). */
|
|
40
|
+
deliver(fromPeerId: string, msg: Uint8Array): void;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=cohort-gossip-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cohort-gossip-transport.d.ts","sourceRoot":"","sources":["../../../src/cohort-topic/cohort-gossip-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMrC,kGAAkG;AAClG,MAAM,WAAW,kBAAkB;IAClC,2EAA2E;IAC3E,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACvD;AAED,MAAM,WAAW,gCAAgC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,qBAAa,yBAA0B,YAAW,sBAAsB;IAM3D,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAU,OAAO,CAAC,QAAQ,CAAC,QAAQ;IALpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuD;IAChF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAEP,IAAI,EAAE,MAAM,EAAmB,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,gCAAgC;IAMnI,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;IAIlD;;;;;OAKG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;IAWxE,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxE,6FAA6F;IAC7F,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;CAMlD"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { peerIdFromString } from "@libp2p/peer-id";
|
|
2
|
+
import { peerIdToBytes } from "./peer-codec.js";
|
|
3
|
+
import { sendOneWay } from "./stream-util.js";
|
|
4
|
+
import { PROTOCOL_COHORT_GOSSIP } from "./protocols.js";
|
|
5
|
+
/**
|
|
6
|
+
* FRET-backed {@link ICohortGossipTransport}: intra-cohort gossip over
|
|
7
|
+
* `/optimystic/cohort-topic/1.0.0/cohort-gossip`. `broadcast` fans a frame out to the FRET-assembled
|
|
8
|
+
* cohort for `coord` (fire-and-forget, self excluded); inbound frames arrive through the host's gossip
|
|
9
|
+
* protocol handler, which calls {@link deliver}. Subscribers registered via `onMessage` see every
|
|
10
|
+
* delivered frame.
|
|
11
|
+
*/
|
|
12
|
+
export class FretCohortGossipTransport {
|
|
13
|
+
node;
|
|
14
|
+
resolver;
|
|
15
|
+
handlers = new Set();
|
|
16
|
+
gossipProtocol;
|
|
17
|
+
wants;
|
|
18
|
+
selfPeerId;
|
|
19
|
+
constructor(node, resolver, options) {
|
|
20
|
+
this.node = node;
|
|
21
|
+
this.resolver = resolver;
|
|
22
|
+
this.gossipProtocol = options.gossipProtocol ?? PROTOCOL_COHORT_GOSSIP;
|
|
23
|
+
this.wants = options.wants ?? 16;
|
|
24
|
+
this.selfPeerId = options.selfPeerId;
|
|
25
|
+
}
|
|
26
|
+
broadcast(coord, msg) {
|
|
27
|
+
this.broadcastOver(this.gossipProtocol, coord, msg);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Fan `msg` out to the FRET-assembled cohort for `coord` over an arbitrary one-way `protocol` (self
|
|
31
|
+
* excluded, fire-and-forget). Reuses the cohort peer resolution so the `promote` protocol's
|
|
32
|
+
* promotion/demotion notice broadcast shares the gossip transport's wiring; a single unreachable
|
|
33
|
+
* member is recovered by the cohort's next convergence round, so per-peer failures are swallowed.
|
|
34
|
+
*/
|
|
35
|
+
broadcastOver(protocol, coord, msg) {
|
|
36
|
+
for (const peerStr of this.resolver.cohortPeers(coord, this.wants)) {
|
|
37
|
+
if (peerStr === this.selfPeerId) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
void sendOneWay(this.node, peerIdFromString(peerStr), protocol, msg).catch(() => {
|
|
41
|
+
// Best-effort: a single unreachable member is recovered by the next round.
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
onMessage(handler) {
|
|
46
|
+
this.handlers.add(handler);
|
|
47
|
+
return () => this.handlers.delete(handler);
|
|
48
|
+
}
|
|
49
|
+
/** Feed an inbound gossip frame (called by the host's `/cohort-gossip` protocol handler). */
|
|
50
|
+
deliver(fromPeerId, msg) {
|
|
51
|
+
const from = { id: peerIdToBytes(fromPeerId) };
|
|
52
|
+
for (const handler of this.handlers) {
|
|
53
|
+
handler(from, msg);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=cohort-gossip-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cohort-gossip-transport.js","sourceRoot":"","sources":["../../../src/cohort-topic/cohort-gossip-transport.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAgBxD;;;;;;GAMG;AACH,MAAM,OAAO,yBAAyB;IAMR;IAA+B;IAL3C,QAAQ,GAAG,IAAI,GAAG,EAA4C,CAAC;IAC/D,cAAc,CAAS;IACvB,KAAK,CAAS;IACd,UAAU,CAAS;IAEpC,YAA6B,IAAY,EAAmB,QAA4B,EAAE,OAAyC;QAAtG,SAAI,GAAJ,IAAI,CAAQ;QAAmB,aAAQ,GAAR,QAAQ,CAAoB;QACvF,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,sBAAsB,CAAC;QACvE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACtC,CAAC;IAED,SAAS,CAAC,KAAgB,EAAE,GAAe;QAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,QAAgB,EAAE,KAAgB,EAAE,GAAe;QAChE,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACpE,IAAI,OAAO,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,SAAS;YACV,CAAC;YACD,KAAK,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC/E,2EAA2E;YAC5E,CAAC,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,SAAS,CAAC,OAAiD;QAC1D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,6FAA6F;IAC7F,OAAO,CAAC,UAAkB,EAAE,GAAe;QAC1C,MAAM,IAAI,GAAY,EAAE,EAAE,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;QACxD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACpB,CAAC;IACF,CAAC;CACD"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — the **FRET-ring direct trust anchor** (db-p2p side of `IMembershipTrustAnchor`).
|
|
3
|
+
*
|
|
4
|
+
* `cohort-topic-trust-anchor-core` added the db-core trust gate (`membership/verifier.ts`): a (re)fetched
|
|
5
|
+
* `MembershipCertV1` is believed only if it is self-consistent **and** anchored by a trust root, the
|
|
6
|
+
* injected direct anchor, or the attestation chain — else it falls to interim trust-on-first-use. db-core
|
|
7
|
+
* ships only `noAuthorityTrustAnchor` (every coord `"unknown"`), so until db-p2p binds a real anchor the
|
|
8
|
+
* gate stays at TOFU. This module binds the **direct anchor** to the one coord→keyset authority FRET
|
|
9
|
+
* exposes today.
|
|
10
|
+
*
|
|
11
|
+
* **What authority FRET offers.** p2p-fret 0.5.0 has **no transferable stabilization proof** — there is no
|
|
12
|
+
* membership-cert/attestation API and the cert's `fretAttestation` field is never populated. The only
|
|
13
|
+
* coord→keyset authority FRET offers is `assembleCohort(coord, wants)` — the ring's two-sided closest-`k`
|
|
14
|
+
* selection — which is **local**: it answers correctly only for coords the node's routing table actually
|
|
15
|
+
* covers (coords the node is near / serves). That is exactly the amplification-exposed `promote`-handler
|
|
16
|
+
* path (`verifyAndApplyNotice` verifies against `target.servedCoord`, a coord the node serves), so binding
|
|
17
|
+
* the anchor to `assembleCohort` rejects the forged-cert attack precisely where the core ticket calls it out.
|
|
18
|
+
*
|
|
19
|
+
* **The rule (`directAnchor(cert, tier)`):**
|
|
20
|
+
*
|
|
21
|
+
* - **Committed tiers (T0/T1)** route to the tx-log commit certificate, not the FRET ring, so this anchor
|
|
22
|
+
* returns `"unknown"` for them — it composes with (does not fight) the future tx-log anchor.
|
|
23
|
+
* - **No local authority** — the node cannot cover the coord (cold/partitioned table, or a distant coord the
|
|
24
|
+
* node is nowhere near, so `assembleCohort` does not yield a populated neighborhood the node is part of) →
|
|
25
|
+
* `"unknown"`. The db-core gate then falls through to the chain / interim TOFU, so distant verification is
|
|
26
|
+
* never broken or falsely rejected.
|
|
27
|
+
* - **Covered coord** — compare the cert's **signing quorum** (`cert.signers`, the `≥ minSigs` that actually
|
|
28
|
+
* signed) against the ring-expected cohort, widened by a small {@link FretTrustAnchorOptions.churnSlack} to
|
|
29
|
+
* tolerate stabilization skew (membership stabilizes slightly behind the live ring):
|
|
30
|
+
* - every signer is in the slack-widened ring view → `"anchored"`;
|
|
31
|
+
* - the quorum is **disjoint** from the ring view (the ring says a wholly different cohort owns this coord)
|
|
32
|
+
* → `"rejected"` (a forgery — fatal even though it is internally self-consistent);
|
|
33
|
+
* - partial overlap beyond the slack (genuinely ambiguous within churn tolerance) → `"unknown"` (do not
|
|
34
|
+
* over-reject on transient skew; defer to the chain / TOFU).
|
|
35
|
+
*
|
|
36
|
+
* **Why anchoring on `signers` (not exact member-set equality) is sound and sufficient.** A forged cert
|
|
37
|
+
* must sign its threshold multisig with keys the adversary controls; those keys are not in the legitimate
|
|
38
|
+
* ring cohort, so a forged quorum is disjoint from `assembleCohort(coord)` → `"rejected"`. A legitimate
|
|
39
|
+
* cert's signers are real cohort members — present in the ring view (within slack) → `"anchored"`. The
|
|
40
|
+
* adversary cannot list real members as `signers` without their keys (the message multisig would not
|
|
41
|
+
* verify), so the quorum-subset test has teeth without demanding brittle full-set equality across churn.
|
|
42
|
+
*
|
|
43
|
+
* **db-core never imports FRET.** This adapter lives in db-p2p and depends only on the narrow
|
|
44
|
+
* {@link FretRingView} (satisfied by `FretService`), keeping the db-core trust gate transport-agnostic.
|
|
45
|
+
*/
|
|
46
|
+
import type { IMembershipTrustAnchor, MembershipCertV1, TrustAnchorVerdict } from "@optimystic/db-core";
|
|
47
|
+
/**
|
|
48
|
+
* The minimal slice of `FretService` the trust anchor needs: the ring's local two-sided cohort assembly
|
|
49
|
+
* around a coord, plus an optional partition signal. `FretService` satisfies it directly; a test (and the
|
|
50
|
+
* mock-mesh harness facade) can supply a stub with just `assembleCohort`.
|
|
51
|
+
*/
|
|
52
|
+
export interface FretRingView {
|
|
53
|
+
/** Two-sided closest-`wants` selection around `coord` from the node's routing table (peer-id strings). */
|
|
54
|
+
assembleCohort(coord: Uint8Array, wants: number, exclude?: Set<string>): string[];
|
|
55
|
+
/** Optional: `true` when the node believes it is partitioned (table unreliable → never reject). */
|
|
56
|
+
detectPartition?(): boolean;
|
|
57
|
+
}
|
|
58
|
+
/** Configuration for a {@link FretTrustAnchor}. */
|
|
59
|
+
export interface FretTrustAnchorOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Requested cohort size — must match the host's `wantK`, so the ring view the anchor computes lines up
|
|
62
|
+
* with the cohort the cohort-side `cohortAround` published the cert over.
|
|
63
|
+
*/
|
|
64
|
+
readonly k: number;
|
|
65
|
+
/** This node's own peer-id string — used for the coverage check (the node must be in the coord's cohort). */
|
|
66
|
+
readonly selfPeerId: string;
|
|
67
|
+
/**
|
|
68
|
+
* Stabilization-skew slack: the ring-expected cohort is widened to `k + churnSlack` members before the
|
|
69
|
+
* quorum-subset test, so a legit cert whose signing quorum lags the live ring by a rotation or two is
|
|
70
|
+
* still `"anchored"`. Kept small so a disjoint forged keyset cannot hide in the slack. Default
|
|
71
|
+
* {@link DEFAULT_CHURN_SLACK}.
|
|
72
|
+
*/
|
|
73
|
+
readonly churnSlack?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Highest tier whose membership is anchored in the tx-log commit certificate (T0/T1), for which this
|
|
76
|
+
* FRET-ring anchor has no authority and returns `"unknown"`. Mirrors the membership-source dispatch
|
|
77
|
+
* (`createMembershipSourceRouter` treats tier 0/1 as committed). Default {@link DEFAULT_MAX_NO_POW_TIER}.
|
|
78
|
+
*/
|
|
79
|
+
readonly maxCommittedTier?: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Default stabilization-skew slack (`churn_slack ≈ 2`): a legit cert may lag the live ring by ~1–2 members
|
|
83
|
+
* (one rotates in, one rotates out) between stabilization and the verifier's table view; widening to
|
|
84
|
+
* `k + 2` admits that skew while still rejecting a wholly-disjoint forged keyset.
|
|
85
|
+
*/
|
|
86
|
+
export declare const DEFAULT_CHURN_SLACK = 2;
|
|
87
|
+
/**
|
|
88
|
+
* The FRET-ring {@link IMembershipTrustAnchor}: judges a cert's `coord → keyset` binding against the
|
|
89
|
+
* node's local FRET cohort assembly. See the module header for the full rule.
|
|
90
|
+
*/
|
|
91
|
+
export declare class FretTrustAnchor implements IMembershipTrustAnchor {
|
|
92
|
+
private readonly fret;
|
|
93
|
+
private readonly k;
|
|
94
|
+
private readonly selfPeerId;
|
|
95
|
+
private readonly churnSlack;
|
|
96
|
+
private readonly maxCommittedTier;
|
|
97
|
+
constructor(fret: FretRingView, options: FretTrustAnchorOptions);
|
|
98
|
+
directAnchor(cert: MembershipCertV1, tier: number): TrustAnchorVerdict;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=fret-trust-anchor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fret-trust-anchor.d.ts","sourceRoot":"","sources":["../../../src/cohort-topic/fret-trust-anchor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAa,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAInH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B,0GAA0G;IAC1G,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC;IAClF,mGAAmG;IACnG,eAAe,CAAC,IAAI,OAAO,CAAC;CAC5B;AAED,mDAAmD;AACnD,MAAM,WAAW,sBAAsB;IACtC;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,6GAA6G;IAC7G,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;GAGG;AACH,qBAAa,eAAgB,YAAW,sBAAsB;IAMjD,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAEb,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,sBAAsB;IAOhF,YAAY,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB;CA0CtE"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — the **FRET-ring direct trust anchor** (db-p2p side of `IMembershipTrustAnchor`).
|
|
3
|
+
*
|
|
4
|
+
* `cohort-topic-trust-anchor-core` added the db-core trust gate (`membership/verifier.ts`): a (re)fetched
|
|
5
|
+
* `MembershipCertV1` is believed only if it is self-consistent **and** anchored by a trust root, the
|
|
6
|
+
* injected direct anchor, or the attestation chain — else it falls to interim trust-on-first-use. db-core
|
|
7
|
+
* ships only `noAuthorityTrustAnchor` (every coord `"unknown"`), so until db-p2p binds a real anchor the
|
|
8
|
+
* gate stays at TOFU. This module binds the **direct anchor** to the one coord→keyset authority FRET
|
|
9
|
+
* exposes today.
|
|
10
|
+
*
|
|
11
|
+
* **What authority FRET offers.** p2p-fret 0.5.0 has **no transferable stabilization proof** — there is no
|
|
12
|
+
* membership-cert/attestation API and the cert's `fretAttestation` field is never populated. The only
|
|
13
|
+
* coord→keyset authority FRET offers is `assembleCohort(coord, wants)` — the ring's two-sided closest-`k`
|
|
14
|
+
* selection — which is **local**: it answers correctly only for coords the node's routing table actually
|
|
15
|
+
* covers (coords the node is near / serves). That is exactly the amplification-exposed `promote`-handler
|
|
16
|
+
* path (`verifyAndApplyNotice` verifies against `target.servedCoord`, a coord the node serves), so binding
|
|
17
|
+
* the anchor to `assembleCohort` rejects the forged-cert attack precisely where the core ticket calls it out.
|
|
18
|
+
*
|
|
19
|
+
* **The rule (`directAnchor(cert, tier)`):**
|
|
20
|
+
*
|
|
21
|
+
* - **Committed tiers (T0/T1)** route to the tx-log commit certificate, not the FRET ring, so this anchor
|
|
22
|
+
* returns `"unknown"` for them — it composes with (does not fight) the future tx-log anchor.
|
|
23
|
+
* - **No local authority** — the node cannot cover the coord (cold/partitioned table, or a distant coord the
|
|
24
|
+
* node is nowhere near, so `assembleCohort` does not yield a populated neighborhood the node is part of) →
|
|
25
|
+
* `"unknown"`. The db-core gate then falls through to the chain / interim TOFU, so distant verification is
|
|
26
|
+
* never broken or falsely rejected.
|
|
27
|
+
* - **Covered coord** — compare the cert's **signing quorum** (`cert.signers`, the `≥ minSigs` that actually
|
|
28
|
+
* signed) against the ring-expected cohort, widened by a small {@link FretTrustAnchorOptions.churnSlack} to
|
|
29
|
+
* tolerate stabilization skew (membership stabilizes slightly behind the live ring):
|
|
30
|
+
* - every signer is in the slack-widened ring view → `"anchored"`;
|
|
31
|
+
* - the quorum is **disjoint** from the ring view (the ring says a wholly different cohort owns this coord)
|
|
32
|
+
* → `"rejected"` (a forgery — fatal even though it is internally self-consistent);
|
|
33
|
+
* - partial overlap beyond the slack (genuinely ambiguous within churn tolerance) → `"unknown"` (do not
|
|
34
|
+
* over-reject on transient skew; defer to the chain / TOFU).
|
|
35
|
+
*
|
|
36
|
+
* **Why anchoring on `signers` (not exact member-set equality) is sound and sufficient.** A forged cert
|
|
37
|
+
* must sign its threshold multisig with keys the adversary controls; those keys are not in the legitimate
|
|
38
|
+
* ring cohort, so a forged quorum is disjoint from `assembleCohort(coord)` → `"rejected"`. A legitimate
|
|
39
|
+
* cert's signers are real cohort members — present in the ring view (within slack) → `"anchored"`. The
|
|
40
|
+
* adversary cannot list real members as `signers` without their keys (the message multisig would not
|
|
41
|
+
* verify), so the quorum-subset test has teeth without demanding brittle full-set equality across churn.
|
|
42
|
+
*
|
|
43
|
+
* **db-core never imports FRET.** This adapter lives in db-p2p and depends only on the narrow
|
|
44
|
+
* {@link FretRingView} (satisfied by `FretService`), keeping the db-core trust gate transport-agnostic.
|
|
45
|
+
*/
|
|
46
|
+
import { b64urlToBytes, DEFAULT_MAX_NO_POW_TIER } from "@optimystic/db-core";
|
|
47
|
+
import { bytesToPeerIdString } from "./peer-codec.js";
|
|
48
|
+
/**
|
|
49
|
+
* Default stabilization-skew slack (`churn_slack ≈ 2`): a legit cert may lag the live ring by ~1–2 members
|
|
50
|
+
* (one rotates in, one rotates out) between stabilization and the verifier's table view; widening to
|
|
51
|
+
* `k + 2` admits that skew while still rejecting a wholly-disjoint forged keyset.
|
|
52
|
+
*/
|
|
53
|
+
export const DEFAULT_CHURN_SLACK = 2;
|
|
54
|
+
/**
|
|
55
|
+
* The FRET-ring {@link IMembershipTrustAnchor}: judges a cert's `coord → keyset` binding against the
|
|
56
|
+
* node's local FRET cohort assembly. See the module header for the full rule.
|
|
57
|
+
*/
|
|
58
|
+
export class FretTrustAnchor {
|
|
59
|
+
fret;
|
|
60
|
+
k;
|
|
61
|
+
selfPeerId;
|
|
62
|
+
churnSlack;
|
|
63
|
+
maxCommittedTier;
|
|
64
|
+
constructor(fret, options) {
|
|
65
|
+
this.fret = fret;
|
|
66
|
+
this.k = options.k;
|
|
67
|
+
this.selfPeerId = options.selfPeerId;
|
|
68
|
+
this.churnSlack = options.churnSlack ?? DEFAULT_CHURN_SLACK;
|
|
69
|
+
this.maxCommittedTier = options.maxCommittedTier ?? DEFAULT_MAX_NO_POW_TIER;
|
|
70
|
+
}
|
|
71
|
+
directAnchor(cert, tier) {
|
|
72
|
+
// Committed tiers (T0/T1) are the tx-log anchor's job, not the FRET ring's — defer.
|
|
73
|
+
if (tier <= this.maxCommittedTier) {
|
|
74
|
+
return "unknown";
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
// A partitioned table is unreliable: never reject a legit cert during a partition — defer to TOFU.
|
|
78
|
+
if (this.fret.detectPartition?.() === true) {
|
|
79
|
+
return "unknown";
|
|
80
|
+
}
|
|
81
|
+
const coord = b64urlToBytes(cert.cohortCoord);
|
|
82
|
+
const expected = this.fret.assembleCohort(coord, this.k);
|
|
83
|
+
// Coverage / local authority: a populated neighborhood the node is itself part of. A cold or
|
|
84
|
+
// partitioned table yields `< k`; a distant coord the node is nowhere near omits self. Either way
|
|
85
|
+
// the node cannot judge → `"unknown"` (no regression on coords nothing can anchor).
|
|
86
|
+
if (expected.length < this.k || !expected.includes(this.selfPeerId)) {
|
|
87
|
+
return "unknown";
|
|
88
|
+
}
|
|
89
|
+
const widened = new Set(this.fret.assembleCohort(coord, this.k + this.churnSlack));
|
|
90
|
+
// Decode the cert's signing quorum into FRET peer-id strings (the same form `assembleCohort` yields).
|
|
91
|
+
const signers = cert.signers.map((s) => bytesToPeerIdString(b64urlToBytes(s)));
|
|
92
|
+
if (signers.length === 0) {
|
|
93
|
+
return "unknown"; // nothing to judge (a self-consistent cert always has signers; defensive)
|
|
94
|
+
}
|
|
95
|
+
let inRing = 0;
|
|
96
|
+
for (const signer of signers) {
|
|
97
|
+
if (widened.has(signer)) {
|
|
98
|
+
inRing++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (inRing === signers.length) {
|
|
102
|
+
return "anchored"; // the whole signing quorum is a subset of a reasonable ring view
|
|
103
|
+
}
|
|
104
|
+
if (inRing === 0) {
|
|
105
|
+
return "rejected"; // a wholly-disjoint quorum — the ring knows a different cohort owns this coord
|
|
106
|
+
}
|
|
107
|
+
return "unknown"; // partial overlap beyond the slack — ambiguous churn; defer rather than over-reject
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Any decode failure on attacker-supplied bytes → the ring cannot judge it. Total, never throws.
|
|
111
|
+
return "unknown";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=fret-trust-anchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fret-trust-anchor.js","sourceRoot":"","sources":["../../../src/cohort-topic/fret-trust-anchor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAGH,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAsCtD;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAErC;;;GAGG;AACH,MAAM,OAAO,eAAe;IAME;IALZ,CAAC,CAAS;IACV,UAAU,CAAS;IACnB,UAAU,CAAS;IACnB,gBAAgB,CAAS;IAE1C,YAA6B,IAAkB,EAAE,OAA+B;QAAnD,SAAI,GAAJ,IAAI,CAAc;QAC9C,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC;QAC5D,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,uBAAuB,CAAC;IAC7E,CAAC;IAED,YAAY,CAAC,IAAsB,EAAE,IAAY;QAChD,oFAAoF;QACpF,IAAI,IAAI,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACnC,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,IAAI,CAAC;YACJ,mGAAmG;YACnG,IAAI,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,KAAK,IAAI,EAAE,CAAC;gBAC5C,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,MAAM,KAAK,GAAc,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;YACzD,6FAA6F;YAC7F,kGAAkG;YAClG,oFAAoF;YACpF,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;gBACrE,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YACnF,sGAAsG;YACtG,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,OAAO,SAAS,CAAC,CAAC,0EAA0E;YAC7F,CAAC;YACD,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACzB,MAAM,EAAE,CAAC;gBACV,CAAC;YACF,CAAC;YACD,IAAI,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC/B,OAAO,UAAU,CAAC,CAAC,iEAAiE;YACrF,CAAC;YACD,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClB,OAAO,UAAU,CAAC,CAAC,+EAA+E;YACnG,CAAC;YACD,OAAO,SAAS,CAAC,CAAC,oFAAoF;QACvG,CAAC;QAAC,MAAM,CAAC;YACR,iGAAiG;YACjG,OAAO,SAAS,CAAC;QAClB,CAAC;IACF,CAAC;CACD"}
|