@optimystic/db-core 0.14.0 → 0.16.2
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/blocks/block-store.d.ts +18 -1
- package/dist/src/blocks/block-store.d.ts.map +1 -1
- package/dist/src/blocks/helpers.d.ts +2 -2
- package/dist/src/blocks/helpers.d.ts.map +1 -1
- package/dist/src/blocks/helpers.js +2 -2
- package/dist/src/blocks/helpers.js.map +1 -1
- package/dist/src/btree/btree.d.ts +27 -6
- package/dist/src/btree/btree.d.ts.map +1 -1
- package/dist/src/btree/btree.js +104 -61
- package/dist/src/btree/btree.js.map +1 -1
- package/dist/src/chain/chain.d.ts.map +1 -1
- package/dist/src/chain/chain.js +11 -1
- package/dist/src/chain/chain.js.map +1 -1
- package/dist/src/cluster/index.d.ts +1 -0
- package/dist/src/cluster/index.d.ts.map +1 -1
- package/dist/src/cluster/index.js +1 -0
- package/dist/src/cluster/index.js.map +1 -1
- package/dist/src/cluster/membership.d.ts +49 -0
- package/dist/src/cluster/membership.d.ts.map +1 -0
- package/dist/src/cluster/membership.js +75 -0
- package/dist/src/cluster/membership.js.map +1 -0
- package/dist/src/cluster/structs.d.ts +50 -1
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cluster/structs.js +9 -1
- package/dist/src/cluster/structs.js.map +1 -1
- package/dist/src/cohort-topic/addressing.d.ts +8 -2
- package/dist/src/cohort-topic/addressing.d.ts.map +1 -1
- package/dist/src/cohort-topic/addressing.js +11 -3
- package/dist/src/cohort-topic/addressing.js.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +13 -7
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +17 -6
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts +17 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.js +33 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts +7 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.js +8 -2
- package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -1
- package/dist/src/cohort-topic/coldstart.d.ts +34 -14
- package/dist/src/cohort-topic/coldstart.d.ts.map +1 -1
- package/dist/src/cohort-topic/coldstart.js +25 -11
- package/dist/src/cohort-topic/coldstart.js.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts +12 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.js +27 -1
- package/dist/src/cohort-topic/gossip/bus.js.map +1 -1
- package/dist/src/cohort-topic/gossip/view.d.ts +8 -0
- package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/view.js +17 -1
- package/dist/src/cohort-topic/gossip/view.js.map +1 -1
- package/dist/src/cohort-topic/member-engine.d.ts +10 -3
- package/dist/src/cohort-topic/member-engine.d.ts.map +1 -1
- package/dist/src/cohort-topic/member-engine.js +29 -14
- package/dist/src/cohort-topic/member-engine.js.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.d.ts +14 -7
- package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.js +15 -27
- package/dist/src/cohort-topic/membership/publisher.js.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.d.ts +35 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.js +88 -3
- package/dist/src/cohort-topic/membership/verifier.js.map +1 -1
- package/dist/src/cohort-topic/promotion.d.ts +7 -0
- package/dist/src/cohort-topic/promotion.d.ts.map +1 -1
- package/dist/src/cohort-topic/promotion.js +16 -2
- package/dist/src/cohort-topic/promotion.js.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.d.ts +18 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.js +54 -0
- package/dist/src/cohort-topic/registration/renewal.js.map +1 -1
- package/dist/src/cohort-topic/registration/types.d.ts +12 -0
- package/dist/src/cohort-topic/registration/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/types.js +14 -0
- package/dist/src/cohort-topic/registration/types.js.map +1 -1
- package/dist/src/cohort-topic/service.d.ts +4 -3
- package/dist/src/cohort-topic/service.d.ts.map +1 -1
- package/dist/src/cohort-topic/service.js +38 -12
- package/dist/src/cohort-topic/service.js.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.d.ts +23 -7
- package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.js +22 -6
- package/dist/src/cohort-topic/sig/payloads.js.map +1 -1
- package/dist/src/cohort-topic/traffic.d.ts +6 -0
- package/dist/src/cohort-topic/traffic.d.ts.map +1 -1
- package/dist/src/cohort-topic/traffic.js +16 -1
- package/dist/src/cohort-topic/traffic.js.map +1 -1
- package/dist/src/cohort-topic/walk.d.ts +22 -7
- package/dist/src/cohort-topic/walk.d.ts.map +1 -1
- package/dist/src/cohort-topic/walk.js +78 -18
- package/dist/src/cohort-topic/walk.js.map +1 -1
- package/dist/src/cohort-topic/wire/codec.d.ts +3 -1
- package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/codec.js +7 -1
- package/dist/src/cohort-topic/wire/codec.js.map +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/index.js +1 -1
- package/dist/src/cohort-topic/wire/index.js.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.d.ts +5 -4
- package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.js +14 -5
- package/dist/src/cohort-topic/wire/payloads.js.map +1 -1
- package/dist/src/cohort-topic/wire/primitives.d.ts +65 -0
- package/dist/src/cohort-topic/wire/primitives.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/primitives.js +173 -0
- package/dist/src/cohort-topic/wire/primitives.js.map +1 -0
- package/dist/src/cohort-topic/wire/types.d.ts +132 -5
- package/dist/src/cohort-topic/wire/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.d.ts +15 -5
- package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.js +146 -123
- package/dist/src/cohort-topic/wire/validate.js.map +1 -1
- package/dist/src/collection/collection.d.ts +23 -12
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +111 -35
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collection/struct.d.ts +37 -2
- package/dist/src/collection/struct.d.ts.map +1 -1
- package/dist/src/collection/struct.js +16 -1
- package/dist/src/collection/struct.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +18 -1
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +36 -6
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/log/log.d.ts +26 -3
- package/dist/src/log/log.d.ts.map +1 -1
- package/dist/src/log/log.js +34 -7
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/log/struct.d.ts +5 -4
- package/dist/src/log/struct.d.ts.map +1 -1
- package/dist/src/log/struct.js.map +1 -1
- package/dist/src/matchmaking/wire.d.ts +2 -1
- package/dist/src/matchmaking/wire.d.ts.map +1 -1
- package/dist/src/matchmaking/wire.js +13 -92
- package/dist/src/matchmaking/wire.js.map +1 -1
- package/dist/src/network/i-key-network.d.ts +9 -0
- package/dist/src/network/i-key-network.d.ts.map +1 -1
- package/dist/src/network/i-repo.d.ts +8 -0
- package/dist/src/network/i-repo.d.ts.map +1 -1
- package/dist/src/network/struct.d.ts +10 -0
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.js +4 -33
- package/dist/src/reactivity/push-state.js.map +1 -1
- package/dist/src/reactivity/wire-validate.d.ts +7 -22
- package/dist/src/reactivity/wire-validate.d.ts.map +1 -1
- package/dist/src/reactivity/wire-validate.js +7 -73
- package/dist/src/reactivity/wire-validate.js.map +1 -1
- package/dist/src/reactivity/wire.js +3 -3
- package/dist/src/reactivity/wire.js.map +1 -1
- package/dist/src/testing/async-wait.d.ts +23 -0
- package/dist/src/testing/async-wait.d.ts.map +1 -0
- package/dist/src/testing/async-wait.js +42 -0
- package/dist/src/testing/async-wait.js.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +3 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/test-transactor.d.ts +44 -0
- package/dist/src/testing/test-transactor.d.ts.map +1 -0
- package/dist/src/testing/test-transactor.js +423 -0
- package/dist/src/testing/test-transactor.js.map +1 -0
- package/dist/src/transaction/actions-engine.d.ts +6 -3
- package/dist/src/transaction/actions-engine.d.ts.map +1 -1
- package/dist/src/transaction/actions-engine.js +8 -7
- package/dist/src/transaction/actions-engine.js.map +1 -1
- package/dist/src/transaction/coordinator.d.ts +43 -22
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +443 -178
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/errors.d.ts +82 -0
- package/dist/src/transaction/errors.d.ts.map +1 -0
- package/dist/src/transaction/errors.js +93 -0
- package/dist/src/transaction/errors.js.map +1 -0
- package/dist/src/transaction/index.d.ts +6 -3
- package/dist/src/transaction/index.d.ts.map +1 -1
- package/dist/src/transaction/index.js +4 -2
- package/dist/src/transaction/index.js.map +1 -1
- package/dist/src/transaction/operations-hash.d.ts +119 -0
- package/dist/src/transaction/operations-hash.d.ts.map +1 -0
- package/dist/src/transaction/operations-hash.js +180 -0
- package/dist/src/transaction/operations-hash.js.map +1 -0
- package/dist/src/transaction/read-dependency-collector.d.ts +43 -0
- package/dist/src/transaction/read-dependency-collector.d.ts.map +1 -0
- package/dist/src/transaction/read-dependency-collector.js +69 -0
- package/dist/src/transaction/read-dependency-collector.js.map +1 -0
- package/dist/src/transaction/session.d.ts +12 -3
- package/dist/src/transaction/session.d.ts.map +1 -1
- package/dist/src/transaction/session.js +35 -9
- package/dist/src/transaction/session.js.map +1 -1
- package/dist/src/transaction/transaction.d.ts +162 -1
- package/dist/src/transaction/transaction.d.ts.map +1 -1
- package/dist/src/transaction/transaction.js +85 -3
- package/dist/src/transaction/transaction.js.map +1 -1
- package/dist/src/transaction/validator.d.ts +3 -11
- package/dist/src/transaction/validator.d.ts.map +1 -1
- package/dist/src/transaction/validator.js +69 -28
- package/dist/src/transaction/validator.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts +35 -0
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +146 -40
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts +19 -5
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +43 -11
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transform/atomic-proxy.d.ts +35 -5
- package/dist/src/transform/atomic-proxy.d.ts.map +1 -1
- package/dist/src/transform/atomic-proxy.js +39 -7
- package/dist/src/transform/atomic-proxy.js.map +1 -1
- package/dist/src/transform/cache-source.d.ts +36 -5
- package/dist/src/transform/cache-source.d.ts.map +1 -1
- package/dist/src/transform/cache-source.js +84 -8
- package/dist/src/transform/cache-source.js.map +1 -1
- package/dist/src/transform/helpers.d.ts.map +1 -1
- package/dist/src/transform/helpers.js +8 -2
- package/dist/src/transform/helpers.js.map +1 -1
- package/dist/src/transform/tracker.d.ts +17 -2
- package/dist/src/transform/tracker.d.ts.map +1 -1
- package/dist/src/transform/tracker.js +66 -11
- package/dist/src/transform/tracker.js.map +1 -1
- package/dist/src/utility/backoff.d.ts +48 -0
- package/dist/src/utility/backoff.d.ts.map +1 -0
- package/dist/src/utility/backoff.js +60 -0
- package/dist/src/utility/backoff.js.map +1 -0
- package/dist/src/utility/pending.d.ts +1 -0
- package/dist/src/utility/pending.d.ts.map +1 -1
- package/dist/src/utility/pending.js +3 -1
- package/dist/src/utility/pending.js.map +1 -1
- package/package.json +4 -4
- package/src/blocks/block-store.ts +19 -1
- package/src/blocks/helpers.ts +3 -3
- package/src/btree/btree.ts +108 -63
- package/src/chain/chain.ts +11 -1
- package/src/cluster/index.ts +1 -0
- package/src/cluster/membership.ts +85 -0
- package/src/cluster/structs.ts +51 -1
- package/src/cohort-topic/addressing.ts +12 -4
- package/src/cohort-topic/antidos/bootstrap-evidence.ts +21 -9
- package/src/cohort-topic/antidos/replay-guard.ts +37 -0
- package/src/cohort-topic/antidos/topic-budget.ts +17 -2
- package/src/cohort-topic/coldstart.ts +48 -15
- package/src/cohort-topic/gossip/bus.ts +39 -2
- package/src/cohort-topic/gossip/view.ts +26 -1
- package/src/cohort-topic/member-engine.ts +36 -15
- package/src/cohort-topic/membership/publisher.ts +25 -33
- package/src/cohort-topic/membership/verifier.ts +119 -3
- package/src/cohort-topic/promotion.ts +23 -2
- package/src/cohort-topic/registration/renewal.ts +73 -1
- package/src/cohort-topic/registration/types.ts +15 -0
- package/src/cohort-topic/service.ts +41 -15
- package/src/cohort-topic/sig/payloads.ts +29 -9
- package/src/cohort-topic/traffic.ts +23 -1
- package/src/cohort-topic/walk.ts +95 -24
- package/src/cohort-topic/wire/codec.ts +12 -0
- package/src/cohort-topic/wire/index.ts +2 -0
- package/src/cohort-topic/wire/payloads.ts +14 -5
- package/src/cohort-topic/wire/primitives.ts +188 -0
- package/src/cohort-topic/wire/types.ts +136 -4
- package/src/cohort-topic/wire/validate.ts +168 -134
- package/src/collection/collection.ts +123 -40
- package/src/collection/struct.ts +44 -2
- package/src/collections/tree/tree.ts +42 -5
- package/src/index.ts +1 -0
- package/src/log/log.ts +37 -9
- package/src/log/struct.ts +5 -4
- package/src/matchmaking/wire.ts +25 -102
- package/src/network/i-key-network.ts +10 -0
- package/src/network/i-repo.ts +8 -0
- package/src/network/struct.ts +10 -0
- package/src/reactivity/push-state.ts +4 -37
- package/src/reactivity/wire-validate.ts +7 -81
- package/src/reactivity/wire.ts +3 -3
- package/src/testing/async-wait.ts +65 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/test-transactor.ts +468 -0
- package/src/transaction/actions-engine.ts +8 -6
- package/src/transaction/coordinator.ts +498 -229
- package/src/transaction/errors.ts +91 -0
- package/src/transaction/index.ts +23 -3
- package/src/transaction/operations-hash.ts +196 -0
- package/src/transaction/read-dependency-collector.ts +78 -0
- package/src/transaction/session.ts +37 -9
- package/src/transaction/transaction.ts +211 -4
- package/src/transaction/validator.ts +73 -48
- package/src/transactor/network-transactor.ts +164 -49
- package/src/transactor/transactor-source.ts +47 -12
- package/src/transform/atomic-proxy.ts +92 -49
- package/src/transform/cache-source.ts +87 -9
- package/src/transform/helpers.ts +8 -2
- package/src/transform/tracker.ts +69 -11
- package/src/utility/backoff.ts +95 -0
- package/src/utility/pending.ts +3 -1
- package/src/transaction/context.ts +0 -103
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cohort-topic substrate — bootstrap evidence policy (anti-DoS).
|
|
3
3
|
*
|
|
4
|
-
* Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold cohort that accepts a cold-start
|
|
5
|
+
* registration must demand evidence, so a cold topic cannot be instantiated for free by an attacker.
|
|
6
|
+
* **A follow-on cold-start (`followOn: true`) is gated identically to a root bootstrap
|
|
7
|
+
* (`bootstrap: true`)**: the cold tier-`(d+1)` child a `Promoted` redirect points at is participant-
|
|
8
|
+
* asserted (the child cannot infer the redirect — the routing key decorrelates tiers and it holds no
|
|
9
|
+
* breadcrumb of where the participant came from), so its safety cannot rest on provenance; it rests on
|
|
10
|
+
* this gate paying the same anti-abuse cost the root already pays. The accepted evidence is
|
|
11
|
+
* **tier-dependent**:
|
|
7
12
|
*
|
|
8
13
|
* - **T0 / T1** — generally no proof-of-work, because these tiers correspond to committed work; a
|
|
9
14
|
* *signed reference to a committed parent topic that does exist* is sufficient.
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
* `bootstrapEvidence` field — a versioned `BootstrapEvidenceEnvelopeV1`
|
|
19
24
|
* (`./bootstrap-evidence-envelope.js`), parsed crypto-free here and checked by the injected verifiers;
|
|
20
25
|
* NOT the opaque `appPayload` slot (which the cohort copies verbatim into the registration's appState).
|
|
21
|
-
* A registration that is
|
|
26
|
+
* A registration that is neither a bootstrap nor a follow-on cold-start needs no evidence and is admitted.
|
|
22
27
|
*/
|
|
23
28
|
import type { RegisterV1 } from "../wire/types.js";
|
|
24
29
|
/** Tiers at/below this need no proof-of-work — a signed parent reference suffices (T0/T1). */
|
|
@@ -39,9 +44,10 @@ export interface BootstrapEvidenceConfig {
|
|
|
39
44
|
/** Tier-dependent bootstrap-evidence gate for cold-root registrations. */
|
|
40
45
|
export interface BootstrapEvidence {
|
|
41
46
|
/**
|
|
42
|
-
* Whether `reg` carries acceptable
|
|
43
|
-
* always acceptable (nothing to prove). For a
|
|
44
|
-
* T2/T3 accept proof-of-work OR a reputation signature OR a
|
|
47
|
+
* Whether `reg` carries acceptable cold-start evidence for `tier`. A registration that is neither a
|
|
48
|
+
* `bootstrap` nor a `followOn` is always acceptable (nothing to prove). For a cold-start (either flag):
|
|
49
|
+
* T0/T1 require a signed parent reference; T2/T3 accept proof-of-work OR a reputation signature OR a
|
|
50
|
+
* signed parent reference.
|
|
45
51
|
*/
|
|
46
52
|
verify(reg: RegisterV1, tier: number): boolean;
|
|
47
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap-evidence.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bootstrap-evidence.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,8FAA8F;AAC9F,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,MAAM,WAAW,qBAAqB;IACrC,gGAAgG;IAChG,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IACzC,kGAAkG;IAClG,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IAChD,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IACrD,MAAM,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACvC,mGAAmG;IACnG,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/C;AA0CD,uGAAuG;AACvG,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,qBAA0B,GAAG,iBAAiB,CAE3F"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cohort-topic substrate — bootstrap evidence policy (anti-DoS).
|
|
3
3
|
*
|
|
4
|
-
* Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold cohort that accepts a cold-start
|
|
5
|
+
* registration must demand evidence, so a cold topic cannot be instantiated for free by an attacker.
|
|
6
|
+
* **A follow-on cold-start (`followOn: true`) is gated identically to a root bootstrap
|
|
7
|
+
* (`bootstrap: true`)**: the cold tier-`(d+1)` child a `Promoted` redirect points at is participant-
|
|
8
|
+
* asserted (the child cannot infer the redirect — the routing key decorrelates tiers and it holds no
|
|
9
|
+
* breadcrumb of where the participant came from), so its safety cannot rest on provenance; it rests on
|
|
10
|
+
* this gate paying the same anti-abuse cost the root already pays. The accepted evidence is
|
|
11
|
+
* **tier-dependent**:
|
|
7
12
|
*
|
|
8
13
|
* - **T0 / T1** — generally no proof-of-work, because these tiers correspond to committed work; a
|
|
9
14
|
* *signed reference to a committed parent topic that does exist* is sufficient.
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
* `bootstrapEvidence` field — a versioned `BootstrapEvidenceEnvelopeV1`
|
|
19
24
|
* (`./bootstrap-evidence-envelope.js`), parsed crypto-free here and checked by the injected verifiers;
|
|
20
25
|
* NOT the opaque `appPayload` slot (which the cohort copies verbatim into the registration's appState).
|
|
21
|
-
* A registration that is
|
|
26
|
+
* A registration that is neither a bootstrap nor a follow-on cold-start needs no evidence and is admitted.
|
|
22
27
|
*/
|
|
23
28
|
import { createLogger } from "../../logger.js";
|
|
24
29
|
const log = createLogger("cohort-topic:antidos");
|
|
@@ -36,8 +41,14 @@ class TieredBootstrapEvidence {
|
|
|
36
41
|
this.verifyParentReference = deps.verifyParentReference ?? (() => false);
|
|
37
42
|
}
|
|
38
43
|
verify(reg, tier) {
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
// A follow-on cold-start is gated identically to a root bootstrap — both must carry evidence.
|
|
45
|
+
// NOTE: follow-on cold-start is PoW-gated exactly like a root bootstrap; if a spoofed, PoW-paid
|
|
46
|
+
// follow-on instantiation ever shows up as real abuse, upgrade to a parent-vouched redirect —
|
|
47
|
+
// echo the parent cohort's threshold-signed PromotionNoticeV1 on the follow-on and verify it
|
|
48
|
+
// against the parent's MembershipCertV1 (needs a RegisterReplyV1 field + admission-path membership
|
|
49
|
+
// verify). See docs/cohort-topic.md §Anti-DoS "follow-on hardening".
|
|
50
|
+
if (reg.bootstrap !== true && reg.followOn !== true) {
|
|
51
|
+
return true; // only a cold-start (bootstrap or follow-on) must carry evidence
|
|
41
52
|
}
|
|
42
53
|
if (tier <= this.maxNoPowTier) {
|
|
43
54
|
// T0/T1: a signed reference to a committed parent topic is sufficient (no PoW expected).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap-evidence.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bootstrap-evidence.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAEjD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AA4BzC,MAAM,uBAAuB;IACX,YAAY,CAAS;IACrB,SAAS,CAA+B;IACxC,gBAAgB,CAA+B;IAC/C,qBAAqB,CAA+B;IAErE,YAAY,OAA8B,EAAE;QAC3C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,IAAI,uBAAuB,CAAC;QACzE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;QACxE,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,CAAC,GAAe,EAAE,IAAY;QACnC,8FAA8F;QAC9F,gGAAgG;QAChG,8FAA8F;QAC9F,6FAA6F;QAC7F,mGAAmG;QACnG,qEAAqE;QACrE,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC,CAAC,iEAAiE;QAC/E,CAAC;QACD,IAAI,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/B,yFAAyF;YACzF,MAAM,EAAE,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,CAAC,EAAE,EAAE,CAAC;gBACT,GAAG,CAAC,oEAAoE,EAAE,IAAI,CAAC,CAAC;YACjF,CAAC;YACD,OAAO,EAAE,CAAC;QACX,CAAC;QACD,+EAA+E;QAC/E,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAChG,IAAI,CAAC,EAAE,EAAE,CAAC;YACT,GAAG,CAAC,8EAA8E,EAAE,IAAI,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,EAAE,CAAC;IACX,CAAC;CACD;AAED,uGAAuG;AACvG,MAAM,UAAU,uBAAuB,CAAC,OAA8B,EAAE;IACvE,OAAO,IAAI,uBAAuB,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -11,16 +11,31 @@
|
|
|
11
11
|
* Because a stale registration is rejected outright, the guard only needs to remember correlation ids
|
|
12
12
|
* for one `maxAgeMs` window: an id older than that would be rejected on timestamp alone, so its record
|
|
13
13
|
* can be pruned. Pruning runs on access, bounding memory to the live window's worth of registrations.
|
|
14
|
+
*
|
|
15
|
+
* On top of the age-based prune, the map carries a **hard LRU `maxKeys` cap** (mirroring the sibling
|
|
16
|
+
* {@link import("./rate-limiter.js").RegisterRateLimiter}) so a flood of genuinely-fresh, admitted
|
|
17
|
+
* correlationIds cannot grow `seen` without bound before the age prune fires. When a new id would
|
|
18
|
+
* exceed the cap, the **oldest-inserted** entries are evicted until within cap. That victim is the
|
|
19
|
+
* least-bad one: replay entries are inserted once and never refreshed, so `Map` insertion order tracks
|
|
20
|
+
* timestamp order — the oldest entry is the one nearest to aging out of the window and being pruned as
|
|
21
|
+
* stale anyway. Evicting it forgives at most that entry's remaining replay-protection window: a bounded,
|
|
22
|
+
* documented tradeoff (unlike the rate limiter's fully penalty-free eviction), and one that only
|
|
23
|
+
* triggers under a flood of admitted ids — which, in the register pipeline, must also have passed the
|
|
24
|
+
* signature, rate, and bootstrap gates before ever reaching this guard.
|
|
14
25
|
*/
|
|
15
26
|
/** Default acceptance window: a registration timestamped older than this (relative to `now`) is stale. */
|
|
16
27
|
export declare const DEFAULT_REPLAY_MAX_AGE_MS = 60000;
|
|
17
28
|
/** Default tolerated forward clock skew: a timestamp this far past `now` is rejected as implausible. */
|
|
18
29
|
export declare const DEFAULT_REPLAY_MAX_FUTURE_SKEW_MS = 5000;
|
|
30
|
+
/** Default hard cap on remembered correlationIds; the oldest-inserted are evicted beyond this. */
|
|
31
|
+
export declare const DEFAULT_REPLAY_GUARD_MAX_KEYS = 100000;
|
|
19
32
|
export interface CorrelationReplayGuardConfig {
|
|
20
33
|
/** A timestamp older than `now − maxAgeMs` is stale. Default {@link DEFAULT_REPLAY_MAX_AGE_MS}. */
|
|
21
34
|
maxAgeMs?: number;
|
|
22
35
|
/** A timestamp newer than `now + maxFutureSkewMs` is rejected. Default {@link DEFAULT_REPLAY_MAX_FUTURE_SKEW_MS}. */
|
|
23
36
|
maxFutureSkewMs?: number;
|
|
37
|
+
/** Hard LRU cap on remembered correlationIds; oldest-inserted (≈ oldest-timestamp) evicted beyond this. Default {@link DEFAULT_REPLAY_GUARD_MAX_KEYS}. */
|
|
38
|
+
maxKeys?: number;
|
|
24
39
|
}
|
|
25
40
|
/** Freshness + anti-replay gate over registration `correlationId`s and timestamps. */
|
|
26
41
|
export interface CorrelationReplayGuard {
|
|
@@ -31,6 +46,8 @@ export interface CorrelationReplayGuard {
|
|
|
31
46
|
* id on first sight of a fresh registration.
|
|
32
47
|
*/
|
|
33
48
|
accept(correlationId: Uint8Array, peerId: Uint8Array, timestamp: number, now: number): boolean;
|
|
49
|
+
/** Remembered correlationId count (test/diagnostic introspection). */
|
|
50
|
+
readonly size: number;
|
|
34
51
|
}
|
|
35
52
|
/** Build a {@link CorrelationReplayGuard} over the configured staleness window and forward skew. */
|
|
36
53
|
export declare function createCorrelationReplayGuard(config?: CorrelationReplayGuardConfig): CorrelationReplayGuard;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay-guard.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"replay-guard.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH,0GAA0G;AAC1G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAChD,wGAAwG;AACxG,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AACvD,kGAAkG;AAClG,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAErD,MAAM,WAAW,4BAA4B;IAC5C,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qHAAqH;IACrH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0JAA0J;IAC1J,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sFAAsF;AACtF,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/F,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAoFD,oGAAoG;AACpG,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,4BAAiC,GAAG,sBAAsB,CAE9G"}
|
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
* Because a stale registration is rejected outright, the guard only needs to remember correlation ids
|
|
12
12
|
* for one `maxAgeMs` window: an id older than that would be rejected on timestamp alone, so its record
|
|
13
13
|
* can be pruned. Pruning runs on access, bounding memory to the live window's worth of registrations.
|
|
14
|
+
*
|
|
15
|
+
* On top of the age-based prune, the map carries a **hard LRU `maxKeys` cap** (mirroring the sibling
|
|
16
|
+
* {@link import("./rate-limiter.js").RegisterRateLimiter}) so a flood of genuinely-fresh, admitted
|
|
17
|
+
* correlationIds cannot grow `seen` without bound before the age prune fires. When a new id would
|
|
18
|
+
* exceed the cap, the **oldest-inserted** entries are evicted until within cap. That victim is the
|
|
19
|
+
* least-bad one: replay entries are inserted once and never refreshed, so `Map` insertion order tracks
|
|
20
|
+
* timestamp order — the oldest entry is the one nearest to aging out of the window and being pruned as
|
|
21
|
+
* stale anyway. Evicting it forgives at most that entry's remaining replay-protection window: a bounded,
|
|
22
|
+
* documented tradeoff (unlike the rate limiter's fully penalty-free eviction), and one that only
|
|
23
|
+
* triggers under a flood of admitted ids — which, in the register pipeline, must also have passed the
|
|
24
|
+
* signature, rate, and bootstrap gates before ever reaching this guard.
|
|
14
25
|
*/
|
|
15
26
|
import { bytesKey } from "../registration/bytes.js";
|
|
16
27
|
import { createLogger } from "../../logger.js";
|
|
@@ -19,10 +30,13 @@ const log = createLogger("cohort-topic:antidos");
|
|
|
19
30
|
export const DEFAULT_REPLAY_MAX_AGE_MS = 60_000;
|
|
20
31
|
/** Default tolerated forward clock skew: a timestamp this far past `now` is rejected as implausible. */
|
|
21
32
|
export const DEFAULT_REPLAY_MAX_FUTURE_SKEW_MS = 5_000;
|
|
33
|
+
/** Default hard cap on remembered correlationIds; the oldest-inserted are evicted beyond this. */
|
|
34
|
+
export const DEFAULT_REPLAY_GUARD_MAX_KEYS = 100_000;
|
|
22
35
|
class WindowedReplayGuard {
|
|
23
36
|
seen = new Map();
|
|
24
37
|
maxAgeMs;
|
|
25
38
|
maxFutureSkewMs;
|
|
39
|
+
maxKeys;
|
|
26
40
|
/** `now` of the last prune, so pruning amortizes rather than scanning on every call. */
|
|
27
41
|
lastPruneAt = -Infinity;
|
|
28
42
|
constructor(config = {}) {
|
|
@@ -34,6 +48,10 @@ class WindowedReplayGuard {
|
|
|
34
48
|
if (!(this.maxFutureSkewMs >= 0)) {
|
|
35
49
|
throw new RangeError(`maxFutureSkewMs must be >= 0, got ${this.maxFutureSkewMs}`);
|
|
36
50
|
}
|
|
51
|
+
this.maxKeys = config.maxKeys ?? DEFAULT_REPLAY_GUARD_MAX_KEYS;
|
|
52
|
+
if (!Number.isInteger(this.maxKeys) || this.maxKeys <= 0) {
|
|
53
|
+
throw new RangeError(`maxKeys must be a positive integer, got ${this.maxKeys}`);
|
|
54
|
+
}
|
|
37
55
|
}
|
|
38
56
|
accept(correlationId, peerId, timestamp, now) {
|
|
39
57
|
if (timestamp < now - this.maxAgeMs) {
|
|
@@ -53,6 +71,18 @@ class WindowedReplayGuard {
|
|
|
53
71
|
log("replay-guard reject: replayed correlationId (first seen from peer=%s)", prior.peer);
|
|
54
72
|
return false; // replay
|
|
55
73
|
}
|
|
74
|
+
// New id: enforce the hard cap by evicting the oldest-inserted entries (oldest by `Map` insertion
|
|
75
|
+
// order) until this insertion stays within `maxKeys`. Since entries are inserted once and never
|
|
76
|
+
// refreshed, insertion order closely tracks timestamp order, so the victim is ≈ the oldest-timestamp
|
|
77
|
+
// — nearest to aging out as stale. (It is only ≈: ids from distinct peers can arrive out of
|
|
78
|
+
// timestamp order within the skew window, so a victim may retain slightly more window than a
|
|
79
|
+
// strictly-oldest pick. The forgiveness bound — at most one entry's remaining window — holds either way.)
|
|
80
|
+
while (this.seen.size >= this.maxKeys) {
|
|
81
|
+
const oldest = this.seen.keys().next().value;
|
|
82
|
+
if (oldest === undefined)
|
|
83
|
+
break;
|
|
84
|
+
this.seen.delete(oldest);
|
|
85
|
+
}
|
|
56
86
|
this.seen.set(key, { timestamp, peer: bytesKey(peerId) });
|
|
57
87
|
return true;
|
|
58
88
|
}
|
|
@@ -70,6 +100,9 @@ class WindowedReplayGuard {
|
|
|
70
100
|
}
|
|
71
101
|
}
|
|
72
102
|
}
|
|
103
|
+
get size() {
|
|
104
|
+
return this.seen.size;
|
|
105
|
+
}
|
|
73
106
|
}
|
|
74
107
|
/** Build a {@link CorrelationReplayGuard} over the configured staleness window and forward skew. */
|
|
75
108
|
export function createCorrelationReplayGuard(config = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay-guard.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"replay-guard.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,13 @@ export declare const DEFAULT_TOPICS_MAX = 2048;
|
|
|
19
19
|
export interface TopicBudgetConfig {
|
|
20
20
|
/** Max topics with forwarder state. Default {@link DEFAULT_TOPICS_MAX}. */
|
|
21
21
|
topicsMax?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Called with the evicted topic's id just before its slot is freed in {@link TopicBudget.admit}, so
|
|
24
|
+
* the caller can tear down the now-unbacked forwarder + traffic window that outlives the slot. Fires
|
|
25
|
+
* only for a genuine eviction (a zero-participant victim), never for a plain admit, an already-resident
|
|
26
|
+
* re-admit, or a refusal. Absent → no teardown hook.
|
|
27
|
+
*/
|
|
28
|
+
onEvict?: (topicId: Uint8Array) => void;
|
|
22
29
|
}
|
|
23
30
|
/** Per-cohort cap on the number of topics with forwarder state, with LRU eviction of cold topics. */
|
|
24
31
|
export interface TopicBudget {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topic-budget.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"topic-budget.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAED,qGAAqG;AACrG,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,yDAAyD;IACzD,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IAClC;;;;OAIG;IACH,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1D,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AA+FD,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,iBAAsB,GAAG,WAAW,CAE7E"}
|
|
@@ -22,12 +22,14 @@ export const DEFAULT_TOPICS_MAX = 2048;
|
|
|
22
22
|
class LruTopicBudget {
|
|
23
23
|
residents = new Map();
|
|
24
24
|
topicsMax;
|
|
25
|
+
onEvict;
|
|
25
26
|
seqCounter = 0;
|
|
26
27
|
constructor(config = {}) {
|
|
27
28
|
this.topicsMax = config.topicsMax ?? DEFAULT_TOPICS_MAX;
|
|
28
29
|
if (!Number.isInteger(this.topicsMax) || this.topicsMax <= 0) {
|
|
29
30
|
throw new RangeError(`topicsMax must be a positive integer, got ${this.topicsMax}`);
|
|
30
31
|
}
|
|
32
|
+
this.onEvict = config.onEvict;
|
|
31
33
|
}
|
|
32
34
|
get size() {
|
|
33
35
|
return this.residents.size;
|
|
@@ -46,7 +48,7 @@ class LruTopicBudget {
|
|
|
46
48
|
return true;
|
|
47
49
|
}
|
|
48
50
|
if (this.residents.size < this.topicsMax) {
|
|
49
|
-
this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter });
|
|
51
|
+
this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter, topicId });
|
|
50
52
|
return true;
|
|
51
53
|
}
|
|
52
54
|
// Full: only a zero-participant resident may be evicted to make room for a new topic.
|
|
@@ -55,8 +57,12 @@ class LruTopicBudget {
|
|
|
55
57
|
log("topic-budget full size=%d max=%d — refuse new topic", this.residents.size, this.topicsMax);
|
|
56
58
|
return false;
|
|
57
59
|
}
|
|
60
|
+
// Fire the teardown hook with the victim's real bytes BEFORE freeing the slot, so the callback
|
|
61
|
+
// (drop the now-unbacked forwarder + traffic window) sees a coherent state. Only a genuine
|
|
62
|
+
// eviction reaches here — `coldestEvictable()` already refused a full-of-populated budget above.
|
|
63
|
+
this.onEvict?.(this.residents.get(victim).topicId);
|
|
58
64
|
this.residents.delete(victim);
|
|
59
|
-
this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter });
|
|
65
|
+
this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter, topicId });
|
|
60
66
|
log("topic-budget evicted cold topic to admit new (size=%d max=%d)", this.residents.size, this.topicsMax);
|
|
61
67
|
return true;
|
|
62
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topic-budget.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAEjD,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"topic-budget.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,18 +11,22 @@
|
|
|
11
11
|
* cold-start case) **or** it arrived as a follow-on to a parent cohort's `Promoted` redirect, AND
|
|
12
12
|
* - a quorum of cohort members is willing to serve `T` at the registration's tier.
|
|
13
13
|
*
|
|
14
|
-
* The `followOn` signal is
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* The `followOn` signal **is** carried on the wire (`RegisterV1.followOn`), set only on the dedicated
|
|
15
|
+
* re-issue a participant sends after a parent cohort's `Promoted` redirect target answered `NoState`
|
|
16
|
+
* (the deeper child is cold). A cold high-tier cohort still returns `NoState` to a *speculative* `d_max`
|
|
17
|
+
* probe (neither flag set), but the single-direction walk means the only registrations that reach a cold
|
|
18
|
+
* tier-`(d+1)` cohort carrying `followOn: true` are those that followed the parent's `Promoted` redirect.
|
|
19
|
+
* The db-p2p cohort host derives `ctx.followOn` from the wire flag (`followOn: reg.followOn === true`) and
|
|
20
|
+
* db-core takes it as an explicit input to {@link shouldInstantiate}, keeping this module FRET-free. Because
|
|
21
|
+
* the flag is participant-forgeable, a `followOn: true` register is gated by the **same** tier-dependent
|
|
22
|
+
* bootstrap-evidence policy a `bootstrap: true` cold-root register passes (§Anti-DoS), so an unbacked
|
|
23
|
+
* follow-on never reaches this instantiation decision.
|
|
21
24
|
*
|
|
22
25
|
* Once instantiated, the new forwarder **registers itself with its tier-`(d − 1)` parent at first
|
|
23
|
-
* opportunity
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* opportunity** by sending a child-link the parent authenticates + records; until that link is acked
|
|
27
|
+
* (`linked`) it {@link Forwarder.acceptsParticipants | accepts participants} but
|
|
28
|
+
* {@link Forwarder.servesParentOps | holds} notifications/queries that need parent involvement. The root
|
|
29
|
+
* (tree tier 0) has no parent and is serving immediately.
|
|
26
30
|
*
|
|
27
31
|
* **Just-promoted burst (GROUNDING-resolved: bounce, don't buffer).** A cohort that has just promoted
|
|
28
32
|
* but whose tier-`(d+1)` isn't fully instantiated yet, on a burst of new same-tier registrations,
|
|
@@ -82,13 +86,18 @@ export declare function createForwarder(tier: number): Forwarder;
|
|
|
82
86
|
* redirected participant can estimate whether the target tier is hot (§Topic traffic signal).
|
|
83
87
|
*/
|
|
84
88
|
export declare function promotedRedirectReply(targetTier: number, traffic?: TopicTrafficV1): RegisterReplyV1;
|
|
85
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Registers a newly-instantiated forwarder with its tier-`(d − 1)` parent; resolves only when the parent
|
|
91
|
+
* authenticates + records the child and returns a `linked` ack. A `rejected` reply (bad coord binding /
|
|
92
|
+
* failed threshold verify) or an unreachable parent rejects the promise, so the forwarder stays
|
|
93
|
+
* `awaiting_parent` for a later retry.
|
|
94
|
+
*/
|
|
86
95
|
export interface ParentRegistrar {
|
|
87
96
|
/**
|
|
88
97
|
* Register the forwarder for `topicId` (served at tree tier `tier`, i.e. `d`) with the cohort at
|
|
89
|
-
* `parentCoord`; resolves on ack. `opTier` is the topic's *capacity* tier (T0–T3), threaded so
|
|
90
|
-
* transport can stamp a well-formed
|
|
91
|
-
*
|
|
98
|
+
* `parentCoord`; resolves on a `linked` ack. `opTier` is the topic's *capacity* tier (T0–T3), threaded so
|
|
99
|
+
* the transport can stamp a well-formed child-link frame; absent when the caller has no op-tier context
|
|
100
|
+
* (the cohort host always supplies it from the instantiating `RegisterV1`).
|
|
92
101
|
*/
|
|
93
102
|
registerWithParent(topicId: Uint8Array, parentCoord: Uint8Array, tier: number, opTier?: number): Promise<void>;
|
|
94
103
|
}
|
|
@@ -111,6 +120,17 @@ export interface ColdStartManager {
|
|
|
111
120
|
instantiate(topicId: Uint8Array, tier: number, parentCoord?: Uint8Array, opTier?: number): Forwarder;
|
|
112
121
|
/** The tracked forwarder for `topicId`, or `undefined`. */
|
|
113
122
|
get(topicId: Uint8Array): Forwarder | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Drop the forwarder for `topicId` (budget eviction / teardown). Idempotent; a no-op if the topic is
|
|
125
|
+
* not tracked. After this, {@link get} returns `undefined` for `topicId`, so the cohort no longer
|
|
126
|
+
* reports serving it via cold-start state.
|
|
127
|
+
*/
|
|
128
|
+
remove(topicId: Uint8Array): void;
|
|
129
|
+
/**
|
|
130
|
+
* Whether this manager currently tracks any forwarder. The host's idle-engine eviction reads this to
|
|
131
|
+
* avoid reclaiming a cohort that holds a live (possibly `awaiting_parent`) cold-start forwarder.
|
|
132
|
+
*/
|
|
133
|
+
hasForwarders(): boolean;
|
|
114
134
|
}
|
|
115
135
|
/** Build a {@link ColdStartManager} over the injected parent registrar. */
|
|
116
136
|
export declare function createColdStartManager(deps: ColdStartManagerDeps): ColdStartManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coldstart.d.ts","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"coldstart.d.ts","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKvE,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAChC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAEpE;AAED,2DAA2D;AAC3D,MAAM,MAAM,cAAc;AACzB,mGAAmG;AACjG,iBAAiB;AACnB,8EAA8E;GAC5E,SAAS,CAAC;AAEb;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,KAAK,IAAI,cAAc,CAAC;IACxB,4FAA4F;IAC5F,mBAAmB,IAAI,OAAO,CAAC;IAC/B,kGAAkG;IAClG,eAAe,IAAI,OAAO,CAAC;IAC3B,uGAAuG;IACvG,WAAW,IAAI,IAAI,CAAC;CACpB;AA2BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAKvD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,eAAe,CAMnG;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/G;AAED,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,eAAe,EAAE,eAAe,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrG,2DAA2D;IAC3D,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAChD;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC;CACzB;AA+CD,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAEnF"}
|
|
@@ -11,18 +11,22 @@
|
|
|
11
11
|
* cold-start case) **or** it arrived as a follow-on to a parent cohort's `Promoted` redirect, AND
|
|
12
12
|
* - a quorum of cohort members is willing to serve `T` at the registration's tier.
|
|
13
13
|
*
|
|
14
|
-
* The `followOn` signal is
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* The `followOn` signal **is** carried on the wire (`RegisterV1.followOn`), set only on the dedicated
|
|
15
|
+
* re-issue a participant sends after a parent cohort's `Promoted` redirect target answered `NoState`
|
|
16
|
+
* (the deeper child is cold). A cold high-tier cohort still returns `NoState` to a *speculative* `d_max`
|
|
17
|
+
* probe (neither flag set), but the single-direction walk means the only registrations that reach a cold
|
|
18
|
+
* tier-`(d+1)` cohort carrying `followOn: true` are those that followed the parent's `Promoted` redirect.
|
|
19
|
+
* The db-p2p cohort host derives `ctx.followOn` from the wire flag (`followOn: reg.followOn === true`) and
|
|
20
|
+
* db-core takes it as an explicit input to {@link shouldInstantiate}, keeping this module FRET-free. Because
|
|
21
|
+
* the flag is participant-forgeable, a `followOn: true` register is gated by the **same** tier-dependent
|
|
22
|
+
* bootstrap-evidence policy a `bootstrap: true` cold-root register passes (§Anti-DoS), so an unbacked
|
|
23
|
+
* follow-on never reaches this instantiation decision.
|
|
21
24
|
*
|
|
22
25
|
* Once instantiated, the new forwarder **registers itself with its tier-`(d − 1)` parent at first
|
|
23
|
-
* opportunity
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* opportunity** by sending a child-link the parent authenticates + records; until that link is acked
|
|
27
|
+
* (`linked`) it {@link Forwarder.acceptsParticipants | accepts participants} but
|
|
28
|
+
* {@link Forwarder.servesParentOps | holds} notifications/queries that need parent involvement. The root
|
|
29
|
+
* (tree tier 0) has no parent and is serving immediately.
|
|
26
30
|
*
|
|
27
31
|
* **Just-promoted burst (GROUNDING-resolved: bounce, don't buffer).** A cohort that has just promoted
|
|
28
32
|
* but whose tier-`(d+1)` isn't fully instantiated yet, on a burst of new same-tier registrations,
|
|
@@ -32,6 +36,8 @@
|
|
|
32
36
|
*/
|
|
33
37
|
import { attachTopicTraffic } from "./traffic.js";
|
|
34
38
|
import { bytesKey } from "./registration/bytes.js";
|
|
39
|
+
import { createLogger } from "../logger.js";
|
|
40
|
+
const log = createLogger('cohort-topic:coldstart');
|
|
35
41
|
/**
|
|
36
42
|
* Whether a cold cohort should instantiate forwarder state for an inbound register: it must be a
|
|
37
43
|
* legitimate growth point (`bootstrap` root case **or** a `Promoted` follow-on) **and** have a willing
|
|
@@ -111,7 +117,7 @@ class TrackingColdStartManager {
|
|
|
111
117
|
.registerWithParent(topicId, parentCoord, tier, opTier)
|
|
112
118
|
.then(() => forwarder.onParentAck())
|
|
113
119
|
.catch((err) => {
|
|
114
|
-
|
|
120
|
+
log('WARN: parent registration for tier-%s forwarder failed %o', tier, err);
|
|
115
121
|
});
|
|
116
122
|
}
|
|
117
123
|
return forwarder;
|
|
@@ -119,6 +125,14 @@ class TrackingColdStartManager {
|
|
|
119
125
|
get(topicId) {
|
|
120
126
|
return this.forwarders.get(bytesKey(topicId));
|
|
121
127
|
}
|
|
128
|
+
remove(topicId) {
|
|
129
|
+
// Idempotent: `Map.delete` on an absent key is a safe no-op, so a double-remove (or a remove of a
|
|
130
|
+
// never-instantiated topic) does nothing.
|
|
131
|
+
this.forwarders.delete(bytesKey(topicId));
|
|
132
|
+
}
|
|
133
|
+
hasForwarders() {
|
|
134
|
+
return this.forwarders.size > 0;
|
|
135
|
+
}
|
|
122
136
|
}
|
|
123
137
|
/** Build a {@link ColdStartManager} over the injected parent registrar. */
|
|
124
138
|
export function createColdStartManager(deps) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coldstart.js","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"coldstart.js","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import type { RegistrationStore } from "../registration/types.js";
|
|
26
26
|
import type { ICohortGossipTransport, RingCoord } from "../ports.js";
|
|
27
|
-
import type { CohortGossipV1 } from "../wire/types.js";
|
|
27
|
+
import type { ChildLinkRefV1, CohortGossipV1 } from "../wire/types.js";
|
|
28
28
|
import { type CohortView } from "./view.js";
|
|
29
29
|
/** Handler for inbound gossip (after merge). */
|
|
30
30
|
export type GossipHandler = (g: CohortGossipV1) => void;
|
|
@@ -64,6 +64,17 @@ export interface CohortGossipBusDeps {
|
|
|
64
64
|
* callback so this gossip/replication module carries no anti-DoS dependency. Absent → skipped.
|
|
65
65
|
*/
|
|
66
66
|
onRecordsEvicted?: (topicIds: readonly Uint8Array[]) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Optional hook fired for the child-cohort link/unlink deltas on an inbound frame (db-p2p merges them into
|
|
69
|
+
* this coord's child registry, last-writer-wins by `effectiveAt`), so every parent member converges on the
|
|
70
|
+
* same child set — not only the FRET-routed member that recorded the child. Fired for **our-coord** frames
|
|
71
|
+
* regardless of epoch match: the child set is keyed by child coord, not the parent's epoch, so a parent
|
|
72
|
+
* membership rotation (an epoch drift) must not skip the merge (unlike record deltas, whose slot
|
|
73
|
+
* assignments are epoch-scoped). The merge is a direct registry write — it does **not** re-enqueue the
|
|
74
|
+
* delta, so a received link/unlink is not re-gossiped (one broadcast reaches the whole cohort). Kept as a
|
|
75
|
+
* decoupled callback so this module carries no child-registry dependency. Absent → skipped.
|
|
76
|
+
*/
|
|
77
|
+
onChildDeltas?: (childLinks: readonly ChildLinkRefV1[], childUnlinks: readonly ChildLinkRefV1[]) => void;
|
|
67
78
|
}
|
|
68
79
|
/** Intra-cohort gossip bus (merge logic over an injected transport). */
|
|
69
80
|
export interface CohortGossipBus {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/gossip/bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/gossip/bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,KAAK,EAAE,sBAAsB,EAAW,SAAS,EAAE,MAAM,aAAa,CAAC;AAE9E,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAoB,KAAK,UAAU,EAA0B,MAAM,WAAW,CAAC;AAEtF,gDAAgD;AAChD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;AACxD,sEAAsE;AACtE,MAAM,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AACpG,qGAAqG;AACrG,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IACnC,mEAAmE;IACnE,SAAS,EAAE,sBAAsB,CAAC;IAClC,yDAAyD;IACzD,KAAK,EAAE,iBAAiB,CAAC;IACzB,kHAAkH;IAClH,KAAK,EAAE,SAAS,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,UAAU,CAAC;IAC7B,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7D;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,EAAE,YAAY,EAAE,SAAS,cAAc,EAAE,KAAK,IAAI,CAAC;CACzG;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC/B,mCAAmC;IACnC,SAAS,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,IAAI,CAAC;IAC7C,0EAA0E;IAC1E,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;IAC3C,sFAAsF;IACtF,YAAY,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,mEAAmE;IACnE,IAAI,IAAI,UAAU,CAAC;IACnB,yCAAyC;IACzC,KAAK,IAAI,IAAI,CAAC;CACd;AAsJD,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,eAAe,CAEhF"}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* members — one {@link CohortGossipBus.applyInbound} of that gossip makes it visible locally.
|
|
24
24
|
*/
|
|
25
25
|
import { bytesEqual, bytesKey } from "../registration/bytes.js";
|
|
26
|
+
import { clampTtl } from "../registration/types.js";
|
|
26
27
|
import { b64urlToBytes, decodeCohortGossipV1, encodeCohortMessage } from "../wire/codec.js";
|
|
27
28
|
import { fromGossipRecord } from "./records.js";
|
|
28
29
|
import { createCohortView } from "./view.js";
|
|
@@ -69,6 +70,14 @@ class TransportCohortGossipBus {
|
|
|
69
70
|
if (epochMatches) {
|
|
70
71
|
this.mergeRecords(g, now);
|
|
71
72
|
}
|
|
73
|
+
// Child link/unlink deltas merge regardless of epoch match: the child set is keyed by child coord, not
|
|
74
|
+
// the parent's epoch, so a rotation (epoch drift) must not drop it. Freshness (last-writer-wins by
|
|
75
|
+
// effectiveAt) lives in the registry the callback writes.
|
|
76
|
+
const childLinks = g.childLinks ?? [];
|
|
77
|
+
const childUnlinks = g.childUnlinks ?? [];
|
|
78
|
+
if (this.deps.onChildDeltas !== undefined && (childLinks.length > 0 || childUnlinks.length > 0)) {
|
|
79
|
+
this.deps.onChildDeltas(childLinks, childUnlinks);
|
|
80
|
+
}
|
|
72
81
|
}
|
|
73
82
|
view() {
|
|
74
83
|
return this.memberView;
|
|
@@ -109,6 +118,11 @@ class TransportCohortGossipBus {
|
|
|
109
118
|
mergeRecords(g, now) {
|
|
110
119
|
for (const gr of g.records ?? []) {
|
|
111
120
|
const incoming = fromGossipRecord(gr);
|
|
121
|
+
// Gossip is a second admission path into the store — a peer (buggy, unpatched, or hostile)
|
|
122
|
+
// can replicate a record whose TTL never passed local `accept()`'s clamp. Re-clamp here so
|
|
123
|
+
// no replica can hold an unbounded lifetime that wedges the topic budget. Clamp BEFORE the
|
|
124
|
+
// dead-check below so a poison TTL cannot dodge staleness eviction.
|
|
125
|
+
incoming.ttl = clampTtl(incoming.ttl);
|
|
112
126
|
// A record already past its TTL is dead; merging it would resurrect a registration the
|
|
113
127
|
// owner has (or soon will have) evicted. Drop it, matching `store.evictStale`'s predicate
|
|
114
128
|
// so replication can never reintroduce what local eviction removes.
|
|
@@ -129,7 +143,19 @@ class TransportCohortGossipBus {
|
|
|
129
143
|
let evictedTopics;
|
|
130
144
|
for (const ref of g.evicted ?? []) {
|
|
131
145
|
const topicId = b64urlToBytes(ref.topicId);
|
|
132
|
-
|
|
146
|
+
const participantId = b64urlToBytes(ref.participantId);
|
|
147
|
+
// NOTE: freshness guard adds one getByParticipant per eviction ref (on top of the delete's own
|
|
148
|
+
// lookup); evictions are low-volume, but if this path ever shows as hot, fold the guard into a
|
|
149
|
+
// single conditional-delete store op.
|
|
150
|
+
const held = this.deps.store.getByParticipant(topicId, participantId);
|
|
151
|
+
// A stale eviction (older than the held record) must not delete a fresher re-registration.
|
|
152
|
+
// Evictions are otherwise NOT last-writer-wins like merges are, so a reordered/slow delta would
|
|
153
|
+
// win against a record the participant already renewed. Skip it — and, since nothing drained,
|
|
154
|
+
// do NOT add the topic to `evictedTopics` (no delete → no budget re-touch).
|
|
155
|
+
if (held !== undefined && held.lastPing > ref.lastPing) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
this.deps.store.delete(topicId, participantId);
|
|
133
159
|
if (this.deps.onRecordsEvicted !== undefined) {
|
|
134
160
|
(evictedTopics ??= new Map()).set(bytesKey(topicId), topicId);
|
|
135
161
|
}
|