From 8723d6860735f2fcc444b4395ccce80090184d9b Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:39:37 +0000 Subject: [PATCH 1/3] fix(relayer): Ensure argument keys are sorted before hashing Unclear whether this has actually caused an issue, but implementation details at the upstream RPC provider may lead to undetected duplicate events being passed into the relayer. --- src/utils/EventUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 1c12e88371..ea239d4ad9 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -188,7 +188,7 @@ export class EventManager { */ hashEvent(event: Log): string { const { event: eventName, blockNumber, blockHash, transactionHash, transactionIndex, logIndex, args } = event; - const _args = Object.values(args).join("-"); + const _args = Object.values(args).sort().join("-"); const key = `${eventName}-${blockNumber}-${blockHash}-${transactionHash}-${transactionIndex}-${logIndex}-${_args}`; return ethersUtils.id(key); } From 60b6fe92c99a920a5d1d45a0a91029bb44d9d1b2 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Tue, 11 Feb 2025 21:09:40 +0000 Subject: [PATCH 2/3] Update ordering & unpack nested structs --- src/utils/EventUtils.ts | 13 ++++++++++++- test/EventManager.ts | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 786dcb4920..7a8b5b9e44 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -216,7 +216,18 @@ export class EventManager { */ hashEvent(event: Log): string { const { event: eventName, blockNumber, blockHash, transactionHash, transactionIndex, logIndex, args } = event; - const _args = Object.values(args).sort().join("-"); + const _args = Object.keys(args) + .sort() + .map((k) => { + const val = args[k]; + // When val is a nested object, flatten and stringify it. Assume no nested objects. + return typeof val === "object" + ? Object.keys(val).sort().map((k) => val[k]).join("-") + : val; + }) + .flat() + .join("-"); + const key = `${eventName}-${blockNumber}-${blockHash}-${transactionHash}-${transactionIndex}-${logIndex}-${_args}`; return ethersUtils.id(key); } diff --git a/test/EventManager.ts b/test/EventManager.ts index 4b2ae67e37..c0dca6bb96 100644 --- a/test/EventManager.ts +++ b/test/EventManager.ts @@ -116,7 +116,7 @@ describe("EventManager: Event Handling ", async function () { expect(events.length).to.equal(0); }); - it("Hashes events correctly", async function () { + it("Hashes events correctly: uniqueness", async function () { const log1 = eventTemplate; const hash1 = eventMgr.hashEvent(log1); expect(hash1).to.exist; @@ -130,6 +130,45 @@ describe("EventManager: Event Handling ", async function () { expect(hash3).to.equal(hash1); }); + it("Hashes events correctly: sorting", async function () { + const log = { + ...eventTemplate, + args: { + c: 3, + b: 2, + f: { + h: 7, + i: 8, + g: 6 + }, + a: 1, + d: 4, + e: 5, + }, + }; + const sortedLog = { + ...log, + args: { + a: 1, + b: 2, + c: 3, + d: 4, + e: 5, + f: { + g: 6, + h: 7, + i: 8 + }, + } + }; + + const hash1 = eventMgr.hashEvent(log); + expect(hash1).to.exist; + + const hash2 = eventMgr.hashEvent(sortedLog); + expect(hash2).to.equal(hash1); + }); + it("Does not submit duplicate events", async function () { expect(quorum).to.equal(2); From 6fb8ea58bffb9560c9d114085e196572f285ac8c Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Tue, 11 Feb 2025 21:39:10 +0000 Subject: [PATCH 3/3] wip --- src/utils/EventUtils.ts | 27 +++++++++++++++++---------- test/EventManager.ts | 6 +++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 7a8b5b9e44..0dcda3aab0 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -216,19 +216,26 @@ export class EventManager { */ hashEvent(event: Log): string { const { event: eventName, blockNumber, blockHash, transactionHash, transactionIndex, logIndex, args } = event; - const _args = Object.keys(args) + const _args = this.flattenObject(args); + const key = `${eventName}-${blockNumber}-${blockHash}-${transactionHash}-${transactionIndex}-${logIndex}-${_args}`; + return ethersUtils.id(key); + } + + /** + * Recurse through an object and sort its keys in order to produce an ordered string of values. + * @param obj Object to iterate through. + * @returns string A hyphenated string containing all arguments of the object, sorted by key. + */ + private flattenObject(obj: Record): string { + const args = Object.keys(obj) .sort() .map((k) => { - const val = args[k]; - // When val is a nested object, flatten and stringify it. Assume no nested objects. - return typeof val === "object" - ? Object.keys(val).sort().map((k) => val[k]).join("-") - : val; + const val = obj[k]; + // When val is a nested object, recursively flatten and stringify it. + return typeof val === "object" ? this.flattenObject(val as Record) : val; }) - .flat() - .join("-"); + .join("-"); - const key = `${eventName}-${blockNumber}-${blockHash}-${transactionHash}-${transactionIndex}-${logIndex}-${_args}`; - return ethersUtils.id(key); + return args; } } diff --git a/test/EventManager.ts b/test/EventManager.ts index c0dca6bb96..2e469bad71 100644 --- a/test/EventManager.ts +++ b/test/EventManager.ts @@ -139,7 +139,7 @@ describe("EventManager: Event Handling ", async function () { f: { h: 7, i: 8, - g: 6 + g: 6, }, a: 1, d: 4, @@ -157,9 +157,9 @@ describe("EventManager: Event Handling ", async function () { f: { g: 6, h: 7, - i: 8 + i: 8, }, - } + }, }; const hash1 = eventMgr.hashEvent(log);