UNPKG

@waku/core

Version:

TypeScript implementation of the Waku v2 protocol

187 lines (158 loc) 4.67 kB
import type { PeerId } from "@libp2p/interface"; import { IncomingStreamData } from "@libp2p/interface"; import { type ClusterId, type IMetadata, type Libp2pComponents, type MetadataQueryResult, type PeerIdStr, ProtocolError, type ShardInfo } from "@waku/interfaces"; import { proto_metadata } from "@waku/proto"; import { encodeRelayShard, Logger } from "@waku/utils"; import all from "it-all"; import * as lp from "it-length-prefixed"; import { pipe } from "it-pipe"; import { Uint8ArrayList } from "uint8arraylist"; import { StreamManager } from "../stream_manager/index.js"; const log = new Logger("metadata"); export const MetadataCodec = "/vac/waku/metadata/1.0.0"; class Metadata implements IMetadata { private readonly streamManager: StreamManager; private readonly libp2pComponents: Libp2pComponents; protected handshakesConfirmed: Map<PeerIdStr, ShardInfo> = new Map(); public readonly multicodec = MetadataCodec; public constructor( public clusterId: ClusterId, libp2p: Libp2pComponents ) { this.streamManager = new StreamManager(MetadataCodec, libp2p); this.libp2pComponents = libp2p; void libp2p.registrar.handle(MetadataCodec, (streamData) => { void this.onRequest(streamData); }); } /** * Make a metadata query to a peer */ public async query(peerId: PeerId): Promise<MetadataQueryResult> { const request = proto_metadata.WakuMetadataRequest.encode({ clusterId: this.clusterId, shards: [] // Only services node need to provide shards }); const peer = await this.libp2pComponents.peerStore.get(peerId); if (!peer) { return { shardInfo: null, error: ProtocolError.NO_PEER_AVAILABLE }; } let stream; try { stream = await this.streamManager.getStream(peerId); } catch (error) { log.error("Failed to get stream", error); return { shardInfo: null, error: ProtocolError.NO_STREAM_AVAILABLE }; } const encodedResponse = await pipe( [request], lp.encode, stream, lp.decode, async (source) => await all(source) ); const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse); if (error) { return { shardInfo: null, error }; } await this.savePeerShardInfo(peerId, shardInfo); return { shardInfo, error: null }; } public async confirmOrAttemptHandshake( peerId: PeerId ): Promise<MetadataQueryResult> { const shardInfo = this.handshakesConfirmed.get(peerId.toString()); if (shardInfo) { return { shardInfo, error: null }; } return await this.query(peerId); } /** * Handle an incoming metadata request */ private async onRequest(streamData: IncomingStreamData): Promise<void> { try { const { stream, connection } = streamData; const encodedShardInfo = proto_metadata.WakuMetadataResponse.encode({ clusterId: this.clusterId, shards: [] // Only service nodes need to provide shards }); const encodedResponse = await pipe( [encodedShardInfo], lp.encode, stream, lp.decode, async (source) => await all(source) ); const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse); if (error) { return; } await this.savePeerShardInfo(connection.remotePeer, shardInfo); } catch (error) { log.error("Error handling metadata request", error); } } private decodeMetadataResponse( encodedResponse: Uint8ArrayList[] ): MetadataQueryResult { const bytes = new Uint8ArrayList(); encodedResponse.forEach((chunk) => { bytes.append(chunk); }); const response = proto_metadata.WakuMetadataResponse.decode( bytes ) as ShardInfo; if (!response) { log.error("Error decoding metadata response"); return { shardInfo: null, error: ProtocolError.DECODE_FAILED }; } return { shardInfo: response, error: null }; } private async savePeerShardInfo( peerId: PeerId, shardInfo: ShardInfo ): Promise<void> { // add or update the shardInfo to peer store await this.libp2pComponents.peerStore.merge(peerId, { metadata: { shardInfo: encodeRelayShard(shardInfo) } }); this.handshakesConfirmed.set(peerId.toString(), shardInfo); } } export function wakuMetadata( clusterId: ClusterId ): (components: Libp2pComponents) => IMetadata { return (components: Libp2pComponents) => new Metadata(clusterId, components); }