diff --git a/CHANGELOG.md b/CHANGELOG.md index caf8830aad..e4642ba344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `RateLimitProof` field in Waku Message protobuf for RLN. +### Changed + +- `Message` interface changed to ensure implementations do not omit fields. + ## [0.29.0] - 2022-09-21 ### Changed diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index 2a63316720..80d35fe92e 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -62,24 +62,24 @@ export interface RateLimitProof { } export interface ProtoMessage { - payload?: Uint8Array; - contentTopic?: string; - version?: number; - timestamp?: bigint; - rateLimitProof?: RateLimitProof; + payload: Uint8Array | undefined; + contentTopic: string | undefined; + version: number | undefined; + timestamp: bigint | undefined; + rateLimitProof: RateLimitProof | undefined; } export interface Message { - payload?: Uint8Array; - contentTopic?: string; - timestamp?: Date; - rateLimitProof?: RateLimitProof; + payload: Uint8Array | undefined; + contentTopic: string | undefined; + timestamp: Date | undefined; + rateLimitProof: RateLimitProof | undefined; } export interface Encoder { contentTopic: string; - encode: (message: Message) => Promise; - encodeProto: (message: Message) => Promise; + encode: (message: Partial) => Promise; + encodeProto: (message: Partial) => Promise; } export interface Decoder { diff --git a/src/lib/to_proto_message.spec.ts b/src/lib/to_proto_message.spec.ts new file mode 100644 index 0000000000..4bc7ca0fa7 --- /dev/null +++ b/src/lib/to_proto_message.spec.ts @@ -0,0 +1,24 @@ +import { expect } from "chai"; + +import { WakuMessage as WakuMessageProto } from "../proto/message"; + +import { toProtoMessage } from "./to_proto_message"; + +describe("to proto message", () => { + it("Fields are not dropped", () => { + const wire: WakuMessageProto = { + contentTopic: "foo", + }; + + const protoMessage = toProtoMessage(wire); + + expect(protoMessage.contentTopic).to.eq("foo"); + + const keys = Object.keys(protoMessage); + expect(keys).to.contain("payload"); + expect(keys).to.contain("contentTopic"); + expect(keys).to.contain("version"); + expect(keys).to.contain("timestamp"); + expect(keys).to.contain("rateLimitProof"); + }); +}); diff --git a/src/lib/to_proto_message.ts b/src/lib/to_proto_message.ts new file mode 100644 index 0000000000..edb8a7695d --- /dev/null +++ b/src/lib/to_proto_message.ts @@ -0,0 +1,15 @@ +import { WakuMessage as WakuMessageProto } from "../proto/message"; + +import { ProtoMessage } from "./interfaces"; + +const EmptyMessage: ProtoMessage = { + payload: undefined, + contentTopic: undefined, + version: undefined, + timestamp: undefined, + rateLimitProof: undefined, +}; + +export function toProtoMessage(wire: WakuMessageProto): ProtoMessage { + return { ...EmptyMessage, ...wire }; +} diff --git a/src/lib/waku_filter/index.ts b/src/lib/waku_filter/index.ts index ff36b086fc..10914021ae 100644 --- a/src/lib/waku_filter/index.ts +++ b/src/lib/waku_filter/index.ts @@ -18,6 +18,7 @@ import { selectPeerForProtocol, selectRandomPeer, } from "../select_peer"; +import { toProtoMessage } from "../to_proto_message"; import { ContentFilter, FilterRPC } from "./filter_rpc"; export { ContentFilter }; @@ -198,7 +199,7 @@ export class WakuFilter { // noinspection ES6MissingAwait decoders.forEach(async (dec) => { if (msg) return; - const decoded = await dec.decode(protoMessage); + const decoded = await dec.decode(toProtoMessage(protoMessage)); if (!decoded) { log("Not able to decode message"); return; diff --git a/src/lib/waku_light_push/index.ts b/src/lib/waku_light_push/index.ts index 3681dfbf90..5e4dbd7dba 100644 --- a/src/lib/waku_light_push/index.ts +++ b/src/lib/waku_light_push/index.ts @@ -53,7 +53,7 @@ export class WakuLightPush { async push( encoder: Encoder, - message: Message, + message: Partial, opts?: PushOptions ): Promise { const pubSubTopic = opts?.pubSubTopic ? opts.pubSubTopic : this.pubSubTopic; diff --git a/src/lib/waku_message/topic_only_message.ts b/src/lib/waku_message/topic_only_message.ts index 682c732ef9..3b543e93ae 100644 --- a/src/lib/waku_message/topic_only_message.ts +++ b/src/lib/waku_message/topic_only_message.ts @@ -6,6 +6,10 @@ import type { Decoder, Message, ProtoMessage } from "../interfaces"; const log = debug("waku:message:topic-only"); export class TopicOnlyMessage implements Message { + public payload: undefined; + public rateLimitProof: undefined; + public timestamp: undefined; + constructor(private proto: proto.TopicOnlyMessage) {} get contentTopic(): string { @@ -19,7 +23,13 @@ export class TopicOnlyDecoder implements Decoder { decodeProto(bytes: Uint8Array): Promise { const protoMessage = proto.TopicOnlyMessage.decode(bytes); log("Message decoded", protoMessage); - return Promise.resolve(protoMessage); + return Promise.resolve({ + contentTopic: protoMessage.contentTopic, + payload: undefined, + rateLimitProof: undefined, + timestamp: undefined, + version: undefined, + }); } async decode(proto: ProtoMessage): Promise { diff --git a/src/lib/waku_message/version_0.ts b/src/lib/waku_message/version_0.ts index 932d532998..683f67da75 100644 --- a/src/lib/waku_message/version_0.ts +++ b/src/lib/waku_message/version_0.ts @@ -1,7 +1,7 @@ import debug from "debug"; import * as proto from "../../proto/message"; -import { Decoder, Message, ProtoMessage } from "../interfaces"; +import { Decoder, Message, ProtoMessage, RateLimitProof } from "../interfaces"; import { Encoder } from "../interfaces"; const log = debug("waku:message:version-0"); @@ -54,16 +54,20 @@ export class MessageV0 implements Message { // https://github.com/status-im/js-waku/issues/921 return this.proto.version ?? 0; } + + get rateLimitProof(): RateLimitProof | undefined { + return this.proto.rateLimitProof; + } } export class EncoderV0 implements Encoder { constructor(public contentTopic: string) {} - async encode(message: Message): Promise { + async encode(message: Partial): Promise { return proto.WakuMessage.encode(await this.encodeProto(message)); } - async encodeProto(message: Message): Promise { + async encodeProto(message: Partial): Promise { const timestamp = message.timestamp ?? new Date(); return { @@ -71,6 +75,7 @@ export class EncoderV0 implements Encoder { version: Version, contentTopic: message.contentTopic ?? this.contentTopic, timestamp: BigInt(timestamp.valueOf()) * OneMillion, + rateLimitProof: message.rateLimitProof, }; } } @@ -81,7 +86,13 @@ export class DecoderV0 implements Decoder { decodeProto(bytes: Uint8Array): Promise { const protoMessage = proto.WakuMessage.decode(bytes); log("Message decoded", protoMessage); - return Promise.resolve(protoMessage); + return Promise.resolve({ + payload: protoMessage.payload ?? undefined, + contentTopic: protoMessage.contentTopic ?? undefined, + version: protoMessage.version ?? undefined, + timestamp: protoMessage.timestamp ?? undefined, + rateLimitProof: protoMessage.rateLimitProof ?? undefined, + }); } async decode(proto: ProtoMessage): Promise { diff --git a/src/lib/waku_message/version_1.ts b/src/lib/waku_message/version_1.ts index f0af9a7223..a2b588dd77 100644 --- a/src/lib/waku_message/version_1.ts +++ b/src/lib/waku_message/version_1.ts @@ -52,14 +52,16 @@ export class AsymEncoder implements Encoder { private sigPrivKey?: Uint8Array ) {} - async encode(message: Message): Promise { + async encode(message: Partial): Promise { const protoMessage = await this.encodeProto(message); if (!protoMessage) return; return proto.WakuMessage.encode(protoMessage); } - async encodeProto(message: Message): Promise { + async encodeProto( + message: Partial + ): Promise { const timestamp = message.timestamp ?? new Date(); if (!message.payload) { log("No payload to encrypt, skipping: ", message); @@ -74,6 +76,7 @@ export class AsymEncoder implements Encoder { version: Version, contentTopic: this.contentTopic, timestamp: BigInt(timestamp.valueOf()) * OneMillion, + rateLimitProof: message.rateLimitProof, }; } } @@ -85,14 +88,16 @@ export class SymEncoder implements Encoder { private sigPrivKey?: Uint8Array ) {} - async encode(message: Message): Promise { + async encode(message: Partial): Promise { const protoMessage = await this.encodeProto(message); if (!protoMessage) return; return proto.WakuMessage.encode(protoMessage); } - async encodeProto(message: Message): Promise { + async encodeProto( + message: Partial + ): Promise { const timestamp = message.timestamp ?? new Date(); if (!message.payload) { log("No payload to encrypt, skipping: ", message); @@ -106,6 +111,7 @@ export class SymEncoder implements Encoder { version: Version, contentTopic: this.contentTopic, timestamp: BigInt(timestamp.valueOf()) * OneMillion, + rateLimitProof: message.rateLimitProof, }; } } diff --git a/src/lib/waku_relay/index.ts b/src/lib/waku_relay/index.ts index 6dd7f5c729..a4ebe04747 100644 --- a/src/lib/waku_relay/index.ts +++ b/src/lib/waku_relay/index.ts @@ -92,7 +92,10 @@ export class WakuRelay extends GossipSub { /** * Send Waku message. */ - public async send(encoder: Encoder, message: Message): Promise { + public async send( + encoder: Encoder, + message: Partial + ): Promise { const msg = await encoder.encode(message); if (!msg) { log("Failed to encode message, aborting publish"); diff --git a/src/lib/waku_store/index.ts b/src/lib/waku_store/index.ts index 8de878653b..ce53bf97e2 100644 --- a/src/lib/waku_store/index.ts +++ b/src/lib/waku_store/index.ts @@ -14,6 +14,7 @@ import { DefaultPubSubTopic, StoreCodecs } from "../constants"; import { Decoder, Message } from "../interfaces"; import { selectConnection } from "../select_connection"; import { getPeersForProtocol, selectPeerForProtocol } from "../select_peer"; +import { toProtoMessage } from "../to_proto_message"; import { HistoryRPC, PageDirection, Params } from "./history_rpc"; @@ -341,7 +342,7 @@ async function* paginate( if (typeof contentTopic !== "undefined") { const decoder = decoders.get(contentTopic); if (decoder) { - return decoder.decode(protoMsg); + return decoder.decode(toProtoMessage(protoMsg)); } } return Promise.resolve(undefined);