From fb0ce25e2adf04953b448d2eaacbec2369c26803 Mon Sep 17 00:00:00 2001 From: steveluscher Date: Tue, 29 Nov 2022 01:51:48 +0000 Subject: [PATCH 1/3] Rename `subscriptionCommitment` to `confirmationCommitment` --- web3.js/src/connection.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index c89d03c98fa86d..4f991e47612600 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -3386,7 +3386,7 @@ export class Connection { assert(decodedSignature.length === 64, 'signature has invalid length'); - const subscriptionCommitment = commitment || this.commitment; + const confirmationCommitment = commitment || this.commitment; let timeoutId; let signatureSubscriptionId: number | undefined; let disposeSignatureSubscriptionStateChangeObserver: @@ -3410,7 +3410,7 @@ export class Connection { done = true; resolve({__type: TransactionStatus.PROCESSED, response}); }, - subscriptionCommitment, + confirmationCommitment, ); const subscriptionSetupPromise = new Promise( resolveSubscriptionSetup => { @@ -3463,7 +3463,7 @@ export class Connection { >(resolve => { if (typeof strategy === 'string') { let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000; - switch (subscriptionCommitment) { + switch (confirmationCommitment) { case 'processed': case 'recent': case 'single': From 1ba08dfd374f44f8fbecb70ff8a8d5fbcf3ca4de Mon Sep 17 00:00:00 2001 From: steveluscher Date: Tue, 29 Nov 2022 01:53:49 +0000 Subject: [PATCH 2/3] Reorganize status checking code to return early if `value` is `null` --- web3.js/src/connection.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index 4f991e47612600..c7b0ab22563a6d 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -3438,10 +3438,12 @@ export class Connection { return; } const {context, value} = response; + if (value == null) { + return; + } if (value?.err) { reject(value.err); - } - if (value) { + } else { done = true; resolve({ __type: TransactionStatus.PROCESSED, From e354ba97ed5989c3b11732a0debe84d543c982a3 Mon Sep 17 00:00:00 2001 From: steveluscher Date: Tue, 29 Nov 2022 02:45:58 +0000 Subject: [PATCH 3/3] Bail if the one-shot signature result does not meet the target commitment --- web3.js/src/connection.ts | 24 ++++++++++++++++++++++ web3.js/test/connection.test.ts | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/web3.js/src/connection.ts b/web3.js/src/connection.ts index c7b0ab22563a6d..d6457567d8770a 100644 --- a/web3.js/src/connection.ts +++ b/web3.js/src/connection.ts @@ -3444,6 +3444,30 @@ export class Connection { if (value?.err) { reject(value.err); } else { + switch (confirmationCommitment) { + case 'confirmed': + case 'single': + case 'singleGossip': { + if (value.confirmationStatus === 'processed') { + return; + } + break; + } + case 'finalized': + case 'max': + case 'root': { + if ( + value.confirmationStatus === 'processed' || + value.confirmationStatus === 'confirmed' + ) { + return; + } + break; + } + // exhaust enums to ensure full coverage + case 'processed': + case 'recent': + } done = true; resolve({ __type: TransactionStatus.PROCESSED, diff --git a/web3.js/test/connection.test.ts b/web3.js/test/connection.test.ts index e13260540ed397..dd333a778bcb56 100644 --- a/web3.js/test/connection.test.ts +++ b/web3.js/test/connection.test.ts @@ -1214,6 +1214,42 @@ describe('Connection', function () { // value: {err: null}, // }); // }); + + it('confirm transaction - does not confirm the transaction when signature status check yields confirmation for a lower commitment before signature subscription confirms the transaction', async () => { + const mockSignature = + 'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt'; + + // Keep the subscription from ever returning data. + await mockRpcMessage({ + method: 'signatureSubscribe', + params: [mockSignature, {commitment: 'finalized'}], + result: new Promise(() => {}), // Never resolve. + }); + clock.runAllAsync(); + + const confirmationPromise = + connection.confirmTransaction(mockSignature); + clock.runAllAsync(); + + // Return a signature status with a lower finality through the RPC API. + await mockRpcResponse({ + method: 'getSignatureStatuses', + params: [[mockSignature]], + value: [ + { + slot: 0, + confirmations: null, + confirmationStatus: 'processed', // Lower than we expect + err: null, + }, + ], + }); + clock.runAllAsync(); + + await expect(confirmationPromise).to.be.rejectedWith( + TransactionExpiredTimeoutError, + ); + }); }); }