@waku/core 0.0.22 → 0.0.23
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/CHANGELOG.md +27 -0
- package/bundle/base_protocol-84d9b670.js +1198 -0
- package/bundle/index.js +1132 -1251
- package/bundle/lib/base_protocol.js +2 -116
- package/bundle/lib/message/version_0.js +1 -1
- package/bundle/lib/predefined_bootstrap_nodes.js +6 -6
- package/bundle/{version_0-86411fdf.js → version_0-74b4b9db.js} +875 -794
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/base_protocol.d.ts +18 -5
- package/dist/lib/base_protocol.js +25 -8
- package/dist/lib/base_protocol.js.map +1 -1
- package/dist/lib/connection_manager.js +10 -10
- package/dist/lib/connection_manager.js.map +1 -1
- package/dist/lib/filter/filter_rpc.js +4 -4
- package/dist/lib/filter/index.d.ts +4 -0
- package/dist/lib/filter/index.js +15 -11
- package/dist/lib/filter/index.js.map +1 -1
- package/dist/lib/filterPeers.d.ts +10 -0
- package/dist/lib/filterPeers.js +31 -0
- package/dist/lib/filterPeers.js.map +1 -0
- package/dist/lib/keep_alive_manager.d.ts +3 -2
- package/dist/lib/keep_alive_manager.js +27 -8
- package/dist/lib/keep_alive_manager.js.map +1 -1
- package/dist/lib/light_push/index.js +62 -33
- package/dist/lib/light_push/index.js.map +1 -1
- package/dist/lib/light_push/push_rpc.js +2 -2
- package/dist/lib/message/version_0.d.ts +1 -1
- package/dist/lib/message/version_0.js +3 -3
- package/dist/lib/message/version_0.js.map +1 -1
- package/dist/lib/predefined_bootstrap_nodes.js +6 -6
- package/dist/lib/store/history_rpc.js +3 -3
- package/dist/lib/store/index.d.ts +0 -5
- package/dist/lib/store/index.js +54 -37
- package/dist/lib/store/index.js.map +1 -1
- package/dist/lib/stream_manager.d.ts +15 -0
- package/dist/lib/stream_manager.js +53 -0
- package/dist/lib/stream_manager.js.map +1 -0
- package/dist/lib/to_proto_message.js +1 -1
- package/dist/lib/waku.d.ts +2 -2
- package/dist/lib/waku.js +1 -1
- package/package.json +15 -21
- package/src/index.ts +6 -9
- package/src/lib/base_protocol.ts +49 -18
- package/src/lib/connection_manager.ts +17 -13
- package/src/lib/filter/filter_rpc.ts +4 -4
- package/src/lib/filter/index.ts +21 -22
- package/src/lib/filterPeers.ts +43 -0
- package/src/lib/keep_alive_manager.ts +33 -9
- package/src/lib/light_push/index.ts +103 -47
- package/src/lib/light_push/push_rpc.ts +2 -2
- package/src/lib/message/version_0.ts +8 -5
- package/src/lib/predefined_bootstrap_nodes.ts +7 -7
- package/src/lib/store/history_rpc.ts +4 -4
- package/src/lib/store/index.ts +70 -51
- package/src/lib/stream_manager.ts +69 -0
- package/src/lib/to_proto_message.ts +1 -1
- package/src/lib/wait_for_remote_peer.ts +1 -1
- package/src/lib/waku.ts +3 -3
@@ -1,10 +1,12 @@
|
|
1
|
-
import type { PeerId } from "@libp2p/interface
|
1
|
+
import type { PeerId } from "@libp2p/interface/peer-id";
|
2
|
+
import type { PeerStore } from "@libp2p/interface/peer-store";
|
2
3
|
import type { IRelay } from "@waku/interfaces";
|
3
4
|
import type { KeepAliveOptions } from "@waku/interfaces";
|
5
|
+
import { utf8ToBytes } from "@waku/utils/bytes";
|
4
6
|
import debug from "debug";
|
5
7
|
import type { PingService } from "libp2p/ping";
|
6
8
|
|
7
|
-
import { createEncoder } from "
|
9
|
+
import { createEncoder } from "./message/version_0.js";
|
8
10
|
|
9
11
|
export const RelayPingContentTopic = "/relay-ping/1/ping/null";
|
10
12
|
const log = debug("waku:keep-alive");
|
@@ -22,8 +24,12 @@ export class KeepAliveManager {
|
|
22
24
|
this.relay = relay;
|
23
25
|
}
|
24
26
|
|
25
|
-
public start(
|
26
|
-
|
27
|
+
public start(
|
28
|
+
peerId: PeerId,
|
29
|
+
libp2pPing: PingService,
|
30
|
+
peerStore: PeerStore
|
31
|
+
): void {
|
32
|
+
// Just in case a timer already exists for this peer
|
27
33
|
this.stop(peerId);
|
28
34
|
|
29
35
|
const { pingKeepAlive: pingPeriodSecs, relayKeepAlive: relayPeriodSecs } =
|
@@ -33,10 +39,28 @@ export class KeepAliveManager {
|
|
33
39
|
|
34
40
|
if (pingPeriodSecs !== 0) {
|
35
41
|
const interval = setInterval(() => {
|
36
|
-
|
37
|
-
|
38
|
-
|
42
|
+
void (async () => {
|
43
|
+
try {
|
44
|
+
// ping the peer for keep alive
|
45
|
+
// also update the peer store with the latency
|
46
|
+
const ping = await libp2pPing.ping(peerId);
|
47
|
+
log(`Ping succeeded (${peerIdStr})`, ping);
|
48
|
+
|
49
|
+
try {
|
50
|
+
await peerStore.patch(peerId, {
|
51
|
+
metadata: {
|
52
|
+
ping: utf8ToBytes(ping.toString())
|
53
|
+
}
|
54
|
+
});
|
55
|
+
} catch (e) {
|
56
|
+
log("Failed to update ping", e);
|
57
|
+
}
|
58
|
+
} catch (e) {
|
59
|
+
log(`Ping failed (${peerIdStr})`, e);
|
60
|
+
}
|
61
|
+
})();
|
39
62
|
}, pingPeriodSecs * 1000);
|
63
|
+
|
40
64
|
this.pingKeepAliveTimers.set(peerIdStr, interval);
|
41
65
|
}
|
42
66
|
|
@@ -44,7 +68,7 @@ export class KeepAliveManager {
|
|
44
68
|
if (relay && relayPeriodSecs !== 0) {
|
45
69
|
const encoder = createEncoder({
|
46
70
|
contentTopic: RelayPingContentTopic,
|
47
|
-
ephemeral: true
|
71
|
+
ephemeral: true
|
48
72
|
});
|
49
73
|
const interval = setInterval(() => {
|
50
74
|
log("Sending Waku Relay ping message");
|
@@ -73,7 +97,7 @@ export class KeepAliveManager {
|
|
73
97
|
public stopAll(): void {
|
74
98
|
for (const timer of [
|
75
99
|
...Object.values(this.pingKeepAliveTimers),
|
76
|
-
...Object.values(this.relayKeepAliveTimers)
|
100
|
+
...Object.values(this.relayKeepAliveTimers)
|
77
101
|
]) {
|
78
102
|
clearInterval(timer);
|
79
103
|
}
|
@@ -1,13 +1,12 @@
|
|
1
|
-
import type { PeerId } from "@libp2p/interface
|
1
|
+
import type { PeerId } from "@libp2p/interface/peer-id";
|
2
2
|
import {
|
3
3
|
IEncoder,
|
4
4
|
ILightPush,
|
5
5
|
IMessage,
|
6
6
|
Libp2p,
|
7
7
|
ProtocolCreateOptions,
|
8
|
-
ProtocolOptions,
|
9
8
|
SendError,
|
10
|
-
SendResult
|
9
|
+
SendResult
|
11
10
|
} from "@waku/interfaces";
|
12
11
|
import { PushResponse } from "@waku/proto";
|
13
12
|
import { isSizeValid } from "@waku/utils";
|
@@ -27,80 +26,137 @@ const log = debug("waku:light-push");
|
|
27
26
|
export const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
|
28
27
|
export { PushResponse };
|
29
28
|
|
29
|
+
type PreparePushMessageResult =
|
30
|
+
| {
|
31
|
+
query: PushRpc;
|
32
|
+
error: null;
|
33
|
+
}
|
34
|
+
| {
|
35
|
+
query: null;
|
36
|
+
error: SendError;
|
37
|
+
};
|
38
|
+
|
30
39
|
/**
|
31
40
|
* Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
|
32
41
|
*/
|
33
42
|
class LightPush extends BaseProtocol implements ILightPush {
|
34
43
|
options: ProtocolCreateOptions;
|
44
|
+
private readonly NUM_PEERS_PROTOCOL = 1;
|
35
45
|
|
36
46
|
constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
|
37
47
|
super(LightPushCodec, libp2p.components);
|
38
48
|
this.options = options || {};
|
39
49
|
}
|
40
50
|
|
41
|
-
async
|
51
|
+
private async preparePushMessage(
|
42
52
|
encoder: IEncoder,
|
43
53
|
message: IMessage,
|
44
|
-
|
45
|
-
): Promise<
|
46
|
-
const { pubSubTopic = DefaultPubSubTopic } = this.options;
|
47
|
-
|
48
|
-
const peer = await this.getPeer(opts?.peerId);
|
49
|
-
const stream = await this.newStream(peer);
|
50
|
-
|
51
|
-
const recipients: PeerId[] = [];
|
52
|
-
let error: undefined | SendError = undefined;
|
53
|
-
|
54
|
+
pubSubTopic: string
|
55
|
+
): Promise<PreparePushMessageResult> {
|
54
56
|
try {
|
55
57
|
if (!isSizeValid(message.payload)) {
|
56
|
-
log("Failed to send waku light push: message is bigger
|
57
|
-
return {
|
58
|
-
recipients,
|
59
|
-
error: SendError.SIZE_TOO_BIG,
|
60
|
-
};
|
58
|
+
log("Failed to send waku light push: message is bigger than 1MB");
|
59
|
+
return { query: null, error: SendError.SIZE_TOO_BIG };
|
61
60
|
}
|
62
61
|
|
63
62
|
const protoMessage = await encoder.toProtoObj(message);
|
64
63
|
if (!protoMessage) {
|
65
64
|
log("Failed to encode to protoMessage, aborting push");
|
66
65
|
return {
|
67
|
-
|
68
|
-
error: SendError.ENCODE_FAILED
|
66
|
+
query: null,
|
67
|
+
error: SendError.ENCODE_FAILED
|
69
68
|
};
|
70
69
|
}
|
70
|
+
|
71
71
|
const query = PushRpc.createRequest(protoMessage, pubSubTopic);
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
return { query, error: null };
|
73
|
+
} catch (error) {
|
74
|
+
log("Failed to prepare push message", error);
|
75
|
+
|
76
|
+
return {
|
77
|
+
query: null,
|
78
|
+
error: SendError.GENERIC_FAIL
|
79
|
+
};
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
async send(encoder: IEncoder, message: IMessage): Promise<SendResult> {
|
84
|
+
const { pubSubTopic = DefaultPubSubTopic } = this.options;
|
85
|
+
const recipients: PeerId[] = [];
|
86
|
+
|
87
|
+
const { query, error: preparationError } = await this.preparePushMessage(
|
88
|
+
encoder,
|
89
|
+
message,
|
90
|
+
pubSubTopic
|
91
|
+
);
|
92
|
+
|
93
|
+
if (preparationError || !query) {
|
94
|
+
return {
|
95
|
+
recipients,
|
96
|
+
errors: [preparationError]
|
97
|
+
};
|
98
|
+
}
|
99
|
+
|
100
|
+
const peers = await this.getPeers({
|
101
|
+
maxBootstrapPeers: 1,
|
102
|
+
numPeers: this.NUM_PEERS_PROTOCOL
|
103
|
+
});
|
104
|
+
|
105
|
+
const promises = peers.map(async (peer) => {
|
106
|
+
let error: SendError | undefined;
|
107
|
+
const stream = await this.getStream(peer);
|
108
|
+
|
79
109
|
try {
|
80
|
-
const
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
110
|
+
const res = await pipe(
|
111
|
+
[query.encode()],
|
112
|
+
lp.encode,
|
113
|
+
stream,
|
114
|
+
lp.decode,
|
115
|
+
async (source) => await all(source)
|
116
|
+
);
|
117
|
+
try {
|
118
|
+
const bytes = new Uint8ArrayList();
|
119
|
+
res.forEach((chunk) => {
|
120
|
+
bytes.append(chunk);
|
121
|
+
});
|
122
|
+
|
123
|
+
const response = PushRpc.decode(bytes).response;
|
124
|
+
|
125
|
+
if (response?.isSuccess) {
|
126
|
+
recipients.some((recipient) => recipient.equals(peer.id)) ||
|
127
|
+
recipients.push(peer.id);
|
128
|
+
} else {
|
129
|
+
log("No response in PushRPC");
|
130
|
+
error = SendError.NO_RPC_RESPONSE;
|
131
|
+
}
|
132
|
+
} catch (err) {
|
133
|
+
log("Failed to decode push reply", err);
|
134
|
+
error = SendError.DECODE_FAILED;
|
92
135
|
}
|
93
136
|
} catch (err) {
|
94
|
-
log("Failed to
|
95
|
-
error = SendError.
|
137
|
+
log("Failed to send waku light push request", err);
|
138
|
+
error = SendError.GENERIC_FAIL;
|
96
139
|
}
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
140
|
+
|
141
|
+
return { recipients, error };
|
142
|
+
});
|
143
|
+
|
144
|
+
const results = await Promise.allSettled(promises);
|
145
|
+
const errors = results
|
146
|
+
.filter(
|
147
|
+
(
|
148
|
+
result
|
149
|
+
): result is PromiseFulfilledResult<{
|
150
|
+
recipients: PeerId[];
|
151
|
+
error: SendError | undefined;
|
152
|
+
}> => result.status === "fulfilled"
|
153
|
+
)
|
154
|
+
.map((result) => result.value.error)
|
155
|
+
.filter((error) => error !== undefined) as SendError[];
|
156
|
+
|
101
157
|
return {
|
102
|
-
error,
|
103
158
|
recipients,
|
159
|
+
errors
|
104
160
|
};
|
105
161
|
}
|
106
162
|
}
|
@@ -6,7 +6,7 @@ import type {
|
|
6
6
|
IMessage,
|
7
7
|
IMetaSetter,
|
8
8
|
IProtoMessage,
|
9
|
-
IRateLimitProof
|
9
|
+
IRateLimitProof
|
10
10
|
} from "@waku/interfaces";
|
11
11
|
import { proto_message as proto } from "@waku/proto";
|
12
12
|
import debug from "debug";
|
@@ -18,7 +18,10 @@ export const Version = 0;
|
|
18
18
|
export { proto };
|
19
19
|
|
20
20
|
export class DecodedMessage implements IDecodedMessage {
|
21
|
-
constructor(
|
21
|
+
constructor(
|
22
|
+
public pubSubTopic: string,
|
23
|
+
protected proto: proto.WakuMessage
|
24
|
+
) {}
|
22
25
|
|
23
26
|
get ephemeral(): boolean {
|
24
27
|
return Boolean(this.proto.ephemeral);
|
@@ -91,7 +94,7 @@ export class Encoder implements IEncoder {
|
|
91
94
|
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
|
92
95
|
meta: undefined,
|
93
96
|
rateLimitProof: message.rateLimitProof,
|
94
|
-
ephemeral: this.ephemeral
|
97
|
+
ephemeral: this.ephemeral
|
95
98
|
};
|
96
99
|
|
97
100
|
if (this.metaSetter) {
|
@@ -115,7 +118,7 @@ export class Encoder implements IEncoder {
|
|
115
118
|
export function createEncoder({
|
116
119
|
contentTopic,
|
117
120
|
ephemeral,
|
118
|
-
metaSetter
|
121
|
+
metaSetter
|
119
122
|
}: EncoderOptions): Encoder {
|
120
123
|
return new Encoder(contentTopic, ephemeral, metaSetter);
|
121
124
|
}
|
@@ -137,7 +140,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
|
|
137
140
|
timestamp: protoMessage.timestamp ?? undefined,
|
138
141
|
meta: protoMessage.meta ?? undefined,
|
139
142
|
rateLimitProof: protoMessage.rateLimitProof ?? undefined,
|
140
|
-
ephemeral: protoMessage.ephemeral ?? false
|
143
|
+
ephemeral: protoMessage.ephemeral ?? false
|
141
144
|
});
|
142
145
|
}
|
143
146
|
|
@@ -4,7 +4,7 @@ export const DefaultWantedNumber = 1;
|
|
4
4
|
|
5
5
|
export enum Fleet {
|
6
6
|
Prod = "prod",
|
7
|
-
Test = "test"
|
7
|
+
Test = "test"
|
8
8
|
}
|
9
9
|
|
10
10
|
/**
|
@@ -51,8 +51,8 @@ export const fleets = {
|
|
51
51
|
"node-01.do-ams3.wakuv2.prod":
|
52
52
|
"/dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e",
|
53
53
|
"node-01.gc-us-central1-a.wakuv2.prod":
|
54
|
-
"/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA"
|
55
|
-
}
|
54
|
+
"/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA"
|
55
|
+
}
|
56
56
|
},
|
57
57
|
"wakuv2.test": {
|
58
58
|
"waku-websocket": {
|
@@ -61,8 +61,8 @@ export const fleets = {
|
|
61
61
|
"node-01.do-ams3.wakuv2.test":
|
62
62
|
"/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
|
63
63
|
"node-01.gc-us-central1-a.wakuv2.test":
|
64
|
-
"/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS"
|
65
|
-
}
|
66
|
-
}
|
67
|
-
}
|
64
|
+
"/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS"
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
68
|
};
|
@@ -6,7 +6,7 @@ const OneMillion = BigInt(1_000_000);
|
|
6
6
|
|
7
7
|
export enum PageDirection {
|
8
8
|
BACKWARD = "backward",
|
9
|
-
FORWARD = "forward"
|
9
|
+
FORWARD = "forward"
|
10
10
|
}
|
11
11
|
|
12
12
|
export interface Params {
|
@@ -43,7 +43,7 @@ export class HistoryRpc {
|
|
43
43
|
const pagingInfo = {
|
44
44
|
pageSize: BigInt(params.pageSize),
|
45
45
|
cursor: params.cursor,
|
46
|
-
direction
|
46
|
+
direction
|
47
47
|
} as proto.PagingInfo;
|
48
48
|
|
49
49
|
let startTime, endTime;
|
@@ -63,9 +63,9 @@ export class HistoryRpc {
|
|
63
63
|
contentFilters,
|
64
64
|
pagingInfo,
|
65
65
|
startTime,
|
66
|
-
endTime
|
66
|
+
endTime
|
67
67
|
},
|
68
|
-
response: undefined
|
68
|
+
response: undefined
|
69
69
|
});
|
70
70
|
}
|
71
71
|
|
package/src/lib/store/index.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
import type { Stream } from "@libp2p/interface
|
2
|
-
import type { PeerId } from "@libp2p/interface-peer-id";
|
1
|
+
import type { Stream } from "@libp2p/interface/connection";
|
3
2
|
import { sha256 } from "@noble/hashes/sha256";
|
4
3
|
import {
|
5
4
|
Cursor,
|
@@ -7,7 +6,7 @@ import {
|
|
7
6
|
IDecoder,
|
8
7
|
IStore,
|
9
8
|
Libp2p,
|
10
|
-
ProtocolCreateOptions
|
9
|
+
ProtocolCreateOptions
|
11
10
|
} from "@waku/interfaces";
|
12
11
|
import { proto_store as proto } from "@waku/proto";
|
13
12
|
import { isDefined } from "@waku/utils";
|
@@ -40,10 +39,6 @@ export interface TimeFilter {
|
|
40
39
|
}
|
41
40
|
|
42
41
|
export interface QueryOptions {
|
43
|
-
/**
|
44
|
-
* The peer to query. If undefined, a pseudo-random peer is selected from the connected Waku Store peers.
|
45
|
-
*/
|
46
|
-
peerId?: PeerId;
|
47
42
|
/**
|
48
43
|
* The direction in which pages are retrieved:
|
49
44
|
* - { @link PageDirection.BACKWARD }: Most recent page first.
|
@@ -80,12 +75,61 @@ export interface QueryOptions {
|
|
80
75
|
*/
|
81
76
|
class Store extends BaseProtocol implements IStore {
|
82
77
|
options: ProtocolCreateOptions;
|
78
|
+
private readonly NUM_PEERS_PROTOCOL = 1;
|
83
79
|
|
84
80
|
constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
|
85
81
|
super(StoreCodec, libp2p.components);
|
86
82
|
this.options = options ?? {};
|
87
83
|
}
|
88
84
|
|
85
|
+
/**
|
86
|
+
* Processes messages based on the provided callback and options.
|
87
|
+
* @private
|
88
|
+
*/
|
89
|
+
private async processMessages<T extends IDecodedMessage>(
|
90
|
+
messages: Promise<T | undefined>[],
|
91
|
+
callback: (message: T) => Promise<void | boolean> | boolean | void,
|
92
|
+
options?: QueryOptions
|
93
|
+
): Promise<boolean> {
|
94
|
+
let abort = false;
|
95
|
+
const messagesOrUndef: Array<T | undefined> = await Promise.all(messages);
|
96
|
+
let processedMessages: Array<T> = messagesOrUndef.filter(isDefined);
|
97
|
+
|
98
|
+
if (this.shouldReverseOrder(options)) {
|
99
|
+
processedMessages = processedMessages.reverse();
|
100
|
+
}
|
101
|
+
|
102
|
+
await Promise.all(
|
103
|
+
processedMessages.map(async (msg) => {
|
104
|
+
if (msg && !abort) {
|
105
|
+
abort = Boolean(await callback(msg));
|
106
|
+
}
|
107
|
+
})
|
108
|
+
);
|
109
|
+
|
110
|
+
return abort;
|
111
|
+
}
|
112
|
+
|
113
|
+
/**
|
114
|
+
* Determines whether to reverse the order of messages based on the provided options.
|
115
|
+
*
|
116
|
+
* Messages in pages are ordered from oldest (first) to most recent (last).
|
117
|
+
* https://github.com/vacp2p/rfc/issues/533
|
118
|
+
*
|
119
|
+
* @private
|
120
|
+
*/
|
121
|
+
private shouldReverseOrder(options?: QueryOptions): boolean {
|
122
|
+
return (
|
123
|
+
typeof options?.pageDirection === "undefined" ||
|
124
|
+
options?.pageDirection === PageDirection.BACKWARD
|
125
|
+
);
|
126
|
+
}
|
127
|
+
|
128
|
+
/**
|
129
|
+
* @deprecated Use `queryWithOrderedCallback` instead
|
130
|
+
**/
|
131
|
+
queryOrderedCallback = this.queryWithOrderedCallback;
|
132
|
+
|
89
133
|
/**
|
90
134
|
* Do a query to a Waku Store to retrieve historical/missed messages.
|
91
135
|
*
|
@@ -103,42 +147,20 @@ class Store extends BaseProtocol implements IStore {
|
|
103
147
|
* or if an error is encountered when processing the reply,
|
104
148
|
* or if two decoders with the same content topic are passed.
|
105
149
|
*/
|
106
|
-
async
|
150
|
+
async queryWithOrderedCallback<T extends IDecodedMessage>(
|
107
151
|
decoders: IDecoder<T>[],
|
108
152
|
callback: (message: T) => Promise<void | boolean> | boolean | void,
|
109
153
|
options?: QueryOptions
|
110
154
|
): Promise<void> {
|
111
|
-
let abort = false;
|
112
155
|
for await (const promises of this.queryGenerator(decoders, options)) {
|
113
|
-
if (
|
114
|
-
const messagesOrUndef: Array<T | undefined> = await Promise.all(promises);
|
115
|
-
|
116
|
-
let messages: Array<T> = messagesOrUndef.filter(isDefined);
|
117
|
-
|
118
|
-
// Messages in pages are ordered from oldest (first) to most recent (last).
|
119
|
-
// https://github.com/vacp2p/rfc/issues/533
|
120
|
-
if (
|
121
|
-
typeof options?.pageDirection === "undefined" ||
|
122
|
-
options?.pageDirection === PageDirection.BACKWARD
|
123
|
-
) {
|
124
|
-
messages = messages.reverse();
|
125
|
-
}
|
126
|
-
|
127
|
-
await Promise.all(
|
128
|
-
messages.map(async (msg) => {
|
129
|
-
if (msg && !abort) {
|
130
|
-
abort = Boolean(await callback(msg));
|
131
|
-
}
|
132
|
-
})
|
133
|
-
);
|
156
|
+
if (await this.processMessages(promises, callback, options)) break;
|
134
157
|
}
|
135
158
|
}
|
136
159
|
|
137
160
|
/**
|
138
161
|
* Do a query to a Waku Store to retrieve historical/missed messages.
|
139
|
-
*
|
140
162
|
* The callback function takes a `Promise<WakuMessage>` in input,
|
141
|
-
* useful if messages
|
163
|
+
* useful if messages need to be decrypted and performance matters.
|
142
164
|
*
|
143
165
|
* The order of the messages passed to the callback is as follows:
|
144
166
|
* - within a page, messages are expected to be ordered from oldest to most recent
|
@@ -152,7 +174,7 @@ class Store extends BaseProtocol implements IStore {
|
|
152
174
|
* or if an error is encountered when processing the reply,
|
153
175
|
* or if two decoders with the same content topic are passed.
|
154
176
|
*/
|
155
|
-
async
|
177
|
+
async queryWithPromiseCallback<T extends IDecodedMessage>(
|
156
178
|
decoders: IDecoder<T>[],
|
157
179
|
callback: (
|
158
180
|
message: Promise<T | undefined>
|
@@ -160,17 +182,15 @@ class Store extends BaseProtocol implements IStore {
|
|
160
182
|
options?: QueryOptions
|
161
183
|
): Promise<void> {
|
162
184
|
let abort = false;
|
163
|
-
let promises: Promise<void>[] = [];
|
164
185
|
for await (const page of this.queryGenerator(decoders, options)) {
|
165
|
-
const _promises = page.map(async (
|
166
|
-
if (
|
167
|
-
|
168
|
-
}
|
186
|
+
const _promises = page.map(async (msgPromise) => {
|
187
|
+
if (abort) return;
|
188
|
+
abort = Boolean(await callback(msgPromise));
|
169
189
|
});
|
170
190
|
|
171
|
-
|
191
|
+
await Promise.all(_promises);
|
192
|
+
if (abort) break;
|
172
193
|
}
|
173
|
-
await Promise.all(promises);
|
174
194
|
}
|
175
195
|
|
176
196
|
/**
|
@@ -183,9 +203,6 @@ class Store extends BaseProtocol implements IStore {
|
|
183
203
|
* as follows:
|
184
204
|
* - within a page, messages SHOULD be ordered from oldest to most recent
|
185
205
|
* - pages direction depends on { @link QueryOptions.pageDirection }
|
186
|
-
*
|
187
|
-
* However, there is no way to guarantee the behavior of the remote node.
|
188
|
-
*
|
189
206
|
* @throws If not able to reach a Waku Store peer to query,
|
190
207
|
* or if an error is encountered when processing the reply,
|
191
208
|
* or if two decoders with the same content topic are passed.
|
@@ -219,21 +236,23 @@ class Store extends BaseProtocol implements IStore {
|
|
219
236
|
{
|
220
237
|
pubSubTopic: pubSubTopic,
|
221
238
|
pageDirection: PageDirection.BACKWARD,
|
222
|
-
pageSize: DefaultPageSize
|
239
|
+
pageSize: DefaultPageSize
|
223
240
|
},
|
224
241
|
options,
|
225
242
|
{ contentTopics, startTime, endTime }
|
226
243
|
);
|
227
244
|
|
228
|
-
log("Querying history with the following options",
|
229
|
-
...options,
|
230
|
-
peerId: options?.peerId?.toString(),
|
231
|
-
});
|
245
|
+
log("Querying history with the following options", options);
|
232
246
|
|
233
|
-
const peer =
|
247
|
+
const peer = (
|
248
|
+
await this.getPeers({
|
249
|
+
numPeers: this.NUM_PEERS_PROTOCOL,
|
250
|
+
maxBootstrapPeers: 1
|
251
|
+
})
|
252
|
+
)[0];
|
234
253
|
|
235
254
|
for await (const messages of paginate<T>(
|
236
|
-
this.
|
255
|
+
this.getStream.bind(this, peer),
|
237
256
|
queryOpts,
|
238
257
|
decodersAsMap,
|
239
258
|
options?.cursor
|
@@ -369,7 +388,7 @@ export async function createCursor(
|
|
369
388
|
digest,
|
370
389
|
pubsubTopic,
|
371
390
|
senderTime: messageTime,
|
372
|
-
receiverTime: messageTime
|
391
|
+
receiverTime: messageTime
|
373
392
|
};
|
374
393
|
}
|
375
394
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import type { PeerUpdate } from "@libp2p/interface";
|
2
|
+
import type { Stream } from "@libp2p/interface/connection";
|
3
|
+
import { Peer } from "@libp2p/interface/peer-store";
|
4
|
+
import { Libp2p } from "@waku/interfaces";
|
5
|
+
import { selectConnection } from "@waku/utils/libp2p";
|
6
|
+
import debug from "debug";
|
7
|
+
|
8
|
+
export class StreamManager {
|
9
|
+
private streamPool: Map<string, Promise<Stream>>;
|
10
|
+
private log: debug.Debugger;
|
11
|
+
|
12
|
+
constructor(
|
13
|
+
public multicodec: string,
|
14
|
+
public getConnections: Libp2p["getConnections"],
|
15
|
+
public addEventListener: Libp2p["addEventListener"]
|
16
|
+
) {
|
17
|
+
this.log = debug(`waku:stream-manager:${multicodec}`);
|
18
|
+
this.addEventListener(
|
19
|
+
"peer:update",
|
20
|
+
this.handlePeerUpdateStreamPool.bind(this)
|
21
|
+
);
|
22
|
+
this.getStream = this.getStream.bind(this);
|
23
|
+
this.streamPool = new Map();
|
24
|
+
}
|
25
|
+
|
26
|
+
public async getStream(peer: Peer): Promise<Stream> {
|
27
|
+
const peerIdStr = peer.id.toString();
|
28
|
+
const streamPromise = this.streamPool.get(peerIdStr);
|
29
|
+
|
30
|
+
if (!streamPromise) {
|
31
|
+
return this.newStream(peer); // fallback by creating a new stream on the spot
|
32
|
+
}
|
33
|
+
|
34
|
+
// We have the stream, let's remove it from the map
|
35
|
+
this.streamPool.delete(peerIdStr);
|
36
|
+
|
37
|
+
this.prepareNewStream(peer);
|
38
|
+
|
39
|
+
const stream = await streamPromise;
|
40
|
+
|
41
|
+
if (stream.status === "closed") {
|
42
|
+
return this.newStream(peer); // fallback by creating a new stream on the spot
|
43
|
+
}
|
44
|
+
|
45
|
+
return stream;
|
46
|
+
}
|
47
|
+
|
48
|
+
private async newStream(peer: Peer): Promise<Stream> {
|
49
|
+
const connections = this.getConnections(peer.id);
|
50
|
+
const connection = selectConnection(connections);
|
51
|
+
if (!connection) {
|
52
|
+
throw new Error("Failed to get a connection to the peer");
|
53
|
+
}
|
54
|
+
return connection.newStream(this.multicodec);
|
55
|
+
}
|
56
|
+
|
57
|
+
private prepareNewStream(peer: Peer): void {
|
58
|
+
const streamPromise = this.newStream(peer);
|
59
|
+
this.streamPool.set(peer.id.toString(), streamPromise);
|
60
|
+
}
|
61
|
+
|
62
|
+
private handlePeerUpdateStreamPool = (evt: CustomEvent<PeerUpdate>): void => {
|
63
|
+
const peer = evt.detail.peer;
|
64
|
+
if (peer.protocols.includes(this.multicodec)) {
|
65
|
+
this.log(`Preemptively opening a stream to ${peer.id.toString()}`);
|
66
|
+
this.prepareNewStream(peer);
|
67
|
+
}
|
68
|
+
};
|
69
|
+
}
|