@libp2p/kad-dht 9.1.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,9 +13,9 @@ import { Libp2pRecord } from '@libp2p/record'
13
13
  import { logger } from '@libp2p/logger'
14
14
  import { keys } from '@libp2p/crypto'
15
15
  import { peerIdFromKeys } from '@libp2p/peer-id'
16
- import type { DHTRecord, DialingPeerEvent, FinalPeerEvent, QueryEvent, QueryOptions, Validators } from '@libp2p/interface-dht'
16
+ import type { DHTRecord, DialingPeerEvent, FinalPeerEvent, QueryEvent, Validators } from '@libp2p/interface-dht'
17
17
  import type { RoutingTable } from '../routing-table/index.js'
18
- import type { QueryManager } from '../query/manager.js'
18
+ import type { QueryManager, QueryOptions } from '../query/manager.js'
19
19
  import type { Network } from '../network.js'
20
20
  import type { Logger } from '@libp2p/logger'
21
21
  import type { AbortOptions } from '@libp2p/interfaces'
@@ -11,9 +11,12 @@ import { logger } from '@libp2p/logger'
11
11
  import type { PeerId } from '@libp2p/interface-peer-id'
12
12
  import type { Startable } from '@libp2p/interfaces/startable'
13
13
  import type { QueryFunc } from './types.js'
14
- import type { QueryEvent, QueryOptions } from '@libp2p/interface-dht'
14
+ import type { QueryEvent } from '@libp2p/interface-dht'
15
15
  import { PeerSet } from '@libp2p/peer-collections'
16
16
  import type { Metric, Metrics } from '@libp2p/interface-metrics'
17
+ import type { DeferredPromise } from 'p-defer'
18
+ import type { AbortOptions } from '@libp2p/interfaces'
19
+ import { AbortError } from '@libp2p/interfaces/errors'
17
20
 
18
21
  export interface CleanUpEvents {
19
22
  'cleanup': CustomEvent
@@ -23,6 +26,7 @@ export interface QueryManagerInit {
23
26
  lan?: boolean
24
27
  disjointPaths?: number
25
28
  alpha?: number
29
+ initialQuerySelfHasRun: DeferredPromise<void>
26
30
  }
27
31
 
28
32
  export interface QueryManagerComponents {
@@ -30,6 +34,11 @@ export interface QueryManagerComponents {
30
34
  metrics?: Metrics
31
35
  }
32
36
 
37
+ export interface QueryOptions extends AbortOptions {
38
+ queryFuncTimeout?: number
39
+ isSelfQuery?: boolean
40
+ }
41
+
33
42
  /**
34
43
  * Keeps track of all running queries
35
44
  */
@@ -46,6 +55,8 @@ export class QueryManager implements Startable {
46
55
  queryTime: Metric
47
56
  }
48
57
 
58
+ private initialQuerySelfHasRun?: DeferredPromise<void>
59
+
49
60
  constructor (components: QueryManagerComponents, init: QueryManagerInit) {
50
61
  const { lan = false, disjointPaths = K, alpha = ALPHA } = init
51
62
 
@@ -55,6 +66,7 @@ export class QueryManager implements Startable {
55
66
  this.alpha = alpha ?? ALPHA
56
67
  this.lan = lan
57
68
  this.queries = 0
69
+ this.initialQuerySelfHasRun = init.initialQuerySelfHasRun
58
70
 
59
71
  // allow us to stop queries on shut down
60
72
  this.shutDownController = new AbortController()
@@ -131,6 +143,21 @@ export class QueryManager implements Startable {
131
143
  const cleanUp = new EventEmitter<CleanUpEvents>()
132
144
 
133
145
  try {
146
+ if (options.isSelfQuery !== true && this.initialQuerySelfHasRun != null) {
147
+ log('waiting for initial query-self query before continuing')
148
+
149
+ await Promise.race([
150
+ new Promise((resolve, reject) => {
151
+ signal.addEventListener('abort', () => {
152
+ reject(new AbortError('Query was aborted before self-query ran'))
153
+ })
154
+ }),
155
+ this.initialQuerySelfHasRun.promise
156
+ ])
157
+
158
+ this.initialQuerySelfHasRun = undefined
159
+ }
160
+
134
161
  log('query:start')
135
162
  this.queries++
136
163
  this.metrics?.runningQueries.update(this.queries)
package/src/query-self.ts CHANGED
@@ -1,20 +1,39 @@
1
1
  import { setMaxListeners } from 'events'
2
2
  import take from 'it-take'
3
3
  import length from 'it-length'
4
- import { QUERY_SELF_INTERVAL, QUERY_SELF_TIMEOUT, K } from './constants.js'
4
+ import { QUERY_SELF_INTERVAL, QUERY_SELF_TIMEOUT, K, QUERY_SELF_INITIAL_INTERVAL } from './constants.js'
5
5
  import { anySignal } from 'any-signal'
6
6
  import { logger, Logger } from '@libp2p/logger'
7
7
  import type { PeerRouting } from './peer-routing/index.js'
8
8
  import type { Startable } from '@libp2p/interfaces/startable'
9
9
  import { pipe } from 'it-pipe'
10
10
  import type { KadDHTComponents } from './index.js'
11
+ import type { DeferredPromise } from 'p-defer'
12
+ import type { RoutingTable } from './routing-table/index.js'
11
13
 
12
14
  export interface QuerySelfInit {
13
15
  lan: boolean
14
16
  peerRouting: PeerRouting
17
+ routingTable: RoutingTable
15
18
  count?: number
16
19
  interval?: number
20
+ initialInterval?: number
17
21
  queryTimeout?: number
22
+ initialQuerySelfHasRun: DeferredPromise<void>
23
+ }
24
+
25
+ function debounce (func: () => void, wait: number): () => void {
26
+ let timeout: ReturnType<typeof setTimeout> | undefined
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
+ }
18
37
  }
19
38
 
20
39
  /**
@@ -24,40 +43,51 @@ export class QuerySelf implements Startable {
24
43
  private readonly log: Logger
25
44
  private readonly components: KadDHTComponents
26
45
  private readonly peerRouting: PeerRouting
46
+ private readonly routingTable: RoutingTable
27
47
  private readonly count: number
28
48
  private readonly interval: number
49
+ private readonly initialInterval: number
29
50
  private readonly queryTimeout: number
51
+ private started: boolean
30
52
  private running: boolean
31
53
  private timeoutId?: NodeJS.Timer
32
54
  private controller?: AbortController
55
+ private initialQuerySelfHasRun?: DeferredPromise<void>
33
56
 
34
57
  constructor (components: KadDHTComponents, init: QuerySelfInit) {
35
- const { peerRouting, lan, count, interval, queryTimeout } = init
58
+ const { peerRouting, lan, count, interval, queryTimeout, routingTable } = init
36
59
 
37
60
  this.components = components
38
61
  this.log = logger(`libp2p:kad-dht:${lan ? 'lan' : 'wan'}:query-self`)
39
62
  this.running = false
63
+ this.started = false
40
64
  this.peerRouting = peerRouting
65
+ this.routingTable = routingTable
41
66
  this.count = count ?? K
42
67
  this.interval = interval ?? QUERY_SELF_INTERVAL
68
+ this.initialInterval = init.initialInterval ?? QUERY_SELF_INITIAL_INTERVAL
43
69
  this.queryTimeout = queryTimeout ?? QUERY_SELF_TIMEOUT
70
+ this.initialQuerySelfHasRun = init.initialQuerySelfHasRun
71
+
72
+ this.querySelf = debounce(this.querySelf.bind(this), 100)
44
73
  }
45
74
 
46
75
  isStarted (): boolean {
47
- return this.running
76
+ return this.started
48
77
  }
49
78
 
50
79
  async start (): Promise<void> {
51
- if (this.running) {
80
+ if (this.started) {
52
81
  return
53
82
  }
54
83
 
55
- this.running = true
56
- this._querySelf()
84
+ this.started = true
85
+ clearTimeout(this.timeoutId)
86
+ this.timeoutId = setTimeout(this.querySelf.bind(this), this.initialInterval)
57
87
  }
58
88
 
59
89
  async stop (): Promise<void> {
60
- this.running = false
90
+ this.started = false
61
91
 
62
92
  if (this.timeoutId != null) {
63
93
  clearTimeout(this.timeoutId)
@@ -68,36 +98,76 @@ export class QuerySelf implements Startable {
68
98
  }
69
99
  }
70
100
 
71
- _querySelf (): void {
72
- Promise.resolve().then(async () => {
73
- this.controller = new AbortController()
74
- const signal = anySignal([this.controller.signal, AbortSignal.timeout(this.queryTimeout)])
101
+ querySelf (): void {
102
+ if (!this.started) {
103
+ this.log('skip self-query because we are not started')
104
+ return
105
+ }
75
106
 
76
- // this controller will get used for lots of dial attempts so make sure we don't cause warnings to be logged
77
- try {
78
- if (setMaxListeners != null) {
79
- setMaxListeners(Infinity, signal)
80
- }
81
- } catch {} // fails on node < 15.4
82
-
83
- try {
84
- const found = await pipe(
85
- this.peerRouting.getClosestPeers(this.components.peerId.toBytes(), {
86
- signal
87
- }),
88
- (source) => take(source, this.count),
89
- async (source) => await length(source)
90
- )
91
-
92
- this.log('query ran successfully - found %d peers', found)
93
- } catch (err: any) {
94
- this.log('query error', err)
95
- } finally {
96
- this.timeoutId = setTimeout(this._querySelf.bind(this), this.interval)
97
- signal.clear()
107
+ if (this.running) {
108
+ this.log('skip self-query because we are already running, will run again in %dms', this.interval)
109
+ return
110
+ }
111
+
112
+ if (this.routingTable.size === 0) {
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
98
118
  }
99
- }).catch(err => {
100
- this.log('query error', err)
101
- })
119
+
120
+ this.log('skip self-query because routing table is empty, will run again in %dms', nextInterval)
121
+ clearTimeout(this.timeoutId)
122
+ this.timeoutId = setTimeout(this.querySelf.bind(this), nextInterval)
123
+ return
124
+ }
125
+
126
+ this.running = true
127
+
128
+ Promise.resolve()
129
+ .then(async () => {
130
+ this.controller = new AbortController()
131
+ const signal = anySignal([this.controller.signal, AbortSignal.timeout(this.queryTimeout)])
132
+
133
+ // this controller will get used for lots of dial attempts so make sure we don't cause warnings to be logged
134
+ try {
135
+ if (setMaxListeners != null) {
136
+ setMaxListeners(Infinity, signal)
137
+ }
138
+ } catch {} // fails on node < 15.4
139
+
140
+ try {
141
+ this.log('run self-query, look for %d peers timing out after %dms', this.count, this.queryTimeout)
142
+
143
+ const found = await pipe(
144
+ this.peerRouting.getClosestPeers(this.components.peerId.toBytes(), {
145
+ signal,
146
+ isSelfQuery: true
147
+ }),
148
+ (source) => take(source, this.count),
149
+ async (source) => await length(source)
150
+ )
151
+
152
+ this.log('self-query ran successfully - found %d peers', found)
153
+
154
+ if (this.initialQuerySelfHasRun != null) {
155
+ this.initialQuerySelfHasRun.resolve()
156
+ this.initialQuerySelfHasRun = undefined
157
+ }
158
+ } catch (err: any) {
159
+ this.log.error('self-query error', err)
160
+ } finally {
161
+ signal.clear()
162
+ }
163
+ }).catch(err => {
164
+ this.log('self-query error', err)
165
+ }).finally(() => {
166
+ this.running = false
167
+
168
+ this.log('running self-query again in %dms', this.interval)
169
+ clearTimeout(this.timeoutId)
170
+ this.timeoutId = setTimeout(this.querySelf.bind(this), this.interval)
171
+ })
102
172
  }
103
173
  }