Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sdk: allow delegates in single client #432

Merged
merged 21 commits into from
Apr 27, 2023

Conversation

lowkeynicc
Copy link
Contributor

Tried to make this as backwards compatible as possible, consumers of the SDK shouldn't need to update unless they were accessing the driftClient.users map directly (instead of using something like driftClient.getUsers(). Basically with the possibility of delegate accounts sharing the same id as user accounts now, user map is now keyed off a string in the format of "[subAccountId]_[authority]"

Consumers will need to pass includeDelegates flag in the driftClientConfig (and to updateWallet) to have those accounts loaded and subscribed to. Can change this to load them by default if necessary

@@ -154,8 +155,8 @@ export class DriftClient {
);

this.authority = config.authority ?? this.wallet.publicKey;
const subAccountIds = config.subAccountIds ?? [0];
this.activeSubAccountId = config.activeSubAccountId ?? subAccountIds[0];
this.activeSubAccountId = config.activeSubAccountId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why'd you get rid of the ?? [0]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this way it will start undefined if not specified, then the call to addUsers will know no default is set and use the first user

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this might break some backwards compatibility since not every user will call addUsers

this.createUsers(subAccountIds, this.userAccountSubscriptionConfig);

// load all or some users and add them to the driftClient
this.addAllUsers(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think we want to do any async calls in the constructor, thats why the createUsers call didn't call subscribe before

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we have a new DriftClientConfig Param which is authoritySubaccountMap which allows you to specify the authority & subaccount pairs

@@ -56,7 +56,7 @@ export class RetryTxSender implements TxSender {
opts = this.provider.opts;
}

const signedTx = preSigned
const signedTx = preSigned && tx.feePayer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's preSigned, why would it not have a feePayer? seems like there is an issue upstream?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no issue, just sort of a redundancy check but i can remove

const userAccounts = await this.getUserAccountsForAuthority(this.authority);
public async addAllUsers(
subAccountIds?: number[],
setFirstActive?: boolean,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when do you set this true versus false, trying to understand when we'd use it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the moment if the user passes a subaccount id to the constructor or updateWallet it defaults the user to that, if not then this flag is true and it defaults to the first user

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel like the constructor always needs to set the activeSubAccount for backwards compatability

for (const userAccount of userAccounts) {
await this.addUser(userAccount.subAccountId);
this.addUser(userAccount.subAccountId, this.authority);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not having await for these addUser will lead to race conditions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't have any race condition issues with it and tested a lot, wanted to make it faster, will add back

public async addAllUsers(
subAccountIds?: number[],
setFirstActive?: boolean,
includeDelegates?: boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't you just check that if the this.authority == wallet.pubkey to decide if the user has delegate set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean by this, this flag indicates we want to load any/all of the accounts delegated to an authority as part of their subscribed driftclient users

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i don't think my comment made sense, nvm

public async addAllUsers(): Promise<void> {
const userAccounts = await this.getUserAccountsForAuthority(this.authority);
public async addAllUsers(
subAccountIds?: number[],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this just for the constructor? would be nice if we didn't need to add this as it'd make the function less confusing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's to allow them to only subscribe to a subset of subaccount ids

this.userStatsAccountPublicKey = undefined;

this.users.clear();
await this.addAllUsers(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you adding the users before the unsubscribe? feel like this is backwards?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mistake, not sure how it wasn't breaking anything

this.userStatsAccountPublicKey = undefined;
this.users.clear();

await this.addAndSubscribeToUsers(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why'd you move this out of the this.isSubscribed block? feel like it makes sense to only re-subscribe if you were already subscribed

Copy link
Contributor Author

@lowkeynicc lowkeynicc Apr 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think isSubscribed would still be true from the driftclient being initialized?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isSubscribed doesnt get set in the constructor, just if you call subscribe, so it could not be set? why'd you need to move this re-subscribe outta the if block?

this.accountSubscriber.subscribe()
);
let subscribePromises = [
this.addAndSubscribeToUsers(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the parameters for this function exist on the DriftClient, do you need to pass em as parameters?

@@ -20,6 +20,8 @@ export type DriftClientConfig = {
env?: DriftEnv;
userStats?: boolean;
authority?: PublicKey; // explicitly pass an authority if signer is delegate
includeDelegates?: boolean; // flag for whether to load delegate accounts as well
authoritySubaccountMap?: [string, number][]; // if passed this will override subAccountIds and includeDelegates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be a PublicKey instead of string

nice job adding comments 🙏

@@ -129,6 +130,9 @@ export class DriftClient {
authority: PublicKey;
marketLookupTable: PublicKey;
lookupTableAccount: AddressLookupTableAccount;
subAccountIds?: number[];
includeDelegates?: boolean;
authoritySubaccountMap?: [string, number][];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking we only have authoritySubaccountMap and get rid of the subAccountIds on the actual DriftClient. If the user passes the subAccounts, we just default to creating the map for them given the authority?

this.userStatsAccountPublicKey = undefined;
this.users.clear();

await this.addAndSubscribeToUsers(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isSubscribed doesnt get set in the constructor, just if you call subscribe, so it could not be set? why'd you need to move this re-subscribe outta the if block?

const userAccounts = await this.getUserAccountsForAuthority(this.authority);
for (const userAccount of userAccounts) {
await this.addUser(userAccount.subAccountId);
public async addAndSubscribeToUsers(): Promise<boolean> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this, i'm thinking if we've made the change to just have the DriftClient to only have the map:

  1. if the map isn't empty, add the users based on the authority + sub account ids in the map
  2. if it is empty, make the call to getUserAccountsForAuthority/getUserAccountsForDelegates

I think this is desierable because the calls to getUserAccountsForAuthority and getUserAccountsForDelegate make getProgramAccount rpc calls, which can be expensive, so good to avoid em if we can

const subAccountIds = config.subAccountIds ?? [0];
this.activeSubAccountId = config.activeSubAccountId ?? subAccountIds[0];
this.activeSubAccountId = config.activeSubAccountId ?? 0;
this.authoritySubaccountMap = config.authoritySubaccountMap ?? undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think you can get rid of this since you reset it below

? config.authoritySubaccountMap
: config.subAccountIds
? config.subAccountIds.map((acctId) => [this.authority, acctId])
: undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible we just set it as empty if they don't pass anything? all else equal, nice to avoid undefined/null

for (const account of userAccounts.concat(delegatedAccounts)) {
result =
result &&
(await this.addUser(account.subAccountId, account.authority));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we made it so you can pass in a UserAccount object to user.subscribe to speed up the subscribe logic, we should probably use that here since we already have all the UserAccounts (we can do this in a separate pr later)

@@ -129,6 +130,8 @@ export class DriftClient {
authority: PublicKey;
marketLookupTable: PublicKey;
lookupTableAccount: AddressLookupTableAccount;
includeDelegates?: boolean;
authoritySubaccountMap?: [PublicKey, number][];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry last nit pit, should this be a map instead of an array of arrays? if you wanted multiple sub accounts from same pub key, you'd have to do something like:

[
  [Pubkey.default(), 0],
  [Pubkey.default(), 1],
  [Pubkey.default(), 2],
]

with a map it'd be:

new Map([
  [Pubkey.default(), [0, 1, 2]]
])

(I think it'd be to be Map<string, number[]> since PublicKey doesnt really work as a key)

@lowkeynicc lowkeynicc merged commit 294b832 into master Apr 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants