-
Notifications
You must be signed in to change notification settings - Fork 18
feat(SvmSpokePoolClient): relayFillStatus and fillStatusArray implementation #990
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
feat(SvmSpokePoolClient): relayFillStatus and fillStatusArray implementation #990
Conversation
src/arch/svm/SpokeUtils.ts
Outdated
if (blockTag === "confirmed") { | ||
toSlot = await provider.getSlot({ commitment: "confirmed" }).send(); | ||
} else { | ||
toSlot = BigInt(blockTag); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth validating toSlot >= fromSlot
here? (nb. this assumes we retain fromSlot
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can get rid of fromSlot
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@melisaguevara This looks great!
src/arch/svm/SpokeUtils.ts
Outdated
const relevantEvents = ( | ||
await Promise.all( | ||
eventsToQuery.map((eventName) => | ||
pdaEventsClient.queryDerivedAddressEvents(eventName, BigInt(fromSlot), toSlot, { limit: 50 }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooc, is there an increased cost by setting a low fromSlot
here? Having to be explicit about fromSlot
seems like a burden on the caller; in most (all?) cases I think the caller probably only cares about the upper bound (i.e. was this completed before some previous point in time, or alternatively, what's the current status?).
(If we do need to set reasonable bounds on fromSlot
then I wonder if we could infer a safe lower bound based on the relayData.fillDeadline
property)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the events client's perspective, if we don't set fromSlot
, it will fetch all events for the address. I think that should be fine for PDAs, since we expect them to have only a few events. The only case where they'd have many is if they were targeted with spam. So it seems safe to remove it, do you agree?
I've optimistically removed it here: 988c565. For now I'm only setting it as undefined when calling svmEventsClient.queryDerivedAddressEvents
, but I think we can improve readability if we pass the search range as an object rather than individual params to the events client functions. I can follow up with that change after we merge the update function to avoid conflicts.
src/arch/svm/SpokeUtils.ts
Outdated
): Promise<(FillStatus | undefined)[]> { | ||
throw new Error("fillStatusArray: not implemented"); | ||
assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain"); | ||
const chunkSize = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this intended to be 2 or 200? If I read the commit message properly then I think it hints at 200. 2 seems quite low, though we seem to lack any multicall-like read capabilities here, so going for high-parallelism adds the risk of rate-limiting as well :(
As background, the relayer can hit this function with an array of hundreds of relayData objects. If we're querying 2 at a time it's going to take quite a while to resolve that. I also wonder if we might see some nasty overheads given that relayFillStatus()
instantiates a new events client for each individual query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this was intended to be 200. I was locally testing with 2 😅
But I agree that both rate-limiting and instantiating a new events client for each query might be problematic. For the latter, I'm thinking we can use a single client instance if the pda is not a property of the class but only a param for the function that queries events. And for the former, maybe we can start with 100 parallel requests and see how that goes? Not sure there are many options for this without a multicall-like solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0015fb4 This would use a single events client instance.
I'm doing some testing regarding the chunk size to have an idea of how long will it take. Will post some numbers in a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pxrl The performance of this is really poor. It takes up to 20 seconds to fetch 100 deposits. So I’m looking into using the fill status PDA data when possible. There’s a method that allows querying 100 accounts at once. When checking the latest slot (which is what the relayer does), we can use it to filter out deposits that have either been filled or have a slow fill requested and still have an open PDA.
For deposits that are unfilled or filled but have a closed PDA, I'm afraid we’ll still have to reconstruct from events 😞. I'm still thinking if there's any faster approach for this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
182cde9 Based on our discussion, now we are also relying on the fill deadline to filter unfilled deposits easily and reduce the amount of statuses we have to resolve with events.
src/arch/svm/SpokeUtils.ts
Outdated
programId: Address, | ||
relayData: RelayData, | ||
fromSlot: number, | ||
blockTag: number | "confirmed" = "confirmed", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the delta between processed
and confirmed
is reliably 0 - 2 slots then I wonder if we can just revert to a raw slot number for the upper bound (i.e. toSlot?: number
), where toSlot
would be a caller-specified slot, or otherwise resolved dynamically to the latest confirmed
slot. wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I covered this below.
public abstract relayFillStatus(relayData: RelayData, blockTag?: number | "latest"): Promise<FillStatus>; | ||
public abstract relayFillStatus( | ||
relayData: RelayData, | ||
blockTag?: number | "latest" | "confirmed" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exposing confirmed
at this interface means that the caller needs to be aware of which chain type they're on, and we then need to validate in each chain-specific implementation that the correct tag was passed in. If we modify the type to be just an optional number then we could infer "chain-specific latest" if the input is undefined. That seems more platform-agnostic, though it means that we internally must decide on processed
or confirmed
for SVM. Based on @james-a-morris feedback about the delta being only 1 - 2 slots, it seems like we could probably live with confirmed
for now. wdyt?
blockTag?: number | "latest" | "confirmed" | |
blockTag?: number |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea! Fwiw, we aren't relying too much on this option, the only place where I see we are using "latest" explicitly is in this line of the Dataworker. For svm it seems safe to use confirmed
. So I think this makes sense, wdyt @james-a-morris?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how it would look like d3cfa15
I wonder if we should change the name of this param too, blockTag might be confusing when used from the SVM Spoke Pool. Wdyt? It could be "atHeight" or something similar.
bd17e8d
to
7dad9b1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work!
src/arch/svm/SpokeUtils.ts
Outdated
console.warn( | ||
"fillStatusArray: Querying specific slots for large arrays is slow. " + | ||
"For current status, omit 'atHeight' param to use latest confirmed slot instead." | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftover log?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it was intentional, since reconstructing status from events is really slow I thought it would be useful to have this just in case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be worth having this use a logger intstance - not sure if console goes to gcp in a nicely formatted way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, good call! a5b03ce
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one comment on the logger instance being used over the console
src/arch/svm/SpokeUtils.ts
Outdated
console.warn( | ||
"fillStatusArray: Querying specific slots for large arrays is slow. " + | ||
"For current status, omit 'atHeight' param to use latest confirmed slot instead." | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be worth having this use a logger intstance - not sure if console goes to gcp in a nicely formatted way
This PR introduces the SVM implementation of
relayFillStatus
, which resolves a relay's fill status using PDA data or, if necessary, PDA events.The resolution process is:
This PR also introduces
fillStatusArray
, a batched version of the same logic for resolving multiple deposits. Note that resolving status via events for large arrays is slow. If it becomes a bottleneck, we could introduce a newUnknown
fill status to skip event reconstruction entirely. This should be acceptable, as event-based resolution is only needed for deposits whose fill deadline has already passed or for queries at a specific slot.Some execution times observed during local testing:
Note: These timings were measured without caching which could reduce them further.