@optimystic/db-p2p 0.14.1 → 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/README.md +616 -609
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +4 -3
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +52 -3
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +129 -6
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +3 -1
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +182 -11
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +429 -69
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/peer-key-binding.d.ts +25 -0
- package/dist/src/cluster/peer-key-binding.d.ts.map +1 -0
- package/dist/src/cluster/peer-key-binding.js +39 -0
- package/dist/src/cluster/peer-key-binding.js.map +1 -0
- package/dist/src/cluster/quorum-restore.d.ts +72 -0
- package/dist/src/cluster/quorum-restore.d.ts.map +1 -0
- package/dist/src/cluster/quorum-restore.js +0 -0
- package/dist/src/cluster/quorum-restore.js.map +1 -0
- package/dist/src/cluster/rebalance-monitor.d.ts +23 -1
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +17 -2
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +7 -1
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts +7 -0
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +4 -1
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cluster/supermajority-coupling.d.ts +22 -0
- package/dist/src/cluster/supermajority-coupling.d.ts.map +1 -0
- package/dist/src/cluster/supermajority-coupling.js +27 -0
- package/dist/src/cluster/supermajority-coupling.js.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.d.ts +46 -4
- package/dist/src/cohort-topic/cohort-gossip-driver.d.ts.map +1 -1
- package/dist/src/cohort-topic/cohort-gossip-driver.js +62 -7
- package/dist/src/cohort-topic/cohort-gossip-driver.js.map +1 -1
- package/dist/src/cohort-topic/cohort-gossip-transport.d.ts +7 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.d.ts.map +1 -1
- package/dist/src/cohort-topic/cohort-gossip-transport.js +9 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.js.map +1 -1
- package/dist/src/cohort-topic/host.d.ts +197 -27
- package/dist/src/cohort-topic/host.d.ts.map +1 -1
- package/dist/src/cohort-topic/host.js +643 -87
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/dispute/arbitrator-selection.d.ts +68 -7
- package/dist/src/dispute/arbitrator-selection.d.ts.map +1 -1
- package/dist/src/dispute/arbitrator-selection.js +105 -16
- package/dist/src/dispute/arbitrator-selection.js.map +1 -1
- package/dist/src/dispute/cascade.d.ts +7 -0
- package/dist/src/dispute/cascade.d.ts.map +1 -1
- package/dist/src/dispute/cascade.js +1 -1
- package/dist/src/dispute/cascade.js.map +1 -1
- package/dist/src/dispute/client.d.ts.map +1 -1
- package/dist/src/dispute/client.js +3 -2
- package/dist/src/dispute/client.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +32 -3
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +82 -9
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/dispute/index.d.ts +1 -1
- package/dist/src/dispute/index.d.ts.map +1 -1
- package/dist/src/dispute/index.js +1 -1
- package/dist/src/dispute/index.js.map +1 -1
- package/dist/src/dispute/invalidation.d.ts +25 -0
- package/dist/src/dispute/invalidation.d.ts.map +1 -1
- package/dist/src/dispute/invalidation.js +14 -2
- package/dist/src/dispute/invalidation.js.map +1 -1
- package/dist/src/dispute/service.d.ts.map +1 -1
- package/dist/src/dispute/service.js +7 -1
- package/dist/src/dispute/service.js.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/it-utility.d.ts +1 -1
- package/dist/src/it-utility.d.ts.map +1 -1
- package/dist/src/it-utility.js +4 -13
- package/dist/src/it-utility.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +98 -1
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +272 -17
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +12 -0
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +415 -87
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/matchmaking/query-transport.d.ts +2 -0
- package/dist/src/matchmaking/query-transport.d.ts.map +1 -1
- package/dist/src/matchmaking/query-transport.js +6 -0
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/matchmaking/traffic-validation.js +1 -0
- package/dist/src/matchmaking/traffic-validation.js.map +1 -1
- package/dist/src/network/network-manager-service.d.ts +2 -2
- package/dist/src/network/network-manager-service.d.ts.map +1 -1
- package/dist/src/network/network-manager-service.js +8 -4
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/owned-block-seed.d.ts +27 -0
- package/dist/src/owned-block-seed.d.ts.map +1 -0
- package/dist/src/owned-block-seed.js +37 -0
- package/dist/src/owned-block-seed.js.map +1 -0
- package/dist/src/protocol-client.d.ts +1 -0
- package/dist/src/protocol-client.d.ts.map +1 -1
- package/dist/src/protocol-client.js +9 -2
- package/dist/src/protocol-client.js.map +1 -1
- package/dist/src/protocol-limits.d.ts +32 -0
- package/dist/src/protocol-limits.d.ts.map +1 -0
- package/dist/src/protocol-limits.js +32 -0
- package/dist/src/protocol-limits.js.map +1 -0
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +3 -0
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +41 -4
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +128 -80
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +38 -1
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +92 -13
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +7 -1
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/reputation/peer-reputation.js +1 -1
- package/dist/src/reputation/peer-reputation.js.map +1 -1
- package/dist/src/reputation/types.d.ts +3 -1
- package/dist/src/reputation/types.d.ts.map +1 -1
- package/dist/src/reputation/types.js +3 -0
- package/dist/src/reputation/types.js.map +1 -1
- package/dist/src/rn.d.ts +5 -1
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +7 -1
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/arachnode-fret-adapter.d.ts +30 -1
- package/dist/src/storage/arachnode-fret-adapter.d.ts.map +1 -1
- package/dist/src/storage/arachnode-fret-adapter.js +15 -2
- package/dist/src/storage/arachnode-fret-adapter.js.map +1 -1
- package/dist/src/storage/arachnode-partition.d.ts +49 -0
- package/dist/src/storage/arachnode-partition.d.ts.map +1 -0
- package/dist/src/storage/arachnode-partition.js +61 -0
- package/dist/src/storage/arachnode-partition.js.map +1 -0
- package/dist/src/storage/block-storage.d.ts +40 -1
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +150 -57
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +8 -0
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-raw-storage.d.ts +13 -0
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.d.ts +45 -0
- package/dist/src/storage/kv-raw-storage.d.ts.map +1 -0
- package/dist/src/storage/kv-raw-storage.js +113 -0
- package/dist/src/storage/kv-raw-storage.js.map +1 -0
- package/dist/src/storage/memory-storage.d.ts +20 -58
- package/dist/src/storage/memory-storage.d.ts.map +1 -1
- package/dist/src/storage/memory-storage.js +22 -148
- package/dist/src/storage/memory-storage.js.map +1 -1
- package/dist/src/storage/memory-store-driver.d.ts +39 -0
- package/dist/src/storage/memory-store-driver.d.ts.map +1 -0
- package/dist/src/storage/memory-store-driver.js +129 -0
- package/dist/src/storage/memory-store-driver.js.map +1 -0
- package/dist/src/storage/raw-store-codec.d.ts +10 -0
- package/dist/src/storage/raw-store-codec.d.ts.map +1 -0
- package/dist/src/storage/raw-store-codec.js +36 -0
- package/dist/src/storage/raw-store-codec.js.map +1 -0
- package/dist/src/storage/raw-store-driver.d.ts +69 -0
- package/dist/src/storage/raw-store-driver.d.ts.map +1 -0
- package/dist/src/storage/raw-store-driver.js +2 -0
- package/dist/src/storage/raw-store-driver.js.map +1 -0
- package/dist/src/storage/restoration-coordinator-v2.d.ts.map +1 -1
- package/dist/src/storage/restoration-coordinator-v2.js +1 -1
- package/dist/src/storage/restoration-coordinator-v2.js.map +1 -1
- package/dist/src/storage/restoration-coordinator.d.ts +73 -0
- package/dist/src/storage/restoration-coordinator.d.ts.map +1 -0
- package/dist/src/storage/restoration-coordinator.js +181 -0
- package/dist/src/storage/restoration-coordinator.js.map +1 -0
- package/dist/src/storage/ring-selector.d.ts +92 -5
- package/dist/src/storage/ring-selector.d.ts.map +1 -1
- package/dist/src/storage/ring-selector.js +153 -51
- package/dist/src/storage/ring-selector.js.map +1 -1
- package/dist/src/storage/ring-shift-coordinator.d.ts +128 -0
- package/dist/src/storage/ring-shift-coordinator.d.ts.map +1 -0
- package/dist/src/storage/ring-shift-coordinator.js +196 -0
- package/dist/src/storage/ring-shift-coordinator.js.map +1 -0
- package/dist/src/storage/storage-monitor.d.ts +28 -0
- package/dist/src/storage/storage-monitor.d.ts.map +1 -1
- package/dist/src/storage/storage-monitor.js +42 -2
- package/dist/src/storage/storage-monitor.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +19 -0
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +172 -22
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/sync/client.d.ts.map +1 -1
- package/dist/src/sync/client.js +5 -1
- package/dist/src/sync/client.js.map +1 -1
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +2 -1
- package/dist/src/sync/service.js.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +28 -4
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.js +51 -10
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
- package/dist/src/testing/index.d.ts +1 -0
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js +1 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +15 -7
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +24 -0
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -0
- package/dist/src/testing/raw-storage-conformance.js +313 -0
- package/dist/src/testing/raw-storage-conformance.js.map +1 -0
- package/package.json +4 -4
- package/src/cluster/block-transfer-service.ts +4 -3
- package/src/cluster/block-transfer.ts +424 -265
- package/src/cluster/client.ts +3 -1
- package/src/cluster/cluster-repo.ts +481 -71
- package/src/cluster/peer-key-binding.ts +37 -0
- package/src/cluster/quorum-restore.ts +0 -0
- package/src/cluster/rebalance-monitor.ts +34 -3
- package/src/cluster/service.ts +7 -1
- package/src/cluster/spread-on-churn.ts +11 -1
- package/src/cluster/supermajority-coupling.ts +37 -0
- package/src/cohort-topic/cohort-gossip-driver.ts +96 -9
- package/src/cohort-topic/cohort-gossip-transport.ts +10 -0
- package/src/cohort-topic/host.ts +831 -105
- package/src/dispute/arbitrator-selection.ts +138 -28
- package/src/dispute/cascade.ts +8 -1
- package/src/dispute/client.ts +3 -2
- package/src/dispute/dispute-service.ts +94 -9
- package/src/dispute/index.ts +7 -1
- package/src/dispute/invalidation.ts +40 -2
- package/src/dispute/service.ts +7 -1
- package/src/index.ts +46 -43
- package/src/it-utility.ts +5 -13
- package/src/libp2p-key-network.ts +281 -18
- package/src/libp2p-node-base.ts +485 -79
- package/src/matchmaking/query-transport.ts +9 -0
- package/src/network/network-manager-service.ts +8 -2
- package/src/owned-block-seed.ts +40 -0
- package/src/protocol-client.ts +10 -3
- package/src/protocol-limits.ts +33 -0
- package/src/repo/client.ts +3 -0
- package/src/repo/cluster-coordinator.ts +145 -82
- package/src/repo/coordinator-repo.ts +96 -16
- package/src/repo/service.ts +7 -1
- package/src/reputation/types.ts +3 -0
- package/src/rn.ts +7 -1
- package/src/storage/arachnode-fret-adapter.ts +33 -2
- package/src/storage/arachnode-partition.ts +74 -0
- package/src/storage/block-storage.ts +169 -61
- package/src/storage/i-block-storage.ts +9 -0
- package/src/storage/i-raw-storage.ts +14 -0
- package/src/storage/kv-raw-storage.ts +135 -0
- package/src/storage/memory-storage.ts +25 -173
- package/src/storage/memory-store-driver.ts +157 -0
- package/src/storage/raw-store-codec.ts +42 -0
- package/src/storage/raw-store-driver.ts +80 -0
- package/src/storage/{restoration-coordinator-v2.ts → restoration-coordinator.ts} +43 -37
- package/src/storage/ring-selector.ts +317 -176
- package/src/storage/ring-shift-coordinator.ts +271 -0
- package/src/storage/storage-monitor.ts +70 -3
- package/src/storage/storage-repo.ts +710 -558
- package/src/sync/client.ts +5 -1
- package/src/sync/service.ts +2 -1
- package/src/testing/cohort-topic-mesh-harness.ts +61 -11
- package/src/testing/index.ts +1 -0
- package/src/testing/mesh-harness.ts +14 -6
- package/src/testing/raw-storage-conformance.ts +397 -0
|
@@ -1,173 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* `saveMetadata` call fails, masking mid-commit crashes that a persistent store
|
|
27
|
-
* (file/sqlite/leveldb) would surface correctly.
|
|
28
|
-
* @see docs/internals.md "Storage Returns References" pitfall
|
|
29
|
-
*/
|
|
30
|
-
async getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined> {
|
|
31
|
-
const meta = this.metadata.get(blockId);
|
|
32
|
-
return meta ? structuredClone(meta) : undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Stores metadata for a block.
|
|
37
|
-
*
|
|
38
|
-
* @pitfall **MUST store a clone** - callers may continue mutating the metadata object
|
|
39
|
-
* after saving; storing the reference lets those mutations corrupt persisted state.
|
|
40
|
-
* @see docs/internals.md "Storage Returns References" pitfall
|
|
41
|
-
*/
|
|
42
|
-
async saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void> {
|
|
43
|
-
this.metadata.set(blockId, structuredClone(metadata));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined> {
|
|
47
|
-
return this.revisions.get(this.getRevisionKey(blockId, rev));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void> {
|
|
51
|
-
this.revisions.set(this.getRevisionKey(blockId, rev), actionId);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async *listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev> {
|
|
55
|
-
const ascending = startRev <= endRev;
|
|
56
|
-
const actualStart = ascending ? startRev : endRev;
|
|
57
|
-
const actualEnd = ascending ? endRev : startRev;
|
|
58
|
-
|
|
59
|
-
const results: ActionRev[] = [];
|
|
60
|
-
for (let rev = actualStart; rev <= actualEnd; rev++) {
|
|
61
|
-
const actionId = this.revisions.get(this.getRevisionKey(blockId, rev));
|
|
62
|
-
if (actionId) {
|
|
63
|
-
results.push({ rev, actionId });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!ascending) {
|
|
68
|
-
results.reverse();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for (const result of results) {
|
|
72
|
-
yield result;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
77
|
-
return this.pendingActions.get(this.getActionKey(blockId, actionId));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
81
|
-
// Clone transform to prevent external modifications from affecting stored data
|
|
82
|
-
this.pendingActions.set(this.getActionKey(blockId, actionId), structuredClone(transform));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
86
|
-
this.pendingActions.delete(this.getActionKey(blockId, actionId));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async *listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId> {
|
|
90
|
-
const prefix = `${blockId}:`;
|
|
91
|
-
for (const [key] of Array.from(this.pendingActions.entries())) {
|
|
92
|
-
if (key.startsWith(prefix)) {
|
|
93
|
-
yield key.substring(prefix.length);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
99
|
-
return this.actions.get(this.getActionKey(blockId, actionId));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
103
|
-
this.actions.set(this.getActionKey(blockId, actionId), transform);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Retrieves a materialized block at a specific revision.
|
|
108
|
-
*
|
|
109
|
-
* @pitfall **MUST return a clone** - `applyTransform()` mutates blocks in place.
|
|
110
|
-
* If we return the stored reference, mutations corrupt ALL revisions that share
|
|
111
|
-
* the same underlying object.
|
|
112
|
-
* @see docs/internals.md "Storage Returns References" pitfall
|
|
113
|
-
*/
|
|
114
|
-
async getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined> {
|
|
115
|
-
const block = this.materializedBlocks.get(this.getActionKey(blockId, actionId));
|
|
116
|
-
// Clone to prevent external mutations from affecting stored data
|
|
117
|
-
return block ? structuredClone(block) : undefined;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Stores a materialized block at a specific revision.
|
|
122
|
-
*
|
|
123
|
-
* @pitfall **MUST store a clone** - callers may continue mutating the block after saving.
|
|
124
|
-
* If we store the reference, those mutations corrupt the stored data.
|
|
125
|
-
* @see docs/internals.md "Storage Returns References" pitfall
|
|
126
|
-
*/
|
|
127
|
-
async saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void> {
|
|
128
|
-
const key = this.getActionKey(blockId, actionId);
|
|
129
|
-
if (block) {
|
|
130
|
-
// Clone to prevent external mutations from affecting stored data
|
|
131
|
-
this.materializedBlocks.set(key, structuredClone(block));
|
|
132
|
-
} else {
|
|
133
|
-
this.materializedBlocks.delete(key);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
138
|
-
const key = this.getActionKey(blockId, actionId);
|
|
139
|
-
const transform = this.pendingActions.get(key);
|
|
140
|
-
if (transform) {
|
|
141
|
-
this.actions.set(key, transform);
|
|
142
|
-
this.pendingActions.delete(key);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async getApproximateBytesUsed(): Promise<number> {
|
|
147
|
-
let total = 0;
|
|
148
|
-
for (const [blockId, meta] of this.metadata) {
|
|
149
|
-
total += blockId.length + jsonByteSize(meta);
|
|
150
|
-
}
|
|
151
|
-
for (const [key, actionId] of this.revisions) {
|
|
152
|
-
total += key.length + actionId.length;
|
|
153
|
-
}
|
|
154
|
-
for (const [key, transform] of this.pendingActions) {
|
|
155
|
-
total += key.length + jsonByteSize(transform);
|
|
156
|
-
}
|
|
157
|
-
for (const [key, transform] of this.actions) {
|
|
158
|
-
total += key.length + jsonByteSize(transform);
|
|
159
|
-
}
|
|
160
|
-
for (const [key, block] of this.materializedBlocks) {
|
|
161
|
-
total += key.length + jsonByteSize(block);
|
|
162
|
-
}
|
|
163
|
-
return total;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function jsonByteSize(value: unknown): number {
|
|
168
|
-
try {
|
|
169
|
-
return JSON.stringify(value)?.length ?? 0;
|
|
170
|
-
} catch {
|
|
171
|
-
return 0;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
1
|
+
import { KvRawStorage } from "./kv-raw-storage.js";
|
|
2
|
+
import { MemoryStoreDriver } from "./memory-store-driver.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* In-memory {@link IRawStorage}, now a thin shell over the shared
|
|
6
|
+
* {@link KvRawStorage} kernel backed by a {@link MemoryStoreDriver}.
|
|
7
|
+
*
|
|
8
|
+
* @pitfall The old hand-rolled version had to `structuredClone` on every get and
|
|
9
|
+
* put — otherwise callers mutating a returned/stored object corrupted the store
|
|
10
|
+
* (see docs/internals.md "Storage Returns References"). That discipline is GONE
|
|
11
|
+
* and must not be reintroduced: values cross the driver boundary as
|
|
12
|
+
* `Uint8Array` produced by `JSON`-encode and consumed by `JSON`-decode, so every
|
|
13
|
+
* read decodes a fresh object and every write stored an independent byte
|
|
14
|
+
* snapshot BY CONSTRUCTION. The clone-on-store / clone-on-read invariant is now
|
|
15
|
+
* structural, not a rule. The conformance suite asserts it so a future driver
|
|
16
|
+
* that shortcuts the byte boundary is caught.
|
|
17
|
+
*
|
|
18
|
+
* The name is kept stable so existing imports (`from '@optimystic/db-p2p'`)
|
|
19
|
+
* continue to resolve.
|
|
20
|
+
*/
|
|
21
|
+
export class MemoryRawStorage extends KvRawStorage {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(new MemoryStoreDriver());
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { BlockId, ActionId } from "@optimystic/db-core";
|
|
2
|
+
import type { RawStoreDriver } from "./raw-store-driver.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* In-memory {@link RawStoreDriver}: the five logical block-storage stores as
|
|
6
|
+
* `Map`s of `Uint8Array` values. `KvRawStorage` hands this driver bytes produced
|
|
7
|
+
* by `JSON`-encode and reads them back via `JSON`-decode, so every get yields a
|
|
8
|
+
* fresh object and every save stored an independent byte snapshot BY
|
|
9
|
+
* CONSTRUCTION — the old `structuredClone`-on-every-get/put discipline is now
|
|
10
|
+
* structural, not a rule a maintainer has to remember. The driver therefore
|
|
11
|
+
* stores the byte reference directly: those bytes are never mutated by the
|
|
12
|
+
* kernel or exposed to callers as a mutable handle.
|
|
13
|
+
*/
|
|
14
|
+
export class MemoryStoreDriver implements RawStoreDriver {
|
|
15
|
+
private readonly metadata = new Map<BlockId, Uint8Array>();
|
|
16
|
+
private readonly revisions = new Map<string, Uint8Array>(); // `${blockId}:${rev}` -> actionId bytes
|
|
17
|
+
private readonly pending = new Map<string, Uint8Array>(); // `${blockId}:${actionId}` -> transform bytes
|
|
18
|
+
private readonly transactions = new Map<string, Uint8Array>();
|
|
19
|
+
private readonly materialized = new Map<string, Uint8Array>();
|
|
20
|
+
|
|
21
|
+
private revisionKey(blockId: BlockId, rev: number): string {
|
|
22
|
+
return `${blockId}:${rev}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private actionKey(blockId: BlockId, actionId: ActionId): string {
|
|
26
|
+
return `${blockId}:${actionId}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// --- metadata ---
|
|
30
|
+
|
|
31
|
+
async getMetadata(blockId: BlockId): Promise<Uint8Array | undefined> {
|
|
32
|
+
return this.metadata.get(blockId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async putMetadata(blockId: BlockId, value: Uint8Array): Promise<void> {
|
|
36
|
+
this.metadata.set(blockId, value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- revisions ---
|
|
40
|
+
|
|
41
|
+
async getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined> {
|
|
42
|
+
return this.revisions.get(this.revisionKey(blockId, rev));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void> {
|
|
46
|
+
this.revisions.set(this.revisionKey(blockId, rev), value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async *rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]> {
|
|
50
|
+
// Drain into an array before yielding (drain-before-yield contract): the
|
|
51
|
+
// consumer awaits between yields, and a Map has no live cursor to pin, but
|
|
52
|
+
// this keeps the memory driver's semantics identical to the native backends.
|
|
53
|
+
const results: [number, Uint8Array][] = [];
|
|
54
|
+
for (let rev = lo; rev <= hi; rev++) {
|
|
55
|
+
const value = this.revisions.get(this.revisionKey(blockId, rev));
|
|
56
|
+
if (value !== undefined) {
|
|
57
|
+
results.push([rev, value]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (reverse) {
|
|
61
|
+
results.reverse();
|
|
62
|
+
}
|
|
63
|
+
for (const result of results) {
|
|
64
|
+
yield result;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- pending ---
|
|
69
|
+
|
|
70
|
+
async getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
71
|
+
return this.pending.get(this.actionKey(blockId, actionId));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
75
|
+
this.pending.set(this.actionKey(blockId, actionId), value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async deletePending(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
79
|
+
this.pending.delete(this.actionKey(blockId, actionId));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async *listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId> {
|
|
83
|
+
const prefix = `${blockId}:`;
|
|
84
|
+
// Snapshot before yielding (drain-before-yield): a concurrent putPending during
|
|
85
|
+
// the scan must not invalidate a live map iterator.
|
|
86
|
+
const ids: ActionId[] = [];
|
|
87
|
+
for (const key of Array.from(this.pending.keys())) {
|
|
88
|
+
if (key.startsWith(prefix)) {
|
|
89
|
+
ids.push(key.substring(prefix.length) as ActionId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const id of ids) {
|
|
93
|
+
yield id;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- transactions ---
|
|
98
|
+
|
|
99
|
+
async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
100
|
+
return this.transactions.get(this.actionKey(blockId, actionId));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
104
|
+
this.transactions.set(this.actionKey(blockId, actionId), value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- materialized ---
|
|
108
|
+
|
|
109
|
+
async getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
110
|
+
return this.materialized.get(this.actionKey(blockId, actionId));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
114
|
+
this.materialized.set(this.actionKey(blockId, actionId), value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
118
|
+
this.materialized.delete(this.actionKey(blockId, actionId));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- promote (atomic move; synchronous Map ops make it indivisible here) ---
|
|
122
|
+
|
|
123
|
+
async promote(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
124
|
+
const key = this.actionKey(blockId, actionId);
|
|
125
|
+
const value = this.pending.get(key);
|
|
126
|
+
if (value === undefined) {
|
|
127
|
+
throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
|
|
128
|
+
}
|
|
129
|
+
this.transactions.set(key, value);
|
|
130
|
+
this.pending.delete(key);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- optional passthroughs ---
|
|
134
|
+
|
|
135
|
+
async *listBlockIds(): AsyncIterable<BlockId> {
|
|
136
|
+
// Snapshot the keys before yielding so a concurrent putMetadata during the scan
|
|
137
|
+
// doesn't invalidate a live map iterator. Fresh in-memory storage is empty, so at
|
|
138
|
+
// real process startup this yields nothing — it exists so the seed path can be
|
|
139
|
+
// unit-tested against a pre-populated store.
|
|
140
|
+
for (const blockId of Array.from(this.metadata.keys())) {
|
|
141
|
+
yield blockId;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async approximateBytesUsed(): Promise<number> {
|
|
146
|
+
let total = 0;
|
|
147
|
+
for (const [blockId, value] of this.metadata) {
|
|
148
|
+
total += blockId.length + value.byteLength;
|
|
149
|
+
}
|
|
150
|
+
for (const store of [this.revisions, this.pending, this.transactions, this.materialized]) {
|
|
151
|
+
for (const [key, value] of store) {
|
|
152
|
+
total += key.length + value.byteLength;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return total;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ActionId } from "@optimystic/db-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Value codec shared by every `KvRawStorage`-backed store. Values cross the
|
|
5
|
+
* `RawStoreDriver` boundary as `Uint8Array`, so this module is the single place
|
|
6
|
+
* that turns `BlockMetadata`/`Transform`/`IBlock` (via JSON) and the `ActionId`
|
|
7
|
+
* string (via UTF-8) into bytes and back.
|
|
8
|
+
*
|
|
9
|
+
* Because a get always decodes fresh bytes into a NEW object and a save always
|
|
10
|
+
* encodes an independent byte copy, the clone-on-store / clone-on-read invariant
|
|
11
|
+
* that in-memory storage used to enforce by hand (`structuredClone`) is now
|
|
12
|
+
* structural for kernel-backed stores — see docs/internals.md "Storage Returns
|
|
13
|
+
* References".
|
|
14
|
+
*
|
|
15
|
+
* Round-trip fidelity matters for `BlockMetadata`: an open-ended `RevisionRange`
|
|
16
|
+
* is encoded `[E]` (one element, upper bound `undefined`). `JSON.stringify([5])`
|
|
17
|
+
* → `"[5]"` → `JSON.parse` → `[5]`, so the open-ended encoding survives byte-exact.
|
|
18
|
+
* Do NOT normalize ranges here.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const encoder = new TextEncoder();
|
|
22
|
+
const decoder = new TextDecoder();
|
|
23
|
+
|
|
24
|
+
/** JSON-encode a value (`BlockMetadata` / `Transform` / `IBlock`) to UTF-8 bytes. */
|
|
25
|
+
export function encodeJson<T>(value: T): Uint8Array {
|
|
26
|
+
return encoder.encode(JSON.stringify(value));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Decode UTF-8 JSON bytes back into a value. Callers guard the miss (`undefined`) first. */
|
|
30
|
+
export function decodeJson<T>(bytes: Uint8Array): T {
|
|
31
|
+
return JSON.parse(decoder.decode(bytes)) as T;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Encode an `ActionId` string to UTF-8 bytes (stored as the revisions-store value). */
|
|
35
|
+
export function encodeActionId(actionId: ActionId): Uint8Array {
|
|
36
|
+
return encoder.encode(actionId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Decode UTF-8 bytes back into an `ActionId` string. */
|
|
40
|
+
export function decodeActionId(bytes: Uint8Array): ActionId {
|
|
41
|
+
return decoder.decode(bytes) as ActionId;
|
|
42
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { BlockId, ActionId } from "@optimystic/db-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bytes-valued, per-logical-store driver surface. Each backend implements the
|
|
5
|
+
* five block-storage stores (metadata, revisions, pending, transactions,
|
|
6
|
+
* materialized) over its native mechanism (LevelDB tag-ranges, five SQLite
|
|
7
|
+
* tables, five IndexedDB object stores, five filesystem subdirectories, five
|
|
8
|
+
* in-memory maps). `KvRawStorage` layers all JSON serialization and call
|
|
9
|
+
* orchestration on top — drivers never (de)serialize values and never see the
|
|
10
|
+
* `BlockMetadata`/`Transform`/`IBlock` types. Drivers speak only
|
|
11
|
+
* `Uint8Array`/`BlockId`/`ActionId`/`number`.
|
|
12
|
+
*
|
|
13
|
+
* ### Iteration semantics (drain-before-yield)
|
|
14
|
+
*
|
|
15
|
+
* `rangeRevisions` and `listPendingActionIds` return an `AsyncIterable`, but a
|
|
16
|
+
* driver MUST drain its native cursor/iterator into memory BEFORE yielding to
|
|
17
|
+
* the consumer. A live LevelDB iterator, IndexedDB transaction, or SQLite
|
|
18
|
+
* cursor must not straddle the consumer's `await`s: IndexedDB auto-commits an
|
|
19
|
+
* idle transaction, SQLite would hold its mutex slot, and LevelDB pins native
|
|
20
|
+
* resources. The kernel encodes this as a contract (not a shared implementation)
|
|
21
|
+
* because the drain is backend-specific. The conformance suite exercises it by
|
|
22
|
+
* interleaving other awaits between yielded items.
|
|
23
|
+
*
|
|
24
|
+
* ### Promote atomicity
|
|
25
|
+
*
|
|
26
|
+
* `promote` is the ONLY cross-key atomic operation the kernel requires. Every
|
|
27
|
+
* other write is a single put/delete. Each backend satisfies `promote` with its
|
|
28
|
+
* native atomic mechanism (LevelDB batch, SQLite transaction, IndexedDB
|
|
29
|
+
* readwrite transaction, filesystem rename); the kernel never assumes an
|
|
30
|
+
* atomicity a backend cannot deliver.
|
|
31
|
+
*/
|
|
32
|
+
export interface RawStoreDriver {
|
|
33
|
+
// metadata store — keyed by blockId
|
|
34
|
+
getMetadata(blockId: BlockId): Promise<Uint8Array | undefined>;
|
|
35
|
+
putMetadata(blockId: BlockId, value: Uint8Array): Promise<void>;
|
|
36
|
+
|
|
37
|
+
// revisions store — keyed by (blockId, rev), ORDERED BY rev
|
|
38
|
+
getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
|
|
39
|
+
putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Yield `[rev, value]` for every present rev in `[lo, hi]` (both inclusive),
|
|
42
|
+
* ascending when `reverse` is false, descending when `reverse` is true. The
|
|
43
|
+
* driver MUST drain any native cursor into memory before yielding — see the
|
|
44
|
+
* "drain-before-yield" contract above.
|
|
45
|
+
*/
|
|
46
|
+
rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]>;
|
|
47
|
+
|
|
48
|
+
// pending store — keyed by (blockId, actionId)
|
|
49
|
+
getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
50
|
+
putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
51
|
+
deletePending(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
52
|
+
/** Yield each present pending actionId for the block. MUST drain before yielding (see above). */
|
|
53
|
+
listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId>;
|
|
54
|
+
|
|
55
|
+
// transactions store — keyed by (blockId, actionId)
|
|
56
|
+
getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
57
|
+
putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
58
|
+
|
|
59
|
+
// materialized store — keyed by (blockId, actionId)
|
|
60
|
+
getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
61
|
+
putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
62
|
+
deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Atomically move `pending(blockId, actionId)` → `transactions(blockId, actionId)`:
|
|
66
|
+
* write the transactions entry and remove the pending entry as one indivisible
|
|
67
|
+
* step (batch / DB transaction / rename). A crash must leave exactly one of the
|
|
68
|
+
* two states, never both/neither. Throw
|
|
69
|
+
* `Pending action <actionId> not found for block <blockId>` when no pending
|
|
70
|
+
* entry exists. This is the ONLY cross-key atomic operation the kernel requires.
|
|
71
|
+
*/
|
|
72
|
+
promote(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
73
|
+
|
|
74
|
+
/** Optional — enumerate block ids with durable metadata (startup seed). Passed through by the kernel. */
|
|
75
|
+
listBlockIds?(): AsyncIterable<BlockId>;
|
|
76
|
+
/** Optional — best cheap byte estimate. Passed through by the kernel. */
|
|
77
|
+
approximateBytesUsed?(): Promise<number>;
|
|
78
|
+
/** Optional — release the underlying handle. */
|
|
79
|
+
close?(): Promise<void>;
|
|
80
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { BlockId } from '@optimystic/db-core';
|
|
2
|
-
import { hashKey } from 'p2p-fret';
|
|
2
|
+
import { hashKey, type RingCoord } from 'p2p-fret';
|
|
3
3
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
4
4
|
import type { BlockArchive, RestoreCallback } from './struct.js';
|
|
5
5
|
import { SyncClient } from '../sync/client.js';
|
|
6
6
|
import type { IPeerNetwork } from '@optimystic/db-core';
|
|
7
7
|
import type { ArachnodeFretAdapter } from './arachnode-fret-adapter.js';
|
|
8
|
+
import { partitionCovers } from './arachnode-partition.js';
|
|
8
9
|
import { createLogger } from '../logger.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -41,8 +42,14 @@ export class RestorationCoordinator {
|
|
|
41
42
|
const startTime = Date.now();
|
|
42
43
|
this.metrics.totalRequests++;
|
|
43
44
|
|
|
45
|
+
// Hash the block id once into the shared coordinate space. Peer responsibility
|
|
46
|
+
// (RingSelector.calculatePartition) and cohort assembly both operate on hashed
|
|
47
|
+
// coords, so restoration must filter in the same space — see getMyRingPeers and
|
|
48
|
+
// filterByPartition below, which reuse this value.
|
|
49
|
+
const blockCoord = await hashKey(new TextEncoder().encode(blockId));
|
|
50
|
+
|
|
44
51
|
// 1. Try my transaction ring peers first
|
|
45
|
-
const myPeers =
|
|
52
|
+
const myPeers = this.getMyRingPeers(blockCoord);
|
|
46
53
|
const myRingDepth = this.getMyRingDepth();
|
|
47
54
|
|
|
48
55
|
// Avoid dialing self: on a solo/bootstrap node with no listenAddrs, dialing self
|
|
@@ -63,13 +70,15 @@ export class RestorationCoordinator {
|
|
|
63
70
|
return archive;
|
|
64
71
|
}
|
|
65
72
|
}
|
|
73
|
+
// My transaction ring was queried and yielded nothing — count it as one ring failure.
|
|
74
|
+
this.recordFailure(myRingDepth);
|
|
66
75
|
|
|
67
76
|
// 2. Try inner storage rings (broader coverage)
|
|
68
77
|
for (let ringDepth = myRingDepth - 1; ringDepth >= 0; ringDepth--) {
|
|
69
78
|
const storagePeers = this.fretAdapter.findPeersAtRing(ringDepth);
|
|
70
79
|
|
|
71
80
|
// Filter to peers responsible for this block's partition (skip self — cannot dial self).
|
|
72
|
-
const responsiblePeers = this.filterByPartition(storagePeers,
|
|
81
|
+
const responsiblePeers = this.filterByPartition(storagePeers, blockCoord, ringDepth)
|
|
73
82
|
.filter(p => !this.selfPeerId || p !== this.selfPeerId);
|
|
74
83
|
|
|
75
84
|
for (const peerIdStr of responsiblePeers) {
|
|
@@ -79,6 +88,8 @@ export class RestorationCoordinator {
|
|
|
79
88
|
return archive;
|
|
80
89
|
}
|
|
81
90
|
}
|
|
91
|
+
// This inner ring was queried and yielded nothing — count it as one ring failure.
|
|
92
|
+
this.recordFailure(ringDepth);
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
// No ring had the data
|
|
@@ -98,11 +109,10 @@ export class RestorationCoordinator {
|
|
|
98
109
|
|
|
99
110
|
/**
|
|
100
111
|
* Get peers in my transaction ring for a given block.
|
|
112
|
+
* @param blockCoord the block id already hashed into the shared coordinate space.
|
|
101
113
|
*/
|
|
102
|
-
private
|
|
103
|
-
|
|
104
|
-
const coord = await hashKey(blockIdBytes);
|
|
105
|
-
return this.fretAdapter.getFret().assembleCohort(coord, 10);
|
|
114
|
+
private getMyRingPeers(blockCoord: RingCoord): string[] {
|
|
115
|
+
return this.fretAdapter.getFret().assembleCohort(blockCoord, 10);
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
/**
|
|
@@ -115,43 +125,22 @@ export class RestorationCoordinator {
|
|
|
115
125
|
|
|
116
126
|
/**
|
|
117
127
|
* Filter peers by partition responsibility.
|
|
128
|
+
* @param blockCoord the block id hashed into the shared coordinate space (same space
|
|
129
|
+
* RingSelector.calculatePartition uses for peer partitions).
|
|
118
130
|
*/
|
|
119
|
-
private filterByPartition(peers: string[],
|
|
131
|
+
private filterByPartition(peers: string[], blockCoord: RingCoord, ringDepth: number): string[] {
|
|
120
132
|
if (ringDepth === 0) {
|
|
121
133
|
return peers; // Ring 0 covers all blocks
|
|
122
134
|
}
|
|
123
135
|
|
|
124
|
-
const blockPrefix = this.extractBlockPrefix(blockId, ringDepth);
|
|
125
|
-
|
|
126
136
|
return peers.filter(peerId => {
|
|
127
137
|
const info = this.fretAdapter.getArachnodeInfo(peerId);
|
|
128
138
|
if (!info || !info.partition) return false;
|
|
129
|
-
|
|
139
|
+
// Compare block prefix vs peer partition via the shared responsibility derivation.
|
|
140
|
+
return partitionCovers(info.partition, blockCoord);
|
|
130
141
|
});
|
|
131
142
|
}
|
|
132
143
|
|
|
133
|
-
/**
|
|
134
|
-
* Extract prefix bits from block ID for partition matching.
|
|
135
|
-
*/
|
|
136
|
-
private extractBlockPrefix(blockId: BlockId, bits: number): number {
|
|
137
|
-
const bytes = new TextEncoder().encode(blockId);
|
|
138
|
-
// Hash the block ID to get uniform distribution
|
|
139
|
-
const hash = new Uint8Array(32);
|
|
140
|
-
for (let i = 0; i < Math.min(bytes.length, hash.length); i++) {
|
|
141
|
-
hash[i] = bytes[i]!;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Extract first N bits
|
|
145
|
-
let value = 0;
|
|
146
|
-
for (let i = 0; i < bits; i++) {
|
|
147
|
-
const byteIndex = Math.floor(i / 8);
|
|
148
|
-
const bitIndex = 7 - (i % 8);
|
|
149
|
-
const bit = (hash[byteIndex]! >> bitIndex) & 1;
|
|
150
|
-
value = (value << 1) | bit;
|
|
151
|
-
}
|
|
152
|
-
return value;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
144
|
/**
|
|
156
145
|
* Query a specific peer for a block.
|
|
157
146
|
*/
|
|
@@ -185,9 +174,18 @@ export class RestorationCoordinator {
|
|
|
185
174
|
const prevTotal = this.metrics.averageDurationMs * (totalSuccesses - 1);
|
|
186
175
|
this.metrics.averageDurationMs = (prevTotal + durationMs) / totalSuccesses;
|
|
187
176
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
177
|
+
this.log('[Ring %d] Successfully restored block %s in %dms', ringDepth, blockId, durationMs);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Record that a ring was queried during a restore but did not yield the block.
|
|
182
|
+
* Counterpart to {@link recordSuccess}: the ring — not the individual peer query — is the
|
|
183
|
+
* failure unit, so this increments once per exhausted ring at the points where the peer
|
|
184
|
+
* loops in {@link restore} complete without returning an archive.
|
|
185
|
+
*/
|
|
186
|
+
private recordFailure(ringDepth: number): void {
|
|
187
|
+
const count = this.metrics.failureByRing.get(ringDepth) ?? 0;
|
|
188
|
+
this.metrics.failureByRing.set(ringDepth, count + 1);
|
|
191
189
|
}
|
|
192
190
|
|
|
193
191
|
/**
|
|
@@ -199,7 +197,15 @@ export class RestorationCoordinator {
|
|
|
199
197
|
failureByRing: Map<number, number>;
|
|
200
198
|
averageDurationMs: number;
|
|
201
199
|
} {
|
|
202
|
-
|
|
200
|
+
// Copy the Maps: a bare `{ ...this.metrics }` spreads only the top level, handing the
|
|
201
|
+
// caller the SAME Map instances this coordinator keeps mutating — the snapshot would
|
|
202
|
+
// then change under the caller's feet, and a caller mutation would corrupt internal state.
|
|
203
|
+
return {
|
|
204
|
+
totalRequests: this.metrics.totalRequests,
|
|
205
|
+
successByRing: new Map(this.metrics.successByRing),
|
|
206
|
+
failureByRing: new Map(this.metrics.failureByRing),
|
|
207
|
+
averageDurationMs: this.metrics.averageDurationMs
|
|
208
|
+
};
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
|