fix: verify commitment level when confirming transactions with one-shot fetch (#28969)
* Rename `subscriptionCommitment` to `confirmationCommitment` * Reorganize status checking code to return early if `value` is `null` * Bail if the one-shot signature result does not meet the target commitment
This commit is contained in:
parent
3b2014ddc1
commit
04789cab81
|
@ -3386,7 +3386,7 @@ export class Connection {
|
||||||
|
|
||||||
assert(decodedSignature.length === 64, 'signature has invalid length');
|
assert(decodedSignature.length === 64, 'signature has invalid length');
|
||||||
|
|
||||||
const subscriptionCommitment = commitment || this.commitment;
|
const confirmationCommitment = commitment || this.commitment;
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
let signatureSubscriptionId: number | undefined;
|
let signatureSubscriptionId: number | undefined;
|
||||||
let disposeSignatureSubscriptionStateChangeObserver:
|
let disposeSignatureSubscriptionStateChangeObserver:
|
||||||
|
@ -3410,7 +3410,7 @@ export class Connection {
|
||||||
done = true;
|
done = true;
|
||||||
resolve({__type: TransactionStatus.PROCESSED, response});
|
resolve({__type: TransactionStatus.PROCESSED, response});
|
||||||
},
|
},
|
||||||
subscriptionCommitment,
|
confirmationCommitment,
|
||||||
);
|
);
|
||||||
const subscriptionSetupPromise = new Promise<void>(
|
const subscriptionSetupPromise = new Promise<void>(
|
||||||
resolveSubscriptionSetup => {
|
resolveSubscriptionSetup => {
|
||||||
|
@ -3438,10 +3438,36 @@ export class Connection {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {context, value} = response;
|
const {context, value} = response;
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (value?.err) {
|
if (value?.err) {
|
||||||
reject(value.err);
|
reject(value.err);
|
||||||
}
|
} else {
|
||||||
if (value) {
|
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;
|
done = true;
|
||||||
resolve({
|
resolve({
|
||||||
__type: TransactionStatus.PROCESSED,
|
__type: TransactionStatus.PROCESSED,
|
||||||
|
@ -3463,7 +3489,7 @@ export class Connection {
|
||||||
>(resolve => {
|
>(resolve => {
|
||||||
if (typeof strategy === 'string') {
|
if (typeof strategy === 'string') {
|
||||||
let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
|
let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000;
|
||||||
switch (subscriptionCommitment) {
|
switch (confirmationCommitment) {
|
||||||
case 'processed':
|
case 'processed':
|
||||||
case 'recent':
|
case 'recent':
|
||||||
case 'single':
|
case 'single':
|
||||||
|
|
|
@ -1214,6 +1214,42 @@ describe('Connection', function () {
|
||||||
// value: {err: null},
|
// 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue