Skip to content

Commit 81a52a8

Browse files
feat!: set peer-exchange with default bootstrap (#1469)
* set peer-exchange with default bootstrap * only initialise protocols with bootstrap peers * update package * update package-lock * refactor `getPeers` while setting up a protocol * move codecs to `@waku/interfaces` * lightpush: send messages to multiple peers * only use multiple peers for LP and Filter * fix: ts warnings * lightpush: tests pass * update breaking changes for new API * move codecs back into protocol files * refactor: `getPeers()` * rm: log as an arg * add tsdoc for getPeers * add import * add prettier rule to eslint * add: peer exchange to sdk as a dep * fix eslint error * add try catch * revert unecessary diff * revert unecessary diff * fix imports * convert relaycodecs to array * remove: peerId as an arg for protocol methods * keep peerId as an arg for peer-exchange * remove: peerId from getPeers() * lightpush: extract hardcoded numPeers as a constant * return all peers if numPeers is 0 and increase readability for random peers * refactor considering more than 1 bootstrap peers can exist * use `getPeers` * change arg for `getPeers` to object * address comments * refactor tests for new API * lightpush: make constant the class variable * use `maxBootstrapPeers` instead of `includeBootstrap` * refactor protocols for new API * add tests for `getPeers` * skip getPeers test * rm: only from test * move tests to `base_protocol.spec.ts` * break down `getPeers` into a `filter` method * return all bootstrap peers if arg is 0 * refactor test without stubbing * address comments * update test title * move `filterPeers` to a separate file * address comments & add more test * make test title more verbose * address comments * remove ProtocolOptions * chore: refactor tests for new API * add defaults for getPeers * address comments * rm unneeded comment * address comment: add diversity of node tags to test * address comments * fix: imports
1 parent 408b79d commit 81a52a8

23 files changed

+348
-151
lines changed

package-lock.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/index.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,11 @@ export * as waku_filter from "./lib/filter/index.js";
1515
export { wakuFilter, FilterCodecs } from "./lib/filter/index.js";
1616

1717
export * as waku_light_push from "./lib/light_push/index.js";
18-
export { wakuLightPush, LightPushCodec } from "./lib/light_push/index.js";
18+
export { wakuLightPush } from "./lib/light_push/index.js";
1919

2020
export * as waku_store from "./lib/store/index.js";
21-
export {
22-
PageDirection,
23-
wakuStore,
24-
StoreCodec,
25-
createCursor
26-
} from "./lib/store/index.js";
21+
22+
export { PageDirection, wakuStore, createCursor } from "./lib/store/index.js";
2723

2824
export { waitForRemotePeer } from "./lib/wait_for_remote_peer.js";
2925

packages/core/src/lib/base_protocol.ts

+29
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Peer, PeerStore } from "@libp2p/interface/peer-store";
55
import type { IBaseProtocol, Libp2pComponents } from "@waku/interfaces";
66
import { getPeersForProtocol, selectPeerForProtocol } from "@waku/utils/libp2p";
77

8+
import { filterPeers } from "./filterPeers.js";
89
import { StreamManager } from "./stream_manager.js";
910

1011
/**
@@ -60,4 +61,32 @@ export class BaseProtocol implements IBaseProtocol {
6061
);
6162
return peer;
6263
}
64+
65+
/**
66+
* Retrieves a list of peers based on the specified criteria.
67+
*
68+
* @param numPeers - The total number of peers to retrieve. If 0, all peers are returned.
69+
* @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve.
70+
* @returns A Promise that resolves to an array of peers based on the specified criteria.
71+
*/
72+
protected async getPeers(
73+
{
74+
numPeers,
75+
maxBootstrapPeers
76+
}: {
77+
numPeers: number;
78+
maxBootstrapPeers: number;
79+
} = {
80+
maxBootstrapPeers: 1,
81+
numPeers: 0
82+
}
83+
): Promise<Peer[]> {
84+
// Retrieve all peers that support the protocol
85+
const allPeersForProtocol = await getPeersForProtocol(this.peerStore, [
86+
this.multicodec
87+
]);
88+
89+
// Filter the peers based on the specified criteria
90+
return filterPeers(allPeersForProtocol, numPeers, maxBootstrapPeers);
91+
}
6392
}

packages/core/src/lib/filter/index.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Stream } from "@libp2p/interface/connection";
2-
import type { PeerId } from "@libp2p/interface/peer-id";
32
import type { Peer } from "@libp2p/interface/peer-store";
43
import type { IncomingStreamData } from "@libp2p/interface-internal/registrar";
54
import type {
@@ -14,7 +13,6 @@ import type {
1413
Libp2p,
1514
PeerIdStr,
1615
ProtocolCreateOptions,
17-
ProtocolOptions,
1816
PubSubTopic,
1917
Unsubscribe
2018
} from "@waku/interfaces";
@@ -228,6 +226,7 @@ class Subscription {
228226
class Filter extends BaseProtocol implements IReceiver {
229227
private readonly options: ProtocolCreateOptions;
230228
private activeSubscriptions = new Map<string, Subscription>();
229+
private readonly NUM_PEERS_PROTOCOL = 1;
231230

232231
private getActiveSubscription(
233232
pubSubTopic: PubSubTopic,
@@ -257,14 +256,16 @@ class Filter extends BaseProtocol implements IReceiver {
257256
this.options = options ?? {};
258257
}
259258

260-
async createSubscription(
261-
pubSubTopic?: string,
262-
peerId?: PeerId
263-
): Promise<Subscription> {
259+
async createSubscription(pubSubTopic?: string): Promise<Subscription> {
264260
const _pubSubTopic =
265261
pubSubTopic ?? this.options.pubSubTopic ?? DefaultPubSubTopic;
266262

267-
const peer = await this.getPeer(peerId);
263+
const peer = (
264+
await this.getPeers({
265+
maxBootstrapPeers: 1,
266+
numPeers: this.NUM_PEERS_PROTOCOL
267+
})
268+
)[0];
268269

269270
const subscription =
270271
this.getActiveSubscription(_pubSubTopic, peer.id.toString()) ??
@@ -278,10 +279,9 @@ class Filter extends BaseProtocol implements IReceiver {
278279
}
279280

280281
public toSubscriptionIterator<T extends IDecodedMessage>(
281-
decoders: IDecoder<T> | IDecoder<T>[],
282-
opts?: ProtocolOptions | undefined
282+
decoders: IDecoder<T> | IDecoder<T>[]
283283
): Promise<IAsyncIterator<T>> {
284-
return toAsyncIterator(this, decoders, opts);
284+
return toAsyncIterator(this, decoders);
285285
}
286286

287287
/**
@@ -301,10 +301,9 @@ class Filter extends BaseProtocol implements IReceiver {
301301
*/
302302
async subscribe<T extends IDecodedMessage>(
303303
decoders: IDecoder<T> | IDecoder<T>[],
304-
callback: Callback<T>,
305-
opts?: ProtocolOptions
304+
callback: Callback<T>
306305
): Promise<Unsubscribe> {
307-
const subscription = await this.createSubscription(undefined, opts?.peerId);
306+
const subscription = await this.createSubscription();
308307

309308
await subscription.subscribe(decoders, callback);
310309

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Peer } from "@libp2p/interface/peer-store";
2+
import type { Tag } from "@libp2p/interface/peer-store";
3+
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
4+
import { Tags } from "@waku/interfaces";
5+
import { expect } from "chai";
6+
7+
import { filterPeers } from "./filterPeers.js";
8+
9+
describe("filterPeers function", function () {
10+
it("should return all peers when numPeers is 0", async function () {
11+
const peer1 = await createSecp256k1PeerId();
12+
const peer2 = await createSecp256k1PeerId();
13+
const peer3 = await createSecp256k1PeerId();
14+
15+
const mockPeers = [
16+
{
17+
id: peer1,
18+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
19+
},
20+
{
21+
id: peer2,
22+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
23+
},
24+
{
25+
id: peer3,
26+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
27+
}
28+
] as unknown as Peer[];
29+
30+
const result = await filterPeers(mockPeers, 0, 10);
31+
expect(result.length).to.deep.equal(mockPeers.length);
32+
});
33+
34+
it("should return all non-bootstrap peers and no bootstrap peer when numPeers is 0 and maxBootstrapPeers is 0", async function () {
35+
const peer1 = await createSecp256k1PeerId();
36+
const peer2 = await createSecp256k1PeerId();
37+
const peer3 = await createSecp256k1PeerId();
38+
const peer4 = await createSecp256k1PeerId();
39+
40+
const mockPeers = [
41+
{
42+
id: peer1,
43+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
44+
},
45+
{
46+
id: peer2,
47+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
48+
},
49+
{
50+
id: peer3,
51+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
52+
},
53+
{
54+
id: peer4,
55+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
56+
}
57+
] as unknown as Peer[];
58+
59+
const result = await filterPeers(mockPeers, 0, 0);
60+
61+
// result should have no bootstrap peers, and a total of 2 peers
62+
expect(result.length).to.equal(2);
63+
expect(
64+
result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length
65+
).to.equal(0);
66+
});
67+
68+
it("should return one bootstrap peer, and all non-boostrap peers, when numPeers is 0 & maxBootstrap is 1", async function () {
69+
const peer1 = await createSecp256k1PeerId();
70+
const peer2 = await createSecp256k1PeerId();
71+
const peer3 = await createSecp256k1PeerId();
72+
const peer4 = await createSecp256k1PeerId();
73+
const peer5 = await createSecp256k1PeerId();
74+
75+
const mockPeers = [
76+
{
77+
id: peer1,
78+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
79+
},
80+
{
81+
id: peer2,
82+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
83+
},
84+
{
85+
id: peer3,
86+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
87+
},
88+
{
89+
id: peer4,
90+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
91+
},
92+
{
93+
id: peer5,
94+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
95+
}
96+
] as unknown as Peer[];
97+
98+
const result = await filterPeers(mockPeers, 0, 1);
99+
100+
// result should have 1 bootstrap peers, and a total of 4 peers
101+
expect(result.length).to.equal(4);
102+
expect(
103+
result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length
104+
).to.equal(1);
105+
});
106+
107+
it("should return only bootstrap peers up to maxBootstrapPeers", async function () {
108+
const peer1 = await createSecp256k1PeerId();
109+
const peer2 = await createSecp256k1PeerId();
110+
const peer3 = await createSecp256k1PeerId();
111+
const peer4 = await createSecp256k1PeerId();
112+
const peer5 = await createSecp256k1PeerId();
113+
114+
const mockPeers = [
115+
{
116+
id: peer1,
117+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
118+
},
119+
{
120+
id: peer2,
121+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
122+
},
123+
{
124+
id: peer3,
125+
tags: new Map<string, Tag>([[Tags.BOOTSTRAP, { value: 100 }]])
126+
},
127+
{
128+
id: peer4,
129+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
130+
},
131+
{
132+
id: peer5,
133+
tags: new Map<string, Tag>([[Tags.PEER_EXCHANGE, { value: 100 }]])
134+
}
135+
] as unknown as Peer[];
136+
137+
const result = await filterPeers(mockPeers, 5, 2);
138+
139+
// check that result has at least 2 bootstrap peers and no more than 5 peers
140+
expect(result.length).to.be.at.least(2);
141+
expect(result.length).to.be.at.most(5);
142+
expect(result.filter((peer: Peer) => peer.tags.has(Tags.BOOTSTRAP)).length);
143+
});
144+
});

packages/core/src/lib/filterPeers.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Peer } from "@libp2p/interface/peer-store";
2+
import { Tags } from "@waku/interfaces";
3+
4+
/**
5+
* Retrieves a list of peers based on the specified criteria.
6+
*
7+
* @param peers - The list of peers to filter from.
8+
* @param numPeers - The total number of peers to retrieve. If 0, all peers are returned.
9+
* @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve.
10+
* @returns A Promise that resolves to an array of peers based on the specified criteria.
11+
*/
12+
export async function filterPeers(
13+
peers: Peer[],
14+
numPeers: number,
15+
maxBootstrapPeers: number
16+
): Promise<Peer[]> {
17+
// Collect the bootstrap peers up to the specified maximum
18+
const bootstrapPeers = peers
19+
.filter((peer) => peer.tags.has(Tags.BOOTSTRAP))
20+
.slice(0, maxBootstrapPeers);
21+
22+
// Collect non-bootstrap peers
23+
const nonBootstrapPeers = peers.filter(
24+
(peer) => !peer.tags.has(Tags.BOOTSTRAP)
25+
);
26+
27+
// If numPeers is 0, return all peers
28+
if (numPeers === 0) {
29+
return [...bootstrapPeers, ...nonBootstrapPeers];
30+
}
31+
32+
// Initialize the list of selected peers with the bootstrap peers
33+
const selectedPeers: Peer[] = [...bootstrapPeers];
34+
35+
// Fill up to numPeers with remaining random peers if needed
36+
while (selectedPeers.length < numPeers && nonBootstrapPeers.length > 0) {
37+
const randomIndex = Math.floor(Math.random() * nonBootstrapPeers.length);
38+
const randomPeer = nonBootstrapPeers.splice(randomIndex, 1)[0];
39+
selectedPeers.push(randomPeer);
40+
}
41+
42+
return selectedPeers;
43+
}

0 commit comments

Comments
 (0)