Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(relayer): Suppress duplicate event data from external listeners #2083

Merged
merged 13 commits into from
Feb 11, 2025
12 changes: 12 additions & 0 deletions src/utils/EventUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type QuorumEvent = Log & { providers: string[] };
export class EventManager {
public readonly chain: string;
public readonly events: { [blockNumber: number]: QuorumEvent[] } = {};
public readonly finalisedEvents: { [eventKey: string]: boolean } = {};
pxrl marked this conversation as resolved.
Show resolved Hide resolved

private blockNumber: number;

Expand Down Expand Up @@ -109,6 +110,12 @@ export class EventManager {
const events = (this.events[event.blockNumber] ??= []);
const storedEvent = this.findEvent(event);

const eventKey = this.hashEvent(event);
bmzig marked this conversation as resolved.
Show resolved Hide resolved
if (this.finalisedEvents[eventKey]) {
// Nothing to do - this event has already been handled.
return;
}

// Store or update the set of events for this block number.
if (!isDefined(storedEvent)) {
// Event hasn't been seen before, so store it.
Expand Down Expand Up @@ -173,6 +180,11 @@ export class EventManager {
}

quorumEvents.push(event);

// Protect against re-sending this event if it later arrives from another provider.
const eventKey = this.hashEvent(event);
pxrl marked this conversation as resolved.
Show resolved Hide resolved
this.finalisedEvents[eventKey] = true;

return false;
});
});
Expand Down
26 changes: 25 additions & 1 deletion test/EventManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createSpyLogger, expect, randomAddress } from "./utils";

describe("EventManager: Event Handling ", async function () {
const chainId = CHAIN_IDs.MAINNET;
const providers = ["infura", "alchemy", "llamanodes"];
const providers = ["infura", "alchemy", "llamanodes", "quicknode"];

const randomNumber = (ceil = 1_000_000) => Math.floor(Math.random() * ceil);
const makeHash = () => ethersUtils.id(randomNumber().toString());
Expand Down Expand Up @@ -121,4 +121,28 @@ describe("EventManager: Event Handling ", async function () {
const hash = eventMgr.hashEvent(log);
expect(hash).to.exist;
});

it("Does not submit duplicate events", async function () {
expect(quorum).to.equal(2);

const [provider1, provider2, provider3, provider4] = providers;

// Add the event once (not finalised).
eventMgr.add(eventTemplate, provider1);
let events = eventMgr.tick(eventTemplate.blockNumber + 1);
expect(events.length).to.equal(0);

// Add the same event from a different provider.
eventMgr.add(eventTemplate, provider2);
events = eventMgr.tick(eventTemplate.blockNumber + 1);
expect(events.length).to.equal(1);

// Re-add the same event again, from two new providers.
eventMgr.add(eventTemplate, provider3);
eventMgr.add(eventTemplate, provider4);

// Verify that the same event was not replayed.
events = eventMgr.tick(eventTemplate.blockNumber + 1);
expect(events.length).to.equal(0);
});
});