Skip to content

Commit

Permalink
fix(blockchain-link): fix Solana transaction confirmation
Browse files Browse the repository at this point in the history
Combine `signatureSubscribe` and `getSignatureStatus` when confirming transactions. This resolves the issue with Quicknode not sending a signature update event. Transaction will confirm if either a signature update event is received or if the periodic `getSignatureStatus` confirms the transaction as `finalized`.

(cherry picked from commit 38cd7cc)
  • Loading branch information
gabrielKerekes authored and komret committed Mar 27, 2024
1 parent a0fd548 commit 3a726b7
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 15 deletions.
24 changes: 9 additions & 15 deletions packages/blockchain-link/src/workers/solana/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ import type * as MessageTypes from '@trezor/blockchain-link-types/lib/messages';
import { CustomError } from '@trezor/blockchain-link-types/lib/constants/errors';
import { BaseWorker, ContextType, CONTEXT } from '../baseWorker';
import { MESSAGES, RESPONSES } from '@trezor/blockchain-link-types/src/constants';
import {
BlockheightBasedTransactionConfirmationStrategy,
Connection,
Message,
PublicKey,
} from '@solana/web3.js';
import { Connection, Message, PublicKey } from '@solana/web3.js';
import { solanaUtils } from '@trezor/blockchain-link-utils';

import {
Expand All @@ -28,6 +23,7 @@ import {
} from '@trezor/blockchain-link-utils/lib/solana';
import { TOKEN_ACCOUNT_LAYOUT } from './tokenUtils';
import { getBaseFee, getPriorityFee } from './fee';
import { confirmTransaction } from './transactionConfirmation';

export type SolanaAPI = Connection;

Expand Down Expand Up @@ -100,19 +96,17 @@ const isValidTransaction = (tx: ParsedTransactionWithMeta): tx is SolanaValidPar
const pushTransaction = async (request: Request<MessageTypes.PushTransaction>) => {
const rawTx = request.payload.startsWith('0x') ? request.payload.slice(2) : request.payload;
const api = await request.connect();
const payload = await api.sendRawTransaction(Buffer.from(rawTx, 'hex'));
const { blockhash, lastValidBlockHeight } = await api.getLatestBlockhash('finalized');
const confirmStrategy: BlockheightBasedTransactionConfirmationStrategy = {
blockhash,
lastValidBlockHeight,
signature: payload,
};

await api.confirmTransaction(confirmStrategy);
const { lastValidBlockHeight } = await api.getLatestBlockhash('finalized');

const txBuffer = Buffer.from(rawTx, 'hex');
const signature = await api.sendRawTransaction(txBuffer);

await confirmTransaction(api, signature, lastValidBlockHeight);

return {
type: RESPONSES.PUSH_TRANSACTION,
payload,
payload: signature,
} as const;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Connection } from '@solana/web3.js';

const COMMITMENT = 'finalized';

const tryConfirmBySignatureStatus = async (
connection: Connection,
signature: string,
lastValidBlockHeight: number,
abortSignal: AbortSignal,
) => {
const getCurrentBlockHeight = async () => {
try {
return await connection.getBlockHeight('finalized');
} catch (_) {
return -1;
}
};

let currentBlockHeight = await getCurrentBlockHeight();
while (currentBlockHeight <= lastValidBlockHeight) {
const signatureStatus = await connection.getSignatureStatus(signature);
if (
signatureStatus.value != null &&
signatureStatus.value.confirmationStatus === COMMITMENT
) {
return signature;
}

await new Promise(resolve => setTimeout(resolve, 5000));
if (abortSignal.aborted) {
return signature;
}
currentBlockHeight = await getCurrentBlockHeight();
}

throw new Error(
`TransactionExpiredBlockheightExceededError: Signature ${signature} has expired: block height exceeded.`,
);
};

const tryConfirmBySignatureSubscription = (connection: Connection, signature: string) => {
let subscriptionId: number | undefined;
const confirmationPromise = new Promise<string>((resolve, reject) => {
subscriptionId = connection.onSignature(
signature,
result => {
if (result.err != null) {
reject(result.err);
}
resolve(signature);
},
COMMITMENT,
);
});

return { subscriptionId, confirmationPromise };
};

export const confirmTransaction = async (
api: Connection,
signature: string,
lastValidBlockHeight: number,
) => {
const { subscriptionId, confirmationPromise: signatureSubscriptionConfirmationPromise } =
tryConfirmBySignatureSubscription(api, signature);

const abortController = new AbortController();
const signatureStatusConfirmationPromise = tryConfirmBySignatureStatus(
api,
signature,
lastValidBlockHeight,
abortController.signal,
);

await Promise.race([
signatureSubscriptionConfirmationPromise,
signatureStatusConfirmationPromise,
]);

abortController.abort();
if (subscriptionId != null) {
api.removeSignatureListener(subscriptionId);
}

return signature;
};

0 comments on commit 3a726b7

Please sign in to comment.