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

backend/btc: do not panic when retrieving balance #2076

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 1 addition & 6 deletions backend/coins/btc/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,12 +544,7 @@ func (account *Account) Balance() (*accounts.Balance, error) {
if account.fatalError.Load() {
return nil, errp.New("can't call Balance() after a fatal error")
}
balance, err := account.transactions.Balance()
if err != nil {
// TODO
panic(err)
}
return balance, nil
return account.transactions.Balance()
}

func (account *Account) incAndEmitSyncCounter() {
Expand Down
22 changes: 15 additions & 7 deletions backend/coins/btc/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,24 @@ func (handlers *Handlers) getUTXOs(_ *http.Request) (interface{}, error) {
}

func (handlers *Handlers) getAccountBalance(_ *http.Request) (interface{}, error) {
var result struct {
Success bool `json:"success"`
HasAvailable bool `json:"hasAvailable"`
Available FormattedAmount `json:"available"`
HasIncoming bool `json:"hasIncoming"`
Incoming FormattedAmount `json:"incoming"`
}
balance, err := handlers.account.Balance()
if err != nil {
return nil, err
handlers.log.WithError(err).Error("Error getting account balance")
return result, nil
}
return map[string]interface{}{
"hasAvailable": balance.Available().BigInt().Sign() > 0,
"available": handlers.formatAmountAsJSON(balance.Available(), false),
"hasIncoming": balance.Incoming().BigInt().Sign() > 0,
"incoming": handlers.formatAmountAsJSON(balance.Incoming(), false),
}, nil
result.Success = true
result.HasAvailable = balance.Available().BigInt().Sign() > 0
result.Available = handlers.formatAmountAsJSON(balance.Available(), false)
result.HasIncoming = balance.Incoming().BigInt().Sign() > 0
result.Incoming = handlers.formatAmountAsJSON(balance.Incoming(), false)
return result, nil
}

type sendTxInput struct {
Expand Down
5 changes: 4 additions & 1 deletion frontends/web/src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { apiGet, apiPost } from '../utils/request';
import { SuccessResponse } from './response';
import { ChartData } from '../routes/account/summary/chart';

export type CoinCode = 'btc' | 'tbtc' | 'ltc' | 'tltc' | 'eth' | 'goeth';
Expand Down Expand Up @@ -151,7 +152,9 @@ export interface IBalance {
incoming: IAmount;
}

export const getBalance = (code: AccountCode): Promise<IBalance> => {
export type TBalanceResult = { success: false } | (SuccessResponse & IBalance);

export const getBalance = (code: AccountCode): Promise<TBalanceResult> => {
return apiGet(`account/${code}/balance`);
};

Expand Down
5 changes: 3 additions & 2 deletions frontends/web/src/components/balance/balance.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
*/

import { render } from '@testing-library/react';
import { IBalance } from '../../api/account';
import { TBalanceResult } from '../../api/account';
import I18NWrapper from '../../i18n/forTests/i18nwrapper';
import { Balance } from './balance';

describe('components/balance/balance', () => {
it('renders balance properly', () => {
const MOCK_BALANCE: IBalance = {
const MOCK_BALANCE: TBalanceResult = {
success: true,
hasAvailable: true,
hasIncoming: true,
available: {
Expand Down
9 changes: 7 additions & 2 deletions frontends/web/src/components/balance/balance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
*/

import { useTranslation } from 'react-i18next';
import { IBalance } from '../../api/account';
import { TBalanceResult } from '../../api/account';
import { FiatConversion } from '../../components/rates/rates';
import { bitcoinRemoveTrailingZeroes } from '../../utils/trailing-zeroes';
import style from './balance.module.css';

type TProps = {
balance?: IBalance;
balance?: TBalanceResult;
noRotateFiat?: boolean;
}

Expand All @@ -36,6 +36,11 @@ export const Balance = ({
<header className={style.balance}></header>
);
}
if (!balance.success) {
return (
<header className={style.balance}>{t('account.balanceError')}</header>
);
}

// remove trailing zeroes from Bitcoin balance
const availableBalance = bitcoinRemoveTrailingZeroes(balance.available.amount, balance.available.unit);
Expand Down
1 change: 1 addition & 0 deletions frontends/web/src/locales/en/app.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"account": {
"balanceError": "Error retrieving balance",
"disconnect": "Connection lost. Retrying…",
"export": "Export",
"exportTransactions": "Export transactions to downloads folder as CSV file",
Expand Down
11 changes: 6 additions & 5 deletions frontends/web/src/routes/account/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function Account({
}: Props) {
const { t } = useTranslation();

const [balance, setBalance] = useState<accountApi.IBalance>();
const [balance, setBalance] = useState<accountApi.TBalanceResult>();
const [status, setStatus] = useState<accountApi.IStatus>();
const [syncedAddressesCount, setSyncedAddressesCount] = useState<number>();
const [transactions, setTransactions] = useState<accountApi.TTransactions>();
Expand Down Expand Up @@ -166,7 +166,7 @@ export function Account({
return null;
}

const canSend = balance && balance.hasAvailable;
const canSend = balance && balance.success && balance.hasAvailable;

const initializingSpinnerText =
(syncedAddressesCount !== undefined && syncedAddressesCount > 1) ? (
Expand All @@ -188,6 +188,7 @@ export function Account({
const exchangeBuySupported = supportedExchanges && supportedExchanges.exchanges.length > 0;

const isAccountEmpty = balance
&& balance.success
&& !balance.hasAvailable
&& !balance.hasIncoming
&& transactions
Expand Down Expand Up @@ -265,10 +266,10 @@ export function Account({
</div>
<AccountGuide
account={account}
unit={balance?.available.unit}
hasIncomingBalance={balance && balance.hasIncoming}
unit={balance?.success ? balance?.available.unit : undefined}
hasIncomingBalance={balance && balance.success && balance.hasIncoming}
hasTransactions={transactions !== undefined && transactions.success && transactions.list.length > 0}
hasNoBalance={balance && balance.available.amount === '0'} />
hasNoBalance={balance && balance.success && balance.available.amount === '0'} />
</div>
);
}
8 changes: 4 additions & 4 deletions frontends/web/src/routes/account/info/buyReceiveCTA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

import { useTranslation } from 'react-i18next';
import { route } from '../../../utils/route';
import { CoinWithSAT, IBalance } from '../../../api/account';
import { CoinWithSAT, TBalanceResult } from '../../../api/account';
import { Button } from '../../../components/forms';
import { Balances } from '../summary/accountssummary';
import styles from './buyReceiveCTA.module.css';
import { isBitcoinCoin } from '../utils';

type TBuyReceiveCTAProps = {
balanceList?: [string, IBalance][];
balanceList?: [string, TBalanceResult][];
code?: string;
unit?: string;
};
Expand Down Expand Up @@ -59,10 +59,10 @@ export const AddBuyReceiveOnEmptyBalances = ({ balances }: {balances?: Balances}
return null;
}
const balanceList = Object.entries(balances);
if (balanceList.some(entry => entry[1].hasAvailable)) {
if (balanceList.some(entry => !entry[1].success || entry[1].hasAvailable)) {
return null;
}
if (balanceList.map(entry => entry[1].available.unit).every(isBitcoinCoin)) {
if (balanceList.every(entry => entry[1].success && isBitcoinCoin(entry[1].available.unit))) {
return <BuyReceiveCTA code={balanceList[0][0]} unit={'BTC'} balanceList={balanceList} />;
}
return <BuyReceiveCTA balanceList={balanceList} />;
Expand Down
4 changes: 2 additions & 2 deletions frontends/web/src/routes/account/send/send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type Props = SendProps & TranslateProps;

interface State {
account?: accountApi.IAccount;
balance?: accountApi.IBalance;
balance?: accountApi.TBalanceResult;
proposedFee?: accountApi.IAmount;
proposedTotal?: accountApi.IAmount;
recipientAddress: string;
Expand Down Expand Up @@ -680,7 +680,7 @@ class Send extends Component<Props, State> {
type="number"
step="any"
min="0"
label={balance ? balance.available.unit : t('send.amount.label')}
label={balance && balance.success ? balance.available.unit : t('send.amount.label')}
id="amount"
onInput={this.handleFormChange}
disabled={sendAll}
Expand Down
13 changes: 9 additions & 4 deletions frontends/web/src/routes/account/summary/accountssummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface AccountSummaryProps {
}

export interface Balances {
[code: string]: accountApi.IBalance;
[code: string]: accountApi.TBalanceResult;
}

interface SyncStatus {
Expand Down Expand Up @@ -214,12 +214,17 @@ class AccountsSummary extends Component<Props, State> {
{ nameCol }
<td data-label={t('accountSummary.balance')}>
<span className={style.summaryTableBalance}>
{balance.available.amount}{' '}
<span className={style.coinUnit}>{balance.available.unit}</span>
{ balance.success ? (
<>
{balance.available.amount}{' '}
<span className={style.coinUnit}>{balance.available.unit}</span>
</>
) : <>{t('account.balanceError')}</>
}
</span>
</td>
<td data-label={t('accountSummary.fiatBalance')}>
<FiatConversion amount={balance.available} noAction={true} />
{ balance.success && <FiatConversion amount={balance.available} noAction={true} /> }
</td>
</tr>
);
Expand Down
26 changes: 15 additions & 11 deletions frontends/web/src/routes/accounts/select-receive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { getBalance, IAccount } from '../../api/account';
import { AccountSelector, TOption } from '../../components/accountselector/accountselector';
Expand All @@ -31,13 +31,26 @@ export const ReceiveAccountsSelector = ({ activeAccounts }: TReceiveAccountsSele
const [code, setCode] = useState('');
const { t } = useTranslation();

const getBalances = useCallback(async (options: TOption[]) => {
return Promise.all(options.map((option) => (
getBalance(option.value).then(balance => {
return {
...option,
balance: balance.success ?
`${balance.available.amount} ${balance.available.unit}` :
t('account.balanceError'),
};
})
)));
}, [t]);

useEffect(() => {
const options = activeAccounts.map(account => ({ label: account.name, value: account.code, disabled: false, coinCode: account.coinCode } as TOption));
//setting options without balance
setOptions(options);
//asynchronously fetching each account's balance
getBalances(options).then(options => setOptions(options));
}, [activeAccounts]);
}, [activeAccounts, getBalances]);

const handleProceed = () => {
route(`/account/${code}/receive`);
Expand All @@ -47,14 +60,6 @@ export const ReceiveAccountsSelector = ({ activeAccounts }: TReceiveAccountsSele

const title = t('receive.title', { accountName: hasOnlyBTCAccounts ? 'Bitcoin' : t('buy.info.crypto') });

const getBalances = async (options: TOption[]) => {
return Promise.all(options.map((option) => (
getBalance(option.value).then(balance => {
return { ...option, balance: `${balance.available.amount} ${balance.available.unit}` };
})
)));
};

return (
<>
<Header title={<h2>{title}</h2>} />
Expand All @@ -67,4 +72,3 @@ export const ReceiveAccountsSelector = ({ activeAccounts }: TReceiveAccountsSele

);
};

28 changes: 16 additions & 12 deletions frontends/web/src/routes/buy/info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ export const BuyInfo = ({ code, accounts }: TProps) => {

const { t } = useTranslation();

const getBalances = useCallback((options: TOption[]) => {
Promise.all(options.map((option) => (
getBalance(option.value).then(balance => {
return {
...option,
balance: balance.success ?
`${balance.available.amount} ${balance.available.unit}` :
t('account.balanceError'),
};
})
))).then(options => {
setOptions(options);
});
}, [t]);

const checkSupportedCoins = useCallback(async () => {
try {
const accountsWithFalsyValue = await Promise.all(
Expand All @@ -56,7 +71,7 @@ export const BuyInfo = ({ code, accounts }: TProps) => {
console.error(e);
}

}, [accounts]);
}, [accounts, getBalances]);

const maybeProceed = useCallback(() => {
if (options !== undefined && options.length === 1) {
Expand All @@ -76,17 +91,6 @@ export const BuyInfo = ({ code, accounts }: TProps) => {
maybeProceed();
}, [maybeProceed, options]);


const getBalances = (options: TOption[]) => {
Promise.all(options.map((option) => (
getBalance(option.value).then(balance => {
return { ...option, balance: `${balance.available.amount} ${balance.available.unit}` };
})
))).then(options => {
setOptions(options);
});
};

const handleProceed = () => {
route(`/buy/exchange/${selected}`);
};
Expand Down