Skip to content

Commit 14927eb

Browse files
committed
backend/btc: do not panic when retrieving balance
Similar to 52da637, where the same as done for the transactions list. Fixes one panic TODO. All call sites deal with the error now. An error here is usually a database access for a database that was already closed (e.g. transactions endpoint called at the same time as a bitbox02 is unplugged or the account is closed for some other reason).
1 parent 4fbb3b3 commit 14927eb

File tree

12 files changed

+85
-58
lines changed

12 files changed

+85
-58
lines changed

backend/coins/btc/account.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -544,12 +544,7 @@ func (account *Account) Balance() (*accounts.Balance, error) {
544544
if account.fatalError.Load() {
545545
return nil, errp.New("can't call Balance() after a fatal error")
546546
}
547-
balance, err := account.transactions.Balance()
548-
if err != nil {
549-
// TODO
550-
panic(err)
551-
}
552-
return balance, nil
547+
return account.transactions.Balance()
553548
}
554549

555550
func (account *Account) incAndEmitSyncCounter() {

backend/coins/btc/handlers/handlers.go

+15-7
Original file line numberDiff line numberDiff line change
@@ -326,16 +326,24 @@ func (handlers *Handlers) getUTXOs(_ *http.Request) (interface{}, error) {
326326
}
327327

328328
func (handlers *Handlers) getAccountBalance(_ *http.Request) (interface{}, error) {
329+
var result struct {
330+
Success bool `json:"success"`
331+
HasAvailable bool `json:"hasAvailable"`
332+
Available FormattedAmount `json:"available"`
333+
HasIncoming bool `json:"hasIncoming"`
334+
Incoming FormattedAmount `json:"incoming"`
335+
}
329336
balance, err := handlers.account.Balance()
330337
if err != nil {
331-
return nil, err
338+
handlers.log.WithError(err).Error("Error getting account balance")
339+
return result, nil
332340
}
333-
return map[string]interface{}{
334-
"hasAvailable": balance.Available().BigInt().Sign() > 0,
335-
"available": handlers.formatAmountAsJSON(balance.Available(), false),
336-
"hasIncoming": balance.Incoming().BigInt().Sign() > 0,
337-
"incoming": handlers.formatAmountAsJSON(balance.Incoming(), false),
338-
}, nil
341+
result.Success = true
342+
result.HasAvailable = balance.Available().BigInt().Sign() > 0
343+
result.Available = handlers.formatAmountAsJSON(balance.Available(), false)
344+
result.HasIncoming = balance.Incoming().BigInt().Sign() > 0
345+
result.Incoming = handlers.formatAmountAsJSON(balance.Incoming(), false)
346+
return result, nil
339347
}
340348

341349
type sendTxInput struct {

frontends/web/src/api/account.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

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

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

154-
export const getBalance = (code: AccountCode): Promise<IBalance> => {
155+
export type TBalanceResult = { success: false } | (SuccessResponse & IBalance);
156+
157+
export const getBalance = (code: AccountCode): Promise<TBalanceResult> => {
155158
return apiGet(`account/${code}/balance`);
156159
};
157160

frontends/web/src/components/balance/balance.test.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
*/
1616

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

2222
describe('components/balance/balance', () => {
2323
it('renders balance properly', () => {
24-
const MOCK_BALANCE: IBalance = {
24+
const MOCK_BALANCE: TBalanceResult = {
25+
success: true,
2526
hasAvailable: true,
2627
hasIncoming: true,
2728
available: {

frontends/web/src/components/balance/balance.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
*/
1717

1818
import { useTranslation } from 'react-i18next';
19-
import { IBalance } from '../../api/account';
19+
import { TBalanceResult } from '../../api/account';
2020
import { FiatConversion } from '../../components/rates/rates';
2121
import { bitcoinRemoveTrailingZeroes } from '../../utils/trailing-zeroes';
2222
import style from './balance.module.css';
2323

2424
type TProps = {
25-
balance?: IBalance;
25+
balance?: TBalanceResult;
2626
noRotateFiat?: boolean;
2727
}
2828

@@ -36,6 +36,11 @@ export const Balance = ({
3636
<header className={style.balance}></header>
3737
);
3838
}
39+
if (!balance.success) {
40+
return (
41+
<header className={style.balance}>{t('account.balanceError')}</header>
42+
);
43+
}
3944

4045
// remove trailing zeroes from Bitcoin balance
4146
const availableBalance = bitcoinRemoveTrailingZeroes(balance.available.amount, balance.available.unit);

frontends/web/src/locales/en/app.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"initializing": "Getting information from the blockchain…",
99
"maybeProxyError": "Tor proxy enabled. Ensure that your Tor proxy is running properly, or disable the proxy setting.",
1010
"reconnecting": "Lost connection, trying to reconnect…",
11-
"syncedAddressesCount": "Scanned {{count}} addresses"
11+
"syncedAddressesCount": "Scanned {{count}} addresses",
12+
"balanceError": "Error retrieving balance"
1213
},
1314
"accountInfo": {
1415
"address": "Address",
@@ -1446,4 +1447,4 @@
14461447
"insertDevice": "Please connect your device to get started",
14471448
"title": "Welcome"
14481449
}
1449-
}
1450+
}

frontends/web/src/routes/account/account.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function Account({
5454
}: Props) {
5555
const { t } = useTranslation();
5656

57-
const [balance, setBalance] = useState<accountApi.IBalance>();
57+
const [balance, setBalance] = useState<accountApi.TBalanceResult>();
5858
const [status, setStatus] = useState<accountApi.IStatus>();
5959
const [syncedAddressesCount, setSyncedAddressesCount] = useState<number>();
6060
const [transactions, setTransactions] = useState<accountApi.TTransactions>();
@@ -166,7 +166,7 @@ export function Account({
166166
return null;
167167
}
168168

169-
const canSend = balance && balance.hasAvailable;
169+
const canSend = balance && balance.success && balance.hasAvailable;
170170

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

190190
const isAccountEmpty = balance
191+
&& balance.success
191192
&& !balance.hasAvailable
192193
&& !balance.hasIncoming
193194
&& transactions
@@ -265,10 +266,10 @@ export function Account({
265266
</div>
266267
<AccountGuide
267268
account={account}
268-
unit={balance?.available.unit}
269-
hasIncomingBalance={balance && balance.hasIncoming}
269+
unit={balance?.success ? balance?.available.unit : undefined}
270+
hasIncomingBalance={balance && balance.success && balance.hasIncoming}
270271
hasTransactions={transactions !== undefined && transactions.success && transactions.list.length > 0}
271-
hasNoBalance={balance && balance.available.amount === '0'} />
272+
hasNoBalance={balance && balance.success && balance.available.amount === '0'} />
272273
</div>
273274
);
274275
}

frontends/web/src/routes/account/info/buyReceiveCTA.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
import { useTranslation } from 'react-i18next';
1818
import { route } from '../../../utils/route';
19-
import { CoinWithSAT, IBalance } from '../../../api/account';
19+
import { CoinWithSAT, TBalanceResult } from '../../../api/account';
2020
import { Button } from '../../../components/forms';
2121
import { Balances } from '../summary/accountssummary';
2222
import styles from './buyReceiveCTA.module.css';
2323
import { isBitcoinCoin } from '../utils';
2424

2525
type TBuyReceiveCTAProps = {
26-
balanceList?: [string, IBalance][];
26+
balanceList?: [string, TBalanceResult][];
2727
code?: string;
2828
unit?: string;
2929
};
@@ -59,10 +59,10 @@ export const AddBuyReceiveOnEmptyBalances = ({ balances }: {balances?: Balances}
5959
return null;
6060
}
6161
const balanceList = Object.entries(balances);
62-
if (balanceList.some(entry => entry[1].hasAvailable)) {
62+
if (balanceList.some(entry => entry[1].success && entry[1].hasAvailable)) {
6363
return null;
6464
}
65-
if (balanceList.map(entry => entry[1].available.unit).every(isBitcoinCoin)) {
65+
if (balanceList.every(entry => entry[1].success && isBitcoinCoin(entry[1].available.unit))) {
6666
return <BuyReceiveCTA code={balanceList[0][0]} unit={'BTC'} balanceList={balanceList} />;
6767
}
6868
return <BuyReceiveCTA balanceList={balanceList} />;

frontends/web/src/routes/account/send/send.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type Props = SendProps & TranslateProps;
6262

6363
interface State {
6464
account?: accountApi.IAccount;
65-
balance?: accountApi.IBalance;
65+
balance?: accountApi.TBalanceResult;
6666
proposedFee?: accountApi.IAmount;
6767
proposedTotal?: accountApi.IAmount;
6868
recipientAddress: string;
@@ -680,7 +680,7 @@ class Send extends Component<Props, State> {
680680
type="number"
681681
step="any"
682682
min="0"
683-
label={balance ? balance.available.unit : t('send.amount.label')}
683+
label={balance && balance.success ? balance.available.unit : t('send.amount.label')}
684684
id="amount"
685685
onInput={this.handleFormChange}
686686
disabled={sendAll}

frontends/web/src/routes/account/summary/accountssummary.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface AccountSummaryProps {
4040
}
4141

4242
export interface Balances {
43-
[code: string]: accountApi.IBalance;
43+
[code: string]: accountApi.TBalanceResult;
4444
}
4545

4646
interface SyncStatus {
@@ -214,12 +214,17 @@ class AccountsSummary extends Component<Props, State> {
214214
{ nameCol }
215215
<td data-label={t('accountSummary.balance')}>
216216
<span className={style.summaryTableBalance}>
217-
{balance.available.amount}{' '}
218-
<span className={style.coinUnit}>{balance.available.unit}</span>
217+
{ balance.success ? (
218+
<>
219+
{balance.available.amount}{' '}
220+
<span className={style.coinUnit}>{balance.available.unit}</span>
221+
</>
222+
) : <>{t('account.balanceError')}</>
223+
}
219224
</span>
220225
</td>
221226
<td data-label={t('accountSummary.fiatBalance')}>
222-
<FiatConversion amount={balance.available} noAction={true} />
227+
{ balance.success && <FiatConversion amount={balance.available} noAction={true} /> }
223228
</td>
224229
</tr>
225230
);

frontends/web/src/routes/accounts/select-receive.tsx

+15-11
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

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

34+
const getBalances = useCallback(async (options: TOption[]) => {
35+
return Promise.all(options.map((option) => (
36+
getBalance(option.value).then(balance => {
37+
return {
38+
...option,
39+
balance: balance.success ?
40+
`${balance.available.amount} ${balance.available.unit}` :
41+
t('account.balanceError'),
42+
};
43+
})
44+
)));
45+
}, [t]);
46+
3447
useEffect(() => {
3548
const options = activeAccounts.map(account => ({ label: account.name, value: account.code, disabled: false, coinCode: account.coinCode } as TOption));
3649
//setting options without balance
3750
setOptions(options);
3851
//asynchronously fetching each account's balance
3952
getBalances(options).then(options => setOptions(options));
40-
}, [activeAccounts]);
53+
}, [activeAccounts, getBalances]);
4154

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

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

50-
const getBalances = async (options: TOption[]) => {
51-
return Promise.all(options.map((option) => (
52-
getBalance(option.value).then(balance => {
53-
return { ...option, balance: `${balance.available.amount} ${balance.available.unit}` };
54-
})
55-
)));
56-
};
57-
5863
return (
5964
<>
6065
<Header title={<h2>{title}</h2>} />
@@ -67,4 +72,3 @@ export const ReceiveAccountsSelector = ({ activeAccounts }: TReceiveAccountsSele
6772

6873
);
6974
};
70-

frontends/web/src/routes/buy/info.tsx

+16-12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ export const BuyInfo = ({ code, accounts }: TProps) => {
3838

3939
const { t } = useTranslation();
4040

41+
const getBalances = useCallback((options: TOption[]) => {
42+
Promise.all(options.map((option) => (
43+
getBalance(option.value).then(balance => {
44+
return {
45+
...option,
46+
balance: balance.success ?
47+
`${balance.available.amount} ${balance.available.unit}` :
48+
t('account.balanceError'),
49+
};
50+
})
51+
))).then(options => {
52+
setOptions(options);
53+
});
54+
}, [t]);
55+
4156
const checkSupportedCoins = useCallback(async () => {
4257
try {
4358
const accountsWithFalsyValue = await Promise.all(
@@ -56,7 +71,7 @@ export const BuyInfo = ({ code, accounts }: TProps) => {
5671
console.error(e);
5772
}
5873

59-
}, [accounts]);
74+
}, [accounts, getBalances]);
6075

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

79-
80-
const getBalances = (options: TOption[]) => {
81-
Promise.all(options.map((option) => (
82-
getBalance(option.value).then(balance => {
83-
return { ...option, balance: `${balance.available.amount} ${balance.available.unit}` };
84-
})
85-
))).then(options => {
86-
setOptions(options);
87-
});
88-
};
89-
9094
const handleProceed = () => {
9195
route(`/buy/exchange/${selected}`);
9296
};

0 commit comments

Comments
 (0)