@optimystic/db-p2p 0.22.0 → 0.24.1
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/client.d.ts +10 -0
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +30 -1
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +39 -14
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +226 -119
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/service.d.ts +32 -1
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +43 -2
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cohort-topic/host.js +34 -11
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +37 -7
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +77 -19
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +9 -3
- package/dist/src/dispute/dispute-service.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/libp2p-key-network.d.ts +88 -2
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +134 -28
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +25 -1
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/logger.d.ts +17 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +19 -2
- package/dist/src/logger.js.map +1 -1
- package/dist/src/matchmaking/query-transport.js +3 -3
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/owned-block-seed.d.ts +6 -3
- package/dist/src/owned-block-seed.d.ts.map +1 -1
- package/dist/src/owned-block-seed.js +16 -3
- package/dist/src/owned-block-seed.js.map +1 -1
- package/dist/src/peer-address-book.d.ts +72 -0
- package/dist/src/peer-address-book.d.ts.map +1 -0
- package/dist/src/peer-address-book.js +123 -0
- package/dist/src/peer-address-book.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts +4 -4
- package/dist/src/reactivity/notify-transport.js +6 -6
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -2
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/reactivity/recover-transport.d.ts +6 -2
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
- package/dist/src/reactivity/recover-transport.js +7 -3
- package/dist/src/reactivity/recover-transport.js.map +1 -1
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +11 -2
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +30 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +95 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +62 -9
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +242 -73
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/rn.d.ts +3 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +3 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +83 -0
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
- package/dist/src/storage/cached-raw-storage.js +152 -0
- package/dist/src/storage/cached-raw-storage.js.map +1 -0
- package/dist/src/storage/cached-store-driver.d.ts +186 -0
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
- package/dist/src/storage/cached-store-driver.js +775 -0
- package/dist/src/storage/cached-store-driver.js.map +1 -0
- package/dist/src/storage/i-raw-storage.d.ts +12 -5
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +234 -0
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
- package/dist/src/storage/shared-cache-pool.js +354 -0
- package/dist/src/storage/shared-cache-pool.js.map +1 -0
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +35 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +668 -668
- package/src/cluster/block-transfer.ts +424 -424
- package/src/cluster/client.ts +119 -88
- package/src/cluster/cluster-error.ts +64 -64
- package/src/cluster/cluster-policy.ts +203 -203
- package/src/cluster/cluster-repo.ts +242 -122
- package/src/cluster/cluster-size-coupling.ts +45 -45
- package/src/cluster/commit-cert.ts +139 -139
- package/src/cluster/i-transaction-state-store.ts +43 -43
- package/src/cluster/memory-transaction-state-store.ts +56 -56
- package/src/cluster/peer-key-binding.ts +37 -37
- package/src/cluster/persistent-transaction-state-store.ts +92 -92
- package/src/cluster/quorum-restore.ts +223 -223
- package/src/cluster/reconcile-block.ts +203 -203
- package/src/cluster/service.ts +293 -241
- package/src/cluster/supermajority-coupling.ts +37 -37
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
- package/src/cohort-topic/change-bridge.ts +109 -109
- package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
- package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
- package/src/cohort-topic/fret-trust-anchor.ts +153 -153
- package/src/cohort-topic/host.ts +42 -11
- package/src/cohort-topic/index.ts +13 -13
- package/src/cohort-topic/membership-publish-sink.ts +20 -20
- package/src/cohort-topic/membership-source.ts +68 -68
- package/src/cohort-topic/peer-codec.ts +31 -31
- package/src/cohort-topic/peer-sig.ts +86 -86
- package/src/cohort-topic/protocols.ts +71 -71
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
- package/src/cohort-topic/size-estimator.ts +16 -16
- package/src/cohort-topic/stream-util.ts +79 -19
- package/src/cohort-topic/threshold-crypto.ts +239 -239
- package/src/cohort-topic/topic-router.ts +77 -77
- package/src/dispute/arbitrator-selection.ts +138 -138
- package/src/dispute/cascade.ts +524 -524
- package/src/dispute/dispute-service.ts +11 -5
- package/src/dispute/invalidation.ts +625 -625
- package/src/inbound-authorization.ts +190 -190
- package/src/index.ts +52 -49
- package/src/libp2p-key-network.ts +1120 -990
- package/src/libp2p-node-base.ts +1675 -1651
- package/src/libp2p-node-rn.ts +30 -30
- package/src/libp2p-node.ts +36 -36
- package/src/logger.ts +19 -2
- package/src/matchmaking/aggregate-counts.ts +104 -104
- package/src/matchmaking/index.ts +20 -20
- package/src/matchmaking/module.ts +363 -363
- package/src/matchmaking/protocols.ts +51 -51
- package/src/matchmaking/provider-manager.ts +95 -95
- package/src/matchmaking/query-handler.ts +88 -88
- package/src/matchmaking/query-transport.ts +3 -3
- package/src/matchmaking/seeker-manager.ts +64 -64
- package/src/matchmaking/seeker-walk-client.ts +293 -293
- package/src/matchmaking/traffic-validation.ts +195 -195
- package/src/optimystic-node.ts +36 -36
- package/src/owned-block-seed.ts +53 -40
- package/src/peer-address-book.ts +149 -0
- package/src/protocol-limits.ts +33 -33
- package/src/reactivity/forwarder-host.ts +438 -438
- package/src/reactivity/index.ts +19 -19
- package/src/reactivity/notify-transport.ts +144 -144
- package/src/reactivity/origination-manager.ts +192 -192
- package/src/reactivity/protocols.ts +61 -61
- package/src/reactivity/push-state-gossip.ts +291 -291
- package/src/reactivity/recover-transport.ts +7 -3
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
- package/src/reactivity/subscriber-registry.ts +96 -96
- package/src/reactivity/subscription-manager.ts +450 -450
- package/src/reactivity/topic-bytes.ts +37 -37
- package/src/repo/client.ts +12 -2
- package/src/repo/cluster-coordinator.ts +99 -3
- package/src/repo/coordinator-repo.ts +281 -74
- package/src/repo/types.ts +7 -7
- package/src/rn.ts +39 -36
- package/src/rpc-deadline.ts +45 -45
- package/src/storage/arachnode-partition.ts +74 -74
- package/src/storage/cached-raw-storage.ts +180 -0
- package/src/storage/cached-store-driver.ts +859 -0
- package/src/storage/i-kv-store.ts +8 -8
- package/src/storage/i-raw-storage.ts +12 -5
- package/src/storage/kv-raw-storage.ts +135 -135
- package/src/storage/memory-kv-store.ts +28 -28
- package/src/storage/memory-storage.ts +25 -25
- package/src/storage/memory-store-driver.ts +157 -157
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +80 -80
- package/src/storage/ring-selector.ts +317 -317
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/shared-cache-pool.ts +452 -0
- package/src/storage/storage-repo.ts +1014 -1014
- package/src/testing/cohort-topic-mesh-harness.ts +673 -663
- package/src/testing/index.ts +8 -8
- package/src/testing/matchmaking-mesh-harness.ts +475 -475
- package/src/testing/raw-storage-conformance.ts +453 -417
- package/src/testing/reactivity-mesh-harness.ts +922 -922
- package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
- package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
- package/dist/src/storage/restoration-coordinator-v2.js +0 -172
- package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
package/src/rn.ts
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
export * from './cluster/client.js';
|
|
2
|
-
export * from './cluster/cluster-policy.js';
|
|
3
|
-
export * from './cluster/cluster-repo.js';
|
|
4
|
-
export * from './cluster/service.js';
|
|
5
|
-
export * from './protocol-client.js';
|
|
6
|
-
export * from './repo/client.js';
|
|
7
|
-
export * from './repo/cluster-coordinator.js';
|
|
8
|
-
export * from './repo/coordinator-repo.js';
|
|
9
|
-
export * from './repo/service.js';
|
|
10
|
-
export * from './storage/block-storage.js';
|
|
11
|
-
export * from './storage/raw-store-driver.js';
|
|
12
|
-
export * from './storage/kv-raw-storage.js';
|
|
13
|
-
export * from './storage/
|
|
14
|
-
export * from './storage/
|
|
15
|
-
export * from './storage/
|
|
16
|
-
export * from './storage/
|
|
17
|
-
export * from './storage/
|
|
18
|
-
export * from './storage/storage
|
|
19
|
-
export * from './storage/
|
|
20
|
-
export * from './storage/
|
|
21
|
-
export * from './storage/storage-
|
|
22
|
-
export * from './storage/
|
|
23
|
-
export * from './
|
|
24
|
-
export * from './
|
|
25
|
-
export * from './
|
|
26
|
-
export * from './
|
|
27
|
-
export * from './
|
|
28
|
-
export * from './
|
|
29
|
-
export * from './
|
|
30
|
-
export * from './
|
|
31
|
-
export * from './
|
|
32
|
-
export * from './
|
|
33
|
-
export * from './
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export * from './
|
|
1
|
+
export * from './cluster/client.js';
|
|
2
|
+
export * from './cluster/cluster-policy.js';
|
|
3
|
+
export * from './cluster/cluster-repo.js';
|
|
4
|
+
export * from './cluster/service.js';
|
|
5
|
+
export * from './protocol-client.js';
|
|
6
|
+
export * from './repo/client.js';
|
|
7
|
+
export * from './repo/cluster-coordinator.js';
|
|
8
|
+
export * from './repo/coordinator-repo.js';
|
|
9
|
+
export * from './repo/service.js';
|
|
10
|
+
export * from './storage/block-storage.js';
|
|
11
|
+
export * from './storage/raw-store-driver.js';
|
|
12
|
+
export * from './storage/kv-raw-storage.js';
|
|
13
|
+
export * from './storage/shared-cache-pool.js';
|
|
14
|
+
export * from './storage/cached-store-driver.js';
|
|
15
|
+
export * from './storage/cached-raw-storage.js';
|
|
16
|
+
export * from './storage/memory-store-driver.js';
|
|
17
|
+
export * from './storage/memory-storage.js';
|
|
18
|
+
export * from './storage/i-block-storage.js';
|
|
19
|
+
export * from './storage/i-raw-storage.js';
|
|
20
|
+
export * from './storage/struct.js';
|
|
21
|
+
export * from './storage/storage-repo.js';
|
|
22
|
+
export * from './storage/restoration-coordinator.js';
|
|
23
|
+
export * from './storage/ring-selector.js';
|
|
24
|
+
export * from './storage/storage-monitor.js';
|
|
25
|
+
export * from './storage/arachnode-fret-adapter.js';
|
|
26
|
+
export * from './sync/protocol.js';
|
|
27
|
+
export * from './sync/client.js';
|
|
28
|
+
export * from './sync/service.js';
|
|
29
|
+
export * from './it-utility.js';
|
|
30
|
+
export * from './libp2p-key-network.js';
|
|
31
|
+
export * from './libp2p-node-rn.js';
|
|
32
|
+
export * from './optimystic-node.js';
|
|
33
|
+
export * from './routing/responsibility.js';
|
|
34
|
+
export * from './routing/libp2p-known-peers.js';
|
|
35
|
+
export * from './network/network-manager-service.js';
|
|
36
|
+
export * from './network/get-network-manager.js';
|
|
37
|
+
// Browser-safe peer signing seam. The rest of ./cohort-topic pulls node-heavy host.js,
|
|
38
|
+
// so only peer-sig (@noble/curves + peer-id) is surfaced through the RN/browser entry.
|
|
39
|
+
export * from './cohort-topic/peer-sig.js';
|
package/src/rpc-deadline.ts
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-RPC deadline knobs shared by the simple {@link ProtocolClient} subclasses
|
|
3
|
-
* (cluster / sync / dispute). `dialTimeoutMs` bounds connecting; `responseTimeoutMs`
|
|
4
|
-
* bounds waiting for the reply once connected (so a peer that connects then goes
|
|
5
|
-
* silent throws {@link ResponseTimeoutError} instead of hanging the caller forever);
|
|
6
|
-
* `signal` cancels the whole request. All optional.
|
|
7
|
-
*/
|
|
8
|
-
export type RpcDeadlineOptions = {
|
|
9
|
-
signal?: AbortSignal;
|
|
10
|
-
dialTimeoutMs?: number;
|
|
11
|
-
responseTimeoutMs?: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Default per-peer dial deadline. Matches `spread-on-churn.ts` `pushDialTimeoutMs`
|
|
16
|
-
* (3000ms) — an unreachable peer fails the dial fast so the caller can re-pick a
|
|
17
|
-
* different coordinator rather than blocking on a dead route.
|
|
18
|
-
*/
|
|
19
|
-
export const DEFAULT_DIAL_TIMEOUT_MS = 3000;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Default per-peer response deadline. Matches `spread-on-churn.ts`
|
|
23
|
-
* `pushResponseTimeoutMs` (10000ms) — a peer that connects then never writes a
|
|
24
|
-
* reply is abandoned instead of hanging the caller forever.
|
|
25
|
-
*/
|
|
26
|
-
export const DEFAULT_RESPONSE_TIMEOUT_MS = 10000;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Merge caller-supplied deadline options with the client-level defaults. An
|
|
30
|
-
* explicitly-supplied value wins (including a deliberate `0`, which
|
|
31
|
-
* {@link ProtocolClient.processMessage} reads as "no cap"); an absent key falls
|
|
32
|
-
* back to the default so a caller that passes nothing still gets a deadline.
|
|
33
|
-
* `signal` has no default — cancellation is always caller-driven.
|
|
34
|
-
*
|
|
35
|
-
* Unlike `BlockTransferClient` (which leaves its defaults to the owning
|
|
36
|
-
* `SpreadOnChurnMonitor` config), these clients have many callers and no single
|
|
37
|
-
* owning monitor, so the deadline default belongs here on the client.
|
|
38
|
-
*/
|
|
39
|
-
export function withRpcDeadlineDefaults(options?: RpcDeadlineOptions): RpcDeadlineOptions {
|
|
40
|
-
return {
|
|
41
|
-
dialTimeoutMs: options?.dialTimeoutMs ?? DEFAULT_DIAL_TIMEOUT_MS,
|
|
42
|
-
responseTimeoutMs: options?.responseTimeoutMs ?? DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
43
|
-
signal: options?.signal,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Per-RPC deadline knobs shared by the simple {@link ProtocolClient} subclasses
|
|
3
|
+
* (cluster / sync / dispute). `dialTimeoutMs` bounds connecting; `responseTimeoutMs`
|
|
4
|
+
* bounds waiting for the reply once connected (so a peer that connects then goes
|
|
5
|
+
* silent throws {@link ResponseTimeoutError} instead of hanging the caller forever);
|
|
6
|
+
* `signal` cancels the whole request. All optional.
|
|
7
|
+
*/
|
|
8
|
+
export type RpcDeadlineOptions = {
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
dialTimeoutMs?: number;
|
|
11
|
+
responseTimeoutMs?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default per-peer dial deadline. Matches `spread-on-churn.ts` `pushDialTimeoutMs`
|
|
16
|
+
* (3000ms) — an unreachable peer fails the dial fast so the caller can re-pick a
|
|
17
|
+
* different coordinator rather than blocking on a dead route.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_DIAL_TIMEOUT_MS = 3000;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default per-peer response deadline. Matches `spread-on-churn.ts`
|
|
23
|
+
* `pushResponseTimeoutMs` (10000ms) — a peer that connects then never writes a
|
|
24
|
+
* reply is abandoned instead of hanging the caller forever.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_RESPONSE_TIMEOUT_MS = 10000;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Merge caller-supplied deadline options with the client-level defaults. An
|
|
30
|
+
* explicitly-supplied value wins (including a deliberate `0`, which
|
|
31
|
+
* {@link ProtocolClient.processMessage} reads as "no cap"); an absent key falls
|
|
32
|
+
* back to the default so a caller that passes nothing still gets a deadline.
|
|
33
|
+
* `signal` has no default — cancellation is always caller-driven.
|
|
34
|
+
*
|
|
35
|
+
* Unlike `BlockTransferClient` (which leaves its defaults to the owning
|
|
36
|
+
* `SpreadOnChurnMonitor` config), these clients have many callers and no single
|
|
37
|
+
* owning monitor, so the deadline default belongs here on the client.
|
|
38
|
+
*/
|
|
39
|
+
export function withRpcDeadlineDefaults(options?: RpcDeadlineOptions): RpcDeadlineOptions {
|
|
40
|
+
return {
|
|
41
|
+
dialTimeoutMs: options?.dialTimeoutMs ?? DEFAULT_DIAL_TIMEOUT_MS,
|
|
42
|
+
responseTimeoutMs: options?.responseTimeoutMs ?? DEFAULT_RESPONSE_TIMEOUT_MS,
|
|
43
|
+
signal: options?.signal,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import type { ArachnodeInfo } from './arachnode-fret-adapter.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* An Arachnode ring partition: the first `prefixBits` bits of a hashed coordinate must equal
|
|
5
|
-
* `prefixValue` for a key to fall inside it. Structurally identical to `ArachnodeInfo.partition`.
|
|
6
|
-
*/
|
|
7
|
-
export interface RingPartition {
|
|
8
|
-
prefixBits: number;
|
|
9
|
-
prefixValue: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Extract the first `bits` bits of a hashed coordinate as an integer (MSB-first).
|
|
14
|
-
*
|
|
15
|
-
* The single source of truth for the block/peer prefix comparison Arachnode uses to decide
|
|
16
|
-
* responsibility. `RingSelector.calculatePartition` (peer side) and `RestorationCoordinator`
|
|
17
|
-
* (block side) both route through this, so a peer's advertised partition and a block's derived
|
|
18
|
-
* prefix are computed the same way — otherwise "this peer owns this block's slice" would silently
|
|
19
|
-
* stop meaning what it says.
|
|
20
|
-
*/
|
|
21
|
-
export function extractPrefix(coord: Uint8Array, bits: number): number {
|
|
22
|
-
let value = 0;
|
|
23
|
-
for (let i = 0; i < bits; i++) {
|
|
24
|
-
const byteIndex = Math.floor(i / 8);
|
|
25
|
-
const bitIndex = 7 - (i % 8);
|
|
26
|
-
const bit = (coord[byteIndex]! >> bitIndex) & 1;
|
|
27
|
-
value = (value << 1) | bit;
|
|
28
|
-
}
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Does `partition` cover the key at `coord`? Ring 0 (an undefined partition) covers the whole
|
|
34
|
-
* keyspace, so it always returns true.
|
|
35
|
-
*/
|
|
36
|
-
export function partitionCovers(partition: RingPartition | undefined, coord: Uint8Array): boolean {
|
|
37
|
-
if (!partition) {
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
return extractPrefix(coord, partition.prefixBits) === partition.prefixValue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Is the node described by `info` a currently-serving responsible holder for the key at `coord`?
|
|
45
|
-
*
|
|
46
|
-
* **Fail-toward-old-holder.** A node mid-move (`status === 'moving'`) advertises its *target*
|
|
47
|
-
* `partition`, but it keeps serving its *old* range (`moveFrom`) until it releases. So a moving
|
|
48
|
-
* node covers the key if EITHER its old range or its target range covers it — it is still counted
|
|
49
|
-
* as a serving holder for everything it served before the move until it transitions to `active` at
|
|
50
|
-
* the new ring. This is what keeps a shed key covered through Phases A–B and across a mid-handoff
|
|
51
|
-
* crash. See `docs/arachnode-ring-handoff.md` § Part 3.
|
|
52
|
-
*/
|
|
53
|
-
export function isServingHolder(info: ArachnodeInfo, coord: Uint8Array): boolean {
|
|
54
|
-
if (info.status === 'moving' && info.moveFrom) {
|
|
55
|
-
return partitionCovers(info.moveFrom.partition, coord) || partitionCovers(info.partition, coord);
|
|
56
|
-
}
|
|
57
|
-
return partitionCovers(info.partition, coord);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Does the node described by `info` qualify toward *another* mover's Phase-B replication floor for
|
|
62
|
-
* the key at `coord`?
|
|
63
|
-
*
|
|
64
|
-
* A qualifying holder must still cover the key **after its own advertised move** — i.e. its
|
|
65
|
-
* *target* `partition` (the one it currently advertises) must cover it. A concurrent mover that is
|
|
66
|
-
* shedding the same sub-range advertises a target that does NOT cover the key, so it is excluded:
|
|
67
|
-
* two adjacent movers can never both count the other, so at most one reaches "confirmed" on the
|
|
68
|
-
* shared overlap and the other rolls back. For a non-moving (`active`) node, the advertised
|
|
69
|
-
* partition IS its serving range, so this reduces to plain partition coverage. See
|
|
70
|
-
* `docs/arachnode-ring-handoff.md` § Part 3 (concurrent moves).
|
|
71
|
-
*/
|
|
72
|
-
export function qualifiesForFloor(info: ArachnodeInfo, coord: Uint8Array): boolean {
|
|
73
|
-
return partitionCovers(info.partition, coord);
|
|
74
|
-
}
|
|
1
|
+
import type { ArachnodeInfo } from './arachnode-fret-adapter.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An Arachnode ring partition: the first `prefixBits` bits of a hashed coordinate must equal
|
|
5
|
+
* `prefixValue` for a key to fall inside it. Structurally identical to `ArachnodeInfo.partition`.
|
|
6
|
+
*/
|
|
7
|
+
export interface RingPartition {
|
|
8
|
+
prefixBits: number;
|
|
9
|
+
prefixValue: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract the first `bits` bits of a hashed coordinate as an integer (MSB-first).
|
|
14
|
+
*
|
|
15
|
+
* The single source of truth for the block/peer prefix comparison Arachnode uses to decide
|
|
16
|
+
* responsibility. `RingSelector.calculatePartition` (peer side) and `RestorationCoordinator`
|
|
17
|
+
* (block side) both route through this, so a peer's advertised partition and a block's derived
|
|
18
|
+
* prefix are computed the same way — otherwise "this peer owns this block's slice" would silently
|
|
19
|
+
* stop meaning what it says.
|
|
20
|
+
*/
|
|
21
|
+
export function extractPrefix(coord: Uint8Array, bits: number): number {
|
|
22
|
+
let value = 0;
|
|
23
|
+
for (let i = 0; i < bits; i++) {
|
|
24
|
+
const byteIndex = Math.floor(i / 8);
|
|
25
|
+
const bitIndex = 7 - (i % 8);
|
|
26
|
+
const bit = (coord[byteIndex]! >> bitIndex) & 1;
|
|
27
|
+
value = (value << 1) | bit;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Does `partition` cover the key at `coord`? Ring 0 (an undefined partition) covers the whole
|
|
34
|
+
* keyspace, so it always returns true.
|
|
35
|
+
*/
|
|
36
|
+
export function partitionCovers(partition: RingPartition | undefined, coord: Uint8Array): boolean {
|
|
37
|
+
if (!partition) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return extractPrefix(coord, partition.prefixBits) === partition.prefixValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Is the node described by `info` a currently-serving responsible holder for the key at `coord`?
|
|
45
|
+
*
|
|
46
|
+
* **Fail-toward-old-holder.** A node mid-move (`status === 'moving'`) advertises its *target*
|
|
47
|
+
* `partition`, but it keeps serving its *old* range (`moveFrom`) until it releases. So a moving
|
|
48
|
+
* node covers the key if EITHER its old range or its target range covers it — it is still counted
|
|
49
|
+
* as a serving holder for everything it served before the move until it transitions to `active` at
|
|
50
|
+
* the new ring. This is what keeps a shed key covered through Phases A–B and across a mid-handoff
|
|
51
|
+
* crash. See `docs/arachnode-ring-handoff.md` § Part 3.
|
|
52
|
+
*/
|
|
53
|
+
export function isServingHolder(info: ArachnodeInfo, coord: Uint8Array): boolean {
|
|
54
|
+
if (info.status === 'moving' && info.moveFrom) {
|
|
55
|
+
return partitionCovers(info.moveFrom.partition, coord) || partitionCovers(info.partition, coord);
|
|
56
|
+
}
|
|
57
|
+
return partitionCovers(info.partition, coord);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Does the node described by `info` qualify toward *another* mover's Phase-B replication floor for
|
|
62
|
+
* the key at `coord`?
|
|
63
|
+
*
|
|
64
|
+
* A qualifying holder must still cover the key **after its own advertised move** — i.e. its
|
|
65
|
+
* *target* `partition` (the one it currently advertises) must cover it. A concurrent mover that is
|
|
66
|
+
* shedding the same sub-range advertises a target that does NOT cover the key, so it is excluded:
|
|
67
|
+
* two adjacent movers can never both count the other, so at most one reaches "confirmed" on the
|
|
68
|
+
* shared overlap and the other rolls back. For a non-moving (`active`) node, the advertised
|
|
69
|
+
* partition IS its serving range, so this reduces to plain partition coverage. See
|
|
70
|
+
* `docs/arachnode-ring-handoff.md` § Part 3 (concurrent moves).
|
|
71
|
+
*/
|
|
72
|
+
export function qualifiesForFloor(info: ArachnodeInfo, coord: Uint8Array): boolean {
|
|
73
|
+
return partitionCovers(info.partition, coord);
|
|
74
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { BlockId, ActionId } from "@optimystic/db-core";
|
|
2
|
+
import type { IRawStorage } from "./i-raw-storage.js";
|
|
3
|
+
import type { RawStoreDriver } from "./raw-store-driver.js";
|
|
4
|
+
import { KvRawStorage } from "./kv-raw-storage.js";
|
|
5
|
+
import { CachedStoreDriver } from "./cached-store-driver.js";
|
|
6
|
+
import type { SharedCachePool } from "./shared-cache-pool.js";
|
|
7
|
+
import { encodeJson, decodeJson, encodeActionId, decodeActionId } from "./raw-store-codec.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Presents a plain {@link IRawStorage} as a {@link RawStoreDriver}, so the
|
|
11
|
+
* {@link CachedStoreDriver} (and the `KvRawStorage` kernel above it) can wrap a backend
|
|
12
|
+
* that does NOT expose its driver — the composition {@link CachedRawStorage} uses.
|
|
13
|
+
*
|
|
14
|
+
* Every value crossing this adapter is re-encoded to bytes on read and re-decoded on
|
|
15
|
+
* write, on top of whatever (de)serialization the inner storage does itself. For a
|
|
16
|
+
* kernel-backed inner storage that is a double codec pass per COLD miss — cache hits
|
|
17
|
+
* never reach this adapter. Wrapping the backend's `RawStoreDriver` directly with
|
|
18
|
+
* `CachedStoreDriver` avoids the double pass entirely; prefer that when the driver is
|
|
19
|
+
* reachable, and this adapter when only the `IRawStorage` surface is.
|
|
20
|
+
*/
|
|
21
|
+
export class RawStorageDriverAdapter implements RawStoreDriver {
|
|
22
|
+
/**
|
|
23
|
+
* Optional passthroughs wired only when the inner storage provides them, mirroring
|
|
24
|
+
* `KvRawStorage`'s constructor: feature-detection above must observe the inner
|
|
25
|
+
* storage's true capability. `IRawStorage` has no `close`/`approximateBytesUsed`
|
|
26
|
+
* driver names — they map from `listBlockIds`/`getApproximateBytesUsed`.
|
|
27
|
+
*/
|
|
28
|
+
listBlockIds?: () => AsyncIterable<BlockId>;
|
|
29
|
+
approximateBytesUsed?: () => Promise<number>;
|
|
30
|
+
|
|
31
|
+
constructor(private readonly inner: IRawStorage) {
|
|
32
|
+
if (inner.listBlockIds) {
|
|
33
|
+
this.listBlockIds = () => inner.listBlockIds!();
|
|
34
|
+
}
|
|
35
|
+
if (inner.getApproximateBytesUsed) {
|
|
36
|
+
this.approximateBytesUsed = () => inner.getApproximateBytesUsed!();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- metadata ---
|
|
41
|
+
|
|
42
|
+
async getMetadata(blockId: BlockId): Promise<Uint8Array | undefined> {
|
|
43
|
+
const meta = await this.inner.getMetadata(blockId);
|
|
44
|
+
return meta === undefined ? undefined : encodeJson(meta);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async putMetadata(blockId: BlockId, value: Uint8Array): Promise<void> {
|
|
48
|
+
await this.inner.saveMetadata(blockId, decodeJson(value));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- revisions ---
|
|
52
|
+
|
|
53
|
+
async getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined> {
|
|
54
|
+
const actionId = await this.inner.getRevision(blockId, rev);
|
|
55
|
+
return actionId === undefined ? undefined : encodeActionId(actionId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void> {
|
|
59
|
+
await this.inner.saveRevision(blockId, rev, decodeActionId(value));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async *rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]> {
|
|
63
|
+
// `listRevisions` derives direction from bound order (start > end ⇒ descending);
|
|
64
|
+
// map (lo, hi, reverse) back onto that. Drain before yielding — the inner
|
|
65
|
+
// iterable already drained per its own contract, but the consumer's awaits must
|
|
66
|
+
// not straddle OUR loop over it either.
|
|
67
|
+
const startRev = reverse ? hi : lo;
|
|
68
|
+
const endRev = reverse ? lo : hi;
|
|
69
|
+
const drained: [number, Uint8Array][] = [];
|
|
70
|
+
for await (const { rev, actionId } of this.inner.listRevisions(blockId, startRev, endRev)) {
|
|
71
|
+
drained.push([rev, encodeActionId(actionId)]);
|
|
72
|
+
}
|
|
73
|
+
yield* drained;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- pending ---
|
|
77
|
+
|
|
78
|
+
async getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
79
|
+
const transform = await this.inner.getPendingTransaction(blockId, actionId);
|
|
80
|
+
return transform === undefined ? undefined : encodeJson(transform);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
84
|
+
await this.inner.savePendingTransaction(blockId, actionId, decodeJson(value));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async deletePending(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
88
|
+
await this.inner.deletePendingTransaction(blockId, actionId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async *listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId> {
|
|
92
|
+
const drained: ActionId[] = [];
|
|
93
|
+
for await (const id of this.inner.listPendingTransactions(blockId)) {
|
|
94
|
+
drained.push(id);
|
|
95
|
+
}
|
|
96
|
+
yield* drained;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// --- transactions ---
|
|
100
|
+
|
|
101
|
+
async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
102
|
+
const transform = await this.inner.getTransaction(blockId, actionId);
|
|
103
|
+
return transform === undefined ? undefined : encodeJson(transform);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
107
|
+
await this.inner.saveTransaction(blockId, actionId, decodeJson(value));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- materialized ---
|
|
111
|
+
|
|
112
|
+
async getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
113
|
+
const block = await this.inner.getMaterializedBlock(blockId, actionId);
|
|
114
|
+
return block === undefined ? undefined : encodeJson(block);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
118
|
+
await this.inner.saveMaterializedBlock(blockId, actionId, decodeJson(value));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
122
|
+
await this.inner.saveMaterializedBlock(blockId, actionId, undefined);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --- promote ---
|
|
126
|
+
|
|
127
|
+
async promote(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
128
|
+
await this.inner.promotePendingTransaction(blockId, actionId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Write-through cached view over a plain {@link IRawStorage} — the wrapper form for a
|
|
134
|
+
* backend whose {@link RawStoreDriver} is not reachable. Composition:
|
|
135
|
+
*
|
|
136
|
+
* ```
|
|
137
|
+
* KvRawStorage kernel → CachedStoreDriver → RawStorageDriverAdapter → inner IRawStorage
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* All caching semantics (write-through coherence, proven-absence negatives, list
|
|
141
|
+
* completeness, promote mirroring) live in {@link CachedStoreDriver}; see its class doc
|
|
142
|
+
* and docs/storage.md "Write-through raw-storage cache" for the invariants this relies
|
|
143
|
+
* on — including the single-process-owner precondition.
|
|
144
|
+
*
|
|
145
|
+
* Prefer wrapping the backend's driver directly — `new KvRawStorage(new
|
|
146
|
+
* CachedStoreDriver(driver))` — when the driver is available: it skips this adapter's
|
|
147
|
+
* extra codec pass on cold misses. Do not wrap `MemoryRawStorage` in production wiring
|
|
148
|
+
* (already in-memory; the cache adds bookkeeping with nothing to save) — tests do, to
|
|
149
|
+
* prove semantics are identical through the full composition.
|
|
150
|
+
*
|
|
151
|
+
* By default the cache joins the process-wide shared pool (`defaultCachePool()`), so
|
|
152
|
+
* every workspace's cache competes inside ONE memory budget; pass a specific
|
|
153
|
+
* {@link SharedCachePool} only for isolation (tests) or host-specific sizing, and a
|
|
154
|
+
* `label` to make this store recognizable in the pool's `stats()`.
|
|
155
|
+
*/
|
|
156
|
+
export class CachedRawStorage extends KvRawStorage {
|
|
157
|
+
private readonly cacheDriver: CachedStoreDriver;
|
|
158
|
+
|
|
159
|
+
constructor(inner: IRawStorage, pool?: SharedCachePool, label?: string) {
|
|
160
|
+
const cacheDriver = new CachedStoreDriver(new RawStorageDriverAdapter(inner), pool, label);
|
|
161
|
+
super(cacheDriver);
|
|
162
|
+
this.cacheDriver = cacheDriver;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Drop every cached entry. Always safe — the cache is clean (write-through, never write-behind). */
|
|
166
|
+
clearCache(): void {
|
|
167
|
+
this.cacheDriver.clear();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Release this storage's cache registration with the shared pool (drops all entries,
|
|
172
|
+
* retires the store id). Call when the workspace departs; a skipped dispose leaks only
|
|
173
|
+
* cold entries the pool will evict under pressure, but the polite release keeps a
|
|
174
|
+
* long-lived process's occupancy honest. `IRawStorage` itself has no close, so this is
|
|
175
|
+
* the wrapper's own lifecycle method.
|
|
176
|
+
*/
|
|
177
|
+
async dispose(): Promise<void> {
|
|
178
|
+
await this.cacheDriver.close();
|
|
179
|
+
}
|
|
180
|
+
}
|