@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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { ActionId, ClusterRecord, CollectionChangeEvent, CommitCert } from "@optimystic/db-core";
|
|
2
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays';
|
|
3
|
+
import { createLogger } from "../logger.js";
|
|
4
|
+
|
|
5
|
+
const log = createLogger('commit-cert');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build the {@link CommitCert} for a consensus-committed action from its {@link ClusterRecord}.
|
|
9
|
+
*
|
|
10
|
+
* The threshold signature is the cluster's `approve` **commit** signatures — the per-member Ed25519
|
|
11
|
+
* commit votes the cohort already produced over the commit hash — concatenated in ascending
|
|
12
|
+
* signer-id order, so the blob is reproducible and `signers[i]` aligns with chunk `i` (the
|
|
13
|
+
* collected-multisig convention the cohort-topic verifier uses). This is the **authoritative**
|
|
14
|
+
* commit cert: reactivity reuses these exact bytes as a notification's signature and never re-signs.
|
|
15
|
+
*
|
|
16
|
+
* `signedPayload` is the exact byte preimage every approving member signed — the cluster commit-vote
|
|
17
|
+
* payload `utf8(commitHash + ":approve")`, identical across signers. The caller supplies it (this stays
|
|
18
|
+
* pure and does **not** recompute the commit hash, since the canonical-JSON helper that derives it is
|
|
19
|
+
* private to `ClusterMember` and duplicating it here risks drift). Reactivity sets a notification's
|
|
20
|
+
* `digest` to base64url(signedPayload) so a subscriber's threshold-verify over `digest` reproduces the
|
|
21
|
+
* exact signed image. Pure.
|
|
22
|
+
*/
|
|
23
|
+
export function buildCommitCert(record: ClusterRecord, minSigs: number, signedPayload: Uint8Array): CommitCert {
|
|
24
|
+
const approvals = Object.entries(record.commits)
|
|
25
|
+
.filter(([, sig]) => sig.type === 'approve')
|
|
26
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
27
|
+
const signers = approvals.map(([peerId]) => peerId);
|
|
28
|
+
const thresholdSig = concatBytes(approvals.map(([, sig]) => uint8ArrayFromString(sig.signature, 'base64url')));
|
|
29
|
+
return { thresholdSig, signers, minSigs, signedPayload };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The deterministic change-event/notification `actionId` for a durable invalidation, derived from the
|
|
34
|
+
* `(invalidatedActionId, disputeId)` pair that uniquely identifies the reversal. An invalidation has no
|
|
35
|
+
* transaction `actionId` of its own (it reverses one), but reactivity keys both the captured commit cert
|
|
36
|
+
* and the emitted {@link CollectionChangeEvent} by an `actionId` so the bridge's cert extractor can
|
|
37
|
+
* resolve the reused cert. Both the cluster member's cert capture and the invalidation-apply sink that
|
|
38
|
+
* emits the change event derive this same id from the same pair, closing the loop. Distinct from the
|
|
39
|
+
* per-block deterministic revert `actionId` in `dispute/invalidation.ts` (which also carries blockId+rev);
|
|
40
|
+
* this is the single transaction-level id for the whole reversal.
|
|
41
|
+
*/
|
|
42
|
+
export function invalidationActionId(invalidatedActionId: ActionId, disputeId: string): ActionId {
|
|
43
|
+
return `inv:${invalidatedActionId}:${disputeId}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function concatBytes(parts: readonly Uint8Array[]): Uint8Array {
|
|
47
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
48
|
+
const out = new Uint8Array(total);
|
|
49
|
+
let offset = 0;
|
|
50
|
+
for (const part of parts) {
|
|
51
|
+
out.set(part, offset);
|
|
52
|
+
offset += part.length;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Default retention for a captured commit cert — long enough for the post-commit change event to
|
|
58
|
+
* resolve it, short enough that the store stays bounded under sustained commit load. */
|
|
59
|
+
export const DEFAULT_COMMIT_CERT_TTL_MS = 60_000;
|
|
60
|
+
|
|
61
|
+
/** Default cap on retained certs — a backstop against unbounded growth if commits ever stop emitting. */
|
|
62
|
+
export const DEFAULT_COMMIT_CERT_MAX_ENTRIES = 4096;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Bounded, TTL'd cache of recently-committed {@link CommitCert}s keyed by `actionId`. The cluster
|
|
66
|
+
* member records a cert here just before consensus applies the commit to local storage; the
|
|
67
|
+
* change-notifier bridge's extractor reads it when the StorageRepo emits the matching
|
|
68
|
+
* {@link CollectionChangeEvent}. Both happen on the same node within one commit, so the window is
|
|
69
|
+
* tiny — but a TTL + cap keep the map from leaking if a commit ever lands without emitting a change
|
|
70
|
+
* event (e.g. an idempotent re-commit, or a path with no catch-all subscriber).
|
|
71
|
+
*/
|
|
72
|
+
export interface CommitCertStore {
|
|
73
|
+
/** Record `cert` for `actionId` (overwrites any prior entry). `now` is unix ms (injectable for tests). */
|
|
74
|
+
put(actionId: ActionId, cert: CommitCert, now?: number): void;
|
|
75
|
+
/** The cert for `actionId` if still retained, else `undefined`. */
|
|
76
|
+
get(actionId: ActionId, now?: number): CommitCert | undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface CommitCertStoreOptions {
|
|
80
|
+
/** Retention per entry (ms). Default {@link DEFAULT_COMMIT_CERT_TTL_MS}. */
|
|
81
|
+
readonly ttlMs?: number;
|
|
82
|
+
/** Maximum retained entries (oldest evicted first). Default {@link DEFAULT_COMMIT_CERT_MAX_ENTRIES}. */
|
|
83
|
+
readonly maxEntries?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Build an in-memory {@link CommitCertStore} (insertion-ordered Map → recency for eviction). */
|
|
87
|
+
export function createCommitCertStore(options: CommitCertStoreOptions = {}): CommitCertStore {
|
|
88
|
+
const ttlMs = options.ttlMs ?? DEFAULT_COMMIT_CERT_TTL_MS;
|
|
89
|
+
const maxEntries = options.maxEntries ?? DEFAULT_COMMIT_CERT_MAX_ENTRIES;
|
|
90
|
+
const entries = new Map<ActionId, { cert: CommitCert; expiresAt: number }>();
|
|
91
|
+
|
|
92
|
+
const dropExpired = (now: number): void => {
|
|
93
|
+
for (const [actionId, entry] of entries) {
|
|
94
|
+
if (entry.expiresAt <= now) {
|
|
95
|
+
entries.delete(actionId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
put(actionId, cert, now = Date.now()): void {
|
|
102
|
+
dropExpired(now);
|
|
103
|
+
// Re-insert so a refreshed key moves to the most-recent end (Map preserves insertion order).
|
|
104
|
+
entries.delete(actionId);
|
|
105
|
+
entries.set(actionId, { cert, expiresAt: now + ttlMs });
|
|
106
|
+
while (entries.size > maxEntries) {
|
|
107
|
+
const oldest = entries.keys().next().value;
|
|
108
|
+
if (oldest === undefined) break;
|
|
109
|
+
entries.delete(oldest);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
get(actionId, now = Date.now()): CommitCert | undefined {
|
|
113
|
+
const entry = entries.get(actionId);
|
|
114
|
+
if (!entry) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (entry.expiresAt <= now) {
|
|
118
|
+
entries.delete(actionId);
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
return entry.cert;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The bridge's `extractCommitCert` over a {@link CommitCertStore}: resolve a change event's committed
|
|
128
|
+
* action to the cluster commit cert captured for it. Returns `undefined` (logged) when no cert is
|
|
129
|
+
* retained — the bridge then skips origination rather than fabricating an unsigned notification.
|
|
130
|
+
*/
|
|
131
|
+
export function makeClusterCommitCertExtractor(store: CommitCertStore): (event: CollectionChangeEvent) => CommitCert | undefined {
|
|
132
|
+
return (event) => {
|
|
133
|
+
const cert = store.get(event.actionId);
|
|
134
|
+
if (!cert) {
|
|
135
|
+
log('no commit cert retained for actionId=%s (collection=%s rev=%d); skipping origination', event.actionId, event.collectionId, event.rev);
|
|
136
|
+
}
|
|
137
|
+
return cert;
|
|
138
|
+
};
|
|
139
|
+
}
|
package/src/cluster/service.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { pipe } from 'it-pipe';
|
|
2
2
|
import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed';
|
|
3
|
-
import
|
|
3
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
4
|
+
import type { Startable, Logger, Stream, Connection, StreamHandler, PeerId } from '@libp2p/interface';
|
|
4
5
|
import type { ICluster, ClusterRecord } from '@optimystic/db-core';
|
|
6
|
+
import { encodePeers, type RedirectPayload } from '../repo/redirect.js';
|
|
7
|
+
import { toClusterErrorEnvelope } from './cluster-error.js';
|
|
5
8
|
import type { Uint8ArrayList } from 'uint8arraylist';
|
|
6
9
|
|
|
7
10
|
interface BaseComponents {
|
|
@@ -14,6 +17,17 @@ interface BaseComponents {
|
|
|
14
17
|
|
|
15
18
|
export interface ClusterServiceComponents extends BaseComponents {
|
|
16
19
|
cluster: ICluster
|
|
20
|
+
/**
|
|
21
|
+
* This node's own peer id, used to decide whether we are a member of a
|
|
22
|
+
* cluster record's peer set. When absent the service cannot scope membership
|
|
23
|
+
* and processes every update locally (no redirect).
|
|
24
|
+
*/
|
|
25
|
+
peerId?: PeerId
|
|
26
|
+
/**
|
|
27
|
+
* Optional resolver for a peer's dialable multiaddrs, used as a fallback when
|
|
28
|
+
* a redirect target has no multiaddrs embedded in `record.peers`.
|
|
29
|
+
*/
|
|
30
|
+
getConnectionAddrs?: (peerId: PeerId) => string[]
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
export interface ClusterServiceInit {
|
|
@@ -22,17 +36,13 @@ export interface ClusterServiceInit {
|
|
|
22
36
|
maxInboundStreams?: number,
|
|
23
37
|
maxOutboundStreams?: number,
|
|
24
38
|
logPrefix?: string,
|
|
25
|
-
kBucketSize?: number,
|
|
26
|
-
configuredClusterSize?: number,
|
|
27
|
-
allowClusterDownsize?: boolean,
|
|
28
|
-
clusterSizeTolerance?: number,
|
|
29
39
|
/**
|
|
30
40
|
* Responsibility K - the replica set size for determining cluster membership.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* Default: 1 (only
|
|
41
|
+
* When the cluster record's peer set is smaller than this, the mesh is treated
|
|
42
|
+
* as "small" and the update is processed locally regardless of membership. When
|
|
43
|
+
* the peer set is at least this size and we are not a member, the update is
|
|
44
|
+
* redirected to the responsible peers.
|
|
45
|
+
* Default: 1 (only members process; any larger non-member set redirects)
|
|
36
46
|
*/
|
|
37
47
|
responsibilityK?: number,
|
|
38
48
|
}
|
|
@@ -52,6 +62,8 @@ export class ClusterService implements Startable {
|
|
|
52
62
|
private readonly cluster: ICluster;
|
|
53
63
|
private readonly components: ClusterServiceComponents;
|
|
54
64
|
private running: boolean;
|
|
65
|
+
/** Responsibility K - small-mesh bypass threshold for redirect decisions */
|
|
66
|
+
private readonly responsibilityK: number;
|
|
55
67
|
|
|
56
68
|
constructor(components: ClusterServiceComponents, init: ClusterServiceInit = {}) {
|
|
57
69
|
this.components = components;
|
|
@@ -61,10 +73,74 @@ export class ClusterService implements Startable {
|
|
|
61
73
|
this.log = components.logger.forComponent(init.logPrefix ?? 'db-p2p:cluster');
|
|
62
74
|
this.cluster = components.cluster;
|
|
63
75
|
this.running = false;
|
|
76
|
+
this.responsibilityK = init.responsibilityK ?? 1;
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
readonly [Symbol.toStringTag] = '@libp2p/cluster';
|
|
67
80
|
|
|
81
|
+
private getSelfId(): PeerId | undefined {
|
|
82
|
+
if (this.components.peerId) return this.components.peerId;
|
|
83
|
+
return (this.components as any).libp2p?.peerId as PeerId | undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private getPeerAddrs(id: string): string[] {
|
|
87
|
+
let pid: PeerId;
|
|
88
|
+
try {
|
|
89
|
+
pid = peerIdFromString(id);
|
|
90
|
+
} catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
if (this.components.getConnectionAddrs) return this.components.getConnectionAddrs(pid);
|
|
94
|
+
const libp2p = (this.components as any).libp2p;
|
|
95
|
+
if (!libp2p?.getConnections) return [];
|
|
96
|
+
const conns: any[] = libp2p.getConnections(pid) ?? [];
|
|
97
|
+
const addrs: string[] = [];
|
|
98
|
+
for (const c of conns) {
|
|
99
|
+
const addr = c.remoteAddr?.toString?.();
|
|
100
|
+
if (addr) addrs.push(addr);
|
|
101
|
+
}
|
|
102
|
+
return addrs;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Decide whether this node should redirect a cluster update instead of
|
|
107
|
+
* participating in its consensus.
|
|
108
|
+
*
|
|
109
|
+
* Membership is scoped against `record.peers` — the authoritative set the
|
|
110
|
+
* coordinator already computed and embedded (it only ever dials peers in this
|
|
111
|
+
* set). Using it directly (rather than independently recomputing the cluster
|
|
112
|
+
* from the key) is regression-proof against the "empty promises" symptom: a
|
|
113
|
+
* peer the coordinator legitimately included is, by construction, present in
|
|
114
|
+
* `record.peers` and is therefore never redirected.
|
|
115
|
+
*
|
|
116
|
+
* Returns a {@link RedirectPayload} when this node is not responsible, or null
|
|
117
|
+
* when the update should be processed locally (we are a member, the mesh is too
|
|
118
|
+
* small to scope, or we lack the identity/peer set to make a decision).
|
|
119
|
+
*/
|
|
120
|
+
checkRedirect(record: ClusterRecord): RedirectPayload | null {
|
|
121
|
+
const selfId = this.getSelfId();
|
|
122
|
+
if (!selfId) return null; // no identity → can't scope, process locally
|
|
123
|
+
|
|
124
|
+
const peers = record.peers ?? {};
|
|
125
|
+
const peerIds = Object.keys(peers);
|
|
126
|
+
if (peerIds.length === 0) return null; // nothing to scope against → process locally
|
|
127
|
+
|
|
128
|
+
const selfStr = selfId.toString();
|
|
129
|
+
const isMember = peerIds.includes(selfStr);
|
|
130
|
+
const smallMesh = peerIds.length < this.responsibilityK;
|
|
131
|
+
|
|
132
|
+
if (!smallMesh && !isMember) {
|
|
133
|
+
const others = peerIds.filter(id => id !== selfStr);
|
|
134
|
+
return encodePeers(others.map(id => {
|
|
135
|
+
const recAddrs = peers[id]?.multiaddrs ?? [];
|
|
136
|
+
const addrs = recAddrs.length > 0 ? recAddrs : this.getPeerAddrs(id);
|
|
137
|
+
return { id, addrs };
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
68
144
|
async start(): Promise<void> {
|
|
69
145
|
if (this.running) {
|
|
70
146
|
return;
|
|
@@ -87,51 +163,47 @@ export class ClusterService implements Startable {
|
|
|
87
163
|
this.running = false;
|
|
88
164
|
}
|
|
89
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Run a single decoded protocol message. An application-level throw
|
|
168
|
+
* (validation / signature / merge / consensus failure inside `cluster.update`)
|
|
169
|
+
* propagates to the caller, which turns it into a structured error envelope;
|
|
170
|
+
* a redirect or a successful {@link ClusterRecord} is returned as-is.
|
|
171
|
+
*/
|
|
172
|
+
private async processOperation(message: { operation: string; record: ClusterRecord }): Promise<unknown> {
|
|
173
|
+
if (message.operation === 'update') {
|
|
174
|
+
// Scope consensus to responsible peers: redirect when we are not a
|
|
175
|
+
// member of the record's authoritative peer set, otherwise process.
|
|
176
|
+
const redirect = this.checkRedirect(message.record);
|
|
177
|
+
return redirect ?? await this.cluster.update(message.record);
|
|
178
|
+
}
|
|
179
|
+
throw new Error(`Unknown operation: ${message.operation}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
90
182
|
private handleIncomingStream(stream: Stream, connection: Connection): void {
|
|
91
183
|
const peerId = connection.remotePeer;
|
|
92
184
|
|
|
93
185
|
const processStream = async function* (this: ClusterService, source: AsyncIterable<Uint8ArrayList>) {
|
|
94
186
|
for await (const msg of source) {
|
|
95
|
-
// Decode the message
|
|
187
|
+
// Decode the framing. A malformed/undecodable message is a transport
|
|
188
|
+
// fault handled by the outer abort path, not an application error.
|
|
96
189
|
const decoded = new TextDecoder().decode(msg.subarray());
|
|
97
190
|
const message = JSON.parse(decoded) as { operation: string; record: ClusterRecord };
|
|
98
191
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
192
|
+
// Application-level processing: surface any throw to the coordinator as
|
|
193
|
+
// a structured error envelope (closing the stream normally) instead of
|
|
194
|
+
// aborting, so the real cause — not an opaque StreamResetError — reaches
|
|
195
|
+
// the coordinator, which already enables debug logging. The abort path
|
|
196
|
+
// is reserved for genuinely unrecoverable framing/transport faults.
|
|
197
|
+
let response: unknown;
|
|
198
|
+
try {
|
|
199
|
+
response = await this.processOperation(message);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
this.log.error('error processing cluster %s from %p - %e', message.operation, peerId, err);
|
|
202
|
+
response = toClusterErrorEnvelope(err);
|
|
109
203
|
}
|
|
110
204
|
|
|
111
205
|
// Encode and yield the response
|
|
112
|
-
|
|
113
|
-
const rec = response as any;
|
|
114
|
-
this.log('cluster-service:pre-serialize', {
|
|
115
|
-
messageHash: rec?.messageHash,
|
|
116
|
-
responseType: typeof response,
|
|
117
|
-
hasPromises: 'promises' in (rec ?? {}),
|
|
118
|
-
hasCommits: 'commits' in (rec ?? {}),
|
|
119
|
-
promiseKeys: Object.keys(rec?.promises ?? {}),
|
|
120
|
-
commitKeys: Object.keys(rec?.commits ?? {}),
|
|
121
|
-
promiseValues: rec?.promises,
|
|
122
|
-
commitValues: rec?.commits
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
const serialized = JSON.stringify(response);
|
|
126
|
-
if (message.operation === 'update') {
|
|
127
|
-
const deserialized = JSON.parse(serialized);
|
|
128
|
-
this.log('cluster-service:post-serialize', {
|
|
129
|
-
messageHash: (deserialized as any)?.messageHash,
|
|
130
|
-
promiseKeys: Object.keys((deserialized as any)?.promises ?? {}),
|
|
131
|
-
commitKeys: Object.keys((deserialized as any)?.commits ?? {})
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
yield new TextEncoder().encode(serialized);
|
|
206
|
+
yield new TextEncoder().encode(JSON.stringify(response));
|
|
135
207
|
}
|
|
136
208
|
};
|
|
137
209
|
|