2024-10-04 10:50:58 +02:00
|
|
|
import type { Peer, PeerId } from "@libp2p/interface";
|
2024-10-11 03:17:12 +05:30
|
|
|
import {
|
|
|
|
|
ConnectionManager,
|
|
|
|
|
getHealthManager,
|
|
|
|
|
LightPushCodec,
|
|
|
|
|
LightPushCore
|
|
|
|
|
} from "@waku/core";
|
2024-03-11 18:50:34 +05:30
|
|
|
import {
|
2024-10-17 00:49:24 +02:00
|
|
|
type CoreProtocolResult,
|
2024-03-11 18:50:34 +05:30
|
|
|
Failure,
|
|
|
|
|
type IEncoder,
|
2024-10-04 10:50:58 +02:00
|
|
|
ILightPush,
|
2024-03-11 18:50:34 +05:30
|
|
|
type IMessage,
|
2024-10-17 00:49:24 +02:00
|
|
|
type ISenderOptions,
|
2024-03-11 18:50:34 +05:30
|
|
|
type Libp2p,
|
|
|
|
|
type ProtocolCreateOptions,
|
2024-03-12 16:40:08 +05:30
|
|
|
ProtocolError,
|
2024-07-03 12:09:34 +05:30
|
|
|
SDKProtocolResult
|
2024-03-11 18:50:34 +05:30
|
|
|
} from "@waku/interfaces";
|
|
|
|
|
import { ensurePubsubTopicIsConfigured, Logger } from "@waku/utils";
|
|
|
|
|
|
2024-10-17 00:49:24 +02:00
|
|
|
import { DEFAULT_NUM_PEERS_TO_USE } from "../base_protocol.js";
|
2024-03-11 18:50:34 +05:30
|
|
|
|
|
|
|
|
const log = new Logger("sdk:light-push");
|
|
|
|
|
|
2024-10-17 00:49:24 +02:00
|
|
|
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
|
|
|
const DEFAULT_SEND_OPTIONS: ISenderOptions = {
|
|
|
|
|
autoRetry: false,
|
|
|
|
|
maxAttempts: DEFAULT_MAX_ATTEMPTS
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type RetryCallback = (peer: Peer) => Promise<CoreProtocolResult>;
|
2024-03-11 18:50:34 +05:30
|
|
|
|
2024-10-17 00:49:24 +02:00
|
|
|
export class LightPush implements ILightPush {
|
|
|
|
|
private numPeersToUse: number = DEFAULT_NUM_PEERS_TO_USE;
|
|
|
|
|
public readonly protocol: LightPushCore;
|
2024-09-17 11:34:59 +05:30
|
|
|
|
2024-07-19 15:58:17 +05:30
|
|
|
public constructor(
|
2024-06-19 01:52:16 -04:00
|
|
|
connectionManager: ConnectionManager,
|
2024-10-04 10:50:58 +02:00
|
|
|
private libp2p: Libp2p,
|
2024-06-19 01:52:16 -04:00
|
|
|
options?: ProtocolCreateOptions
|
|
|
|
|
) {
|
2024-10-17 00:49:24 +02:00
|
|
|
this.numPeersToUse = options?.numPeersToUse ?? DEFAULT_NUM_PEERS_TO_USE;
|
|
|
|
|
this.protocol = new LightPushCore(
|
|
|
|
|
connectionManager.configuredPubsubTopics,
|
|
|
|
|
libp2p
|
2024-08-13 05:23:20 +05:30
|
|
|
);
|
2024-03-11 18:50:34 +05:30
|
|
|
}
|
|
|
|
|
|
2024-07-19 15:58:17 +05:30
|
|
|
public async send(
|
2024-06-19 01:52:16 -04:00
|
|
|
encoder: IEncoder,
|
|
|
|
|
message: IMessage,
|
2024-10-17 00:49:24 +02:00
|
|
|
options: ISenderOptions = DEFAULT_SEND_OPTIONS
|
2024-06-19 01:52:16 -04:00
|
|
|
): Promise<SDKProtocolResult> {
|
2024-03-11 18:50:34 +05:30
|
|
|
const successes: PeerId[] = [];
|
|
|
|
|
const failures: Failure[] = [];
|
|
|
|
|
|
|
|
|
|
const { pubsubTopic } = encoder;
|
|
|
|
|
try {
|
|
|
|
|
ensurePubsubTopicIsConfigured(pubsubTopic, this.protocol.pubsubTopics);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
log.error("Failed to send waku light push: pubsub topic not configured");
|
|
|
|
|
return {
|
2024-10-04 10:50:58 +02:00
|
|
|
successes,
|
2024-03-11 18:50:34 +05:30
|
|
|
failures: [
|
|
|
|
|
{
|
2024-03-12 16:40:08 +05:30
|
|
|
error: ProtocolError.TOPIC_NOT_CONFIGURED
|
2024-03-11 18:50:34 +05:30
|
|
|
}
|
2024-10-04 10:50:58 +02:00
|
|
|
]
|
2024-03-11 18:50:34 +05:30
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 10:50:58 +02:00
|
|
|
const peers = await this.getConnectedPeers();
|
|
|
|
|
if (peers.length === 0) {
|
2024-03-11 18:50:34 +05:30
|
|
|
return {
|
|
|
|
|
successes,
|
2024-06-19 01:52:16 -04:00
|
|
|
failures: [
|
|
|
|
|
{
|
|
|
|
|
error: ProtocolError.NO_PEER_AVAILABLE
|
|
|
|
|
}
|
|
|
|
|
]
|
2024-03-11 18:50:34 +05:30
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 10:50:58 +02:00
|
|
|
const results = await Promise.allSettled(
|
|
|
|
|
peers.map((peer) => this.protocol.send(encoder, message, peer))
|
2024-03-11 18:50:34 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const result of results) {
|
2024-10-04 10:50:58 +02:00
|
|
|
if (result.status !== "fulfilled") {
|
2024-08-29 11:20:19 +02:00
|
|
|
log.error("Failed unexpectedly while sending:", result.reason);
|
2024-03-12 16:40:08 +05:30
|
|
|
failures.push({ error: ProtocolError.GENERIC_FAIL });
|
2024-10-04 10:50:58 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { failure, success } = result.value;
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
successes.push(success);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (failure) {
|
|
|
|
|
failures.push(failure);
|
|
|
|
|
|
2024-10-17 00:49:24 +02:00
|
|
|
if (options?.autoRetry) {
|
|
|
|
|
void this.attemptRetries(
|
|
|
|
|
(peer: Peer) => this.protocol.send(encoder, message, peer),
|
|
|
|
|
options.maxAttempts
|
2024-10-04 10:50:58 +02:00
|
|
|
);
|
|
|
|
|
}
|
2024-03-11 18:50:34 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 03:17:12 +05:30
|
|
|
getHealthManager().updateProtocolHealth(
|
|
|
|
|
this.protocol.multicodec,
|
|
|
|
|
successes.length
|
|
|
|
|
);
|
2024-10-04 10:50:58 +02:00
|
|
|
|
2024-03-11 18:50:34 +05:30
|
|
|
return {
|
|
|
|
|
successes,
|
|
|
|
|
failures
|
|
|
|
|
};
|
|
|
|
|
}
|
2024-10-04 10:50:58 +02:00
|
|
|
|
2024-10-17 00:49:24 +02:00
|
|
|
private async attemptRetries(
|
|
|
|
|
fn: RetryCallback,
|
|
|
|
|
maxAttempts?: number
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
maxAttempts = maxAttempts || DEFAULT_MAX_ATTEMPTS;
|
|
|
|
|
const connectedPeers = await this.getConnectedPeers();
|
|
|
|
|
|
|
|
|
|
if (connectedPeers.length === 0) {
|
|
|
|
|
log.warn("Cannot retry with no connected peers.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
|
|
|
const peer = connectedPeers[i % connectedPeers.length]; // always present as we checked for the length already
|
|
|
|
|
const response = await fn(peer);
|
|
|
|
|
|
|
|
|
|
if (response.success) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.info(
|
|
|
|
|
`Attempted retry for peer:${peer.id} failed with:${response?.failure?.error}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 10:50:58 +02:00
|
|
|
private async getConnectedPeers(): Promise<Peer[]> {
|
|
|
|
|
const peerIDs = this.libp2p.getPeers();
|
|
|
|
|
|
|
|
|
|
if (peerIDs.length === 0) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const peers = await Promise.all(
|
|
|
|
|
peerIDs.map(async (id) => {
|
|
|
|
|
try {
|
|
|
|
|
return await this.libp2p.peerStore.get(id);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return peers
|
|
|
|
|
.filter((p) => !!p)
|
|
|
|
|
.filter((p) => (p as Peer).protocols.includes(LightPushCodec))
|
|
|
|
|
.slice(0, this.numPeersToUse) as Peer[];
|
|
|
|
|
}
|
2024-03-11 18:50:34 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function wakuLightPush(
|
2024-06-19 01:52:16 -04:00
|
|
|
connectionManager: ConnectionManager,
|
2024-03-11 18:50:34 +05:30
|
|
|
init: Partial<ProtocolCreateOptions> = {}
|
2024-10-04 10:50:58 +02:00
|
|
|
): (libp2p: Libp2p) => ILightPush {
|
|
|
|
|
return (libp2p: Libp2p) => new LightPush(connectionManager, libp2p, init);
|
2024-03-11 18:50:34 +05:30
|
|
|
}
|