@optimystic/db-p2p 0.11.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cluster/block-transfer-service.d.ts +0 -1
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +0 -2
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +1 -2
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +5 -5
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/partition-detector.d.ts.map +1 -1
- package/dist/src/cluster/partition-detector.js +0 -1
- package/dist/src/cluster/partition-detector.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/service.d.ts +0 -6
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +0 -13
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +1 -1
- package/dist/src/dispute/dispute-service.js.map +1 -1
- 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 +14 -1
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/network/network-manager-service.js +1 -1
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +3 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +17 -9
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/service.d.ts +0 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +0 -2
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/routing/simple-cluster-coordinator.d.ts +2 -2
- package/dist/src/routing/simple-cluster-coordinator.d.ts.map +1 -1
- package/dist/src/routing/simple-cluster-coordinator.js +2 -2
- package/dist/src/routing/simple-cluster-coordinator.js.map +1 -1
- package/dist/src/storage/i-raw-storage.d.ts +9 -0
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/memory-storage.d.ts +1 -0
- package/dist/src/storage/memory-storage.d.ts.map +1 -1
- package/dist/src/storage/memory-storage.js +27 -0
- package/dist/src/storage/memory-storage.js.map +1 -1
- package/dist/src/storage/ring-selector.d.ts.map +1 -1
- package/dist/src/storage/ring-selector.js +23 -5
- package/dist/src/storage/ring-selector.js.map +1 -1
- package/dist/src/storage/storage-monitor.d.ts.map +1 -1
- package/dist/src/storage/storage-monitor.js +10 -14
- package/dist/src/storage/storage-monitor.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +2 -2
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +2 -2
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/sync/service.d.ts +0 -1
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +0 -2
- package/dist/src/sync/service.js.map +1 -1
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/package.json +2 -2
- package/src/cluster/block-transfer-service.ts +1 -1
- package/src/cluster/cluster-repo.ts +5 -3
- package/src/cluster/partition-detector.ts +0 -2
- package/src/cluster/rebalance-monitor.ts +1 -1
- package/src/cluster/service.ts +0 -13
- package/src/dispute/dispute-service.ts +1 -2
- package/src/dispute/service.ts +1 -1
- package/src/libp2p-key-network.ts +1 -1
- package/src/libp2p-node-base.ts +13 -3
- package/src/network/network-manager-service.ts +1 -1
- package/src/repo/cluster-coordinator.ts +4 -4
- package/src/repo/coordinator-repo.ts +20 -10
- package/src/repo/service.ts +0 -2
- package/src/routing/simple-cluster-coordinator.ts +2 -2
- package/src/storage/i-raw-storage.ts +10 -0
- package/src/storage/memory-storage.ts +28 -0
- package/src/storage/ring-selector.ts +26 -5
- package/src/storage/storage-monitor.ts +10 -17
- package/src/storage/storage-repo.ts +2 -3
- package/src/sync/service.ts +2 -3
- package/src/testing/mesh-harness.ts +1 -1
|
@@ -169,7 +169,7 @@ export class NetworkManagerService implements Startable {
|
|
|
169
169
|
throw new Error('FRET service not available for seeding keys');
|
|
170
170
|
}
|
|
171
171
|
const coord = await hashKey(key);
|
|
172
|
-
|
|
172
|
+
fret.getNeighbors(coord, 'both', 1);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
private toCacheKey(key: Uint8Array): string {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { peerIdFromString } from "@libp2p/peer-id";
|
|
2
|
-
import type { ClusterRecord, IKeyNetwork, RepoMessage, BlockId, ClusterPeers, MessageOptions,
|
|
2
|
+
import type { ClusterRecord, IKeyNetwork, RepoMessage, BlockId, ClusterPeers, MessageOptions, ClusterConsensusConfig } from "@optimystic/db-core";
|
|
3
3
|
import { base58btc } from "multiformats/bases/base58";
|
|
4
4
|
import { sha256 } from "multiformats/hashes/sha2";
|
|
5
5
|
import { ClusterClient } from "../cluster/client.js";
|
|
@@ -123,7 +123,7 @@ export class ClusterCoordinator {
|
|
|
123
123
|
* Initiates a 2-phase transaction for a specific block ID.
|
|
124
124
|
* Returns the cluster record and whether the local cluster already executed the operations.
|
|
125
125
|
*/
|
|
126
|
-
async executeClusterTransaction(blockId: BlockId, message: RepoMessage,
|
|
126
|
+
async executeClusterTransaction(blockId: BlockId, message: RepoMessage, _options?: MessageOptions): Promise<{
|
|
127
127
|
record: ClusterRecord;
|
|
128
128
|
localExecuted: boolean;
|
|
129
129
|
}> {
|
|
@@ -298,7 +298,7 @@ export class ClusterCoordinator {
|
|
|
298
298
|
* Validate that a small cluster size is legitimate by querying remote peers
|
|
299
299
|
* for their network size estimates. Returns true if estimates roughly agree.
|
|
300
300
|
*/
|
|
301
|
-
private async validateSmallCluster(localSize: number,
|
|
301
|
+
private async validateSmallCluster(localSize: number, _peers: ClusterPeers): Promise<boolean> {
|
|
302
302
|
// If we have FRET and it shows confident estimate
|
|
303
303
|
if (this.fretService) {
|
|
304
304
|
try {
|
|
@@ -581,7 +581,7 @@ export class ClusterCoordinator {
|
|
|
581
581
|
});
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
-
private scheduleCommitRetry(messageHash: string,
|
|
584
|
+
private scheduleCommitRetry(messageHash: string, _record: ClusterRecord, missingPeers: string[]): void {
|
|
585
585
|
const state = this.transactions.get(messageHash);
|
|
586
586
|
if (!state) {
|
|
587
587
|
return;
|
|
@@ -187,8 +187,25 @@ export class CoordinatorRepo implements IRepo {
|
|
|
187
187
|
private async fetchBlockFromCluster(blockId: BlockId, context?: ActionContext): Promise<void> {
|
|
188
188
|
if (!this.clusterLatestCallback) return;
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
const
|
|
190
|
+
const blockIdBytes = new TextEncoder().encode(blockId);
|
|
191
|
+
const peers = await this.keyNetwork.findCluster(blockIdBytes);
|
|
192
|
+
const peerIds = peers ? Object.keys(peers) : [];
|
|
193
|
+
if (peerIds.length === 0) return;
|
|
194
|
+
|
|
195
|
+
// Solo-cluster short-circuit: the only responsible peer is us. There is no
|
|
196
|
+
// remote to sync from, so skip the callback entirely. Querying ourselves
|
|
197
|
+
// would dial self via SyncClient — pointless at best, and on nodes without
|
|
198
|
+
// listen addresses (e.g. solo WebSocket-only) the dial can hang.
|
|
199
|
+
if (
|
|
200
|
+
peerIds.length === 1
|
|
201
|
+
&& this.localPeerId
|
|
202
|
+
&& peerIds[0] === this.localPeerId.toString()
|
|
203
|
+
) {
|
|
204
|
+
log('cluster-fetch:solo-self-skip', { blockId });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const clusterLatest = await this.queryClusterForLatest(peerIds, blockId, context);
|
|
192
209
|
if (clusterLatest) {
|
|
193
210
|
// Found on cluster - trigger restoration to sync the block
|
|
194
211
|
await this.storageRepo.get({ blockIds: [blockId], context: { committed: [clusterLatest], rev: clusterLatest.rev } });
|
|
@@ -199,14 +216,7 @@ export class CoordinatorRepo implements IRepo {
|
|
|
199
216
|
/**
|
|
200
217
|
* Query cluster peers to find the maximum latest revision for a block.
|
|
201
218
|
*/
|
|
202
|
-
private async queryClusterForLatest(blockId: BlockId, context?: ActionContext): Promise<ActionRev | undefined> {
|
|
203
|
-
const blockIdBytes = new TextEncoder().encode(blockId);
|
|
204
|
-
const peers = await this.keyNetwork.findCluster(blockIdBytes);
|
|
205
|
-
if (!peers || Object.keys(peers).length === 0) {
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const peerIds = Object.keys(peers);
|
|
219
|
+
private async queryClusterForLatest(peerIds: string[], blockId: BlockId, context?: ActionContext): Promise<ActionRev | undefined> {
|
|
210
220
|
let maxLatest: ActionRev | undefined;
|
|
211
221
|
|
|
212
222
|
// Add timeout wrapper to prevent hanging on unresponsive peers
|
package/src/repo/service.ts
CHANGED
|
@@ -63,7 +63,6 @@ export class RepoService implements Startable {
|
|
|
63
63
|
private readonly repo: IRepo
|
|
64
64
|
private readonly components: RepoServiceComponents
|
|
65
65
|
private running: boolean
|
|
66
|
-
private readonly k: number
|
|
67
66
|
/** Responsibility K - how many peers are responsible for a key (for redirect decisions) */
|
|
68
67
|
private readonly responsibilityK: number
|
|
69
68
|
|
|
@@ -76,7 +75,6 @@ export class RepoService implements Startable {
|
|
|
76
75
|
this.log = components.logger.forComponent(init.logPrefix ?? 'db-p2p:repo-service')
|
|
77
76
|
this.repo = components.repo
|
|
78
77
|
this.running = false
|
|
79
|
-
this.k = init.kBucketSize ?? 10
|
|
80
78
|
this.responsibilityK = init.responsibilityK ?? 1
|
|
81
79
|
}
|
|
82
80
|
|
|
@@ -57,13 +57,13 @@ export class ModuloCoordinator implements SimpleClusterCoordinator {
|
|
|
57
57
|
* For very small clusters, just replicate everywhere
|
|
58
58
|
*/
|
|
59
59
|
export class FullReplicationCoordinator implements SimpleClusterCoordinator {
|
|
60
|
-
async selectCoordinator(
|
|
60
|
+
async selectCoordinator(_key: Uint8Array, peers: PeerId[]): Promise<PeerId> {
|
|
61
61
|
// Always select first peer as primary
|
|
62
62
|
if (peers.length === 0) throw new Error('No peers available')
|
|
63
63
|
return peers[0]!
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
async selectReplicas(
|
|
66
|
+
async selectReplicas(_key: Uint8Array, peers: PeerId[], _replicationFactor: number): Promise<PeerId[]> {
|
|
67
67
|
// Replicate to all peers in small clusters
|
|
68
68
|
return [...peers]
|
|
69
69
|
}
|
|
@@ -27,4 +27,14 @@ export interface IRawStorage {
|
|
|
27
27
|
|
|
28
28
|
// Promote a pending action to a committed action
|
|
29
29
|
promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Approximate bytes currently stored by this backend.
|
|
33
|
+
*
|
|
34
|
+
* Used by `StorageMonitor` to feed real used-space figures into ring selection.
|
|
35
|
+
* Implementations should return their best cheap estimate (e.g. on-disk size for
|
|
36
|
+
* filesystem backends, tracked footprint for in-memory backends). The result is
|
|
37
|
+
* advisory — `StorageMonitor` treats a missing implementation as 0.
|
|
38
|
+
*/
|
|
39
|
+
getApproximateBytesUsed?(): Promise<number>;
|
|
30
40
|
}
|
|
@@ -142,4 +142,32 @@ export class MemoryRawStorage implements IRawStorage {
|
|
|
142
142
|
this.pendingActions.delete(key);
|
|
143
143
|
}
|
|
144
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
|
+
}
|
|
145
173
|
}
|
|
@@ -42,11 +42,32 @@ export class RingSelector {
|
|
|
42
42
|
return -1;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
// Estimate total network
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
// Estimate total network spare capacity by aggregating observed per-ring stats.
|
|
46
|
+
// See docs/arachnode.md §"Capacity Management and Ring Adjustment": ring selection
|
|
47
|
+
// should reflect observed network demand, not a hard-coded constant. We treat
|
|
48
|
+
// Σ (peerCount × avgCapacity) over known rings as a proxy for total network spare
|
|
49
|
+
// capacity, so `coverage` becomes our share of it. More peers (or richer peers) →
|
|
50
|
+
// smaller share → higher ring depth (more specialization). On bootstrap, when no
|
|
51
|
+
// ring stats are observed yet, we fall back to a conservative fixed estimate so
|
|
52
|
+
// first-boot behavior is deterministic.
|
|
53
|
+
const ringStats = this.fretAdapter.getRingStats();
|
|
54
|
+
let estimatedTotalData: number;
|
|
55
|
+
if (ringStats.length > 0) {
|
|
56
|
+
estimatedTotalData = ringStats.reduce(
|
|
57
|
+
(sum, stat) => sum + stat.peerCount * stat.avgCapacity,
|
|
58
|
+
0
|
|
59
|
+
);
|
|
60
|
+
} else {
|
|
61
|
+
const avgBlockSize = 100 * 1024; // 100KB typical block
|
|
62
|
+
const estimatedTotalBlocks = 1000; // Conservative estimate
|
|
63
|
+
estimatedTotalData = estimatedTotalBlocks * avgBlockSize;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Guard against pathological zero-aggregate (e.g. all peers report 0 available);
|
|
67
|
+
// fall back to local capacity so coverage = 1.0 (ring 0).
|
|
68
|
+
if (estimatedTotalData <= 0) {
|
|
69
|
+
estimatedTotalData = capacity.available;
|
|
70
|
+
}
|
|
50
71
|
|
|
51
72
|
// Calculate what fraction of keyspace we can cover
|
|
52
73
|
const coverage = capacity.available / estimatedTotalData;
|
|
@@ -28,22 +28,15 @@ export class StorageMonitor {
|
|
|
28
28
|
const usedOverride = this.config.usedBytes;
|
|
29
29
|
const availableOverride = this.config.availableBytes;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
used: Math.min(total, Math.max(0, used)),
|
|
41
|
-
available: Math.max(0, Math.min(total, available))
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const used = await this.estimateUsedSpace();
|
|
46
|
-
const available = Math.max(0, total - used);
|
|
31
|
+
// Only short-circuit the backend when used/available is explicitly supplied;
|
|
32
|
+
// a bare `totalBytes` is just the disk-size hint and should still let the
|
|
33
|
+
// backend report actual used bytes.
|
|
34
|
+
const rawUsed = usedOverride
|
|
35
|
+
?? (availableOverride !== undefined ? total - availableOverride : await this.estimateUsedSpace());
|
|
36
|
+
const used = Math.min(total, Math.max(0, rawUsed));
|
|
37
|
+
const available = availableOverride !== undefined
|
|
38
|
+
? Math.max(0, Math.min(total, availableOverride))
|
|
39
|
+
: Math.max(0, total - used);
|
|
47
40
|
|
|
48
41
|
return {
|
|
49
42
|
total,
|
|
@@ -53,7 +46,7 @@ export class StorageMonitor {
|
|
|
53
46
|
}
|
|
54
47
|
|
|
55
48
|
private async estimateUsedSpace(): Promise<number> {
|
|
56
|
-
return
|
|
49
|
+
return (await this.storage.getApproximateBytesUsed?.()) ?? 0;
|
|
57
50
|
}
|
|
58
51
|
}
|
|
59
52
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
IRepo, MessageOptions, BlockId, CommitRequest, CommitResult, GetBlockResults, PendRequest, PendResult, ActionBlocks,
|
|
3
3
|
ActionId, BlockGets, ActionPending, PendSuccess, ActionTransform, ActionTransforms,
|
|
4
|
-
Transforms,
|
|
5
4
|
GetBlockResult,
|
|
6
5
|
PendValidationHook
|
|
7
6
|
} from "@optimystic/db-core";
|
|
@@ -30,7 +29,7 @@ export class StorageRepo implements IRepo {
|
|
|
30
29
|
this.validatePend = options?.validatePend;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
async get({ blockIds, context }: BlockGets,
|
|
32
|
+
async get({ blockIds, context }: BlockGets, _options?: MessageOptions): Promise<GetBlockResults> {
|
|
34
33
|
const distinctBlockIds = Array.from(new Set(blockIds));
|
|
35
34
|
log('get blockIds=%d', distinctBlockIds.length);
|
|
36
35
|
const results = await Promise.all(distinctBlockIds.map(async (blockId) => {
|
|
@@ -191,7 +190,7 @@ export class StorageRepo implements IRepo {
|
|
|
191
190
|
}));
|
|
192
191
|
}
|
|
193
192
|
|
|
194
|
-
async commit(request: CommitRequest,
|
|
193
|
+
async commit(request: CommitRequest, _options?: MessageOptions): Promise<CommitResult> {
|
|
195
194
|
log('commit actionId=%s rev=%d blockIds=%d', request.actionId, request.rev, request.blockIds.length);
|
|
196
195
|
const uniqueBlockIds = Array.from(new Set(request.blockIds)).sort();
|
|
197
196
|
const releases: (() => void)[] = [];
|
package/src/sync/service.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { ComponentLogger, Startable
|
|
1
|
+
import type { ComponentLogger, Startable } from '@libp2p/interface';
|
|
2
2
|
import type { IRepo } from '@optimystic/db-core';
|
|
3
3
|
import { buildSyncProtocol, type SyncRequest, type SyncResponse } from './protocol.js';
|
|
4
4
|
import { pipe } from 'it-pipe';
|
|
5
|
-
import { fromString as u8FromString } from 'uint8arrays/from-string';
|
|
6
5
|
import { toString as u8ToString } from 'uint8arrays/to-string';
|
|
7
6
|
import * as lp from 'it-length-prefixed';
|
|
8
7
|
import type { Uint8ArrayList } from 'uint8arraylist';
|
|
@@ -37,7 +36,7 @@ export class SyncService implements Startable {
|
|
|
37
36
|
private readonly registrar: { handle: (...args: any[]) => Promise<void>, unhandle: (...args: any[]) => Promise<void> };
|
|
38
37
|
|
|
39
38
|
constructor(
|
|
40
|
-
|
|
39
|
+
components: SyncServiceComponents,
|
|
41
40
|
init: SyncServiceInit = {}
|
|
42
41
|
) {
|
|
43
42
|
this.log = components.logger.forComponent('db-p2p:sync-service');
|
|
@@ -62,7 +62,7 @@ class MockMeshKeyNetwork implements IKeyNetwork {
|
|
|
62
62
|
private readonly failures: MeshFailureConfig = {}
|
|
63
63
|
) {}
|
|
64
64
|
|
|
65
|
-
async findCoordinator
|
|
65
|
+
async findCoordinator(key: Uint8Array, options?: Partial<FindCoordinatorOptions>): Promise<PeerId> {
|
|
66
66
|
const excluded = new Set((options?.excludedPeers ?? []).map(p => p.toString()));
|
|
67
67
|
const sorted = this.sortedByDistance(key);
|
|
68
68
|
const pick = sorted.find(n => !excluded.has(n.peerId.toString()));
|