From 9f87316b007e914448b0a9693da0c5e698e52252 Mon Sep 17 00:00:00 2001 From: Nam Nguyen Date: Tue, 25 Jul 2023 09:34:48 +0700 Subject: [PATCH] refactor: add cancel block query (#2116) --- src/components/swapv2/AdvancedSwapDetails.tsx | 2 +- src/data/poolRate.ts | 15 ++++-- src/state/application/hooks.ts | 26 +++++++--- src/state/farms/classic/updater.ts | 35 +++++++------- src/state/pools/hooks.ts | 40 +++++++++------- src/state/prommPools/hooks.ts | 18 ++++--- .../useGetElasticPoolsV1.ts | 10 +++- src/utils/index.ts | 47 ++++++++----------- 8 files changed, 112 insertions(+), 81 deletions(-) diff --git a/src/components/swapv2/AdvancedSwapDetails.tsx b/src/components/swapv2/AdvancedSwapDetails.tsx index c9ed2d1dc4..5b4a4ad6e6 100644 --- a/src/components/swapv2/AdvancedSwapDetails.tsx +++ b/src/components/swapv2/AdvancedSwapDetails.tsx @@ -239,7 +239,7 @@ export function TradeSummaryBridge({ outputInfo }: { outputInfo: OutputBridgeInf {tokenInfoOut?.SwapFeeRatePerMillion}% Transaction Fee {tokenInfoOut?.MinimumSwapFee === tokenInfoOut?.MaximumSwapFee ? ( - outputInfo.fee > 0 && ( + Number(outputInfo.fee) > 0 && ( Gas Fee: {`${fee} ${tokenInfoOut.symbol} `} diff --git a/src/data/poolRate.ts b/src/data/poolRate.ts index 688e7aeec2..325e9c7084 100644 --- a/src/data/poolRate.ts +++ b/src/data/poolRate.ts @@ -20,7 +20,7 @@ export const getHourlyRateData = async ( networkInfo: EVMNetworkInfo, elasticClient: ApolloClient, blockClient: ApolloClient, - abortSignal: AbortSignal, + signal: AbortSignal, ): Promise<[PoolRatesEntry[], PoolRatesEntry[]] | undefined> => { try { const utcEndTime = dayjs.utc() @@ -39,8 +39,14 @@ export const getHourlyRateData = async ( } // once you have all the timestamps, get the blocks for each timestamp in a bulk query - let blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, timestamps, networkInfo.chainId) - if (abortSignal.aborted) return + let blocks = await getBlocksFromTimestamps( + isEnableBlockService, + blockClient, + timestamps, + networkInfo.chainId, + signal, + ) + if (signal.aborted) return // catch failing case if (!blocks || blocks?.length === 0) { return @@ -52,9 +58,10 @@ export const getHourlyRateData = async ( elasticClient, blocks, [poolAddress], + signal, 100, ) - if (abortSignal.aborted) return + if (signal.aborted) return // format token ETH price results const values: { diff --git a/src/state/application/hooks.ts b/src/state/application/hooks.ts index a5af1f0eb1..e8ed063c11 100644 --- a/src/state/application/hooks.ts +++ b/src/state/application/hooks.ts @@ -26,7 +26,7 @@ import { useActiveWeb3React } from 'hooks/index' import { useAppSelector } from 'state/hooks' import { AppDispatch, AppState } from 'state/index' import { useTokenPricesWithLoading } from 'state/tokenPrices/hooks' -import { getBlockFromTimestamp, getPercentChange } from 'utils' +import { getBlocksFromTimestamps, getPercentChange } from 'utils' import { createClient } from 'utils/client' import { @@ -228,9 +228,11 @@ export function useActivePopups() { * Gets the current price of ETH, 24 hour price, and % change between them */ export const getEthPrice = async ( + isEnableBlockService: boolean, chainId: ChainId, apolloClient: ApolloClient, blockClient: ApolloClient, + signal: AbortSignal, ) => { const utcCurrentTime = dayjs() const utcOneDayBack = utcCurrentTime.subtract(1, 'day').startOf('minute').unix() @@ -240,7 +242,9 @@ export const getEthPrice = async ( let priceChangeETH = 0 try { - const oneDayBlock = await getBlockFromTimestamp(utcOneDayBack, chainId, blockClient) + const oneDayBlock = ( + await getBlocksFromTimestamps(isEnableBlockService, blockClient, [utcOneDayBack], chainId, signal) + )?.[0]?.number const result = await apolloClient.query({ query: ETH_PRICE(), fetchPolicy: 'network-only', @@ -264,9 +268,11 @@ export const getEthPrice = async ( } const getPrommEthPrice = async ( + isEnableBlockService: boolean, chainId: ChainId, apolloClient: ApolloClient, blockClient: ApolloClient, + signal: AbortSignal, ) => { const utcCurrentTime = dayjs() const utcOneDayBack = utcCurrentTime.subtract(1, 'day').startOf('minute').unix() @@ -276,7 +282,9 @@ const getPrommEthPrice = async ( let priceChangeETH = 0 try { - const oneDayBlock = await getBlockFromTimestamp(utcOneDayBack, chainId, blockClient) + const oneDayBlock = ( + await getBlocksFromTimestamps(isEnableBlockService, blockClient, [utcOneDayBack], chainId, signal) + )?.[0]?.number const result = await apolloClient.query({ query: PROMM_ETH_PRICE(), fetchPolicy: 'network-only', @@ -303,13 +311,14 @@ let fetchingETHPrice = false export function useETHPrice(version: string = VERSION.CLASSIC): AppState['application']['ethPrice'] { const dispatch = useDispatch() const { isEVM, chainId } = useActiveWeb3React() - const { elasticClient, classicClient, blockClient } = useKyberSwapConfig() + const { elasticClient, classicClient, blockClient, isEnableBlockService } = useKyberSwapConfig() const ethPrice = useSelector((state: AppState) => version === VERSION.ELASTIC ? state.application.prommEthPrice : state.application.ethPrice, ) useEffect(() => { + const controller = new AbortController() if (!isEVM) return async function checkForEthPrice() { @@ -317,8 +326,8 @@ export function useETHPrice(version: string = VERSION.CLASSIC): AppState['applic fetchingETHPrice = true try { const [newPrice, oneDayBackPrice, pricePercentChange] = await (version === VERSION.ELASTIC - ? getPrommEthPrice(chainId, elasticClient, blockClient) - : getEthPrice(chainId, classicClient, blockClient)) + ? getPrommEthPrice(isEnableBlockService, chainId, elasticClient, blockClient, controller.signal) + : getEthPrice(isEnableBlockService, chainId, classicClient, blockClient, controller.signal)) dispatch( version === VERSION.ELASTIC @@ -338,7 +347,10 @@ export function useETHPrice(version: string = VERSION.CLASSIC): AppState['applic } } checkForEthPrice() - }, [dispatch, chainId, version, isEVM, elasticClient, classicClient, blockClient]) + return () => { + controller.abort() + } + }, [dispatch, chainId, version, isEVM, elasticClient, classicClient, blockClient, isEnableBlockService]) return ethPrice } diff --git a/src/state/farms/classic/updater.ts b/src/state/farms/classic/updater.ts index a85de335b0..6dbbab3cde 100644 --- a/src/state/farms/classic/updater.ts +++ b/src/state/farms/classic/updater.ts @@ -3,7 +3,7 @@ import { Contract } from '@ethersproject/contracts' import { useEffect, useRef } from 'react' import { useSelector } from 'react-redux' -import { ETHER_ADDRESS, ZERO_ADDRESS } from 'constants/index' +import { AbortedError, ETHER_ADDRESS, ZERO_ADDRESS } from 'constants/index' import { EVMNetworkInfo } from 'constants/networks/type' import { NativeCurrencies } from 'constants/tokens' import { useActiveWeb3React } from 'hooks' @@ -38,7 +38,7 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) useEffect(() => { if (!isEVM) return console.count('running farm updater') - let cancelled = false + const abortController = new AbortController() async function getListFarmsForContract(contract: Contract): Promise { const isV3 = (networkInfo as EVMNetworkInfo).classic.fairlaunchV3?.includes(contract.address) @@ -46,9 +46,9 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) if (!isEVM) return [] let rewardTokenAddresses: string[] = [] if (!isV3) rewardTokenAddresses = await contract?.getRewardTokens() - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() const poolLength = await contract?.poolLength() - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() const pids = [...Array(BigNumber.from(poolLength).toNumber()).keys()] @@ -63,7 +63,7 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) const poolInfos = await Promise.all( pids.map(async (pid: number) => { const poolInfo = await contract?.getPoolInfo(pid) - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() if (isV2 || isV3) { return { ...poolInfo, @@ -98,22 +98,22 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) const stakedBalances = await Promise.all( pids.map(async (pid: number) => { const stakedBalance = account ? await contract?.getUserInfo(pid, account as string) : { amount: 0 } - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() return stakedBalance.amount }), ) - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() const pendingRewards = await Promise.all( pids.map(async (pid: number) => { const pendingRewards = account ? await contract?.pendingRewards(pid, account as string) : null - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() return pendingRewards }), ) - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() const poolAddresses = poolInfos.map(poolInfo => poolInfo.stakeToken.toLowerCase()) @@ -124,8 +124,9 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) blockClient, chainId, ethPriceRef.current, + abortController.signal, ) - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() const farms: Farm[] = poolInfos.map((poolInfo, index) => { return { @@ -169,19 +170,19 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) ) const promiseResult = await Promise.all(promises) - if (cancelled) throw new Error('canceled') + if (abortController.signal.aborted) throw new AbortedError() fairLaunchAddresses.forEach((address, index) => { result[address] = promiseResult[index] }) - if (latestChainId.current === chainId && (Object.keys(farmsDataRef.current).length === 0 || !cancelled)) { + if (latestChainId.current === chainId && Object.keys(farmsDataRef.current).length === 0) { dispatch(setFarmsData(result)) } } catch (err) { - if (!cancelled) { - console.error(err) - dispatch(setYieldPoolsError(err as Error)) - } + if (err instanceof AbortedError) return + if (abortController.signal.aborted) return + console.error(err) + dispatch(setYieldPoolsError(err as Error)) } dispatch(setLoading(false)) @@ -196,7 +197,7 @@ export default function Updater({ isInterval = true }: { isInterval?: boolean }) }, 30_000) return () => { - cancelled = true + abortController.abort() i && clearInterval(i) } }, [ diff --git a/src/state/pools/hooks.ts b/src/state/pools/hooks.ts index 41eeef00fd..5a9886b45b 100644 --- a/src/state/pools/hooks.ts +++ b/src/state/pools/hooks.ts @@ -137,7 +137,8 @@ export async function getBulkPoolDataFromPoolList( apolloClient: ApolloClient, blockClient: ApolloClient, chainId: ChainId, - ethPrice?: string, + ethPrice: string | undefined, + signal: AbortSignal, ): Promise { try { const current = await apolloClient.query({ @@ -146,7 +147,7 @@ export async function getBulkPoolDataFromPoolList( }) let poolData const [t1] = getTimestampsForChanges() - const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId) + const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId, signal) if (!blocks.length) { return current.data.pools } else { @@ -205,10 +206,11 @@ export async function getBulkPoolDataWithPagination( blockClient: ApolloClient, ethPrice: string, chainId: ChainId, + signal: AbortSignal, ): Promise { try { const [t1] = getTimestampsForChanges() - const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId) + const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [t1], chainId, signal) // In case we can't get the block one day ago then we set it to 0 which is fine // because our subgraph never syncs from block 0 => response is empty @@ -322,7 +324,7 @@ export function useAllPoolsData(): { const poolCountSubgraph = usePoolCountInSubgraph() useEffect(() => { if (!isEVM) return - let cancelled = false + const controller = new AbortController() const getPoolsData = async () => { try { @@ -340,24 +342,25 @@ export function useAllPoolsData(): { blockClient, ethPrice, chainId, + controller.signal, ), ) } const pools = (await Promise.all(promises.map(callback => callback()))).flat() - !cancelled && dispatch(updatePools({ pools })) - !cancelled && dispatch(setLoading(false)) + if (controller.signal.aborted) return + dispatch(updatePools({ pools })) + dispatch(setLoading(false)) } } catch (error) { - !cancelled && dispatch(setError(error as Error)) - !cancelled && dispatch(setLoading(false)) + if (controller.signal.aborted) return + dispatch(setError(error as Error)) + dispatch(setLoading(false)) } } getPoolsData() - return () => { - cancelled = true - } + return () => controller.abort() }, [ chainId, dispatch, @@ -396,7 +399,8 @@ export function useSinglePoolData( useEffect(() => { if (!isEVM) return - let isCanceled = false + const controller = new AbortController() + async function checkForPools() { setLoading(true) @@ -409,14 +413,16 @@ export function useSinglePoolData( blockClient, chainId, ethPrice, + controller.signal, ) - + if (controller.signal.aborted) return if (pools.length > 0) { - !isCanceled && setPoolData(pools[0]) + setPoolData(pools[0]) } } } catch (error) { - !isCanceled && setError(error as Error) + if (controller.signal.aborted) return + setError(error as Error) } setLoading(false) @@ -424,9 +430,7 @@ export function useSinglePoolData( checkForPools() - return () => { - isCanceled = true - } + return () => controller.abort() }, [ethPrice, error, poolAddress, chainId, isEVM, networkInfo, classicClient, blockClient, isEnableBlockService]) return { loading, error, data: poolData } diff --git a/src/state/prommPools/hooks.ts b/src/state/prommPools/hooks.ts index fff66659f7..740d10c73c 100644 --- a/src/state/prommPools/hooks.ts +++ b/src/state/prommPools/hooks.ts @@ -239,20 +239,26 @@ export const usePoolBlocks = () => { const utcCurrentTime = dayjs() const last24h = utcCurrentTime.subtract(1, 'day').startOf('minute').unix() - const [blocks, setBlocks] = useState<{ number: number }[]>([]) + const [block, setBlock] = useState(undefined) useEffect(() => { + const controller = new AbortController() const getBlocks = async () => { - const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [last24h], chainId) - setBlocks(blocks) + const [block] = await getBlocksFromTimestamps( + isEnableBlockService, + blockClient, + [last24h], + chainId, + controller.signal, + ) + setBlock(block?.number) } getBlocks() + return () => controller.abort() }, [chainId, last24h, blockClient, isEnableBlockService]) - const [blockLast24h] = blocks ?? [] - - return { blockLast24h: blockLast24h?.number } + return { blockLast24h: block } } export function useSelectedPool() { diff --git a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts index 790ca9f6bc..a4941e9fc5 100644 --- a/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts +++ b/src/state/prommPools/useGetElasticPools/useGetElasticPoolsV1.ts @@ -27,12 +27,20 @@ const usePoolBlocks = () => { const [blocks, setBlocks] = useState<{ number: number }[]>([]) useEffect(() => { + const controller = new AbortController() const getBlocks = async () => { - const blocks = await getBlocksFromTimestamps(isEnableBlockService, blockClient, [last24h], chainId) + const blocks = await getBlocksFromTimestamps( + isEnableBlockService, + blockClient, + [last24h], + chainId, + controller.signal, + ) setBlocks(blocks) } getBlocks() + return () => controller.abort() }, [chainId, last24h, blockClient, isEnableBlockService]) const [blockLast24h] = blocks ?? [] diff --git a/src/utils/index.ts b/src/utils/index.ts index 054cc1c39a..fba70d104b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,7 +6,7 @@ import dayjs from 'dayjs' import JSBI from 'jsbi' import Numeral from 'numeral' -import { GET_BLOCK, GET_BLOCKS } from 'apollo/queries' +import { GET_BLOCKS } from 'apollo/queries' import { BLOCK_SERVICE_API, ENV_KEY } from 'constants/env' import { DEFAULT_GAS_LIMIT_MARGIN, ZERO_ADDRESS } from 'constants/index' import { NETWORKS_INFO, NETWORKS_INFO_CONFIG, isEVM } from 'constants/networks' @@ -252,6 +252,7 @@ export async function splitQuery( localClient: ApolloClient, list: T[], vars: U[], + signal: AbortSignal, skipCount = 100, ): Promise< | { @@ -272,6 +273,11 @@ export async function splitQuery( const result = await localClient.query({ query: query(sliced, ...vars), fetchPolicy: 'no-cache', + context: { + fetchOptions: { + signal, + }, + }, }) fetchedData = { ...fetchedData, @@ -287,29 +293,6 @@ export async function splitQuery( return fetchedData } -/** - * @notice Fetches first block after a given timestamp - * @dev Query speed is optimized by limiting to a 600-second period - * @param {Int} timestamp in seconds - */ -export async function getBlockFromTimestamp( - timestamp: number, - chainId: ChainId, - blockClient: ApolloClient, -) { - if (!isEVM(chainId)) return - const result = await blockClient.query({ - query: GET_BLOCK, - variables: { - timestampFrom: timestamp, - timestampTo: timestamp + 600, - }, - fetchPolicy: 'cache-first', - }) - - return result?.data?.blocks?.[0]?.number -} - /** * @notice Fetches block objects for an array of timestamps. * @dev blocks are returned in chronological order (ASC) regardless of input. @@ -321,13 +304,20 @@ export async function getBlocksFromTimestampsSubgraph( blockClient: ApolloClient, timestamps: number[], chainId: ChainId, + signal: AbortSignal, ): Promise<{ timestamp: number; number: number }[]> { if (!isEVM(chainId)) return [] if (timestamps?.length === 0) { return [] } - const fetchedData = await splitQuery<{ number: string }[], number, any>(GET_BLOCKS, blockClient, timestamps, []) + const fetchedData = await splitQuery<{ number: string }[], number, any>( + GET_BLOCKS, + blockClient, + timestamps, + [], + signal, + ) const blocks: { timestamp: number; number: number }[] = [] if (fetchedData) { for (const t in fetchedData) { @@ -346,6 +336,7 @@ export async function getBlocksFromTimestampsSubgraph( export async function getBlocksFromTimestampsBlockService( timestamps: number[], chainId: ChainId, + signal: AbortSignal, ): Promise<{ timestamp: number; number: number }[]> { if (!isEVM(chainId)) return [] if (timestamps?.length === 0) { @@ -360,6 +351,7 @@ export async function getBlocksFromTimestampsBlockService( `${BLOCK_SERVICE_API}/${ NETWORKS_INFO[chainId].aggregatorRoute }/api/v1/block?timestamps=${timestampsChunk.join(',')}`, + { signal }, ) ).json() as Promise<{ data: { timestamp: number; number: number }[] }>, ), @@ -377,9 +369,10 @@ export async function getBlocksFromTimestamps( blockClient: ApolloClient, timestamps: number[], chainId: ChainId, + signal: AbortSignal, ): Promise<{ timestamp: number; number: number }[]> { - if (isEnableBlockService) return getBlocksFromTimestampsBlockService(timestamps, chainId) - return getBlocksFromTimestampsSubgraph(blockClient, timestamps, chainId) + if (isEnableBlockService) return getBlocksFromTimestampsBlockService(timestamps, chainId, signal) + return getBlocksFromTimestampsSubgraph(blockClient, timestamps, chainId, signal) } /**