@helia/ipns 9.1.9-11802dd6 → 9.1.9-1a2ebead

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.
@@ -1,6 +1,8 @@
1
- import type { IPNSRouting } from './index.ts';
2
- import type { PeerId, TypedEventTarget, ComponentLogger } from '@libp2p/interface';
1
+ import type { GetOptions, IPNSRouting, PutOptions } from './index.ts';
2
+ import type { Fetch } from '@libp2p/fetch';
3
+ import type { PeerId, PublicKey, TypedEventTarget, ComponentLogger, Startable, Metrics, Libp2p } from '@libp2p/interface';
3
4
  import type { Datastore } from 'interface-datastore';
5
+ import type { MultihashDigest } from 'multiformats/hashes/interface';
4
6
  import type { ProgressEvent } from 'progress-events';
5
7
  export interface Message {
6
8
  type: 'signed' | 'unsigned';
@@ -8,7 +10,16 @@ export interface Message {
8
10
  topic: string;
9
11
  data: Uint8Array;
10
12
  }
13
+ export interface Subscription {
14
+ topic: string;
15
+ subscribe: boolean;
16
+ }
17
+ export interface SubscriptionChangeData {
18
+ peerId: PeerId;
19
+ subscriptions: Subscription[];
20
+ }
11
21
  export interface PubSubEvents {
22
+ 'subscription-change': CustomEvent<SubscriptionChangeData>;
12
23
  message: CustomEvent<Message>;
13
24
  }
14
25
  export interface PublishResult {
@@ -24,12 +35,31 @@ export interface PubSub extends TypedEventTarget<PubSubEvents> {
24
35
  export interface PubsubRoutingComponents {
25
36
  datastore: Datastore;
26
37
  logger: ComponentLogger;
27
- libp2p: {
28
- peerId: PeerId;
29
- services: {
30
- pubsub: PubSub;
31
- };
32
- };
38
+ metrics?: Metrics;
39
+ libp2p: Pick<Libp2p<{
40
+ pubsub: PubSub;
41
+ fetch?: Fetch;
42
+ }>, 'peerId' | 'register' | 'unregister' | 'services'>;
43
+ }
44
+ export interface PubsubRoutingInit {
45
+ /**
46
+ * How many fetch requests to run concurrently
47
+ *
48
+ * @default 8
49
+ */
50
+ fetchConcurrency?: number;
51
+ /**
52
+ * How long to allow a fetch request to run for in ms
53
+ *
54
+ * @default 2_500
55
+ */
56
+ fetchTimeout?: number;
57
+ /**
58
+ * How many ms to wait before sending a fetch request to a topic peer
59
+ *
60
+ * @default 0
61
+ */
62
+ fetchDelay?: number;
33
63
  }
34
64
  export type PubSubProgressEvents = ProgressEvent<'ipns:pubsub:publish', {
35
65
  topic: string;
@@ -37,6 +67,41 @@ export type PubSubProgressEvents = ProgressEvent<'ipns:pubsub:publish', {
37
67
  }> | ProgressEvent<'ipns:pubsub:subscribe', {
38
68
  topic: string;
39
69
  }> | ProgressEvent<'ipns:pubsub:error', Error>;
70
+ export declare class PubSubRouting implements IPNSRouting, Startable {
71
+ #private;
72
+ private readonly subscriptions;
73
+ private readonly localStore;
74
+ private readonly libp2p;
75
+ private readonly fetchConcurrency;
76
+ private readonly fetchTimeout;
77
+ private readonly fetchDelay;
78
+ private readonly fetchQueue;
79
+ private readonly fetchPeers;
80
+ private shutdownController;
81
+ private fetchTopologyId?;
82
+ constructor(components: PubsubRoutingComponents, init?: PubsubRoutingInit);
83
+ /**
84
+ * Put a value to the pubsub datastore indexed by the received key properly encoded
85
+ */
86
+ put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: PutOptions): Promise<void>;
87
+ /**
88
+ * Get a value from the pubsub datastore indexed by the received key properly encoded.
89
+ * Also, the identifier topic is subscribed to and the pubsub datastore records will be
90
+ * updated once new publishes occur
91
+ */
92
+ get(routingKey: Uint8Array, options?: GetOptions): Promise<Uint8Array>;
93
+ /**
94
+ * Get pubsub subscriptions related to ipns
95
+ */
96
+ getSubscriptions(): string[];
97
+ /**
98
+ * Cancel pubsub subscriptions related to ipns
99
+ */
100
+ cancel(key: PublicKey | MultihashDigest<0x00 | 0x12>): void;
101
+ toString(): string;
102
+ start(): Promise<void>;
103
+ stop(): void;
104
+ }
40
105
  /**
41
106
  * This IPNS routing receives IPNS record updates via dedicated
42
107
  * pubsub topic.
@@ -45,5 +110,5 @@ export type PubSubProgressEvents = ProgressEvent<'ipns:pubsub:publish', {
45
110
  * updated records, so the first call to `.get` should be expected
46
111
  * to fail!
47
112
  */
48
- export declare function pubsub(components: PubsubRoutingComponents): IPNSRouting;
113
+ export declare function pubsub(components: PubsubRoutingComponents, init?: PubsubRoutingInit): IPNSRouting;
49
114
  //# sourceMappingURL=pubsub.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pubsub.d.ts","sourceRoot":"","sources":["../../../src/routing/pubsub.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAc,WAAW,EAAc,MAAM,YAAY,CAAA;AAErE,OAAO,KAAK,EAAE,MAAM,EAAa,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC7F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAIpD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,MAAO,SAAQ,gBAAgB,CAAC,YAAY,CAAC;IAC5D,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IACnE,SAAS,IAAI,MAAM,EAAE,CAAA;IACrB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,SAAS,CAAA;IACpB,MAAM,EAAE,eAAe,CAAA;IACvB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE;YACR,MAAM,EAAE,MAAM,CAAA;SACf,CAAA;KACF,CAAA;CACF;AAED,MAAM,MAAM,oBAAoB,GAC9B,aAAa,CAAC,qBAAqB,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAC,GAC9E,aAAa,CAAC,uBAAuB,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACzD,aAAa,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAA;AAmK3C;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAE,UAAU,EAAE,uBAAuB,GAAG,WAAW,CAExE"}
1
+ {"version":3,"file":"pubsub.d.ts","sourceRoot":"","sources":["../../../src/routing/pubsub.ts"],"names":[],"mappings":"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"}
@@ -1,61 +1,172 @@
1
- import { isPublicKey } from '@libp2p/interface';
1
+ import { isPublicKey, NotFoundError, setMaxListeners } from '@libp2p/interface';
2
2
  import { logger } from '@libp2p/logger';
3
+ import { PeerSet } from '@libp2p/peer-collections';
4
+ import { Queue } from '@libp2p/utils';
5
+ import { anySignal } from 'any-signal';
6
+ import delay from 'delay';
3
7
  import { multihashToIPNSRoutingKey } from 'ipns';
4
8
  import { ipnsSelector } from 'ipns/selector';
5
9
  import { ipnsValidator } from 'ipns/validator';
6
10
  import { CustomProgressEvent } from 'progress-events';
11
+ import { raceSignal } from 'race-signal';
7
12
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals';
8
13
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
9
14
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
10
15
  import { InvalidTopicError } from "../errors.js";
11
16
  import { localStore } from "../local-store.js";
17
+ import { IPNS_STRING_PREFIX } from "../utils.js";
12
18
  const log = logger('helia:ipns:routing:pubsub');
13
- class PubSubRouting {
19
+ export class PubSubRouting {
14
20
  subscriptions;
15
21
  localStore;
16
- peerId;
17
- pubsub;
18
- constructor(components) {
19
- this.subscriptions = [];
22
+ libp2p;
23
+ fetchConcurrency;
24
+ fetchTimeout;
25
+ fetchDelay;
26
+ fetchQueue;
27
+ fetchPeers;
28
+ shutdownController;
29
+ fetchTopologyId;
30
+ constructor(components, init = {}) {
31
+ this.subscriptions = new Set();
32
+ this.shutdownController = new AbortController();
33
+ setMaxListeners(Infinity, this.shutdownController.signal);
34
+ this.fetchPeers = new PeerSet();
20
35
  this.localStore = localStore(components.datastore, components.logger.forComponent('helia:ipns:local-store'));
21
- this.peerId = components.libp2p.peerId;
22
- this.pubsub = components.libp2p.services.pubsub;
23
- this.pubsub.addEventListener('message', (evt) => {
36
+ this.libp2p = components.libp2p;
37
+ this.fetchConcurrency = init.fetchConcurrency ?? 8;
38
+ this.fetchDelay = init.fetchDelay ?? 0;
39
+ // default libp2p-fetch timeout is 10 seconds - we should have an existing
40
+ // connection to the peer so this can be shortened
41
+ this.fetchTimeout = init.fetchTimeout ?? 2_500;
42
+ this.fetchQueue = new Queue({
43
+ concurrency: this.fetchConcurrency,
44
+ metrics: components.metrics,
45
+ metricName: 'helia_ipns_pubsub_fetch_queue'
46
+ });
47
+ this.libp2p.services.pubsub.addEventListener('message', (evt) => {
24
48
  const message = evt.detail;
25
- if (!this.subscriptions.includes(message.topic)) {
49
+ if (!this.subscriptions.has(message.topic)) {
26
50
  return;
27
51
  }
28
- this.#processPubSubMessage(message).catch(err => {
52
+ this.#processPubSubMessage(message, {
53
+ signal: this.shutdownController.signal
54
+ }).catch(err => {
29
55
  log.error('Error processing message - %e', err);
30
56
  });
31
57
  });
58
+ // ipns over libp2p-fetch feature
59
+ if (this.libp2p.services.fetch != null) {
60
+ try {
61
+ this.libp2p.services.pubsub.addEventListener('subscription-change', (evt) => {
62
+ const { peerId, subscriptions } = evt.detail;
63
+ if (!this.fetchPeers.has(peerId)) {
64
+ return;
65
+ }
66
+ for (const sub of subscriptions) {
67
+ if (!this.subscriptions.has(sub.topic)) {
68
+ continue;
69
+ }
70
+ if (sub.subscribe === false) {
71
+ continue;
72
+ }
73
+ log('peer %s joined topic %s', peerId, sub.topic);
74
+ const routingKey = topicToKey(sub.topic);
75
+ this.#fetchFromPeer(sub.topic, routingKey, peerId, {
76
+ signal: this.shutdownController.signal
77
+ })
78
+ .catch(err => {
79
+ log.error('failed to fetch IPNS record for %m from peer %s - %e', routingKey, peerId, err);
80
+ });
81
+ }
82
+ });
83
+ this.libp2p.services.fetch.registerLookupFunction(IPNS_STRING_PREFIX, async (key) => {
84
+ try {
85
+ const { record } = await this.localStore.get(key, {
86
+ signal: this.shutdownController.signal
87
+ });
88
+ return record;
89
+ }
90
+ catch (err) {
91
+ if (err.name !== 'NotFoundError') {
92
+ throw err;
93
+ }
94
+ }
95
+ });
96
+ log('registered lookup function for IPNS with libp2p/fetch service');
97
+ }
98
+ catch (e) {
99
+ log('unable to register lookup function for IPNS with libp2p/fetch service - %e', e);
100
+ }
101
+ }
102
+ else {
103
+ log('no libp2p/fetch service found. Skipping registration of lookup function for IPNS.');
104
+ }
32
105
  }
33
- async #processPubSubMessage(message) {
106
+ async #processPubSubMessage(message, options) {
34
107
  log('message received for topic', message.topic);
35
108
  if (message.type !== 'signed') {
36
109
  log.error('unsigned message received, this module can only work with signed messages');
37
110
  return;
38
111
  }
39
- if (message.from.equals(this.peerId)) {
112
+ if (message.from.equals(this.libp2p.peerId)) {
40
113
  log('not storing record from self');
41
114
  return;
42
115
  }
43
- const routingKey = topicToKey(message.topic);
44
- await ipnsValidator(routingKey, message.data);
116
+ await this.#handleRecord(message.topic, topicToKey(message.topic), message.data, false, options);
117
+ }
118
+ async #fetchFromPeer(topic, routingKey, peerId, options) {
119
+ const marshalledRecord = await this.fetchQueue.add(async ({ signal }) => {
120
+ log('fetching ipns record for %m from peer %s', routingKey, peerId);
121
+ const sig = anySignal([
122
+ signal,
123
+ AbortSignal.timeout(this.fetchTimeout)
124
+ ]);
125
+ try {
126
+ return await this.libp2p.services.fetch?.fetch(peerId, routingKey, {
127
+ signal: sig
128
+ });
129
+ }
130
+ finally {
131
+ sig.clear();
132
+ }
133
+ }, options);
134
+ if (marshalledRecord == null) {
135
+ throw new NotFoundError(`Peer ${peerId} did not have record for routing key ${uint8ArrayToString(routingKey, 'base64')}`);
136
+ }
137
+ log('fetched ipns record for %m from peer %s', routingKey, peerId);
138
+ return this.#handleRecord(topic, routingKey, marshalledRecord, true, options);
139
+ }
140
+ async #handleRecord(topic, routingKey, marshalledRecord, publish, options) {
141
+ await ipnsValidator(routingKey, marshalledRecord);
142
+ this.shutdownController.signal.throwIfAborted();
45
143
  if (await this.localStore.has(routingKey)) {
46
- const { record: currentRecord } = await this.localStore.get(routingKey);
47
- if (uint8ArrayEquals(currentRecord, message.data)) {
48
- log('not storing record as we already have it');
49
- return;
144
+ const { record: currentRecord } = await this.localStore.get(routingKey, options);
145
+ if (uint8ArrayEquals(currentRecord, marshalledRecord)) {
146
+ log.trace('found identical record for %m', routingKey);
147
+ return currentRecord;
50
148
  }
51
- const records = [currentRecord, message.data];
149
+ const records = [currentRecord, marshalledRecord];
52
150
  const index = ipnsSelector(routingKey, records);
53
151
  if (index === 0) {
54
- log('not storing record as the one we have is better');
55
- return;
152
+ log.trace('found old record for %m', routingKey);
153
+ return currentRecord;
154
+ }
155
+ }
156
+ log('found new record for %m', routingKey);
157
+ await this.localStore.put(routingKey, marshalledRecord, options);
158
+ // if the record was received via fetch, republish it
159
+ if (publish) {
160
+ log('publish value for topic %s', topic);
161
+ try {
162
+ const result = await this.libp2p.services.pubsub.publish(topic, marshalledRecord);
163
+ log('published record on topic %s to %d recipients', topic, result.recipients);
164
+ }
165
+ catch (err) {
166
+ log.error('could not publish record on topic %s - %e', err);
56
167
  }
57
168
  }
58
- await this.localStore.put(routingKey, message.data);
169
+ return marshalledRecord;
59
170
  }
60
171
  /**
61
172
  * Put a value to the pubsub datastore indexed by the received key properly encoded
@@ -64,7 +175,8 @@ class PubSubRouting {
64
175
  try {
65
176
  const topic = keyToTopic(routingKey);
66
177
  log('publish value for topic %s', topic);
67
- const result = await this.pubsub.publish(topic, marshaledRecord);
178
+ const result = await this.libp2p.services.pubsub.publish(topic, marshaledRecord);
179
+ options?.signal?.throwIfAborted();
68
180
  log('published record on topic %s to %d recipients', topic, result.recipients);
69
181
  options.onProgress?.(new CustomProgressEvent('ipns:pubsub:publish', { topic, result }));
70
182
  }
@@ -79,29 +191,53 @@ class PubSubRouting {
79
191
  * updated once new publishes occur
80
192
  */
81
193
  async get(routingKey, options = {}) {
194
+ const topic = keyToTopic(routingKey);
82
195
  try {
83
- const topic = keyToTopic(routingKey);
84
196
  // ensure we are subscribed to topic
85
- if (!this.pubsub.getTopics().includes(topic)) {
197
+ if (!this.libp2p.services.pubsub.getTopics().includes(topic)) {
86
198
  log('add subscription for topic', topic);
87
- this.pubsub.subscribe(topic);
88
- this.subscriptions.push(topic);
199
+ this.libp2p.services.pubsub.subscribe(topic);
200
+ this.subscriptions.add(topic);
89
201
  options.onProgress?.(new CustomProgressEvent('ipns:pubsub:subscribe', { topic }));
90
202
  }
91
- // chain through to local store
92
- const { record } = await this.localStore.get(routingKey, options);
93
- return record;
94
203
  }
95
204
  catch (err) {
96
205
  options.onProgress?.(new CustomProgressEvent('ipns:pubsub:error', err));
97
206
  throw err;
98
207
  }
208
+ // delay before fetch to allow pubsub to resolve
209
+ await raceSignal(delay(this.fetchDelay), this.shutdownController.signal);
210
+ const fetchController = new AbortController();
211
+ const promises = [];
212
+ for (const peerId of this.libp2p.services.pubsub.getSubscribers(topic)) {
213
+ const signal = anySignal([
214
+ options?.signal,
215
+ fetchController.signal
216
+ ]);
217
+ promises.push(this.#fetchFromPeer(topic, routingKey, peerId, {
218
+ ...options,
219
+ signal
220
+ })
221
+ .finally(() => {
222
+ signal.clear();
223
+ }));
224
+ }
225
+ if (promises.length > 0) {
226
+ // fetch record from topic peers
227
+ const record = await Promise.any(promises);
228
+ // cancel any in-flight requests
229
+ fetchController.abort();
230
+ if (record != null) {
231
+ return record;
232
+ }
233
+ }
234
+ throw new NotFoundError('Pubsub routing does not actively query peers');
99
235
  }
100
236
  /**
101
237
  * Get pubsub subscriptions related to ipns
102
238
  */
103
239
  getSubscriptions() {
104
- return this.subscriptions;
240
+ return [...this.subscriptions];
105
241
  }
106
242
  /**
107
243
  * Cancel pubsub subscriptions related to ipns
@@ -111,15 +247,38 @@ class PubSubRouting {
111
247
  const routingKey = multihashToIPNSRoutingKey(digest);
112
248
  const topic = keyToTopic(routingKey);
113
249
  // Not found topic
114
- if (!this.subscriptions.includes(topic)) {
250
+ if (!this.subscriptions.has(topic)) {
115
251
  return;
116
252
  }
117
- this.pubsub.unsubscribe(topic);
118
- this.subscriptions = this.subscriptions.filter(t => t !== topic);
253
+ this.libp2p.services.pubsub.unsubscribe(topic);
254
+ this.subscriptions.delete(topic);
119
255
  }
120
256
  toString() {
121
257
  return 'PubSubRouting()';
122
258
  }
259
+ async start() {
260
+ this.shutdownController = new AbortController();
261
+ setMaxListeners(Infinity, this.shutdownController.signal);
262
+ if (this.libp2p.services.fetch != null) {
263
+ this.fetchTopologyId = await this.libp2p.register(this.libp2p.services.fetch.protocol, {
264
+ onConnect: (peerId) => {
265
+ this.fetchPeers.add(peerId);
266
+ },
267
+ onDisconnect: (peerId) => {
268
+ this.fetchPeers.delete(peerId);
269
+ }
270
+ }, {
271
+ signal: this.shutdownController.signal
272
+ });
273
+ }
274
+ }
275
+ stop() {
276
+ this.fetchQueue.abort();
277
+ this.shutdownController.abort();
278
+ if (this.fetchTopologyId != null) {
279
+ this.libp2p.unregister(this.fetchTopologyId);
280
+ }
281
+ }
123
282
  }
124
283
  const PUBSUB_NAMESPACE = '/record/';
125
284
  /**
@@ -147,7 +306,7 @@ function topicToKey(topic) {
147
306
  * updated records, so the first call to `.get` should be expected
148
307
  * to fail!
149
308
  */
150
- export function pubsub(components) {
151
- return new PubSubRouting(components);
309
+ export function pubsub(components, init = {}) {
310
+ return new PubSubRouting(components, init);
152
311
  }
153
312
  //# sourceMappingURL=pubsub.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pubsub.js","sourceRoot":"","sources":["../../../src/routing/pubsub.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pubsub.js","sourceRoot":"","sources":["../../../src/routing/pubsub.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helia/ipns",
3
- "version": "9.1.9-11802dd6",
3
+ "version": "9.1.9-1a2ebead",
4
4
  "description": "An implementation of IPNS for Helia",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/ipfs/helia/tree/main/packages/ipns#readme",
@@ -73,24 +73,29 @@
73
73
  "test:electron-main": "aegir test -t electron-main"
74
74
  },
75
75
  "dependencies": {
76
- "@helia/interface": "6.1.1-11802dd6",
76
+ "@helia/interface": "6.1.1-1a2ebead",
77
77
  "@libp2p/crypto": "^5.1.7",
78
- "@libp2p/interface": "^3.1.0",
78
+ "@libp2p/fetch": "^4.1.0",
79
+ "@libp2p/interface": "^3.2.0",
79
80
  "@libp2p/kad-dht": "^16.1.0",
80
81
  "@libp2p/keychain": "^6.0.5",
81
82
  "@libp2p/logger": "^6.0.5",
83
+ "@libp2p/peer-collections": "^7.0.14",
82
84
  "@libp2p/utils": "^7.0.5",
85
+ "any-signal": "^4.2.0",
86
+ "delay": "^7.0.0",
83
87
  "interface-datastore": "^9.0.2",
84
88
  "ipns": "^10.1.2",
85
89
  "multiformats": "^13.4.1",
86
90
  "progress-events": "^1.0.1",
87
91
  "protons-runtime": "^6.0.1",
92
+ "race-signal": "^2.0.0",
88
93
  "uint8arraylist": "^2.4.8",
89
94
  "uint8arrays": "^5.1.0"
90
95
  },
91
96
  "devDependencies": {
92
97
  "@libp2p/crypto": "^5.1.12",
93
- "@libp2p/peer-id": "^6.0.3",
98
+ "@libp2p/peer-id": "^6.0.6",
94
99
  "aegir": "^47.0.22",
95
100
  "datastore-core": "^11.0.2",
96
101
  "it-drain": "^3.0.10",
@@ -1,17 +1,24 @@
1
- import { isPublicKey } from '@libp2p/interface'
1
+ import { isPublicKey, NotFoundError, setMaxListeners } from '@libp2p/interface'
2
2
  import { logger } from '@libp2p/logger'
3
+ import { PeerSet } from '@libp2p/peer-collections'
4
+ import { Queue } from '@libp2p/utils'
5
+ import { anySignal } from 'any-signal'
6
+ import delay from 'delay'
3
7
  import { multihashToIPNSRoutingKey } from 'ipns'
4
8
  import { ipnsSelector } from 'ipns/selector'
5
9
  import { ipnsValidator } from 'ipns/validator'
6
10
  import { CustomProgressEvent } from 'progress-events'
11
+ import { raceSignal } from 'race-signal'
7
12
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
8
13
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
9
14
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
10
15
  import { InvalidTopicError } from '../errors.ts'
11
16
  import { localStore } from '../local-store.ts'
17
+ import { IPNS_STRING_PREFIX } from '../utils.ts'
12
18
  import type { GetOptions, IPNSRouting, PutOptions } from './index.ts'
13
19
  import type { LocalStore } from '../local-store.ts'
14
- import type { PeerId, PublicKey, TypedEventTarget, ComponentLogger } from '@libp2p/interface'
20
+ import type { Fetch } from '@libp2p/fetch'
21
+ import type { PeerId, PublicKey, TypedEventTarget, ComponentLogger, Startable, AbortOptions, Metrics, Libp2p } from '@libp2p/interface'
15
22
  import type { Datastore } from 'interface-datastore'
16
23
  import type { MultihashDigest } from 'multiformats/hashes/interface'
17
24
  import type { ProgressEvent } from 'progress-events'
@@ -25,7 +32,18 @@ export interface Message {
25
32
  data: Uint8Array
26
33
  }
27
34
 
35
+ export interface Subscription {
36
+ topic: string
37
+ subscribe: boolean
38
+ }
39
+
40
+ export interface SubscriptionChangeData {
41
+ peerId: PeerId
42
+ subscriptions: Subscription[]
43
+ }
44
+
28
45
  export interface PubSubEvents {
46
+ 'subscription-change': CustomEvent<SubscriptionChangeData>
29
47
  message: CustomEvent<Message>
30
48
  }
31
49
 
@@ -44,12 +62,31 @@ export interface PubSub extends TypedEventTarget<PubSubEvents> {
44
62
  export interface PubsubRoutingComponents {
45
63
  datastore: Datastore
46
64
  logger: ComponentLogger
47
- libp2p: {
48
- peerId: PeerId
49
- services: {
50
- pubsub: PubSub
51
- }
52
- }
65
+ metrics?: Metrics
66
+ libp2p: Pick<Libp2p<{ pubsub: PubSub, fetch?: Fetch }>, 'peerId' | 'register' | 'unregister' | 'services'>
67
+ }
68
+
69
+ export interface PubsubRoutingInit {
70
+ /**
71
+ * How many fetch requests to run concurrently
72
+ *
73
+ * @default 8
74
+ */
75
+ fetchConcurrency?: number
76
+
77
+ /**
78
+ * How long to allow a fetch request to run for in ms
79
+ *
80
+ * @default 2_500
81
+ */
82
+ fetchTimeout?: number
83
+
84
+ /**
85
+ * How many ms to wait before sending a fetch request to a topic peer
86
+ *
87
+ * @default 0
88
+ */
89
+ fetchDelay?: number
53
90
  }
54
91
 
55
92
  export type PubSubProgressEvents =
@@ -57,32 +94,105 @@ export type PubSubProgressEvents =
57
94
  ProgressEvent<'ipns:pubsub:subscribe', { topic: string }> |
58
95
  ProgressEvent<'ipns:pubsub:error', Error>
59
96
 
60
- class PubSubRouting implements IPNSRouting {
61
- private subscriptions: string[]
97
+ export class PubSubRouting implements IPNSRouting, Startable {
98
+ private readonly subscriptions: Set<string>
62
99
  private readonly localStore: LocalStore
63
- private readonly peerId: PeerId
64
- private readonly pubsub: PubSub
65
-
66
- constructor (components: PubsubRoutingComponents) {
67
- this.subscriptions = []
100
+ private readonly libp2p: Pick<Libp2p<{ pubsub: PubSub, fetch?: Fetch }>, 'peerId' | 'register' | 'unregister' | 'services'>
101
+ private readonly fetchConcurrency: number
102
+ private readonly fetchTimeout: number
103
+ private readonly fetchDelay: number
104
+ private readonly fetchQueue: Queue<Uint8Array | undefined>
105
+ private readonly fetchPeers: PeerSet
106
+ private shutdownController: AbortController
107
+ private fetchTopologyId?: string
108
+
109
+ constructor (components: PubsubRoutingComponents, init: PubsubRoutingInit = {}) {
110
+ this.subscriptions = new Set()
111
+ this.shutdownController = new AbortController()
112
+ setMaxListeners(Infinity, this.shutdownController.signal)
113
+ this.fetchPeers = new PeerSet()
68
114
  this.localStore = localStore(components.datastore, components.logger.forComponent('helia:ipns:local-store'))
69
- this.peerId = components.libp2p.peerId
70
- this.pubsub = components.libp2p.services.pubsub
115
+ this.libp2p = components.libp2p
116
+ this.fetchConcurrency = init.fetchConcurrency ?? 8
117
+ this.fetchDelay = init.fetchDelay ?? 0
118
+
119
+ // default libp2p-fetch timeout is 10 seconds - we should have an existing
120
+ // connection to the peer so this can be shortened
121
+ this.fetchTimeout = init.fetchTimeout ?? 2_500
122
+ this.fetchQueue = new Queue<Uint8Array | undefined>({
123
+ concurrency: this.fetchConcurrency,
124
+ metrics: components.metrics,
125
+ metricName: 'helia_ipns_pubsub_fetch_queue'
126
+ })
71
127
 
72
- this.pubsub.addEventListener('message', (evt) => {
128
+ this.libp2p.services.pubsub.addEventListener('message', (evt) => {
73
129
  const message = evt.detail
74
130
 
75
- if (!this.subscriptions.includes(message.topic)) {
131
+ if (!this.subscriptions.has(message.topic)) {
76
132
  return
77
133
  }
78
134
 
79
- this.#processPubSubMessage(message).catch(err => {
135
+ this.#processPubSubMessage(message, {
136
+ signal: this.shutdownController.signal
137
+ }).catch(err => {
80
138
  log.error('Error processing message - %e', err)
81
139
  })
82
140
  })
141
+
142
+ // ipns over libp2p-fetch feature
143
+ if (this.libp2p.services.fetch != null) {
144
+ try {
145
+ this.libp2p.services.pubsub.addEventListener('subscription-change', (evt) => {
146
+ const { peerId, subscriptions } = evt.detail
147
+
148
+ if (!this.fetchPeers.has(peerId)) {
149
+ return
150
+ }
151
+
152
+ for (const sub of subscriptions) {
153
+ if (!this.subscriptions.has(sub.topic)) {
154
+ continue
155
+ }
156
+
157
+ if (sub.subscribe === false) {
158
+ continue
159
+ }
160
+
161
+ log('peer %s joined topic %s', peerId, sub.topic)
162
+
163
+ const routingKey = topicToKey(sub.topic)
164
+ this.#fetchFromPeer(sub.topic, routingKey, peerId, {
165
+ signal: this.shutdownController.signal
166
+ })
167
+ .catch(err => {
168
+ log.error('failed to fetch IPNS record for %m from peer %s - %e', routingKey, peerId, err)
169
+ })
170
+ }
171
+ })
172
+
173
+ this.libp2p.services.fetch.registerLookupFunction(IPNS_STRING_PREFIX, async (key) => {
174
+ try {
175
+ const { record } = await this.localStore.get(key, {
176
+ signal: this.shutdownController.signal
177
+ })
178
+
179
+ return record
180
+ } catch (err: any) {
181
+ if (err.name !== 'NotFoundError') {
182
+ throw err
183
+ }
184
+ }
185
+ })
186
+ log('registered lookup function for IPNS with libp2p/fetch service')
187
+ } catch (e) {
188
+ log('unable to register lookup function for IPNS with libp2p/fetch service - %e', e)
189
+ }
190
+ } else {
191
+ log('no libp2p/fetch service found. Skipping registration of lookup function for IPNS.')
192
+ }
83
193
  }
84
194
 
85
- async #processPubSubMessage (message: Message): Promise<void> {
195
+ async #processPubSubMessage (message: Message, options?: AbortOptions): Promise<void> {
86
196
  log('message received for topic', message.topic)
87
197
 
88
198
  if (message.type !== 'signed') {
@@ -90,33 +200,77 @@ class PubSubRouting implements IPNSRouting {
90
200
  return
91
201
  }
92
202
 
93
- if (message.from.equals(this.peerId)) {
203
+ if (message.from.equals(this.libp2p.peerId)) {
94
204
  log('not storing record from self')
95
205
  return
96
206
  }
97
207
 
98
- const routingKey = topicToKey(message.topic)
208
+ await this.#handleRecord(message.topic, topicToKey(message.topic), message.data, false, options)
209
+ }
99
210
 
100
- await ipnsValidator(routingKey, message.data)
211
+ async #fetchFromPeer (topic: string, routingKey: Uint8Array, peerId: PeerId, options?: AbortOptions): Promise<Uint8Array> {
212
+ const marshalledRecord = await this.fetchQueue.add(async ({ signal }) => {
213
+ log('fetching ipns record for %m from peer %s', routingKey, peerId)
214
+
215
+ const sig = anySignal([
216
+ signal,
217
+ AbortSignal.timeout(this.fetchTimeout)
218
+ ])
219
+
220
+ try {
221
+ return await this.libp2p.services.fetch?.fetch(peerId, routingKey, {
222
+ signal: sig
223
+ })
224
+ } finally {
225
+ sig.clear()
226
+ }
227
+ }, options)
228
+
229
+ if (marshalledRecord == null) {
230
+ throw new NotFoundError(`Peer ${peerId} did not have record for routing key ${uint8ArrayToString(routingKey, 'base64')}`)
231
+ }
232
+
233
+ log('fetched ipns record for %m from peer %s', routingKey, peerId)
234
+ return this.#handleRecord(topic, routingKey, marshalledRecord, true, options)
235
+ }
236
+
237
+ async #handleRecord (topic: string, routingKey: Uint8Array, marshalledRecord: Uint8Array, publish: boolean, options?: AbortOptions): Promise<Uint8Array> {
238
+ await ipnsValidator(routingKey, marshalledRecord)
239
+ this.shutdownController.signal.throwIfAborted()
101
240
 
102
241
  if (await this.localStore.has(routingKey)) {
103
- const { record: currentRecord } = await this.localStore.get(routingKey)
242
+ const { record: currentRecord } = await this.localStore.get(routingKey, options)
104
243
 
105
- if (uint8ArrayEquals(currentRecord, message.data)) {
106
- log('not storing record as we already have it')
107
- return
244
+ if (uint8ArrayEquals(currentRecord, marshalledRecord)) {
245
+ log.trace('found identical record for %m', routingKey)
246
+ return currentRecord
108
247
  }
109
248
 
110
- const records = [currentRecord, message.data]
249
+ const records = [currentRecord, marshalledRecord]
111
250
  const index = ipnsSelector(routingKey, records)
112
251
 
113
252
  if (index === 0) {
114
- log('not storing record as the one we have is better')
115
- return
253
+ log.trace('found old record for %m', routingKey)
254
+ return currentRecord
255
+ }
256
+ }
257
+
258
+ log('found new record for %m', routingKey)
259
+ await this.localStore.put(routingKey, marshalledRecord, options)
260
+
261
+ // if the record was received via fetch, republish it
262
+ if (publish) {
263
+ log('publish value for topic %s', topic)
264
+
265
+ try {
266
+ const result = await this.libp2p.services.pubsub.publish(topic, marshalledRecord)
267
+ log('published record on topic %s to %d recipients', topic, result.recipients)
268
+ } catch (err) {
269
+ log.error('could not publish record on topic %s - %e', err)
116
270
  }
117
271
  }
118
272
 
119
- await this.localStore.put(routingKey, message.data)
273
+ return marshalledRecord
120
274
  }
121
275
 
122
276
  /**
@@ -127,7 +281,8 @@ class PubSubRouting implements IPNSRouting {
127
281
  const topic = keyToTopic(routingKey)
128
282
 
129
283
  log('publish value for topic %s', topic)
130
- const result = await this.pubsub.publish(topic, marshaledRecord)
284
+ const result = await this.libp2p.services.pubsub.publish(topic, marshaledRecord)
285
+ options?.signal?.throwIfAborted()
131
286
 
132
287
  log('published record on topic %s to %d recipients', topic, result.recipients)
133
288
  options.onProgress?.(new CustomProgressEvent('ipns:pubsub:publish', { topic, result }))
@@ -143,33 +298,65 @@ class PubSubRouting implements IPNSRouting {
143
298
  * updated once new publishes occur
144
299
  */
145
300
  async get (routingKey: Uint8Array, options: GetOptions = {}): Promise<Uint8Array> {
146
- try {
147
- const topic = keyToTopic(routingKey)
301
+ const topic = keyToTopic(routingKey)
148
302
 
303
+ try {
149
304
  // ensure we are subscribed to topic
150
- if (!this.pubsub.getTopics().includes(topic)) {
305
+ if (!this.libp2p.services.pubsub.getTopics().includes(topic)) {
151
306
  log('add subscription for topic', topic)
152
- this.pubsub.subscribe(topic)
153
- this.subscriptions.push(topic)
307
+ this.libp2p.services.pubsub.subscribe(topic)
308
+ this.subscriptions.add(topic)
154
309
 
155
310
  options.onProgress?.(new CustomProgressEvent('ipns:pubsub:subscribe', { topic }))
156
311
  }
157
-
158
- // chain through to local store
159
- const { record } = await this.localStore.get(routingKey, options)
160
-
161
- return record
162
312
  } catch (err: any) {
163
313
  options.onProgress?.(new CustomProgressEvent<Error>('ipns:pubsub:error', err))
164
314
  throw err
165
315
  }
316
+
317
+ // delay before fetch to allow pubsub to resolve
318
+ await raceSignal(delay(this.fetchDelay), this.shutdownController.signal)
319
+
320
+ const fetchController = new AbortController()
321
+ const promises: Array<Promise<Uint8Array>> = []
322
+
323
+ for (const peerId of this.libp2p.services.pubsub.getSubscribers(topic)) {
324
+ const signal = anySignal([
325
+ options?.signal,
326
+ fetchController.signal
327
+ ])
328
+
329
+ promises.push(
330
+ this.#fetchFromPeer(topic, routingKey, peerId, {
331
+ ...options,
332
+ signal
333
+ })
334
+ .finally(() => {
335
+ signal.clear()
336
+ })
337
+ )
338
+ }
339
+
340
+ if (promises.length > 0) {
341
+ // fetch record from topic peers
342
+ const record = await Promise.any(promises)
343
+
344
+ // cancel any in-flight requests
345
+ fetchController.abort()
346
+
347
+ if (record != null) {
348
+ return record
349
+ }
350
+ }
351
+
352
+ throw new NotFoundError('Pubsub routing does not actively query peers')
166
353
  }
167
354
 
168
355
  /**
169
356
  * Get pubsub subscriptions related to ipns
170
357
  */
171
358
  getSubscriptions (): string[] {
172
- return this.subscriptions
359
+ return [...this.subscriptions]
173
360
  }
174
361
 
175
362
  /**
@@ -181,17 +368,44 @@ class PubSubRouting implements IPNSRouting {
181
368
  const topic = keyToTopic(routingKey)
182
369
 
183
370
  // Not found topic
184
- if (!this.subscriptions.includes(topic)) {
371
+ if (!this.subscriptions.has(topic)) {
185
372
  return
186
373
  }
187
374
 
188
- this.pubsub.unsubscribe(topic)
189
- this.subscriptions = this.subscriptions.filter(t => t !== topic)
375
+ this.libp2p.services.pubsub.unsubscribe(topic)
376
+ this.subscriptions.delete(topic)
190
377
  }
191
378
 
192
379
  toString (): string {
193
380
  return 'PubSubRouting()'
194
381
  }
382
+
383
+ async start (): Promise<void> {
384
+ this.shutdownController = new AbortController()
385
+ setMaxListeners(Infinity, this.shutdownController.signal)
386
+
387
+ if (this.libp2p.services.fetch != null) {
388
+ this.fetchTopologyId = await this.libp2p.register(this.libp2p.services.fetch.protocol, {
389
+ onConnect: (peerId) => {
390
+ this.fetchPeers.add(peerId)
391
+ },
392
+ onDisconnect: (peerId) => {
393
+ this.fetchPeers.delete(peerId)
394
+ }
395
+ }, {
396
+ signal: this.shutdownController.signal
397
+ })
398
+ }
399
+ }
400
+
401
+ stop (): void {
402
+ this.fetchQueue.abort()
403
+ this.shutdownController.abort()
404
+
405
+ if (this.fetchTopologyId != null) {
406
+ this.libp2p.unregister(this.fetchTopologyId)
407
+ }
408
+ }
195
409
  }
196
410
 
197
411
  const PUBSUB_NAMESPACE = '/record/'
@@ -226,6 +440,6 @@ function topicToKey (topic: string): Uint8Array {
226
440
  * updated records, so the first call to `.get` should be expected
227
441
  * to fail!
228
442
  */
229
- export function pubsub (components: PubsubRoutingComponents): IPNSRouting {
230
- return new PubSubRouting(components)
443
+ export function pubsub (components: PubsubRoutingComponents, init: PubsubRoutingInit = {}): IPNSRouting {
444
+ return new PubSubRouting(components, init)
231
445
  }