@libp2p/kad-dht 9.3.4 → 9.3.6-05abd49f
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 +3 -3
- package/dist/index.min.js +23 -23
- package/dist/src/content-fetching/index.d.ts +2 -2
- package/dist/src/content-fetching/index.d.ts.map +1 -1
- package/dist/src/content-fetching/index.js +4 -4
- package/dist/src/content-fetching/index.js.map +1 -1
- package/dist/src/dual-kad-dht.d.ts +5 -5
- package/dist/src/dual-kad-dht.d.ts.map +1 -1
- package/dist/src/dual-kad-dht.js +5 -5
- package/dist/src/dual-kad-dht.js.map +1 -1
- package/dist/src/index.d.ts +9 -10
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/kad-dht.d.ts +4 -4
- package/dist/src/kad-dht.d.ts.map +1 -1
- package/dist/src/kad-dht.js +7 -7
- package/dist/src/kad-dht.js.map +1 -1
- package/dist/src/message/index.d.ts +2 -2
- package/dist/src/message/index.d.ts.map +1 -1
- package/dist/src/message/index.js +1 -1
- package/dist/src/message/index.js.map +1 -1
- package/dist/src/network.d.ts +5 -5
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +2 -2
- package/dist/src/network.js.map +1 -1
- package/dist/src/peer-list/index.d.ts +1 -1
- package/dist/src/peer-list/peer-distance-list.d.ts +1 -1
- package/dist/src/peer-routing/index.d.ts +3 -3
- package/dist/src/peer-routing/index.d.ts.map +1 -1
- package/dist/src/peer-routing/index.js +3 -3
- package/dist/src/peer-routing/index.js.map +1 -1
- package/dist/src/providers.d.ts +2 -2
- package/dist/src/providers.d.ts.map +1 -1
- package/dist/src/query/events.d.ts +3 -3
- package/dist/src/query/events.d.ts.map +1 -1
- package/dist/src/query/events.js +1 -1
- package/dist/src/query/events.js.map +1 -1
- package/dist/src/query/manager.d.ts +3 -3
- package/dist/src/query/manager.d.ts.map +1 -1
- package/dist/src/query/manager.js +2 -2
- package/dist/src/query/manager.js.map +1 -1
- package/dist/src/query/query-path.d.ts +2 -2
- package/dist/src/query/query-path.d.ts.map +1 -1
- package/dist/src/query/query-path.js +1 -1
- package/dist/src/query/query-path.js.map +1 -1
- package/dist/src/query/types.d.ts +1 -1
- package/dist/src/query-self.d.ts +10 -7
- package/dist/src/query-self.d.ts.map +1 -1
- package/dist/src/query-self.js +31 -47
- package/dist/src/query-self.js.map +1 -1
- package/dist/src/record/index.d.ts +22 -0
- package/dist/src/record/index.d.ts.map +1 -0
- package/dist/src/record/index.js +53 -0
- package/dist/src/record/index.js.map +1 -0
- package/dist/src/record/record.d.ts +13 -0
- package/dist/src/record/record.d.ts.map +1 -0
- package/dist/src/record/record.js +67 -0
- package/dist/src/record/record.js.map +1 -0
- package/dist/src/record/selectors.d.ts +7 -0
- package/dist/src/record/selectors.d.ts.map +1 -0
- package/dist/src/record/selectors.js +38 -0
- package/dist/src/record/selectors.js.map +1 -0
- package/dist/src/record/utils.d.ts +11 -0
- package/dist/src/record/utils.d.ts.map +1 -0
- package/dist/src/record/utils.js +41 -0
- package/dist/src/record/utils.js.map +1 -0
- package/dist/src/record/validators.d.ts +10 -0
- package/dist/src/record/validators.d.ts.map +1 -0
- package/dist/src/record/validators.js +54 -0
- package/dist/src/record/validators.js.map +1 -0
- package/dist/src/routing-table/index.d.ts +11 -6
- package/dist/src/routing-table/index.d.ts.map +1 -1
- package/dist/src/routing-table/index.js +7 -3
- package/dist/src/routing-table/index.js.map +1 -1
- package/dist/src/routing-table/k-bucket.d.ts +2 -2
- package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
- package/dist/src/routing-table/k-bucket.js +1 -1
- package/dist/src/routing-table/k-bucket.js.map +1 -1
- package/dist/src/routing-table/refresh.d.ts +1 -1
- package/dist/src/rpc/handlers/add-provider.d.ts +1 -1
- package/dist/src/rpc/handlers/add-provider.js +1 -1
- package/dist/src/rpc/handlers/add-provider.js.map +1 -1
- package/dist/src/rpc/handlers/find-node.d.ts +2 -2
- package/dist/src/rpc/handlers/find-node.d.ts.map +1 -1
- package/dist/src/rpc/handlers/get-providers.d.ts +3 -3
- package/dist/src/rpc/handlers/get-providers.js +1 -1
- package/dist/src/rpc/handlers/get-providers.js.map +1 -1
- package/dist/src/rpc/handlers/get-value.d.ts +3 -3
- package/dist/src/rpc/handlers/get-value.d.ts.map +1 -1
- package/dist/src/rpc/handlers/get-value.js +2 -2
- package/dist/src/rpc/handlers/get-value.js.map +1 -1
- package/dist/src/rpc/handlers/ping.d.ts +1 -1
- package/dist/src/rpc/handlers/put-value.d.ts +1 -1
- package/dist/src/rpc/handlers/put-value.js +2 -2
- package/dist/src/rpc/handlers/put-value.js.map +1 -1
- package/dist/src/rpc/index.d.ts +2 -2
- package/dist/src/rpc/index.d.ts.map +1 -1
- package/dist/src/topology-listener.d.ts +3 -3
- package/dist/src/topology-listener.d.ts.map +1 -1
- package/dist/src/topology-listener.js +2 -4
- package/dist/src/topology-listener.js.map +1 -1
- package/dist/src/utils.d.ts +2 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +1 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +31 -136
- package/src/content-fetching/index.ts +5 -5
- package/src/content-routing/index.ts +1 -1
- package/src/dual-kad-dht.ts +7 -7
- package/src/index.ts +9 -10
- package/src/kad-dht.ts +12 -10
- package/src/message/index.ts +2 -2
- package/src/network.ts +7 -7
- package/src/peer-list/index.ts +1 -1
- package/src/peer-list/peer-distance-list.ts +1 -1
- package/src/peer-routing/index.ts +6 -6
- package/src/providers.ts +2 -2
- package/src/query/events.ts +4 -4
- package/src/query/manager.ts +5 -5
- package/src/query/query-path.ts +3 -3
- package/src/query/types.ts +1 -1
- package/src/query-self.ts +68 -86
- package/src/record/index.ts +70 -0
- package/src/record/record.proto +20 -0
- package/src/record/record.ts +87 -0
- package/src/record/selectors.ts +50 -0
- package/src/record/utils.ts +46 -0
- package/src/record/validators.ts +69 -0
- package/src/routing-table/index.ts +20 -8
- package/src/routing-table/k-bucket.ts +2 -2
- package/src/routing-table/refresh.ts +1 -1
- package/src/rpc/handlers/add-provider.ts +2 -2
- package/src/rpc/handlers/find-node.ts +3 -3
- package/src/rpc/handlers/get-providers.ts +4 -4
- package/src/rpc/handlers/get-value.ts +4 -4
- package/src/rpc/handlers/ping.ts +1 -1
- package/src/rpc/handlers/put-value.ts +3 -3
- package/src/rpc/index.ts +2 -2
- package/src/topology-listener.ts +4 -6
- package/src/utils.ts +3 -3
- package/dist/typedoc-urls.json +0 -29
package/src/query/query-path.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CodeError } from '@libp2p/
|
|
1
|
+
import { CodeError } from '@libp2p/interface/errors'
|
|
2
2
|
import { anySignal } from 'any-signal'
|
|
3
3
|
import defer from 'p-defer'
|
|
4
4
|
import Queue from 'p-queue'
|
|
@@ -9,8 +9,8 @@ import { queryErrorEvent } from './events.js'
|
|
|
9
9
|
import type { CleanUpEvents } from './manager.js'
|
|
10
10
|
import type { QueryEvent, QueryOptions } from '../index.js'
|
|
11
11
|
import type { QueryFunc } from '../query/types.js'
|
|
12
|
-
import type {
|
|
13
|
-
import type {
|
|
12
|
+
import type { EventEmitter } from '@libp2p/interface/events'
|
|
13
|
+
import type { PeerId } from '@libp2p/interface/peer-id'
|
|
14
14
|
import type { Logger } from '@libp2p/logger'
|
|
15
15
|
import type { PeerSet } from '@libp2p/peer-collections'
|
|
16
16
|
|
package/src/query/types.ts
CHANGED
package/src/query-self.ts
CHANGED
|
@@ -4,11 +4,13 @@ import { anySignal } from 'any-signal'
|
|
|
4
4
|
import length from 'it-length'
|
|
5
5
|
import { pipe } from 'it-pipe'
|
|
6
6
|
import take from 'it-take'
|
|
7
|
+
import pDefer from 'p-defer'
|
|
8
|
+
import { pEvent } from 'p-event'
|
|
7
9
|
import { QUERY_SELF_INTERVAL, QUERY_SELF_TIMEOUT, K, QUERY_SELF_INITIAL_INTERVAL } from './constants.js'
|
|
8
|
-
import type { KadDHTComponents } from './index.js'
|
|
9
10
|
import type { PeerRouting } from './peer-routing/index.js'
|
|
10
11
|
import type { RoutingTable } from './routing-table/index.js'
|
|
11
|
-
import type {
|
|
12
|
+
import type { PeerId } from '@libp2p/interface/peer-id'
|
|
13
|
+
import type { Startable } from '@libp2p/interface/startable'
|
|
12
14
|
import type { DeferredPromise } from 'p-defer'
|
|
13
15
|
|
|
14
16
|
export interface QuerySelfInit {
|
|
@@ -22,18 +24,8 @@ export interface QuerySelfInit {
|
|
|
22
24
|
initialQuerySelfHasRun: DeferredPromise<void>
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return function () {
|
|
29
|
-
const later = function (): void {
|
|
30
|
-
timeout = undefined
|
|
31
|
-
func()
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
clearTimeout(timeout)
|
|
35
|
-
timeout = setTimeout(later, wait)
|
|
36
|
-
}
|
|
27
|
+
export interface QuerySelfComponents {
|
|
28
|
+
peerId: PeerId
|
|
37
29
|
}
|
|
38
30
|
|
|
39
31
|
/**
|
|
@@ -41,7 +33,7 @@ function debounce (func: () => void, wait: number): () => void {
|
|
|
41
33
|
*/
|
|
42
34
|
export class QuerySelf implements Startable {
|
|
43
35
|
private readonly log: Logger
|
|
44
|
-
private readonly components:
|
|
36
|
+
private readonly components: QuerySelfComponents
|
|
45
37
|
private readonly peerRouting: PeerRouting
|
|
46
38
|
private readonly routingTable: RoutingTable
|
|
47
39
|
private readonly count: number
|
|
@@ -49,17 +41,16 @@ export class QuerySelf implements Startable {
|
|
|
49
41
|
private readonly initialInterval: number
|
|
50
42
|
private readonly queryTimeout: number
|
|
51
43
|
private started: boolean
|
|
52
|
-
private running: boolean
|
|
53
44
|
private timeoutId?: NodeJS.Timer
|
|
54
45
|
private controller?: AbortController
|
|
55
46
|
private initialQuerySelfHasRun?: DeferredPromise<void>
|
|
47
|
+
private querySelfPromise?: DeferredPromise<void>
|
|
56
48
|
|
|
57
|
-
constructor (components:
|
|
49
|
+
constructor (components: QuerySelfComponents, init: QuerySelfInit) {
|
|
58
50
|
const { peerRouting, lan, count, interval, queryTimeout, routingTable } = init
|
|
59
51
|
|
|
60
52
|
this.components = components
|
|
61
53
|
this.log = logger(`libp2p:kad-dht:${lan ? 'lan' : 'wan'}:query-self`)
|
|
62
|
-
this.running = false
|
|
63
54
|
this.started = false
|
|
64
55
|
this.peerRouting = peerRouting
|
|
65
56
|
this.routingTable = routingTable
|
|
@@ -68,25 +59,28 @@ export class QuerySelf implements Startable {
|
|
|
68
59
|
this.initialInterval = init.initialInterval ?? QUERY_SELF_INITIAL_INTERVAL
|
|
69
60
|
this.queryTimeout = queryTimeout ?? QUERY_SELF_TIMEOUT
|
|
70
61
|
this.initialQuerySelfHasRun = init.initialQuerySelfHasRun
|
|
71
|
-
|
|
72
|
-
this.querySelf = debounce(this.querySelf.bind(this), 100)
|
|
73
62
|
}
|
|
74
63
|
|
|
75
64
|
isStarted (): boolean {
|
|
76
65
|
return this.started
|
|
77
66
|
}
|
|
78
67
|
|
|
79
|
-
|
|
68
|
+
start (): void {
|
|
80
69
|
if (this.started) {
|
|
81
70
|
return
|
|
82
71
|
}
|
|
83
72
|
|
|
84
73
|
this.started = true
|
|
85
74
|
clearTimeout(this.timeoutId)
|
|
86
|
-
this.timeoutId = setTimeout(
|
|
75
|
+
this.timeoutId = setTimeout(() => {
|
|
76
|
+
this.querySelf()
|
|
77
|
+
.catch(err => {
|
|
78
|
+
this.log.error('error running self-query', err)
|
|
79
|
+
})
|
|
80
|
+
}, this.initialInterval)
|
|
87
81
|
}
|
|
88
82
|
|
|
89
|
-
|
|
83
|
+
stop (): void {
|
|
90
84
|
this.started = false
|
|
91
85
|
|
|
92
86
|
if (this.timeoutId != null) {
|
|
@@ -98,84 +92,72 @@ export class QuerySelf implements Startable {
|
|
|
98
92
|
}
|
|
99
93
|
}
|
|
100
94
|
|
|
101
|
-
querySelf (): void {
|
|
95
|
+
async querySelf (): Promise<void> {
|
|
102
96
|
if (!this.started) {
|
|
103
97
|
this.log('skip self-query because we are not started')
|
|
104
98
|
return
|
|
105
99
|
}
|
|
106
100
|
|
|
107
|
-
if (this.
|
|
108
|
-
this.log('
|
|
109
|
-
return
|
|
101
|
+
if (this.querySelfPromise != null) {
|
|
102
|
+
this.log('joining existing self query')
|
|
103
|
+
return this.querySelfPromise.promise
|
|
110
104
|
}
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
let nextInterval = this.interval
|
|
114
|
-
|
|
115
|
-
if (this.initialQuerySelfHasRun != null) {
|
|
116
|
-
// if we've not yet run the first self query, shorten the interval until we try again
|
|
117
|
-
nextInterval = this.initialInterval
|
|
118
|
-
}
|
|
106
|
+
this.querySelfPromise = pDefer()
|
|
119
107
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return
|
|
108
|
+
if (this.routingTable.size === 0) {
|
|
109
|
+
// wait to discover at least one DHT peer
|
|
110
|
+
await pEvent(this.routingTable, 'peer:add')
|
|
124
111
|
}
|
|
125
112
|
|
|
126
|
-
this.
|
|
113
|
+
if (this.started) {
|
|
114
|
+
this.controller = new AbortController()
|
|
115
|
+
const signal = anySignal([this.controller.signal, AbortSignal.timeout(this.queryTimeout)])
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
return
|
|
117
|
+
// this controller will get used for lots of dial attempts so make sure we don't cause warnings to be logged
|
|
118
|
+
try {
|
|
119
|
+
if (setMaxListeners != null) {
|
|
120
|
+
setMaxListeners(Infinity, signal)
|
|
133
121
|
}
|
|
122
|
+
} catch {} // fails on node < 15.4
|
|
134
123
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.log('run self-query, look for %d peers timing out after %dms', this.count, this.queryTimeout)
|
|
147
|
-
|
|
148
|
-
const found = await pipe(
|
|
149
|
-
this.peerRouting.getClosestPeers(this.components.peerId.toBytes(), {
|
|
150
|
-
signal,
|
|
151
|
-
isSelfQuery: true
|
|
152
|
-
}),
|
|
153
|
-
(source) => take(source, this.count),
|
|
154
|
-
async (source) => length(source)
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
this.log('self-query ran successfully - found %d peers', found)
|
|
158
|
-
|
|
159
|
-
if (this.initialQuerySelfHasRun != null) {
|
|
160
|
-
this.initialQuerySelfHasRun.resolve()
|
|
161
|
-
this.initialQuerySelfHasRun = undefined
|
|
162
|
-
}
|
|
163
|
-
} catch (err: any) {
|
|
164
|
-
this.log.error('self-query error', err)
|
|
165
|
-
} finally {
|
|
166
|
-
signal.clear()
|
|
167
|
-
}
|
|
168
|
-
}).catch(err => {
|
|
169
|
-
this.log('self-query error', err)
|
|
170
|
-
}).finally(() => {
|
|
171
|
-
this.running = false
|
|
124
|
+
try {
|
|
125
|
+
this.log('run self-query, look for %d peers timing out after %dms', this.count, this.queryTimeout)
|
|
126
|
+
|
|
127
|
+
const found = await pipe(
|
|
128
|
+
this.peerRouting.getClosestPeers(this.components.peerId.toBytes(), {
|
|
129
|
+
signal,
|
|
130
|
+
isSelfQuery: true
|
|
131
|
+
}),
|
|
132
|
+
(source) => take(source, this.count),
|
|
133
|
+
async (source) => length(source)
|
|
134
|
+
)
|
|
172
135
|
|
|
173
|
-
|
|
136
|
+
this.log('self-query ran successfully - found %d peers', found)
|
|
174
137
|
|
|
175
|
-
if (this.
|
|
176
|
-
this.
|
|
177
|
-
this.
|
|
138
|
+
if (this.initialQuerySelfHasRun != null) {
|
|
139
|
+
this.initialQuerySelfHasRun.resolve()
|
|
140
|
+
this.initialQuerySelfHasRun = undefined
|
|
178
141
|
}
|
|
179
|
-
})
|
|
142
|
+
} catch (err: any) {
|
|
143
|
+
this.log.error('self-query error', err)
|
|
144
|
+
} finally {
|
|
145
|
+
signal.clear()
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.querySelfPromise.resolve()
|
|
150
|
+
this.querySelfPromise = undefined
|
|
151
|
+
|
|
152
|
+
if (!this.started) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.timeoutId = setTimeout(() => {
|
|
157
|
+
this.querySelf()
|
|
158
|
+
.catch(err => {
|
|
159
|
+
this.log.error('error running self-query', err)
|
|
160
|
+
})
|
|
161
|
+
}, this.interval)
|
|
180
162
|
}
|
|
181
163
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Record
|
|
3
|
+
} from './record.js'
|
|
4
|
+
import * as utils from './utils.js'
|
|
5
|
+
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
6
|
+
|
|
7
|
+
export class Libp2pRecord {
|
|
8
|
+
public key: Uint8Array
|
|
9
|
+
public value: Uint8Array
|
|
10
|
+
public timeReceived: Date
|
|
11
|
+
|
|
12
|
+
constructor (key: Uint8Array, value: Uint8Array, timeReceived: Date) {
|
|
13
|
+
if (!(key instanceof Uint8Array)) {
|
|
14
|
+
throw new Error('key must be a Uint8Array')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!(value instanceof Uint8Array)) {
|
|
18
|
+
throw new Error('value must be a Uint8Array')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.key = key
|
|
22
|
+
this.value = value
|
|
23
|
+
this.timeReceived = timeReceived
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
serialize (): Uint8Array {
|
|
27
|
+
return Record.encode(this.prepareSerialize())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Return the object format ready to be given to the protobuf library.
|
|
32
|
+
*/
|
|
33
|
+
prepareSerialize (): Record {
|
|
34
|
+
return {
|
|
35
|
+
key: this.key,
|
|
36
|
+
value: this.value,
|
|
37
|
+
timeReceived: utils.toRFC3339(this.timeReceived)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Decode a protobuf encoded record
|
|
43
|
+
*/
|
|
44
|
+
static deserialize (raw: Uint8Array | Uint8ArrayList): Libp2pRecord {
|
|
45
|
+
const rec = Record.decode(raw)
|
|
46
|
+
|
|
47
|
+
return new Libp2pRecord(rec.key, rec.value, new Date(rec.timeReceived))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a record from the raw object returned from the protobuf library
|
|
52
|
+
*/
|
|
53
|
+
static fromDeserialized (obj: Record): Libp2pRecord {
|
|
54
|
+
const recvtime = utils.parseRFC3339(obj.timeReceived)
|
|
55
|
+
|
|
56
|
+
if (obj.key == null) {
|
|
57
|
+
throw new Error('key missing from deserialized object')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (obj.value == null) {
|
|
61
|
+
throw new Error('value missing from deserialized object')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const rec = new Libp2pRecord(
|
|
65
|
+
obj.key, obj.value, recvtime
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return rec
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
// Record represents a dht record that contains a value
|
|
4
|
+
// for a key value pair
|
|
5
|
+
message Record {
|
|
6
|
+
// The key that references this record
|
|
7
|
+
bytes key = 1;
|
|
8
|
+
|
|
9
|
+
// The actual value this record is storing
|
|
10
|
+
bytes value = 2;
|
|
11
|
+
|
|
12
|
+
// Note: These fields were removed from the Record message
|
|
13
|
+
// hash of the authors public key
|
|
14
|
+
// optional bytes author = 3;
|
|
15
|
+
// A PKI signature for the key+value+author
|
|
16
|
+
// optional bytes signature = 4;
|
|
17
|
+
|
|
18
|
+
// Time the record was received, set by receiver
|
|
19
|
+
string timeReceived = 5;
|
|
20
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* eslint-disable import/export */
|
|
2
|
+
/* eslint-disable complexity */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
6
|
+
|
|
7
|
+
import { encodeMessage, decodeMessage, message } from 'protons-runtime'
|
|
8
|
+
import type { Codec } from 'protons-runtime'
|
|
9
|
+
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
10
|
+
|
|
11
|
+
export interface Record {
|
|
12
|
+
key: Uint8Array
|
|
13
|
+
value: Uint8Array
|
|
14
|
+
timeReceived: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export namespace Record {
|
|
18
|
+
let _codec: Codec<Record>
|
|
19
|
+
|
|
20
|
+
export const codec = (): Codec<Record> => {
|
|
21
|
+
if (_codec == null) {
|
|
22
|
+
_codec = message<Record>((obj, w, opts = {}) => {
|
|
23
|
+
if (opts.lengthDelimited !== false) {
|
|
24
|
+
w.fork()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if ((obj.key != null && obj.key.byteLength > 0)) {
|
|
28
|
+
w.uint32(10)
|
|
29
|
+
w.bytes(obj.key)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if ((obj.value != null && obj.value.byteLength > 0)) {
|
|
33
|
+
w.uint32(18)
|
|
34
|
+
w.bytes(obj.value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if ((obj.timeReceived != null && obj.timeReceived !== '')) {
|
|
38
|
+
w.uint32(42)
|
|
39
|
+
w.string(obj.timeReceived)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (opts.lengthDelimited !== false) {
|
|
43
|
+
w.ldelim()
|
|
44
|
+
}
|
|
45
|
+
}, (reader, length) => {
|
|
46
|
+
const obj: any = {
|
|
47
|
+
key: new Uint8Array(0),
|
|
48
|
+
value: new Uint8Array(0),
|
|
49
|
+
timeReceived: ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const end = length == null ? reader.len : reader.pos + length
|
|
53
|
+
|
|
54
|
+
while (reader.pos < end) {
|
|
55
|
+
const tag = reader.uint32()
|
|
56
|
+
|
|
57
|
+
switch (tag >>> 3) {
|
|
58
|
+
case 1:
|
|
59
|
+
obj.key = reader.bytes()
|
|
60
|
+
break
|
|
61
|
+
case 2:
|
|
62
|
+
obj.value = reader.bytes()
|
|
63
|
+
break
|
|
64
|
+
case 5:
|
|
65
|
+
obj.timeReceived = reader.string()
|
|
66
|
+
break
|
|
67
|
+
default:
|
|
68
|
+
reader.skipType(tag & 7)
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return obj
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return _codec
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const encode = (obj: Partial<Record>): Uint8Array => {
|
|
81
|
+
return encodeMessage(obj, Record.codec())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList): Record => {
|
|
85
|
+
return decodeMessage(buf, Record.codec())
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CodeError } from '@libp2p/interface/errors'
|
|
2
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
3
|
+
import type { Selectors } from '../index.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Select the best record out of the given records
|
|
7
|
+
*/
|
|
8
|
+
export function bestRecord (selectors: Selectors, k: Uint8Array, records: Uint8Array[]): number {
|
|
9
|
+
if (records.length === 0) {
|
|
10
|
+
const errMsg = 'No records given'
|
|
11
|
+
|
|
12
|
+
throw new CodeError(errMsg, 'ERR_NO_RECORDS_RECEIVED')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const kStr = uint8ArrayToString(k)
|
|
16
|
+
const parts = kStr.split('/')
|
|
17
|
+
|
|
18
|
+
if (parts.length < 3) {
|
|
19
|
+
const errMsg = 'Record key does not have a selector function'
|
|
20
|
+
|
|
21
|
+
throw new CodeError(errMsg, 'ERR_NO_SELECTOR_FUNCTION_FOR_RECORD_KEY')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const selector = selectors[parts[1].toString()]
|
|
25
|
+
|
|
26
|
+
if (selector == null) {
|
|
27
|
+
const errMsg = `Unrecognized key prefix: ${parts[1]}`
|
|
28
|
+
|
|
29
|
+
throw new CodeError(errMsg, 'ERR_UNRECOGNIZED_KEY_PREFIX')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (records.length === 1) {
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return selector(k, records)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Best record selector, for public key records.
|
|
41
|
+
* Simply returns the first record, as all valid public key
|
|
42
|
+
* records are equal
|
|
43
|
+
*/
|
|
44
|
+
function publickKey (k: Uint8Array, records: Uint8Array[]): number {
|
|
45
|
+
return 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const selectors: Selectors = {
|
|
49
|
+
pk: publickKey
|
|
50
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a JavaScript date into an `RFC3339Nano` formatted
|
|
3
|
+
* string
|
|
4
|
+
*/
|
|
5
|
+
export function toRFC3339 (time: Date): string {
|
|
6
|
+
const year = time.getUTCFullYear()
|
|
7
|
+
const month = String(time.getUTCMonth() + 1).padStart(2, '0')
|
|
8
|
+
const day = String(time.getUTCDate()).padStart(2, '0')
|
|
9
|
+
const hour = String(time.getUTCHours()).padStart(2, '0')
|
|
10
|
+
const minute = String(time.getUTCMinutes()).padStart(2, '0')
|
|
11
|
+
const seconds = String(time.getUTCSeconds()).padStart(2, '0')
|
|
12
|
+
const milliseconds = time.getUTCMilliseconds()
|
|
13
|
+
const nanoseconds = String(milliseconds * 1000 * 1000).padStart(9, '0')
|
|
14
|
+
|
|
15
|
+
return `${year}-${month}-${day}T${hour}:${minute}:${seconds}.${nanoseconds}Z`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parses a date string formatted as `RFC3339Nano` into a
|
|
20
|
+
* JavaScript Date object
|
|
21
|
+
*/
|
|
22
|
+
export function parseRFC3339 (time: string): Date {
|
|
23
|
+
const rfc3339Matcher = new RegExp(
|
|
24
|
+
// 2006-01-02T
|
|
25
|
+
'(\\d{4})-(\\d{2})-(\\d{2})T' +
|
|
26
|
+
// 15:04:05
|
|
27
|
+
'(\\d{2}):(\\d{2}):(\\d{2})' +
|
|
28
|
+
// .999999999Z
|
|
29
|
+
'\\.(\\d+)Z'
|
|
30
|
+
)
|
|
31
|
+
const m = String(time).trim().match(rfc3339Matcher)
|
|
32
|
+
|
|
33
|
+
if (m == null) {
|
|
34
|
+
throw new Error('Invalid format')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const year = parseInt(m[1], 10)
|
|
38
|
+
const month = parseInt(m[2], 10) - 1
|
|
39
|
+
const date = parseInt(m[3], 10)
|
|
40
|
+
const hour = parseInt(m[4], 10)
|
|
41
|
+
const minute = parseInt(m[5], 10)
|
|
42
|
+
const second = parseInt(m[6], 10)
|
|
43
|
+
const millisecond = parseInt(m[7].slice(0, -6), 10)
|
|
44
|
+
|
|
45
|
+
return new Date(Date.UTC(year, month, date, hour, minute, second, millisecond))
|
|
46
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { CodeError } from '@libp2p/interface/errors'
|
|
2
|
+
import { sha256 } from 'multiformats/hashes/sha2'
|
|
3
|
+
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
4
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
5
|
+
import type { Libp2pRecord } from './index.js'
|
|
6
|
+
import type { Validators } from '../index.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks a record and ensures it is still valid.
|
|
10
|
+
* It runs the needed validators.
|
|
11
|
+
* If verification fails the returned Promise will reject with the error.
|
|
12
|
+
*/
|
|
13
|
+
export async function verifyRecord (validators: Validators, record: Libp2pRecord): Promise<void> {
|
|
14
|
+
const key = record.key
|
|
15
|
+
const keyString = uint8ArrayToString(key)
|
|
16
|
+
const parts = keyString.split('/')
|
|
17
|
+
|
|
18
|
+
if (parts.length < 3) {
|
|
19
|
+
// No validator available
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validator = validators[parts[1].toString()]
|
|
24
|
+
|
|
25
|
+
if (validator == null) {
|
|
26
|
+
const errMsg = 'Invalid record keytype'
|
|
27
|
+
|
|
28
|
+
throw new CodeError(errMsg, 'ERR_INVALID_RECORD_KEY_TYPE')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await validator(key, record.value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validator for public key records.
|
|
36
|
+
* Verifies that the passed in record value is the PublicKey
|
|
37
|
+
* that matches the passed in key.
|
|
38
|
+
* If validation fails the returned Promise will reject with the error.
|
|
39
|
+
*
|
|
40
|
+
* @param {Uint8Array} key - A valid key is of the form `'/pk/<keymultihash>'`
|
|
41
|
+
* @param {Uint8Array} publicKey - The public key to validate against (protobuf encoded).
|
|
42
|
+
*/
|
|
43
|
+
const validatePublicKeyRecord = async (key: Uint8Array, publicKey: Uint8Array): Promise<void> => {
|
|
44
|
+
if (!(key instanceof Uint8Array)) {
|
|
45
|
+
throw new CodeError('"key" must be a Uint8Array', 'ERR_INVALID_RECORD_KEY_NOT_BUFFER')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (key.byteLength < 5) {
|
|
49
|
+
throw new CodeError('invalid public key record', 'ERR_INVALID_RECORD_KEY_TOO_SHORT')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const prefix = uint8ArrayToString(key.subarray(0, 4))
|
|
53
|
+
|
|
54
|
+
if (prefix !== '/pk/') {
|
|
55
|
+
throw new CodeError('key was not prefixed with /pk/', 'ERR_INVALID_RECORD_KEY_BAD_PREFIX')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const keyhash = key.slice(4)
|
|
59
|
+
|
|
60
|
+
const publicKeyHash = await sha256.digest(publicKey)
|
|
61
|
+
|
|
62
|
+
if (!uint8ArrayEquals(keyhash, publicKeyHash.bytes)) {
|
|
63
|
+
throw new CodeError('public key does not match passed in key', 'ERR_INVALID_RECORD_HASH_MISMATCH')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const validators: Validators = {
|
|
68
|
+
pk: validatePublicKeyRecord
|
|
69
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { EventEmitter } from '@libp2p/interface/events'
|
|
1
2
|
import { logger } from '@libp2p/logger'
|
|
2
3
|
import { PeerSet } from '@libp2p/peer-collections'
|
|
3
4
|
import Queue from 'p-queue'
|
|
4
5
|
import * as utils from '../utils.js'
|
|
5
6
|
import { KBucket, type PingEventDetails } from './k-bucket.js'
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
7
|
+
import type { Metric, Metrics } from '@libp2p/interface/metrics'
|
|
8
|
+
import type { PeerId } from '@libp2p/interface/peer-id'
|
|
9
|
+
import type { PeerStore } from '@libp2p/interface/peer-store'
|
|
10
|
+
import type { Startable } from '@libp2p/interface/startable'
|
|
11
|
+
import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
|
|
11
12
|
import type { Logger } from '@libp2p/logger'
|
|
12
13
|
|
|
13
14
|
export const KAD_CLOSE_TAG_NAME = 'kad-close'
|
|
@@ -33,11 +34,16 @@ export interface RoutingTableComponents {
|
|
|
33
34
|
metrics?: Metrics
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export interface RoutingTableEvents {
|
|
38
|
+
'peer:add': CustomEvent<PeerId>
|
|
39
|
+
'peer:remove': CustomEvent<PeerId>
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* A wrapper around `k-bucket`, to provide easy store and
|
|
38
44
|
* retrieval for peers.
|
|
39
45
|
*/
|
|
40
|
-
export class RoutingTable implements Startable {
|
|
46
|
+
export class RoutingTable extends EventEmitter<RoutingTableEvents> implements Startable {
|
|
41
47
|
public kBucketSize: number
|
|
42
48
|
public kb?: KBucket
|
|
43
49
|
public pingQueue: Queue
|
|
@@ -58,6 +64,8 @@ export class RoutingTable implements Startable {
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
constructor (components: RoutingTableComponents, init: RoutingTableInit) {
|
|
67
|
+
super()
|
|
68
|
+
|
|
61
69
|
const { kBucketSize, pingTimeout, lan, pingConcurrency, protocol, tagName, tagValue } = init
|
|
62
70
|
|
|
63
71
|
this.components = components
|
|
@@ -160,11 +168,15 @@ export class RoutingTable implements Startable {
|
|
|
160
168
|
kClosest = newClosest
|
|
161
169
|
})
|
|
162
170
|
|
|
163
|
-
kBuck.addEventListener('added', () => {
|
|
171
|
+
kBuck.addEventListener('added', (evt) => {
|
|
164
172
|
updatePeerTags()
|
|
173
|
+
|
|
174
|
+
this.safeDispatchEvent('peer:add', { detail: evt.detail.peer })
|
|
165
175
|
})
|
|
166
|
-
kBuck.addEventListener('removed', () => {
|
|
176
|
+
kBuck.addEventListener('removed', (evt) => {
|
|
167
177
|
updatePeerTags()
|
|
178
|
+
|
|
179
|
+
this.safeDispatchEvent('peer:remove', { detail: evt.detail.peer })
|
|
168
180
|
})
|
|
169
181
|
}
|
|
170
182
|
|
|
@@ -27,8 +27,8 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
|
27
27
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import { EventEmitter } from '@libp2p/
|
|
31
|
-
import type { PeerId } from '@libp2p/interface
|
|
30
|
+
import { EventEmitter } from '@libp2p/interface/events'
|
|
31
|
+
import type { PeerId } from '@libp2p/interface/peer-id'
|
|
32
32
|
|
|
33
33
|
function arrayEquals (array1: Uint8Array, array2: Uint8Array): boolean {
|
|
34
34
|
if (array1 === array2) {
|