Skip to content

Commit

Permalink
wallet: add deep clean and backup wdb to setting
Browse files Browse the repository at this point in the history
  • Loading branch information
chikeichan committed Mar 3, 2021
1 parent e0b7a08 commit c951e99
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 13,313 deletions.
2 changes: 2 additions & 0 deletions app/background/wallet/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const clientStub = ipcRendererInjector => makeClient(ipcRendererInjector,
'estimateMaxSend',
'removeWalletById',
'updateAccountDepth',
'backup',
'rescan',
'deepClean',
'reset',
'sendOpen',
'sendBid',
Expand Down
43 changes: 12 additions & 31 deletions app/background/wallet/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,38 +185,10 @@ class WalletService {
return res;
};

updateAccountDepth = async (changeDepth, receiveDepth) => {
if (!this.name) return null;

backup = async (path) => {
if (!path) throw new Error('path must not be undefined');
await this._ensureClient();
const wallet = await this.node.wdb.get(this.name);

if (!wallet) return null;

const account = await wallet.getAccount('default');
const initChange = 0;
const initReceive = 0;
const b = this.node.wdb.db.batch();

account.changeDepth = changeDepth;
account.receiveDepth = receiveDepth;
await account.save(b);

if (changeDepth) {
for (let i = initChange; i < changeDepth; i++) {
const key = account.deriveChange(i);
await account.saveKey(b, key);
}
}

if (receiveDepth) {
for (let j = initReceive; j < receiveDepth; j++) {
const key = account.deriveReceive(j);
await account.saveKey(b, key);
}
}

await b.write();
return this.client.execute('backupwallet', [path]);
};

rescan = async (height = 0) => {
Expand All @@ -232,6 +204,13 @@ class WalletService {
await wdb.rescan(height);
};

deepClean = async () => {
await this._ensureClient();
const wdb = this.node.wdb;

await wdb.deepClean();
};

importSeed = async (name, passphrase, mnemonic) => {
await this._ensureClient();
this.setWallet(name);
Expand Down Expand Up @@ -882,7 +861,9 @@ const methods = {
estimateMaxSend: service.estimateMaxSend,
removeWalletById: service.removeWalletById,
updateAccountDepth: service.updateAccountDepth,
backup: service.backup,
rescan: service.rescan,
deepClean: service.deepClean,
reset: service.reset,
sendOpen: service.sendOpen,
sendBid: service.sendBid,
Expand Down
5 changes: 5 additions & 0 deletions app/components/Alert/alert.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@
border: 1px solid $orange-red;
color: $orange-red;
}

&--warning {
border: 1px solid $manatee-gray;
color: $manatee-gray;
}
}
12 changes: 8 additions & 4 deletions app/components/Alert/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ export default class Alert extends Component {
static propTypes = {
type: PropTypes.oneOf([
'error',
'warning',
'success'
]).isRequired,
message: PropTypes.string.isRequired
message: PropTypes.string,
children: PropTypes.node,
};

render() {
if (!this.props.message) {
const { message, type, children} = this.props;

if (!message && !children) {
return null;
}

const name = `alert alert--${this.props.type}`;
const name = `alert alert--${type}`;

return (
<div className={name}>
{this.props.message}
{children || message}
</div>
);
}
Expand Down
32 changes: 5 additions & 27 deletions app/pages/Onboarding/ImportSeedFlow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const CREATE_PASSWORD = 'CREATE_PASSWORD';
const ENTRY_STEP = 'ENTRY';
const OPT_IN_ANALYTICS = 'ANALYTICS';
const SET_NAME = 'SET_NAME';
const UPDATE_ACCOUNT_DEPTH = 'UPDATE_ACCOUNT_DEPTH';

class ImportSeedFlow extends Component {
static propTypes = {
Expand All @@ -46,7 +45,6 @@ class ImportSeedFlow extends Component {
passphrase: '',
mnemonic: '',
isLoading: false,
accountDepth: 200,
};

render() {
Expand Down Expand Up @@ -103,38 +101,23 @@ class ImportSeedFlow extends Component {
return (
<ImportSeedEnterMnemonic
currentStep={3}
totalSteps={5}
totalSteps={4}
onBack={() => this.goTo(CREATE_PASSWORD)}
onNext={(mnemonic) => {
this.setState({
mnemonic,
});
this.goTo(UPDATE_ACCOUNT_DEPTH);
}}
onCancel={() => this.props.history.push('/funding-options')}
/>
);
case UPDATE_ACCOUNT_DEPTH:
return (
<UpdateAccountDepth
currentStep={4}
totalSteps={5}
onBack={() => this.goTo(ENTRY_STEP)}
onNext={(accountDepth) => {
this.setState({
accountDepth,
});
this.goTo(OPT_IN_ANALYTICS);
}}
onCancel={() => this.props.history.push('/funding-options')}
/>
)
);
case OPT_IN_ANALYTICS:
return (
<OptInAnalytics
currentStep={5}
totalSteps={5}
onBack={() => this.goTo(UPDATE_ACCOUNT_DEPTH)}
currentStep={4}
totalSteps={4}
onBack={() => this.setState({currentStep: ENTRY_STEP})}
onNext={async (optInState) => {
await analytics.setOptIn(optInState);
await this.finishFlow(this.state.mnemonic);
Expand All @@ -153,14 +136,9 @@ class ImportSeedFlow extends Component {
}

finishFlow = async mnemonic => {
const {accountDepth} = this.state;
this.setState({isLoading: true});
try {
await walletClient.importSeed(this.state.name, this.state.passphrase, mnemonic);
if (accountDepth > 200) {

await walletClient.updateAccountDepth(accountDepth, accountDepth);
}
walletClient.rescan(0);
await this.props.completeInitialization(this.state.name, this.state.passphrase);
await this.props.fetchWallet();
Expand Down
55 changes: 55 additions & 0 deletions app/pages/Settings/DeepCleanAndRescanModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, {Component} from "react";
import MiniModal from "../../components/Modal/MiniModal";
import {Route, withRouter} from "react-router-dom";
import Checkbox from "../../components/Checkbox";
import Anchor from "../../components/Anchor";
import Alert from "../../components/Alert";

@withRouter
export default class DeepCleanAndRescanModal extends Component {
constructor(props) {
super(props);
this.state = {
hasBackup: false,
}
}

render() {
const { hasBackup } = this.state;
return (
<MiniModal
closeRoute="/settings/wallet"
title="Deep Clean + Rescan"
centered
>
<Alert
type="warning"
>
This action wipes out balance and transaction history in the wallet DB but retains key hashes and name maps. It should be used only if the wallet state has been corrupted by issues like the <Anchor href="https://github.com/handshake-org/hsd/issues/454">reserved name registration bug</Anchor> or the <Anchor href="https://github.com/handshake-org/hsd/pull/464">locked coins balance after FINALIZE bug</Anchor>.
</Alert>
<div className="interstitial-warning-modal__checkbox">
<Checkbox
className="interstitial-warning-modal__checkbox-box"
onChange={() => this.setState({ hasBackup: !hasBackup })}
checked={hasBackup}
/>
<div className="interstitial-warning-modal__checkbox-label">
I have my recovery seed phrase backed up.
</div>
</div>
<button
className="settings__btn"
onClick={async () => {
if (!hasBackup) return;
await walletClient.deepClean();
walletClient.rescan(0);
this.props.history.push('/settings/wallet');
}}
disabled={!hasBackup}
>
Deep Clean and Rescan
</button>
</MiniModal>
);
}
}
89 changes: 49 additions & 40 deletions app/pages/Settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import {fetchWalletAPIKey} from "../../ducks/walletActions";
import Anchor from "../../components/Anchor";
import walletClient from "../../utils/walletClient";
import InterstitialWarningModal from "./InterstitialWarningModal";
import DeepCleanAndRescanModal from "./DeepCleanAndRescanModal";
import {showError, showSuccess} from "../../ducks/notifications";
const {dialog} = require('electron').remote;

const analytics = aClientStub(() => require('electron').ipcRenderer);

Expand All @@ -50,13 +53,17 @@ const analytics = aClientStub(() => require('electron').ipcRenderer);
startNode: () => dispatch(nodeActions.start()),
setCustomRPCStatus: isConnected => dispatch(setCustomRPCStatus(isConnected)),
fetchWalletAPIKey: () => dispatch(fetchWalletAPIKey()),
showError: (message) => dispatch(showError(message)),
showSuccess: (message) => dispatch(showSuccess(message)),
}),
)
export default class Settings extends Component {
static propTypes = {
network: PropTypes.string.isRequired,
apiKey: PropTypes.string.isRequired,
wid: PropTypes.string.isRequired,
changeDepth: PropTypes.number.isRequired,
receiveDepth: PropTypes.number.isRequired,
walletApiKey: PropTypes.string.isRequired,
isRunning: PropTypes.bool.isRequired,
walletSync: PropTypes.bool.isRequired,
Expand All @@ -69,6 +76,15 @@ export default class Settings extends Component {
transactions: PropTypes.object.isRequired,
};

constructor(props) {
super(props);
this.state = {
changeDepth: props.changeDepth,
receiveDepth: props.receiveDepth,
isUpdatingDepth: false,
}
}

componentDidMount() {
analytics.screenView('Settings');
this.props.fetchWalletAPIKey();
Expand All @@ -94,6 +110,20 @@ export default class Settings extends Component {
}
};

onBackupWDB = async () => {
try {
let savePath = dialog.showOpenDialogSync({
properties: ["openDirectory", "promptToCreate", "createDirectory"],
});

await walletClient.backup(savePath[0]);
this.props.showSuccess(`WalletDB backup successfully`);
} catch (e) {
this.props.showError(e.message);
}

};

renderNav() {
const { history, location } = this.props;

Expand Down Expand Up @@ -195,7 +225,22 @@ export default class Settings extends Component {
null,
walletSync,
)}

{this.renderSection(
'Backup WalletDB',
<div>
<div>{`Back up wallet database and files to a directory.`}</div>
</div>,
'Backup WalletDB',
this.onBackupWDB,
)}
{this.renderSection(
'Deep Clean and Rescan Wallet',
<div>
<div>For more information on deep clean, please see <Anchor href="https://github.com/handshake-org/hsd/blob/master/CHANGELOG.md#wallet-api-changes-1">here</Anchor></div>
</div>,
'Deep Clean + Rescan',
() => history.push('/settings/wallet/deep-clean-and-rescan'),
)}
{this.renderSection(
'API Key',
<span>
Expand Down Expand Up @@ -387,47 +432,11 @@ export default class Settings extends Component {
</button>
</MiniModal>
</Route>
<Route path="/settings/wallet/deep-clean-and-rescan">
<DeepCleanAndRescanModal />
</Route>
</Switch>
</ContentArea>
);
}
}

function SettingSection(props) {
const {
disabled = false,
title = '',
description = '',
children,
cta,
onClick,
} = props;
return (
<div
className={c("settings__content__section", {
'settings__content__section--disabled': disabled,
})}
>
<div className="settings__content__section__info">
<div className="settings__content__section__info__title">
{title}
</div>
<div className="settings__content__section__info__description">
{description}
</div>
</div>
<div className="settings__content__section__footer">
{children}
{cta && (
<button
className="settings__content__section__cta"
onClick={onClick}
disabled={disabled}
>
{cta}
</button>
)}
</div>
</div>
);
}
Loading

0 comments on commit c951e99

Please sign in to comment.