Skip to content

Commit

Permalink
add wallet-recover command
Browse files Browse the repository at this point in the history
- it will rescan the blokchain on wallet creation
- wallet-create will only scan forward from the current block height
  • Loading branch information
OBorce committed Aug 13, 2024
1 parent 2bfa44c commit 1cd8110
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 7 deletions.
1 change: 0 additions & 1 deletion node-gui/src/main_window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,6 @@ impl MainWindow {

ActiveDialog::WalletRecover { wallet_type } => {
let wallet_type = *wallet_type;
// FIXME
match wallet_type {
WalletType::Hot | WalletType::Cold => wallet_mnemonic_dialog(
None,
Expand Down
2 changes: 1 addition & 1 deletion test/functional/test_framework/wallet_cli_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ async def open_wallet(self, name: str, password: Optional[str] = None, force_cha

async def recover_wallet(self, mnemonic: str, name: str = "recovered_wallet") -> str:
wallet_file = os.path.join(self.node.datadir, name)
return await self._write_command(f"wallet-create \"{wallet_file}\" store-seed-phrase \"{mnemonic}\"\n")
return await self._write_command(f"wallet-recover \"{wallet_file}\" store-seed-phrase \"{mnemonic}\"\n")

async def close_wallet(self) -> str:
return await self._write_command("wallet-close\n")
Expand Down
56 changes: 56 additions & 0 deletions wallet/wallet-cli-commands/src/command_handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,62 @@ where
})
}

WalletManagementCommand::RecoverWallet {
wallet_path,
mnemonic,
whether_to_store_seed_phrase,
passphrase,
hardware_wallet,
} => {
let hardware_wallet = hardware_wallet.and_then(|t| match t {
#[cfg(feature = "trezor")]
CLIHardwareWalletType::Trezor => Some(HardwareWalletType::Trezor),
CLIHardwareWalletType::None => None,
});

let newly_generated_mnemonic = self
.wallet()
.await?
.recover_wallet(
wallet_path,
whether_to_store_seed_phrase.to_bool(),
mnemonic,
passphrase,
hardware_wallet,
)
.await?;

self.wallet.update_wallet::<N>().await;

let msg = match newly_generated_mnemonic.mnemonic {
MnemonicInfo::NewlyGenerated {
mnemonic,
passphrase,
} => {
let passphrase = if let Some(passphrase) = passphrase {
format!("passphrase: {passphrase}\n")
} else {
String::new()
};
format!(
"New wallet created successfully\nYour mnemonic: {}\n{passphrase}\
Please write it somewhere safe to be able to restore your wallet. \
It's recommended that you attempt to recover the wallet now as practice\
to check that you arrive at the same addresses, \
to ensure that you have done everything correctly.
",
mnemonic
)
}
MnemonicInfo::UserProvided => "New wallet created successfully".to_owned(),
};

Ok(ConsoleCommand::SetStatus {
status: self.repl_status().await?,
print_message: msg,
})
}

WalletManagementCommand::OpenWallet {
wallet_path,
encryption_password,
Expand Down
25 changes: 25 additions & 0 deletions wallet/wallet-cli-commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@ pub enum WalletManagementCommand {
hardware_wallet: Option<CLIHardwareWalletType>,
},

#[clap(name = "wallet-recover")]
RecoverWallet {
/// File path of the wallet file
wallet_path: PathBuf,

/// If 'store-seed-phrase', the seed-phrase will be stored in the wallet file.
/// If 'do-not-store-seed-phrase', the seed-phrase will only be printed on the screen.
/// Not storing the seed-phrase can be seen as a security measure
/// to ensure sufficient secrecy in case that seed-phrase is reused
/// elsewhere if this wallet is compromised.
whether_to_store_seed_phrase: CliStoreSeedPhrase,

/// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). If not specified, a new mnemonic phrase is generated and printed.
mnemonic: Option<String>,

/// Passphrase along the mnemonic
#[arg(long = "passphrase")]
passphrase: Option<String>,

/// Create a wallet using a connected hardware wallet. Only the public keys will be kept in
/// the software wallet
#[arg(long, conflicts_with_all(["mnemonic", "passphrase"]))]
hardware_wallet: Option<CLIHardwareWalletType>,
},

#[clap(name = "wallet-open")]
OpenWallet {
/// File path of the wallet file
Expand Down
46 changes: 45 additions & 1 deletion wallet/wallet-rpc-client/src/handles_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,51 @@ where
}
};

let scan_blockchain = args.user_supplied_menmonic();
let scan_blockchain = false;
self.wallet_rpc
.create_wallet(path, args, false, scan_blockchain)
.await
.map(Into::into)
.map_err(WalletRpcHandlesClientError::WalletRpcError)
}

async fn recover_wallet(
&self,
path: PathBuf,
store_seed_phrase: bool,
mnemonic: Option<String>,
passphrase: Option<String>,
hardware_wallet: Option<HardwareWalletType>,
) -> Result<CreatedWallet, Self::Error> {
let store_seed_phrase = if store_seed_phrase {
StoreSeedPhrase::Store
} else {
StoreSeedPhrase::DoNotStore
};

let args = match hardware_wallet {
None => WalletTypeArgs::Software {
mnemonic,
passphrase,
store_seed_phrase,
},
#[cfg(feature = "trezor")]
Some(HardwareWalletType::Trezor) => {
ensure!(
mnemonic.is_none()
&& passphrase.is_none()
&& store_seed_phrase == StoreSeedPhrase::DoNotStore,
RpcError::HardwareWalletWithMnemonic
);
WalletTypeArgs::Trezor
}
#[cfg(not(feature = "trezor"))]
Some(_) => {
return Err(RpcError::<N>::InvalidHardwareWallet)?;
}
};

let scan_blockchain = true;
self.wallet_rpc
.create_wallet(path, args, false, scan_blockchain)
.await
Expand Down
20 changes: 20 additions & 0 deletions wallet/wallet-rpc-client/src/rpc_client/client_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@ impl WalletInterface for ClientWalletRpc {
.map_err(WalletRpcError::ResponseError)
}

async fn recover_wallet(
&self,
path: PathBuf,
store_seed_phrase: bool,
mnemonic: Option<String>,
passphrase: Option<String>,
hardware_wallet: Option<HardwareWalletType>,
) -> Result<CreatedWallet, Self::Error> {
ColdWalletRpcClient::recover_wallet(
&self.http_client,
path.to_string_lossy().to_string(),
store_seed_phrase,
mnemonic,
passphrase,
hardware_wallet,
)
.await
.map_err(WalletRpcError::ResponseError)
}

async fn open_wallet(
&self,
path: PathBuf,
Expand Down
9 changes: 9 additions & 0 deletions wallet/wallet-rpc-client/src/wallet_rpc_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ pub trait WalletInterface {
hardware_wallet: Option<HardwareWalletType>,
) -> Result<CreatedWallet, Self::Error>;

async fn recover_wallet(
&self,
path: PathBuf,
store_seed_phrase: bool,
mnemonic: Option<String>,
passphrase: Option<String>,
hardware_wallet: Option<HardwareWalletType>,
) -> Result<CreatedWallet, Self::Error>;

async fn open_wallet(
&self,
path: PathBuf,
Expand Down
37 changes: 36 additions & 1 deletion wallet/wallet-rpc-daemon/docs/RPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1749,7 +1749,42 @@ string

### Method `wallet_create`

Create new wallet
Create a new wallet, this will skip scanning the blockchain


Parameters:
```
{
"path": string,
"store_seed_phrase": bool,
"mnemonic": EITHER OF
1) string
2) null,
"passphrase": EITHER OF
1) string
2) null,
"hardware_wallet": null,
}
```

Returns:
```
{ "mnemonic": EITHER OF
1) { "type": "UserProvided" }
2) {
"type": "NewlyGenerated",
"content": {
"mnemonic": string,
"passphrase": EITHER OF
1) string
2) null,
},
} }
```

### Method `wallet_recover`

Recover new wallet, this will rescan the blockchain upon creation


Parameters:
Expand Down
13 changes: 12 additions & 1 deletion wallet/wallet-rpc-lib/src/rpc/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ trait ColdWalletRpc {
#[method(name = "version")]
async fn version(&self) -> rpc::RpcResult<String>;

/// Create new wallet
/// Create a new wallet, this will skip scanning the blockchain
#[method(name = "wallet_create")]
async fn create_wallet(
&self,
Expand All @@ -72,6 +72,17 @@ trait ColdWalletRpc {
hardware_wallet: Option<HardwareWalletType>,
) -> rpc::RpcResult<CreatedWallet>;

/// Recover new wallet, this will rescan the blockchain upon creation
#[method(name = "wallet_recover")]
async fn recover_wallet(
&self,
path: String,
store_seed_phrase: bool,
mnemonic: Option<String>,
passphrase: Option<String>,
hardware_wallet: Option<HardwareWalletType>,
) -> rpc::RpcResult<CreatedWallet>;

/// Open an exiting wallet by specifying the file location of the wallet file
#[method(name = "wallet_open")]
async fn open_wallet(
Expand Down
50 changes: 48 additions & 2 deletions wallet/wallet-rpc-lib/src/rpc/server_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ use common::{
use crypto::key::PrivateKey;
use p2p_types::{bannable_address::BannableAddress, socket_address::SocketAddress, PeerId};
use serialization::{hex::HexEncode, json_encoded::JsonEncoded};
#[cfg(feature = "trezor")]
use utils::ensure;
use utils_networking::IpOrSocketAddress;
use wallet::{account::TxInfo, version::get_version};
Expand Down Expand Up @@ -118,7 +117,54 @@ where
}
};

let scan_blockchain = args.user_supplied_menmonic();
let scan_blockchain = false;
rpc::handle_result(
self.create_wallet(path.into(), args, false, scan_blockchain)
.await
.map(Into::<CreatedWallet>::into),
)
}

async fn recover_wallet(
&self,
path: String,
store_seed_phrase: bool,
mnemonic: Option<String>,
passphrase: Option<String>,
hardware_wallet: Option<HardwareWalletType>,
) -> rpc::RpcResult<CreatedWallet> {
let store_seed_phrase = if store_seed_phrase {
StoreSeedPhrase::Store
} else {
StoreSeedPhrase::DoNotStore
};

let args = match hardware_wallet {
None => {
ensure!(mnemonic.is_some(), RpcError::<N>::EmptyMnemonic);
WalletTypeArgs::Software {
mnemonic,
passphrase,
store_seed_phrase,
}
}
#[cfg(feature = "trezor")]
Some(HardwareWalletType::Trezor) => {
ensure!(
mnemonic.is_none()
&& passphrase.is_none()
&& store_seed_phrase == StoreSeedPhrase::DoNotStore,
RpcError::<N>::HardwareWalletWithMnemonic
);
WalletTypeArgs::Trezor
}
#[cfg(not(feature = "trezor"))]
Some(_) => {
return Err(RpcError::<N>::InvalidHardwareWallet)?;
}
};

let scan_blockchain = true;
rpc::handle_result(
self.create_wallet(path.into(), args, false, scan_blockchain)
.await
Expand Down
3 changes: 3 additions & 0 deletions wallet/wallet-rpc-lib/src/rpc/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ pub enum RpcError<N: NodeInterface> {
#[error("Invalid mnemonic: {0}")]
InvalidMnemonic(wallet_controller::mnemonic::Error),

#[error("Cannont recover a software wallet without providing a mnemonic")]
EmptyMnemonic,

#[error("Cannot specify a mnemonic or passphrase when using a hardware wallet")]
HardwareWalletWithMnemonic,

Expand Down

0 comments on commit 1cd8110

Please sign in to comment.