@optimystic/db-core 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/dist/src/cluster/structs.d.ts +29 -0
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cohort-topic/addressing.d.ts +53 -0
- package/dist/src/cohort-topic/addressing.d.ts.map +1 -0
- package/dist/src/cohort-topic/addressing.js +95 -0
- package/dist/src/cohort-topic/addressing.js.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts +105 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js +197 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +50 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +62 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -0
- package/dist/src/cohort-topic/antidos/index.d.ts +6 -0
- package/dist/src/cohort-topic/antidos/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/index.js +6 -0
- package/dist/src/cohort-topic/antidos/index.js.map +1 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.d.ts +95 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.js +146 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.js.map +1 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts +37 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js +78 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts +50 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/topic-budget.js +95 -0
- package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -0
- package/dist/src/cohort-topic/antiflood/index.d.ts +3 -0
- package/dist/src/cohort-topic/antiflood/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/antiflood/index.js +3 -0
- package/dist/src/cohort-topic/antiflood/index.js.map +1 -0
- package/dist/src/cohort-topic/antiflood/invariants.d.ts +70 -0
- package/dist/src/cohort-topic/antiflood/invariants.d.ts.map +1 -0
- package/dist/src/cohort-topic/antiflood/invariants.js +86 -0
- package/dist/src/cohort-topic/antiflood/invariants.js.map +1 -0
- package/dist/src/cohort-topic/antiflood/jitter.d.ts +65 -0
- package/dist/src/cohort-topic/antiflood/jitter.d.ts.map +1 -0
- package/dist/src/cohort-topic/antiflood/jitter.js +75 -0
- package/dist/src/cohort-topic/antiflood/jitter.js.map +1 -0
- package/dist/src/cohort-topic/coldstart.d.ts +117 -0
- package/dist/src/cohort-topic/coldstart.d.ts.map +1 -0
- package/dist/src/cohort-topic/coldstart.js +127 -0
- package/dist/src/cohort-topic/coldstart.js.map +1 -0
- package/dist/src/cohort-topic/dmax.d.ts +44 -0
- package/dist/src/cohort-topic/dmax.d.ts.map +1 -0
- package/dist/src/cohort-topic/dmax.js +70 -0
- package/dist/src/cohort-topic/dmax.js.map +1 -0
- package/dist/src/cohort-topic/gossip/bus.d.ts +85 -0
- package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/bus.js +146 -0
- package/dist/src/cohort-topic/gossip/bus.js.map +1 -0
- package/dist/src/cohort-topic/gossip/index.d.ts +4 -0
- package/dist/src/cohort-topic/gossip/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/index.js +4 -0
- package/dist/src/cohort-topic/gossip/index.js.map +1 -0
- package/dist/src/cohort-topic/gossip/records.d.ts +12 -0
- package/dist/src/cohort-topic/gossip/records.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/records.js +41 -0
- package/dist/src/cohort-topic/gossip/records.js.map +1 -0
- package/dist/src/cohort-topic/gossip/view.d.ts +39 -0
- package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/view.js +30 -0
- package/dist/src/cohort-topic/gossip/view.js.map +1 -0
- package/dist/src/cohort-topic/index.d.ts +21 -0
- package/dist/src/cohort-topic/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/index.js +21 -0
- package/dist/src/cohort-topic/index.js.map +1 -0
- package/dist/src/cohort-topic/load/barometer.d.ts +78 -0
- package/dist/src/cohort-topic/load/barometer.d.ts.map +1 -0
- package/dist/src/cohort-topic/load/barometer.js +93 -0
- package/dist/src/cohort-topic/load/barometer.js.map +1 -0
- package/dist/src/cohort-topic/load/index.d.ts +2 -0
- package/dist/src/cohort-topic/load/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/load/index.js +2 -0
- package/dist/src/cohort-topic/load/index.js.map +1 -0
- package/dist/src/cohort-topic/member-engine.d.ts +134 -0
- package/dist/src/cohort-topic/member-engine.d.ts.map +1 -0
- package/dist/src/cohort-topic/member-engine.js +264 -0
- package/dist/src/cohort-topic/member-engine.js.map +1 -0
- package/dist/src/cohort-topic/membership/index.d.ts +4 -0
- package/dist/src/cohort-topic/membership/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/index.js +4 -0
- package/dist/src/cohort-topic/membership/index.js.map +1 -0
- package/dist/src/cohort-topic/membership/publisher.d.ts +82 -0
- package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/publisher.js +107 -0
- package/dist/src/cohort-topic/membership/publisher.js.map +1 -0
- package/dist/src/cohort-topic/membership/source.d.ts +31 -0
- package/dist/src/cohort-topic/membership/source.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/source.js +32 -0
- package/dist/src/cohort-topic/membership/source.js.map +1 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts +96 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/verifier.js +250 -0
- package/dist/src/cohort-topic/membership/verifier.js.map +1 -0
- package/dist/src/cohort-topic/ports.d.ts +149 -0
- package/dist/src/cohort-topic/ports.d.ts.map +1 -0
- package/dist/src/cohort-topic/ports.js +25 -0
- package/dist/src/cohort-topic/ports.js.map +1 -0
- package/dist/src/cohort-topic/promotion.d.ts +135 -0
- package/dist/src/cohort-topic/promotion.d.ts.map +1 -0
- package/dist/src/cohort-topic/promotion.js +269 -0
- package/dist/src/cohort-topic/promotion.js.map +1 -0
- package/dist/src/cohort-topic/registration/bytes.d.ts +16 -0
- package/dist/src/cohort-topic/registration/bytes.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/bytes.js +32 -0
- package/dist/src/cohort-topic/registration/bytes.js.map +1 -0
- package/dist/src/cohort-topic/registration/handoff.d.ts +69 -0
- package/dist/src/cohort-topic/registration/handoff.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/handoff.js +98 -0
- package/dist/src/cohort-topic/registration/handoff.js.map +1 -0
- package/dist/src/cohort-topic/registration/index.d.ts +7 -0
- package/dist/src/cohort-topic/registration/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/index.js +7 -0
- package/dist/src/cohort-topic/registration/index.js.map +1 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts +115 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/renewal.js +306 -0
- package/dist/src/cohort-topic/registration/renewal.js.map +1 -0
- package/dist/src/cohort-topic/registration/sharding.d.ts +31 -0
- package/dist/src/cohort-topic/registration/sharding.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/sharding.js +48 -0
- package/dist/src/cohort-topic/registration/sharding.js.map +1 -0
- package/dist/src/cohort-topic/registration/store.d.ts +13 -0
- package/dist/src/cohort-topic/registration/store.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/store.js +72 -0
- package/dist/src/cohort-topic/registration/store.js.map +1 -0
- package/dist/src/cohort-topic/registration/types.d.ts +71 -0
- package/dist/src/cohort-topic/registration/types.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/types.js +27 -0
- package/dist/src/cohort-topic/registration/types.js.map +1 -0
- package/dist/src/cohort-topic/ring-hash.d.ts +27 -0
- package/dist/src/cohort-topic/ring-hash.d.ts.map +1 -0
- package/dist/src/cohort-topic/ring-hash.js +45 -0
- package/dist/src/cohort-topic/ring-hash.js.map +1 -0
- package/dist/src/cohort-topic/service.d.ts +157 -0
- package/dist/src/cohort-topic/service.d.ts.map +1 -0
- package/dist/src/cohort-topic/service.js +237 -0
- package/dist/src/cohort-topic/service.js.map +1 -0
- package/dist/src/cohort-topic/sig/index.d.ts +3 -0
- package/dist/src/cohort-topic/sig/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/sig/index.js +3 -0
- package/dist/src/cohort-topic/sig/index.js.map +1 -0
- package/dist/src/cohort-topic/sig/payloads.d.ts +25 -0
- package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -0
- package/dist/src/cohort-topic/sig/payloads.js +25 -0
- package/dist/src/cohort-topic/sig/payloads.js.map +1 -0
- package/dist/src/cohort-topic/sig/threshold.d.ts +33 -0
- package/dist/src/cohort-topic/sig/threshold.d.ts.map +1 -0
- package/dist/src/cohort-topic/sig/threshold.js +51 -0
- package/dist/src/cohort-topic/sig/threshold.js.map +1 -0
- package/dist/src/cohort-topic/tiers.d.ts +58 -0
- package/dist/src/cohort-topic/tiers.d.ts.map +1 -0
- package/dist/src/cohort-topic/tiers.js +49 -0
- package/dist/src/cohort-topic/tiers.js.map +1 -0
- package/dist/src/cohort-topic/traffic.d.ts +90 -0
- package/dist/src/cohort-topic/traffic.d.ts.map +1 -0
- package/dist/src/cohort-topic/traffic.js +146 -0
- package/dist/src/cohort-topic/traffic.js.map +1 -0
- package/dist/src/cohort-topic/walk.d.ts +123 -0
- package/dist/src/cohort-topic/walk.d.ts.map +1 -0
- package/dist/src/cohort-topic/walk.js +150 -0
- package/dist/src/cohort-topic/walk.js.map +1 -0
- package/dist/src/cohort-topic/willingness.d.ts +123 -0
- package/dist/src/cohort-topic/willingness.d.ts.map +1 -0
- package/dist/src/cohort-topic/willingness.js +143 -0
- package/dist/src/cohort-topic/willingness.js.map +1 -0
- package/dist/src/cohort-topic/wire/codec.d.ts +42 -0
- package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/codec.js +163 -0
- package/dist/src/cohort-topic/wire/codec.js.map +1 -0
- package/dist/src/cohort-topic/wire/index.d.ts +6 -0
- package/dist/src/cohort-topic/wire/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/index.js +6 -0
- package/dist/src/cohort-topic/wire/index.js.map +1 -0
- package/dist/src/cohort-topic/wire/payloads.d.ts +38 -0
- package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/payloads.js +102 -0
- package/dist/src/cohort-topic/wire/payloads.js.map +1 -0
- package/dist/src/cohort-topic/wire/types.d.ts +304 -0
- package/dist/src/cohort-topic/wire/types.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/types.js +16 -0
- package/dist/src/cohort-topic/wire/types.js.map +1 -0
- package/dist/src/cohort-topic/wire/validate.d.ts +25 -0
- package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/validate.js +427 -0
- package/dist/src/cohort-topic/wire/validate.js.map +1 -0
- package/dist/src/collection/collection.d.ts +58 -1
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +76 -0
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +58 -2
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +59 -2
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/log/log.d.ts +42 -4
- package/dist/src/log/log.d.ts.map +1 -1
- package/dist/src/log/log.js +71 -3
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/log/struct.d.ts +122 -2
- package/dist/src/log/struct.d.ts.map +1 -1
- package/dist/src/log/struct.js.map +1 -1
- package/dist/src/matchmaking/capability-filter.d.ts +24 -0
- package/dist/src/matchmaking/capability-filter.d.ts.map +1 -0
- package/dist/src/matchmaking/capability-filter.js +37 -0
- package/dist/src/matchmaking/capability-filter.js.map +1 -0
- package/dist/src/matchmaking/config.d.ts +77 -0
- package/dist/src/matchmaking/config.d.ts.map +1 -0
- package/dist/src/matchmaking/config.js +69 -0
- package/dist/src/matchmaking/config.js.map +1 -0
- package/dist/src/matchmaking/index.d.ts +21 -0
- package/dist/src/matchmaking/index.d.ts.map +1 -0
- package/dist/src/matchmaking/index.js +21 -0
- package/dist/src/matchmaking/index.js.map +1 -0
- package/dist/src/matchmaking/multi-cohort-seeker.d.ts +133 -0
- package/dist/src/matchmaking/multi-cohort-seeker.d.ts.map +1 -0
- package/dist/src/matchmaking/multi-cohort-seeker.js +142 -0
- package/dist/src/matchmaking/multi-cohort-seeker.js.map +1 -0
- package/dist/src/matchmaking/provider.d.ts +70 -0
- package/dist/src/matchmaking/provider.d.ts.map +1 -0
- package/dist/src/matchmaking/provider.js +93 -0
- package/dist/src/matchmaking/provider.js.map +1 -0
- package/dist/src/matchmaking/query-eval.d.ts +53 -0
- package/dist/src/matchmaking/query-eval.d.ts.map +1 -0
- package/dist/src/matchmaking/query-eval.js +69 -0
- package/dist/src/matchmaking/query-eval.js.map +1 -0
- package/dist/src/matchmaking/seeker-walk.d.ts +95 -0
- package/dist/src/matchmaking/seeker-walk.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker-walk.js +75 -0
- package/dist/src/matchmaking/seeker-walk.js.map +1 -0
- package/dist/src/matchmaking/seeker.d.ts +51 -0
- package/dist/src/matchmaking/seeker.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker.js +62 -0
- package/dist/src/matchmaking/seeker.js.map +1 -0
- package/dist/src/matchmaking/topic-anchor.d.ts +54 -0
- package/dist/src/matchmaking/topic-anchor.d.ts.map +1 -0
- package/dist/src/matchmaking/topic-anchor.js +67 -0
- package/dist/src/matchmaking/topic-anchor.js.map +1 -0
- package/dist/src/matchmaking/voting-quorum.d.ts +233 -0
- package/dist/src/matchmaking/voting-quorum.d.ts.map +1 -0
- package/dist/src/matchmaking/voting-quorum.js +238 -0
- package/dist/src/matchmaking/voting-quorum.js.map +1 -0
- package/dist/src/matchmaking/wire.d.ts +259 -0
- package/dist/src/matchmaking/wire.d.ts.map +1 -0
- package/dist/src/matchmaking/wire.js +484 -0
- package/dist/src/matchmaking/wire.js.map +1 -0
- package/dist/src/network/i-repo.d.ts +19 -0
- package/dist/src/network/i-repo.d.ts.map +1 -1
- package/dist/src/network/repo-protocol.d.ts +3 -1
- package/dist/src/network/repo-protocol.d.ts.map +1 -1
- package/dist/src/network/struct.d.ts +23 -1
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/reactivity/backfill.d.ts +118 -0
- package/dist/src/reactivity/backfill.d.ts.map +1 -0
- package/dist/src/reactivity/backfill.js +137 -0
- package/dist/src/reactivity/backfill.js.map +1 -0
- package/dist/src/reactivity/backpressure.d.ts +99 -0
- package/dist/src/reactivity/backpressure.d.ts.map +1 -0
- package/dist/src/reactivity/backpressure.js +153 -0
- package/dist/src/reactivity/backpressure.js.map +1 -0
- package/dist/src/reactivity/checkpoint.d.ts +165 -0
- package/dist/src/reactivity/checkpoint.d.ts.map +1 -0
- package/dist/src/reactivity/checkpoint.js +232 -0
- package/dist/src/reactivity/checkpoint.js.map +1 -0
- package/dist/src/reactivity/config.d.ts +131 -0
- package/dist/src/reactivity/config.d.ts.map +1 -0
- package/dist/src/reactivity/config.js +135 -0
- package/dist/src/reactivity/config.js.map +1 -0
- package/dist/src/reactivity/dedupe.d.ts +51 -0
- package/dist/src/reactivity/dedupe.d.ts.map +1 -0
- package/dist/src/reactivity/dedupe.js +90 -0
- package/dist/src/reactivity/dedupe.js.map +1 -0
- package/dist/src/reactivity/forwarder.d.ts +50 -0
- package/dist/src/reactivity/forwarder.d.ts.map +1 -0
- package/dist/src/reactivity/forwarder.js +52 -0
- package/dist/src/reactivity/forwarder.js.map +1 -0
- package/dist/src/reactivity/index.d.ts +34 -0
- package/dist/src/reactivity/index.d.ts.map +1 -0
- package/dist/src/reactivity/index.js +34 -0
- package/dist/src/reactivity/index.js.map +1 -0
- package/dist/src/reactivity/notification.d.ts +72 -0
- package/dist/src/reactivity/notification.d.ts.map +1 -0
- package/dist/src/reactivity/notification.js +92 -0
- package/dist/src/reactivity/notification.js.map +1 -0
- package/dist/src/reactivity/policy.d.ts +59 -0
- package/dist/src/reactivity/policy.d.ts.map +1 -0
- package/dist/src/reactivity/policy.js +64 -0
- package/dist/src/reactivity/policy.js.map +1 -0
- package/dist/src/reactivity/push-state.d.ts +140 -0
- package/dist/src/reactivity/push-state.d.ts.map +1 -0
- package/dist/src/reactivity/push-state.js +270 -0
- package/dist/src/reactivity/push-state.js.map +1 -0
- package/dist/src/reactivity/recover.d.ts +75 -0
- package/dist/src/reactivity/recover.d.ts.map +1 -0
- package/dist/src/reactivity/recover.js +105 -0
- package/dist/src/reactivity/recover.js.map +1 -0
- package/dist/src/reactivity/replay-buffer.d.ts +57 -0
- package/dist/src/reactivity/replay-buffer.d.ts.map +1 -0
- package/dist/src/reactivity/replay-buffer.js +88 -0
- package/dist/src/reactivity/replay-buffer.js.map +1 -0
- package/dist/src/reactivity/resume.d.ts +214 -0
- package/dist/src/reactivity/resume.d.ts.map +1 -0
- package/dist/src/reactivity/resume.js +358 -0
- package/dist/src/reactivity/resume.js.map +1 -0
- package/dist/src/reactivity/rotation.d.ts +283 -0
- package/dist/src/reactivity/rotation.d.ts.map +1 -0
- package/dist/src/reactivity/rotation.js +265 -0
- package/dist/src/reactivity/rotation.js.map +1 -0
- package/dist/src/reactivity/subscriber.d.ts +66 -0
- package/dist/src/reactivity/subscriber.d.ts.map +1 -0
- package/dist/src/reactivity/subscriber.js +75 -0
- package/dist/src/reactivity/subscriber.js.map +1 -0
- package/dist/src/reactivity/subscription.d.ts +52 -0
- package/dist/src/reactivity/subscription.d.ts.map +1 -0
- package/dist/src/reactivity/subscription.js +28 -0
- package/dist/src/reactivity/subscription.js.map +1 -0
- package/dist/src/reactivity/topic-anchor.d.ts +46 -0
- package/dist/src/reactivity/topic-anchor.d.ts.map +1 -0
- package/dist/src/reactivity/topic-anchor.js +61 -0
- package/dist/src/reactivity/topic-anchor.js.map +1 -0
- package/dist/src/reactivity/verify.d.ts +41 -0
- package/dist/src/reactivity/verify.d.ts.map +1 -0
- package/dist/src/reactivity/verify.js +48 -0
- package/dist/src/reactivity/verify.js.map +1 -0
- package/dist/src/reactivity/wire-validate.d.ts +28 -0
- package/dist/src/reactivity/wire-validate.d.ts.map +1 -0
- package/dist/src/reactivity/wire-validate.js +79 -0
- package/dist/src/reactivity/wire-validate.js.map +1 -0
- package/dist/src/reactivity/wire.d.ts +102 -0
- package/dist/src/reactivity/wire.d.ts.map +1 -0
- package/dist/src/reactivity/wire.js +129 -0
- package/dist/src/reactivity/wire.js.map +1 -0
- package/dist/src/transaction/coordinator.d.ts +9 -5
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +48 -25
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transactor/change-notifier.d.ts +74 -0
- package/dist/src/transactor/change-notifier.d.ts.map +1 -0
- package/dist/src/transactor/change-notifier.js +4 -0
- package/dist/src/transactor/change-notifier.js.map +1 -0
- package/dist/src/transactor/index.d.ts +1 -0
- package/dist/src/transactor/index.d.ts.map +1 -1
- package/dist/src/transactor/index.js +1 -0
- package/dist/src/transactor/index.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts +44 -2
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +132 -17
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/package.json +3 -2
- package/src/cluster/structs.ts +86 -57
- package/src/cohort-topic/addressing.ts +112 -0
- package/src/cohort-topic/antidos/bootstrap-evidence-envelope.ts +253 -0
- package/src/cohort-topic/antidos/bootstrap-evidence.ts +94 -0
- package/src/cohort-topic/antidos/index.ts +5 -0
- package/src/cohort-topic/antidos/rate-limiter.ts +210 -0
- package/src/cohort-topic/antidos/replay-guard.ts +109 -0
- package/src/cohort-topic/antidos/topic-budget.ts +145 -0
- package/src/cohort-topic/antiflood/index.ts +2 -0
- package/src/cohort-topic/antiflood/invariants.ts +108 -0
- package/src/cohort-topic/antiflood/jitter.ts +117 -0
- package/src/cohort-topic/coldstart.ts +204 -0
- package/src/cohort-topic/dmax.ts +88 -0
- package/src/cohort-topic/gossip/bus.ts +217 -0
- package/src/cohort-topic/gossip/index.ts +3 -0
- package/src/cohort-topic/gossip/records.ts +45 -0
- package/src/cohort-topic/gossip/view.ts +66 -0
- package/src/cohort-topic/index.ts +20 -0
- package/src/cohort-topic/load/barometer.ts +134 -0
- package/src/cohort-topic/load/index.ts +1 -0
- package/src/cohort-topic/member-engine.ts +409 -0
- package/src/cohort-topic/membership/index.ts +3 -0
- package/src/cohort-topic/membership/publisher.ts +171 -0
- package/src/cohort-topic/membership/source.ts +41 -0
- package/src/cohort-topic/membership/verifier.ts +345 -0
- package/src/cohort-topic/ports.ts +157 -0
- package/src/cohort-topic/promotion.ts +384 -0
- package/src/cohort-topic/registration/bytes.ts +37 -0
- package/src/cohort-topic/registration/handoff.ts +154 -0
- package/src/cohort-topic/registration/index.ts +6 -0
- package/src/cohort-topic/registration/renewal.ts +423 -0
- package/src/cohort-topic/registration/sharding.ts +61 -0
- package/src/cohort-topic/registration/store.ts +81 -0
- package/src/cohort-topic/registration/types.ts +76 -0
- package/src/cohort-topic/ring-hash.ts +50 -0
- package/src/cohort-topic/service.ts +390 -0
- package/src/cohort-topic/sig/index.ts +2 -0
- package/src/cohort-topic/sig/payloads.ts +39 -0
- package/src/cohort-topic/sig/threshold.ts +64 -0
- package/src/cohort-topic/tiers.ts +74 -0
- package/src/cohort-topic/traffic.ts +211 -0
- package/src/cohort-topic/walk.ts +255 -0
- package/src/cohort-topic/willingness.ts +237 -0
- package/src/cohort-topic/wire/codec.ts +204 -0
- package/src/cohort-topic/wire/index.ts +16 -0
- package/src/cohort-topic/wire/payloads.ts +117 -0
- package/src/cohort-topic/wire/types.ts +343 -0
- package/src/cohort-topic/wire/validate.ts +478 -0
- package/src/collection/collection.ts +93 -1
- package/src/collections/tree/tree.ts +84 -3
- package/src/index.ts +3 -0
- package/src/log/log.ts +86 -7
- package/src/log/struct.ts +126 -2
- package/src/matchmaking/capability-filter.ts +45 -0
- package/src/matchmaking/config.ts +98 -0
- package/src/matchmaking/index.ts +21 -0
- package/src/matchmaking/multi-cohort-seeker.ts +234 -0
- package/src/matchmaking/provider.ts +123 -0
- package/src/matchmaking/query-eval.ts +105 -0
- package/src/matchmaking/seeker-walk.ts +127 -0
- package/src/matchmaking/seeker.ts +86 -0
- package/src/matchmaking/topic-anchor.ts +90 -0
- package/src/matchmaking/voting-quorum.ts +394 -0
- package/src/matchmaking/wire.ts +680 -0
- package/src/network/i-repo.ts +19 -0
- package/src/network/repo-protocol.ts +3 -2
- package/src/network/struct.ts +24 -1
- package/src/reactivity/backfill.ts +220 -0
- package/src/reactivity/backpressure.ts +191 -0
- package/src/reactivity/checkpoint.ts +308 -0
- package/src/reactivity/config.ts +172 -0
- package/src/reactivity/dedupe.ts +132 -0
- package/src/reactivity/forwarder.ts +87 -0
- package/src/reactivity/index.ts +34 -0
- package/src/reactivity/notification.ts +123 -0
- package/src/reactivity/policy.ts +79 -0
- package/src/reactivity/push-state.ts +343 -0
- package/src/reactivity/recover.ts +153 -0
- package/src/reactivity/replay-buffer.ts +141 -0
- package/src/reactivity/resume.ts +549 -0
- package/src/reactivity/rotation.ts +415 -0
- package/src/reactivity/subscriber.ts +132 -0
- package/src/reactivity/subscription.ts +66 -0
- package/src/reactivity/topic-anchor.ts +71 -0
- package/src/reactivity/verify.ts +73 -0
- package/src/reactivity/wire-validate.ts +87 -0
- package/src/reactivity/wire.ts +224 -0
- package/src/transaction/coordinator.ts +53 -27
- package/src/transactor/change-notifier.ts +80 -0
- package/src/transactor/index.ts +1 -0
- package/src/transactor/network-transactor.ts +162 -19
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — transport ports.
|
|
3
|
+
*
|
|
4
|
+
* These are the seams that keep the cohort-topic substrate split cleanly across packages:
|
|
5
|
+
* db-core defines the interfaces here (pure logic depends only on a hash function and
|
|
6
|
+
* byte-array peer IDs), and db-p2p supplies the concrete FRET + libp2p implementations.
|
|
7
|
+
*
|
|
8
|
+
* **db-core never imports FRET or libp2p.** Every db-core substrate module takes these ports
|
|
9
|
+
* by injection (constructor / factory args); db-p2p constructs the FRET-backed versions and
|
|
10
|
+
* composes them with the db-core logic in `CohortTopicService`'s transport host.
|
|
11
|
+
*
|
|
12
|
+
* See `docs/cohort-topic.md` §FRET integration and `tickets` slug
|
|
13
|
+
* `cohort-topic-package-layering` for the authoritative layering rule.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { MembershipCertV1 } from "./wire/types.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ring coordinate — SHA-256 truncated to the ring width `B` (see {@link IRingHash}). db-core
|
|
20
|
+
* owns this type; db-p2p maps it onto FRET's coordinate type, ensuring the byte representation
|
|
21
|
+
* matches FRET's ring width so routing keys line up on the wire.
|
|
22
|
+
*/
|
|
23
|
+
export type RingCoord = Uint8Array;
|
|
24
|
+
|
|
25
|
+
/** A peer, referenced only by its opaque byte-array id. No multiaddr, no libp2p PeerId here. */
|
|
26
|
+
export interface PeerRef {
|
|
27
|
+
readonly id: Uint8Array;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Wraps FRET's `RouteAndMaybeAct` so the walk / registration decision logic in db-core stays
|
|
32
|
+
* transport-agnostic.
|
|
33
|
+
*/
|
|
34
|
+
export interface ITopicRouter {
|
|
35
|
+
/**
|
|
36
|
+
* Route `activity` to the cohort owning `key` and run the cohort's activity callback,
|
|
37
|
+
* collecting up to `wantK` participants and at least `minSigs` signatures.
|
|
38
|
+
* @returns the encoded cohort reply.
|
|
39
|
+
*/
|
|
40
|
+
routeAndAct(key: RingCoord, activity: Uint8Array, opts: { wantK: number; minSigs: number }): Promise<Uint8Array>;
|
|
41
|
+
/** Direct dial to a cached primary; falls back to {@link routeAndAct} on failure (caller decides). */
|
|
42
|
+
dialMember(member: PeerRef, activity: Uint8Array): Promise<Uint8Array>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Intra-cohort gossip transport (FRET cohort gossip underneath). */
|
|
46
|
+
export interface ICohortGossipTransport {
|
|
47
|
+
/** Fire-and-forget broadcast of `msg` to the cohort at `coord`. */
|
|
48
|
+
broadcast(coord: RingCoord, msg: Uint8Array): void;
|
|
49
|
+
/** Subscribe to inbound cohort gossip; returns an unsubscribe handle. */
|
|
50
|
+
onMessage(handler: (from: PeerRef, msg: Uint8Array) => void): () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Authoritative cohort membership snapshots (FRET `MembershipCertV1` / stabilization underneath). */
|
|
54
|
+
export interface IMembershipSource {
|
|
55
|
+
/** Current cached membership cert for `coord`, encoded; `undefined` if none is cached. */
|
|
56
|
+
current(coord: RingCoord): Promise<Uint8Array | undefined>;
|
|
57
|
+
/** Force one refresh of `coord`'s membership cert (stale-cache retry); encoded, or `undefined`. */
|
|
58
|
+
fetch(coord: RingCoord): Promise<Uint8Array | undefined>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Cohort threshold-signature primitive (FRET's `minSigs = k − x` cohort-signature assembly
|
|
63
|
+
* underneath). db-core defines the seam; db-p2p binds FRET's signer/verifier without modifying it.
|
|
64
|
+
* Peer ids are raw byte arrays here ({@link PeerRef}`.id`), matching the rest of the substrate; the
|
|
65
|
+
* wire layer carries them as base64url strings.
|
|
66
|
+
*/
|
|
67
|
+
export interface ICohortThresholdCrypto {
|
|
68
|
+
/**
|
|
69
|
+
* Assemble a cohort threshold signature over `payload`, collecting at least `minSigs` signers.
|
|
70
|
+
* Resolves to the combined signature and the peers that contributed.
|
|
71
|
+
*/
|
|
72
|
+
assemble(payload: Uint8Array, minSigs: number): Promise<{ thresholdSig: Uint8Array; signers: Uint8Array[] }>;
|
|
73
|
+
/** Verify `thresholdSig` is a valid cohort signature over `payload` produced by `signers`. */
|
|
74
|
+
verify(payload: Uint8Array, thresholdSig: Uint8Array, signers: readonly Uint8Array[]): boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Sink the cohort uses to publish a `MembershipCertV1` (over FRET's `/membership` protocol).
|
|
79
|
+
* db-core's publisher builds + threshold-signs the cert and hands it here; db-p2p serves it.
|
|
80
|
+
*/
|
|
81
|
+
export interface IMembershipPublishSink {
|
|
82
|
+
/** Publish (advertise/serve) an already-signed membership certificate, encoded. */
|
|
83
|
+
publish(encodedCert: Uint8Array): void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Network-size estimate feeding `d_max`. */
|
|
87
|
+
export interface ISizeEstimator {
|
|
88
|
+
estimate(): { nEst: number; confidence: number };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Verdict on whether a {@link MembershipCertV1}'s `(cohortCoord → members)` binding is vouched by a
|
|
93
|
+
* source the verifying node *directly* trusts.
|
|
94
|
+
*
|
|
95
|
+
* - `"anchored"` — the binding is vouched (e.g. matches FRET `assembleCohort(coord)`, or the
|
|
96
|
+
* tx-log commit certificate). The cert is trusted on this basis.
|
|
97
|
+
* - `"rejected"` — the binding is contradicted (a known-different keyset owns this coord). A
|
|
98
|
+
* `"rejected"` verdict is **fatal**: the cert is a forgery and is rejected even if it is
|
|
99
|
+
* internally self-consistent, overriding the interim TOFU fallback.
|
|
100
|
+
* - `"unknown"` — this node has no local authority over the coord, so it cannot judge. The
|
|
101
|
+
* verifier falls through to the attestation chain / interim TOFU fallback.
|
|
102
|
+
*/
|
|
103
|
+
export type TrustAnchorVerdict = "anchored" | "rejected" | "unknown";
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Direct (base-case) trust anchor for a {@link MembershipCertV1}'s `coord → keyset` binding.
|
|
107
|
+
*
|
|
108
|
+
* db-core owns the chain-of-attestations and trust-root logic; the **direct** anchor is
|
|
109
|
+
* tier/transport-specific (FRET ring agreement for T2/T3, tx-log commit certificate for T0/T1) and
|
|
110
|
+
* is therefore delegated through this port. db-p2p binds it; **db-core never imports FRET**. A node
|
|
111
|
+
* with no local authority for the coord returns `"unknown"` so the verifier falls through to the
|
|
112
|
+
* chain / interim fallback instead of breaking distant verification. See {@link noAuthorityTrustAnchor}
|
|
113
|
+
* for the db-core default (every coord `"unknown"`).
|
|
114
|
+
*/
|
|
115
|
+
export interface IMembershipTrustAnchor {
|
|
116
|
+
/** Judge, from a directly-trusted source, whether `cert.members` is authoritative for `cert.cohortCoord` at `tier`. */
|
|
117
|
+
directAnchor(cert: MembershipCertV1, tier: number): TrustAnchorVerdict;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* An out-of-band-seeded trust root: the genesis-block-related cohorts a joining participant is told
|
|
122
|
+
* to trust (validated against the genesis block hash by the caller before seeding). A trust root is
|
|
123
|
+
* the base case of every attestation chain — a cert matching a root by `(coord, epoch, member-set)`
|
|
124
|
+
* is trusted directly. Byte fields are raw (the verifier compares them against the cert's base64url
|
|
125
|
+
* form internally).
|
|
126
|
+
*/
|
|
127
|
+
export interface TrustRoot {
|
|
128
|
+
/** Cohort coord (32 bytes). */
|
|
129
|
+
readonly coord: Uint8Array;
|
|
130
|
+
/** Cohort epoch (32 bytes) — required so a cert reusing a genesis coord with a swapped keyset is not a root. */
|
|
131
|
+
readonly epoch: Uint8Array;
|
|
132
|
+
/** Authoritative cohort members (any order; matched as a set). */
|
|
133
|
+
readonly members: readonly Uint8Array[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The db-core default {@link IMembershipTrustAnchor}: it has no local authority over any coord, so it
|
|
138
|
+
* returns `"unknown"` for every cert. Existing verifier callers that inject no anchor get this, keeping
|
|
139
|
+
* the interim TOFU behavior (no regression) until db-p2p binds a real anchor.
|
|
140
|
+
*/
|
|
141
|
+
export const noAuthorityTrustAnchor: IMembershipTrustAnchor = {
|
|
142
|
+
directAnchor(_cert: MembershipCertV1, _tier: number): TrustAnchorVerdict {
|
|
143
|
+
return "unknown";
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Hash + ring math. db-core supplies its OWN SHA-256 (it already hashes logs and block ids);
|
|
149
|
+
* the ring width is configuration. db-p2p, when mapping {@link RingCoord} onto FRET's coordinate
|
|
150
|
+
* type, ensures `ringBits` matches FRET's ring width so coords are byte-compatible.
|
|
151
|
+
*/
|
|
152
|
+
export interface IRingHash {
|
|
153
|
+
/** SHA-256 of `bytes`, truncated to the ring width `B` ({@link ringBits}). */
|
|
154
|
+
H(bytes: Uint8Array): RingCoord;
|
|
155
|
+
/** Ring width in bits. */
|
|
156
|
+
readonly ringBits: number;
|
|
157
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — promotion / demotion lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Transcribed from `docs/cohort-topic.md` §Promotion and demotion lifecycle and folded back from the
|
|
5
|
+
* simulator-validated `packages/substrate-simulator/src/topic-tree.ts`. A forwarder cohort grows
|
|
6
|
+
* (promotes) and shrinks (demotes) for a topic `T` based on its per-topic direct-participant count
|
|
7
|
+
* and the per-tier load barometer; both transitions are **threshold-signed** (via the gossip ticket's
|
|
8
|
+
* {@link CohortSigner}) so participants can verify them.
|
|
9
|
+
*
|
|
10
|
+
* **Promote** when, for a quorum (the quorum is enforced by the threshold signature — `minSigs`
|
|
11
|
+
* signers must agree to the notice):
|
|
12
|
+
* - `directParticipants(T) ≥ cap_promote` (64), OR
|
|
13
|
+
* - `loadBucket[tier(T)] ≥ bucket_overload` (6) AND `directParticipants(T) ≥ cap_promote_fast` (32)
|
|
14
|
+
* — the hot-load fast path from the capacity-barometer ticket, OR
|
|
15
|
+
* - the growth **slope** predicts crossing `cap_promote` within `T_promote_lookahead` (30s) — fires
|
|
16
|
+
* early to avoid the gossip-lag overshoot (`§Promotion`: pre-promotion on slope).
|
|
17
|
+
*
|
|
18
|
+
* Once promoted, the state is **sticky** for ≥ `T_promote_sticky` (60s) before it can be reconsidered
|
|
19
|
+
* for demotion, so transient count drops don't flap a cohort back to accepting.
|
|
20
|
+
*
|
|
21
|
+
* **Demote** when, for a quorum: `directParticipants(T) ≤ cap_demote` (16) AND that has held for
|
|
22
|
+
* ≥ `T_demote` (5min) AND the cohort has no live child cohorts AND it has a parent (the root, tree
|
|
23
|
+
* tier 0, never demotes — it has nowhere to hand off). The `4×` gap between `cap_promote` and
|
|
24
|
+
* `cap_demote` plus `T_demote` is the hysteresis that prevents thrash.
|
|
25
|
+
*
|
|
26
|
+
* **Deviation from the doc's interface sketch (documented).** `docs/cohort-topic.md` sketches
|
|
27
|
+
* `onParticipantCountChange` as returning `void` and "may emit `PromotionNoticeV1`". Because
|
|
28
|
+
* threshold signing is asynchronous ({@link CohortSigner.thresholdSign} resolves a Promise), the
|
|
29
|
+
* methods here are `async` and **return** the signed notice (or `undefined`) instead of emitting it
|
|
30
|
+
* through a side channel — the caller broadcasts whatever it gets back. This keeps the module pure
|
|
31
|
+
* and unit-testable and avoids an awkward async callback. The op-tier `tier(T)` the barometer is
|
|
32
|
+
* indexed by is supplied through the injected {@link PromotionDeps.loadBucket} resolver, so this
|
|
33
|
+
* module never needs to know a topic's tier.
|
|
34
|
+
*
|
|
35
|
+
* **Remote apply path.** `promote()` / `demote()` set this cohort's per-topic state as a side effect of
|
|
36
|
+
* the **local** signing path — only the member that originates a notice adopts the new state that way. A
|
|
37
|
+
* member that *learns* of a promotion/demotion via gossip or a broadcast notice adopts it through
|
|
38
|
+
* {@link PromotionLifecycle.applyPromotionNotice} / {@link PromotionLifecycle.applyDemotionNotice},
|
|
39
|
+
* which set the same {@link PromotionState} **without re-signing** (the notice is already a verified
|
|
40
|
+
* quorum decision). **Precondition: the caller has already verified the notice's threshold signature**
|
|
41
|
+
* (db-p2p's `MembershipVerifier`); this module is crypto-free and trusts that gate. The apply path is
|
|
42
|
+
* idempotent and `effectiveAt`-ordered via the monotonic {@link PromotionState.lastEffectiveAt}
|
|
43
|
+
* high-water mark, so re-applying a notice (including this member's own echoed broadcast) or a replayed
|
|
44
|
+
* older notice is a no-op — a stale promotion can never un-demote a cohort that has since demoted.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { b64urlToBytes, bytesToB64url } from "./wire/codec.js";
|
|
48
|
+
import type { CohortSigner } from "./sig/threshold.js";
|
|
49
|
+
import { demotionNoticeSigningPayload, promotionNoticeSigningPayload } from "./sig/payloads.js";
|
|
50
|
+
import type { DemotionNoticeV1, PromotionNoticeV1 } from "./wire/types.js";
|
|
51
|
+
import type { RegistrationStore } from "./registration/types.js";
|
|
52
|
+
import { bytesKey } from "./registration/bytes.js";
|
|
53
|
+
|
|
54
|
+
// --- defaults (docs/cohort-topic.md §Configuration; simulator-confirmed) ---
|
|
55
|
+
|
|
56
|
+
/** Direct-participant cap before promotion. */
|
|
57
|
+
export const DEFAULT_CAP_PROMOTE = 64;
|
|
58
|
+
/** Direct-participant cap when the load barometer is hot (fast path). */
|
|
59
|
+
export const DEFAULT_CAP_PROMOTE_FAST = 32;
|
|
60
|
+
/** Load-barometer bucket at/above which the fast promote path is armed. */
|
|
61
|
+
export const DEFAULT_BUCKET_OVERLOAD = 6;
|
|
62
|
+
/** Direct-participant floor for demotion (= `cap_promote / 4`). */
|
|
63
|
+
export const DEFAULT_CAP_DEMOTE = 16;
|
|
64
|
+
/** Hysteresis window the low-load condition must hold before demotion (ms). */
|
|
65
|
+
export const DEFAULT_T_DEMOTE_MS = 300_000;
|
|
66
|
+
/** Pre-promotion slope lookahead (ms). */
|
|
67
|
+
export const DEFAULT_T_PROMOTE_LOOKAHEAD_MS = 30_000;
|
|
68
|
+
/** Minimum time a cohort stays promoted before it can be reconsidered for demotion (ms). */
|
|
69
|
+
export const DEFAULT_T_PROMOTE_STICKY_MS = 60_000;
|
|
70
|
+
/**
|
|
71
|
+
* Slope window over which the growth rate is measured for pre-promotion. **Not pinned by
|
|
72
|
+
* `docs/cohort-topic.md`** — the simulator (`topic-tree.ts` `DEFAULT_LIFECYCLE_CONFIG`) uses 10s and
|
|
73
|
+
* this matches it.
|
|
74
|
+
*/
|
|
75
|
+
export const DEFAULT_GROWTH_WINDOW_MS = 10_000;
|
|
76
|
+
|
|
77
|
+
export interface PromotionConfig {
|
|
78
|
+
capPromote?: number;
|
|
79
|
+
capPromoteFast?: number;
|
|
80
|
+
bucketOverload?: number;
|
|
81
|
+
capDemote?: number;
|
|
82
|
+
tDemoteMs?: number;
|
|
83
|
+
tPromoteLookaheadMs?: number;
|
|
84
|
+
tPromoteStickyMs?: number;
|
|
85
|
+
growthWindowMs?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A sampled `(time, count)` growth point, for slope-based pre-promotion. */
|
|
89
|
+
interface GrowthSample {
|
|
90
|
+
readonly t: number;
|
|
91
|
+
readonly count: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Per-topic promotion bookkeeping held by this cohort. */
|
|
95
|
+
interface PromotionState {
|
|
96
|
+
promoted: boolean;
|
|
97
|
+
/** When the cohort last entered promoted mode (sticky-window anchor). */
|
|
98
|
+
promotedAt?: number;
|
|
99
|
+
/** When `directParticipants` last dropped to ≤ `cap_demote` (demotion hysteresis anchor). */
|
|
100
|
+
lowLoadSince?: number;
|
|
101
|
+
/** Recent growth samples within `growthWindowMs`, for slope extrapolation. */
|
|
102
|
+
samples: GrowthSample[];
|
|
103
|
+
/**
|
|
104
|
+
* `effectiveAt` of the most recent promotion/demotion this cohort has adopted — locally originated
|
|
105
|
+
* (`promote()` / `demote()`) or remotely applied (`applyPromotionNotice` / `applyDemotionNotice`).
|
|
106
|
+
* Monotonic and **never cleared** (a demotion clears `promoted`/`promotedAt` but keeps this), so it
|
|
107
|
+
* is the high-water mark the remote-apply path orders against: a notice whose `effectiveAt` is not
|
|
108
|
+
* strictly newer is a no-op — re-applied (incl. this member's own echoed broadcast) or stale-replayed.
|
|
109
|
+
*/
|
|
110
|
+
lastEffectiveAt?: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface PromotionDeps {
|
|
114
|
+
/** Replicated registration store — supplies the per-topic `directParticipants` stock count. */
|
|
115
|
+
store: Pick<RegistrationStore, "directParticipants">;
|
|
116
|
+
/**
|
|
117
|
+
* Current load-barometer bucket (0..7) for the topic's op tier `tier(T)`. The caller resolves the
|
|
118
|
+
* topic→tier mapping and the barometer lookup, keeping this module tier-agnostic and FRET-free.
|
|
119
|
+
*/
|
|
120
|
+
loadBucket: (topicId: Uint8Array) => number;
|
|
121
|
+
/** Live child-cohort count for the topic (0 if none); `> 0` blocks demotion. */
|
|
122
|
+
childCohortCount: (topicId: Uint8Array) => number;
|
|
123
|
+
/** Tree tier `d` this cohort serves the topic at — `fromTier` on a notice; `toTier = d + 1`. */
|
|
124
|
+
treeTier: (topicId: Uint8Array) => number;
|
|
125
|
+
/** The tier-`(d − 1)` parent cohort coord, target of a {@link DemotionNoticeV1}. */
|
|
126
|
+
parentCoord: (topicId: Uint8Array) => Uint8Array;
|
|
127
|
+
/** Cohort epoch (raw bytes) the notices are signed under. */
|
|
128
|
+
cohortEpoch: () => Uint8Array;
|
|
129
|
+
/** Threshold signer (the gossip ticket's `k − x` cohort signer). */
|
|
130
|
+
signer: CohortSigner;
|
|
131
|
+
config?: PromotionConfig;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Promotion / demotion lifecycle for one cohort across all the topics it serves. Keyed by `topicId`;
|
|
136
|
+
* each topic carries its own promoted/low-load/sticky state.
|
|
137
|
+
*/
|
|
138
|
+
export interface PromotionLifecycle {
|
|
139
|
+
/**
|
|
140
|
+
* React to a change in `topicId`'s direct-participant count (call after every arrival/eviction).
|
|
141
|
+
* Refreshes the growth + hysteresis clocks and, if a promote trigger fires and the cohort is not
|
|
142
|
+
* already promoted, threshold-signs and returns a {@link PromotionNoticeV1}. Returns `undefined`
|
|
143
|
+
* when no promotion fires.
|
|
144
|
+
*/
|
|
145
|
+
onParticipantCountChange(topicId: Uint8Array, now: number): Promise<PromotionNoticeV1 | undefined>;
|
|
146
|
+
/**
|
|
147
|
+
* Time-driven demotion check (call on the gossip tick). Returns a threshold-signed
|
|
148
|
+
* {@link DemotionNoticeV1} for the parent when every demotion condition holds, else `undefined`.
|
|
149
|
+
*/
|
|
150
|
+
maybeDemote(topicId: Uint8Array, now: number): Promise<DemotionNoticeV1 | undefined>;
|
|
151
|
+
/** Whether `topicId` is currently in promoted mode (new registrations get `Promoted(d+1)`). */
|
|
152
|
+
isPromoted(topicId: Uint8Array): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Adopt a promotion this member did **not** originate (learned via a verified broadcast notice).
|
|
155
|
+
* Sets `promoted = true` for `n.topicId` without re-signing. **Precondition: the caller has verified
|
|
156
|
+
* `n`'s threshold signature** — this module performs no crypto. Idempotent and `effectiveAt`-ordered
|
|
157
|
+
* (see {@link PromotionState.lastEffectiveAt}): a notice not strictly newer than the last adopted
|
|
158
|
+
* transition is a no-op, so this member's own echoed broadcast and stale replays are absorbed.
|
|
159
|
+
*/
|
|
160
|
+
applyPromotionNotice(n: PromotionNoticeV1, now: number): void;
|
|
161
|
+
/**
|
|
162
|
+
* Adopt a demotion this member did **not** originate (learned via a verified broadcast notice).
|
|
163
|
+
* Clears `promoted` state for `n.topicId` without re-signing. Same precondition, idempotency, and
|
|
164
|
+
* `effectiveAt` ordering as {@link applyPromotionNotice} — a stale promotion can never un-demote.
|
|
165
|
+
*/
|
|
166
|
+
applyDemotionNotice(n: DemotionNoticeV1, now: number): void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class CohortPromotionLifecycle implements PromotionLifecycle {
|
|
170
|
+
private readonly states = new Map<string, PromotionState>();
|
|
171
|
+
private readonly capPromote: number;
|
|
172
|
+
private readonly capPromoteFast: number;
|
|
173
|
+
private readonly bucketOverload: number;
|
|
174
|
+
private readonly capDemote: number;
|
|
175
|
+
private readonly tDemoteMs: number;
|
|
176
|
+
private readonly tPromoteLookaheadMs: number;
|
|
177
|
+
private readonly tPromoteStickyMs: number;
|
|
178
|
+
private readonly growthWindowMs: number;
|
|
179
|
+
|
|
180
|
+
constructor(private readonly deps: PromotionDeps) {
|
|
181
|
+
const cfg = deps.config ?? {};
|
|
182
|
+
this.capPromote = cfg.capPromote ?? DEFAULT_CAP_PROMOTE;
|
|
183
|
+
this.capPromoteFast = cfg.capPromoteFast ?? DEFAULT_CAP_PROMOTE_FAST;
|
|
184
|
+
this.bucketOverload = cfg.bucketOverload ?? DEFAULT_BUCKET_OVERLOAD;
|
|
185
|
+
this.capDemote = cfg.capDemote ?? DEFAULT_CAP_DEMOTE;
|
|
186
|
+
this.tDemoteMs = cfg.tDemoteMs ?? DEFAULT_T_DEMOTE_MS;
|
|
187
|
+
this.tPromoteLookaheadMs = cfg.tPromoteLookaheadMs ?? DEFAULT_T_PROMOTE_LOOKAHEAD_MS;
|
|
188
|
+
this.tPromoteStickyMs = cfg.tPromoteStickyMs ?? DEFAULT_T_PROMOTE_STICKY_MS;
|
|
189
|
+
this.growthWindowMs = cfg.growthWindowMs ?? DEFAULT_GROWTH_WINDOW_MS;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async onParticipantCountChange(topicId: Uint8Array, now: number): Promise<PromotionNoticeV1 | undefined> {
|
|
193
|
+
const state = this.stateFor(topicId);
|
|
194
|
+
const count = this.deps.store.directParticipants(topicId);
|
|
195
|
+
this.pushGrowthSample(state, count, now);
|
|
196
|
+
this.refreshLowLoadClock(state, count, now);
|
|
197
|
+
if (state.promoted) {
|
|
198
|
+
return undefined; // already promoted — sticky until a demotion clears it
|
|
199
|
+
}
|
|
200
|
+
if (!this.promotionTriggered(topicId, state, count, now)) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
return this.promote(topicId, state, now);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async maybeDemote(topicId: Uint8Array, now: number): Promise<DemotionNoticeV1 | undefined> {
|
|
207
|
+
const state = this.states.get(bytesKey(topicId));
|
|
208
|
+
if (state === undefined) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
if (!this.demotionTriggered(topicId, state, now)) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
return this.demote(topicId, state, now);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
isPromoted(topicId: Uint8Array): boolean {
|
|
218
|
+
return this.states.get(bytesKey(topicId))?.promoted ?? false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// --- remote apply (verified notices this member did not originate) ---
|
|
222
|
+
|
|
223
|
+
applyPromotionNotice(n: PromotionNoticeV1, now: number): void {
|
|
224
|
+
const state = this.stateFor(b64urlToBytes(n.topicId));
|
|
225
|
+
if (!this.isNewerTransition(state, n.effectiveAt)) {
|
|
226
|
+
return; // already adopted, our own echoed broadcast, or a stale replay
|
|
227
|
+
}
|
|
228
|
+
state.promoted = true;
|
|
229
|
+
state.promotedAt = now;
|
|
230
|
+
state.lastEffectiveAt = n.effectiveAt;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
applyDemotionNotice(n: DemotionNoticeV1, _now: number): void {
|
|
234
|
+
const state = this.stateFor(b64urlToBytes(n.topicId));
|
|
235
|
+
if (!this.isNewerTransition(state, n.effectiveAt)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// Mirror the local demote() release so a later re-growth re-evaluates cleanly.
|
|
239
|
+
state.promoted = false;
|
|
240
|
+
state.promotedAt = undefined;
|
|
241
|
+
state.lowLoadSince = undefined;
|
|
242
|
+
state.lastEffectiveAt = n.effectiveAt;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** A notice is adopted only if strictly newer than the last transition we adopted (the high-water mark). */
|
|
246
|
+
private isNewerTransition(state: PromotionState, effectiveAt: number): boolean {
|
|
247
|
+
return state.lastEffectiveAt === undefined || effectiveAt > state.lastEffectiveAt;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// --- promotion ---
|
|
251
|
+
|
|
252
|
+
private promotionTriggered(topicId: Uint8Array, state: PromotionState, count: number, now: number): boolean {
|
|
253
|
+
if (count >= this.capPromote) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (this.deps.loadBucket(topicId) >= this.bucketOverload && count >= this.capPromoteFast) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
return this.slopePredictsCrossing(state, count, now);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Linear extrapolation over the growth window: will `directParticipants` cross `cap_promote` within lookahead? */
|
|
263
|
+
private slopePredictsCrossing(state: PromotionState, count: number, now: number): boolean {
|
|
264
|
+
const samples = state.samples;
|
|
265
|
+
if (samples.length < 2) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
const first = samples[0]!;
|
|
269
|
+
const last = samples[samples.length - 1]!;
|
|
270
|
+
const span = last.t - first.t;
|
|
271
|
+
if (span <= 0) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const slope = (last.count - first.count) / span; // participants per ms
|
|
275
|
+
if (slope <= 0) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
const predicted = count + slope * this.tPromoteLookaheadMs;
|
|
279
|
+
return predicted >= this.capPromote;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async promote(topicId: Uint8Array, state: PromotionState, now: number): Promise<PromotionNoticeV1> {
|
|
283
|
+
const fromTier = this.deps.treeTier(topicId);
|
|
284
|
+
const topicB64 = bytesToB64url(topicId);
|
|
285
|
+
const epochB64 = bytesToB64url(this.deps.cohortEpoch());
|
|
286
|
+
const signable = { topicId: topicB64, fromTier, toTier: fromTier + 1, effectiveAt: now, cohortEpoch: epochB64 };
|
|
287
|
+
const { thresholdSig, signers } = await this.deps.signer.thresholdSign(promotionNoticeSigningPayload(signable));
|
|
288
|
+
state.promoted = true;
|
|
289
|
+
state.promotedAt = now;
|
|
290
|
+
state.lastEffectiveAt = now; // local effectiveAt — so our own echoed broadcast applies as a no-op
|
|
291
|
+
return {
|
|
292
|
+
v: 1,
|
|
293
|
+
topicId: topicB64,
|
|
294
|
+
fromTier,
|
|
295
|
+
toTier: fromTier + 1,
|
|
296
|
+
effectiveAt: now,
|
|
297
|
+
thresholdSig: bytesToB64url(thresholdSig),
|
|
298
|
+
signers: signers.map(bytesToB64url),
|
|
299
|
+
cohortEpoch: epochB64,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// --- demotion ---
|
|
304
|
+
|
|
305
|
+
private demotionTriggered(topicId: Uint8Array, state: PromotionState, now: number): boolean {
|
|
306
|
+
// The root (tree tier 0) has no parent to hand off to — it never demotes.
|
|
307
|
+
if (this.deps.treeTier(topicId) <= 0) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
// Sticky: a freshly-promoted cohort holds promoted mode through transient drops.
|
|
311
|
+
if (state.promoted && state.promotedAt !== undefined && now - state.promotedAt < this.tPromoteStickyMs) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
// Never collapse a cohort that still has live children beneath it.
|
|
315
|
+
if (this.deps.childCohortCount(topicId) > 0) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
if (this.deps.store.directParticipants(topicId) > this.capDemote) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
if (state.lowLoadSince === undefined) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return now - state.lowLoadSince >= this.tDemoteMs;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async demote(topicId: Uint8Array, state: PromotionState, now: number): Promise<DemotionNoticeV1> {
|
|
328
|
+
const tier = this.deps.treeTier(topicId);
|
|
329
|
+
const topicB64 = bytesToB64url(topicId);
|
|
330
|
+
const epochB64 = bytesToB64url(this.deps.cohortEpoch());
|
|
331
|
+
const parentB64 = bytesToB64url(this.deps.parentCoord(topicId));
|
|
332
|
+
const signable = { topicId: topicB64, tier, parentCohortCoord: parentB64, effectiveAt: now, cohortEpoch: epochB64 };
|
|
333
|
+
const { thresholdSig, signers } = await this.deps.signer.thresholdSign(demotionNoticeSigningPayload(signable));
|
|
334
|
+
// Release forwarder state: a demoted cohort leaves promoted mode and resets its clocks so a
|
|
335
|
+
// later re-growth re-evaluates cleanly.
|
|
336
|
+
state.promoted = false;
|
|
337
|
+
state.promotedAt = undefined;
|
|
338
|
+
state.lowLoadSince = undefined;
|
|
339
|
+
state.lastEffectiveAt = now; // local effectiveAt — so our own echoed broadcast applies as a no-op
|
|
340
|
+
return {
|
|
341
|
+
v: 1,
|
|
342
|
+
topicId: topicB64,
|
|
343
|
+
tier,
|
|
344
|
+
parentCohortCoord: parentB64,
|
|
345
|
+
effectiveAt: now,
|
|
346
|
+
thresholdSig: bytesToB64url(thresholdSig),
|
|
347
|
+
signers: signers.map(bytesToB64url),
|
|
348
|
+
cohortEpoch: epochB64,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// --- bookkeeping ---
|
|
353
|
+
|
|
354
|
+
private stateFor(topicId: Uint8Array): PromotionState {
|
|
355
|
+
const key = bytesKey(topicId);
|
|
356
|
+
let state = this.states.get(key);
|
|
357
|
+
if (state === undefined) {
|
|
358
|
+
state = { promoted: false, samples: [] };
|
|
359
|
+
this.states.set(key, state);
|
|
360
|
+
}
|
|
361
|
+
return state;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private pushGrowthSample(state: PromotionState, count: number, now: number): void {
|
|
365
|
+
state.samples.push({ t: now, count });
|
|
366
|
+
const cutoff = now - this.growthWindowMs;
|
|
367
|
+
while (state.samples.length > 1 && state.samples[0]!.t < cutoff) {
|
|
368
|
+
state.samples.shift();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private refreshLowLoadClock(state: PromotionState, count: number, now: number): void {
|
|
373
|
+
if (count <= this.capDemote) {
|
|
374
|
+
state.lowLoadSince ??= now;
|
|
375
|
+
} else {
|
|
376
|
+
state.lowLoadSince = undefined;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Build a {@link PromotionLifecycle} over the injected store, barometer resolver, and signer. */
|
|
382
|
+
export function createPromotionLifecycle(deps: PromotionDeps): PromotionLifecycle {
|
|
383
|
+
return new CohortPromotionLifecycle(deps);
|
|
384
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic substrate — byte-array helpers shared by the registration store, sharding, and
|
|
3
|
+
* handoff. Peer ids and topic ids are raw {@link Uint8Array}; these give them stable string map
|
|
4
|
+
* keys, value equality, and the ascending order the sharding hash relies on.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { compare as compareU8 } from "uint8arrays/compare";
|
|
8
|
+
import { equals as equalsU8 } from "uint8arrays/equals";
|
|
9
|
+
import { bytesToB64url } from "../wire/codec.js";
|
|
10
|
+
|
|
11
|
+
/** Stable, collision-free string map key for a byte array (base64url, the package's canonical form). */
|
|
12
|
+
export function bytesKey(b: Uint8Array): string {
|
|
13
|
+
return bytesToB64url(b);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Composite key for a registration, `topicKey|participantKey`. */
|
|
17
|
+
export function recordKey(topicId: Uint8Array, participantId: Uint8Array): string {
|
|
18
|
+
return `${bytesKey(topicId)}|${bytesKey(participantId)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Value equality over byte arrays. */
|
|
22
|
+
export function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
23
|
+
return equalsU8(a, b);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Lexicographic byte order — the `sort(... by PeerId ascending)` of §Primary and backup sharding. */
|
|
27
|
+
export function compareBytes(a: Uint8Array, b: Uint8Array): number {
|
|
28
|
+
return compareU8(a, b);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Concatenate two byte arrays into a fresh buffer (the `participantId ‖ cohortEpoch` hash input). */
|
|
32
|
+
export function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
33
|
+
const out = new Uint8Array(a.length + b.length);
|
|
34
|
+
out.set(a, 0);
|
|
35
|
+
out.set(b, a.length);
|
|
36
|
+
return out;
|
|
37
|
+
}
|