2021-04-22 14:47:43 +10:00
|
|
|
import {
|
2022-06-21 13:23:42 +10:00
|
|
|
GossipSub,
|
2022-11-16 20:30:48 +11:00
|
|
|
GossipSubComponents,
|
2022-06-21 13:23:42 +10:00
|
|
|
GossipsubMessage,
|
|
|
|
|
GossipsubOpts,
|
|
|
|
|
} from "@chainsafe/libp2p-gossipsub";
|
2022-12-02 15:43:46 +11:00
|
|
|
import type { PeerIdStr, TopicStr } from "@chainsafe/libp2p-gossipsub/types";
|
2022-06-21 13:23:42 +10:00
|
|
|
import { SignaturePolicy } from "@chainsafe/libp2p-gossipsub/types";
|
2022-11-01 16:30:24 +11:00
|
|
|
import type {
|
|
|
|
|
Callback,
|
2022-12-05 17:07:03 +11:00
|
|
|
IDecoder,
|
|
|
|
|
IEncoder,
|
|
|
|
|
IMessage,
|
2022-12-06 12:36:29 +11:00
|
|
|
IRelay,
|
2022-11-01 16:30:24 +11:00
|
|
|
SendResult,
|
|
|
|
|
} from "@waku/interfaces";
|
2022-12-05 17:07:03 +11:00
|
|
|
import { IDecodedMessage } from "@waku/interfaces";
|
2022-06-21 13:23:42 +10:00
|
|
|
import debug from "debug";
|
2021-04-01 16:37:05 +11:00
|
|
|
|
2022-12-02 15:43:46 +11:00
|
|
|
import { DefaultPubSubTopic } from "../constants.js";
|
2022-12-06 12:49:02 +11:00
|
|
|
import { TopicOnlyDecoder } from "../message/topic_only_message.js";
|
2022-12-02 15:43:46 +11:00
|
|
|
import { pushOrInitMapSet } from "../push_or_init_map.js";
|
2021-04-01 16:37:05 +11:00
|
|
|
|
2022-12-02 15:43:46 +11:00
|
|
|
import * as constants from "./constants.js";
|
2021-04-01 16:37:05 +11:00
|
|
|
|
2022-09-05 21:30:29 +10:00
|
|
|
const log = debug("waku:relay");
|
2021-07-09 10:07:39 +10:00
|
|
|
|
2022-12-05 17:07:03 +11:00
|
|
|
export type Observer<T extends IDecodedMessage> = {
|
|
|
|
|
decoder: IDecoder<T>;
|
2022-09-19 16:06:03 +10:00
|
|
|
callback: Callback<T>;
|
|
|
|
|
};
|
2022-09-19 13:50:29 +10:00
|
|
|
|
2022-09-07 12:17:33 +10:00
|
|
|
export type CreateOptions = {
|
2022-08-01 15:43:43 +10:00
|
|
|
/**
|
|
|
|
|
* The PubSub Topic to use. Defaults to {@link DefaultPubSubTopic}.
|
|
|
|
|
*
|
|
|
|
|
* One and only one pubsub topic is used by Waku. This is used by:
|
|
|
|
|
* - WakuRelay to receive, route and send messages,
|
|
|
|
|
* - WakuLightPush to send messages,
|
|
|
|
|
* - WakuStore to retrieve messages.
|
|
|
|
|
*
|
|
|
|
|
* The usage of the default pubsub topic is recommended.
|
|
|
|
|
* See [Waku v2 Topic Usage Recommendations](https://rfc.vac.dev/spec/23/) for details.
|
|
|
|
|
*
|
|
|
|
|
* @default {@link DefaultPubSubTopic}
|
|
|
|
|
*/
|
|
|
|
|
pubSubTopic?: string;
|
2022-09-07 12:17:33 +10:00
|
|
|
} & GossipsubOpts;
|
2022-08-01 15:43:43 +10:00
|
|
|
|
2021-05-10 11:41:13 +10:00
|
|
|
/**
|
2022-08-25 15:50:07 +10:00
|
|
|
* Implements the [Waku v2 Relay protocol](https://rfc.vac.dev/spec/11/).
|
|
|
|
|
* Must be passed as a `pubsub` module to a `Libp2p` instance.
|
2021-05-10 11:41:13 +10:00
|
|
|
*
|
2021-07-15 12:16:21 +10:00
|
|
|
* @implements {require('libp2p-interfaces/src/pubsub')}
|
2021-05-10 11:41:13 +10:00
|
|
|
*/
|
2022-12-06 12:51:02 +11:00
|
|
|
class Relay extends GossipSub implements IRelay {
|
2021-08-20 10:12:19 +10:00
|
|
|
pubSubTopic: string;
|
2022-12-05 17:07:03 +11:00
|
|
|
defaultDecoder: IDecoder<IDecodedMessage>;
|
2022-06-21 13:23:42 +10:00
|
|
|
public static multicodec: string = constants.RelayCodecs[0];
|
2021-07-09 10:07:39 +10:00
|
|
|
|
2021-05-10 14:54:08 +10:00
|
|
|
/**
|
|
|
|
|
* observers called when receiving new message.
|
2021-07-09 10:07:39 +10:00
|
|
|
* Observers under key `""` are always called.
|
2021-05-10 14:54:08 +10:00
|
|
|
*/
|
2022-09-19 16:06:03 +10:00
|
|
|
public observers: Map<string, Set<Observer<any>>>;
|
2021-04-01 16:37:05 +11:00
|
|
|
|
2022-11-16 20:30:48 +11:00
|
|
|
constructor(
|
|
|
|
|
components: GossipSubComponents,
|
|
|
|
|
options?: Partial<CreateOptions>
|
|
|
|
|
) {
|
2022-06-23 16:36:56 +10:00
|
|
|
options = Object.assign(options ?? {}, {
|
|
|
|
|
// Ensure that no signature is included nor expected in the messages.
|
|
|
|
|
globalSignaturePolicy: SignaturePolicy.StrictNoSign,
|
|
|
|
|
fallbackToFloodsub: false,
|
|
|
|
|
});
|
2022-11-16 20:30:48 +11:00
|
|
|
super(components, options);
|
2022-06-21 13:23:42 +10:00
|
|
|
this.multicodecs = constants.RelayCodecs;
|
2021-04-01 16:37:05 +11:00
|
|
|
|
2022-09-19 13:50:29 +10:00
|
|
|
this.observers = new Map();
|
2021-04-01 16:37:05 +11:00
|
|
|
|
2022-07-25 17:04:41 +10:00
|
|
|
this.pubSubTopic = options?.pubSubTopic ?? DefaultPubSubTopic;
|
2022-09-19 16:20:33 +10:00
|
|
|
|
|
|
|
|
// TODO: User might want to decide what decoder should be used (e.g. for RLN)
|
|
|
|
|
this.defaultDecoder = new TopicOnlyDecoder();
|
2021-04-01 16:37:05 +11:00
|
|
|
}
|
|
|
|
|
|
2021-04-16 11:25:08 +10:00
|
|
|
/**
|
|
|
|
|
* Mounts the gossipsub protocol onto the libp2p node
|
2021-05-10 11:41:13 +10:00
|
|
|
* and subscribes to the default topic.
|
|
|
|
|
*
|
2021-04-16 11:25:08 +10:00
|
|
|
* @override
|
|
|
|
|
* @returns {void}
|
|
|
|
|
*/
|
2022-02-02 15:12:08 +11:00
|
|
|
public async start(): Promise<void> {
|
|
|
|
|
await super.start();
|
2021-08-20 10:12:19 +10:00
|
|
|
this.subscribe(this.pubSubTopic);
|
2021-04-16 11:25:08 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-05-10 11:41:13 +10:00
|
|
|
* Send Waku message.
|
2021-04-16 11:25:08 +10:00
|
|
|
*/
|
2022-12-05 17:07:03 +11:00
|
|
|
public async send(encoder: IEncoder, message: IMessage): Promise<SendResult> {
|
2022-09-30 12:55:25 +10:00
|
|
|
const msg = await encoder.toWire(message);
|
2022-09-19 13:50:29 +10:00
|
|
|
if (!msg) {
|
|
|
|
|
log("Failed to encode message, aborting publish");
|
|
|
|
|
return { recipients: [] };
|
|
|
|
|
}
|
|
|
|
|
return this.publish(this.pubSubTopic, msg);
|
2021-07-09 10:07:39 +10:00
|
|
|
}
|
|
|
|
|
|
2021-05-10 12:27:20 +10:00
|
|
|
/**
|
2022-09-19 13:50:29 +10:00
|
|
|
* Add an observer and associated Decoder to process incoming messages on a given content topic.
|
2021-05-10 12:27:20 +10:00
|
|
|
*
|
2022-09-19 12:21:29 +10:00
|
|
|
* @returns Function to delete the observer
|
2021-05-10 12:27:20 +10:00
|
|
|
*/
|
2022-12-05 17:07:03 +11:00
|
|
|
addObserver<T extends IDecodedMessage>(
|
|
|
|
|
decoder: IDecoder<T>,
|
2022-09-19 16:06:03 +10:00
|
|
|
callback: Callback<T>
|
|
|
|
|
): () => void {
|
2022-09-19 13:50:29 +10:00
|
|
|
const observer = {
|
|
|
|
|
decoder,
|
|
|
|
|
callback,
|
|
|
|
|
};
|
|
|
|
|
pushOrInitMapSet(this.observers, decoder.contentTopic, observer);
|
2022-09-19 12:21:29 +10:00
|
|
|
|
|
|
|
|
return () => {
|
2022-09-19 13:50:29 +10:00
|
|
|
const observers = this.observers.get(decoder.contentTopic);
|
|
|
|
|
if (observers) {
|
|
|
|
|
observers.delete(observer);
|
2022-09-19 12:21:29 +10:00
|
|
|
}
|
|
|
|
|
};
|
2021-05-10 12:27:20 +10:00
|
|
|
}
|
|
|
|
|
|
2021-06-09 10:13:41 +10:00
|
|
|
/**
|
|
|
|
|
* Subscribe to a pubsub topic and start emitting Waku messages to observers.
|
|
|
|
|
*
|
|
|
|
|
* @override
|
|
|
|
|
*/
|
2021-08-20 10:12:19 +10:00
|
|
|
subscribe(pubSubTopic: string): void {
|
2022-06-21 13:23:42 +10:00
|
|
|
this.addEventListener(
|
|
|
|
|
"gossipsub:message",
|
2022-09-19 13:50:29 +10:00
|
|
|
async (event: CustomEvent<GossipsubMessage>) => {
|
|
|
|
|
if (event.detail.msg.topic !== pubSubTopic) return;
|
|
|
|
|
log(`Message received on ${pubSubTopic}`);
|
|
|
|
|
|
2022-09-30 12:55:25 +10:00
|
|
|
const topicOnlyMsg = await this.defaultDecoder.fromWireToProtoObj(
|
2022-09-19 16:20:33 +10:00
|
|
|
event.detail.msg.data
|
|
|
|
|
);
|
|
|
|
|
if (!topicOnlyMsg || !topicOnlyMsg.contentTopic) {
|
2022-09-19 13:50:29 +10:00
|
|
|
log("Message does not have a content topic, skipping");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-19 16:20:33 +10:00
|
|
|
const observers = this.observers.get(topicOnlyMsg.contentTopic);
|
2022-09-19 13:50:29 +10:00
|
|
|
if (!observers) {
|
|
|
|
|
return;
|
2021-04-01 16:37:05 +11:00
|
|
|
}
|
2022-09-19 13:50:29 +10:00
|
|
|
await Promise.all(
|
|
|
|
|
Array.from(observers).map(async ({ decoder, callback }) => {
|
2022-09-30 12:55:25 +10:00
|
|
|
const protoMsg = await decoder.fromWireToProtoObj(
|
|
|
|
|
event.detail.msg.data
|
|
|
|
|
);
|
2022-09-19 16:20:33 +10:00
|
|
|
if (!protoMsg) {
|
|
|
|
|
log(
|
|
|
|
|
"Internal error: message previously decoded failed on 2nd pass."
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-09-30 12:55:25 +10:00
|
|
|
const msg = await decoder.fromProtoObj(protoMsg);
|
2022-09-19 13:50:29 +10:00
|
|
|
if (msg) {
|
|
|
|
|
callback(msg);
|
|
|
|
|
} else {
|
2022-09-19 16:20:33 +10:00
|
|
|
log("Failed to decode messages on", topicOnlyMsg.contentTopic);
|
2022-09-19 13:50:29 +10:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
2021-04-01 16:37:05 +11:00
|
|
|
}
|
2022-06-21 13:23:42 +10:00
|
|
|
);
|
2021-04-01 16:37:05 +11:00
|
|
|
|
2022-06-21 13:23:42 +10:00
|
|
|
super.subscribe(pubSubTopic);
|
2021-04-01 16:49:35 +11:00
|
|
|
}
|
|
|
|
|
|
2022-06-22 15:52:02 +10:00
|
|
|
getMeshPeers(topic?: TopicStr): PeerIdStr[] {
|
2022-07-25 13:13:47 +10:00
|
|
|
return super.getMeshPeers(topic ?? this.pubSubTopic);
|
2022-06-22 15:52:02 +10:00
|
|
|
}
|
2021-04-01 16:37:05 +11:00
|
|
|
}
|
2022-09-07 16:51:43 +10:00
|
|
|
|
2022-12-06 12:51:02 +11:00
|
|
|
Relay.multicodec = constants.RelayCodecs[constants.RelayCodecs.length - 1];
|
2022-11-16 20:30:48 +11:00
|
|
|
|
|
|
|
|
export function wakuRelay(
|
|
|
|
|
init: Partial<CreateOptions> = {}
|
2022-12-06 12:36:29 +11:00
|
|
|
): (components: GossipSubComponents) => IRelay {
|
2022-12-06 12:51:02 +11:00
|
|
|
return (components: GossipSubComponents) => new Relay(components, init);
|
2022-11-16 20:30:48 +11:00
|
|
|
}
|