Skip to content

Commit

Permalink
ft_watcher: check for multiple ixs in tx and inline deconstructor
Browse files Browse the repository at this point in the history
Signed-off-by: bingyuyap <[email protected]>
  • Loading branch information
bingyuyap committed Aug 26, 2024
1 parent af5e302 commit 445fe3e
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 49 deletions.
2 changes: 1 addition & 1 deletion database/fast-transfer-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ CREATE TABLE fast_transfer_executions (
execution_slot BIGINT,
execution_time TIMESTAMP,
-- fill_id can be a vaa id (cctp) or solana account pubkey (local)
fill_id VARCHAR(255),
fill_id VARCHAR(255)
);

-- Settlement is created when the settlement is created in the `settleFastTransfer`
Expand Down
83 changes: 42 additions & 41 deletions watcher/src/fastTransfer/swapLayer/solParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,56 +51,60 @@ export class SwapLayerParser {
*/
private async processTransaction(
transaction: VersionedTransactionResponse
): Promise<TransferCompletion | null> {
): Promise<TransferCompletion[]> {
const sig = transaction.transaction.signatures[0];
const programInstructions = this.getProgramInstructions(transaction);

for (const { ix } of programInstructions) {
const decoded = this.swapLayerBorshCoder.instruction.decode(Buffer.from(ix.data));
if (!decoded) continue;

try {
switch (decoded.name) {
case 'complete_swap_direct':
case 'complete_swap_relay':
case 'complete_swap_payload':
return await this.parseSwapInstruction(transaction, ix, decoded.name);

case 'complete_transfer_direct':
case 'complete_transfer_relay':
case 'complete_transfer_payload':
return await this.parseTransferInstruction(transaction, ix, decoded.name);

case 'release_inbound':
return await this.parseReleaseInbound(transaction, ix, decoded.name);

default:
// we will not log when there are unknown instructions to prevent log congestion
continue;
const results = await Promise.all(
programInstructions.map(async ({ ix }) => {
const decoded = this.swapLayerBorshCoder.instruction.decode(Buffer.from(ix.data));
if (!decoded) return null;

try {
switch (decoded.name) {
case 'complete_swap_direct':
case 'complete_swap_relay':
case 'complete_swap_payload':
return await this.parseSwapInstruction(transaction, ix, decoded.name);

case 'complete_transfer_direct':
case 'complete_transfer_relay':
case 'complete_transfer_payload':
return await this.parseTransferInstruction(transaction, ix, decoded.name);

case 'release_inbound':
return await this.parseReleaseInbound(transaction, ix, decoded.name);

default:
// Skip unknown instructions
// we will not log when there are unknown instructions to prevent log congestion
return null;
}
} catch (error) {
console.error(`Error processing ${decoded.name} in transaction ${sig}:`, error);
// Continue to the next instruction if there's an error
return null;
}
} catch (error) {
console.error(`Error processing ${decoded.name} in transaction ${sig}:`, error);
// Continue to the next instruction if there's an error
continue;
}
}
})
);

return null;
// Filter out any null results
return results.filter((result): result is TransferCompletion => result !== null);
}

/**
* Fetches and processes a single transaction by its signature. This is only used for testing for now.
*
* @param signature - The signature of the transaction to fetch and process.
*
* @returns A `TransferCompletion` object containing parsed details from the transaction,
* or `null` if no relevant instructions were found.
* @returns An array of `TransferCompletion` objects containing parsed details from the transaction.
* If no relevant instructions were found, an empty array is returned.
*/
async parseTransaction(signature: string): Promise<TransferCompletion | null> {
async parseTransaction(signature: string): Promise<TransferCompletion[]> {
const transaction = await this.connection.getTransaction(signature, {
maxSupportedTransactionVersion: 0,
});
if (!transaction) return null;
if (!transaction) return [];

return this.processTransaction(transaction);
}
Expand All @@ -123,15 +127,14 @@ export class SwapLayerParser {
(tx): tx is VersionedTransactionResponse => tx !== null
);

// Process each transaction and filter out null results
// Process each transaction and gather the results
const promises = nonNullTransactions.map(async (tx) => await this.processTransaction(tx));

const results = await Promise.all(promises);

// Filter out null results from the processed transactions
return results.filter((res): res is TransferCompletion => res !== null);
// Flatten the array and filter out any null values
return results.flat().filter((res): res is TransferCompletion => res !== null);
}

// === parsing logic ===

/**
Expand Down Expand Up @@ -223,9 +226,7 @@ export class SwapLayerParser {
throw new Error(`Transaction block time not found: ${sig}`);
}

const instructionConfig = this.getInstructionConfig(instructionName);

const { fillAccountIndex, recipientIndex } = instructionConfig;
const { fillAccountIndex, recipientIndex } = this.getInstructionConfig(instructionName);

if (ix.accountKeyIndexes.length <= recipientIndex) {
throw new Error(`${INSUFFICIENT_ACCOUNTS} for ${instructionName} in ${sig}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ describe('SwapLayerParser', () => {
const txHash =
'32goGrEsPb6Kky65Z4wX6wswzjDbT9pBWs1HSZFsfWhxoA1fnSsoE9hJgtepPL8VyKQJUdRrfGWPrXCizDufArwR';
const result = await parser.parseTransaction(txHash);
expect(result.length).toBe(1);
const expected = {
fill_id: 'BkWHY4H2kEVevdUeiRmFYNtg5zURRbTEtjt29KWdbjzV',
output_token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
Expand All @@ -168,7 +169,7 @@ describe('SwapLayerParser', () => {
relaying_fee: '0',
};

const serializedRes = seralizedRes(result);
const serializedRes = seralizedRes(result[0]);
expect(serializedRes).toEqual(expected);
});

Expand All @@ -177,6 +178,7 @@ describe('SwapLayerParser', () => {
'4EWH6ZetTTjdYSbxqXddKNKLKDpBctELAhqChmkey2jwunZaj1Digj1fQxBMxtw6uhDeqkX3ev2vucu7jrexhWka';

const result = await parser.parseTransaction(txHash);
expect(result.length).toBe(1);

const expected = {
recipient: 'FQ4PBuykgHqemPhqqktJL9y1L7oTbShYiwGkwgM1VceF',
Expand All @@ -190,7 +192,7 @@ describe('SwapLayerParser', () => {
staged_inbound: undefined,
};

const serializedRes = seralizedRes(result);
const serializedRes = seralizedRes(result[0]);
expect(serializedRes).toEqual(expected);
});

Expand All @@ -199,6 +201,7 @@ describe('SwapLayerParser', () => {
'3Ufce773W4xgVsZiGBhSRPQssfaNdrEWeTBPLTnQSFZHsVx9ADaSN9yQBF6kcQMyDAoAnM3BVU88tQ2TbDZn1kUJ';

const result = await parser.parseTransaction(txHash);
expect(result.length).toBe(1);
const expected = {
recipient: 'GcppBeM1UYGU4b7aX9uPAqL4ZEUThNHt5FpxPtzBE1xx',
tx_hash:
Expand All @@ -210,7 +213,7 @@ describe('SwapLayerParser', () => {
output_amount: '49564106',
staged_inbound: undefined,
};
const serializedRes = seralizedRes(result);
const serializedRes = seralizedRes(result[0]);
expect(serializedRes).toEqual(expected);
});

Expand All @@ -219,6 +222,7 @@ describe('SwapLayerParser', () => {
'39K8aHVDmyAjne6J4PBFkvmKZH9CQR9QpbmTFafeiTLxeWg5n5RgcRdX5AYhebLR9shiUHrDeqg4YSD1EhRZNpS1';

const result = await parser.parseTransaction(txHash);
expect(result.length).toBe(1);
const expected = {
fill_id: 'Hru6CBfyXtG18zF33DnXEjmECjgj1eMjNfPRaESBqpUr',
output_token: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
Expand All @@ -230,7 +234,7 @@ describe('SwapLayerParser', () => {
'39K8aHVDmyAjne6J4PBFkvmKZH9CQR9QpbmTFafeiTLxeWg5n5RgcRdX5AYhebLR9shiUHrDeqg4YSD1EhRZNpS1',
relaying_fee: '0',
};
const serializedRes = seralizedRes(result);
const serializedRes = seralizedRes(result[0]);
expect(serializedRes).toEqual(expected);
});

Expand All @@ -239,6 +243,7 @@ describe('SwapLayerParser', () => {
'eo2CugBsJ9Efbtg9TAiYyBvvZZsbh93ZZcLDxxjbmbEpZojCF8BDphVVrCjXtMkSLaP2EGQE5zSrjU4r6fxsxRP';

const result = await parser.parseTransaction(txHash);
expect(result.length).toBe(1);
const expected = {
recipient: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
tx_hash:
Expand All @@ -250,7 +255,7 @@ describe('SwapLayerParser', () => {
output_amount: '0',
staged_inbound: 'ECiEWJndTfUJaEQ59gYgy6e4331mkrh1USQCmDcBwBvj',
};
const serializedRes = seralizedRes(result);
const serializedRes = seralizedRes(result[0]);
expect(serializedRes).toEqual(expected);
});

Expand All @@ -259,6 +264,7 @@ describe('SwapLayerParser', () => {
'4yCcw8MJ1BokhPJM2fQC3BMfoezteM4MkaHLfjPrLG25AEW4EeNxcNsrgU3ECkwQ1sy3AKFseafxM2mfjdwbzo8x';

const result = await parser.parseTransaction(txHash);
expect(result.length).toBe(1);
const expected = {
fill_id: 'ESccxJbedTgsu7kwK6uNWnMrg3GiD7pgexXfWeyZNK3J',
output_token: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
Expand All @@ -270,7 +276,7 @@ describe('SwapLayerParser', () => {
'4yCcw8MJ1BokhPJM2fQC3BMfoezteM4MkaHLfjPrLG25AEW4EeNxcNsrgU3ECkwQ1sy3AKFseafxM2mfjdwbzo8x',
relaying_fee: '0',
};
const serializedRes = seralizedRes(result);
const serializedRes = seralizedRes(result[0]);
expect(serializedRes).toEqual(expected);
});

Expand All @@ -279,6 +285,7 @@ describe('SwapLayerParser', () => {
'2EFLPdYpdJzeoe4HD4fNRWwphhy9HyEHFj3EQtY9agUPmQ5LjJkXFjEt5dnshS9sSTby9nN2QF9BaCbVyiBFGLxj';

const result = await parser.parseTransaction(txHash);
expect(result.length).toBe(1);
const expected = {
tx_hash:
'2EFLPdYpdJzeoe4HD4fNRWwphhy9HyEHFj3EQtY9agUPmQ5LjJkXFjEt5dnshS9sSTby9nN2QF9BaCbVyiBFGLxj',
Expand All @@ -290,7 +297,7 @@ describe('SwapLayerParser', () => {
output_amount: '6900000000',
staged_inbound: 'GFJ6699xu2BER8t98S4Vy6ZQam4mvr539AaqvHHBh9i3',
};
const serializedRes = seralizedRes(result);
const serializedRes = seralizedRes(result[0]);
expect(serializedRes).toEqual(expected);
});
});

0 comments on commit 445fe3e

Please sign in to comment.