Skip to content

Commit cb7702c

Browse files
committed
Default commitment to confirmed when not explicitly specified
1 parent 2962a3f commit cb7702c

File tree

6 files changed

+435
-1
lines changed

6 files changed

+435
-1
lines changed

packages/library/src/rpc-default-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createSolanaRpcApi } from '@solana/rpc-core';
33
import { SolanaJsonRpcIntegerOverflowError } from './rpc-integer-overflow-error';
44

55
export const DEFAULT_RPC_CONFIG: Partial<Parameters<typeof createSolanaRpcApi>[0]> = {
6+
defaultCommitment: 'confirmed',
67
onIntegerOverflow(methodName, keyPath, value) {
78
throw new SolanaJsonRpcIntegerOverflowError(methodName, keyPath, value);
89
},
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { Commitment } from '@solana/rpc-types';
2+
3+
import { applyDefaultCommitment } from '../default-commitment';
4+
5+
const MOCK_COMMITMENT_PROPERTY_NAME = 'commitmentProperty';
6+
7+
describe('applyDefaultCommitment', () => {
8+
describe.each([0, 1, 2])('in relation to a method whose commitment config is argument #%s', expectedPosition => {
9+
it('adds the default commitment when absent from the call', () => {
10+
expect.assertions(1);
11+
expect(
12+
applyDefaultCommitment({
13+
commitmentPropertyName: MOCK_COMMITMENT_PROPERTY_NAME,
14+
optionsObjectPositionInParams: expectedPosition,
15+
overrideCommitment: 'processed',
16+
params: [],
17+
}),
18+
).toEqual([
19+
...new Array(expectedPosition).map(() => expect.anything()),
20+
{ [MOCK_COMMITMENT_PROPERTY_NAME]: 'processed' },
21+
]);
22+
});
23+
describe.each(['confirmed', 'finalized', 'processed'] as Commitment[])(
24+
'when the default commitment is set to `%s`',
25+
defaultCommitment => {
26+
describe.each(['confirmed', 'processed'])(
27+
'and the params already specify a commitment of `%s`',
28+
existingCommitment => {
29+
it('does not overwrite it', () => {
30+
const params = [
31+
...new Array(expectedPosition),
32+
{ [MOCK_COMMITMENT_PROPERTY_NAME]: existingCommitment },
33+
];
34+
expect(
35+
applyDefaultCommitment({
36+
commitmentPropertyName: MOCK_COMMITMENT_PROPERTY_NAME,
37+
optionsObjectPositionInParams: expectedPosition,
38+
overrideCommitment: defaultCommitment,
39+
params,
40+
}),
41+
).toBe(params);
42+
});
43+
},
44+
);
45+
describe.each(['finalized', undefined])(
46+
'and the params already specify a commitment of `%s`',
47+
existingCommitment => {
48+
it('removes the commitment property when there are other properties in the config object', () => {
49+
expect.assertions(1);
50+
const params = [
51+
...new Array(expectedPosition),
52+
{ [MOCK_COMMITMENT_PROPERTY_NAME]: existingCommitment, other: 'property' },
53+
];
54+
expect(
55+
applyDefaultCommitment({
56+
commitmentPropertyName: MOCK_COMMITMENT_PROPERTY_NAME,
57+
optionsObjectPositionInParams: expectedPosition,
58+
overrideCommitment: defaultCommitment,
59+
params,
60+
}),
61+
).toStrictEqual([
62+
...new Array(expectedPosition).map(() => expect.anything()),
63+
{ other: 'property' },
64+
]);
65+
});
66+
it('sets the config object to `undefined` when there are no other properties left and the config object is not the last param', () => {
67+
expect.assertions(1);
68+
const params = [
69+
...new Array(expectedPosition),
70+
{ [MOCK_COMMITMENT_PROPERTY_NAME]: existingCommitment },
71+
'someParam',
72+
];
73+
expect(
74+
applyDefaultCommitment({
75+
commitmentPropertyName: MOCK_COMMITMENT_PROPERTY_NAME,
76+
optionsObjectPositionInParams: expectedPosition,
77+
overrideCommitment: defaultCommitment,
78+
params,
79+
}),
80+
).toStrictEqual([
81+
...new Array(expectedPosition).map(() => expect.anything()),
82+
undefined,
83+
'someParam',
84+
]);
85+
});
86+
it('truncates the params when there are no other properties left and the config object is the last param', () => {
87+
expect.assertions(1);
88+
const params = [
89+
...new Array(expectedPosition),
90+
{ [MOCK_COMMITMENT_PROPERTY_NAME]: existingCommitment },
91+
];
92+
expect(
93+
applyDefaultCommitment({
94+
commitmentPropertyName: MOCK_COMMITMENT_PROPERTY_NAME,
95+
optionsObjectPositionInParams: expectedPosition,
96+
overrideCommitment: defaultCommitment,
97+
params,
98+
}),
99+
).toStrictEqual([...new Array(expectedPosition).map(() => expect.anything())]);
100+
});
101+
},
102+
);
103+
},
104+
);
105+
it.each([null, 1, '1', 1n, [1, 2, 3]])(
106+
"does not overwrite the existing param when it's a non-object like `%s`",
107+
paramInConfigPosition => {
108+
expect.assertions(1);
109+
const params = [...new Array(expectedPosition), paramInConfigPosition];
110+
expect(
111+
applyDefaultCommitment({
112+
commitmentPropertyName: MOCK_COMMITMENT_PROPERTY_NAME,
113+
optionsObjectPositionInParams: expectedPosition,
114+
overrideCommitment: 'processed',
115+
params,
116+
}),
117+
).toBe(params);
118+
},
119+
);
120+
});
121+
});

packages/rpc-core/src/__tests__/params-patcher-test.ts

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import { Commitment } from '@solana/rpc-types';
2+
13
import { getParamsPatcherForSolanaLabsRpc } from '../params-patcher';
4+
import { OPTIONS_OBJECT_POSITION_BY_METHOD } from '../params-patcher-options-object-position-config';
25

36
describe('getParamsPatcherForSolanaLabsRpc', () => {
47
describe('given no config', () => {
@@ -29,6 +32,186 @@ describe('getParamsPatcherForSolanaLabsRpc', () => {
2932
});
3033
});
3134
});
35+
describe('with respect to the default commitment', () => {
36+
const METHODS_SUBJECT_TO_COMMITMENT_DEFAULTING = [
37+
'accountNotifications',
38+
'blockNotifications',
39+
'getAccountInfo',
40+
'getBalance',
41+
'getBlock',
42+
'getBlockHeight',
43+
'getBlockProduction',
44+
'getBlocks',
45+
'getBlocksWithLimit',
46+
'getConfirmedBlock',
47+
'getConfirmedBlocks',
48+
'getConfirmedBlocksWithLimit',
49+
'getConfirmedSignaturesForAddress2',
50+
'getConfirmedTransaction',
51+
'getEpochInfo',
52+
'getFeeCalculatorForBlockhash',
53+
'getFeeForMessage',
54+
'getFees',
55+
'getInflationGovernor',
56+
'getInflationReward',
57+
'getLargestAccounts',
58+
'getLatestBlockhash',
59+
'getLeaderSchedule',
60+
'getMinimumBalanceForRentExemption',
61+
'getMultipleAccounts',
62+
'getProgramAccounts',
63+
'getRecentBlockhash',
64+
'getSignaturesForAddress',
65+
'getSlot',
66+
'getSlotLeader',
67+
'getStakeActivation',
68+
'getStakeMinimumDelegation',
69+
'getSupply',
70+
'getTokenAccountBalance',
71+
'getTokenAccountsByDelegate',
72+
'getTokenAccountsByOwner',
73+
'getTokenLargestAccounts',
74+
'getTokenSupply',
75+
'getTransaction',
76+
'getTransactionCount',
77+
'getVoteAccounts',
78+
'isBlockhashValid',
79+
'logsNotifications',
80+
'programNotifications',
81+
'requestAirdrop',
82+
'signatureNotifications',
83+
'simulateTransaction',
84+
];
85+
describe.each(['processed', 'confirmed'] as Commitment[])(
86+
'with the default commitment set to `%s`',
87+
defaultCommitment => {
88+
let patcher: ReturnType<typeof getParamsPatcherForSolanaLabsRpc>;
89+
beforeEach(() => {
90+
patcher = getParamsPatcherForSolanaLabsRpc({ defaultCommitment });
91+
});
92+
it.each(METHODS_SUBJECT_TO_COMMITMENT_DEFAULTING)(
93+
'adds a default commitment on calls for `%s`',
94+
methodName => {
95+
expect(patcher([], methodName)).toContainEqual({ commitment: defaultCommitment });
96+
},
97+
);
98+
it('adds a default preflight commitment on calls to `sendTransaction`', () => {
99+
expect(patcher([], 'sendTransaction')).toContainEqual({ preflightCommitment: defaultCommitment });
100+
});
101+
},
102+
);
103+
describe('with the default commitment set to `finalized`', () => {
104+
let patcher: ReturnType<typeof getParamsPatcherForSolanaLabsRpc>;
105+
beforeEach(() => {
106+
patcher = getParamsPatcherForSolanaLabsRpc({ defaultCommitment: 'finalized' });
107+
});
108+
it.each(METHODS_SUBJECT_TO_COMMITMENT_DEFAULTING)('adds no commitment on calls for `%s`', methodName => {
109+
expect(patcher([], methodName)).not.toContainEqual(
110+
expect.objectContaining({ commitment: expect.anything() }),
111+
);
112+
});
113+
it('adds no preflight commitment on calls to `sendTransaction`', () => {
114+
expect(patcher([], 'sendTransaction')).not.toContainEqual(
115+
expect.objectContaining({ preflightCommitment: expect.anything() }),
116+
);
117+
});
118+
});
119+
describe('with no default commitment set', () => {
120+
let patcher: ReturnType<typeof getParamsPatcherForSolanaLabsRpc>;
121+
beforeEach(() => {
122+
patcher = getParamsPatcherForSolanaLabsRpc();
123+
});
124+
it.each(METHODS_SUBJECT_TO_COMMITMENT_DEFAULTING)('sets no commitment on calls to `%s`', methodName => {
125+
expect(patcher([], methodName)).not.toContainEqual(
126+
expect.objectContaining({ commitment: expect.anything() }),
127+
);
128+
});
129+
it('adds no preflight commitment on calls to `sendTransaction`', () => {
130+
expect(patcher([], 'sendTransaction')).not.toContainEqual(
131+
expect.objectContaining({ preflightCommitment: expect.anything() }),
132+
);
133+
});
134+
});
135+
describe.each(['finalized', undefined])(
136+
'when the params already specify a commitment of `%s`',
137+
existingCommitment => {
138+
describe.each(METHODS_SUBJECT_TO_COMMITMENT_DEFAULTING)('on calls to `%s`', methodName => {
139+
const optionsObjectPosition = OPTIONS_OBJECT_POSITION_BY_METHOD[methodName]!;
140+
it('removes the commitment property on calls to `%s` when there are other properties in the config object', () => {
141+
expect.assertions(1);
142+
const params = [
143+
...new Array(optionsObjectPosition),
144+
{ commitment: existingCommitment, other: 'property' },
145+
];
146+
const patcher = getParamsPatcherForSolanaLabsRpc();
147+
expect(patcher(params, methodName)).toStrictEqual([
148+
...new Array(optionsObjectPosition).map(() => expect.anything()),
149+
{ other: 'property' },
150+
]);
151+
});
152+
it('deletes the commitment on calls to `%s` when there are no other properties left and the config object is not the last param', () => {
153+
expect.assertions(1);
154+
const params = [
155+
...new Array(optionsObjectPosition),
156+
{ commitment: existingCommitment },
157+
'someParam',
158+
];
159+
const patcher = getParamsPatcherForSolanaLabsRpc();
160+
expect(patcher(params, methodName)).toStrictEqual([
161+
...new Array(optionsObjectPosition).map(() => expect.anything()),
162+
undefined,
163+
'someParam',
164+
]);
165+
});
166+
it('truncates the params on calls to `%s` when there are no other properties left and the config object is the last param', () => {
167+
expect.assertions(1);
168+
const params = [...new Array(optionsObjectPosition), { commitment: existingCommitment }];
169+
const patcher = getParamsPatcherForSolanaLabsRpc();
170+
expect(patcher(params, methodName)).toStrictEqual([
171+
...new Array(optionsObjectPosition).map(() => expect.anything()),
172+
]);
173+
});
174+
});
175+
it('removes the preflight commitment property on calls to `%s` when there are other properties in the config object', () => {
176+
expect.assertions(1);
177+
const optionsObjectPosition = OPTIONS_OBJECT_POSITION_BY_METHOD['sendTransaction']!;
178+
const params = [
179+
...new Array(optionsObjectPosition),
180+
{ other: 'property', preflightCommitment: existingCommitment },
181+
];
182+
const patcher = getParamsPatcherForSolanaLabsRpc();
183+
expect(patcher(params, 'sendTransaction')).toStrictEqual([
184+
...new Array(optionsObjectPosition).map(() => expect.anything()),
185+
{ other: 'property' },
186+
]);
187+
});
188+
it('deletes the preflight commitment on calls to `%s` when there are no other properties left and the config object is not the last param', () => {
189+
expect.assertions(1);
190+
const optionsObjectPosition = OPTIONS_OBJECT_POSITION_BY_METHOD['sendTransaction']!;
191+
const params = [
192+
...new Array(optionsObjectPosition),
193+
{ preflightCommitment: existingCommitment },
194+
'someParam',
195+
];
196+
const patcher = getParamsPatcherForSolanaLabsRpc();
197+
expect(patcher(params, 'sendTransaction')).toStrictEqual([
198+
...new Array(optionsObjectPosition).map(() => expect.anything()),
199+
undefined,
200+
'someParam',
201+
]);
202+
});
203+
it('truncates the params on calls to `%s` when there are no other properties left and the config object is the last param', () => {
204+
expect.assertions(1);
205+
const optionsObjectPosition = OPTIONS_OBJECT_POSITION_BY_METHOD['sendTransaction']!;
206+
const params = [...new Array(optionsObjectPosition), { preflightCommitment: existingCommitment }];
207+
const patcher = getParamsPatcherForSolanaLabsRpc();
208+
expect(patcher(params, 'sendTransaction')).toStrictEqual([
209+
...new Array(optionsObjectPosition).map(() => expect.anything()),
210+
]);
211+
});
212+
},
213+
);
214+
});
32215
describe('given an integer overflow handler', () => {
33216
let onIntegerOverflow: jest.Mock;
34217
let paramsTransformer: (value: unknown) => unknown;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Commitment } from '@solana/rpc-types';
2+
3+
type Config = Readonly<{
4+
params: unknown[];
5+
commitmentPropertyName: string;
6+
optionsObjectPositionInParams: number;
7+
overrideCommitment?: Commitment;
8+
}>;
9+
10+
export function applyDefaultCommitment({
11+
commitmentPropertyName,
12+
params,
13+
optionsObjectPositionInParams,
14+
overrideCommitment,
15+
}: Config) {
16+
const paramInTargetPosition = params[optionsObjectPositionInParams];
17+
if (
18+
// There's no config.
19+
paramInTargetPosition === undefined ||
20+
// There is a config object.
21+
(paramInTargetPosition && typeof paramInTargetPosition === 'object' && !Array.isArray(paramInTargetPosition))
22+
) {
23+
if (
24+
// The config object already has a commitment set.
25+
paramInTargetPosition &&
26+
commitmentPropertyName in paramInTargetPosition
27+
) {
28+
if (
29+
!paramInTargetPosition[commitmentPropertyName as keyof typeof paramInTargetPosition] ||
30+
paramInTargetPosition[commitmentPropertyName as keyof typeof paramInTargetPosition] === 'finalized'
31+
) {
32+
// Delete the commitment property; `finalized` is already the server default.
33+
const nextParams = [...params];
34+
const {
35+
[commitmentPropertyName as keyof typeof paramInTargetPosition]: _, // eslint-disable-line @typescript-eslint/no-unused-vars
36+
...rest
37+
} = paramInTargetPosition;
38+
if (Object.keys(rest).length > 0) {
39+
nextParams[optionsObjectPositionInParams] = rest;
40+
} else {
41+
if (optionsObjectPositionInParams === nextParams.length - 1) {
42+
nextParams.length--;
43+
} else {
44+
nextParams[optionsObjectPositionInParams] = undefined;
45+
}
46+
}
47+
return nextParams;
48+
}
49+
} else if (overrideCommitment !== 'finalized') {
50+
// Apply the default commitment.
51+
const nextParams = [...params];
52+
nextParams[optionsObjectPositionInParams] = {
53+
...paramInTargetPosition,
54+
[commitmentPropertyName]: overrideCommitment,
55+
};
56+
return nextParams;
57+
}
58+
}
59+
return params;
60+
}

0 commit comments

Comments
 (0)