-
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
base: epic/svm-client
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,13 @@ | ||
import assert from "assert"; | ||
import { Rpc, SolanaRpcApi, Address } from "@solana/kit"; | ||
import { SvmSpokeIdl } from "@across-protocol/contracts"; | ||
import { fetchState } from "@across-protocol/contracts/dist/src/svm/clients/SvmSpoke"; | ||
|
||
import { SvmCpiEventsClient } from "./eventsClient"; | ||
import { Deposit, FillStatus, FillWithBlock, RelayData } from "../../interfaces"; | ||
import { BigNumber, isUnsafeDepositId } from "../../utils"; | ||
import { fetchState } from "@across-protocol/contracts/dist/src/svm/clients/SvmSpoke"; | ||
import { BigNumber, chainIsSvm, chunk, isUnsafeDepositId } from "../../utils"; | ||
import { getFillStatusPda } from "./utils"; | ||
import { SVMEventNames } from "./types"; | ||
|
||
type Provider = Rpc<SolanaRpcApi>; | ||
|
||
|
@@ -125,27 +130,86 @@ export function findDepositBlock( | |
} | ||
|
||
/** | ||
* Find the amount filled for a deposit at a particular block. | ||
* Find the fill status for a deposit at a particular block. | ||
* @param spokePool SpokePool contract instance. | ||
* @param relayData Deposit information that is used to complete a fill. | ||
* @param blockTag Block tag (numeric or "latest") to query at. | ||
* @returns The amount filled for the specified deposit at the requested block (or latest). | ||
* @param fromSlot Slot to start the search at. | ||
* @param blockTag Slot (numeric or "confirmed") to query at. | ||
* @returns The fill status for the specified deposit at the requested slot (or at the current confirmed slot). | ||
*/ | ||
export function relayFillStatus( | ||
_spokePool: unknown, | ||
_relayData: RelayData, | ||
_blockTag?: number | "latest", | ||
_destinationChainId?: number | ||
export async function relayFillStatus( | ||
programId: Address, | ||
relayData: RelayData, | ||
fromSlot: number, | ||
blockTag: number | "confirmed" = "confirmed", | ||
destinationChainId: number, | ||
provider: Provider | ||
): Promise<FillStatus> { | ||
throw new Error("relayFillStatus: not implemented"); | ||
assert(chainIsSvm(destinationChainId), "Destination chain must be an SVM chain"); | ||
|
||
// Get fill status PDA using relayData | ||
const fillStatusPda = await getFillStatusPda(programId, relayData, destinationChainId); | ||
|
||
// Set search range | ||
let toSlot: bigint; | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth validating |
||
} | ||
|
||
// Get fill and requested slow fill events from fillStatusPda | ||
const pdaEventsClient = await SvmCpiEventsClient.createFor(provider, programId, SvmSpokeIdl, fillStatusPda); | ||
const eventsToQuery = [SVMEventNames.FilledRelay, SVMEventNames.RequestedSlowFill]; | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. ooc, is there an increased cost by setting a low (If we do need to set reasonable bounds on |
||
) | ||
) | ||
).flat(); | ||
|
||
if (relevantEvents.length === 0) { | ||
// No fill or requested slow fill events found for this fill status PDA | ||
return FillStatus.Unfilled; | ||
} | ||
|
||
// Sort events in ascending order of slot number | ||
relevantEvents.sort((a, b) => Number(a.slot - b.slot)); | ||
|
||
// At this point we have an ordered array of fill and requested slow fill events and since it's not possible to | ||
// submit a slow fill request once a fill has been submitted, we can use the last event in the sorted list to | ||
// determine the fill status at the requested block. | ||
const fillStatusEvent = relevantEvents.pop(); | ||
switch (fillStatusEvent!.name) { | ||
case SVMEventNames.FilledRelay: | ||
return FillStatus.Filled; | ||
case SVMEventNames.RequestedSlowFill: | ||
return FillStatus.RequestedSlowFill; | ||
default: | ||
throw new Error(`Unexpected event name: ${fillStatusEvent!.name}`); | ||
} | ||
} | ||
|
||
export function fillStatusArray( | ||
_spokePool: unknown, | ||
_relayData: RelayData[], | ||
_blockTag = "processed" | ||
export async function fillStatusArray( | ||
programId: Address, | ||
relayData: RelayData[], | ||
fromSlot: number, | ||
blockTag: number | "confirmed" = "confirmed", | ||
destinationChainId: number, | ||
provider: Provider | ||
): 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 commentThe 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 |
||
const chunkedRelayData = chunk(relayData, chunkSize); | ||
const results = []; | ||
for (const chunk of chunkedRelayData) { | ||
const chunkResults = await Promise.all( | ||
chunk.map((relayData) => relayFillStatus(programId, relayData, fromSlot, blockTag, destinationChainId, provider)) | ||
); | ||
results.push(...chunkResults); | ||
} | ||
return results.flat(); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -813,7 +813,10 @@ export abstract class SpokePoolClient extends BaseAbstractClient { | |||||
* @param blockTag The block at which to query the fill status. | ||||||
* @returns The fill status for the given relay data. | ||||||
*/ | ||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Exposing
Suggested change
|
||||||
): Promise<FillStatus>; | ||||||
|
||||||
/** | ||||||
* Retrieves the fill status for an array of given relay data. | ||||||
|
@@ -823,6 +826,6 @@ export abstract class SpokePoolClient extends BaseAbstractClient { | |||||
*/ | ||||||
public abstract fillStatusArray( | ||||||
relayData: RelayData[], | ||||||
blockTag?: number | "latest" | ||||||
blockTag?: number | "latest" | "confirmed" | ||||||
): Promise<(FillStatus | undefined)[]>; | ||||||
} |
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
andconfirmed
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
), wheretoSlot
would be a caller-specified slot, or otherwise resolved dynamically to the latestconfirmed
slot. wdyt?