Skip to content

Commit a45758b

Browse files
authored
Multi User GRPC (#1970)
* feat: multi user grpc w/ debug script * fix: user data not set multi user grpc on fetch * fix: promise in constructor grpc multi user acct sub * fix: lint and formatting
1 parent 5e1fedf commit a45758b

File tree

8 files changed

+473
-19
lines changed

8 files changed

+473
-19
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { grpcUserAccountSubscriber } from '../src/accounts/grpcUserAccountSubscriber';
2+
import { grpcMultiUserAccountSubscriber } from '../src/accounts/grpcMultiUserAccountSubscriber';
3+
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
4+
import { DRIFT_PROGRAM_ID } from '../src';
5+
import { CommitmentLevel } from '@triton-one/yellowstone-grpc';
6+
import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor';
7+
import driftIDL from '../src/idl/drift.json';
8+
import assert from 'assert';
9+
import { Wallet } from '../src';
10+
11+
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT;
12+
const TOKEN = process.env.TOKEN;
13+
const RPC_ENDPOINT = process.env.RPC_ENDPOINT;
14+
15+
const USER_ACCOUNT_PUBKEYS = [
16+
// Add user account public keys here, e.g.:
17+
// new PublicKey('...')
18+
];
19+
20+
async function testGrpcUserAccountSubscriberV1VsV2() {
21+
console.log('🚀 Initializing User Account Subscriber V1 vs V2 Test...');
22+
23+
if (USER_ACCOUNT_PUBKEYS.length === 0) {
24+
console.error('❌ No user account public keys provided. Please add some to USER_ACCOUNT_PUBKEYS array.');
25+
process.exit(1);
26+
}
27+
28+
const connection = new Connection(RPC_ENDPOINT);
29+
const wallet = new Wallet(new Keypair());
30+
31+
const programId = new PublicKey(DRIFT_PROGRAM_ID);
32+
const provider = new AnchorProvider(
33+
connection,
34+
// @ts-ignore
35+
wallet,
36+
{
37+
commitment: 'processed',
38+
}
39+
);
40+
41+
const program = new Program(driftIDL as Idl, programId, provider);
42+
43+
const grpcConfigs = {
44+
endpoint: GRPC_ENDPOINT,
45+
token: TOKEN,
46+
commitmentLevel: CommitmentLevel.PROCESSED,
47+
channelOptions: {
48+
'grpc.keepalive_time_ms': 10_000,
49+
'grpc.keepalive_timeout_ms': 1_000,
50+
'grpc.keepalive_permit_without_calls': 1,
51+
},
52+
};
53+
54+
console.log(`📊 Testing ${USER_ACCOUNT_PUBKEYS.length} user accounts...`);
55+
56+
// V1: Create individual subscribers for each user account
57+
const v1Subscribers = USER_ACCOUNT_PUBKEYS.map(
58+
(pubkey) =>
59+
new grpcUserAccountSubscriber(
60+
grpcConfigs,
61+
program,
62+
pubkey,
63+
{ logResubMessages: true }
64+
)
65+
);
66+
67+
// V2: Create a single multi-subscriber and get per-user interfaces
68+
const v2MultiSubscriber = new grpcMultiUserAccountSubscriber(
69+
program,
70+
grpcConfigs,
71+
{ logResubMessages: true }
72+
);
73+
const v2Subscribers = USER_ACCOUNT_PUBKEYS.map((pubkey) =>
74+
v2MultiSubscriber.forUser(pubkey)
75+
);
76+
77+
// Subscribe all V1 subscribers
78+
console.log('🔗 Subscribing V1 subscribers...');
79+
await Promise.all(v1Subscribers.map((sub) => sub.subscribe()));
80+
console.log('✅ V1 subscribers ready');
81+
82+
// Subscribe all V2 subscribers
83+
console.log('🔗 Subscribing V2 subscribers...');
84+
await v2MultiSubscriber.subscribe();
85+
console.log('✅ V2 subscribers ready');
86+
87+
const compare = () => {
88+
try {
89+
let passedTests = 0;
90+
let totalTests = 0;
91+
92+
// Test each user account
93+
for (let i = 0; i < USER_ACCOUNT_PUBKEYS.length; i++) {
94+
const pubkey = USER_ACCOUNT_PUBKEYS[i];
95+
const v1Sub = v1Subscribers[i];
96+
const v2Sub = v2Subscribers[i];
97+
98+
totalTests++;
99+
100+
// 1. Test isSubscribed
101+
assert.strictEqual(
102+
v1Sub.isSubscribed,
103+
v2Sub.isSubscribed,
104+
`User ${pubkey.toBase58()}: isSubscribed should match`
105+
);
106+
107+
// 2. Test getUserAccountAndSlot
108+
const v1Data = v1Sub.getUserAccountAndSlot();
109+
const v2Data = v2Sub.getUserAccountAndSlot();
110+
111+
// Compare the user account data
112+
assert.deepStrictEqual(
113+
v1Data.data,
114+
v2Data.data,
115+
`User ${pubkey.toBase58()}: account data should match`
116+
);
117+
118+
// Slots might differ slightly due to timing, but let's check if they're close
119+
const slotDiff = Math.abs(v1Data.slot - v2Data.slot);
120+
if (slotDiff > 10) {
121+
console.warn(
122+
`⚠️ User ${pubkey.toBase58()}: slot difference is ${slotDiff} (v1: ${v1Data.slot}, v2: ${v2Data.slot})`
123+
);
124+
}
125+
126+
passedTests++;
127+
}
128+
129+
console.log(`✅ All comparisons passed (${passedTests}/${totalTests} user accounts)`);
130+
} catch (error) {
131+
console.error('❌ Comparison failed:', error);
132+
}
133+
};
134+
135+
// Run initial comparison
136+
compare();
137+
138+
// Run comparison every second to verify live updates
139+
const interval = setInterval(compare, 1000);
140+
141+
const cleanup = async () => {
142+
clearInterval(interval);
143+
console.log('🧹 Cleaning up...');
144+
await Promise.all([
145+
...v1Subscribers.map((sub) => sub.unsubscribe()),
146+
...v2Subscribers.map((sub) => sub.unsubscribe()),
147+
]);
148+
console.log('✅ Cleanup complete');
149+
process.exit(0);
150+
};
151+
152+
process.on('SIGINT', cleanup);
153+
process.on('SIGTERM', cleanup);
154+
}
155+
156+
testGrpcUserAccountSubscriberV1VsV2().catch(console.error);

sdk/scripts/single-grpc-client-test.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ProgramAccount,
2020
} from '@coral-xyz/anchor';
2121
import driftIDL from '../src/idl/drift.json';
22+
import { grpcMultiUserAccountSubscriber } from '../src/accounts/grpcMultiUserAccountSubscriber';
2223

2324
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT;
2425
const TOKEN = process.env.TOKEN;
@@ -96,18 +97,27 @@ async function initializeSingleGrpcClient() {
9697
console.log(`📊 Markets: ${perpMarketIndexes.length} perp, ${spotMarketIndexes.length} spot`);
9798
console.log(`🔮 Oracles: ${oracleInfos.length}`);
9899

100+
101+
const grpcConfigs = {
102+
endpoint: GRPC_ENDPOINT,
103+
token: TOKEN,
104+
commitmentLevel: CommitmentLevel.PROCESSED,
105+
channelOptions: {
106+
'grpc.keepalive_time_ms': 10_000,
107+
'grpc.keepalive_timeout_ms': 1_000,
108+
'grpc.keepalive_permit_without_calls': 1,
109+
},
110+
};
111+
112+
const multiUserSubsciber = new grpcMultiUserAccountSubscriber(
113+
program,
114+
grpcConfigs
115+
);
116+
99117
const baseAccountSubscription = {
100118
type: 'grpc' as const,
101-
grpcConfigs: {
102-
endpoint: GRPC_ENDPOINT,
103-
token: TOKEN,
104-
commitmentLevel: CommitmentLevel.PROCESSED,
105-
channelOptions: {
106-
'grpc.keepalive_time_ms': 10_000,
107-
'grpc.keepalive_timeout_ms': 1_000,
108-
'grpc.keepalive_permit_without_calls': 1,
109-
},
110-
},
119+
grpcConfigs,
120+
grpcMultiUserAccountSubscriber: multiUserSubsciber,
111121
};
112122

113123
const config: DriftClientConfig = {

sdk/src/accounts/grpcMultiAccountSubscriber.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
372372
}
373373
});
374374
});
375+
await this.fetch();
375376
}
376377

377378
async removeAccounts(accounts: PublicKey[]): Promise<void> {

0 commit comments

Comments
 (0)