Skip to content

Commit

Permalink
[pairing] use react-query
Browse files Browse the repository at this point in the history
  • Loading branch information
grod220 committed Sep 3, 2024
1 parent 2bfba05 commit c7dda19
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 144 deletions.
16 changes: 2 additions & 14 deletions apps/extension/src/hooks/full-sync-height.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useQuery } from '@tanstack/react-query';
import { PopupLoaderData } from '../routes/popup/home';
import { useStore } from '../state';
import { networkSelector } from '../state/network';
import { useLoaderData } from 'react-router-dom';
import { fetchBlockHeight } from '../state/block-height';
import { useLatestBlockHeight } from './latest-block-height';

const tryGetMax = (a?: number, b?: number): number | undefined => {
// Height can be 0n which is falsy, so should compare to undefined state
Expand All @@ -28,18 +27,7 @@ const useFullSyncHeight = (): number | undefined => {

export const useSyncProgress = () => {
const fullSyncHeight = useFullSyncHeight();
const { grpcEndpoint } = useStore(networkSelector);

const { data: queriedLatest, error } = useQuery({
queryKey: ['latestBlockHeight'],
queryFn: async () => {
if (!grpcEndpoint) {
return;
}
return await fetchBlockHeight(grpcEndpoint);
},
enabled: Boolean(grpcEndpoint),
});
const { data: queriedLatest, error } = useLatestBlockHeight();

// If we have a queried sync height and it's ahead of our block-height query,
// use the sync value instead
Expand Down
74 changes: 74 additions & 0 deletions apps/extension/src/hooks/latest-block-height.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useQuery } from '@tanstack/react-query';
import { sample } from 'lodash';
import { createPromiseClient } from '@connectrpc/connect';
import { createGrpcWebTransport } from '@connectrpc/connect-web';
import { TendermintProxyService } from '@penumbra-zone/protobuf';
import { ChainRegistryClient } from '@penumbra-labs/registry';
import { useStore } from '../state';
import { networkSelector } from '../state/network';

// Utility function to fetch the block height by randomly querying one of the RPC endpoints
// from the chain registry, using a recursive callback to try another endpoint if the current
// one fails. Additionally, this implements a timeout mechanism at the request level to avoid
// hanging from stalled requests.
const fetchBlockHeightWithFallback = async (endpoints: string[]): Promise<number> => {
if (endpoints.length === 0) {
throw new Error('All RPC endpoints failed to fetch the block height.');
}

// Randomly select an RPC endpoint from the chain registry
const randomGrpcEndpoint = sample(endpoints);
if (!randomGrpcEndpoint) {
throw new Error('No RPC endpoints found.');
}

try {
return await fetchBlockHeight(randomGrpcEndpoint);
} catch (e) {
// Remove the current endpoint from the list and retry with remaining endpoints
const remainingEndpoints = endpoints.filter(endpoint => endpoint !== randomGrpcEndpoint);
return fetchBlockHeightWithFallback(remainingEndpoints);
}
};

// Fetch the block height from a specific RPC endpoint with a timeout to prevent hanging requests.
export const fetchBlockHeight = async (grpcEndpoint: string): Promise<number> => {
const tendermintClient = createPromiseClient(
TendermintProxyService,
createGrpcWebTransport({ baseUrl: grpcEndpoint, defaultTimeoutMs: 2000 }),
);

const result = await tendermintClient.getStatus({});
if (!result.syncInfo) {
throw new Error('No syncInfo in getStatus result');
}
return Number(result.syncInfo.latestBlockHeight);
};

export const useLatestBlockHeightWithFallback = () => {
return useQuery({
queryKey: ['latestBlockHeightWithFallback'],
queryFn: async () => {
const chainRegistryClient = new ChainRegistryClient();
const { rpcs } = chainRegistryClient.bundled.globals();
const suggestedEndpoints = rpcs.map(i => i.url);
return await fetchBlockHeightWithFallback(suggestedEndpoints);
},
retry: false,
});
};

export const useLatestBlockHeight = () => {
const { grpcEndpoint } = useStore(networkSelector);

return useQuery({
queryKey: ['latestBlockHeight'],
queryFn: async () => {
if (!grpcEndpoint) {
return;
}
return await fetchBlockHeight(grpcEndpoint);
},
enabled: Boolean(grpcEndpoint),
});
};
9 changes: 0 additions & 9 deletions apps/extension/src/hooks/onboarding.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useStore } from '../state';
// import { existingWalletBlockHeightSelector } from '../state/block-height';
import { passwordSelector } from '../state/password';
import { generateSelector } from '../state/seed-phrase/generate';
import { importSelector } from '../state/seed-phrase/import';
Expand All @@ -20,11 +19,3 @@ export const useAddWallet = () => {
await addWallet({ label: 'Wallet #1', seedPhrase });
};
};

export const useOnboardingSaveOptional = () => {
const { setBlockHeight } = useStore(state => state.walletHeight);

return async (walletBlockHeight: number) => {
await setBlockHeight(walletBlockHeight);
};
};
49 changes: 18 additions & 31 deletions apps/extension/src/routes/page/onboarding/generate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ExclamationTriangleIcon, LockClosedIcon } from '@radix-ui/react-icons';
import { SeedPhraseLength } from '@penumbra-zone/crypto-web/mnemonic';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useState } from 'react';
import { Button } from '@repo/ui/components/ui/button';
import { BackIcon } from '@repo/ui/components/ui/icons/back-icon';
import { Card, CardContent, CardHeader, CardTitle } from '@repo/ui/components/ui/card';
Expand All @@ -14,38 +14,29 @@ import { generateSelector } from '../../../state/seed-phrase/generate';
import { usePageNav } from '../../../utils/navigate';
import { PagePath } from '../paths';
import { WordLengthToogles } from '../../../shared/containers/word-length-toogles';
import { walletBlockHeightSelector } from '../../../state/block-height';
import { useLatestBlockHeightWithFallback } from '../../../hooks/latest-block-height';
import { localExtStorage } from '../../../storage/local';

export const GenerateSeedPhrase = () => {
const navigate = usePageNav();
const { phrase, generateRandomSeedPhrase } = useStore(generateSelector);
const [count, { startCountdown }] = useCountdown({ countStart: 3 });
const [reveal, setReveal] = useState(false);
const [error, setError] = useState<string | null>(null);
const blockHeight = useStore(walletBlockHeightSelector);
const setBlockHeight = useStore(state => state.walletHeight.setBlockHeight);

// Track if the block height has been initialized to avoid multiple fetch attempts
const isInitialized = useRef(false);
const { data: latestBlockHeight, isLoading, error } = useLatestBlockHeightWithFallback();

const onSubmit = async () => {
await localExtStorage.set('walletCreationBlockHeight', latestBlockHeight);
navigate(PagePath.CONFIRM_BACKUP);
};

// On render, asynchronously generate a new seed phrase and initialize the wallet creation block height
useEffect(() => {
void (async () => {
try {
if (!phrase.length) {
generateRandomSeedPhrase(SeedPhraseLength.TWELVE_WORDS);
}
startCountdown();

if (!isInitialized.current && blockHeight === 0) {
await setBlockHeight(0, true);
isInitialized.current = true;
}
} catch (error) {
setError('Failed to fetch block height. Please try again later');
}
})();
}, [generateRandomSeedPhrase, phrase.length, startCountdown, blockHeight, setBlockHeight]);
if (!phrase.length) {
generateRandomSeedPhrase(SeedPhraseLength.TWELVE_WORDS);
}
startCountdown();
}, [generateRandomSeedPhrase, phrase.length, startCountdown]);

return (
<FadeTransition>
Expand Down Expand Up @@ -84,13 +75,9 @@ export const GenerateSeedPhrase = () => {
<h4 className='text-center text-lg font-semibold text-gray-200'>Wallet Birthday</h4>
<p className='mt-2 text-center text-gray-300'>
<span className='font-bold text-gray-100'>
{error ? (
<span className='text-red-500'>{error}</span>
) : isInitialized.current ? (
Number(blockHeight).toLocaleString()
) : (
'Loading...'
)}
{Boolean(error) && <span className='text-red-500'>{String(error)}</span>}
{isLoading && 'Loading...'}
{latestBlockHeight && Number(latestBlockHeight).toLocaleString()}
</span>
</p>
<p className='mt-2 text-sm text-gray-400'>
Expand Down Expand Up @@ -126,7 +113,7 @@ export const GenerateSeedPhrase = () => {
<Button
className='mt-4'
variant='gradient'
onClick={() => navigate(PagePath.CONFIRM_BACKUP)}
onClick={() => void onSubmit()}
disabled={count !== 0}
>
I have backed this up
Expand Down
9 changes: 4 additions & 5 deletions apps/extension/src/routes/page/onboarding/height.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,17 @@ import { usePageNav } from '../../../utils/navigate';
import { PagePath } from '../paths';
import { FormEvent, useState } from 'react';
import { Input } from '@repo/ui/components/ui/input';
import { useOnboardingSaveOptional } from '../../../hooks/onboarding';
import { localExtStorage } from '../../../storage/local';

export const ImportWalletCreationHeight = () => {
const navigate = usePageNav();
const [blockHeight, setBlockHeight] = useState('');
const onboardingSave = useOnboardingSaveOptional();
const [blockHeight, setBlockHeight] = useState<number>();

const handleSubmit = (event: FormEvent) => {
event.preventDefault();

void (async () => {
await onboardingSave(Number(blockHeight));
await localExtStorage.set('walletCreationBlockHeight', blockHeight);
navigate(PagePath.SET_PASSWORD);
})();
};
Expand All @@ -49,7 +48,7 @@ export const ImportWalletCreationHeight = () => {
type='number'
placeholder='Enter block height'
value={blockHeight}
onChange={e => setBlockHeight(e.target.value)}
onChange={e => setBlockHeight(Number(e.target.value))}
className='rounded-md border border-gray-700 p-3 text-[16px] font-normal leading-[24px]'
/>
<Button className='mt-6 w-full' variant='gradient' onClick={handleSubmit}>
Expand Down
82 changes: 0 additions & 82 deletions apps/extension/src/state/block-height.ts

This file was deleted.

3 changes: 0 additions & 3 deletions apps/extension/src/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { createOriginApprovalSlice, OriginApprovalSlice } from './origin-approva
import { ConnectedSitesSlice, createConnectedSitesSlice } from './connected-sites';
import { createDefaultFrontendSlice, DefaultFrontendSlice } from './default-frontend';
import { createNumerairesSlice, NumerairesSlice } from './numeraires';
import { walletHeightSlice, createWalletCreationBlockHeightSlice } from './block-height';

export interface AllSlices {
wallets: WalletsSlice;
Expand All @@ -26,7 +25,6 @@ export interface AllSlices {
originApproval: OriginApprovalSlice;
connectedSites: ConnectedSitesSlice;
defaultFrontend: DefaultFrontendSlice;
walletHeight: walletHeightSlice;
}

export type SliceCreator<SliceInterface> = StateCreator<
Expand All @@ -50,7 +48,6 @@ export const initializeStore = (
txApproval: createTxApprovalSlice()(setState, getState, store),
originApproval: createOriginApprovalSlice()(setState, getState, store),
defaultFrontend: createDefaultFrontendSlice(local)(setState, getState, store),
walletHeight: createWalletCreationBlockHeightSlice(local)(setState, getState, store),
}));
};

Expand Down

0 comments on commit c7dda19

Please sign in to comment.