mirror of https://github.com/waku-org/js-waku.git
move relay functionality to separate package
This commit is contained in:
parent
a739ada33a
commit
a4e8aab124
|
@ -9,10 +9,10 @@
|
|||
"packages/message-hash",
|
||||
"packages/enr",
|
||||
"packages/core",
|
||||
"packages/relay",
|
||||
"packages/discovery",
|
||||
"packages/message-encryption",
|
||||
"packages/sdk",
|
||||
"packages/relay",
|
||||
"packages/tests",
|
||||
"packages/browser-tests",
|
||||
"packages/build-utils",
|
||||
|
|
|
@ -42,10 +42,3 @@ export interface RelayNode extends Waku {
|
|||
filter: undefined;
|
||||
lightPush: undefined;
|
||||
}
|
||||
|
||||
export interface FullNode extends Waku {
|
||||
relay: IRelay;
|
||||
store: IStoreSDK;
|
||||
filter: IFilterSDK;
|
||||
lightPush: ILightPushSDK;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
"@chainsafe/libp2p-gossipsub": "^12.0.0",
|
||||
"@noble/hashes": "^1.3.2",
|
||||
"@waku/core": "0.0.30",
|
||||
"@waku/sdk": "0.0.26",
|
||||
"@waku/interfaces": "0.0.25",
|
||||
"@waku/proto": "0.0.7",
|
||||
"@waku/utils": "0.0.18",
|
||||
|
@ -67,6 +68,7 @@
|
|||
"rollup": "^4.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@waku/sdk": "0.0.26",
|
||||
"@waku/core": "0.0.30",
|
||||
"@waku/interfaces": "0.0.25",
|
||||
"@waku/proto": "0.0.7",
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { type RelayNode } from "@waku/interfaces";
|
||||
import {
|
||||
createLibp2pAndUpdateOptions,
|
||||
CreateWakuNodeOptions,
|
||||
WakuNode,
|
||||
WakuOptions
|
||||
} from "@waku/sdk";
|
||||
|
||||
import { RelayCreateOptions, wakuGossipSub, wakuRelay } from "./relay.js";
|
||||
|
||||
/**
|
||||
* Create a Waku node that uses Waku Relay to send and receive messages,
|
||||
* enabling some privacy preserving properties.
|
||||
* * @remarks
|
||||
* This function creates a Relay Node using the Waku Relay protocol.
|
||||
* While it is technically possible to use this function in a browser environment,
|
||||
* it is not recommended due to potential performance issues and limited browser capabilities.
|
||||
* If you are developing a browser-based application, consider alternative approaches like creating a Light Node
|
||||
* or use this function with caution.
|
||||
*/
|
||||
export async function createRelayNode(
|
||||
options: CreateWakuNodeOptions & Partial<RelayCreateOptions> = {
|
||||
pubsubTopics: []
|
||||
}
|
||||
): Promise<RelayNode> {
|
||||
options = {
|
||||
...options,
|
||||
libp2p: {
|
||||
...options.libp2p,
|
||||
services: {
|
||||
pubsub: wakuGossipSub(options)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const libp2p = await createLibp2pAndUpdateOptions(options);
|
||||
const relay = wakuRelay(options?.pubsubTopics || [])(libp2p);
|
||||
|
||||
return new WakuNode(options as WakuOptions, libp2p, {}, relay) as RelayNode;
|
||||
}
|
|
@ -1,317 +1,2 @@
|
|||
import {
|
||||
GossipSub,
|
||||
GossipSubComponents,
|
||||
GossipsubMessage,
|
||||
GossipsubOpts
|
||||
} from "@chainsafe/libp2p-gossipsub";
|
||||
import type { PeerIdStr, TopicStr } from "@chainsafe/libp2p-gossipsub/types";
|
||||
import { SignaturePolicy } from "@chainsafe/libp2p-gossipsub/types";
|
||||
import type { PubSub as Libp2pPubsub, PeerId } from "@libp2p/interface";
|
||||
import { sha256 } from "@noble/hashes/sha256";
|
||||
import {
|
||||
ActiveSubscriptions,
|
||||
Callback,
|
||||
DefaultPubsubTopic,
|
||||
IAsyncIterator,
|
||||
IDecodedMessage,
|
||||
IDecoder,
|
||||
IEncoder,
|
||||
IMessage,
|
||||
IRelay,
|
||||
Libp2p,
|
||||
ProtocolCreateOptions,
|
||||
ProtocolError,
|
||||
PubsubTopic,
|
||||
SDKProtocolResult
|
||||
} from "@waku/interfaces";
|
||||
import { isWireSizeUnderCap, toAsyncIterator } from "@waku/utils";
|
||||
import { pushOrInitMapSet } from "@waku/utils";
|
||||
import { Logger } from "@waku/utils";
|
||||
|
||||
import { RelayCodecs } from "./constants.js";
|
||||
import { messageValidator } from "./message_validator.js";
|
||||
import { TopicOnlyDecoder } from "./topic_only_message.js";
|
||||
|
||||
const log = new Logger("relay");
|
||||
|
||||
export type Observer<T extends IDecodedMessage> = {
|
||||
decoder: IDecoder<T>;
|
||||
callback: Callback<T>;
|
||||
};
|
||||
|
||||
export type RelayCreateOptions = ProtocolCreateOptions & GossipsubOpts;
|
||||
export type ContentTopic = string;
|
||||
|
||||
/**
|
||||
* Implements the [Waku v2 Relay protocol](https://rfc.vac.dev/spec/11/).
|
||||
* Throws if libp2p.pubsub does not support Waku Relay
|
||||
*/
|
||||
class Relay implements IRelay {
|
||||
public readonly pubsubTopics: Set<PubsubTopic>;
|
||||
private defaultDecoder: IDecoder<IDecodedMessage>;
|
||||
|
||||
public static multicodec: string = RelayCodecs[0];
|
||||
public readonly gossipSub: GossipSub;
|
||||
|
||||
/**
|
||||
* observers called when receiving new message.
|
||||
* Observers under key `""` are always called.
|
||||
*/
|
||||
private observers: Map<PubsubTopic, Map<ContentTopic, Set<unknown>>>;
|
||||
|
||||
public constructor(libp2p: Libp2p, pubsubTopics: PubsubTopic[]) {
|
||||
if (!this.isRelayPubsub(libp2p.services.pubsub)) {
|
||||
throw Error(
|
||||
`Failed to initialize Relay. libp2p.pubsub does not support ${Relay.multicodec}`
|
||||
);
|
||||
}
|
||||
|
||||
this.gossipSub = libp2p.services.pubsub as GossipSub;
|
||||
this.pubsubTopics = new Set(pubsubTopics);
|
||||
|
||||
if (this.gossipSub.isStarted()) {
|
||||
this.subscribeToAllTopics();
|
||||
}
|
||||
|
||||
this.observers = new Map();
|
||||
|
||||
// Default PubsubTopic decoder
|
||||
// TODO: User might want to decide what decoder should be used (e.g. for RLN)
|
||||
this.defaultDecoder = new TopicOnlyDecoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts the gossipsub protocol onto the libp2p node
|
||||
* and subscribes to all the topics.
|
||||
*
|
||||
* @override
|
||||
* @returns {void}
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
if (this.gossipSub.isStarted()) {
|
||||
throw Error("GossipSub already started.");
|
||||
}
|
||||
|
||||
await this.gossipSub.start();
|
||||
this.subscribeToAllTopics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Waku message.
|
||||
*/
|
||||
public async send(
|
||||
encoder: IEncoder,
|
||||
message: IMessage
|
||||
): Promise<SDKProtocolResult> {
|
||||
const successes: PeerId[] = [];
|
||||
|
||||
const { pubsubTopic } = encoder;
|
||||
if (!this.pubsubTopics.has(pubsubTopic)) {
|
||||
log.error("Failed to send waku relay: topic not configured");
|
||||
return {
|
||||
successes,
|
||||
failures: [
|
||||
{
|
||||
error: ProtocolError.TOPIC_NOT_CONFIGURED
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const msg = await encoder.toWire(message);
|
||||
if (!msg) {
|
||||
log.error("Failed to encode message, aborting publish");
|
||||
return {
|
||||
successes,
|
||||
failures: [
|
||||
{
|
||||
error: ProtocolError.ENCODE_FAILED
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
if (!isWireSizeUnderCap(msg)) {
|
||||
log.error("Failed to send waku relay: message is bigger that 1MB");
|
||||
return {
|
||||
successes,
|
||||
failures: [
|
||||
{
|
||||
error: ProtocolError.SIZE_TOO_BIG
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const { recipients } = await this.gossipSub.publish(pubsubTopic, msg);
|
||||
return {
|
||||
successes: recipients,
|
||||
failures: []
|
||||
};
|
||||
}
|
||||
|
||||
public subscribe<T extends IDecodedMessage>(
|
||||
decoders: IDecoder<T> | IDecoder<T>[],
|
||||
callback: Callback<T>
|
||||
): () => void {
|
||||
const observers: Array<[PubsubTopic, Observer<T>]> = [];
|
||||
|
||||
for (const decoder of Array.isArray(decoders) ? decoders : [decoders]) {
|
||||
const { pubsubTopic } = decoder;
|
||||
const ctObs: Map<ContentTopic, Set<Observer<T>>> = this.observers.get(
|
||||
pubsubTopic
|
||||
) ?? new Map();
|
||||
const observer = { pubsubTopic, decoder, callback };
|
||||
pushOrInitMapSet(ctObs, decoder.contentTopic, observer);
|
||||
|
||||
this.observers.set(pubsubTopic, ctObs);
|
||||
observers.push([pubsubTopic, observer]);
|
||||
}
|
||||
|
||||
return () => {
|
||||
this.removeObservers(observers);
|
||||
};
|
||||
}
|
||||
|
||||
private removeObservers<T extends IDecodedMessage>(
|
||||
observers: Array<[PubsubTopic, Observer<T>]>
|
||||
): void {
|
||||
for (const [pubsubTopic, observer] of observers) {
|
||||
const ctObs = this.observers.get(pubsubTopic);
|
||||
if (!ctObs) continue;
|
||||
|
||||
const contentTopic = observer.decoder.contentTopic;
|
||||
const _obs = ctObs.get(contentTopic);
|
||||
if (!_obs) continue;
|
||||
|
||||
_obs.delete(observer);
|
||||
ctObs.set(contentTopic, _obs);
|
||||
this.observers.set(pubsubTopic, ctObs);
|
||||
}
|
||||
}
|
||||
|
||||
public toSubscriptionIterator<T extends IDecodedMessage>(
|
||||
decoders: IDecoder<T> | IDecoder<T>[]
|
||||
): Promise<IAsyncIterator<T>> {
|
||||
return toAsyncIterator(this, decoders);
|
||||
}
|
||||
|
||||
public getActiveSubscriptions(): ActiveSubscriptions {
|
||||
const map = new Map();
|
||||
for (const pubsubTopic of this.pubsubTopics) {
|
||||
map.set(pubsubTopic, Array.from(this.observers.keys()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public getMeshPeers(topic: TopicStr = DefaultPubsubTopic): PeerIdStr[] {
|
||||
return this.gossipSub.getMeshPeers(topic);
|
||||
}
|
||||
|
||||
private subscribeToAllTopics(): void {
|
||||
for (const pubsubTopic of this.pubsubTopics) {
|
||||
this.gossipSubSubscribe(pubsubTopic);
|
||||
}
|
||||
}
|
||||
|
||||
private async processIncomingMessage<T extends IDecodedMessage>(
|
||||
pubsubTopic: string,
|
||||
bytes: Uint8Array
|
||||
): Promise<void> {
|
||||
const topicOnlyMsg = await this.defaultDecoder.fromWireToProtoObj(bytes);
|
||||
if (!topicOnlyMsg || !topicOnlyMsg.contentTopic) {
|
||||
log.warn("Message does not have a content topic, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the map of content topics for the given pubsubTopic
|
||||
const contentTopicMap = this.observers.get(pubsubTopic);
|
||||
if (!contentTopicMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the set of observers for the given contentTopic
|
||||
const observers = contentTopicMap.get(topicOnlyMsg.contentTopic) as Set<
|
||||
Observer<T>
|
||||
>;
|
||||
if (!observers) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array.from(observers).map(({ decoder, callback }) => {
|
||||
return (async () => {
|
||||
try {
|
||||
const protoMsg = await decoder.fromWireToProtoObj(bytes);
|
||||
if (!protoMsg) {
|
||||
log.error(
|
||||
"Internal error: message previously decoded failed on 2nd pass."
|
||||
);
|
||||
return;
|
||||
}
|
||||
const msg = await decoder.fromProtoObj(pubsubTopic, protoMsg);
|
||||
if (msg) {
|
||||
await callback(msg);
|
||||
} else {
|
||||
log.error(
|
||||
"Failed to decode messages on",
|
||||
topicOnlyMsg.contentTopic
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Error while decoding message:", error);
|
||||
}
|
||||
})();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a pubsub topic and start emitting Waku messages to observers.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
private gossipSubSubscribe(pubsubTopic: string): void {
|
||||
this.gossipSub.addEventListener(
|
||||
"gossipsub:message",
|
||||
(event: CustomEvent<GossipsubMessage>) => {
|
||||
if (event.detail.msg.topic !== pubsubTopic) return;
|
||||
|
||||
this.processIncomingMessage(
|
||||
event.detail.msg.topic,
|
||||
event.detail.msg.data
|
||||
).catch((e) => log.error("Failed to process incoming message", e));
|
||||
}
|
||||
);
|
||||
|
||||
this.gossipSub.topicValidators.set(pubsubTopic, messageValidator);
|
||||
this.gossipSub.subscribe(pubsubTopic);
|
||||
}
|
||||
|
||||
private isRelayPubsub(pubsub: Libp2pPubsub | undefined): boolean {
|
||||
return pubsub?.multicodecs?.includes(Relay.multicodec) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
export function wakuRelay(
|
||||
pubsubTopics: PubsubTopic[]
|
||||
): (libp2p: Libp2p) => IRelay {
|
||||
return (libp2p: Libp2p) => new Relay(libp2p, pubsubTopics);
|
||||
}
|
||||
|
||||
export function wakuGossipSub(
|
||||
init: Partial<RelayCreateOptions> = {}
|
||||
): (components: GossipSubComponents) => GossipSub {
|
||||
return (components: GossipSubComponents) => {
|
||||
init = {
|
||||
...init,
|
||||
msgIdFn: ({ data }) => sha256(data),
|
||||
// Ensure that no signature is included nor expected in the messages.
|
||||
globalSignaturePolicy: SignaturePolicy.StrictNoSign,
|
||||
fallbackToFloodsub: false
|
||||
};
|
||||
const pubsub = new GossipSub(components, init);
|
||||
pubsub.multicodecs = RelayCodecs;
|
||||
return pubsub;
|
||||
};
|
||||
}
|
||||
export * from "./relay.js";
|
||||
export * from "./create.js";
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
import {
|
||||
GossipSub,
|
||||
GossipSubComponents,
|
||||
GossipsubMessage,
|
||||
GossipsubOpts
|
||||
} from "@chainsafe/libp2p-gossipsub";
|
||||
import type { PeerIdStr, TopicStr } from "@chainsafe/libp2p-gossipsub/types";
|
||||
import { SignaturePolicy } from "@chainsafe/libp2p-gossipsub/types";
|
||||
import type { PubSub as Libp2pPubsub, PeerId } from "@libp2p/interface";
|
||||
import { sha256 } from "@noble/hashes/sha256";
|
||||
import {
|
||||
ActiveSubscriptions,
|
||||
Callback,
|
||||
DefaultPubsubTopic,
|
||||
IAsyncIterator,
|
||||
IDecodedMessage,
|
||||
IDecoder,
|
||||
IEncoder,
|
||||
IMessage,
|
||||
IRelay,
|
||||
Libp2p,
|
||||
ProtocolCreateOptions,
|
||||
ProtocolError,
|
||||
PubsubTopic,
|
||||
SDKProtocolResult
|
||||
} from "@waku/interfaces";
|
||||
import { isWireSizeUnderCap, toAsyncIterator } from "@waku/utils";
|
||||
import { pushOrInitMapSet } from "@waku/utils";
|
||||
import { Logger } from "@waku/utils";
|
||||
|
||||
import { RelayCodecs } from "./constants.js";
|
||||
import { messageValidator } from "./message_validator.js";
|
||||
import { TopicOnlyDecoder } from "./topic_only_message.js";
|
||||
|
||||
const log = new Logger("relay");
|
||||
|
||||
export type Observer<T extends IDecodedMessage> = {
|
||||
decoder: IDecoder<T>;
|
||||
callback: Callback<T>;
|
||||
};
|
||||
|
||||
export type RelayCreateOptions = ProtocolCreateOptions & GossipsubOpts;
|
||||
export type ContentTopic = string;
|
||||
|
||||
/**
|
||||
* Implements the [Waku v2 Relay protocol](https://rfc.vac.dev/spec/11/).
|
||||
* Throws if libp2p.pubsub does not support Waku Relay
|
||||
*/
|
||||
class Relay implements IRelay {
|
||||
public readonly pubsubTopics: Set<PubsubTopic>;
|
||||
private defaultDecoder: IDecoder<IDecodedMessage>;
|
||||
|
||||
public static multicodec: string = RelayCodecs[0];
|
||||
public readonly gossipSub: GossipSub;
|
||||
|
||||
/**
|
||||
* observers called when receiving new message.
|
||||
* Observers under key `""` are always called.
|
||||
*/
|
||||
private observers: Map<PubsubTopic, Map<ContentTopic, Set<unknown>>>;
|
||||
|
||||
public constructor(libp2p: Libp2p, pubsubTopics: PubsubTopic[]) {
|
||||
if (!this.isRelayPubsub(libp2p.services.pubsub)) {
|
||||
throw Error(
|
||||
`Failed to initialize Relay. libp2p.pubsub does not support ${Relay.multicodec}`
|
||||
);
|
||||
}
|
||||
|
||||
this.gossipSub = libp2p.services.pubsub as GossipSub;
|
||||
this.pubsubTopics = new Set(pubsubTopics);
|
||||
|
||||
if (this.gossipSub.isStarted()) {
|
||||
this.subscribeToAllTopics();
|
||||
}
|
||||
|
||||
this.observers = new Map();
|
||||
|
||||
// Default PubsubTopic decoder
|
||||
// TODO: User might want to decide what decoder should be used (e.g. for RLN)
|
||||
this.defaultDecoder = new TopicOnlyDecoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts the gossipsub protocol onto the libp2p node
|
||||
* and subscribes to all the topics.
|
||||
*
|
||||
* @override
|
||||
* @returns {void}
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
if (this.gossipSub.isStarted()) {
|
||||
throw Error("GossipSub already started.");
|
||||
}
|
||||
|
||||
await this.gossipSub.start();
|
||||
this.subscribeToAllTopics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Waku message.
|
||||
*/
|
||||
public async send(
|
||||
encoder: IEncoder,
|
||||
message: IMessage
|
||||
): Promise<SDKProtocolResult> {
|
||||
const successes: PeerId[] = [];
|
||||
|
||||
const { pubsubTopic } = encoder;
|
||||
if (!this.pubsubTopics.has(pubsubTopic)) {
|
||||
log.error("Failed to send waku relay: topic not configured");
|
||||
return {
|
||||
successes,
|
||||
failures: [
|
||||
{
|
||||
error: ProtocolError.TOPIC_NOT_CONFIGURED
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const msg = await encoder.toWire(message);
|
||||
if (!msg) {
|
||||
log.error("Failed to encode message, aborting publish");
|
||||
return {
|
||||
successes,
|
||||
failures: [
|
||||
{
|
||||
error: ProtocolError.ENCODE_FAILED
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
if (!isWireSizeUnderCap(msg)) {
|
||||
log.error("Failed to send waku relay: message is bigger that 1MB");
|
||||
return {
|
||||
successes,
|
||||
failures: [
|
||||
{
|
||||
error: ProtocolError.SIZE_TOO_BIG
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const { recipients } = await this.gossipSub.publish(pubsubTopic, msg);
|
||||
return {
|
||||
successes: recipients,
|
||||
failures: []
|
||||
};
|
||||
}
|
||||
|
||||
public subscribe<T extends IDecodedMessage>(
|
||||
decoders: IDecoder<T> | IDecoder<T>[],
|
||||
callback: Callback<T>
|
||||
): () => void {
|
||||
const observers: Array<[PubsubTopic, Observer<T>]> = [];
|
||||
|
||||
for (const decoder of Array.isArray(decoders) ? decoders : [decoders]) {
|
||||
const { pubsubTopic } = decoder;
|
||||
const ctObs: Map<ContentTopic, Set<Observer<T>>> = this.observers.get(
|
||||
pubsubTopic
|
||||
) ?? new Map();
|
||||
const observer = { pubsubTopic, decoder, callback };
|
||||
pushOrInitMapSet(ctObs, decoder.contentTopic, observer);
|
||||
|
||||
this.observers.set(pubsubTopic, ctObs);
|
||||
observers.push([pubsubTopic, observer]);
|
||||
}
|
||||
|
||||
return () => {
|
||||
this.removeObservers(observers);
|
||||
};
|
||||
}
|
||||
|
||||
private removeObservers<T extends IDecodedMessage>(
|
||||
observers: Array<[PubsubTopic, Observer<T>]>
|
||||
): void {
|
||||
for (const [pubsubTopic, observer] of observers) {
|
||||
const ctObs = this.observers.get(pubsubTopic);
|
||||
if (!ctObs) continue;
|
||||
|
||||
const contentTopic = observer.decoder.contentTopic;
|
||||
const _obs = ctObs.get(contentTopic);
|
||||
if (!_obs) continue;
|
||||
|
||||
_obs.delete(observer);
|
||||
ctObs.set(contentTopic, _obs);
|
||||
this.observers.set(pubsubTopic, ctObs);
|
||||
}
|
||||
}
|
||||
|
||||
public toSubscriptionIterator<T extends IDecodedMessage>(
|
||||
decoders: IDecoder<T> | IDecoder<T>[]
|
||||
): Promise<IAsyncIterator<T>> {
|
||||
return toAsyncIterator(this, decoders);
|
||||
}
|
||||
|
||||
public getActiveSubscriptions(): ActiveSubscriptions {
|
||||
const map = new Map();
|
||||
for (const pubsubTopic of this.pubsubTopics) {
|
||||
map.set(pubsubTopic, Array.from(this.observers.keys()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public getMeshPeers(topic: TopicStr = DefaultPubsubTopic): PeerIdStr[] {
|
||||
return this.gossipSub.getMeshPeers(topic);
|
||||
}
|
||||
|
||||
private subscribeToAllTopics(): void {
|
||||
for (const pubsubTopic of this.pubsubTopics) {
|
||||
this.gossipSubSubscribe(pubsubTopic);
|
||||
}
|
||||
}
|
||||
|
||||
private async processIncomingMessage<T extends IDecodedMessage>(
|
||||
pubsubTopic: string,
|
||||
bytes: Uint8Array
|
||||
): Promise<void> {
|
||||
const topicOnlyMsg = await this.defaultDecoder.fromWireToProtoObj(bytes);
|
||||
if (!topicOnlyMsg || !topicOnlyMsg.contentTopic) {
|
||||
log.warn("Message does not have a content topic, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the map of content topics for the given pubsubTopic
|
||||
const contentTopicMap = this.observers.get(pubsubTopic);
|
||||
if (!contentTopicMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the set of observers for the given contentTopic
|
||||
const observers = contentTopicMap.get(topicOnlyMsg.contentTopic) as Set<
|
||||
Observer<T>
|
||||
>;
|
||||
if (!observers) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array.from(observers).map(({ decoder, callback }) => {
|
||||
return (async () => {
|
||||
try {
|
||||
const protoMsg = await decoder.fromWireToProtoObj(bytes);
|
||||
if (!protoMsg) {
|
||||
log.error(
|
||||
"Internal error: message previously decoded failed on 2nd pass."
|
||||
);
|
||||
return;
|
||||
}
|
||||
const msg = await decoder.fromProtoObj(pubsubTopic, protoMsg);
|
||||
if (msg) {
|
||||
await callback(msg);
|
||||
} else {
|
||||
log.error(
|
||||
"Failed to decode messages on",
|
||||
topicOnlyMsg.contentTopic
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Error while decoding message:", error);
|
||||
}
|
||||
})();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a pubsub topic and start emitting Waku messages to observers.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
private gossipSubSubscribe(pubsubTopic: string): void {
|
||||
this.gossipSub.addEventListener(
|
||||
"gossipsub:message",
|
||||
(event: CustomEvent<GossipsubMessage>) => {
|
||||
if (event.detail.msg.topic !== pubsubTopic) return;
|
||||
|
||||
this.processIncomingMessage(
|
||||
event.detail.msg.topic,
|
||||
event.detail.msg.data
|
||||
).catch((e) => log.error("Failed to process incoming message", e));
|
||||
}
|
||||
);
|
||||
|
||||
this.gossipSub.topicValidators.set(pubsubTopic, messageValidator);
|
||||
this.gossipSub.subscribe(pubsubTopic);
|
||||
}
|
||||
|
||||
private isRelayPubsub(pubsub: Libp2pPubsub | undefined): boolean {
|
||||
return pubsub?.multicodecs?.includes(Relay.multicodec) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
export function wakuRelay(
|
||||
pubsubTopics: PubsubTopic[]
|
||||
): (libp2p: Libp2p) => IRelay {
|
||||
return (libp2p: Libp2p) => new Relay(libp2p, pubsubTopics);
|
||||
}
|
||||
|
||||
export function wakuGossipSub(
|
||||
init: Partial<RelayCreateOptions> = {}
|
||||
): (components: GossipSubComponents) => GossipSub {
|
||||
return (components: GossipSubComponents) => {
|
||||
init = {
|
||||
...init,
|
||||
msgIdFn: ({ data }) => sha256(data),
|
||||
// Ensure that no signature is included nor expected in the messages.
|
||||
globalSignaturePolicy: SignaturePolicy.StrictNoSign,
|
||||
fallbackToFloodsub: false
|
||||
};
|
||||
const pubsub = new GossipSub(components, init);
|
||||
pubsub.multicodecs = RelayCodecs;
|
||||
return pubsub;
|
||||
};
|
||||
}
|
|
@ -8,10 +8,6 @@
|
|||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./relay": {
|
||||
"types": "./dist/relay-node/index.d.ts",
|
||||
"import": "./dist/relay-node/index.js"
|
||||
}
|
||||
},
|
||||
"typesVersions": {
|
||||
|
@ -72,7 +68,6 @@
|
|||
"@waku/discovery": "0.0.3",
|
||||
"@waku/interfaces": "0.0.25",
|
||||
"@waku/proto": "^0.0.7",
|
||||
"@waku/relay": "0.0.13",
|
||||
"@waku/utils": "0.0.18",
|
||||
"libp2p": "^1.1.2"
|
||||
},
|
||||
|
@ -93,7 +88,6 @@
|
|||
"@waku/core": "0.0.30",
|
||||
"@waku/interfaces": "0.0.25",
|
||||
"@waku/message-hash": "^0.1.14",
|
||||
"@waku/relay": "0.0.13",
|
||||
"@waku/utils": "0.0.18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export { createLightNode } from "./create.js";
|
||||
export { defaultLibp2p } from "./libp2p.js";
|
||||
export { defaultLibp2p, createLibp2pAndUpdateOptions } from "./libp2p.js";
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { GossipSub } from "@chainsafe/libp2p-gossipsub";
|
||||
import { noise } from "@chainsafe/libp2p-noise";
|
||||
import { bootstrap } from "@libp2p/bootstrap";
|
||||
import { identify } from "@libp2p/identify";
|
||||
|
@ -15,7 +14,6 @@ import {
|
|||
type Libp2pComponents,
|
||||
type ShardInfo
|
||||
} from "@waku/interfaces";
|
||||
import { wakuGossipSub } from "@waku/relay";
|
||||
import { ensureShardingConfigured, Logger } from "@waku/utils";
|
||||
import { createLibp2p } from "libp2p";
|
||||
|
||||
|
@ -27,10 +25,6 @@ import {
|
|||
|
||||
import { defaultPeerDiscoveries } from "./discovery.js";
|
||||
|
||||
type PubsubService = {
|
||||
pubsub?: (components: Libp2pComponents) => GossipSub;
|
||||
};
|
||||
|
||||
type MetadataService = {
|
||||
metadata?: (components: Libp2pComponents) => IMetadata;
|
||||
};
|
||||
|
@ -39,7 +33,6 @@ const logger = new Logger("sdk:create");
|
|||
|
||||
export async function defaultLibp2p(
|
||||
shardInfo?: ShardInfo,
|
||||
wakuGossipSub?: PubsubService["pubsub"],
|
||||
options?: Partial<CreateLibp2pOptions>,
|
||||
userAgent?: string
|
||||
): Promise<Libp2p> {
|
||||
|
@ -56,10 +49,6 @@ export async function defaultLibp2p(
|
|||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
const pubsubService: PubsubService = wakuGossipSub
|
||||
? { pubsub: wakuGossipSub }
|
||||
: {};
|
||||
|
||||
const metadataService: MetadataService = shardInfo
|
||||
? { metadata: wakuMetadata(shardInfo) }
|
||||
: {};
|
||||
|
@ -83,7 +72,6 @@ export async function defaultLibp2p(
|
|||
options?.pingMaxInboundStreams ?? DefaultPingMaxInboundStreams
|
||||
}),
|
||||
...metadataService,
|
||||
...pubsubService,
|
||||
...options?.services
|
||||
}
|
||||
}) as any as Libp2p; // TODO: make libp2p include it;
|
||||
|
@ -109,7 +97,6 @@ export async function createLibp2pAndUpdateOptions(
|
|||
|
||||
const libp2p = await defaultLibp2p(
|
||||
shardInfo,
|
||||
wakuGossipSub(options),
|
||||
libp2pOptions,
|
||||
options?.userAgent
|
||||
);
|
||||
|
|
|
@ -10,7 +10,11 @@ export { utf8ToBytes, bytesToUtf8 } from "@waku/utils/bytes";
|
|||
export * from "./utils/content_topic.js";
|
||||
export * from "./waku.js";
|
||||
|
||||
export { createLightNode, defaultLibp2p } from "./create/index.js";
|
||||
export {
|
||||
createLightNode,
|
||||
defaultLibp2p,
|
||||
createLibp2pAndUpdateOptions
|
||||
} from "./create/index.js";
|
||||
export { wakuLightPush } from "./protocols/light_push.js";
|
||||
export { wakuFilter } from "./protocols/filter.js";
|
||||
export { wakuStore } from "./protocols/store.js";
|
||||
|
@ -18,4 +22,3 @@ export { wakuStore } from "./protocols/store.js";
|
|||
export * as waku from "@waku/core";
|
||||
export * as utils from "@waku/utils";
|
||||
export * from "@waku/interfaces";
|
||||
export * as relay from "@waku/relay";
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
import { type FullNode, type RelayNode } from "@waku/interfaces";
|
||||
import { RelayCreateOptions } from "@waku/relay";
|
||||
|
||||
import { createLibp2pAndUpdateOptions } from "../create/libp2p.js";
|
||||
import { CreateWakuNodeOptions, WakuNode, WakuOptions } from "../waku.js";
|
||||
|
||||
/**
|
||||
* Create a Waku node that uses Waku Relay to send and receive messages,
|
||||
* enabling some privacy preserving properties.
|
||||
* * @remarks
|
||||
* This function creates a Relay Node using the Waku Relay protocol.
|
||||
* While it is technically possible to use this function in a browser environment,
|
||||
* it is not recommended due to potential performance issues and limited browser capabilities.
|
||||
* If you are developing a browser-based application, consider alternative approaches like creating a Light Node
|
||||
* or use this function with caution.
|
||||
*/
|
||||
export async function createRelayNode(
|
||||
options: CreateWakuNodeOptions & Partial<RelayCreateOptions> = {
|
||||
pubsubTopics: []
|
||||
}
|
||||
): Promise<RelayNode> {
|
||||
const libp2p = await createLibp2pAndUpdateOptions(options);
|
||||
|
||||
return new WakuNode(options as WakuOptions, libp2p, {
|
||||
relay: true
|
||||
}) as RelayNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Waku node that uses all Waku protocols.
|
||||
*
|
||||
* This helper is not recommended except if:
|
||||
* - you are interfacing with nwaku v0.11 or below
|
||||
* - you are doing some form of testing
|
||||
*
|
||||
* If you are building a full node, it is recommended to use
|
||||
* [nwaku](github.com/status-im/nwaku) and its JSON RPC API or wip REST API.
|
||||
*
|
||||
* @see https://github.com/status-im/nwaku/issues/1085
|
||||
* @internal
|
||||
*/
|
||||
export async function createFullNode(
|
||||
options: CreateWakuNodeOptions & Partial<RelayCreateOptions> = {
|
||||
pubsubTopics: []
|
||||
}
|
||||
): Promise<FullNode> {
|
||||
const libp2p = await createLibp2pAndUpdateOptions(options);
|
||||
|
||||
return new WakuNode(options as WakuOptions, libp2p, {
|
||||
filter: true,
|
||||
lightpush: true,
|
||||
relay: true,
|
||||
store: true
|
||||
}) as FullNode;
|
||||
}
|
|
@ -16,7 +16,6 @@ import type {
|
|||
Waku
|
||||
} from "@waku/interfaces";
|
||||
import { Protocols } from "@waku/interfaces";
|
||||
import { wakuRelay } from "@waku/relay";
|
||||
import { Logger } from "@waku/utils";
|
||||
|
||||
import { wakuFilter } from "./protocols/filter.js";
|
||||
|
@ -61,7 +60,6 @@ type ProtocolsEnabled = {
|
|||
filter?: boolean;
|
||||
lightpush?: boolean;
|
||||
store?: boolean;
|
||||
relay?: boolean;
|
||||
};
|
||||
|
||||
export class WakuNode implements Waku {
|
||||
|
@ -76,20 +74,21 @@ export class WakuNode implements Waku {
|
|||
public constructor(
|
||||
options: WakuOptions,
|
||||
libp2p: Libp2p,
|
||||
protocolsEnabled: ProtocolsEnabled
|
||||
protocolsEnabled: ProtocolsEnabled,
|
||||
relay?: IRelay
|
||||
) {
|
||||
if (options.pubsubTopics.length == 0) {
|
||||
throw new Error("At least one pubsub topic must be provided");
|
||||
}
|
||||
this.pubsubTopics = options.pubsubTopics;
|
||||
|
||||
this.relay = relay;
|
||||
this.libp2p = libp2p;
|
||||
this.pubsubTopics = options.pubsubTopics;
|
||||
|
||||
protocolsEnabled = {
|
||||
filter: false,
|
||||
lightpush: false,
|
||||
store: false,
|
||||
relay: false,
|
||||
...protocolsEnabled
|
||||
};
|
||||
|
||||
|
@ -124,11 +123,6 @@ export class WakuNode implements Waku {
|
|||
this.filter = filter(libp2p);
|
||||
}
|
||||
|
||||
if (protocolsEnabled.relay) {
|
||||
const relay = wakuRelay(this.pubsubTopics);
|
||||
this.relay = relay(libp2p);
|
||||
}
|
||||
|
||||
log.info(
|
||||
"Waku node created",
|
||||
peerId,
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"@libp2p/peer-id": "^4.0.4",
|
||||
"@waku/core": "*",
|
||||
"@waku/enr": "*",
|
||||
"@waku/relay": "*",
|
||||
"@waku/interfaces": "*",
|
||||
"@waku/utils": "*",
|
||||
"app-root-path": "^3.1.0",
|
||||
|
|
|
@ -5,8 +5,8 @@ import {
|
|||
Protocols,
|
||||
ShardingParams
|
||||
} from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import { createLightNode, WakuNode } from "@waku/sdk";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { Logger, shardInfoToPubsubTopics } from "@waku/utils";
|
||||
import { Context } from "mocha";
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Multiaddr } from "@multiformats/multiaddr";
|
||||
import { EConnectionStateEvents, LightNode, Protocols } from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import { createLightNode } from "@waku/sdk";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { expect } from "chai";
|
||||
|
||||
import {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { waitForRemotePeer } from "@waku/core";
|
|||
import { EnrDecoder } from "@waku/enr";
|
||||
import type { RelayNode } from "@waku/interfaces";
|
||||
import { Protocols } from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import { expect } from "chai";
|
||||
|
||||
import {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { PeerId } from "@libp2p/interface";
|
||||
import { DecodedMessage, waitForRemotePeer } from "@waku/core";
|
||||
import { Protocols, RelayNode } from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes";
|
||||
import { expect } from "chai";
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
SingleShardInfo
|
||||
} from "@waku/interfaces";
|
||||
import { Protocols } from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import {
|
||||
contentTopicToPubsubTopic,
|
||||
pubsubTopicToSingleShardInfo,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createDecoder, createEncoder } from "@waku/core";
|
||||
import { RelayNode } from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import { utf8ToBytes } from "@waku/utils/bytes";
|
||||
import { expect } from "chai";
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
ShardInfo,
|
||||
ShardingParams
|
||||
} from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import { contentTopicToPubsubTopic, Logger } from "@waku/utils";
|
||||
import { Context } from "mocha";
|
||||
|
||||
|
|
|
@ -116,8 +116,9 @@ export async function startAndConnectLightNode(
|
|||
const wakuConnections = waku.libp2p.getConnections();
|
||||
const nwakuPeers = await instance.peers();
|
||||
|
||||
// TODO: return throw when nwaku releases following fix https://github.com/waku-org/nwaku/pull/2923
|
||||
if (wakuConnections.length < 1 || nwakuPeers.length < 1) {
|
||||
throw new Error(
|
||||
log.error(
|
||||
`Expected at least 1 peer in each node. Got waku connections: ${wakuConnections.length} and nwaku: ${nwakuPeers.length}`
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { waitForRemotePeer } from "@waku/core";
|
||||
import type { LightNode, RelayNode } from "@waku/interfaces";
|
||||
import { Protocols } from "@waku/interfaces";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import { createLightNode } from "@waku/sdk";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { expect } from "chai";
|
||||
|
||||
import {
|
||||
|
|
|
@ -8,12 +8,12 @@ import {
|
|||
createDecoder,
|
||||
createEncoder
|
||||
} from "@waku/message-encryption/symmetric";
|
||||
import { createRelayNode } from "@waku/relay";
|
||||
import {
|
||||
createLightNode,
|
||||
createEncoder as createPlainEncoder,
|
||||
DefaultUserAgent
|
||||
} from "@waku/sdk";
|
||||
import { createRelayNode } from "@waku/sdk/relay";
|
||||
import { bytesToUtf8, utf8ToBytes } from "@waku/utils/bytes";
|
||||
import { expect } from "chai";
|
||||
|
||||
|
|
Loading…
Reference in New Issue