Skip to content

Commit 461ba96

Browse files
authored
fix: grpc v2 subscriber bug with multiple sources on same oracle (#1944)
1 parent 1610795 commit 461ba96

File tree

3 files changed

+245
-251
lines changed

3 files changed

+245
-251
lines changed

sdk/scripts/client-test.ts

Lines changed: 123 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { DriftClient } from '../src/driftClient';
22
import { grpcDriftClientAccountSubscriberV2 } from '../src/accounts/grpcDriftClientAccountSubscriberV2';
3+
import { grpcDriftClientAccountSubscriber } from '../src/accounts/grpcDriftClientAccountSubscriber';
34
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
45
import { DriftClientConfig } from '../src/driftClientConfig';
56
import {
6-
decodeName,
77
DRIFT_PROGRAM_ID,
88
PerpMarketAccount,
9+
SpotMarketAccount,
910
Wallet,
11+
OracleInfo,
1012
} from '../src';
1113
import { CommitmentLevel } from '@triton-one/yellowstone-grpc';
1214
import dotenv from 'dotenv';
@@ -21,8 +23,8 @@ import driftIDL from '../src/idl/drift.json';
2123
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT;
2224
const TOKEN = process.env.TOKEN;
2325

24-
async function initializeGrpcDriftClientV2() {
25-
const connection = new Connection('https://api.mainnet-beta.solana.com');
26+
async function initializeGrpcDriftClientV2VersusV1() {
27+
const connection = new Connection('');
2628
const wallet = new Wallet(new Keypair());
2729
dotenv.config({ path: '../' });
2830

@@ -38,177 +40,136 @@ async function initializeGrpcDriftClientV2() {
3840

3941
const program = new Program(driftIDL as Idl, programId, provider);
4042

41-
const perpMarketProgramAccounts =
42-
(await program.account.perpMarket.all()) as ProgramAccount<PerpMarketAccount>[];
43-
const solPerpMarket = perpMarketProgramAccounts.find(
44-
(account) => account.account.marketIndex === 0
45-
);
46-
const solOracleInfo = {
47-
publicKey: solPerpMarket.account.amm.oracle,
48-
source: solPerpMarket.account.amm.oracleSource,
49-
};
50-
const ethPerpMarket = perpMarketProgramAccounts.find(
51-
(account) => account.account.marketIndex === 2
52-
);
53-
const ethOracleInfo = {
54-
publicKey: ethPerpMarket.account.amm.oracle,
55-
source: ethPerpMarket.account.amm.oracleSource,
56-
};
57-
const btcPerpMarket = perpMarketProgramAccounts.find(
58-
(account) => account.account.marketIndex === 1
59-
);
60-
const btcOracleInfo = {
61-
publicKey: btcPerpMarket.account.amm.oracle,
62-
source: btcPerpMarket.account.amm.oracleSource,
43+
const perpMarketIndexes = [4];
44+
const spotMarketIndexes = [32];
45+
46+
const perpMarketProgramAccounts = (
47+
await program.account.perpMarket.all()
48+
).filter((a) =>
49+
perpMarketIndexes.includes(a.account.marketIndex as number)
50+
) as ProgramAccount<PerpMarketAccount>[];
51+
const spotMarketProgramAccounts = (
52+
await program.account.spotMarket.all()
53+
).filter((a) =>
54+
spotMarketIndexes.includes(a.account.marketIndex as number)
55+
) as ProgramAccount<SpotMarketAccount>[];
56+
57+
// const perpMarketIndexes = perpMarketProgramAccounts.map(
58+
// (a) => a.account.marketIndex
59+
// );
60+
// const spotMarketIndexes = spotMarketProgramAccounts.map(
61+
// (a) => a.account.marketIndex
62+
// );
63+
// const oracleInfos = [
64+
// {
65+
// publicKey: new PublicKey('BERaNi6cpEresbq6HC1EQGaB1H1UjvEo4NGnmYSSJof4'),
66+
// source: OracleSource.PYTH_LAZER,
67+
// },
68+
// {
69+
// publicKey: new PublicKey('BERaNi6cpEresbq6HC1EQGaB1H1UjvEo4NGnmYSSJof4'),
70+
// source: OracleSource.PYTH_LAZER_1M,
71+
// },
72+
// ];
73+
74+
const seen = new Set<string>();
75+
const oracleInfos: OracleInfo[] = [];
76+
for (const acct of perpMarketProgramAccounts) {
77+
const key = `${acct.account.amm.oracle.toBase58()}-${Object.keys(
78+
acct.account.amm.oracleSource ?? {}
79+
)?.[0]}`;
80+
if (!seen.has(key)) {
81+
seen.add(key);
82+
oracleInfos.push({
83+
publicKey: acct.account.amm.oracle,
84+
source: acct.account.amm.oracleSource,
85+
});
86+
}
87+
}
88+
for (const acct of spotMarketProgramAccounts) {
89+
const key = `${acct.account.oracle.toBase58()}-${Object.keys(
90+
acct.account.oracleSource ?? {}
91+
)?.[0]}`;
92+
if (!seen.has(key)) {
93+
seen.add(key);
94+
oracleInfos.push({
95+
publicKey: acct.account.oracle,
96+
source: acct.account.oracleSource,
97+
});
98+
}
99+
}
100+
101+
const baseAccountSubscription = {
102+
type: 'grpc' as const,
103+
grpcConfigs: {
104+
endpoint: GRPC_ENDPOINT,
105+
token: TOKEN,
106+
commitmentLevel: 'confirmed' as unknown as CommitmentLevel,
107+
channelOptions: {
108+
'grpc.keepalive_time_ms': 10_000,
109+
'grpc.keepalive_timeout_ms': 1_000,
110+
'grpc.keepalive_permit_without_calls': 1,
111+
},
112+
},
63113
};
64114

65-
const config: DriftClientConfig = {
115+
const configV2: DriftClientConfig = {
66116
connection,
67117
wallet,
68118
programID: new PublicKey(DRIFT_PROGRAM_ID),
69119
accountSubscription: {
70-
type: 'grpc',
71-
grpcConfigs: {
72-
endpoint: GRPC_ENDPOINT,
73-
token: TOKEN,
74-
commitmentLevel: 'confirmed' as unknown as CommitmentLevel,
75-
channelOptions: {
76-
'grpc.keepalive_time_ms': 10_000,
77-
'grpc.keepalive_timeout_ms': 1_000,
78-
'grpc.keepalive_permit_without_calls': 1,
79-
},
80-
},
120+
...baseAccountSubscription,
81121
driftClientAccountSubscriber: grpcDriftClientAccountSubscriberV2,
82122
},
83-
perpMarketIndexes: [0, 1, 2],
84-
spotMarketIndexes: [0, 1, 2],
85-
oracleInfos: [solOracleInfo, ethOracleInfo, btcOracleInfo],
123+
perpMarketIndexes,
124+
spotMarketIndexes,
125+
oracleInfos,
86126
};
87127

88-
const driftClient = new DriftClient(config);
89-
90-
let perpMarketUpdateCount = 0;
91-
let spotMarketUpdateCount = 0;
92-
let oraclePriceUpdateCount = 0;
93-
let userAccountUpdateCount = 0;
94-
95-
const updatePromise = new Promise<void>((resolve) => {
96-
driftClient.accountSubscriber.eventEmitter.on(
97-
'perpMarketAccountUpdate',
98-
(data) => {
99-
console.log(
100-
'Perp market account update:',
101-
decodeName(data.name),
102-
'mmOracleSequenceId:',
103-
data.amm.mmOracleSequenceId.toString()
104-
);
105-
// const perpMarketData = driftClient.getPerpMarketAccount(
106-
// data.marketIndex
107-
// );
108-
// console.log(
109-
// 'Perp market data market index:',
110-
// perpMarketData?.marketIndex
111-
// );
112-
// const oracle = driftClient.getOracleDataForPerpMarket(data.marketIndex);
113-
// const mmOracle = driftClient.getMMOracleDataForPerpMarket(
114-
// data.marketIndex
115-
// );
116-
// console.log('Perp oracle price:', oracle.price.toString());
117-
// console.log('Perp MM oracle price:', mmOracle.price.toString());
118-
// console.log(
119-
// 'Perp MM oracle sequence id:',
120-
// perpMarketData?.amm?.mmOracleSequenceId?.toString()
121-
// );
122-
perpMarketUpdateCount++;
123-
if (
124-
perpMarketUpdateCount >= 10 &&
125-
spotMarketUpdateCount >= 10 &&
126-
oraclePriceUpdateCount >= 10 &&
127-
userAccountUpdateCount >= 2
128-
) {
129-
resolve();
130-
}
131-
}
132-
);
133-
134-
driftClient.accountSubscriber.eventEmitter.on(
135-
'spotMarketAccountUpdate',
136-
(data) => {
137-
console.log('Spot market account update:', decodeName(data.name));
138-
const spotMarketData = driftClient.getSpotMarketAccount(
139-
data.marketIndex
140-
);
141-
console.log(
142-
'Spot market data market index:',
143-
spotMarketData?.marketIndex
144-
);
145-
const oracle = driftClient.getOracleDataForSpotMarket(data.marketIndex);
146-
console.log('Spot oracle price:', oracle.price.toString());
147-
spotMarketUpdateCount++;
148-
if (
149-
perpMarketUpdateCount >= 10 &&
150-
spotMarketUpdateCount >= 10 &&
151-
oraclePriceUpdateCount >= 10 &&
152-
userAccountUpdateCount >= 2
153-
) {
154-
resolve();
155-
}
156-
}
157-
);
158-
159-
driftClient.accountSubscriber.eventEmitter.on(
160-
'oraclePriceUpdate',
161-
(data) => {
162-
console.log('Oracle price update:', data.toBase58());
163-
oraclePriceUpdateCount++;
164-
if (
165-
perpMarketUpdateCount >= 10 &&
166-
spotMarketUpdateCount >= 10 &&
167-
oraclePriceUpdateCount >= 10 &&
168-
userAccountUpdateCount >= 2
169-
) {
170-
resolve();
171-
}
172-
}
173-
);
174-
175-
driftClient.accountSubscriber.eventEmitter.on(
176-
'userAccountUpdate',
177-
(data) => {
178-
console.log('User account update:', decodeName(data.name));
179-
userAccountUpdateCount++;
180-
if (
181-
perpMarketUpdateCount >= 10 &&
182-
spotMarketUpdateCount >= 10 &&
183-
oraclePriceUpdateCount >= 10 &&
184-
userAccountUpdateCount >= 2
185-
) {
186-
resolve();
187-
}
188-
}
189-
);
190-
});
191-
192-
await driftClient.subscribe();
193-
console.log('DriftClient initialized and listening for updates.');
194-
195-
for (const marketIndex of config.perpMarketIndexes) {
196-
const oracle = driftClient.getOracleDataForPerpMarket(marketIndex);
197-
const mmOracle = driftClient.getMMOracleDataForPerpMarket(marketIndex);
198-
console.log('Initial perp oracle price:', oracle.price.toString());
199-
console.log('Initial perp MM oracle price:', mmOracle.price.toString());
200-
}
128+
const configV1: DriftClientConfig = {
129+
connection,
130+
wallet,
131+
programID: new PublicKey(DRIFT_PROGRAM_ID),
132+
accountSubscription: {
133+
...baseAccountSubscription,
134+
driftClientAccountSubscriber: grpcDriftClientAccountSubscriber,
135+
},
136+
perpMarketIndexes,
137+
spotMarketIndexes,
138+
oracleInfos,
139+
};
201140

202-
for (const marketIndex of config.spotMarketIndexes) {
203-
const oracle = driftClient.getOracleDataForSpotMarket(marketIndex);
204-
console.log('Initial spot oracle price:', oracle.price.toString());
205-
}
141+
const clientV2 = new DriftClient(configV2);
142+
const clientV1 = new DriftClient(configV1);
143+
144+
await Promise.all([clientV1.subscribe(), clientV2.subscribe()]);
145+
const compare = () => {
146+
for (const idx of perpMarketIndexes) {
147+
const p1 = clientV1.getOracleDataForPerpMarket(idx).price;
148+
const p2 = clientV2.getOracleDataForPerpMarket(idx).price;
149+
console.log(
150+
`perp mkt ${idx} | v1 ${p1.toString()} | v2 ${p2.toString()}`
151+
);
152+
}
153+
for (const idx of spotMarketIndexes) {
154+
const s1 = clientV1.getOracleDataForSpotMarket(idx).price;
155+
const s2 = clientV2.getOracleDataForSpotMarket(idx).price;
156+
console.log(
157+
`spot mkt ${idx} | v1 ${s1.toString()} | v2 ${s2.toString()}`
158+
);
159+
}
160+
};
161+
162+
compare();
163+
const interval = setInterval(compare, 1000);
206164

207-
const stateAccount = driftClient.getStateAccount();
208-
console.log('Initial state account:', stateAccount.toString());
165+
const cleanup = async () => {
166+
clearInterval(interval);
167+
await Promise.all([clientV1.unsubscribe(), clientV2.unsubscribe()]);
168+
process.exit(0);
169+
};
209170

210-
await updatePromise;
211-
console.log('Received required number of updates.');
171+
process.on('SIGINT', cleanup);
172+
process.on('SIGTERM', cleanup);
212173
}
213174

214-
initializeGrpcDriftClientV2().catch(console.error);
175+
initializeGrpcDriftClientV2VersusV1().catch(console.error);

0 commit comments

Comments
 (0)