Skip to content

feat(svmSpokeUtils): svm relayFillStatus implementation #984

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

Conversation

melisaguevara
Copy link
Contributor

@melisaguevara melisaguevara commented Apr 22, 2025

This PR introduces the SVM implementation for relayFillStatus, which reconstructs the fill status of a given relay based on historical events up to a specific block.

The steps to reconstruct fill status are:

  1. Retrieve the fill status PDA associated with the target relay.
  2. Query all relevant events (fills and slow fill requests) from that PDA, up to the specified block.
  3. Sort the events in ascending order by slot number.
  4. Determine the current fill status based on the most recent event:
    • If no events are found, the status is considered Unfilled.
    • Otherwise, the status reflects the type of the latest event.

NOTE: This is still a draft since the event fetching logic will change to use the new version of the svm events client from this pr: #982

Copy link

linear bot commented Apr 22, 2025

Comment on lines +152 to +171
// TODO: modify this to use svmEventsClient once we can instantiate it with dynamic addresses.
const fillPdaSignatures = await provider
.getSignaturesForAddress(fillStatusPda, {
limit: 1000,
commitment: "confirmed",
})
.send();

const eventsWithSlots = await Promise.all(
fillPdaSignatures.map(async (signatureTransaction) => {
const events = await svmEventsClient.readEventsFromSignature(signatureTransaction.signature);
return events.map((event) => ({
...event,
confirmationStatus: signatureTransaction.confirmationStatus,
blockTime: signatureTransaction.blockTime,
signature: signatureTransaction.signature,
slot: signatureTransaction.slot,
}));
})
);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I copied this from the SvmSpokeEventsClient but it is temporary and doesn't cover the case where a pda has +1000 events (which should be rare though). I aim to replace it once this PR goes in and we can use the events client for other addresses besides the spoke pool: #982

Copy link
Member

Choose a reason for hiding this comment

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

What is the likelihood we will be using 1000+ signatures of lookback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. We'd expect these pdas to have only a few events, I'll set a lower value.

@@ -175,7 +175,7 @@ export class SvmSpokeEventsClient {
* @param txResult - The transaction result.
* @returns A promise that resolves to an array of events with their data and name.
*/
private processEventFromTx(
public processEventFromTx(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These changes are also temporary and might not be needed with the new version of the svm events client.

Copy link
Member

Choose a reason for hiding this comment

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

+1

// TODO: modify this to use svmEventsClient once we can instantiate it with dynamic addresses.
const fillPdaSignatures = await provider
.getSignaturesForAddress(fillStatusPda, {
limit: 1000,
Copy link
Member

Choose a reason for hiding this comment

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

OOC should we allow the user to pass this value? It's possible for small ranges (like in the indexer) we want to pass a smaller value for faster read times

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can add it as an optional param and I'll also set the default to a lower value since we'd expect these pdas to have only a few events. Good call.

Comment on lines +152 to +171
// TODO: modify this to use svmEventsClient once we can instantiate it with dynamic addresses.
const fillPdaSignatures = await provider
.getSignaturesForAddress(fillStatusPda, {
limit: 1000,
commitment: "confirmed",
})
.send();

const eventsWithSlots = await Promise.all(
fillPdaSignatures.map(async (signatureTransaction) => {
const events = await svmEventsClient.readEventsFromSignature(signatureTransaction.signature);
return events.map((event) => ({
...event,
confirmationStatus: signatureTransaction.confirmationStatus,
blockTime: signatureTransaction.blockTime,
signature: signatureTransaction.signature,
slot: signatureTransaction.slot,
}));
})
);
Copy link
Member

Choose a reason for hiding this comment

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

What is the likelihood we will be using 1000+ signatures of lookback?

export async function relayFillStatus(
programId: Address,
relayData: RelayData,
blockTag: number | "latest",
Copy link
Member

Choose a reason for hiding this comment

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

Nit: is latest the correct term in our case? We'd probably be looking for the most recently "processed" or "confirmed" block

Comment on lines +204 to +206
// At this point we have only 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.
Copy link
Member

Choose a reason for hiding this comment

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

Do we have corresponding tests for this?

@@ -175,7 +175,7 @@ export class SvmSpokeEventsClient {
* @param txResult - The transaction result.
* @returns A promise that resolves to an array of events with their data and name.
*/
private processEventFromTx(
public processEventFromTx(
Copy link
Member

Choose a reason for hiding this comment

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

+1

Comment on lines +153 to +165
export async function getFillStatusPda(
programId: Address,
relayData: RelayData,
destinationChainId: number
): Promise<Address> {
const relayDataHash = getRelayDataHash(relayData, destinationChainId);
const uint8RelayDataHash = new Uint8Array(Buffer.from(relayDataHash.slice(2), "hex"));
const [fillStatusPda] = await getProgramDerivedAddress({
programAddress: programId,
seeds: ["fills", uint8RelayDataHash],
});
return fillStatusPda;
}
Copy link
Member

Choose a reason for hiding this comment

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

OOC is there a way we can use the function you created to derive the address? I.e. could this just be a wrapper over getStatePda with the specified address/extraSeed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

getStatePda and getFillStatusPda use different labels which is the first item in the 'seeds' array. We could have a function wrapping getProgramDerivedAddress that takes the seeds but I don't think there's much value on it.

@@ -71,6 +75,46 @@ export function getRelayHashFromEvent(e: RelayData & { destinationChainId: numbe
return getRelayDataHash(e, e.destinationChainId);
}

function _getRelayDataHashSvm(relayData: RelayData, destinationChainId: number): string {
const uint8ArrayFromHexString = (hex: string, littleEndian: boolean = false): Uint8Array => {
Copy link
Member

Choose a reason for hiding this comment

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

OOC - the endianness of the data feels very low level for TS. How often will endianness be a consideration and an we abstract this away from the caller?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to set the endianness to replicate the relay hash calculation doing in the Solana program itself but it is already abstracted from the caller. I mean, these helper functions are intended to be used exclusively within _getRelayDataHashSvm so the caller has to provide only the relayData.

@amateima amateima self-requested a review April 23, 2025 13:23
@melisaguevara
Copy link
Contributor Author

Closing this in favor of #990

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants