feat!: expose pubsub topic in `IDecodedMessage`

Needed for deterministic message hash.

Ref: #1208
This commit is contained in:
fryorcraken.eth 2023-03-10 16:43:21 +11:00
parent 0c63b291f7
commit 628ac50d71
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
15 changed files with 68 additions and 22 deletions

View File

@ -34,6 +34,7 @@ export type RequestID = string;
type Subscription<T extends IDecodedMessage> = {
decoders: IDecoder<T>[];
callback: Callback<T>;
pubSubTopic: string;
};
/**
@ -108,7 +109,8 @@ class Filter extends BaseProtocol implements IFilter {
throw e;
}
this.subscriptions.set(requestId, { callback, decoders });
const subscription: Subscription<T> = { callback, decoders, pubSubTopic };
this.subscriptions.set(requestId, subscription);
return async () => {
await this.unsubscribe(pubSubTopic, contentFilters, requestId, peer);
@ -150,7 +152,7 @@ class Filter extends BaseProtocol implements IFilter {
log(`No subscription locally registered for request ID ${requestId}`);
return;
}
const { decoders, callback } = subscription;
const { decoders, callback, pubSubTopic } = subscription;
if (!decoders || !decoders.length) {
log(`No decoder registered for request ID ${requestId}`);
@ -170,7 +172,10 @@ class Filter extends BaseProtocol implements IFilter {
// noinspection ES6MissingAwait
decoders.forEach(async (dec: IDecoder<T>) => {
if (didDecodeMsg) return;
const decoded = await dec.fromProtoObj(toProtoMessage(protoMessage));
const decoded = await dec.fromProtoObj(
pubSubTopic,
toProtoMessage(protoMessage)
);
if (!decoded) {
log("Not able to decode message");
return;

View File

@ -14,7 +14,10 @@ export class TopicOnlyMessage implements IDecodedMessage {
public timestamp: undefined;
public ephemeral: undefined;
constructor(private proto: ProtoTopicOnlyMessage) {}
constructor(
public pubSubTopic: string,
private proto: ProtoTopicOnlyMessage
) {}
get contentTopic(): string {
return this.proto.contentTopic;
@ -38,8 +41,9 @@ export class TopicOnlyDecoder implements IDecoder<TopicOnlyMessage> {
}
async fromProtoObj(
pubSubTopic: string,
proto: IProtoMessage
): Promise<TopicOnlyMessage | undefined> {
return new TopicOnlyMessage(proto);
return new TopicOnlyMessage(pubSubTopic, proto);
}
}

View File

@ -4,6 +4,7 @@ import fc from "fast-check";
import { createDecoder, createEncoder, DecodedMessage } from "./version_0.js";
const TestContentTopic = "/test/1/waku-message/utf8";
const TestPubSubTopic = "/test/pubsub/topic";
describe("Waku Message version 0", function () {
it("Round trip binary serialization", async function () {
@ -16,10 +17,12 @@ describe("Waku Message version 0", function () {
const decoder = createDecoder(TestContentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
TestPubSubTopic,
protoResult!
)) as DecodedMessage;
expect(result.contentTopic).to.eq(TestContentTopic);
expect(result.pubSubTopic).to.eq(TestPubSubTopic);
expect(result.version).to.eq(0);
expect(result.ephemeral).to.be.false;
expect(result.payload).to.deep.eq(payload);
@ -39,14 +42,11 @@ describe("Waku Message version 0", function () {
const decoder = createDecoder(TestContentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
TestPubSubTopic,
protoResult!
)) as DecodedMessage;
expect(result.contentTopic).to.eq(TestContentTopic);
expect(result.version).to.eq(0);
expect(result.ephemeral).to.be.true;
expect(result.payload).to.deep.eq(payload);
expect(result.timestamp).to.not.be.undefined;
})
);
});

View File

@ -17,7 +17,7 @@ export const Version = 0;
export { proto };
export class DecodedMessage implements IDecodedMessage {
constructor(protected proto: proto.WakuMessage) {}
constructor(public pubSubTopic: string, protected proto: proto.WakuMessage) {}
get ephemeral(): boolean {
return Boolean(this.proto.ephemeral);
@ -115,6 +115,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
}
async fromProtoObj(
pubSubTopic: string,
proto: IProtoMessage
): Promise<DecodedMessage | undefined> {
// https://rfc.vac.dev/spec/14/
@ -129,7 +130,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
return Promise.resolve(undefined);
}
return new DecodedMessage(proto);
return new DecodedMessage(pubSubTopic, proto);
}
}

View File

@ -122,6 +122,7 @@ class Relay extends GossipSub implements IRelay {
}
private async processIncomingMessage<T extends IDecodedMessage>(
pubSubTopic: string,
bytes: Uint8Array
): Promise<void> {
const topicOnlyMsg = await this.defaultDecoder.fromWireToProtoObj(bytes);
@ -143,7 +144,7 @@ class Relay extends GossipSub implements IRelay {
log("Internal error: message previously decoded failed on 2nd pass.");
return;
}
const msg = await decoder.fromProtoObj(protoMsg);
const msg = await decoder.fromProtoObj(pubSubTopic, protoMsg);
if (msg) {
callback(msg);
} else {
@ -165,9 +166,10 @@ class Relay extends GossipSub implements IRelay {
if (event.detail.msg.topic !== pubSubTopic) return;
log(`Message received on ${pubSubTopic}`);
this.processIncomingMessage(event.detail.msg.data).catch((e) =>
log("Failed to process incoming message", e)
);
this.processIncomingMessage(
event.detail.msg.topic,
event.detail.msg.data
).catch((e) => log("Failed to process incoming message", e));
}
);

View File

@ -311,7 +311,10 @@ async function* paginate<T extends IDecodedMessage>(
if (typeof contentTopic !== "undefined") {
const decoder = decoders.get(contentTopic);
if (decoder) {
return decoder.fromProtoObj(toProtoMessage(protoMsg));
return decoder.fromProtoObj(
queryOpts.pubSubTopic,
toProtoMessage(protoMsg)
);
}
}
return Promise.resolve(undefined);

View File

@ -50,6 +50,7 @@ export interface IEncoder {
export interface IDecodedMessage {
payload: Uint8Array;
contentTopic: string;
pubSubTopic: string;
timestamp: Date | undefined;
rateLimitProof: IRateLimitProof | undefined;
ephemeral: boolean | undefined;
@ -58,5 +59,8 @@ export interface IDecodedMessage {
export interface IDecoder<T extends IDecodedMessage> {
contentTopic: string;
fromWireToProtoObj: (bytes: Uint8Array) => Promise<IProtoMessage | undefined>;
fromProtoObj: (proto: IProtoMessage) => Promise<T | undefined>;
fromProtoObj: (
pubSubTopic: string,
proto: IProtoMessage
) => Promise<T | undefined>;
}

View File

@ -11,12 +11,13 @@ export class DecodedMessage
private readonly _decodedPayload: Uint8Array;
constructor(
pubSubTopic: string,
proto: proto.WakuMessage,
decodedPayload: Uint8Array,
public signature?: Uint8Array,
public signaturePublicKey?: Uint8Array
) {
super(proto);
super(pubSubTopic, proto);
this._decodedPayload = decodedPayload;
}

View File

@ -5,6 +5,7 @@ import { getPublicKey } from "./crypto/index.js";
import { createDecoder, createEncoder } from "./ecies.js";
const TestContentTopic = "/test/1/waku-message/utf8";
const TestPubSubTopic = "/test/pubsub/topic";
describe("Ecies Encryption", function () {
it("Round trip binary encryption [ecies, no signature]", async function () {
@ -24,10 +25,14 @@ describe("Ecies Encryption", function () {
const decoder = createDecoder(TestContentTopic, privateKey);
const protoResult = await decoder.fromWireToProtoObj(bytes!);
if (!protoResult) throw "Failed to proto decode";
const result = await decoder.fromProtoObj(protoResult);
const result = await decoder.fromProtoObj(
TestPubSubTopic,
protoResult
);
if (!result) throw "Failed to decode";
expect(result.contentTopic).to.equal(TestContentTopic);
expect(result.pubSubTopic).to.equal(TestPubSubTopic);
expect(result.version).to.equal(1);
expect(result?.payload).to.deep.equal(payload);
expect(result.signature).to.be.undefined;
@ -59,10 +64,14 @@ describe("Ecies Encryption", function () {
const decoder = createDecoder(TestContentTopic, bobPrivateKey);
const protoResult = await decoder.fromWireToProtoObj(bytes!);
if (!protoResult) throw "Failed to proto decode";
const result = await decoder.fromProtoObj(protoResult);
const result = await decoder.fromProtoObj(
TestPubSubTopic,
protoResult
);
if (!result) throw "Failed to decode";
expect(result.contentTopic).to.equal(TestContentTopic);
expect(result.pubSubTopic).to.equal(TestPubSubTopic);
expect(result.version).to.equal(1);
expect(result?.payload).to.deep.equal(payload);
expect(result.signature).to.not.be.undefined;

View File

@ -95,6 +95,7 @@ class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
}
async fromProtoObj(
pubSubTopic: string,
protoMessage: IProtoMessage
): Promise<DecodedMessage | undefined> {
const cipherPayload = protoMessage.payload;
@ -135,6 +136,7 @@ class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
log("Message decrypted", protoMessage);
return new DecodedMessage(
pubSubTopic,
protoMessage,
res.payload,
res.sig?.signature,

View File

@ -5,6 +5,7 @@ import { getPublicKey } from "./crypto/index.js";
import { createDecoder, createEncoder } from "./symmetric.js";
const TestContentTopic = "/test/1/waku-message/utf8";
const TestPubSubTopic = "/test/pubsub/topic";
describe("Symmetric Encryption", function () {
it("Round trip binary encryption [symmetric, no signature]", async function () {
@ -22,10 +23,14 @@ describe("Symmetric Encryption", function () {
const decoder = createDecoder(TestContentTopic, symKey);
const protoResult = await decoder.fromWireToProtoObj(bytes!);
if (!protoResult) throw "Failed to proto decode";
const result = await decoder.fromProtoObj(protoResult);
const result = await decoder.fromProtoObj(
TestPubSubTopic,
protoResult
);
if (!result) throw "Failed to decode";
expect(result.contentTopic).to.equal(TestContentTopic);
expect(result.pubSubTopic).to.equal(TestPubSubTopic);
expect(result.version).to.equal(1);
expect(result?.payload).to.deep.equal(payload);
expect(result.signature).to.be.undefined;
@ -54,10 +59,14 @@ describe("Symmetric Encryption", function () {
const decoder = createDecoder(TestContentTopic, symKey);
const protoResult = await decoder.fromWireToProtoObj(bytes!);
if (!protoResult) throw "Failed to proto decode";
const result = await decoder.fromProtoObj(protoResult);
const result = await decoder.fromProtoObj(
TestPubSubTopic,
protoResult
);
if (!result) throw "Failed to decode";
expect(result.contentTopic).to.equal(TestContentTopic);
expect(result.pubSubTopic).to.equal(TestPubSubTopic);
expect(result.version).to.equal(1);
expect(result?.payload).to.deep.equal(payload);
expect(result.signature).to.not.be.undefined;

View File

@ -90,6 +90,7 @@ class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
}
async fromProtoObj(
pubSubTopic: string,
protoMessage: IProtoMessage
): Promise<DecodedMessage | undefined> {
const cipherPayload = protoMessage.payload;
@ -130,6 +131,7 @@ class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
log("Message decrypted", protoMessage);
return new DecodedMessage(
pubSubTopic,
protoMessage,
res.payload,
res.sig?.signature,

View File

@ -2,6 +2,7 @@ import {
createDecoder,
createEncoder,
DecodedMessage,
DefaultPubSubTopic,
waitForRemotePeer,
} from "@waku/core";
import { createLightNode } from "@waku/create";
@ -52,6 +53,7 @@ describe("Waku Filter", () => {
log("Got a message");
messageCount++;
expect(msg.contentTopic).to.eq(TestContentTopic);
expect(msg.pubSubTopic).to.eq(DefaultPubSubTopic);
expect(bytesToUtf8(msg.payload)).to.eq(messageText);
};

View File

@ -334,6 +334,7 @@ describe("Waku Relay [node only]", () => {
await waku3NoMsgPromise;
expect(bytesToUtf8(waku2ReceivedMsg.payload!)).to.eq(messageText);
expect(waku2ReceivedMsg.pubSubTopic).to.eq(pubSubTopic);
});
});

View File

@ -615,6 +615,7 @@ describe("Waku Store, custom pubsub topic", () => {
const msg = await promise;
if (msg) {
messages.push(msg);
expect(msg.pubSubTopic).to.eq(customPubSubTopic);
}
});