@waku/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +614 -0
- package/README.md +56 -0
- package/bundle/browser-1e1a2f27.js +722 -0
- package/bundle/crypto-8551d579.js +2585 -0
- package/bundle/crypto-b00764b7.js +1772 -0
- package/bundle/enr-564d4a51.js +20785 -0
- package/bundle/enr-9fc5eed8.js +20786 -0
- package/bundle/enr-f6e82a53.js +20785 -0
- package/bundle/events-158407bb.js +1929 -0
- package/bundle/events-fcbda4dc.js +76 -0
- package/bundle/index-02d21809.js +20 -0
- package/bundle/index-0a4bdddc.js +2976 -0
- package/bundle/index-2ae915be.js +1854 -0
- package/bundle/index-64ce43f0.js +69 -0
- package/bundle/index-691c0be6.js +4059 -0
- package/bundle/index-a013a259.js +20 -0
- package/bundle/index-ba42b4fc.js +862 -0
- package/bundle/index.js +13428 -0
- package/bundle/lib/enr.js +8 -0
- package/bundle/lib/peer_discovery_dns.js +5018 -0
- package/bundle/lib/peer_discovery_static_list.js +75 -0
- package/bundle/lib/predefined_bootstrap_nodes.js +59 -0
- package/bundle/lib/utils.js +1 -0
- package/bundle/lib/wait_for_remote_peer.js +327 -0
- package/bundle/lib/waku_message/topic_only_message.js +4 -0
- package/bundle/lib/waku_message/version_0.js +4 -0
- package/bundle/lib/waku_message/version_1.js +463 -0
- package/bundle/message-e2db79d7.js +8393 -0
- package/bundle/multiaddr_to_peer_info-c406b1e1.js +19 -0
- package/bundle/multiaddr_to_peer_info-fd1de516.js +19 -0
- package/bundle/random_subset-75d1c511.js +26 -0
- package/bundle/topic_only_message-34f36fa6.js +82 -0
- package/bundle/utils-9a3221f2.js +815 -0
- package/bundle/version_0-e6fe440c.js +317 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/constants.d.ts +4 -0
- package/dist/lib/constants.js +5 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/crypto.d.ts +34 -0
- package/dist/lib/crypto.js +79 -0
- package/dist/lib/crypto.js.map +1 -0
- package/dist/lib/enr/constants.d.ts +4 -0
- package/dist/lib/enr/constants.js +8 -0
- package/dist/lib/enr/constants.js.map +1 -0
- package/dist/lib/enr/enr.d.ts +90 -0
- package/dist/lib/enr/enr.js +432 -0
- package/dist/lib/enr/enr.js.map +1 -0
- package/dist/lib/enr/index.d.ts +5 -0
- package/dist/lib/enr/index.js +6 -0
- package/dist/lib/enr/index.js.map +1 -0
- package/dist/lib/enr/keypair/index.d.ts +8 -0
- package/dist/lib/enr/keypair/index.js +53 -0
- package/dist/lib/enr/keypair/index.js.map +1 -0
- package/dist/lib/enr/keypair/secp256k1.d.ts +13 -0
- package/dist/lib/enr/keypair/secp256k1.js +57 -0
- package/dist/lib/enr/keypair/secp256k1.js.map +1 -0
- package/dist/lib/enr/keypair/types.d.ts +13 -0
- package/dist/lib/enr/keypair/types.js +7 -0
- package/dist/lib/enr/keypair/types.js.map +1 -0
- package/dist/lib/enr/multiaddr_from_fields.d.ts +2 -0
- package/dist/lib/enr/multiaddr_from_fields.js +8 -0
- package/dist/lib/enr/multiaddr_from_fields.js.map +1 -0
- package/dist/lib/enr/multiaddrs_codec.d.ts +3 -0
- package/dist/lib/enr/multiaddrs_codec.js +32 -0
- package/dist/lib/enr/multiaddrs_codec.js.map +1 -0
- package/dist/lib/enr/types.d.ts +8 -0
- package/dist/lib/enr/types.js +3 -0
- package/dist/lib/enr/types.js.map +1 -0
- package/dist/lib/enr/v4.d.ts +3 -0
- package/dist/lib/enr/v4.js +14 -0
- package/dist/lib/enr/v4.js.map +1 -0
- package/dist/lib/enr/waku2_codec.d.ts +8 -0
- package/dist/lib/enr/waku2_codec.js +36 -0
- package/dist/lib/enr/waku2_codec.js.map +1 -0
- package/dist/lib/group_by.d.ts +3 -0
- package/dist/lib/group_by.js +13 -0
- package/dist/lib/group_by.js.map +1 -0
- package/dist/lib/multiaddr_to_peer_info.d.ts +3 -0
- package/dist/lib/multiaddr_to_peer_info.js +15 -0
- package/dist/lib/multiaddr_to_peer_info.js.map +1 -0
- package/dist/lib/peer_discovery_dns/dns.d.ts +48 -0
- package/dist/lib/peer_discovery_dns/dns.js +158 -0
- package/dist/lib/peer_discovery_dns/dns.js.map +1 -0
- package/dist/lib/peer_discovery_dns/dns_over_https.d.ts +32 -0
- package/dist/lib/peer_discovery_dns/dns_over_https.js +87 -0
- package/dist/lib/peer_discovery_dns/dns_over_https.js.map +1 -0
- package/dist/lib/peer_discovery_dns/enrtree.d.ts +33 -0
- package/dist/lib/peer_discovery_dns/enrtree.js +76 -0
- package/dist/lib/peer_discovery_dns/enrtree.js.map +1 -0
- package/dist/lib/peer_discovery_dns/fetch_nodes.d.ts +14 -0
- package/dist/lib/peer_discovery_dns/fetch_nodes.js +133 -0
- package/dist/lib/peer_discovery_dns/fetch_nodes.js.map +1 -0
- package/dist/lib/peer_discovery_dns/index.d.ts +30 -0
- package/dist/lib/peer_discovery_dns/index.js +54 -0
- package/dist/lib/peer_discovery_dns/index.js.map +1 -0
- package/dist/lib/peer_discovery_static_list.d.ts +44 -0
- package/dist/lib/peer_discovery_static_list.js +72 -0
- package/dist/lib/peer_discovery_static_list.js.map +1 -0
- package/dist/lib/predefined_bootstrap_nodes.d.ts +35 -0
- package/dist/lib/predefined_bootstrap_nodes.js +56 -0
- package/dist/lib/predefined_bootstrap_nodes.js.map +1 -0
- package/dist/lib/push_or_init_map.d.ts +1 -0
- package/dist/lib/push_or_init_map.js +9 -0
- package/dist/lib/push_or_init_map.js.map +1 -0
- package/dist/lib/random_subset.d.ts +4 -0
- package/dist/lib/random_subset.js +25 -0
- package/dist/lib/random_subset.js.map +1 -0
- package/dist/lib/select_connection.d.ts +2 -0
- package/dist/lib/select_connection.js +19 -0
- package/dist/lib/select_connection.js.map +1 -0
- package/dist/lib/select_peer.d.ts +15 -0
- package/dist/lib/select_peer.js +59 -0
- package/dist/lib/select_peer.js.map +1 -0
- package/dist/lib/to_proto_message.d.ts +3 -0
- package/dist/lib/to_proto_message.js +11 -0
- package/dist/lib/to_proto_message.js.map +1 -0
- package/dist/lib/utils.d.ts +22 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/wait_for_remote_peer.d.ts +22 -0
- package/dist/lib/wait_for_remote_peer.js +113 -0
- package/dist/lib/wait_for_remote_peer.js.map +1 -0
- package/dist/lib/waku.d.ts +61 -0
- package/dist/lib/waku.js +174 -0
- package/dist/lib/waku.js.map +1 -0
- package/dist/lib/waku_filter/filter_rpc.d.ts +25 -0
- package/dist/lib/waku_filter/filter_rpc.js +44 -0
- package/dist/lib/waku_filter/filter_rpc.js.map +1 -0
- package/dist/lib/waku_filter/index.d.ts +50 -0
- package/dist/lib/waku_filter/index.js +181 -0
- package/dist/lib/waku_filter/index.js.map +1 -0
- package/dist/lib/waku_light_push/index.d.ts +38 -0
- package/dist/lib/waku_light_push/index.js +83 -0
- package/dist/lib/waku_light_push/index.js.map +1 -0
- package/dist/lib/waku_light_push/push_rpc.d.ts +11 -0
- package/dist/lib/waku_light_push/push_rpc.js +31 -0
- package/dist/lib/waku_light_push/push_rpc.js.map +1 -0
- package/dist/lib/waku_message/constants.d.ts +12 -0
- package/dist/lib/waku_message/constants.js +10 -0
- package/dist/lib/waku_message/constants.js.map +1 -0
- package/dist/lib/waku_message/ecies.d.ts +17 -0
- package/dist/lib/waku_message/ecies.js +126 -0
- package/dist/lib/waku_message/ecies.js.map +1 -0
- package/dist/lib/waku_message/symmetric.d.ts +3 -0
- package/dist/lib/waku_message/symmetric.js +18 -0
- package/dist/lib/waku_message/symmetric.js.map +1 -0
- package/dist/lib/waku_message/topic_only_message.d.ts +15 -0
- package/dist/lib/waku_message/topic_only_message.js +31 -0
- package/dist/lib/waku_message/topic_only_message.js.map +1 -0
- package/dist/lib/waku_message/version_0.d.ts +26 -0
- package/dist/lib/waku_message/version_0.js +96 -0
- package/dist/lib/waku_message/version_0.js.map +1 -0
- package/dist/lib/waku_message/version_1.d.ts +93 -0
- package/dist/lib/waku_message/version_1.js +325 -0
- package/dist/lib/waku_message/version_1.js.map +1 -0
- package/dist/lib/waku_relay/constants.d.ts +63 -0
- package/dist/lib/waku_relay/constants.js +67 -0
- package/dist/lib/waku_relay/constants.js.map +1 -0
- package/dist/lib/waku_relay/index.d.ts +65 -0
- package/dist/lib/waku_relay/index.js +111 -0
- package/dist/lib/waku_relay/index.js.map +1 -0
- package/dist/lib/waku_store/history_rpc.d.ts +27 -0
- package/dist/lib/waku_store/history_rpc.js +71 -0
- package/dist/lib/waku_store/history_rpc.js.map +1 -0
- package/dist/lib/waku_store/index.d.ts +126 -0
- package/dist/lib/waku_store/index.js +218 -0
- package/dist/lib/waku_store/index.js.map +1 -0
- package/dist/proto/filter.d.ts +65 -0
- package/dist/proto/filter.js +425 -0
- package/dist/proto/filter.js.map +1 -0
- package/dist/proto/light_push.d.ts +57 -0
- package/dist/proto/light_push.js +369 -0
- package/dist/proto/light_push.js.map +1 -0
- package/dist/proto/message.d.ts +29 -0
- package/dist/proto/message.js +215 -0
- package/dist/proto/message.js.map +1 -0
- package/dist/proto/store.d.ts +104 -0
- package/dist/proto/store.js +602 -0
- package/dist/proto/store.js.map +1 -0
- package/dist/proto/topic_only_message.d.ts +10 -0
- package/dist/proto/topic_only_message.js +46 -0
- package/dist/proto/topic_only_message.js.map +1 -0
- package/package.json +292 -0
- package/src/index.ts +33 -0
- package/src/lib/constants.ts +4 -0
- package/src/lib/crypto.ts +100 -0
- package/src/lib/enr/constants.ts +10 -0
- package/src/lib/enr/enr.ts +516 -0
- package/src/lib/enr/index.ts +5 -0
- package/src/lib/enr/keypair/index.ts +76 -0
- package/src/lib/enr/keypair/secp256k1.ts +69 -0
- package/src/lib/enr/keypair/types.ts +14 -0
- package/src/lib/enr/multiaddr_from_fields.ts +18 -0
- package/src/lib/enr/multiaddrs_codec.ts +50 -0
- package/src/lib/enr/types.ts +11 -0
- package/src/lib/enr/v4.ts +22 -0
- package/src/lib/enr/waku2_codec.ts +39 -0
- package/src/lib/group_by.ts +14 -0
- package/src/lib/multiaddr_to_peer_info.ts +17 -0
- package/src/lib/peer_discovery_dns/dns.ts +223 -0
- package/src/lib/peer_discovery_dns/dns_over_https.ts +98 -0
- package/src/lib/peer_discovery_dns/enrtree.ts +123 -0
- package/src/lib/peer_discovery_dns/fetch_nodes.ts +180 -0
- package/src/lib/peer_discovery_dns/index.ts +84 -0
- package/src/lib/peer_discovery_static_list.ts +118 -0
- package/src/lib/predefined_bootstrap_nodes.ts +72 -0
- package/src/lib/push_or_init_map.ts +13 -0
- package/src/lib/random_subset.ts +30 -0
- package/src/lib/select_connection.ts +24 -0
- package/src/lib/select_peer.ts +77 -0
- package/src/lib/to_proto_message.ts +15 -0
- package/src/lib/utils.ts +50 -0
- package/src/lib/wait_for_remote_peer.ts +151 -0
- package/src/lib/waku.ts +258 -0
- package/src/lib/waku_filter/filter_rpc.ts +57 -0
- package/src/lib/waku_filter/index.ts +291 -0
- package/src/lib/waku_light_push/index.ts +137 -0
- package/src/lib/waku_light_push/push_rpc.ts +39 -0
- package/src/lib/waku_message/constants.ts +10 -0
- package/src/lib/waku_message/ecies.ts +194 -0
- package/src/lib/waku_message/symmetric.ts +33 -0
- package/src/lib/waku_message/topic_only_message.ts +40 -0
- package/src/lib/waku_message/version_0.ts +121 -0
- package/src/lib/waku_message/version_1.ts +457 -0
- package/src/lib/waku_relay/constants.ts +77 -0
- package/src/lib/waku_relay/index.ts +189 -0
- package/src/lib/waku_store/history_rpc.ts +94 -0
- package/src/lib/waku_store/index.ts +372 -0
- package/src/proto/filter.ts +602 -0
- package/src/proto/light_push.ts +526 -0
- package/src/proto/message.ts +304 -0
- package/src/proto/store.ts +844 -0
- package/src/proto/topic_only_message.ts +67 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
import {
|
2
|
+
GossipSub,
|
3
|
+
GossipsubMessage,
|
4
|
+
GossipsubOpts,
|
5
|
+
} from "@chainsafe/libp2p-gossipsub";
|
6
|
+
import {
|
7
|
+
PeerIdStr,
|
8
|
+
TopicStr,
|
9
|
+
} from "@chainsafe/libp2p-gossipsub/dist/src/types";
|
10
|
+
import { SignaturePolicy } from "@chainsafe/libp2p-gossipsub/types";
|
11
|
+
import type {
|
12
|
+
Callback,
|
13
|
+
Decoder,
|
14
|
+
Encoder,
|
15
|
+
Message,
|
16
|
+
Relay,
|
17
|
+
SendResult,
|
18
|
+
} from "@waku/interfaces";
|
19
|
+
import debug from "debug";
|
20
|
+
|
21
|
+
import { DefaultPubSubTopic } from "../constants";
|
22
|
+
import { pushOrInitMapSet } from "../push_or_init_map";
|
23
|
+
import { TopicOnlyDecoder } from "../waku_message/topic_only_message";
|
24
|
+
|
25
|
+
import * as constants from "./constants";
|
26
|
+
|
27
|
+
const log = debug("waku:relay");
|
28
|
+
|
29
|
+
export type Observer<T extends Message> = {
|
30
|
+
decoder: Decoder<T>;
|
31
|
+
callback: Callback<T>;
|
32
|
+
};
|
33
|
+
|
34
|
+
export type CreateOptions = {
|
35
|
+
/**
|
36
|
+
* The PubSub Topic to use. Defaults to {@link DefaultPubSubTopic}.
|
37
|
+
*
|
38
|
+
* One and only one pubsub topic is used by Waku. This is used by:
|
39
|
+
* - WakuRelay to receive, route and send messages,
|
40
|
+
* - WakuLightPush to send messages,
|
41
|
+
* - WakuStore to retrieve messages.
|
42
|
+
*
|
43
|
+
* The usage of the default pubsub topic is recommended.
|
44
|
+
* See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for details.
|
45
|
+
*
|
46
|
+
* @default {@link DefaultPubSubTopic}
|
47
|
+
*/
|
48
|
+
pubSubTopic?: string;
|
49
|
+
} & GossipsubOpts;
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Implements the [Waku v2 Relay protocol](https://rfc.vac.dev/spec/11/).
|
53
|
+
* Must be passed as a `pubsub` module to a `Libp2p` instance.
|
54
|
+
*
|
55
|
+
* @implements {require('libp2p-interfaces/src/pubsub')}
|
56
|
+
*/
|
57
|
+
export class WakuRelay extends GossipSub implements Relay {
|
58
|
+
pubSubTopic: string;
|
59
|
+
defaultDecoder: Decoder<Message>;
|
60
|
+
public static multicodec: string = constants.RelayCodecs[0];
|
61
|
+
|
62
|
+
/**
|
63
|
+
* observers called when receiving new message.
|
64
|
+
* Observers under key `""` are always called.
|
65
|
+
*/
|
66
|
+
public observers: Map<string, Set<Observer<any>>>;
|
67
|
+
|
68
|
+
constructor(options?: Partial<CreateOptions>) {
|
69
|
+
options = Object.assign(options ?? {}, {
|
70
|
+
// Ensure that no signature is included nor expected in the messages.
|
71
|
+
globalSignaturePolicy: SignaturePolicy.StrictNoSign,
|
72
|
+
fallbackToFloodsub: false,
|
73
|
+
});
|
74
|
+
super(options);
|
75
|
+
this.multicodecs = constants.RelayCodecs;
|
76
|
+
|
77
|
+
this.observers = new Map();
|
78
|
+
|
79
|
+
this.pubSubTopic = options?.pubSubTopic ?? DefaultPubSubTopic;
|
80
|
+
|
81
|
+
// TODO: User might want to decide what decoder should be used (e.g. for RLN)
|
82
|
+
this.defaultDecoder = new TopicOnlyDecoder();
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Mounts the gossipsub protocol onto the libp2p node
|
87
|
+
* and subscribes to the default topic.
|
88
|
+
*
|
89
|
+
* @override
|
90
|
+
* @returns {void}
|
91
|
+
*/
|
92
|
+
public async start(): Promise<void> {
|
93
|
+
await super.start();
|
94
|
+
this.subscribe(this.pubSubTopic);
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Send Waku message.
|
99
|
+
*/
|
100
|
+
public async send(
|
101
|
+
encoder: Encoder,
|
102
|
+
message: Partial<Message>
|
103
|
+
): Promise<SendResult> {
|
104
|
+
const msg = await encoder.toWire(message);
|
105
|
+
if (!msg) {
|
106
|
+
log("Failed to encode message, aborting publish");
|
107
|
+
return { recipients: [] };
|
108
|
+
}
|
109
|
+
return this.publish(this.pubSubTopic, msg);
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Add an observer and associated Decoder to process incoming messages on a given content topic.
|
114
|
+
*
|
115
|
+
* @returns Function to delete the observer
|
116
|
+
*/
|
117
|
+
addObserver<T extends Message>(
|
118
|
+
decoder: Decoder<T>,
|
119
|
+
callback: Callback<T>
|
120
|
+
): () => void {
|
121
|
+
const observer = {
|
122
|
+
decoder,
|
123
|
+
callback,
|
124
|
+
};
|
125
|
+
pushOrInitMapSet(this.observers, decoder.contentTopic, observer);
|
126
|
+
|
127
|
+
return () => {
|
128
|
+
const observers = this.observers.get(decoder.contentTopic);
|
129
|
+
if (observers) {
|
130
|
+
observers.delete(observer);
|
131
|
+
}
|
132
|
+
};
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Subscribe to a pubsub topic and start emitting Waku messages to observers.
|
137
|
+
*
|
138
|
+
* @override
|
139
|
+
*/
|
140
|
+
subscribe(pubSubTopic: string): void {
|
141
|
+
this.addEventListener(
|
142
|
+
"gossipsub:message",
|
143
|
+
async (event: CustomEvent<GossipsubMessage>) => {
|
144
|
+
if (event.detail.msg.topic !== pubSubTopic) return;
|
145
|
+
log(`Message received on ${pubSubTopic}`);
|
146
|
+
|
147
|
+
const topicOnlyMsg = await this.defaultDecoder.fromWireToProtoObj(
|
148
|
+
event.detail.msg.data
|
149
|
+
);
|
150
|
+
if (!topicOnlyMsg || !topicOnlyMsg.contentTopic) {
|
151
|
+
log("Message does not have a content topic, skipping");
|
152
|
+
return;
|
153
|
+
}
|
154
|
+
|
155
|
+
const observers = this.observers.get(topicOnlyMsg.contentTopic);
|
156
|
+
if (!observers) {
|
157
|
+
return;
|
158
|
+
}
|
159
|
+
await Promise.all(
|
160
|
+
Array.from(observers).map(async ({ decoder, callback }) => {
|
161
|
+
const protoMsg = await decoder.fromWireToProtoObj(
|
162
|
+
event.detail.msg.data
|
163
|
+
);
|
164
|
+
if (!protoMsg) {
|
165
|
+
log(
|
166
|
+
"Internal error: message previously decoded failed on 2nd pass."
|
167
|
+
);
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
const msg = await decoder.fromProtoObj(protoMsg);
|
171
|
+
if (msg) {
|
172
|
+
callback(msg);
|
173
|
+
} else {
|
174
|
+
log("Failed to decode messages on", topicOnlyMsg.contentTopic);
|
175
|
+
}
|
176
|
+
})
|
177
|
+
);
|
178
|
+
}
|
179
|
+
);
|
180
|
+
|
181
|
+
super.subscribe(pubSubTopic);
|
182
|
+
}
|
183
|
+
|
184
|
+
getMeshPeers(topic?: TopicStr): PeerIdStr[] {
|
185
|
+
return super.getMeshPeers(topic ?? this.pubSubTopic);
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
WakuRelay.multicodec = constants.RelayCodecs[constants.RelayCodecs.length - 1];
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import type { Uint8ArrayList } from "uint8arraylist";
|
2
|
+
import { v4 as uuid } from "uuid";
|
3
|
+
|
4
|
+
import * as proto from "../../proto/store";
|
5
|
+
|
6
|
+
const OneMillion = BigInt(1_000_000);
|
7
|
+
|
8
|
+
export enum PageDirection {
|
9
|
+
BACKWARD = "backward",
|
10
|
+
FORWARD = "forward",
|
11
|
+
}
|
12
|
+
|
13
|
+
export interface Params {
|
14
|
+
contentTopics: string[];
|
15
|
+
pubSubTopic: string;
|
16
|
+
pageDirection: PageDirection;
|
17
|
+
pageSize: number;
|
18
|
+
startTime?: Date;
|
19
|
+
endTime?: Date;
|
20
|
+
cursor?: proto.Index;
|
21
|
+
}
|
22
|
+
|
23
|
+
export class HistoryRPC {
|
24
|
+
private constructor(public readonly proto: proto.HistoryRPC) {}
|
25
|
+
|
26
|
+
get query(): proto.HistoryQuery | undefined {
|
27
|
+
return this.proto.query;
|
28
|
+
}
|
29
|
+
|
30
|
+
get response(): proto.HistoryResponse | undefined {
|
31
|
+
return this.proto.response;
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Create History Query.
|
36
|
+
*/
|
37
|
+
static createQuery(params: Params): HistoryRPC {
|
38
|
+
const contentFilters = params.contentTopics.map((contentTopic) => {
|
39
|
+
return { contentTopic };
|
40
|
+
});
|
41
|
+
|
42
|
+
const direction = directionToProto(params.pageDirection);
|
43
|
+
|
44
|
+
const pagingInfo = {
|
45
|
+
pageSize: BigInt(params.pageSize),
|
46
|
+
cursor: params.cursor,
|
47
|
+
direction,
|
48
|
+
} as proto.PagingInfo;
|
49
|
+
|
50
|
+
let startTime, endTime;
|
51
|
+
if (params.startTime) {
|
52
|
+
// milliseconds 10^-3 to nanoseconds 10^-9
|
53
|
+
startTime = BigInt(params.startTime.valueOf()) * OneMillion;
|
54
|
+
}
|
55
|
+
|
56
|
+
if (params.endTime) {
|
57
|
+
// milliseconds 10^-3 to nanoseconds 10^-9
|
58
|
+
endTime = BigInt(params.endTime.valueOf()) * OneMillion;
|
59
|
+
}
|
60
|
+
return new HistoryRPC({
|
61
|
+
requestId: uuid(),
|
62
|
+
query: {
|
63
|
+
pubSubTopic: params.pubSubTopic,
|
64
|
+
contentFilters,
|
65
|
+
pagingInfo,
|
66
|
+
startTime,
|
67
|
+
endTime,
|
68
|
+
},
|
69
|
+
response: undefined,
|
70
|
+
});
|
71
|
+
}
|
72
|
+
|
73
|
+
decode(bytes: Uint8ArrayList): HistoryRPC {
|
74
|
+
const res = proto.HistoryRPC.decode(bytes);
|
75
|
+
return new HistoryRPC(res);
|
76
|
+
}
|
77
|
+
|
78
|
+
encode(): Uint8Array {
|
79
|
+
return proto.HistoryRPC.encode(this.proto);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
function directionToProto(
|
84
|
+
pageDirection: PageDirection
|
85
|
+
): proto.PagingInfo.Direction {
|
86
|
+
switch (pageDirection) {
|
87
|
+
case PageDirection.BACKWARD:
|
88
|
+
return proto.PagingInfo.Direction.DIRECTION_BACKWARD_UNSPECIFIED;
|
89
|
+
case PageDirection.FORWARD:
|
90
|
+
return proto.PagingInfo.Direction.DIRECTION_FORWARD;
|
91
|
+
default:
|
92
|
+
return proto.PagingInfo.Direction.DIRECTION_BACKWARD_UNSPECIFIED;
|
93
|
+
}
|
94
|
+
}
|
@@ -0,0 +1,372 @@
|
|
1
|
+
import type { Connection } from "@libp2p/interface-connection";
|
2
|
+
import type { PeerId } from "@libp2p/interface-peer-id";
|
3
|
+
import { Peer } from "@libp2p/interface-peer-store";
|
4
|
+
import { Decoder, Message } from "@waku/interfaces";
|
5
|
+
import debug from "debug";
|
6
|
+
import all from "it-all";
|
7
|
+
import * as lp from "it-length-prefixed";
|
8
|
+
import { pipe } from "it-pipe";
|
9
|
+
import { Libp2p } from "libp2p";
|
10
|
+
import { Uint8ArrayList } from "uint8arraylist";
|
11
|
+
|
12
|
+
import * as proto from "../../proto/store";
|
13
|
+
import { DefaultPubSubTopic } from "../constants";
|
14
|
+
import { selectConnection } from "../select_connection";
|
15
|
+
import { getPeersForProtocol, selectPeerForProtocol } from "../select_peer";
|
16
|
+
import { toProtoMessage } from "../to_proto_message";
|
17
|
+
|
18
|
+
import { HistoryRPC, PageDirection, Params } from "./history_rpc";
|
19
|
+
|
20
|
+
import HistoryError = proto.HistoryResponse.HistoryError;
|
21
|
+
|
22
|
+
const log = debug("waku:store");
|
23
|
+
|
24
|
+
export const StoreCodec = "/vac/waku/store/2.0.0-beta4";
|
25
|
+
|
26
|
+
export const DefaultPageSize = 10;
|
27
|
+
|
28
|
+
export { PageDirection };
|
29
|
+
|
30
|
+
export interface CreateOptions {
|
31
|
+
/**
|
32
|
+
* The PubSub Topic to use. Defaults to {@link DefaultPubSubTopic}.
|
33
|
+
*
|
34
|
+
* The usage of the default pubsub topic is recommended.
|
35
|
+
* See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for details.
|
36
|
+
*
|
37
|
+
* @default {@link DefaultPubSubTopic}
|
38
|
+
*/
|
39
|
+
pubSubTopic?: string;
|
40
|
+
}
|
41
|
+
|
42
|
+
export interface TimeFilter {
|
43
|
+
startTime: Date;
|
44
|
+
endTime: Date;
|
45
|
+
}
|
46
|
+
|
47
|
+
export interface QueryOptions {
|
48
|
+
/**
|
49
|
+
* The peer to query. If undefined, a pseudo-random peer is selected from the connected Waku Store peers.
|
50
|
+
*/
|
51
|
+
peerId?: PeerId;
|
52
|
+
/**
|
53
|
+
* The pubsub topic to pass to the query.
|
54
|
+
* See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/).
|
55
|
+
*/
|
56
|
+
pubSubTopic?: string;
|
57
|
+
/**
|
58
|
+
* The direction in which pages are retrieved:
|
59
|
+
* - { @link PageDirection.BACKWARD }: Most recent page first.
|
60
|
+
* - { @link PageDirection.FORWARD }: Oldest page first.
|
61
|
+
*
|
62
|
+
* Note: This does not affect the ordering of messages with the page
|
63
|
+
* (the oldest message is always first).
|
64
|
+
*
|
65
|
+
* @default { @link PageDirection.BACKWARD }
|
66
|
+
*/
|
67
|
+
pageDirection?: PageDirection;
|
68
|
+
/**
|
69
|
+
* The number of message per page.
|
70
|
+
*
|
71
|
+
* @default { @link DefaultPageSize }
|
72
|
+
*/
|
73
|
+
pageSize?: number;
|
74
|
+
/**
|
75
|
+
* Retrieve messages with a timestamp within the provided values.
|
76
|
+
*/
|
77
|
+
timeFilter?: TimeFilter;
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Implements the [Waku v2 Store protocol](https://rfc.vac.dev/spec/13/).
|
82
|
+
*
|
83
|
+
* The Waku Store protocol can be used to retrieved historical messages.
|
84
|
+
*/
|
85
|
+
export class WakuStore {
|
86
|
+
pubSubTopic: string;
|
87
|
+
|
88
|
+
constructor(public libp2p: Libp2p, options?: CreateOptions) {
|
89
|
+
this.pubSubTopic = options?.pubSubTopic ?? DefaultPubSubTopic;
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* Do a query to a Waku Store to retrieve historical/missed messages.
|
94
|
+
*
|
95
|
+
* The callback function takes a `WakuMessage` in input,
|
96
|
+
* messages are processed in order:
|
97
|
+
* - oldest to latest if `options.pageDirection` == { @link PageDirection.FORWARD }
|
98
|
+
* - latest to oldest if `options.pageDirection` == { @link PageDirection.BACKWARD }
|
99
|
+
*
|
100
|
+
* The ordering may affect performance.
|
101
|
+
* The ordering depends on the behavior of the remote store node.
|
102
|
+
* If strong ordering is needed, you may need to handle this at application level
|
103
|
+
* and set your own timestamps too (the WakuMessage timestamps are not certified).
|
104
|
+
*
|
105
|
+
* @throws If not able to reach a Waku Store peer to query,
|
106
|
+
* or if an error is encountered when processing the reply,
|
107
|
+
* or if two decoders with the same content topic are passed.
|
108
|
+
*/
|
109
|
+
async queryOrderedCallback<T extends Message>(
|
110
|
+
decoders: Decoder<T>[],
|
111
|
+
callback: (message: T) => Promise<void | boolean> | boolean | void,
|
112
|
+
options?: QueryOptions
|
113
|
+
): Promise<void> {
|
114
|
+
let abort = false;
|
115
|
+
for await (const promises of this.queryGenerator(decoders, options)) {
|
116
|
+
if (abort) break;
|
117
|
+
const messagesOrUndef: Array<T | undefined> = await Promise.all(promises);
|
118
|
+
|
119
|
+
let messages: Array<T> = messagesOrUndef.filter(isDefined);
|
120
|
+
|
121
|
+
// Messages in pages are ordered from oldest (first) to most recent (last).
|
122
|
+
// https://github.com/vacp2p/rfc/issues/533
|
123
|
+
if (
|
124
|
+
typeof options?.pageDirection === "undefined" ||
|
125
|
+
options?.pageDirection === PageDirection.BACKWARD
|
126
|
+
) {
|
127
|
+
messages = messages.reverse();
|
128
|
+
}
|
129
|
+
|
130
|
+
await Promise.all(
|
131
|
+
messages.map(async (msg) => {
|
132
|
+
if (msg && !abort) {
|
133
|
+
abort = Boolean(await callback(msg));
|
134
|
+
}
|
135
|
+
})
|
136
|
+
);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Do a query to a Waku Store to retrieve historical/missed messages.
|
142
|
+
*
|
143
|
+
* The callback function takes a `Promise<WakuMessage>` in input,
|
144
|
+
* useful if messages needs to be decrypted and performance matters.
|
145
|
+
*
|
146
|
+
* The order of the messages passed to the callback is as follows:
|
147
|
+
* - within a page, messages are expected to be ordered from oldest to most recent
|
148
|
+
* - pages direction depends on { @link QueryOptions.pageDirection }
|
149
|
+
*
|
150
|
+
* Do note that the resolution of the `Promise<WakuMessage | undefined` may
|
151
|
+
* break the order as it may rely on the browser decryption API, which in turn,
|
152
|
+
* may have a different speed depending on the type of decryption.
|
153
|
+
*
|
154
|
+
* @throws If not able to reach a Waku Store peer to query,
|
155
|
+
* or if an error is encountered when processing the reply,
|
156
|
+
* or if two decoders with the same content topic are passed.
|
157
|
+
*/
|
158
|
+
async queryCallbackOnPromise<T extends Message>(
|
159
|
+
decoders: Decoder<T>[],
|
160
|
+
callback: (
|
161
|
+
message: Promise<T | undefined>
|
162
|
+
) => Promise<void | boolean> | boolean | void,
|
163
|
+
options?: QueryOptions
|
164
|
+
): Promise<void> {
|
165
|
+
let abort = false;
|
166
|
+
let promises: Promise<void>[] = [];
|
167
|
+
for await (const page of this.queryGenerator(decoders, options)) {
|
168
|
+
const _promises = page.map(async (msg) => {
|
169
|
+
if (!abort) {
|
170
|
+
abort = Boolean(await callback(msg));
|
171
|
+
}
|
172
|
+
});
|
173
|
+
|
174
|
+
promises = promises.concat(_promises);
|
175
|
+
}
|
176
|
+
await Promise.all(promises);
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* Do a query to a Waku Store to retrieve historical/missed messages.
|
181
|
+
*
|
182
|
+
* This is a generator, useful if you want most control on how messages
|
183
|
+
* are processed.
|
184
|
+
*
|
185
|
+
* The order of the messages returned by the remote Waku node SHOULD BE
|
186
|
+
* as follows:
|
187
|
+
* - within a page, messages SHOULD be ordered from oldest to most recent
|
188
|
+
* - pages direction depends on { @link QueryOptions.pageDirection }
|
189
|
+
*
|
190
|
+
* However, there is no way to guarantee the behavior of the remote node.
|
191
|
+
*
|
192
|
+
* @throws If not able to reach a Waku Store peer to query,
|
193
|
+
* or if an error is encountered when processing the reply,
|
194
|
+
* or if two decoders with the same content topic are passed.
|
195
|
+
*/
|
196
|
+
async *queryGenerator<T extends Message>(
|
197
|
+
decoders: Decoder<T>[],
|
198
|
+
options?: QueryOptions
|
199
|
+
): AsyncGenerator<Promise<T | undefined>[]> {
|
200
|
+
let startTime, endTime;
|
201
|
+
|
202
|
+
if (options?.timeFilter) {
|
203
|
+
startTime = options.timeFilter.startTime;
|
204
|
+
endTime = options.timeFilter.endTime;
|
205
|
+
}
|
206
|
+
|
207
|
+
const decodersAsMap = new Map();
|
208
|
+
decoders.forEach((dec) => {
|
209
|
+
if (decodersAsMap.has(dec.contentTopic)) {
|
210
|
+
throw new Error(
|
211
|
+
"API does not support different decoder per content topic"
|
212
|
+
);
|
213
|
+
}
|
214
|
+
decodersAsMap.set(dec.contentTopic, dec);
|
215
|
+
});
|
216
|
+
|
217
|
+
const contentTopics = decoders.map((dec) => dec.contentTopic);
|
218
|
+
|
219
|
+
const queryOpts = Object.assign(
|
220
|
+
{
|
221
|
+
pubSubTopic: this.pubSubTopic,
|
222
|
+
pageDirection: PageDirection.BACKWARD,
|
223
|
+
pageSize: DefaultPageSize,
|
224
|
+
},
|
225
|
+
options,
|
226
|
+
{ contentTopics, startTime, endTime }
|
227
|
+
);
|
228
|
+
|
229
|
+
log("Querying history with the following options", {
|
230
|
+
peerId: options?.peerId?.toString(),
|
231
|
+
...options,
|
232
|
+
});
|
233
|
+
|
234
|
+
const res = await selectPeerForProtocol(
|
235
|
+
this.libp2p.peerStore,
|
236
|
+
[StoreCodec],
|
237
|
+
options?.peerId
|
238
|
+
);
|
239
|
+
|
240
|
+
if (!res) {
|
241
|
+
throw new Error("Failed to get a peer");
|
242
|
+
}
|
243
|
+
const { peer, protocol } = res;
|
244
|
+
|
245
|
+
const connections = this.libp2p.connectionManager.getConnections(peer.id);
|
246
|
+
const connection = selectConnection(connections);
|
247
|
+
|
248
|
+
if (!connection) throw "Failed to get a connection to the peer";
|
249
|
+
|
250
|
+
for await (const messages of paginate<T>(
|
251
|
+
connection,
|
252
|
+
protocol,
|
253
|
+
queryOpts,
|
254
|
+
decodersAsMap
|
255
|
+
)) {
|
256
|
+
yield messages;
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
/**
|
261
|
+
* Returns known peers from the address book (`libp2p.peerStore`) that support
|
262
|
+
* store protocol. Waku may or may not be currently connected to these peers.
|
263
|
+
*/
|
264
|
+
async peers(): Promise<Peer[]> {
|
265
|
+
return getPeersForProtocol(this.libp2p.peerStore, [StoreCodec]);
|
266
|
+
}
|
267
|
+
}
|
268
|
+
|
269
|
+
async function* paginate<T extends Message>(
|
270
|
+
connection: Connection,
|
271
|
+
protocol: string,
|
272
|
+
queryOpts: Params,
|
273
|
+
decoders: Map<string, Decoder<T>>
|
274
|
+
): AsyncGenerator<Promise<T | undefined>[]> {
|
275
|
+
if (
|
276
|
+
queryOpts.contentTopics.toString() !==
|
277
|
+
Array.from(decoders.keys()).toString()
|
278
|
+
) {
|
279
|
+
throw new Error(
|
280
|
+
"Internal error, the decoders should match the query's content topics"
|
281
|
+
);
|
282
|
+
}
|
283
|
+
|
284
|
+
let cursor = undefined;
|
285
|
+
while (true) {
|
286
|
+
queryOpts = Object.assign(queryOpts, { cursor });
|
287
|
+
|
288
|
+
const stream = await connection.newStream(protocol);
|
289
|
+
const historyRpcQuery = HistoryRPC.createQuery(queryOpts);
|
290
|
+
|
291
|
+
log(
|
292
|
+
"Querying store peer",
|
293
|
+
connection.remoteAddr.toString(),
|
294
|
+
`for (${queryOpts.pubSubTopic})`,
|
295
|
+
queryOpts.contentTopics
|
296
|
+
);
|
297
|
+
|
298
|
+
const res = await pipe(
|
299
|
+
[historyRpcQuery.encode()],
|
300
|
+
lp.encode(),
|
301
|
+
stream,
|
302
|
+
lp.decode(),
|
303
|
+
async (source) => await all(source)
|
304
|
+
);
|
305
|
+
|
306
|
+
const bytes = new Uint8ArrayList();
|
307
|
+
res.forEach((chunk) => {
|
308
|
+
bytes.append(chunk);
|
309
|
+
});
|
310
|
+
|
311
|
+
const reply = historyRpcQuery.decode(bytes);
|
312
|
+
|
313
|
+
if (!reply.response) {
|
314
|
+
log("Stopping pagination due to store `response` field missing");
|
315
|
+
break;
|
316
|
+
}
|
317
|
+
|
318
|
+
const response = reply.response as proto.HistoryResponse;
|
319
|
+
|
320
|
+
if (
|
321
|
+
response.error &&
|
322
|
+
response.error !== HistoryError.ERROR_NONE_UNSPECIFIED
|
323
|
+
) {
|
324
|
+
throw "History response contains an Error: " + response.error;
|
325
|
+
}
|
326
|
+
|
327
|
+
if (!response.messages || !response.messages.length) {
|
328
|
+
log(
|
329
|
+
"Stopping pagination due to store `response.messages` field missing or empty"
|
330
|
+
);
|
331
|
+
break;
|
332
|
+
}
|
333
|
+
|
334
|
+
log(`${response.messages.length} messages retrieved from store`);
|
335
|
+
|
336
|
+
yield response.messages.map((protoMsg) => {
|
337
|
+
const contentTopic = protoMsg.contentTopic;
|
338
|
+
if (typeof contentTopic !== "undefined") {
|
339
|
+
const decoder = decoders.get(contentTopic);
|
340
|
+
if (decoder) {
|
341
|
+
return decoder.fromProtoObj(toProtoMessage(protoMsg));
|
342
|
+
}
|
343
|
+
}
|
344
|
+
return Promise.resolve(undefined);
|
345
|
+
});
|
346
|
+
|
347
|
+
cursor = response.pagingInfo?.cursor;
|
348
|
+
if (typeof cursor === "undefined") {
|
349
|
+
// If the server does not return cursor then there is an issue,
|
350
|
+
// Need to abort, or we end up in an infinite loop
|
351
|
+
log(
|
352
|
+
"Stopping pagination due to `response.pagingInfo.cursor` missing from store response"
|
353
|
+
);
|
354
|
+
break;
|
355
|
+
}
|
356
|
+
|
357
|
+
const responsePageSize = response.pagingInfo?.pageSize;
|
358
|
+
const queryPageSize = historyRpcQuery.query?.pagingInfo?.pageSize;
|
359
|
+
if (
|
360
|
+
// Response page size smaller than query, meaning this is the last page
|
361
|
+
responsePageSize &&
|
362
|
+
queryPageSize &&
|
363
|
+
responsePageSize < queryPageSize
|
364
|
+
) {
|
365
|
+
break;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
}
|
369
|
+
|
370
|
+
export function isDefined<T>(msg: T | undefined): msg is T {
|
371
|
+
return !!msg;
|
372
|
+
}
|