Skip to content
32 changes: 11 additions & 21 deletions src/clients/SpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,30 +202,20 @@ export function SpokeListener<T extends Constructor<MinGenericSpokePoolClient>>(
const at = "SpokePoolClient#removeEvent";
const eventIdx = this._queryableEventNames().indexOf(event.event);
const pendingEvents = this.#pendingEvents[eventIdx];
const { event: eventName, blockNumber, blockHash, transactionHash, transactionIndex, logIndex } = event;
const { event: eventName, blockNumber, blockHash, transactionHash } = event;

// First check for removal from any pending events.
const pendingEventIdx = pendingEvents.findIndex(
(pending) =>
pending.logIndex === logIndex &&
pending.transactionIndex === transactionIndex &&
pending.transactionHash === transactionHash &&
pending.blockHash === blockHash
);

let removed = false;
if (pendingEventIdx !== -1) {
removed = true;

// Drop the relevant event.
pendingEvents.splice(pendingEventIdx, 1);

this.logger.debug({
at: "SpokePoolClient#removeEvent",
message: `Removed 1 pre-ingested ${this.#chain} ${eventName} event for block ${blockNumber}.`,
event,
});
}
let removedEventIdx: number;
do {
removedEventIdx = pendingEvents.findIndex(({ blockHash }) => blockHash === event.blockHash);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this require the event which was reorged to be reincluded in this block? What if the event was removed by a reorg and not mined again yet?

Copy link
Collaborator Author

@pxrl pxrl Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah; these dropped events are currently reported per-event over the event subscription. Per current implementation we require one notification per event, per blockHash. The change implemented here is to permit a single removed event notification to drop all stored events for a given blockHash, so we basically cast the net a little wider, which is beneficial in case one of the removed event notifications doesn't materialise (for whatever reason).

There's a follow-on change required which will actually track block hashes and reconcile when a re-org happens by finding a common ancestor and invalidating everything inbetween. That's still pending.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Makes sense.

if (removedEventIdx !== -1) {
removed = true;
pendingEvents.splice(removedEventIdx, 1); // Drop the relevant event.
const message = `Removed pre-ingested ${this.#chain} ${eventName} event for block ${blockNumber}.`;
this.logger.debug({ at, message, event });
}
} while (removedEventIdx !== -1);

// Back out any events that were previously ingested via update(). This is best-effort and may help to save the
// relayer from filling a deposit where it must wait for additional deposit confirmations. Note that this is
Expand Down
22 changes: 22 additions & 0 deletions test/Relayer.IndexedSpokePoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,28 @@ describe("IndexedSpokePoolClient: Update", async function () {
expect(droppedDeposit).to.not.exist;
});

it("Correctly removes all pending events for a given blockHash", async function () {
const rawEvents: Log[] = [];
for (let i = 0; i < 25; ++i) {
rawEvents.push(getDepositEvent(blockNumber++));
}

// Modify all events to share the same blockHash.
const events = rawEvents.slice(1).map((event) => ({ ...event, blockHash: rawEvents[0].blockHash }));
sortEventsAscendingInPlace(events);

postEvents(blockNumber, currentTime, events);

const [droppedEvent] = events;
removeEvent(droppedEvent);

await spokePoolClient.update();

// All events should have been dropped before SpokePoolClient update.
const deposits = spokePoolClient.getDeposits();
expect(deposits.length).to.equal(0);
});

it("Correctly removes pending events that are dropped after update", async function () {
const events: Log[] = [];
for (let i = 0; i < 25; ++i) {
Expand Down