Merge pull request #1231 from waku-org/feat/pubsub-topic-message

This commit is contained in:
fryorcraken.eth 2023-03-13 14:00:57 +11:00 committed by GitHub
commit 809681aee8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 68 additions and 22 deletions

View File

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

View File

@ -14,7 +14,10 @@ export class TopicOnlyMessage implements IDecodedMessage {
public timestamp: undefined; public timestamp: undefined;
public ephemeral: undefined; public ephemeral: undefined;
constructor(private proto: ProtoTopicOnlyMessage) {} constructor(
public pubSubTopic: string,
private proto: ProtoTopicOnlyMessage
) {}
get contentTopic(): string { get contentTopic(): string {
return this.proto.contentTopic; return this.proto.contentTopic;
@ -38,8 +41,9 @@ export class TopicOnlyDecoder implements IDecoder<TopicOnlyMessage> {
} }
async fromProtoObj( async fromProtoObj(
pubSubTopic: string,
proto: IProtoMessage proto: IProtoMessage
): Promise<TopicOnlyMessage | undefined> { ): 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"; import { createDecoder, createEncoder, DecodedMessage } from "./version_0.js";
const TestContentTopic = "/test/1/waku-message/utf8"; const TestContentTopic = "/test/1/waku-message/utf8";
const TestPubSubTopic = "/test/pubsub/topic";
describe("Waku Message version 0", function () { describe("Waku Message version 0", function () {
it("Round trip binary serialization", async function () { it("Round trip binary serialization", async function () {
@ -16,10 +17,12 @@ describe("Waku Message version 0", function () {
const decoder = createDecoder(TestContentTopic); const decoder = createDecoder(TestContentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes); const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj( const result = (await decoder.fromProtoObj(
TestPubSubTopic,
protoResult! protoResult!
)) as DecodedMessage; )) as DecodedMessage;
expect(result.contentTopic).to.eq(TestContentTopic); expect(result.contentTopic).to.eq(TestContentTopic);
expect(result.pubSubTopic).to.eq(TestPubSubTopic);
expect(result.version).to.eq(0); expect(result.version).to.eq(0);
expect(result.ephemeral).to.be.false; expect(result.ephemeral).to.be.false;
expect(result.payload).to.deep.eq(payload); expect(result.payload).to.deep.eq(payload);
@ -39,14 +42,11 @@ describe("Waku Message version 0", function () {
const decoder = createDecoder(TestContentTopic); const decoder = createDecoder(TestContentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes); const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj( const result = (await decoder.fromProtoObj(
TestPubSubTopic,
protoResult! protoResult!
)) as DecodedMessage; )) as DecodedMessage;
expect(result.contentTopic).to.eq(TestContentTopic);
expect(result.version).to.eq(0);
expect(result.ephemeral).to.be.true; 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 { proto };
export class DecodedMessage implements IDecodedMessage { export class DecodedMessage implements IDecodedMessage {
constructor(protected proto: proto.WakuMessage) {} constructor(public pubSubTopic: string, protected proto: proto.WakuMessage) {}
get ephemeral(): boolean { get ephemeral(): boolean {
return Boolean(this.proto.ephemeral); return Boolean(this.proto.ephemeral);
@ -115,6 +115,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
} }
async fromProtoObj( async fromProtoObj(
pubSubTopic: string,
proto: IProtoMessage proto: IProtoMessage
): Promise<DecodedMessage | undefined> { ): Promise<DecodedMessage | undefined> {
// https://rfc.vac.dev/spec/14/ // https://rfc.vac.dev/spec/14/
@ -129,7 +130,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
return Promise.resolve(undefined); 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>( private async processIncomingMessage<T extends IDecodedMessage>(
pubSubTopic: string,
bytes: Uint8Array bytes: Uint8Array
): Promise<void> { ): Promise<void> {
const topicOnlyMsg = await this.defaultDecoder.fromWireToProtoObj(bytes); 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."); log("Internal error: message previously decoded failed on 2nd pass.");
return; return;
} }
const msg = await decoder.fromProtoObj(protoMsg); const msg = await decoder.fromProtoObj(pubSubTopic, protoMsg);
if (msg) { if (msg) {
callback(msg); callback(msg);
} else { } else {
@ -165,9 +166,10 @@ class Relay extends GossipSub implements IRelay {
if (event.detail.msg.topic !== pubSubTopic) return; if (event.detail.msg.topic !== pubSubTopic) return;
log(`Message received on ${pubSubTopic}`); log(`Message received on ${pubSubTopic}`);
this.processIncomingMessage(event.detail.msg.data).catch((e) => this.processIncomingMessage(
log("Failed to process incoming message", e) 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") { if (typeof contentTopic !== "undefined") {
const decoder = decoders.get(contentTopic); const decoder = decoders.get(contentTopic);
if (decoder) { if (decoder) {
return decoder.fromProtoObj(toProtoMessage(protoMsg)); return decoder.fromProtoObj(
queryOpts.pubSubTopic,
toProtoMessage(protoMsg)
);
} }
} }
return Promise.resolve(undefined); return Promise.resolve(undefined);

View File

@ -50,6 +50,7 @@ export interface IEncoder {
export interface IDecodedMessage { export interface IDecodedMessage {
payload: Uint8Array; payload: Uint8Array;
contentTopic: string; contentTopic: string;
pubSubTopic: string;
timestamp: Date | undefined; timestamp: Date | undefined;
rateLimitProof: IRateLimitProof | undefined; rateLimitProof: IRateLimitProof | undefined;
ephemeral: boolean | undefined; ephemeral: boolean | undefined;
@ -58,5 +59,8 @@ export interface IDecodedMessage {
export interface IDecoder<T extends IDecodedMessage> { export interface IDecoder<T extends IDecodedMessage> {
contentTopic: string; contentTopic: string;
fromWireToProtoObj: (bytes: Uint8Array) => Promise<IProtoMessage | undefined>; 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; private readonly _decodedPayload: Uint8Array;
constructor( constructor(
pubSubTopic: string,
proto: proto.WakuMessage, proto: proto.WakuMessage,
decodedPayload: Uint8Array, decodedPayload: Uint8Array,
public signature?: Uint8Array, public signature?: Uint8Array,
public signaturePublicKey?: Uint8Array public signaturePublicKey?: Uint8Array
) { ) {
super(proto); super(pubSubTopic, proto);
this._decodedPayload = decodedPayload; this._decodedPayload = decodedPayload;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -334,6 +334,7 @@ describe("Waku Relay [node only]", () => {
await waku3NoMsgPromise; await waku3NoMsgPromise;
expect(bytesToUtf8(waku2ReceivedMsg.payload!)).to.eq(messageText); 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; const msg = await promise;
if (msg) { if (msg) {
messages.push(msg); messages.push(msg);
expect(msg.pubSubTopic).to.eq(customPubSubTopic);
} }
}); });