From eea97c6df1646094891381053a873344a6c1a6dc Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 17:51:59 -0500 Subject: [PATCH 1/2] improve(BundleDataClient): Support pre-fills Support use case that will become frequent once deterministic relay hashes go live where a fill precedes a deposit in real world time. This opens up the possibility that even with a [SpokePoolClient.query lag](https://github.com/across-protocol/sdk/blob/40888c405db19f1860f0d3f22e26cc5dc541306f/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts#L28) applied on all chains, there is a very unlucky situation where a fill is sent at the end of one bundle block range and a deposit is one of the first events in the next bundle block range. Currently, the dataworker would never issue a refund for the fill. There are two ways around this I think and one is very complex to implement while the other is simple but increases relayer repayment latency and puts responsibility of how early to-prefill on the relayer: 1. BundleDataClient will need to "remember" which refunds it has already issued. It can do this easily using the arweave client though this increases dependencies on Arweave. Additionally, it will be quite complex to implement the most efficient data structure. I don't love this approach because of the tradeoffs of the following approach. 2. Increase the buffer that a fill must follow its destination chain HEAD block before the dataworker attempts to repay it. This additional buffer converted to seconds becomes the maximum amount of time that a fill can precede a deposit. I think this can be to something reasonable like 10 minutes assuming that pre-fills are only used by fillers who can guarantee somehow that a deposit lands on-chain when they want it to. Therefore the filler should be able to send a deposit after a fill in under 10 minutes in all cases. --- src/clients/BundleDataClient/BundleDataClient.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index fd992c95d..f49270a9a 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -869,6 +869,13 @@ export class BundleDataClient { // At this point, the v3RelayHashes entry already existed meaning that there is a matching deposit, // so this fill is validated. v3RelayHashes[relayDataHash].fill = fill; + // TODO: Add a buffer here to check for fills that preceded deposits. Need to somehow check that + // fill is not getting double-refunded. We might need to recompute previous bundle or load it + // from arweave to check on fill refund status... + // Another way to handle this is to only consider fills when they are X number of blocks behind HEAD, + // so you can add a buffer to all fill blocks here before considering whether they are in "this" bundle + // block range + if (fill.blockNumber >= destinationChainBlockRange[0]) { validatedBundleV3Fills.push({ ...fill, From a42b2a80c88bd1c595bd41a131fd4fcd86005910 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 9 Jan 2025 19:39:11 -0500 Subject: [PATCH 2/2] add minimal code --- .../BundleDataClient/BundleDataClient.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/clients/BundleDataClient/BundleDataClient.ts b/src/clients/BundleDataClient/BundleDataClient.ts index f49270a9a..34d8d89ba 100644 --- a/src/clients/BundleDataClient/BundleDataClient.ts +++ b/src/clients/BundleDataClient/BundleDataClient.ts @@ -849,13 +849,22 @@ export class BundleDataClient { // Keep track of fast fills that replaced slow fills, which we'll use to create "unexecutable" slow fills // if the slow fill request was sent in a prior bundle. const fastFillsReplacingSlowFills: string[] = []; + + // Returns block number when we should start evaluating this fill for a refund. The lag builds in space for + // a filler to send a fill before a deposit in cases where the depositor is delegating transaction submission + // to the filler and giving them a signed transaction object. + const getFillBlockWithLag = (fill: V3FillWithBlock): number => { + const fillBuffer = /* FILL_BUFFER[fill.destinationChainId] ?? */ 0; + return fill.blockNumber - fillBuffer; + }; + await forEachAsync( destinationClient .getFillsForOriginChain(originChainId) // We can remove fills for deposits with input amount equal to zero because these will result in 0 refunded // tokens to the filler. We can't remove non-empty message deposit here in case there is a slow fill // request for the deposit, we'd want to see the fill took place. - .filter((fill) => fill.blockNumber <= destinationChainBlockRange[1] && !isZeroValueDeposit(fill)), + .filter((fill) => getFillBlockWithLag(fill) <= destinationChainBlockRange[1] && !isZeroValueDeposit(fill)), async (fill) => { const relayDataHash = this.getRelayHashFromEvent(fill); fillCounter++; @@ -874,9 +883,8 @@ export class BundleDataClient { // from arweave to check on fill refund status... // Another way to handle this is to only consider fills when they are X number of blocks behind HEAD, // so you can add a buffer to all fill blocks here before considering whether they are in "this" bundle - // block range - - if (fill.blockNumber >= destinationChainBlockRange[0]) { + // block range: + if (getFillBlockWithLag(fill) >= destinationChainBlockRange[0]) { validatedBundleV3Fills.push({ ...fill, quoteTimestamp: v3RelayHashes[relayDataHash].deposit!.quoteTimestamp, // ! due to assert above @@ -913,7 +921,7 @@ export class BundleDataClient { // older deposit in case the spoke pool client's lookback isn't old enough to find the matching deposit. // We can skip this step if the fill's fill deadline is not infinite, because we can assume that the // spoke pool clients have loaded deposits old enough to cover all fills with a non-infinite fill deadline. - if (fill.blockNumber >= destinationChainBlockRange[0]) { + if (getFillBlockWithLag(fill) >= destinationChainBlockRange[0]) { // Fill has a non-infinite expiry, and we can assume our spoke pool clients have old enough deposits // to conclude that this fill is invalid if we haven't found a matching deposit in memory, so // skip the historical query. @@ -952,6 +960,8 @@ export class BundleDataClient { } ); + // TODO: Do we need to handle slow fill requests sent before fills? I assume no, as I can't think of + // a good reason to ever do this. await forEachAsync( destinationClient .getSlowFillRequestsForOriginChain(originChainId)