@optimystic/db-p2p 0.1.1 → 0.1.3
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 → README.md} +7 -0
- package/dist/index.min.js +31 -30
- package/dist/index.min.js.map +4 -4
- package/dist/src/cluster/cluster-repo.d.ts +27 -0
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +139 -18
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/service.d.ts +13 -2
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +17 -7
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-node.d.ts +13 -2
- package/dist/src/libp2p-node.d.ts.map +1 -1
- package/dist/src/libp2p-node.js +35 -16
- package/dist/src/libp2p-node.js.map +1 -1
- package/dist/src/protocol-client.d.ts.map +1 -1
- package/dist/src/protocol-client.js +8 -7
- package/dist/src/protocol-client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +7 -2
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +18 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +26 -3
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +117 -22
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/service.d.ts +13 -2
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +25 -12
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/storage/memory-storage.d.ts +15 -0
- package/dist/src/storage/memory-storage.d.ts.map +1 -1
- package/dist/src/storage/memory-storage.js +23 -4
- package/dist/src/storage/memory-storage.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +7 -2
- package/dist/src/sync/service.js.map +1 -1
- package/package.json +27 -21
- package/src/cluster/cluster-repo.ts +836 -711
- package/src/cluster/service.ts +44 -31
- package/src/index.ts +1 -1
- package/src/libp2p-key-network.ts +334 -334
- package/src/libp2p-node.ts +371 -339
- package/src/network/network-manager-service.ts +334 -334
- package/src/protocol-client.ts +53 -54
- package/src/repo/client.ts +112 -112
- package/src/repo/cluster-coordinator.ts +613 -592
- package/src/repo/coordinator-repo.ts +269 -137
- package/src/repo/service.ts +237 -219
- package/src/storage/block-storage.ts +182 -182
- package/src/storage/memory-storage.ts +24 -5
- package/src/storage/storage-repo.ts +321 -320
- package/src/sync/service.ts +7 -6
- package/dist/src/storage/file-storage.d.ts +0 -30
- package/dist/src/storage/file-storage.d.ts.map +0 -1
- package/dist/src/storage/file-storage.js +0 -127
- package/dist/src/storage/file-storage.js.map +0 -1
- package/src/storage/file-storage.ts +0 -163
package/src/repo/service.ts
CHANGED
|
@@ -1,219 +1,237 @@
|
|
|
1
|
-
import { pipe } from 'it-pipe'
|
|
2
|
-
import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed'
|
|
3
|
-
import type { Startable, Logger,
|
|
4
|
-
import type { IRepo, RepoMessage } from '@optimystic/db-core'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
private readonly
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
1
|
+
import { pipe } from 'it-pipe'
|
|
2
|
+
import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed'
|
|
3
|
+
import type { Startable, Logger, Stream, Connection, StreamHandler } from '@libp2p/interface'
|
|
4
|
+
import type { IRepo, RepoMessage } from '@optimystic/db-core'
|
|
5
|
+
import { peersEqual } from '../peer-utils.js'
|
|
6
|
+
import { sha256 } from 'multiformats/hashes/sha2'
|
|
7
|
+
import { encodePeers } from './redirect.js'
|
|
8
|
+
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
9
|
+
|
|
10
|
+
// Define Components interface
|
|
11
|
+
interface BaseComponents {
|
|
12
|
+
logger: { forComponent: (name: string) => Logger },
|
|
13
|
+
registrar: {
|
|
14
|
+
handle: (protocol: string, handler: StreamHandler, options: any) => Promise<void>
|
|
15
|
+
unhandle: (protocol: string) => Promise<void>
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type RepoServiceComponents = BaseComponents & {
|
|
20
|
+
repo: IRepo
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type RepoServiceInit = {
|
|
24
|
+
protocol?: string,
|
|
25
|
+
protocolPrefix?: string,
|
|
26
|
+
maxInboundStreams?: number,
|
|
27
|
+
maxOutboundStreams?: number,
|
|
28
|
+
logPrefix?: string,
|
|
29
|
+
kBucketSize?: number,
|
|
30
|
+
/**
|
|
31
|
+
* Responsibility K - the replica set size for determining cluster membership.
|
|
32
|
+
* This is distinct from kBucketSize (DHT routing).
|
|
33
|
+
* When set, this determines how many peers (by XOR distance) are considered
|
|
34
|
+
* responsible for a key. If this node is not in the top responsibilityK peers,
|
|
35
|
+
* it will redirect requests to closer peers.
|
|
36
|
+
* Default: 1 (only the closest peer handles requests)
|
|
37
|
+
*/
|
|
38
|
+
responsibilityK?: number,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function repoService(init: RepoServiceInit = {}): (components: RepoServiceComponents) => RepoService {
|
|
42
|
+
return (components: RepoServiceComponents) => new RepoService(components, init);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A libp2p service that handles repo protocol messages
|
|
47
|
+
*/
|
|
48
|
+
export class RepoService implements Startable {
|
|
49
|
+
private readonly protocol: string
|
|
50
|
+
private readonly maxInboundStreams: number
|
|
51
|
+
private readonly maxOutboundStreams: number
|
|
52
|
+
private readonly log: Logger
|
|
53
|
+
private readonly repo: IRepo
|
|
54
|
+
private readonly components: RepoServiceComponents
|
|
55
|
+
private running: boolean
|
|
56
|
+
private readonly k: number
|
|
57
|
+
/** Responsibility K - how many peers are responsible for a key (for redirect decisions) */
|
|
58
|
+
private readonly responsibilityK: number
|
|
59
|
+
|
|
60
|
+
constructor(components: RepoServiceComponents, init: RepoServiceInit = {}) {
|
|
61
|
+
this.components = components
|
|
62
|
+
const computed = init.protocol ?? (init.protocolPrefix ?? '/db-p2p') + '/repo/1.0.0'
|
|
63
|
+
this.protocol = computed
|
|
64
|
+
this.maxInboundStreams = init.maxInboundStreams ?? 32
|
|
65
|
+
this.maxOutboundStreams = init.maxOutboundStreams ?? 64
|
|
66
|
+
this.log = components.logger.forComponent(init.logPrefix ?? 'db-p2p:repo-service')
|
|
67
|
+
this.repo = components.repo
|
|
68
|
+
this.running = false
|
|
69
|
+
this.k = init.kBucketSize ?? 10
|
|
70
|
+
this.responsibilityK = init.responsibilityK ?? 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
readonly [Symbol.toStringTag] = '@libp2p/repo-service'
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start the service
|
|
77
|
+
*/
|
|
78
|
+
async start(): Promise<void> {
|
|
79
|
+
if (this.running) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await this.components.registrar.handle(this.protocol, this.handleIncomingStream.bind(this), {
|
|
84
|
+
maxInboundStreams: this.maxInboundStreams,
|
|
85
|
+
maxOutboundStreams: this.maxOutboundStreams
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
this.running = true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Stop the service
|
|
93
|
+
*/
|
|
94
|
+
async stop(): Promise<void> {
|
|
95
|
+
if (!this.running) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await this.components.registrar.unhandle(this.protocol)
|
|
100
|
+
this.running = false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handle incoming streams on the repo protocol
|
|
105
|
+
*/
|
|
106
|
+
private handleIncomingStream(stream: Stream, connection: Connection): void {
|
|
107
|
+
const peerId = connection.remotePeer
|
|
108
|
+
|
|
109
|
+
const processStream = async function* (this: RepoService, source: AsyncIterable<Uint8ArrayList>) {
|
|
110
|
+
for await (const msg of source) {
|
|
111
|
+
// Decode the message
|
|
112
|
+
const decoded = new TextDecoder().decode(msg.subarray())
|
|
113
|
+
const message = JSON.parse(decoded) as RepoMessage
|
|
114
|
+
|
|
115
|
+
// Process each operation
|
|
116
|
+
const operation = message.operations[0]
|
|
117
|
+
let response: any
|
|
118
|
+
|
|
119
|
+
if ('get' in operation) {
|
|
120
|
+
{
|
|
121
|
+
// Use sha256 digest of block id string for consistent key space
|
|
122
|
+
const mh = await sha256.digest(new TextEncoder().encode(operation.get.blockIds[0]!))
|
|
123
|
+
const key = mh.digest
|
|
124
|
+
const nm: any = (this.components as any).libp2p?.services?.networkManager
|
|
125
|
+
if (nm?.getCluster) {
|
|
126
|
+
const cluster: any[] = await nm.getCluster(key);
|
|
127
|
+
(message as any).cluster = (cluster as any[]).map(p => p.toString?.() ?? String(p))
|
|
128
|
+
const selfId = (this.components as any).libp2p.peerId
|
|
129
|
+
const isMember = cluster.some((p: any) => peersEqual(p, selfId))
|
|
130
|
+
// Use responsibilityK to determine if we're in the responsible set
|
|
131
|
+
const smallMesh = cluster.length < this.responsibilityK
|
|
132
|
+
if (!smallMesh && !isMember) {
|
|
133
|
+
const peers = cluster.filter((p: any) => !peersEqual(p, selfId))
|
|
134
|
+
console.debug('repo-service:redirect', {
|
|
135
|
+
peerId: selfId.toString(),
|
|
136
|
+
reason: 'not-cluster-member',
|
|
137
|
+
operation: 'get',
|
|
138
|
+
blockId: operation.get.blockIds[0],
|
|
139
|
+
cluster: cluster.map((p: any) => p.toString?.() ?? String(p))
|
|
140
|
+
})
|
|
141
|
+
response = encodePeers(peers.map((pid: any) => ({ id: pid.toString(), addrs: [] })))
|
|
142
|
+
} else {
|
|
143
|
+
response = await this.repo.get(operation.get, { expiration: message.expiration, skipClusterFetch: true } as any)
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
response = await this.repo.get(operation.get, { expiration: message.expiration, skipClusterFetch: true } as any)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else if ('pend' in operation) {
|
|
150
|
+
{
|
|
151
|
+
const id = Object.keys(operation.pend.transforms)[0]!
|
|
152
|
+
const mh = await sha256.digest(new TextEncoder().encode(id))
|
|
153
|
+
const key = mh.digest
|
|
154
|
+
const nm: any = (this.components as any).libp2p?.services?.networkManager
|
|
155
|
+
if (nm?.getCluster) {
|
|
156
|
+
const cluster: any[] = await nm.getCluster(key)
|
|
157
|
+
; (message as any).cluster = (cluster as any[]).map(p => p.toString?.() ?? String(p))
|
|
158
|
+
const selfId = (this.components as any).libp2p.peerId
|
|
159
|
+
const isMember = cluster.some((p: any) => peersEqual(p, selfId))
|
|
160
|
+
// Use responsibilityK to determine if we're in the responsible set
|
|
161
|
+
const smallMesh = cluster.length < this.responsibilityK
|
|
162
|
+
if (!smallMesh && !isMember) {
|
|
163
|
+
const peers = cluster.filter((p: any) => !peersEqual(p, selfId))
|
|
164
|
+
console.debug('repo-service:redirect', {
|
|
165
|
+
peerId: selfId.toString(),
|
|
166
|
+
reason: 'not-cluster-member',
|
|
167
|
+
operation: 'pend',
|
|
168
|
+
blockId: id,
|
|
169
|
+
cluster: cluster.map((p: any) => p.toString?.() ?? String(p))
|
|
170
|
+
})
|
|
171
|
+
response = encodePeers(peers.map((pid: any) => ({ id: pid.toString(), addrs: [] })))
|
|
172
|
+
} else {
|
|
173
|
+
response = await this.repo.pend(operation.pend, { expiration: message.expiration })
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
response = await this.repo.pend(operation.pend, { expiration: message.expiration })
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else if ('cancel' in operation) {
|
|
180
|
+
response = await this.repo.cancel(operation.cancel.actionRef, {
|
|
181
|
+
expiration: message.expiration
|
|
182
|
+
})
|
|
183
|
+
} else if ('commit' in operation) {
|
|
184
|
+
{
|
|
185
|
+
const mh = await sha256.digest(new TextEncoder().encode(operation.commit.tailId))
|
|
186
|
+
const key = mh.digest
|
|
187
|
+
const nm: any = (this.components as any).libp2p?.services?.networkManager
|
|
188
|
+
if (nm?.getCluster) {
|
|
189
|
+
const cluster: any[] = await nm.getCluster(key)
|
|
190
|
+
; (message as any).cluster = (cluster as any[]).map(p => p.toString?.() ?? String(p))
|
|
191
|
+
const selfId = (this.components as any).libp2p.peerId
|
|
192
|
+
const isMember = cluster.some((p: any) => peersEqual(p, selfId))
|
|
193
|
+
// Use responsibilityK to determine if we're in the responsible set
|
|
194
|
+
const smallMesh = cluster.length < this.responsibilityK
|
|
195
|
+
if (!smallMesh && !isMember) {
|
|
196
|
+
const peers = cluster.filter((p: any) => !peersEqual(p, selfId))
|
|
197
|
+
console.debug('repo-service:redirect', {
|
|
198
|
+
peerId: selfId.toString(),
|
|
199
|
+
reason: 'not-cluster-member',
|
|
200
|
+
operation: 'commit',
|
|
201
|
+
tailId: operation.commit.tailId,
|
|
202
|
+
cluster: cluster.map((p: any) => p.toString?.() ?? String(p))
|
|
203
|
+
})
|
|
204
|
+
response = encodePeers(peers.map((pid: any) => ({ id: pid.toString(), addrs: [] })))
|
|
205
|
+
} else {
|
|
206
|
+
response = await this.repo.commit(operation.commit, { expiration: message.expiration })
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
response = await this.repo.commit(operation.commit, { expiration: message.expiration })
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Encode and yield the response
|
|
215
|
+
yield new TextEncoder().encode(JSON.stringify(response))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
void (async () => {
|
|
220
|
+
try {
|
|
221
|
+
const responses = pipe(
|
|
222
|
+
stream,
|
|
223
|
+
(source) => lpDecode(source),
|
|
224
|
+
processStream.bind(this),
|
|
225
|
+
(source) => lpEncode(source)
|
|
226
|
+
)
|
|
227
|
+
for await (const chunk of responses) {
|
|
228
|
+
stream.send(chunk)
|
|
229
|
+
}
|
|
230
|
+
await stream.close()
|
|
231
|
+
} catch (err) {
|
|
232
|
+
this.log.error('error handling repo protocol message from %p - %e', peerId, err)
|
|
233
|
+
stream.abort(err instanceof Error ? err : new Error(String(err)))
|
|
234
|
+
}
|
|
235
|
+
})()
|
|
236
|
+
}
|
|
237
|
+
}
|