From 9850552c280e1a9f73ef1e503d46eede5fda80f3 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 10 Feb 2025 12:55:19 -0500 Subject: [PATCH] getLatestProposedBundleData should never load data from scratch --- .../BundleDataClient/BundleDataClient.ts | 75 +++++++++++-------- src/clients/SpokePoolClient.ts | 32 +++++++- 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index 5e821214..27f85c71 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -240,13 +240,7 @@ export class BundleDataClient { ); } - private async loadPersistedDataFromArweave( - blockRangesForChains: number[][] - ): Promise { - if (!isDefined(this.clients?.arweaveClient)) { - return undefined; - } - const start = performance.now(); + private async getBundleDataFromArweave(blockRangesForChains: number[][]) { const persistedData = await this.clients.arweaveClient.getByTopic( this.getArweaveBundleDataClientKey(blockRangesForChains), BundleDataSS @@ -256,6 +250,20 @@ export class BundleDataClient { if (!isDefined(persistedData) || persistedData.length < 1) { return undefined; } + return persistedData; + } + + private async loadPersistedDataFromArweave( + blockRangesForChains: number[][] + ): Promise { + if (!isDefined(this.clients?.arweaveClient)) { + return undefined; + } + const start = performance.now(); + const persistedData = await this.getBundleDataFromArweave(blockRangesForChains); + if (!isDefined(persistedData)) { + return undefined; + } // A converter function to account for the fact that our SuperStruct schema does not support numeric // keys in records. Fundamentally, this is a limitation of superstruct itself. @@ -435,24 +443,39 @@ export class BundleDataClient { const hubPoolClient = this.clients.hubPoolClient; // Determine which bundle we should fetch from arweave, either the pending bundle or the latest // executed one. Both should have arweave data but if for some reason the arweave data is missing, - // this function will have to compute the bundle data from scratch which will be slow. We have to fallback - // to computing the bundle from scratch since this function needs to return the full bundle data so that - // it can be used to get the running balance proposed using its data. - const bundleBlockRanges = getImpliedBundleBlockRanges( + // this function will load the bundle data from the most recent bundle data published to Arweave. + + let bundleBlockRanges = getImpliedBundleBlockRanges( hubPoolClient, this.clients.configStoreClient, hubPoolClient.hasPendingProposal() ? hubPoolClient.getLatestProposedRootBundle() - : hubPoolClient.getLatestFullyExecutedRootBundle(hubPoolClient.latestBlockSearched)! // ! because we know there is a bundle + : hubPoolClient.getNthFullyExecutedRootBundle(-1)! ); - return { - blockRanges: bundleBlockRanges, - bundleData: await this.loadData( - bundleBlockRanges, - this.spokePoolClients, - true // this bundle data should have been published to arweave - ), - }; + // Check if bundle data exists on arweave, otherwise fallback to last published bundle data. If the + // first bundle block range we are trying is the pending proposal, then we'll grab the most recently + // validated bundle, otherwise we'll grab the second most recently validated bundle. + let n = hubPoolClient.hasPendingProposal() ? 1 : 2; + + // eslint-disable-next-line no-constant-condition + while (true) { + const bundleDataOnArweave = await this.getBundleDataFromArweave(bundleBlockRanges); + if (!isDefined(bundleDataOnArweave)) { + // Bundle data is not arweave, try the next most recently validated bundle. + bundleBlockRanges = getImpliedBundleBlockRanges( + hubPoolClient, + this.clients.configStoreClient, + hubPoolClient.getNthFullyExecutedRootBundle(-n)! + ); + } else { + return { + blockRanges: bundleBlockRanges, + bundleData: await this.loadData(bundleBlockRanges, this.spokePoolClients, true), + }; + } + + n++; + } } async getLatestPoolRebalanceRoot(): Promise<{ root: PoolRebalanceRoot; blockRanges: number[][] }> { @@ -992,11 +1015,7 @@ export class BundleDataClient { } } } else { - this.logger.warn({ - at: "BundleDataClient#loadDataFromScratch", - message: "Detected duplicate fill", - fill, - }); + throw new Error("Duplicate fill detected"); } return; } @@ -1119,11 +1138,7 @@ export class BundleDataClient { validatedBundleSlowFills.push(matchedDeposit); } } else { - this.logger.warn({ - at: "BundleDataClient#loadDataFromScratch", - message: "Detected duplicate slow fill request", - slowFillRequest, - }); + throw new Error("Duplicate slow fill request detected."); } return; } diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 7741929f..d516a0f3 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -726,13 +726,27 @@ export class SpokePoolClient extends BaseAbstractClient { const slowFillRequest = { ...spreadEventWithBlockNumber(event), destinationChainId: this.chainId, - } as SlowFillRequestWithBlock; + } as SlowFillRequestWithBlock; if (eventName === "RequestedV3SlowFill") { slowFillRequest.messageHash = getMessageHash(slowFillRequest.message); } const depositHash = getRelayEventKey({ ...slowFillRequest, destinationChainId: this.chainId }); + + // Sanity check that this event is not a duplicate. + if ( + this.slowFillRequests[depositHash] !== undefined + ) { + this.logger.warn({ + at: "SpokePoolClient#update", + chainId: this.chainId, + message: "Duplicate slow fill request found", + slowFillRequest, + }); + continue; + } + this.slowFillRequests[depositHash] ??= slowFillRequest; } }; @@ -766,6 +780,22 @@ export class SpokePoolClient extends BaseAbstractClient { fill.relayExecutionInfo.updatedMessageHash = getMessageHash(event.args.relayExecutionInfo.updatedMessage); } + // Sanity check that this event is not a duplicate. + if ( + this.fills[fill.originChainId] !== undefined && + this.fills[fill.originChainId].some( + (f) => f.transactionHash === fill.transactionHash && f.logIndex === fill.logIndex + ) + ) { + this.logger.warn({ + at: "SpokePoolClient#update", + chainId: this.chainId, + message: "Duplicate fill found", + fill, + }); + continue; + } + assign(this.fills, [fill.originChainId], [fill]); assign(this.depositHashesToFills, [this.getDepositHash(fill)], [fill]); }