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

Recovery status #2217

Merged
merged 1 commit into from
May 31, 2024
Merged
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
5 changes: 5 additions & 0 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import EditFee from './views/EditFee';
import Seed from './views/Settings/Seed';
import SeedRecovery from './views/Settings/SeedRecovery';
import Sync from './views/Sync';
import SyncRecovery from './views/SyncRecovery';
import LspExplanationFees from './views/Explanations/LspExplanationFees';
import LspExplanationRouting from './views/Explanations/LspExplanationRouting';
import LspExplanationWrappedInvoices from './views/Explanations/LspExplanationWrappedInvoices';
Expand Down Expand Up @@ -603,6 +604,10 @@ export default class App extends React.PureComponent {
name="Sync"
component={Sync}
/>
<Stack.Screen
name="SyncRecovery"
component={SyncRecovery}
/>
<Stack.Screen
name="BumpFee"
component={BumpFee}
Expand Down
2 changes: 2 additions & 0 deletions backends/EmbeddedLND.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const {
sendKeysendPaymentV2,
listPayments,
getNetworkInfo,
getRecoveryInfo,
queryRoutes,
lookupInvoice,
fundingStateStep,
Expand Down Expand Up @@ -75,6 +76,7 @@ export default class EmbeddedLND extends LND {
subscribeCustomMessages = async () => await subscribeCustomMessages();
getMyNodeInfo = async () => await getInfo();
getNetworkInfo = async () => await getNetworkInfo();
getRecoveryInfo = async () => await getRecoveryInfo();
getInvoices = async () => await listInvoices();
createInvoice = async (data: any) =>
await addInvoice({
Expand Down
4 changes: 4 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@
"views.OpenChannel.removeAdditionalChannel": "Remove additional channel",
"views.Wallet.BalancePane.sync.title": "Finishing sync",
"views.Wallet.BalancePane.sync.text": "Hang on tight! You will be ready to use Zeus soon.",
"views.Wallet.BalancePane.recovery.title": "Recovery mode",
"views.Wallet.BalancePane.recovery.text": "Please leave ZEUS open until the process completes.",
"views.Wallet.BalancePane.recovery.textAlt": "Leave ZEUS open until completion.",
"views.Wallet.BalancePane.backup.title": "Back up your funds",
"views.Wallet.BalancePane.backup.text": "Create a backup to never lose access to your bitcoin.",
"views.Wallet.BalancePane.backup.action": "Start backup ->",
Expand Down Expand Up @@ -949,6 +952,7 @@
"views.Sync.tip": "Tip",
"views.Sync.numBlocksUntilSynced": "Number of blocks until synced",
"views.LSPS1.pubkeyAndHostNotFound": "Node pubkey and host are not set",
"views.SyncRecovery.title": "Recovering wallet",
"views.LSPS1.timeoutError": "Did not receive response from server",
"views.LSPS1.channelExpiryBlocks": "Channel Expiry Blocks",
"views.LSPS1.maxChannelExpiryBlocks": "Max channel Expiry Blocks",
Expand Down
41 changes: 41 additions & 0 deletions stores/SyncStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import SettingsStore from './SettingsStore';

export default class SyncStore {
@observable public isSyncing: boolean = false;
@observable public isRecovering: boolean = false;
@observable public recoveryProgress: number | null;
@observable public isInExpressGraphSync: boolean = false;
@observable public bestBlockHeight: number;
@observable public currentBlockHeight: number;
Expand All @@ -26,6 +28,7 @@ export default class SyncStore {
@action
public reset = () => {
this.isSyncing = false;
this.isRecovering = false;
this.isInExpressGraphSync = false;
this.error = false;
};
Expand Down Expand Up @@ -137,4 +140,42 @@ export default class SyncStore {
if (queryMempool) this.getBestBlockHeight();
}
};

@action
public checkRecoveryStatus = () => {
BackendUtils.getRecoveryInfo().then((data: any) => {
if (data.recovery_mode && !data.recovery_finished) {
this.startRecovering();
}
});
};

@action
public getRecoveryStatus = async () => {
await BackendUtils.getRecoveryInfo().then((data: any) => {
if (data.recovery_mode) {
if (data.progress) {
this.recoveryProgress = data.progress;
}
if (data.recovery_finished) {
this.isRecovering = false;
this.recoveryProgress = null;
}
} else {
this.isRecovering = false;
this.recoveryProgress = null;
}
return data;
});
};

@action
public startRecovering = async () => {
this.isRecovering = true;

while (this.isRecovering) {
await sleep(2000);
await this.getRecoveryStatus();
}
};
}
1 change: 1 addition & 0 deletions utils/BackendUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class BackendUtils {
this.call('subscribeCustomMessages', args);
getMyNodeInfo = (...args: any[]) => this.call('getMyNodeInfo', args);
getNetworkInfo = (...args: any[]) => this.call('getNetworkInfo', args);
getRecoveryInfo = (...args: any[]) => this.call('getRecoveryInfo', args);
getInvoices = (...args: any[]) => this.call('getInvoices', args);
createInvoice = (...args: any[]) => this.call('createInvoice', args);
getPayments = (...args: any[]) => this.call('getPayments', args);
Expand Down
92 changes: 92 additions & 0 deletions views/SyncRecovery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React from 'react';
import { Dimensions, View } from 'react-native';
import { inject, observer } from 'mobx-react';
import CircularProgress from 'react-native-circular-progress-indicator';
import { StackNavigationProp } from '@react-navigation/stack';

import Button from '../components/Button';
import Screen from '../components/Screen';
import Header from '../components/Header';

import SyncStore from '../stores/SyncStore';

import { localeString } from '../utils/LocaleUtils';
import { themeColor } from '../utils/ThemeUtils';

interface SyncRecoveryProps {
navigation: StackNavigationProp<any, any>;
SyncStore: SyncStore;
}

@inject('SyncStore')
@observer
export default class SyncRecovery extends React.PureComponent<
SyncRecoveryProps,
{}
> {
render() {
const { navigation, SyncStore } = this.props;
const { recoveryProgress } = SyncStore;

const { width } = Dimensions.get('window');

return (
<Screen>
<Header
leftComponent="Back"
centerComponent={{
text: localeString('views.SyncRecovery.title'),
style: {
color: themeColor('text'),
fontFamily: 'PPNeueMontreal-Book'
}
}}
navigation={navigation}
/>
<View style={{ flex: 1, justifyContent: 'center' }}>
<View style={{ alignItems: 'center', marginBottom: 40 }}>
<CircularProgress
value={
recoveryProgress
? Number(
Math.floor(recoveryProgress * 1000) /
1000
) * 100
: 0
}
radius={width / 3}
inActiveStrokeOpacity={0.5}
activeStrokeWidth={width / 20}
inActiveStrokeWidth={width / 40}
progressValueStyle={{
fontWeight: '100',
color: 'white'
}}
activeStrokeColor={themeColor('highlight')}
activeStrokeSecondaryColor={themeColor('error')}
inActiveStrokeColor={themeColor(
'secondaryBackground'
)}
duration={500}
dashedStrokeConfig={{
count: 50,
width: 4
}}
progressFormatter={(value: number) => {
'worklet';
return value.toFixed && value.toFixed(1); // 1 decimal place
}}
valueSuffix="%"
/>
</View>
</View>
<View style={{ bottom: 15 }}>
<Button
title={localeString('general.goBack')}
onPress={() => navigation.goBack()}
/>
</View>
</Screen>
);
}
}
109 changes: 106 additions & 3 deletions views/Wallet/BalancePane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import NodeInfoStore from '../../stores/NodeInfoStore';
import SettingsStore from '../../stores/SettingsStore';
import SyncStore from '../../stores/SyncStore';

import { version, playStore } from '../../package.json';
import { version } from '../../package.json';

import LockIcon from '../../assets/images/SVG/Lock.svg';

Expand Down Expand Up @@ -69,7 +69,13 @@ export default class BalancePane extends React.PureComponent<
pendingOpenBalance
} = BalanceStore;
const { implementation } = SettingsStore;
const { currentBlockHeight, bestBlockHeight, isSyncing } = SyncStore;
const {
currentBlockHeight,
bestBlockHeight,
recoveryProgress,
isSyncing,
isRecovering
} = SyncStore;

const pendingUnconfirmedBalance = new BigNumber(pendingOpenBalance)
.plus(unconfirmedBlockchainBalance)
Expand Down Expand Up @@ -162,6 +168,103 @@ export default class BalancePane extends React.PureComponent<
marginBottom: 20
}}
>
{isRecovering && recoveryProgress !== 1 && (
<TouchableOpacity
onPress={() => {
if (recoveryProgress) {
navigation.navigate('SyncRecovery');
}
}}
>
<View
style={{
backgroundColor:
themeColor('highlight'),
borderRadius: 10,
margin: 20,
marginBottom: 0,
padding: 15,
borderWidth: 0.5
}}
>
<Text
style={{
fontFamily: 'PPNeueMontreal-Medium',
color: themeColor('background')
}}
>
{`${localeString(
'views.Wallet.BalancePane.recovery.title'
)}${
!recoveryProgress
? ` - ${localeString(
'views.Wallet.BalancePane.recovery.textAlt'
).replace('Zeus', 'ZEUS')}`
: ''
}`}
</Text>
{recoveryProgress && (
<Text
style={{
fontFamily:
'PPNeueMontreal-Book',
color: themeColor('background'),
marginTop: 20
}}
>
{localeString(
'views.Wallet.BalancePane.recovery.text'
).replace('Zeus', 'ZEUS')}
</Text>
)}
{recoveryProgress && (
<View
style={{
marginTop: 30,
flex: 1,
flexDirection: 'row',
display: 'flex',
justifyContent: 'space-between',
minWidth: '100%'
}}
>
<LinearProgress
value={
Math.floor(
recoveryProgress * 100
) / 100
}
variant="determinate"
color={themeColor('background')}
trackColor={themeColor(
'secondaryBackground'
)}
style={{
flex: 1,
flexDirection: 'row'
}}
/>
<Text
style={{
fontFamily:
'PPNeueMontreal-Medium',
color: themeColor(
'background'
),
marginTop: -8,
marginLeft: 14,
height: 40
}}
>
{`${Math.floor(
recoveryProgress * 100
).toString()}%`}
</Text>
</View>
)}
</View>
</TouchableOpacity>
)}
{isSyncing && (
<TouchableOpacity
onPress={() => navigation.navigate('Sync')}
Expand Down Expand Up @@ -379,7 +482,7 @@ export default class BalancePane extends React.PureComponent<
marginBottom: -40
}}
>
{playStore ? `v${version}-play` : `v${version}`}
{`v${version}`}
</Text>
</View>
);
Expand Down
12 changes: 12 additions & 0 deletions views/Wallet/Wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { inject, observer } from 'mobx-react';
import RNRestart from 'react-native-restart';
import { StackNavigationProp } from '@react-navigation/stack';
import SystemNavigationBar from 'react-native-system-navigation-bar';
import EncryptedStorage from 'react-native-encrypted-storage';

import ChannelsPane from '../Channels/ChannelsPane';
import BalancePane from './BalancePane';
Expand Down Expand Up @@ -387,6 +388,8 @@ export default class Wallet extends React.Component<WalletProps, WalletState> {
embeddedLndNetwork === 'Testnet'
);
}
if (implementation === 'embedded-lnd')
SyncStore.checkRecoveryStatus();
await NodeInfoStore.getNodeInfo();
NodeInfoStore.getNetworkInfo();
if (BackendUtils.supportsAccounts()) UTXOsStore.listAccounts();
Expand All @@ -399,6 +402,15 @@ export default class Wallet extends React.Component<WalletProps, WalletState> {
});
}
if (recovery) {
const isBackedUp = await EncryptedStorage.getItem(
'backup-complete'
);
if (!isBackedUp) {
await EncryptedStorage.setItem(
'backup-complete',
JSON.stringify(true)
);
}
if (isSyncing) return;
try {
await ChannelBackupStore.recoverStaticChannelBackup();
Expand Down
Loading