From b3c7a27cae0d5f907c79abc17b49e0c3e05b51d2 Mon Sep 17 00:00:00 2001 From: Maxwell Lasky <mlasky46@gmail.com> Date: Sun, 29 Nov 2020 13:29:42 -0500 Subject: [PATCH] feature: add balance fetch retry logic (#2028) * Adds RPC retry logic * small refactor in balancesActions.js --- app/actions/balancesActions.js | 112 ++++++++++++++++++------------ app/actions/nodeStorageActions.js | 16 +++-- 2 files changed, 78 insertions(+), 50 deletions(-) diff --git a/app/actions/balancesActions.js b/app/actions/balancesActions.js index 77b96b717..c990fff8c 100644 --- a/app/actions/balancesActions.js +++ b/app/actions/balancesActions.js @@ -18,7 +18,8 @@ const MAX_SCRIPT_HASH_CHUNK_SIZE = 3 type Props = { net: string, address: string, - tokens: Array<TokenItemType>, + tokens?: Array<TokenItemType>, + isRetry?: boolean, } let inMemoryBalances = {} @@ -72,14 +73,16 @@ function determineIfBalanceUpdated( }) } -async function getBalances({ net, address }: Props) { +let RETRY_COUNT = 0 + +async function getBalances({ net, address, isRetry = false }: Props) { const { soundEnabled, tokens } = (await getSettings()) || { tokens: [], soundEnabled: true, } const network = findNetworkByDeprecatedLabel(net) - let endpoint = await getNode(net) + let endpoint = await getNode(net, isRetry) if (!endpoint) { endpoint = await getRPCEndpoint(net) } @@ -113,48 +116,63 @@ async function getBalances({ net, address }: Props) { return accum }, []) - const promiseMap = chunks.map(async chunk => { - // NOTE: because the RPC nodes will respond with the contract - // symbol name, we need to use our original token list - // in case two tokens have the same symbol (SWTH vs SWTH OLD) - const balanceResults = await api.nep5.getTokenBalances( - endpoint, - chunk.map(({ scriptHash }) => scriptHash), - address, - ) - const hashBasedBalance = {} + let shouldRetry = false + const results = await Promise.all( + chunks.map(async chunk => { + // NOTE: because the RPC nodes will respond with the contract + // symbol name, we need to use our original token list + // in case two tokens have the same symbol (SWTH vs SWTH OLD) + const balanceResults = await api.nep5 + .getTokenBalances( + endpoint, + chunk.map(({ scriptHash }) => scriptHash), + address, + ) + .catch(e => Promise.reject(e)) - chunk.forEach((token, i) => { - hashBasedBalance[token.symbol] = Object.values(balanceResults)[i] - }) - return hashBasedBalance - }) + const hashBasedBalance = {} - const results = await Promise.all(promiseMap) + chunk.forEach((token, i) => { + hashBasedBalance[token.symbol] = Object.values(balanceResults)[i] + }) + return hashBasedBalance + }), + ).catch(() => { + console.error( + `An error occurred fetching token balances using: ${endpoint} attempting to use a new RPC node.`, + ) + shouldRetry = true + }) + if (shouldRetry && RETRY_COUNT < 4) { + RETRY_COUNT += 1 + return getBalances({ net, address, isRetry: true }) + } - const parsedTokenBalances = results.reduce((accum, currBalance) => { - Object.keys(currBalance).forEach(key => { - const foundToken = tokens.find(token => token.symbol === key) - if (foundToken && currBalance[key]) { - determineIfBalanceUpdated( + const parsedTokenBalances = + results && + results.reduce((accum, currBalance) => { + Object.keys(currBalance).forEach(key => { + const foundToken = tokens.find(token => token.symbol === key) + if (foundToken && currBalance[key]) { + determineIfBalanceUpdated( + // $FlowFixMe + { [foundToken.symbol]: currBalance[key] }, + soundEnabled, + networkHasChanged, + adressHasChanged, + ) // $FlowFixMe - { [foundToken.symbol]: currBalance[key] }, - soundEnabled, - networkHasChanged, - adressHasChanged, - ) - // $FlowFixMe - inMemoryBalances[foundToken.symbol] = currBalance[key] - accum.push({ - [foundToken.scriptHash]: { - ...foundToken, - balance: currBalance[key], - }, - }) - } - }) - return accum - }, []) + inMemoryBalances[foundToken.symbol] = currBalance[key] + accum.push({ + [foundToken.scriptHash]: { + ...foundToken, + balance: currBalance[key], + }, + }) + } + }) + return accum + }, []) // Handle manually added script hashses here const userGeneratedTokenInfo = [] @@ -185,11 +203,13 @@ async function getBalances({ net, address }: Props) { adressHasChanged, ) inMemoryBalances[token.symbol] = token.balance - parsedTokenBalances.push({ - [token.scriptHash]: { - ...token, - }, - }) + if (parsedTokenBalances) { + parsedTokenBalances.push({ + [token.scriptHash]: { + ...token, + }, + }) + } }) // asset balances diff --git a/app/actions/nodeStorageActions.js b/app/actions/nodeStorageActions.js index 8e16132d5..64b4201f5 100644 --- a/app/actions/nodeStorageActions.js +++ b/app/actions/nodeStorageActions.js @@ -122,7 +122,18 @@ export const getRPCEndpoint = async ( } } -export const getNode = async (net: Net): Promise<string> => { +const setNode = async (node: string, net: Net): Promise<string> => + setStorage(`${STORAGE_KEY}-${net}`, { node, timestamp: new Date().getTime() }) + +export const getNode = async ( + net: Net, + errorOccurred?: boolean, +): Promise<string> => { + if (errorOccurred) { + delete cachedRPCUrl[net] + await setNode('', net) + return '' + } const storage = await getStorage(`${STORAGE_KEY}-${net}`).catch(console.error) const nodeInStorage = get(storage, 'node') const expiration = get(storage, 'timestamp') @@ -132,9 +143,6 @@ export const getNode = async (net: Net): Promise<string> => { return nodeInStorage } -const setNode = async (node: string, net: Net): Promise<string> => - setStorage(`${STORAGE_KEY}-${net}`, { node, timestamp: new Date().getTime() }) - export default createActions( ID, ({ url, net }: Props = {}) => async (): Promise<string> => {