@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
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { IRepo, ClusterRecord, Signature, RepoMessage, ITransactionValidator, ClusterConsensusConfig } from "@optimystic/db-core";
|
|
1
|
+
import type { IRepo, ClusterRecord, Signature, RepoMessage, ITransactionValidator, ClusterConsensusConfig, CommitResult, BlockId, ActionId, ActionRev, CommitRequest, CommitCert, InvalidateRequest } from "@optimystic/db-core";
|
|
2
2
|
import type { ICluster } from "@optimystic/db-core";
|
|
3
3
|
import type { IPeerNetwork } from "@optimystic/db-core";
|
|
4
4
|
import { blockIdsForTransforms } from "@optimystic/db-core";
|
|
5
|
+
import { verifyInvalidationCertificate, type ArbitratorSetRecompute } from "../dispute/invalidation.js";
|
|
6
|
+
import { buildCommitCert, invalidationActionId } from "./commit-cert.js";
|
|
5
7
|
import { ClusterClient } from "./client.js";
|
|
6
8
|
import type { PeerId, PrivateKey } from "@libp2p/interface";
|
|
7
9
|
import { peerIdFromString } from "@libp2p/peer-id";
|
|
@@ -35,6 +37,54 @@ interface TransactionState {
|
|
|
35
37
|
lastUpdate: number;
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Actively reconciles a block this member committed without having seen the matching
|
|
42
|
+
* pend (cohort drift between the independent pend and commit cluster-transactions).
|
|
43
|
+
* Pulls the committed revision from a cohort peer that holds it and restores it into
|
|
44
|
+
* local storage. Injected so {@link ClusterMember} stays transport-agnostic — mirrors
|
|
45
|
+
* how `CoordinatorRepo` receives its `clusterLatestCallback`.
|
|
46
|
+
*
|
|
47
|
+
* @param blockId the under-replicated block to restore
|
|
48
|
+
* @param committed the committed `(actionId, rev)` agreed by consensus
|
|
49
|
+
* @param cohortPeerIds cohort members to pull from (self already excluded)
|
|
50
|
+
*/
|
|
51
|
+
export type ReconcileBlockCallback = (blockId: BlockId, committed: ActionRev, cohortPeerIds: string[]) => Promise<void>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sink for the {@link CommitCert} this member assembled at consensus, fired once per committed action
|
|
55
|
+
* **before** the commit is applied to local storage (so it is already retained when
|
|
56
|
+
* {@link StorageRepo.commit} emits the matching `CollectionChangeEvent` the reactivity bridge reads).
|
|
57
|
+
* The bytes are the cluster's own `approve` commit signatures, forwarded UNCHANGED — never re-signed.
|
|
58
|
+
* Optional; absent on nodes that do not originate reactivity notifications. A throwing sink is
|
|
59
|
+
* isolated + logged (it must never break consensus).
|
|
60
|
+
*/
|
|
61
|
+
export type CommitCertificateSink = (actionId: ActionId, cert: CommitCert) => void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Applies a consensus-ordered {@link InvalidateRequest} to local storage — the deterministic
|
|
65
|
+
* reversal every member runs once consensus on the invalidation is reached. The implementation
|
|
66
|
+
* recomputes the per-block as-if-`T_inv`-absent content, writes the compensating revisions, and
|
|
67
|
+
* appends the durable invalidation log entry (see `applyInvalidation` in the dispute module). It
|
|
68
|
+
* is injected so {@link ClusterMember} stays storage/log-agnostic (mirrors {@link ReconcileBlockCallback}).
|
|
69
|
+
*
|
|
70
|
+
* The certificate is verified by {@link ClusterMember} *before* the sink is invoked, so a sink
|
|
71
|
+
* implementation may assume `request.resolution` is already a valid challenger-wins certificate.
|
|
72
|
+
* A throwing sink is logged and tolerated (never resets the cluster stream); the in-memory dedup
|
|
73
|
+
* marker is rolled back so a re-broadcast can retry.
|
|
74
|
+
*/
|
|
75
|
+
export type InvalidationApplySink = (request: InvalidateRequest) => Promise<void>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Optional **layer-2** capability for invalidation-certificate verification: re-derives the
|
|
79
|
+
* legitimately-selected arbitrator set from this member's topology view and judges the carried set
|
|
80
|
+
* (see {@link ArbitratorSetRecompute}). Injected so {@link ClusterMember} stays network-agnostic; the
|
|
81
|
+
* composition root supplies it from FRET when available. When absent (or when the member cannot
|
|
82
|
+
* reconstruct the historical topology), {@link applyConsensusInvalidation} accepts a layer-1-valid
|
|
83
|
+
* certificate and logs that it applied an invalidation it could not fully anchor — the documented
|
|
84
|
+
* interim posture (see `tickets/plan/cohort-topic-membership-cert-trust-anchoring.md`).
|
|
85
|
+
*/
|
|
86
|
+
export type RecomputeArbitratorSetCapability = ArbitratorSetRecompute;
|
|
87
|
+
|
|
38
88
|
interface ClusterMemberComponents {
|
|
39
89
|
storageRepo: IRepo;
|
|
40
90
|
peerNetwork: IPeerNetwork;
|
|
@@ -48,6 +98,14 @@ interface ClusterMemberComponents {
|
|
|
48
98
|
reputation?: IPeerReputation;
|
|
49
99
|
consensusConfig?: ClusterConsensusConfig;
|
|
50
100
|
stateStore?: ITransactionStateStore;
|
|
101
|
+
/** Restores a block under-replicated by cohort drift; see {@link ReconcileBlockCallback}. */
|
|
102
|
+
reconcileBlock?: ReconcileBlockCallback;
|
|
103
|
+
/** Receives the consensus commit cert per committed action; see {@link CommitCertificateSink}. */
|
|
104
|
+
onCommitCertificate?: CommitCertificateSink;
|
|
105
|
+
/** Applies a consensus-ordered invalidation to local storage; see {@link InvalidationApplySink}. */
|
|
106
|
+
onInvalidate?: InvalidationApplySink;
|
|
107
|
+
/** Layer-2 arbitrator-set recompute for invalidation verification; see {@link RecomputeArbitratorSetCapability}. */
|
|
108
|
+
recomputeArbitratorSet?: RecomputeArbitratorSetCapability;
|
|
51
109
|
}
|
|
52
110
|
|
|
53
111
|
export function clusterMember(components: ClusterMemberComponents): ClusterMember {
|
|
@@ -62,13 +120,32 @@ export function clusterMember(components: ClusterMemberComponents): ClusterMembe
|
|
|
62
120
|
components.validator,
|
|
63
121
|
components.reputation,
|
|
64
122
|
components.consensusConfig,
|
|
65
|
-
components.stateStore
|
|
123
|
+
components.stateStore,
|
|
124
|
+
components.reconcileBlock,
|
|
125
|
+
components.onCommitCertificate,
|
|
126
|
+
components.onInvalidate,
|
|
127
|
+
components.recomputeArbitratorSet
|
|
66
128
|
);
|
|
67
129
|
}
|
|
68
130
|
|
|
69
131
|
// How long to keep executed transaction records (10 minutes)
|
|
70
132
|
const ExecutedTransactionTtlMs = 10 * 60 * 1000;
|
|
71
133
|
|
|
134
|
+
// Upper bound on an awaited active reconciliation of a divergent commit. Bounds the
|
|
135
|
+
// consensus path so a slow/unreachable cohort peer can't stall the cluster stream;
|
|
136
|
+
// a timeout is logged and tolerated (never thrown — that would reset the stream).
|
|
137
|
+
const ReconcileTimeoutMs = 5000;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* True when a thrown storage error reports a missing pending action — i.e. this
|
|
141
|
+
* member reached commit-consensus without having seen the matching pend phase.
|
|
142
|
+
* That is recoverable local divergence (reconciled via sync / read-repair), not
|
|
143
|
+
* a transaction fault, so it must not reset the cluster stream.
|
|
144
|
+
*/
|
|
145
|
+
function isMissingPendingActionError(err: unknown): boolean {
|
|
146
|
+
return err instanceof Error && /pending action .+ not found/i.test(err.message);
|
|
147
|
+
}
|
|
148
|
+
|
|
72
149
|
/**
|
|
73
150
|
* Handles cluster-side operations, managing promises and commits for cluster updates
|
|
74
151
|
* and coordinating with the local storage repo.
|
|
@@ -78,12 +155,15 @@ export class ClusterMember implements ICluster {
|
|
|
78
155
|
private activeTransactions: Map<string, TransactionState> = new Map();
|
|
79
156
|
// Track executed consensus transactions to prevent duplicate execution (messageHash -> executedAt timestamp)
|
|
80
157
|
private executedTransactions: Map<string, number> = new Map();
|
|
158
|
+
// Fast in-memory dedup for applied invalidations, keyed `${invalidatedActionId}:${disputeId}`.
|
|
159
|
+
// The durable source of truth is the invalidation log entry (Log.findInvalidation, re-checked
|
|
160
|
+
// inside the sink); this map only spares redundant work when the same invalidation reaches
|
|
161
|
+
// consensus twice (rebroadcast / sync) under different message hashes. (-> appliedAt timestamp)
|
|
162
|
+
private appliedInvalidations: Map<string, number> = new Map();
|
|
81
163
|
// Queue of transactions to clean up
|
|
82
164
|
private cleanupQueue: string[] = [];
|
|
83
165
|
// Serialize concurrent updates for the same transaction
|
|
84
166
|
private pendingUpdates: Map<string, Promise<ClusterRecord>> = new Map();
|
|
85
|
-
// Temporarily set during validateSignatures so verifySignature can access the record
|
|
86
|
-
private currentValidationRecord?: ClusterRecord;
|
|
87
167
|
// Interval handles for periodic cleanup (stored so dispose() can clear them)
|
|
88
168
|
private readonly expirationInterval: NodeJS.Timeout;
|
|
89
169
|
private readonly cleanupInterval: NodeJS.Timeout;
|
|
@@ -103,7 +183,11 @@ export class ClusterMember implements ICluster {
|
|
|
103
183
|
private readonly validator?: ITransactionValidator,
|
|
104
184
|
private readonly reputation?: IPeerReputation,
|
|
105
185
|
consensusConfig?: ClusterConsensusConfig,
|
|
106
|
-
private readonly stateStore?: ITransactionStateStore
|
|
186
|
+
private readonly stateStore?: ITransactionStateStore,
|
|
187
|
+
private readonly reconcileBlock?: ReconcileBlockCallback,
|
|
188
|
+
private readonly onCommitCertificate?: CommitCertificateSink,
|
|
189
|
+
private readonly onInvalidate?: InvalidationApplySink,
|
|
190
|
+
private readonly recomputeArbitratorSet?: RecomputeArbitratorSetCapability
|
|
107
191
|
) {
|
|
108
192
|
this.superMajorityThreshold = consensusConfig?.superMajorityThreshold ?? 1.0;
|
|
109
193
|
// Periodically clean up expired transactions (.unref() so tests/short-lived processes can exit)
|
|
@@ -440,27 +524,22 @@ export class ClusterMember implements ICluster {
|
|
|
440
524
|
}
|
|
441
525
|
|
|
442
526
|
private async validateSignatures(record: ClusterRecord): Promise<void> {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
this.reputation?.reportPeer(peerId, PenaltyReason.InvalidSignature, `promise:${record.messageHash}`);
|
|
450
|
-
throw new Error(`Invalid promise signature from ${peerId}`);
|
|
451
|
-
}
|
|
527
|
+
// Validate promise signatures
|
|
528
|
+
const promiseHash = await this.computePromiseHash(record);
|
|
529
|
+
for (const [peerId, signature] of Object.entries(record.promises)) {
|
|
530
|
+
if (!await this.verifySignature(record, peerId, promiseHash, signature)) {
|
|
531
|
+
this.reputation?.reportPeer(peerId, PenaltyReason.InvalidSignature, `promise:${record.messageHash}`);
|
|
532
|
+
throw new Error(`Invalid promise signature from ${peerId}`);
|
|
452
533
|
}
|
|
534
|
+
}
|
|
453
535
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
536
|
+
// Validate commit signatures
|
|
537
|
+
const commitHash = await this.computeCommitHash(record);
|
|
538
|
+
for (const [peerId, signature] of Object.entries(record.commits)) {
|
|
539
|
+
if (!await this.verifySignature(record, peerId, commitHash, signature)) {
|
|
540
|
+
this.reputation?.reportPeer(peerId, PenaltyReason.InvalidSignature, `commit:${record.messageHash}`);
|
|
541
|
+
throw new Error(`Invalid commit signature from ${peerId}`);
|
|
461
542
|
}
|
|
462
|
-
} finally {
|
|
463
|
-
this.currentValidationRecord = undefined;
|
|
464
543
|
}
|
|
465
544
|
}
|
|
466
545
|
|
|
@@ -496,8 +575,8 @@ export class ClusterMember implements ICluster {
|
|
|
496
575
|
return uint8ArrayToString(sigBytes, 'base64url');
|
|
497
576
|
}
|
|
498
577
|
|
|
499
|
-
private async verifySignature(peerId: string, hash: string, signature: Signature): Promise<boolean> {
|
|
500
|
-
const peerInfo =
|
|
578
|
+
private async verifySignature(record: ClusterRecord, peerId: string, hash: string, signature: Signature): Promise<boolean> {
|
|
579
|
+
const peerInfo = record.peers[peerId];
|
|
501
580
|
if (!peerInfo?.publicKey?.length) {
|
|
502
581
|
throw new Error(`No public key for peer ${peerId}`);
|
|
503
582
|
}
|
|
@@ -677,44 +756,305 @@ export class ClusterMember implements ICluster {
|
|
|
677
756
|
.catch(err => log('cluster-member:persist-executed-error', { messageHash: record.messageHash, error: (err as Error).message }));
|
|
678
757
|
|
|
679
758
|
try {
|
|
680
|
-
// Execute the operations - check return values for failures
|
|
681
759
|
for (const operation of record.message.operations) {
|
|
682
|
-
|
|
683
|
-
await this.storageRepo.get(operation.get);
|
|
684
|
-
} else if ('pend' in operation) {
|
|
685
|
-
const result = await this.storageRepo.pend(operation.pend);
|
|
686
|
-
if (!result.success) {
|
|
687
|
-
log('cluster-member:consensus-pend-failed', {
|
|
688
|
-
messageHash: record.messageHash,
|
|
689
|
-
actionId: operation.pend.actionId,
|
|
690
|
-
reason: result.reason,
|
|
691
|
-
hasMissing: !!result.missing?.length,
|
|
692
|
-
hasPending: !!result.pending?.length
|
|
693
|
-
});
|
|
694
|
-
throw new Error(`Consensus pend failed for action ${operation.pend.actionId}: ${result.reason ?? 'stale revision'}`);
|
|
695
|
-
}
|
|
696
|
-
} else if ('commit' in operation) {
|
|
697
|
-
const result = await this.storageRepo.commit(operation.commit);
|
|
698
|
-
if (!result.success) {
|
|
699
|
-
log('cluster-member:consensus-commit-failed', {
|
|
700
|
-
messageHash: record.messageHash,
|
|
701
|
-
actionId: operation.commit.actionId,
|
|
702
|
-
reason: result.reason,
|
|
703
|
-
hasMissing: !!result.missing?.length
|
|
704
|
-
});
|
|
705
|
-
throw new Error(`Consensus commit failed for action ${operation.commit.actionId}: ${result.reason ?? 'stale revision'}`);
|
|
706
|
-
}
|
|
707
|
-
} else if ('cancel' in operation) {
|
|
708
|
-
await this.storageRepo.cancel(operation.cancel.actionRef);
|
|
709
|
-
}
|
|
760
|
+
await this.applyConsensusOperation(record, operation);
|
|
710
761
|
}
|
|
711
762
|
} catch (err) {
|
|
712
|
-
//
|
|
763
|
+
// A genuinely unexpected fault (e.g. storage I/O) — roll back the executed
|
|
764
|
+
// marker so a corrected retry can re-run, and propagate so the caller learns
|
|
765
|
+
// the real cause. Recoverable local divergence is absorbed inside
|
|
766
|
+
// applyConsensusOperation and never reaches here.
|
|
713
767
|
this.executedTransactions.delete(record.messageHash);
|
|
714
768
|
throw err;
|
|
715
769
|
}
|
|
716
770
|
}
|
|
717
771
|
|
|
772
|
+
/**
|
|
773
|
+
* Applies one consensus-approved operation to local storage.
|
|
774
|
+
*
|
|
775
|
+
* By the time we reach here the cluster has *already* reached consensus, so the
|
|
776
|
+
* operation is authoritative cluster-wide. A failure applying it to THIS member's
|
|
777
|
+
* local store therefore does not mean the operation is invalid — it means our
|
|
778
|
+
* local state has diverged from the agreed history:
|
|
779
|
+
*
|
|
780
|
+
* - **ahead**: we already hold a newer revision, so a stale pend/commit is a
|
|
781
|
+
* no-op for us (`StorageRepo.commit` returns `success:false` with `missing`);
|
|
782
|
+
* - **behind**: we missed the prior `pend` cluster-transaction (cohort drift
|
|
783
|
+
* between the independent pend and commit phases, or transient unreachability),
|
|
784
|
+
* so we lack the pending action — `StorageRepo.commit` *throws* "Pending
|
|
785
|
+
* action … not found".
|
|
786
|
+
*
|
|
787
|
+
* For the **behind** case we hold no revision of the committed blocks at all, so we
|
|
788
|
+
* actively reconcile: pull the committed revision from a cohort peer that holds it
|
|
789
|
+
* (`reconcileBlock`) and restore it locally. Lazy read-repair on a later read cannot
|
|
790
|
+
* recover it on its own when cohort drift has left the block under-replicated (no
|
|
791
|
+
* reachable peer the reader sees holds the newer rev), so reconciling here is what
|
|
792
|
+
* keeps cross-cohort transactions converging. For the **ahead** case we already hold
|
|
793
|
+
* ≥ the committed rev, so we tolerate the no-op without reconciling downward.
|
|
794
|
+
*
|
|
795
|
+
* Whatever happens, we must NOT reset the stream: throwing here would reset the
|
|
796
|
+
* cluster stream the coordinator is awaiting and surface as a spurious
|
|
797
|
+
* `StreamResetError`, sinking an otherwise-successful transaction. So divergence —
|
|
798
|
+
* and any reconciliation failure — is tolerated (logged for observability). A
|
|
799
|
+
* genuinely *invalid* pend can never get this far: it is rejected during the promise
|
|
800
|
+
* phase (`validatePendOperations`, which validates pend ops only — commits carry no
|
|
801
|
+
* promise-phase validation).
|
|
802
|
+
*
|
|
803
|
+
* The propagate-vs-tolerate split keys off the failure's *nature* via `CommitResult`,
|
|
804
|
+
* not throw-vs-return: a missing pend (thrown "not found") or a stale/ahead commit
|
|
805
|
+
* (`success:false` with `missing`) is divergence and tolerated, whereas a genuine
|
|
806
|
+
* mid-commit `internalCommit` fault (`success:false` with a bare `reason`, no
|
|
807
|
+
* `missing`) is propagated so {@link handleConsensus} rolls back the executed marker
|
|
808
|
+
* and rethrows — exactly like an unexpected thrown fault.
|
|
809
|
+
*/
|
|
810
|
+
private async applyConsensusOperation(record: ClusterRecord, operation: RepoMessage['operations'][number]): Promise<void> {
|
|
811
|
+
const messageHash = record.messageHash;
|
|
812
|
+
if ('get' in operation) {
|
|
813
|
+
await this.storageRepo.get(operation.get);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if ('cancel' in operation) {
|
|
817
|
+
await this.storageRepo.cancel(operation.cancel.actionRef);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if ('pend' in operation) {
|
|
821
|
+
const result = await this.storageRepo.pend(operation.pend);
|
|
822
|
+
if (!result.success) {
|
|
823
|
+
log('cluster-member:consensus-pend-diverged', {
|
|
824
|
+
messageHash,
|
|
825
|
+
actionId: operation.pend.actionId,
|
|
826
|
+
reason: result.reason,
|
|
827
|
+
hasMissing: !!result.missing?.length,
|
|
828
|
+
hasPending: !!result.pending?.length
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if ('commit' in operation) {
|
|
834
|
+
const commit = operation.commit;
|
|
835
|
+
// Capture the consensus commit cert BEFORE applying to storage: StorageRepo.commit emits the
|
|
836
|
+
// CollectionChangeEvent synchronously at the end of the call below, and the reactivity bridge
|
|
837
|
+
// resolves the cert from that event — so it must already be retained when commit() returns.
|
|
838
|
+
//
|
|
839
|
+
// The commit-vote signed preimage is computed HERE, before the synchronous capture+commit
|
|
840
|
+
// sequence below: it is the exact bytes each approving signer endorsed
|
|
841
|
+
// (`utf8(commitHash + ":approve")`), reproduced from this already-validated `record` (its
|
|
842
|
+
// commit signatures were verified against the same `computeCommitHash`). Reactivity sets a
|
|
843
|
+
// notification's `digest` from it so a subscriber's threshold-verify over `digest` succeeds.
|
|
844
|
+
// The one `await` happens before `captureCommitCert` runs, so the cert is still retained
|
|
845
|
+
// synchronously before `commit()` emits its change event (do not move this past the commit).
|
|
846
|
+
// Gated on the sink: with no reactivity wired the preimage has no consumer, so a sink-less
|
|
847
|
+
// node pays neither the extra `sha256` nor the extra microtask — the true zero-cost default.
|
|
848
|
+
if (this.onCommitCertificate) {
|
|
849
|
+
const commitSignedPayload = this.computeSigningPayload(await this.computeCommitHash(record), 'approve');
|
|
850
|
+
this.captureCommitCert(record, commit.actionId, commitSignedPayload);
|
|
851
|
+
}
|
|
852
|
+
let result: CommitResult;
|
|
853
|
+
try {
|
|
854
|
+
result = await this.storageRepo.commit(commit);
|
|
855
|
+
} catch (err) {
|
|
856
|
+
// `StorageRepo.commit` throws (rather than returning success:false) when
|
|
857
|
+
// the pending action is missing — the canonical "behind" signal: this
|
|
858
|
+
// member reached commit-consensus without the matching pend (cohort drift).
|
|
859
|
+
if (isMissingPendingActionError(err)) {
|
|
860
|
+
log('cluster-member:consensus-commit-diverged', {
|
|
861
|
+
messageHash,
|
|
862
|
+
actionId: commit.actionId,
|
|
863
|
+
divergence: 'behind',
|
|
864
|
+
reason: (err as Error).message
|
|
865
|
+
});
|
|
866
|
+
// We hold no revision of these blocks; pull the committed revision from a
|
|
867
|
+
// cohort peer so the block is no longer under-replicated. Best-effort:
|
|
868
|
+
// failures are logged inside, never thrown (a throw would reset the stream).
|
|
869
|
+
await this.reconcileDivergentCommit(record, commit);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
throw err;
|
|
873
|
+
}
|
|
874
|
+
if (!result.success) {
|
|
875
|
+
// success:false is a StaleFailure. `missing` ⇒ ahead/stale divergence
|
|
876
|
+
// (we already hold ≥ this rev): tolerate, do NOT reconcile downward. A bare
|
|
877
|
+
// `reason` with no `missing` ⇒ a genuine internalCommit fault: propagate so
|
|
878
|
+
// handleConsensus rolls back the executed marker and rethrows.
|
|
879
|
+
if (result.missing?.length) {
|
|
880
|
+
log('cluster-member:consensus-commit-diverged', {
|
|
881
|
+
messageHash,
|
|
882
|
+
actionId: commit.actionId,
|
|
883
|
+
divergence: 'ahead',
|
|
884
|
+
reason: result.reason,
|
|
885
|
+
hasMissing: true
|
|
886
|
+
});
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
throw new Error(`Consensus commit for action ${commit.actionId} failed: ${result.reason ?? 'unknown reason'}`);
|
|
890
|
+
}
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if ('invalidate' in operation) {
|
|
894
|
+
await this.applyConsensusInvalidation(record, operation.invalidate);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Applies a consensus-ordered invalidation on this member: dedup → certificate verification →
|
|
901
|
+
* capture the invalidation's commit cert (for reactivity reuse) → delegate the compensating write +
|
|
902
|
+
* durable log append to the injected {@link InvalidationApplySink}.
|
|
903
|
+
*
|
|
904
|
+
* Like every other branch of {@link applyConsensusOperation}, a failure here is tolerated rather
|
|
905
|
+
* than thrown — a throw would reset the cluster stream. A forged/sub-threshold certificate is
|
|
906
|
+
* rejected (and never reaches the sink); a sink fault is logged and its dedup marker rolled back
|
|
907
|
+
* so a re-broadcast can retry. The durable, authoritative dedup is the invalidation log entry the
|
|
908
|
+
* sink consults; the in-memory map is only a fast path.
|
|
909
|
+
*/
|
|
910
|
+
private async applyConsensusInvalidation(record: ClusterRecord, request: InvalidateRequest): Promise<void> {
|
|
911
|
+
const messageHash = record.messageHash;
|
|
912
|
+
const dedupKey = `${request.invalidatedActionId}:${request.resolution.disputeId}`;
|
|
913
|
+
if (this.appliedInvalidations.has(dedupKey)) {
|
|
914
|
+
log('cluster-member:consensus-invalidate-duplicate', { messageHash, dedupKey });
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Certificate verification BEFORE apply — never trust the originator's say-so. Verify the proof
|
|
919
|
+
// against THIS request's own target: the votes are bound to the transaction the dispute resolved,
|
|
920
|
+
// so a genuine proof carried in a request that points at a different (innocent) action/blocks fails
|
|
921
|
+
// here (the network-facing replay boundary, #2). The proof is also bound to the legitimately-selected
|
|
922
|
+
// arbitrator set (#1): membership + the challenger's set signature gate it (layer 1), and when a
|
|
923
|
+
// recompute capability is wired we re-derive the eligible set from our topology and reject a forged
|
|
924
|
+
// one (layer 2). When we cannot reconstruct the historical topology, we accept on layer 1 and LOG
|
|
925
|
+
// that the invalidation was applied without full anchoring — the documented interim posture.
|
|
926
|
+
const certified = await verifyInvalidationCertificate(
|
|
927
|
+
request.resolution,
|
|
928
|
+
{ invalidatedActionId: request.invalidatedActionId, blockIds: request.blockIds },
|
|
929
|
+
{
|
|
930
|
+
recomputeArbitratorSet: this.recomputeArbitratorSet,
|
|
931
|
+
onUnanchored: (info) => log('cluster-member:consensus-invalidate-unanchored', {
|
|
932
|
+
messageHash,
|
|
933
|
+
invalidatedActionId: request.invalidatedActionId,
|
|
934
|
+
disputeId: info.disputeId,
|
|
935
|
+
reason: info.reason,
|
|
936
|
+
arbitratorSetSize: info.arbitratorSet.length
|
|
937
|
+
})
|
|
938
|
+
}
|
|
939
|
+
);
|
|
940
|
+
if (!certified) {
|
|
941
|
+
log('cluster-member:consensus-invalidate-reject-certificate', {
|
|
942
|
+
messageHash,
|
|
943
|
+
invalidatedActionId: request.invalidatedActionId,
|
|
944
|
+
disputeId: request.resolution.disputeId,
|
|
945
|
+
outcome: request.resolution.outcome
|
|
946
|
+
});
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Capture the invalidation's own commit cert — the threshold signature the cohort produced for
|
|
951
|
+
// THIS consensus-ordered `invalidate` op (over `computeCommitHash(record)`), the reversal analogue
|
|
952
|
+
// of the commit-cert capture in {@link applyConsensusOperation}. Reactivity reuses it bit-for-bit
|
|
953
|
+
// as the invalidation notification's `sig` (never re-signed). Keyed on the deterministic
|
|
954
|
+
// {@link invalidationActionId} the invalidation's change event also carries, so the bridge's
|
|
955
|
+
// cert extractor resolves it. Gated on the sink — a node with no reactivity wired pays nothing.
|
|
956
|
+
if (this.onCommitCertificate) {
|
|
957
|
+
const invSignedPayload = this.computeSigningPayload(await this.computeCommitHash(record), 'approve');
|
|
958
|
+
this.captureCommitCert(record, invalidationActionId(request.invalidatedActionId, request.resolution.disputeId), invSignedPayload);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (!this.onInvalidate) {
|
|
962
|
+
log('cluster-member:consensus-invalidate-no-sink', { messageHash, dedupKey });
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
this.appliedInvalidations.set(dedupKey, Date.now());
|
|
967
|
+
try {
|
|
968
|
+
await this.onInvalidate(request);
|
|
969
|
+
log('cluster-member:consensus-invalidate-applied', {
|
|
970
|
+
messageHash,
|
|
971
|
+
invalidatedActionId: request.invalidatedActionId,
|
|
972
|
+
disputeId: request.resolution.disputeId,
|
|
973
|
+
blockCount: request.blockIds.length
|
|
974
|
+
});
|
|
975
|
+
} catch (err) {
|
|
976
|
+
// Tolerate (don't reset the stream); roll back the marker so a re-broadcast retries.
|
|
977
|
+
this.appliedInvalidations.delete(dedupKey);
|
|
978
|
+
log('cluster-member:consensus-invalidate-sink-error', { messageHash, dedupKey, error: (err as Error).message });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Record the consensus commit cert for `actionId` into the injected {@link CommitCertificateSink},
|
|
984
|
+
* if one is wired. The cert is built from the agreed `record.commits` (the per-member `approve`
|
|
985
|
+
* commit signatures), forwarded UNCHANGED for reactivity to reuse. `signedPayload` is the exact
|
|
986
|
+
* commit-vote preimage those signatures were produced over (`utf8(commitHash + ":approve")`),
|
|
987
|
+
* computed by the caller from the same `record` *before* the synchronous commit — reactivity sets a
|
|
988
|
+
* notification's `digest` from it so a real threshold-verify over `digest` succeeds. Stays
|
|
989
|
+
* **synchronous** (no `await` here) so the cert is retained before `StorageRepo.commit` emits its
|
|
990
|
+
* change event. No-op (zero cost) when no sink is configured; a throwing sink is isolated + logged so
|
|
991
|
+
* it can never break consensus.
|
|
992
|
+
*/
|
|
993
|
+
private captureCommitCert(record: ClusterRecord, actionId: ActionId, signedPayload: Uint8Array): void {
|
|
994
|
+
if (!this.onCommitCertificate) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const minSigs = Math.ceil(Object.keys(record.peers).length * this.superMajorityThreshold);
|
|
998
|
+
try {
|
|
999
|
+
this.onCommitCertificate(actionId, buildCommitCert(record, minSigs, signedPayload));
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
log('cluster-member:commit-cert-sink-error', { actionId, error: (err as Error).message });
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* After tolerating a "behind" commit divergence, pull the committed revision of
|
|
1007
|
+
* each block from a cohort peer that holds it and restore it locally. Best-effort:
|
|
1008
|
+
* a missing callback, an empty cohort, or a per-block failure/timeout is logged and
|
|
1009
|
+
* tolerated — never thrown, since a throw out of consensus execution resets the
|
|
1010
|
+
* cluster stream.
|
|
1011
|
+
*/
|
|
1012
|
+
private async reconcileDivergentCommit(record: ClusterRecord, commit: CommitRequest): Promise<void> {
|
|
1013
|
+
if (!this.reconcileBlock) {
|
|
1014
|
+
log('cluster-member:consensus-commit-reconcile-skip', { messageHash: record.messageHash, reason: 'no-callback' });
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const cohortPeerIds = Object.keys(record.peers).filter(id => id !== this.peerId.toString());
|
|
1018
|
+
if (cohortPeerIds.length === 0) {
|
|
1019
|
+
log('cluster-member:consensus-commit-reconcile-skip', { messageHash: record.messageHash, reason: 'no-cohort-peers' });
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const committed: ActionRev = { actionId: commit.actionId, rev: commit.rev };
|
|
1023
|
+
await Promise.all(
|
|
1024
|
+
commit.blockIds.map(blockId => this.reconcileOneBlock(record.messageHash, blockId, committed, cohortPeerIds))
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/** Reconcile a single block, bounding the awaited callback and swallowing failures. */
|
|
1029
|
+
private async reconcileOneBlock(messageHash: string, blockId: BlockId, committed: ActionRev, cohortPeerIds: string[]): Promise<void> {
|
|
1030
|
+
try {
|
|
1031
|
+
await this.withReconcileTimeout(this.reconcileBlock!(blockId, committed, cohortPeerIds), blockId);
|
|
1032
|
+
log('cluster-member:consensus-commit-reconciled', { messageHash, blockId, rev: committed.rev });
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
log('cluster-member:consensus-commit-reconcile-failed', {
|
|
1035
|
+
messageHash,
|
|
1036
|
+
blockId,
|
|
1037
|
+
rev: committed.rev,
|
|
1038
|
+
error: (err as Error).message
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/** Bound an awaited reconcile so a slow/unreachable cohort peer can't stall consensus. */
|
|
1044
|
+
private withReconcileTimeout<T>(promise: Promise<T>, blockId: BlockId): Promise<T> {
|
|
1045
|
+
let timer: NodeJS.Timeout | undefined;
|
|
1046
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
1047
|
+
timer = setTimeout(
|
|
1048
|
+
() => reject(new Error(`reconcile for block ${blockId} timed out after ${ReconcileTimeoutMs}ms`)),
|
|
1049
|
+
ReconcileTimeoutMs
|
|
1050
|
+
);
|
|
1051
|
+
timer.unref();
|
|
1052
|
+
});
|
|
1053
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
1054
|
+
if (timer) clearTimeout(timer);
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
718
1058
|
private async handleRejection(_record: ClusterRecord): Promise<void> {
|
|
719
1059
|
// Clean up any resources - will be cleared by shouldPersist = false in the main flow
|
|
720
1060
|
}
|
|
@@ -867,6 +1207,10 @@ export class ClusterMember implements ICluster {
|
|
|
867
1207
|
operation.commit.blockIds.forEach(id => blockIds.add(id));
|
|
868
1208
|
} else if ('cancel' in operation) {
|
|
869
1209
|
operation.cancel.actionRef.blockIds.forEach(id => blockIds.add(id));
|
|
1210
|
+
} else if ('invalidate' in operation) {
|
|
1211
|
+
// The invalidation writes compensating revisions to these blocks; surfacing them lets
|
|
1212
|
+
// conflict detection serialize a concurrent commit racing the invalidation on a block.
|
|
1213
|
+
operation.invalidate.blockIds.forEach(id => blockIds.add(id));
|
|
870
1214
|
}
|
|
871
1215
|
}
|
|
872
1216
|
|
|
@@ -939,6 +1283,12 @@ export class ClusterMember implements ICluster {
|
|
|
939
1283
|
this.executedTransactions.delete(messageHash);
|
|
940
1284
|
}
|
|
941
1285
|
}
|
|
1286
|
+
// Prune old applied-invalidation dedup markers on the same TTL.
|
|
1287
|
+
for (const [dedupKey, appliedAt] of Array.from(this.appliedInvalidations.entries())) {
|
|
1288
|
+
if (appliedAt < expirationThreshold) {
|
|
1289
|
+
this.appliedInvalidations.delete(dedupKey);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
942
1292
|
this.stateStore?.pruneExecuted(expirationThreshold)
|
|
943
1293
|
.catch(err => log('cluster-member:prune-executed-error', { error: (err as Error).message }));
|
|
944
1294
|
}
|