From a1be6f7ab1da68b3989d99ac98b1f1070c5ebaa4 Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Mon, 7 Oct 2024 21:42:00 +0000 Subject: [PATCH] Prevent false blockheight exceedence errors when transaction confirmations are aborted --- .changeset/forty-bananas-bow.md | 5 ++ .../confirmation-strategy-blockheight-test.ts | 51 +++++++++++++++++++ .../src/confirmation-strategy-blockheight.ts | 5 +- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 .changeset/forty-bananas-bow.md diff --git a/.changeset/forty-bananas-bow.md b/.changeset/forty-bananas-bow.md new file mode 100644 index 00000000000..8520295b321 --- /dev/null +++ b/.changeset/forty-bananas-bow.md @@ -0,0 +1,5 @@ +--- +'@solana/transaction-confirmation': patch +--- + +Fixed a bug that could result in the transaction confirmer claiming that the blockheight had been exceeded, when the fact of the matter was that the confirmation was aborted by an `AbortSignal` diff --git a/packages/transaction-confirmation/src/__tests__/confirmation-strategy-blockheight-test.ts b/packages/transaction-confirmation/src/__tests__/confirmation-strategy-blockheight-test.ts index c77241a2fcf..ed895b31da5 100644 --- a/packages/transaction-confirmation/src/__tests__/confirmation-strategy-blockheight-test.ts +++ b/packages/transaction-confirmation/src/__tests__/confirmation-strategy-blockheight-test.ts @@ -222,4 +222,55 @@ describe('createBlockHeightExceedencePromiseFactory', () => { }), ).rejects.toThrow('o no'); }); + it('throws if started aborted', async () => { + expect.assertions(1); + const abortController = new AbortController(); + abortController.abort(); + await expect( + getBlockHeightExceedencePromise({ + abortSignal: abortController.signal, + lastValidBlockHeight: 100n, + }), + ).rejects.toThrow(/operation was aborted/); + }); + it('throws if aborted while waiting for the epoch info', async () => { + expect.assertions(1); + const abortController = new AbortController(); + let resolveEpochInfo!: (value: { absoluteSlot: bigint; blockHeight: bigint }) => void; + const epochInfoPromise = new Promise(r => { + resolveEpochInfo = r; + }); + getEpochInfoRequestSender.mockReturnValue(epochInfoPromise); + const blockHeightExceedencePromise = getBlockHeightExceedencePromise({ + abortSignal: abortController.signal, + lastValidBlockHeight: 100n, + }); + await jest.runAllTimersAsync(); + abortController.abort(); + resolveEpochInfo({ absoluteSlot: 101n, blockHeight: 101n }); + await expect(blockHeightExceedencePromise).rejects.toThrow(/operation was aborted/); + }); + it('throws if aborted while the slot subscription is working', async () => { + expect.assertions(1); + const abortController = new AbortController(); + let resolveSlotSubscription!: (value: { slot: bigint }) => void; + const slotSubscriptionReturnPromise = new Promise(r => { + resolveSlotSubscription = r; + }); + getEpochInfoRequestSender.mockResolvedValue({ absoluteSlot: 100n, blockHeight: 100n }); + slotNotificationsGenerator.mockImplementation( + // eslint-disable-next-line require-yield + async function* () { + await slotSubscriptionReturnPromise; + }, + ); + const blockHeightExceedencePromise = getBlockHeightExceedencePromise({ + abortSignal: abortController.signal, + lastValidBlockHeight: 100n, + }); + await jest.runAllTimersAsync(); + abortController.abort(); + resolveSlotSubscription({ slot: 101n }); + await expect(blockHeightExceedencePromise).rejects.toThrow(/operation was aborted/); + }); }); diff --git a/packages/transaction-confirmation/src/confirmation-strategy-blockheight.ts b/packages/transaction-confirmation/src/confirmation-strategy-blockheight.ts index b68022d0b8e..f16801b2301 100644 --- a/packages/transaction-confirmation/src/confirmation-strategy-blockheight.ts +++ b/packages/transaction-confirmation/src/confirmation-strategy-blockheight.ts @@ -36,7 +36,8 @@ export function createBlockHeightExceedencePromiseFactory< abortSignal: callerAbortSignal, commitment, lastValidBlockHeight, - }) { + }): Promise { + callerAbortSignal.throwIfAborted(); const abortController = new AbortController(); const handleAbort = () => { abortController.abort(); @@ -57,6 +58,7 @@ export function createBlockHeightExceedencePromiseFactory< rpcSubscriptions.slotNotifications().subscribe({ abortSignal: abortController.signal }), getBlockHeightAndDifferenceBetweenSlotHeightAndBlockHeight(), ]); + callerAbortSignal.throwIfAborted(); let currentBlockHeight = initialBlockHeight; if (currentBlockHeight <= lastValidBlockHeight) { let lastKnownDifferenceBetweenSlotHeightAndBlockHeight = differenceBetweenSlotHeightAndBlockHeight; @@ -83,6 +85,7 @@ export function createBlockHeightExceedencePromiseFactory< } } } + callerAbortSignal.throwIfAborted(); throw new SolanaError(SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, { currentBlockHeight, lastValidBlockHeight,