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

Use React context for SolanaConnection #284

Merged
merged 22 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
979b097
Merge branch 'master' of github.com:swim-io/swim
swimdrew Aug 9, 2022
7cc0102
Merge branch 'master' of github.com:swim-io/swim
swimdrew Aug 10, 2022
9353c22
feat: use React context for SolanaConnection
swimdrew Aug 11, 2022
e6fa53b
fix: less naughty error msg
swimdrew Aug 11, 2022
1e24592
fix: update import
swimdrew Aug 11, 2022
bdc38c5
fix: fix some imports and remove dummy websocket connection
swimdrew Aug 11, 2022
72ae60e
fix: import
swimdrew Aug 11, 2022
a4cc973
Merge branch 'master' of github.com:swim-io/swim into ui/solanaContext
swimdrew Aug 12, 2022
84d8178
style: restore useSolanaConnection hook and original imports
swimdrew Aug 12, 2022
2474962
fix: revert TestPage to master
swimdrew Aug 12, 2022
a4ffa49
Merge branch 'master' of github.com:swim-io/swim into ui/solanaContext
swimdrew Aug 12, 2022
ce198a9
Merge branch 'master' of github.com:swim-io/swim into ui/solanaContext
swimdrew Aug 15, 2022
fd81699
fix: remove outdated file
swimdrew Aug 15, 2022
8e43757
fix: restore dummy WS subscription
swimdrew Aug 15, 2022
4579ea2
style: fix lint for imports
swimdrew Aug 15, 2022
48dcd84
fix: lint import
swimdrew Aug 15, 2022
b49e969
fix: import lint error
swimdrew Aug 15, 2022
43aec07
Merge branch 'master' of github.com:swim-io/swim into ui/solanaContext
swimdrew Aug 17, 2022
c7803cd
style: Update apps/ui/src/hooks/solana/useSolanaConnection.ts
swimdrew Aug 17, 2022
6b9ac35
Merge branch 'ui/solanaContext' of github.com:swim-io/swim into ui/so…
swimdrew Aug 17, 2022
3c2455f
fix: reply to PR comments
swimdrew Aug 17, 2022
1ea6027
Merge branch 'master' into ui/solanaContext
wormat Aug 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions apps/ui/src/contexts/SolanaConnection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ReactElement, ReactNode } from "react";
import { createContext, useMemo } from "react";
import shallow from "zustand/shallow.js";

import { Protocol } from "../config/ecosystem";
import { selectConfig } from "../core/selectors";
import { useEnvironment } from "../core/store";
import { SolanaConnection } from "../models";

export const SolanaConnectionContext: React.Context<null | SolanaConnection> =
createContext<null | SolanaConnection>(null);

export const SolanaConnectionProvider = ({
children,
}: {
readonly children?: ReactNode;
}): ReactElement => {
const { chains } = useEnvironment(selectConfig, shallow);
const [{ endpoints }] = chains[Protocol.Solana];

const connection = useMemo(
() => new SolanaConnection(endpoints),
[endpoints],
);

return (
<SolanaConnectionContext.Provider value={connection}>
{children}
</SolanaConnectionContext.Provider>
);
};
5 changes: 4 additions & 1 deletion apps/ui/src/contexts/appContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type React from "react";

import { SolanaConnectionProvider } from "./SolanaConnection";
import { QueryClientProvider } from "./queryClient";

export const AppContext: React.FC = ({ children }) => (
<QueryClientProvider>{children}</QueryClientProvider>
<SolanaConnectionProvider>
<QueryClientProvider>{children}</QueryClientProvider>
</SolanaConnectionProvider>
);
32 changes: 8 additions & 24 deletions apps/ui/src/hooks/solana/useSolanaConnection.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
import { useQueryClient } from "react-query";
import shallow from "zustand/shallow.js";
import { useContext } from "react";

import { Protocol } from "../../config";
import { selectConfig } from "../../core/selectors";
import { useEnvironment } from "../../core/store";
import { SolanaConnection } from "../../models";
import { SolanaConnectionContext } from "../../contexts/SolanaConnection";
import type { SolanaConnection } from "../../models/solana";

export const useSolanaConnection = (): SolanaConnection => {
const { env } = useEnvironment();
const { chains } = useEnvironment(selectConfig, shallow);
const [chain] = chains[Protocol.Solana];
const { endpoints } = chain;

const queryClient = useQueryClient();
const queryKey = [env, "solanaConnection"];

const connection =
// used as context cache to avoid multiple instances
queryClient.getQueryData<SolanaConnection>(queryKey) ||
(function createSolanaConnection(): SolanaConnection {
const solanaConnection = new SolanaConnection(endpoints);
Copy link
Contributor

Choose a reason for hiding this comment

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

I've added console.log around here but doesn't see the connection recreate per min.
Maybe the root cause is from somewhere else?

Copy link
Contributor

Choose a reason for hiding this comment

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

@ramenuncle I think it is related to the react-query refetch on window focus, try switching tabs after 1 min, it should be triggered

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you have a strong preference against useContext, the change I feel most strongly about is removing the dummy subscription in SolanaConnection.ts.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@swimdrew How confident are you we don't need that anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Checkout this code block, the dummy subscription should be defunct: https://github.com/solana-labs/solana-web3.js/blob/7d05857/src/connection.ts#L4634-L4664

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could iterate through all actions our app does, but I feel very confident that removing the dummy connection is safe.

  1. I've read the web3.js Connection class and only the following methods use the class's rpcWsClient:
  • onAccountChange
  • onProgramAccountChange
  • onLogs
  • onSlot{Change, Update}
  • onSignature{WithOptions}
  • onRootChange

Of these methods, we use onAccountChange to establish the dummy connection to keep rpcWsClient alive, thus there is no actual need to keep rpcWsClient alive.

  1. In our /apps/ui/node_modules/.../@solana/web3.js/src/connection.ts, the rpcWsClient has the missing code that the serum code was accounting for, and I thus feel confident that the Connection class correctly opens and closes its WS client.

Copy link
Collaborator

@wormat wormat Aug 15, 2022

Choose a reason for hiding this comment

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

OK let's do some manual testing and ask testers to try leaving the app open for a while in between interactions, then let's try it (after a positive review obvs).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, it turns out our app DOES use onSignature when bridging tokens from Solana to another chain (from the wormhole library) (tested by Kenny). The swap hangs waiting to confirm the bridge, but it does not hang when swapping from Solana to BSC. Likewise, I don't know with certainty why the dummy connection is still necessary, but restoring it made my AVAX swap go through.

My proposal is to do another round of testing with the dummy connection, and keep the Solana context.

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK do you want to make a follow-up issue to investigate the dummy connection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah maybe Thor can give me his bug, and itll be a bit low priority for me.

queryClient.setQueryData(queryKey, solanaConnection);
return solanaConnection;
})();

return connection;
const solanaConnection = useContext(SolanaConnectionContext);
if (!solanaConnection) {
throw new Error("Missing Solana connection context");
}
return solanaConnection;
};
10 changes: 4 additions & 6 deletions apps/ui/src/models/solana/SolanaConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export class SolanaConnection {
private readonly parsedTxCache: Map<string, ParsedTransactionWithMeta>;
private rpcIndex;
private readonly endpoints: readonly string[];

// TODO: Check if this is still necessary.
// The websocket library solana/web3.js closes its websocket connection when the subscription list
wormat marked this conversation as resolved.
Show resolved Hide resolved
// is empty after opening its first time, preventing subsequent subscriptions from receiving responses.
Expand Down Expand Up @@ -354,11 +353,6 @@ export class SolanaConnection {
confirmTransactionInitialTimeout: 60 * 1000,
disableRetryOnRateLimit: true,
});
this.dummySubscriptionId = this.rawConnection.onAccountChange(
Keypair.generate().publicKey,
() => {},
);

this.getAccountInfo = this.rawConnection.getAccountInfo.bind(
this.rawConnection,
);
Expand All @@ -379,6 +373,10 @@ export class SolanaConnection {
);
this.removeAccountChangeListener =
this.rawConnection.removeAccountChangeListener.bind(this.rawConnection);
this.dummySubscriptionId = this.rawConnection.onAccountChange(
Keypair.generate().publicKey,
() => {},
);
}

private async callWithRetry<T>(
Expand Down