@waku/core 0.0.23 → 0.0.25-a42b7be.0
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 +34 -0
- package/bundle/{base_protocol-84d9b670.js → base_protocol-4bcf7514.js} +194 -134
- package/bundle/{browser-bde977a3.js → browser-90197c87.js} +26 -1
- package/bundle/index-27b91e3b.js +31 -0
- package/bundle/index.js +19384 -2421
- package/bundle/lib/base_protocol.js +3 -2
- package/bundle/lib/message/version_0.js +3 -2
- package/bundle/lib/predefined_bootstrap_nodes.js +2 -0
- package/bundle/{version_0-74b4b9db.js → version_0-2f1176e3.js} +36 -24
- package/dist/.tsbuildinfo +1 -1
- package/dist/lib/connection_manager.d.ts +17 -4
- package/dist/lib/connection_manager.js +111 -45
- package/dist/lib/connection_manager.js.map +1 -1
- package/dist/lib/filter/index.js +53 -37
- package/dist/lib/filter/index.js.map +1 -1
- package/dist/lib/keep_alive_manager.d.ts +1 -0
- package/dist/lib/keep_alive_manager.js +42 -18
- package/dist/lib/keep_alive_manager.js.map +1 -1
- package/dist/lib/light_push/index.js +60 -38
- package/dist/lib/light_push/index.js.map +1 -1
- package/dist/lib/light_push/push_rpc.d.ts +1 -1
- package/dist/lib/light_push/push_rpc.js +2 -2
- package/dist/lib/message/version_0.d.ts +13 -13
- package/dist/lib/message/version_0.js +22 -20
- package/dist/lib/message/version_0.js.map +1 -1
- package/dist/lib/store/history_rpc.d.ts +1 -1
- package/dist/lib/store/history_rpc.js +1 -1
- package/dist/lib/store/index.d.ts +1 -1
- package/dist/lib/store/index.js +43 -17
- package/dist/lib/store/index.js.map +1 -1
- package/dist/lib/stream_manager.d.ts +1 -1
- package/dist/lib/stream_manager.js +8 -5
- package/dist/lib/stream_manager.js.map +1 -1
- package/dist/lib/wait_for_remote_peer.d.ts +2 -2
- package/dist/lib/wait_for_remote_peer.js +13 -11
- package/dist/lib/wait_for_remote_peer.js.map +1 -1
- package/dist/lib/waku.d.ts +4 -3
- package/dist/lib/waku.js +13 -11
- package/dist/lib/waku.js.map +1 -1
- package/package.json +1 -137
- package/src/lib/connection_manager.ts +156 -51
- package/src/lib/filter/index.ts +76 -40
- package/src/lib/keep_alive_manager.ts +53 -20
- package/src/lib/light_push/index.ts +74 -38
- package/src/lib/light_push/push_rpc.ts +2 -2
- package/src/lib/message/version_0.ts +25 -17
- package/src/lib/store/history_rpc.ts +2 -2
- package/src/lib/store/index.ts +60 -23
- package/src/lib/stream_manager.ts +12 -7
- package/src/lib/wait_for_remote_peer.ts +13 -11
- package/src/lib/waku.ts +12 -9
- package/dist/lib/push_or_init_map.d.ts +0 -1
- package/dist/lib/push_or_init_map.js +0 -9
- package/dist/lib/push_or_init_map.js.map +0 -1
- package/src/lib/push_or_init_map.ts +0 -13
@@ -1,19 +1,19 @@
|
|
1
1
|
import type { PeerId } from "@libp2p/interface/peer-id";
|
2
2
|
import type { PeerStore } from "@libp2p/interface/peer-store";
|
3
|
-
import type { IRelay } from "@waku/interfaces";
|
3
|
+
import type { IRelay, PeerIdStr } from "@waku/interfaces";
|
4
4
|
import type { KeepAliveOptions } from "@waku/interfaces";
|
5
|
+
import { Logger } from "@waku/utils";
|
5
6
|
import { utf8ToBytes } from "@waku/utils/bytes";
|
6
|
-
import debug from "debug";
|
7
7
|
import type { PingService } from "libp2p/ping";
|
8
8
|
|
9
9
|
import { createEncoder } from "./message/version_0.js";
|
10
10
|
|
11
11
|
export const RelayPingContentTopic = "/relay-ping/1/ping/null";
|
12
|
-
const log =
|
12
|
+
const log = new Logger("keep-alive");
|
13
13
|
|
14
14
|
export class KeepAliveManager {
|
15
15
|
private pingKeepAliveTimers: Map<string, ReturnType<typeof setInterval>>;
|
16
|
-
private relayKeepAliveTimers: Map<PeerId, ReturnType<typeof setInterval
|
16
|
+
private relayKeepAliveTimers: Map<PeerId, ReturnType<typeof setInterval>[]>;
|
17
17
|
private options: KeepAliveOptions;
|
18
18
|
private relay?: IRelay;
|
19
19
|
|
@@ -37,14 +37,24 @@ export class KeepAliveManager {
|
|
37
37
|
|
38
38
|
const peerIdStr = peerId.toString();
|
39
39
|
|
40
|
+
// Ping the peer every pingPeriodSecs seconds
|
41
|
+
// if pingPeriodSecs is 0, don't ping the peer
|
40
42
|
if (pingPeriodSecs !== 0) {
|
41
43
|
const interval = setInterval(() => {
|
42
44
|
void (async () => {
|
45
|
+
let ping: number;
|
43
46
|
try {
|
44
47
|
// ping the peer for keep alive
|
45
48
|
// also update the peer store with the latency
|
46
|
-
|
47
|
-
|
49
|
+
try {
|
50
|
+
ping = await libp2pPing.ping(peerId);
|
51
|
+
log.info(`Ping succeeded (${peerIdStr})`, ping);
|
52
|
+
} catch (error) {
|
53
|
+
log.error(`Ping failed for peer (${peerIdStr}).
|
54
|
+
Next ping will be attempted in ${pingPeriodSecs} seconds.
|
55
|
+
`);
|
56
|
+
return;
|
57
|
+
}
|
48
58
|
|
49
59
|
try {
|
50
60
|
await peerStore.patch(peerId, {
|
@@ -53,10 +63,10 @@ export class KeepAliveManager {
|
|
53
63
|
}
|
54
64
|
});
|
55
65
|
} catch (e) {
|
56
|
-
log("Failed to update ping", e);
|
66
|
+
log.error("Failed to update ping", e);
|
57
67
|
}
|
58
68
|
} catch (e) {
|
59
|
-
log(`Ping failed (${peerIdStr})`, e);
|
69
|
+
log.error(`Ping failed (${peerIdStr})`, e);
|
60
70
|
}
|
61
71
|
})();
|
62
72
|
}, pingPeriodSecs * 1000);
|
@@ -66,17 +76,12 @@ export class KeepAliveManager {
|
|
66
76
|
|
67
77
|
const relay = this.relay;
|
68
78
|
if (relay && relayPeriodSecs !== 0) {
|
69
|
-
const
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
relay
|
76
|
-
.send(encoder, { payload: new Uint8Array([1]) })
|
77
|
-
.catch((e) => log("Failed to send relay ping", e));
|
78
|
-
}, relayPeriodSecs * 1000);
|
79
|
-
this.relayKeepAliveTimers.set(peerId, interval);
|
79
|
+
const intervals = this.scheduleRelayPings(
|
80
|
+
relay,
|
81
|
+
relayPeriodSecs,
|
82
|
+
peerId.toString()
|
83
|
+
);
|
84
|
+
this.relayKeepAliveTimers.set(peerId, intervals);
|
80
85
|
}
|
81
86
|
}
|
82
87
|
|
@@ -89,7 +94,7 @@ export class KeepAliveManager {
|
|
89
94
|
}
|
90
95
|
|
91
96
|
if (this.relayKeepAliveTimers.has(peerId)) {
|
92
|
-
|
97
|
+
this.relayKeepAliveTimers.get(peerId)?.map(clearInterval);
|
93
98
|
this.relayKeepAliveTimers.delete(peerId);
|
94
99
|
}
|
95
100
|
}
|
@@ -105,4 +110,32 @@ export class KeepAliveManager {
|
|
105
110
|
this.pingKeepAliveTimers.clear();
|
106
111
|
this.relayKeepAliveTimers.clear();
|
107
112
|
}
|
113
|
+
|
114
|
+
private scheduleRelayPings(
|
115
|
+
relay: IRelay,
|
116
|
+
relayPeriodSecs: number,
|
117
|
+
peerIdStr: PeerIdStr
|
118
|
+
): NodeJS.Timeout[] {
|
119
|
+
// send a ping message to each PubSubTopic the peer is part of
|
120
|
+
const intervals: NodeJS.Timeout[] = [];
|
121
|
+
for (const topic of relay.pubsubTopics) {
|
122
|
+
const meshPeers = relay.getMeshPeers(topic);
|
123
|
+
if (!meshPeers.includes(peerIdStr)) continue;
|
124
|
+
|
125
|
+
const encoder = createEncoder({
|
126
|
+
pubsubTopic: topic,
|
127
|
+
contentTopic: RelayPingContentTopic,
|
128
|
+
ephemeral: true
|
129
|
+
});
|
130
|
+
const interval = setInterval(() => {
|
131
|
+
log.info("Sending Waku Relay ping message");
|
132
|
+
relay
|
133
|
+
.send(encoder, { payload: new Uint8Array([1]) })
|
134
|
+
.catch((e) => log.error("Failed to send relay ping", e));
|
135
|
+
}, relayPeriodSecs * 1000);
|
136
|
+
intervals.push(interval);
|
137
|
+
}
|
138
|
+
|
139
|
+
return intervals;
|
140
|
+
}
|
108
141
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import type { Stream } from "@libp2p/interface/connection";
|
1
2
|
import type { PeerId } from "@libp2p/interface/peer-id";
|
2
3
|
import {
|
3
4
|
IEncoder,
|
@@ -5,12 +6,16 @@ import {
|
|
5
6
|
IMessage,
|
6
7
|
Libp2p,
|
7
8
|
ProtocolCreateOptions,
|
9
|
+
PubSubTopic,
|
8
10
|
SendError,
|
9
11
|
SendResult
|
10
12
|
} from "@waku/interfaces";
|
11
13
|
import { PushResponse } from "@waku/proto";
|
12
|
-
import {
|
13
|
-
|
14
|
+
import {
|
15
|
+
ensurePubsubTopicIsConfigured,
|
16
|
+
isMessageSizeUnderCap
|
17
|
+
} from "@waku/utils";
|
18
|
+
import { Logger } from "@waku/utils";
|
14
19
|
import all from "it-all";
|
15
20
|
import * as lp from "it-length-prefixed";
|
16
21
|
import { pipe } from "it-pipe";
|
@@ -21,7 +26,7 @@ import { DefaultPubSubTopic } from "../constants.js";
|
|
21
26
|
|
22
27
|
import { PushRpc } from "./push_rpc.js";
|
23
28
|
|
24
|
-
const log =
|
29
|
+
const log = new Logger("light-push");
|
25
30
|
|
26
31
|
export const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
|
27
32
|
export { PushResponse };
|
@@ -40,38 +45,43 @@ type PreparePushMessageResult =
|
|
40
45
|
* Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
|
41
46
|
*/
|
42
47
|
class LightPush extends BaseProtocol implements ILightPush {
|
43
|
-
|
48
|
+
private readonly pubsubTopics: PubSubTopic[];
|
44
49
|
private readonly NUM_PEERS_PROTOCOL = 1;
|
45
50
|
|
46
51
|
constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
|
47
52
|
super(LightPushCodec, libp2p.components);
|
48
|
-
this.
|
53
|
+
this.pubsubTopics = options?.pubsubTopics ?? [DefaultPubSubTopic];
|
49
54
|
}
|
50
55
|
|
51
56
|
private async preparePushMessage(
|
52
57
|
encoder: IEncoder,
|
53
58
|
message: IMessage,
|
54
|
-
|
59
|
+
pubsubTopic: string
|
55
60
|
): Promise<PreparePushMessageResult> {
|
56
61
|
try {
|
57
|
-
if (!
|
58
|
-
log("Failed to send waku light push:
|
62
|
+
if (!message.payload || message.payload.length === 0) {
|
63
|
+
log.error("Failed to send waku light push: payload is empty");
|
64
|
+
return { query: null, error: SendError.EMPTY_PAYLOAD };
|
65
|
+
}
|
66
|
+
|
67
|
+
if (!(await isMessageSizeUnderCap(encoder, message))) {
|
68
|
+
log.error("Failed to send waku light push: message is bigger than 1MB");
|
59
69
|
return { query: null, error: SendError.SIZE_TOO_BIG };
|
60
70
|
}
|
61
71
|
|
62
72
|
const protoMessage = await encoder.toProtoObj(message);
|
63
73
|
if (!protoMessage) {
|
64
|
-
log("Failed to encode to protoMessage, aborting push");
|
74
|
+
log.error("Failed to encode to protoMessage, aborting push");
|
65
75
|
return {
|
66
76
|
query: null,
|
67
77
|
error: SendError.ENCODE_FAILED
|
68
78
|
};
|
69
79
|
}
|
70
80
|
|
71
|
-
const query = PushRpc.createRequest(protoMessage,
|
81
|
+
const query = PushRpc.createRequest(protoMessage, pubsubTopic);
|
72
82
|
return { query, error: null };
|
73
83
|
} catch (error) {
|
74
|
-
log("Failed to prepare push message", error);
|
84
|
+
log.error("Failed to prepare push message", error);
|
75
85
|
|
76
86
|
return {
|
77
87
|
query: null,
|
@@ -81,13 +91,15 @@ class LightPush extends BaseProtocol implements ILightPush {
|
|
81
91
|
}
|
82
92
|
|
83
93
|
async send(encoder: IEncoder, message: IMessage): Promise<SendResult> {
|
84
|
-
const {
|
94
|
+
const { pubsubTopic } = encoder;
|
95
|
+
ensurePubsubTopicIsConfigured(pubsubTopic, this.pubsubTopics);
|
96
|
+
|
85
97
|
const recipients: PeerId[] = [];
|
86
98
|
|
87
99
|
const { query, error: preparationError } = await this.preparePushMessage(
|
88
100
|
encoder,
|
89
101
|
message,
|
90
|
-
|
102
|
+
pubsubTopic
|
91
103
|
);
|
92
104
|
|
93
105
|
if (preparationError || !query) {
|
@@ -97,48 +109,72 @@ class LightPush extends BaseProtocol implements ILightPush {
|
|
97
109
|
};
|
98
110
|
}
|
99
111
|
|
112
|
+
//TODO: get a relevant peer for the topic/shard
|
100
113
|
const peers = await this.getPeers({
|
101
114
|
maxBootstrapPeers: 1,
|
102
115
|
numPeers: this.NUM_PEERS_PROTOCOL
|
103
116
|
});
|
104
117
|
|
118
|
+
if (!peers.length) {
|
119
|
+
return {
|
120
|
+
recipients,
|
121
|
+
errors: [SendError.NO_PEER_AVAILABLE]
|
122
|
+
};
|
123
|
+
}
|
124
|
+
|
105
125
|
const promises = peers.map(async (peer) => {
|
106
|
-
let
|
107
|
-
|
126
|
+
let stream: Stream | undefined;
|
127
|
+
try {
|
128
|
+
stream = await this.getStream(peer);
|
129
|
+
} catch (err) {
|
130
|
+
log.error(
|
131
|
+
`Failed to get a stream for remote peer${peer.id.toString()}`,
|
132
|
+
err
|
133
|
+
);
|
134
|
+
return { recipients, error: SendError.REMOTE_PEER_FAULT };
|
135
|
+
}
|
108
136
|
|
137
|
+
let res: Uint8ArrayList[] | undefined;
|
109
138
|
try {
|
110
|
-
|
139
|
+
res = await pipe(
|
111
140
|
[query.encode()],
|
112
141
|
lp.encode,
|
113
142
|
stream,
|
114
143
|
lp.decode,
|
115
144
|
async (source) => await all(source)
|
116
145
|
);
|
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;
|
135
|
-
}
|
136
146
|
} catch (err) {
|
137
|
-
log("Failed to send waku light push request", err);
|
138
|
-
error
|
147
|
+
log.error("Failed to send waku light push request", err);
|
148
|
+
return { recipients, error: SendError.GENERIC_FAIL };
|
149
|
+
}
|
150
|
+
|
151
|
+
const bytes = new Uint8ArrayList();
|
152
|
+
res.forEach((chunk) => {
|
153
|
+
bytes.append(chunk);
|
154
|
+
});
|
155
|
+
|
156
|
+
let response: PushResponse | undefined;
|
157
|
+
try {
|
158
|
+
response = PushRpc.decode(bytes).response;
|
159
|
+
} catch (err) {
|
160
|
+
log.error("Failed to decode push reply", err);
|
161
|
+
return { recipients, error: SendError.DECODE_FAILED };
|
139
162
|
}
|
140
163
|
|
141
|
-
|
164
|
+
if (!response) {
|
165
|
+
log.error("Remote peer fault: No response in PushRPC");
|
166
|
+
return { recipients, error: SendError.REMOTE_PEER_FAULT };
|
167
|
+
}
|
168
|
+
|
169
|
+
if (!response.isSuccess) {
|
170
|
+
log.error("Remote peer rejected the message: ", response.info);
|
171
|
+
return { recipients, error: SendError.REMOTE_PEER_REJECTED };
|
172
|
+
}
|
173
|
+
|
174
|
+
recipients.some((recipient) => recipient.equals(peer.id)) ||
|
175
|
+
recipients.push(peer.id);
|
176
|
+
|
177
|
+
return { recipients };
|
142
178
|
});
|
143
179
|
|
144
180
|
const results = await Promise.allSettled(promises);
|
@@ -7,13 +7,13 @@ export class PushRpc {
|
|
7
7
|
|
8
8
|
static createRequest(
|
9
9
|
message: proto.WakuMessage,
|
10
|
-
|
10
|
+
pubsubTopic: string
|
11
11
|
): PushRpc {
|
12
12
|
return new PushRpc({
|
13
13
|
requestId: uuid(),
|
14
14
|
request: {
|
15
15
|
message: message,
|
16
|
-
pubsubTopic:
|
16
|
+
pubsubTopic: pubsubTopic
|
17
17
|
},
|
18
18
|
response: undefined
|
19
19
|
});
|
@@ -6,12 +6,15 @@ import type {
|
|
6
6
|
IMessage,
|
7
7
|
IMetaSetter,
|
8
8
|
IProtoMessage,
|
9
|
-
IRateLimitProof
|
9
|
+
IRateLimitProof,
|
10
|
+
PubSubTopic
|
10
11
|
} from "@waku/interfaces";
|
11
12
|
import { proto_message as proto } from "@waku/proto";
|
12
|
-
import
|
13
|
+
import { Logger } from "@waku/utils";
|
13
14
|
|
14
|
-
|
15
|
+
import { DefaultPubSubTopic } from "../constants.js";
|
16
|
+
|
17
|
+
const log = new Logger("message:version-0");
|
15
18
|
const OneMillion = BigInt(1_000_000);
|
16
19
|
|
17
20
|
export const Version = 0;
|
@@ -19,7 +22,7 @@ export { proto };
|
|
19
22
|
|
20
23
|
export class DecodedMessage implements IDecodedMessage {
|
21
24
|
constructor(
|
22
|
-
public
|
25
|
+
public pubsubTopic: string,
|
23
26
|
protected proto: proto.WakuMessage
|
24
27
|
) {}
|
25
28
|
|
@@ -73,6 +76,7 @@ export class Encoder implements IEncoder {
|
|
73
76
|
constructor(
|
74
77
|
public contentTopic: string,
|
75
78
|
public ephemeral: boolean = false,
|
79
|
+
public pubsubTopic: PubSubTopic,
|
76
80
|
public metaSetter?: IMetaSetter
|
77
81
|
) {
|
78
82
|
if (!contentTopic || contentTopic === "") {
|
@@ -109,22 +113,25 @@ export class Encoder implements IEncoder {
|
|
109
113
|
/**
|
110
114
|
* Creates an encoder that encode messages without Waku level encryption or signature.
|
111
115
|
*
|
112
|
-
* An encoder is used to encode messages in the [
|
116
|
+
* An encoder is used to encode messages in the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/)
|
113
117
|
* format to be sent over the Waku network. The resulting encoder can then be
|
114
|
-
* pass to { @link @waku/interfaces.
|
115
|
-
* { @link @waku/interfaces.Relay.send } to automatically encode outgoing
|
118
|
+
* pass to { @link @waku/interfaces!ISender.send } to automatically encode outgoing
|
116
119
|
* messages.
|
117
120
|
*/
|
118
121
|
export function createEncoder({
|
122
|
+
pubsubTopic = DefaultPubSubTopic,
|
119
123
|
contentTopic,
|
120
124
|
ephemeral,
|
121
125
|
metaSetter
|
122
126
|
}: EncoderOptions): Encoder {
|
123
|
-
return new Encoder(contentTopic, ephemeral, metaSetter);
|
127
|
+
return new Encoder(contentTopic, ephemeral, pubsubTopic, metaSetter);
|
124
128
|
}
|
125
129
|
|
126
130
|
export class Decoder implements IDecoder<DecodedMessage> {
|
127
|
-
constructor(
|
131
|
+
constructor(
|
132
|
+
public pubsubTopic: PubSubTopic,
|
133
|
+
public contentTopic: string
|
134
|
+
) {
|
128
135
|
if (!contentTopic || contentTopic === "") {
|
129
136
|
throw new Error("Content topic must be specified");
|
130
137
|
}
|
@@ -132,7 +139,6 @@ export class Decoder implements IDecoder<DecodedMessage> {
|
|
132
139
|
|
133
140
|
fromWireToProtoObj(bytes: Uint8Array): Promise<IProtoMessage | undefined> {
|
134
141
|
const protoMessage = proto.WakuMessage.decode(bytes);
|
135
|
-
log("Message decoded", protoMessage);
|
136
142
|
return Promise.resolve({
|
137
143
|
payload: protoMessage.payload,
|
138
144
|
contentTopic: protoMessage.contentTopic,
|
@@ -145,13 +151,13 @@ export class Decoder implements IDecoder<DecodedMessage> {
|
|
145
151
|
}
|
146
152
|
|
147
153
|
async fromProtoObj(
|
148
|
-
|
154
|
+
pubsubTopic: string,
|
149
155
|
proto: IProtoMessage
|
150
156
|
): Promise<DecodedMessage | undefined> {
|
151
157
|
// https://rfc.vac.dev/spec/14/
|
152
158
|
// > If omitted, the value SHOULD be interpreted as version 0.
|
153
159
|
if (proto.version ?? 0 !== Version) {
|
154
|
-
log(
|
160
|
+
log.error(
|
155
161
|
"Failed to decode due to incorrect version, expected:",
|
156
162
|
Version,
|
157
163
|
", actual:",
|
@@ -160,7 +166,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
|
|
160
166
|
return Promise.resolve(undefined);
|
161
167
|
}
|
162
168
|
|
163
|
-
return new DecodedMessage(
|
169
|
+
return new DecodedMessage(pubsubTopic, proto);
|
164
170
|
}
|
165
171
|
}
|
166
172
|
|
@@ -169,12 +175,14 @@ export class Decoder implements IDecoder<DecodedMessage> {
|
|
169
175
|
*
|
170
176
|
* A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/)
|
171
177
|
* format when received from the Waku network. The resulting decoder can then be
|
172
|
-
* pass to { @link @waku/interfaces.
|
173
|
-
* { @link @waku/interfaces.Relay.subscribe } to automatically decode incoming
|
178
|
+
* pass to { @link @waku/interfaces!IReceiver.subscribe } to automatically decode incoming
|
174
179
|
* messages.
|
175
180
|
*
|
176
181
|
* @param contentTopic The resulting decoder will only decode messages with this content topic.
|
177
182
|
*/
|
178
|
-
export function createDecoder(
|
179
|
-
|
183
|
+
export function createDecoder(
|
184
|
+
contentTopic: string,
|
185
|
+
pubsubTopic: PubSubTopic = DefaultPubSubTopic
|
186
|
+
): Decoder {
|
187
|
+
return new Decoder(pubsubTopic, contentTopic);
|
180
188
|
}
|
@@ -11,7 +11,7 @@ export enum PageDirection {
|
|
11
11
|
|
12
12
|
export interface Params {
|
13
13
|
contentTopics: string[];
|
14
|
-
|
14
|
+
pubsubTopic: string;
|
15
15
|
pageDirection: PageDirection;
|
16
16
|
pageSize: number;
|
17
17
|
startTime?: Date;
|
@@ -59,7 +59,7 @@ export class HistoryRpc {
|
|
59
59
|
return new HistoryRpc({
|
60
60
|
requestId: uuid(),
|
61
61
|
query: {
|
62
|
-
pubsubTopic: params.
|
62
|
+
pubsubTopic: params.pubsubTopic,
|
63
63
|
contentFilters,
|
64
64
|
pagingInfo,
|
65
65
|
startTime,
|
package/src/lib/store/index.ts
CHANGED
@@ -6,12 +6,13 @@ import {
|
|
6
6
|
IDecoder,
|
7
7
|
IStore,
|
8
8
|
Libp2p,
|
9
|
-
ProtocolCreateOptions
|
9
|
+
ProtocolCreateOptions,
|
10
|
+
PubSubTopic
|
10
11
|
} from "@waku/interfaces";
|
11
12
|
import { proto_store as proto } from "@waku/proto";
|
12
|
-
import { isDefined } from "@waku/utils";
|
13
|
+
import { ensurePubsubTopicIsConfigured, isDefined } from "@waku/utils";
|
14
|
+
import { Logger } from "@waku/utils";
|
13
15
|
import { concat, utf8ToBytes } from "@waku/utils/bytes";
|
14
|
-
import debug from "debug";
|
15
16
|
import all from "it-all";
|
16
17
|
import * as lp from "it-length-prefixed";
|
17
18
|
import { pipe } from "it-pipe";
|
@@ -25,7 +26,7 @@ import { HistoryRpc, PageDirection, Params } from "./history_rpc.js";
|
|
25
26
|
|
26
27
|
import HistoryError = proto.HistoryResponse.HistoryError;
|
27
28
|
|
28
|
-
const log =
|
29
|
+
const log = new Logger("store");
|
29
30
|
|
30
31
|
export const StoreCodec = "/vac/waku/store/2.0.0-beta4";
|
31
32
|
|
@@ -74,12 +75,12 @@ export interface QueryOptions {
|
|
74
75
|
* The Waku Store protocol can be used to retrieved historical messages.
|
75
76
|
*/
|
76
77
|
class Store extends BaseProtocol implements IStore {
|
77
|
-
|
78
|
+
private readonly pubsubTopics: PubSubTopic[];
|
78
79
|
private readonly NUM_PEERS_PROTOCOL = 1;
|
79
80
|
|
80
81
|
constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
|
81
82
|
super(StoreCodec, libp2p.components);
|
82
|
-
this.
|
83
|
+
this.pubsubTopics = options?.pubsubTopics ?? [DefaultPubSubTopic];
|
83
84
|
}
|
84
85
|
|
85
86
|
/**
|
@@ -206,12 +207,20 @@ class Store extends BaseProtocol implements IStore {
|
|
206
207
|
* @throws If not able to reach a Waku Store peer to query,
|
207
208
|
* or if an error is encountered when processing the reply,
|
208
209
|
* or if two decoders with the same content topic are passed.
|
210
|
+
*
|
211
|
+
* This API only supports querying a single pubsub topic at a time.
|
212
|
+
* If multiple decoders are provided, they must all have the same pubsub topic.
|
213
|
+
* @throws If multiple decoders with different pubsub topics are provided.
|
214
|
+
* @throws If no decoders are provided.
|
215
|
+
* @throws If no decoders are found for the provided pubsub topic.
|
209
216
|
*/
|
210
217
|
async *queryGenerator<T extends IDecodedMessage>(
|
211
218
|
decoders: IDecoder<T>[],
|
212
219
|
options?: QueryOptions
|
213
220
|
): AsyncGenerator<Promise<T | undefined>[]> {
|
214
|
-
|
221
|
+
if (decoders.length === 0) {
|
222
|
+
throw new Error("No decoders provided");
|
223
|
+
}
|
215
224
|
|
216
225
|
let startTime, endTime;
|
217
226
|
|
@@ -220,6 +229,33 @@ class Store extends BaseProtocol implements IStore {
|
|
220
229
|
endTime = options.timeFilter.endTime;
|
221
230
|
}
|
222
231
|
|
232
|
+
// convert array to set to remove duplicates
|
233
|
+
const uniquePubSubTopicsInQuery = Array.from(
|
234
|
+
new Set(decoders.map((decoder) => decoder.pubsubTopic))
|
235
|
+
);
|
236
|
+
|
237
|
+
// If multiple pubsub topics are provided, throw an error
|
238
|
+
if (uniquePubSubTopicsInQuery.length > 1) {
|
239
|
+
throw new Error(
|
240
|
+
"API does not support querying multiple pubsub topics at once"
|
241
|
+
);
|
242
|
+
}
|
243
|
+
|
244
|
+
// we can be certain that there is only one pubsub topic in the query
|
245
|
+
const pubSubTopicForQuery = uniquePubSubTopicsInQuery[0];
|
246
|
+
|
247
|
+
ensurePubsubTopicIsConfigured(pubSubTopicForQuery, this.pubsubTopics);
|
248
|
+
|
249
|
+
// check that the pubsubTopic from the Cursor and Decoder match
|
250
|
+
if (
|
251
|
+
options?.cursor?.pubsubTopic &&
|
252
|
+
options.cursor.pubsubTopic !== pubSubTopicForQuery
|
253
|
+
) {
|
254
|
+
throw new Error(
|
255
|
+
`Cursor pubsub topic (${options?.cursor?.pubsubTopic}) does not match decoder pubsub topic (${pubSubTopicForQuery})`
|
256
|
+
);
|
257
|
+
}
|
258
|
+
|
223
259
|
const decodersAsMap = new Map();
|
224
260
|
decoders.forEach((dec) => {
|
225
261
|
if (decodersAsMap.has(dec.contentTopic)) {
|
@@ -230,11 +266,17 @@ class Store extends BaseProtocol implements IStore {
|
|
230
266
|
decodersAsMap.set(dec.contentTopic, dec);
|
231
267
|
});
|
232
268
|
|
233
|
-
const contentTopics = decoders
|
269
|
+
const contentTopics = decoders
|
270
|
+
.filter((decoder) => decoder.pubsubTopic === pubSubTopicForQuery)
|
271
|
+
.map((dec) => dec.contentTopic);
|
272
|
+
|
273
|
+
if (contentTopics.length === 0) {
|
274
|
+
throw new Error("No decoders found for topic " + pubSubTopicForQuery);
|
275
|
+
}
|
234
276
|
|
235
277
|
const queryOpts = Object.assign(
|
236
278
|
{
|
237
|
-
|
279
|
+
pubsubTopic: pubSubTopicForQuery,
|
238
280
|
pageDirection: PageDirection.BACKWARD,
|
239
281
|
pageSize: DefaultPageSize
|
240
282
|
},
|
@@ -242,8 +284,6 @@ class Store extends BaseProtocol implements IStore {
|
|
242
284
|
{ contentTopics, startTime, endTime }
|
243
285
|
);
|
244
286
|
|
245
|
-
log("Querying history with the following options", options);
|
246
|
-
|
247
287
|
const peer = (
|
248
288
|
await this.getPeers({
|
249
289
|
numPeers: this.NUM_PEERS_PROTOCOL,
|
@@ -283,9 +323,9 @@ async function* paginate<T extends IDecodedMessage>(
|
|
283
323
|
|
284
324
|
const historyRpcQuery = HistoryRpc.createQuery(queryOpts);
|
285
325
|
|
286
|
-
log(
|
326
|
+
log.info(
|
287
327
|
"Querying store peer",
|
288
|
-
`for (${queryOpts.
|
328
|
+
`for (${queryOpts.pubsubTopic})`,
|
289
329
|
queryOpts.contentTopics
|
290
330
|
);
|
291
331
|
|
@@ -307,7 +347,7 @@ async function* paginate<T extends IDecodedMessage>(
|
|
307
347
|
const reply = historyRpcQuery.decode(bytes);
|
308
348
|
|
309
349
|
if (!reply.response) {
|
310
|
-
log("Stopping pagination due to store `response` field missing");
|
350
|
+
log.warn("Stopping pagination due to store `response` field missing");
|
311
351
|
break;
|
312
352
|
}
|
313
353
|
|
@@ -318,13 +358,13 @@ async function* paginate<T extends IDecodedMessage>(
|
|
318
358
|
}
|
319
359
|
|
320
360
|
if (!response.messages || !response.messages.length) {
|
321
|
-
log(
|
361
|
+
log.warn(
|
322
362
|
"Stopping pagination due to store `response.messages` field missing or empty"
|
323
363
|
);
|
324
364
|
break;
|
325
365
|
}
|
326
366
|
|
327
|
-
log(`${response.messages.length} messages retrieved from store`);
|
367
|
+
log.error(`${response.messages.length} messages retrieved from store`);
|
328
368
|
|
329
369
|
yield response.messages.map((protoMsg) => {
|
330
370
|
const contentTopic = protoMsg.contentTopic;
|
@@ -332,7 +372,7 @@ async function* paginate<T extends IDecodedMessage>(
|
|
332
372
|
const decoder = decoders.get(contentTopic);
|
333
373
|
if (decoder) {
|
334
374
|
return decoder.fromProtoObj(
|
335
|
-
queryOpts.
|
375
|
+
queryOpts.pubsubTopic,
|
336
376
|
toProtoMessage(protoMsg)
|
337
377
|
);
|
338
378
|
}
|
@@ -344,7 +384,7 @@ async function* paginate<T extends IDecodedMessage>(
|
|
344
384
|
if (typeof nextCursor === "undefined") {
|
345
385
|
// If the server does not return cursor then there is an issue,
|
346
386
|
// Need to abort, or we end up in an infinite loop
|
347
|
-
log(
|
387
|
+
log.warn(
|
348
388
|
"Stopping pagination due to `response.pagingInfo.cursor` missing from store response"
|
349
389
|
);
|
350
390
|
break;
|
@@ -365,10 +405,7 @@ async function* paginate<T extends IDecodedMessage>(
|
|
365
405
|
}
|
366
406
|
}
|
367
407
|
|
368
|
-
export async function createCursor(
|
369
|
-
message: IDecodedMessage,
|
370
|
-
pubsubTopic: string = DefaultPubSubTopic
|
371
|
-
): Promise<Cursor> {
|
408
|
+
export async function createCursor(message: IDecodedMessage): Promise<Cursor> {
|
372
409
|
if (
|
373
410
|
!message ||
|
374
411
|
!message.timestamp ||
|
@@ -386,7 +423,7 @@ export async function createCursor(
|
|
386
423
|
|
387
424
|
return {
|
388
425
|
digest,
|
389
|
-
pubsubTopic,
|
426
|
+
pubsubTopic: message.pubsubTopic,
|
390
427
|
senderTime: messageTime,
|
391
428
|
receiverTime: messageTime
|
392
429
|
};
|