mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-02-01 17:01:10 +03:00
'Awaiting finalization' status and changes to 'wallet check' (#8)
* add awaiting confirmation status and change check to not remove unconfirmed transactions by default * test fixes
This commit is contained in:
parent
6d92a67730
commit
ddd9bc2908
12 changed files with 84 additions and 51 deletions
|
@ -842,11 +842,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to check and fix the contents of the wallet
|
/// Attempt to check and fix the contents of the wallet
|
||||||
pub fn check_repair(&mut self) -> Result<(), Error> {
|
pub fn check_repair(&mut self, delete_unconfirmed: bool) -> Result<(), Error> {
|
||||||
let mut w = self.wallet.lock();
|
let mut w = self.wallet.lock();
|
||||||
w.open_with_credentials()?;
|
w.open_with_credentials()?;
|
||||||
self.update_outputs(&mut w, true);
|
self.update_outputs(&mut w, true);
|
||||||
w.check_repair()?;
|
w.check_repair(delete_unconfirmed)?;
|
||||||
w.close()?;
|
w.close()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,7 +267,7 @@ where
|
||||||
/// Check / repair wallet contents
|
/// Check / repair wallet contents
|
||||||
/// assume wallet contents have been freshly updated with contents
|
/// assume wallet contents have been freshly updated with contents
|
||||||
/// of latest block
|
/// of latest block
|
||||||
pub fn check_repair<T, C, K>(wallet: &mut T) -> Result<(), Error>
|
pub fn check_repair<T, C, K>(wallet: &mut T, delete_unconfirmed: bool) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K>,
|
||||||
C: NodeClient,
|
C: NodeClient,
|
||||||
|
@ -335,37 +335,39 @@ where
|
||||||
restore_missing_output(wallet, m, &mut found_parents, &mut None)?;
|
restore_missing_output(wallet, m, &mut found_parents, &mut None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock locked outputs
|
if delete_unconfirmed {
|
||||||
for m in locked_outs.into_iter() {
|
// Unlock locked outputs
|
||||||
let mut o = m.0;
|
for m in locked_outs.into_iter() {
|
||||||
warn!(
|
let mut o = m.0;
|
||||||
"Confirmed output for {} with ID {} ({:?}) exists in UTXO set and is locked. \
|
warn!(
|
||||||
Unlocking and cancelling associated transaction log entries.",
|
"Confirmed output for {} with ID {} ({:?}) exists in UTXO set and is locked. \
|
||||||
o.value, o.key_id, m.1.commit,
|
Unlocking and cancelling associated transaction log entries.",
|
||||||
);
|
o.value, o.key_id, m.1.commit,
|
||||||
o.status = OutputStatus::Unspent;
|
);
|
||||||
cancel_tx_log_entry(wallet, &o)?;
|
o.status = OutputStatus::Unspent;
|
||||||
let mut batch = wallet.batch()?;
|
cancel_tx_log_entry(wallet, &o)?;
|
||||||
batch.save(o)?;
|
let mut batch = wallet.batch()?;
|
||||||
batch.commit()?;
|
batch.save(o)?;
|
||||||
}
|
batch.commit()?;
|
||||||
|
}
|
||||||
|
|
||||||
let unconfirmed_outs: Vec<&(OutputData, pedersen::Commitment)> = wallet_outputs
|
let unconfirmed_outs: Vec<&(OutputData, pedersen::Commitment)> = wallet_outputs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|o| o.0.status == OutputStatus::Unconfirmed)
|
.filter(|o| o.0.status == OutputStatus::Unconfirmed)
|
||||||
.collect();
|
.collect();
|
||||||
// Delete unconfirmed outputs
|
// Delete unconfirmed outputs
|
||||||
for m in unconfirmed_outs.into_iter() {
|
for m in unconfirmed_outs.into_iter() {
|
||||||
let o = m.0.clone();
|
let o = m.0.clone();
|
||||||
warn!(
|
warn!(
|
||||||
"Unconfirmed output for {} with ID {} ({:?}) not in UTXO set. \
|
"Unconfirmed output for {} with ID {} ({:?}) not in UTXO set. \
|
||||||
Deleting and cancelling associated transaction log entries.",
|
Deleting and cancelling associated transaction log entries.",
|
||||||
o.value, o.key_id, m.1,
|
o.value, o.key_id, m.1,
|
||||||
);
|
);
|
||||||
cancel_tx_log_entry(wallet, &o)?;
|
cancel_tx_log_entry(wallet, &o)?;
|
||||||
let mut batch = wallet.batch()?;
|
let mut batch = wallet.batch()?;
|
||||||
batch.delete(&o.key_id, &o.mmr_index)?;
|
batch.delete(&o.key_id, &o.mmr_index)?;
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore labels, account paths and child derivation indices
|
// restore labels, account paths and child derivation indices
|
||||||
|
|
|
@ -384,6 +384,7 @@ where
|
||||||
|
|
||||||
let mut unspent_total = 0;
|
let mut unspent_total = 0;
|
||||||
let mut immature_total = 0;
|
let mut immature_total = 0;
|
||||||
|
let mut awaiting_finalization_total = 0;
|
||||||
let mut unconfirmed_total = 0;
|
let mut unconfirmed_total = 0;
|
||||||
let mut locked_total = 0;
|
let mut locked_total = 0;
|
||||||
|
|
||||||
|
@ -403,9 +404,9 @@ where
|
||||||
// We ignore unconfirmed coinbase outputs completely.
|
// We ignore unconfirmed coinbase outputs completely.
|
||||||
if !out.is_coinbase {
|
if !out.is_coinbase {
|
||||||
if minimum_confirmations == 0 {
|
if minimum_confirmations == 0 {
|
||||||
unspent_total += out.value;
|
|
||||||
} else {
|
|
||||||
unconfirmed_total += out.value;
|
unconfirmed_total += out.value;
|
||||||
|
} else {
|
||||||
|
awaiting_finalization_total += out.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,6 +421,7 @@ where
|
||||||
last_confirmed_height: current_height,
|
last_confirmed_height: current_height,
|
||||||
minimum_confirmations,
|
minimum_confirmations,
|
||||||
total: unspent_total + unconfirmed_total + immature_total,
|
total: unspent_total + unconfirmed_total + immature_total,
|
||||||
|
amount_awaiting_finalization: awaiting_finalization_total,
|
||||||
amount_awaiting_confirmation: unconfirmed_total,
|
amount_awaiting_confirmation: unconfirmed_total,
|
||||||
amount_immature: immature_total,
|
amount_immature: immature_total,
|
||||||
amount_locked: locked_total,
|
amount_locked: locked_total,
|
||||||
|
|
|
@ -130,7 +130,7 @@ where
|
||||||
fn restore(&mut self) -> Result<(), Error>;
|
fn restore(&mut self) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Attempt to check and fix wallet state
|
/// Attempt to check and fix wallet state
|
||||||
fn check_repair(&mut self) -> Result<(), Error>;
|
fn check_repair(&mut self, delete_unconfirmed: bool) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Batch trait to update the output data backend atomically. Trying to use a
|
/// Batch trait to update the output data backend atomically. Trying to use a
|
||||||
|
@ -546,6 +546,8 @@ pub struct WalletInfo {
|
||||||
pub minimum_confirmations: u64,
|
pub minimum_confirmations: u64,
|
||||||
/// total amount in the wallet
|
/// total amount in the wallet
|
||||||
pub total: u64,
|
pub total: u64,
|
||||||
|
/// amount awaiting finalization
|
||||||
|
pub amount_awaiting_finalization: u64,
|
||||||
/// amount awaiting confirmation
|
/// amount awaiting confirmation
|
||||||
pub amount_awaiting_confirmation: u64,
|
pub amount_awaiting_confirmation: u64,
|
||||||
/// coinbases waiting for lock height
|
/// coinbases waiting for lock height
|
||||||
|
|
|
@ -530,13 +530,19 @@ pub fn restore(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// wallet check
|
||||||
|
pub struct CheckArgs {
|
||||||
|
pub delete_unconfirmed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_repair(
|
pub fn check_repair(
|
||||||
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
||||||
|
args: CheckArgs,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
controller::owner_single_use(wallet.clone(), |api| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
warn!("Starting wallet check...",);
|
warn!("Starting wallet check...",);
|
||||||
warn!("Updating all wallet outputs, please wait ...",);
|
warn!("Updating all wallet outputs, please wait ...",);
|
||||||
let result = api.check_repair();
|
let result = api.check_repair(args.delete_unconfirmed);
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
warn!("Wallet check complete",);
|
warn!("Wallet check complete",);
|
||||||
|
|
|
@ -270,7 +270,7 @@ pub fn info(
|
||||||
|
|
||||||
if dark_background_color_scheme {
|
if dark_background_color_scheme {
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
bFG->"Total",
|
bFG->"Confirmed Total",
|
||||||
FG->amount_to_hr_string(wallet_info.total, false)
|
FG->amount_to_hr_string(wallet_info.total, false)
|
||||||
]);
|
]);
|
||||||
// Only dispay "Immature Coinbase" if we have related outputs in the wallet.
|
// Only dispay "Immature Coinbase" if we have related outputs in the wallet.
|
||||||
|
@ -285,6 +285,10 @@ pub fn info(
|
||||||
bFY->format!("Awaiting Confirmation (< {})", wallet_info.minimum_confirmations),
|
bFY->format!("Awaiting Confirmation (< {})", wallet_info.minimum_confirmations),
|
||||||
FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation, false)
|
FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation, false)
|
||||||
]);
|
]);
|
||||||
|
table.add_row(row![
|
||||||
|
bFB->format!("Awaiting Finalization"),
|
||||||
|
FB->amount_to_hr_string(wallet_info.amount_awaiting_finalization, false)
|
||||||
|
]);
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
Fr->"Locked by previous transaction",
|
Fr->"Locked by previous transaction",
|
||||||
Fr->amount_to_hr_string(wallet_info.amount_locked, false)
|
Fr->amount_to_hr_string(wallet_info.amount_locked, false)
|
||||||
|
|
|
@ -350,8 +350,8 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_repair(&mut self) -> Result<(), Error> {
|
fn check_repair(&mut self, delete_unconfirmed: bool) -> Result<(), Error> {
|
||||||
internal::restore::check_repair(self).context(ErrorKind::Restore)?;
|
internal::restore::check_repair(self, delete_unconfirmed).context(ErrorKind::Restore)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// this should restore our missing outputs
|
// this should restore our missing outputs
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
api.check_repair()?;
|
api.check_repair(true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// unlock/restore
|
// unlock/restore
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
api.check_repair()?;
|
api.check_repair(true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -327,7 +327,7 @@ fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// 0) Check repair when all is okay should leave wallet contents alone
|
// 0) Check repair when all is okay should leave wallet contents alone
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
api.check_repair()?;
|
api.check_repair(true)?;
|
||||||
let info = test_framework::wallet_info(api)?;
|
let info = test_framework::wallet_info(api)?;
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 6);
|
assert_eq!(info.amount_currently_spendable, base_amount * 6);
|
||||||
assert_eq!(info.total, base_amount * 6);
|
assert_eq!(info.total, base_amount * 6);
|
||||||
|
@ -381,7 +381,7 @@ fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// 2) check_repair should recover them into a single wallet
|
// 2) check_repair should recover them into a single wallet
|
||||||
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
api.check_repair()?;
|
api.check_repair(true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -447,7 +447,7 @@ fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
wallet::controller::owner_single_use(wallet6.clone(), |api| {
|
wallet::controller::owner_single_use(wallet6.clone(), |api| {
|
||||||
api.check_repair()?;
|
api.check_repair(true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -538,7 +538,7 @@ fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
let outputs = api.retrieve_outputs(true, false, None)?.1;
|
let outputs = api.retrieve_outputs(true, false, None)?.1;
|
||||||
assert_eq!(outputs.len(), 3);
|
assert_eq!(outputs.len(), 3);
|
||||||
assert_eq!(info.amount_currently_spendable, base_amount * 15);
|
assert_eq!(info.amount_currently_spendable, base_amount * 15);
|
||||||
api.check_repair()?;
|
api.check_repair(true)?;
|
||||||
let info = test_framework::wallet_info(api)?;
|
let info = test_framework::wallet_info(api)?;
|
||||||
let outputs = api.retrieve_outputs(true, false, None)?.1;
|
let outputs = api.retrieve_outputs(true, false, None)?.1;
|
||||||
assert_eq!(outputs.len(), 6);
|
assert_eq!(outputs.len(), 6);
|
||||||
|
@ -556,7 +556,7 @@ fn two_wallets_one_seed_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// 7) Ensure check_repair creates missing accounts
|
// 7) Ensure check_repair creates missing accounts
|
||||||
wallet::controller::owner_single_use(wallet10.clone(), |api| {
|
wallet::controller::owner_single_use(wallet10.clone(), |api| {
|
||||||
api.check_repair()?;
|
api.check_repair(true)?;
|
||||||
api.set_active_account("account_1")?;
|
api.set_active_account("account_1")?;
|
||||||
let info = test_framework::wallet_info(api)?;
|
let info = test_framework::wallet_info(api)?;
|
||||||
let outputs = api.retrieve_outputs(true, false, None)?.1;
|
let outputs = api.retrieve_outputs(true, false, None)?.1;
|
||||||
|
|
|
@ -420,11 +420,12 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
let (refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
|
let (refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
|
||||||
assert!(refreshed);
|
assert!(refreshed);
|
||||||
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
|
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
|
||||||
assert_eq!(wallet2_info.total, amount);
|
assert_eq!(wallet2_info.amount_awaiting_finalization, amount);
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// wallet 1 is bold and doesn't ever post the transaction mine a few more blocks
|
// wallet 1 is bold and doesn't ever post the transaction
|
||||||
|
// mine a few more blocks
|
||||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 5);
|
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 5);
|
||||||
|
|
||||||
// Wallet 1 decides to roll back instead
|
// Wallet 1 decides to roll back instead
|
||||||
|
|
|
@ -452,6 +452,13 @@ pub fn parse_info_args(args: &ArgMatches) -> Result<command::InfoArgs, ParseErro
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_check_args(args: &ArgMatches) -> Result<command::CheckArgs, ParseError> {
|
||||||
|
let delete_unconfirmed = args.is_present("delete_unconfirmed");
|
||||||
|
Ok(command::CheckArgs {
|
||||||
|
delete_unconfirmed: delete_unconfirmed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, ParseError> {
|
pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, ParseError> {
|
||||||
let tx_id = match args.value_of("id") {
|
let tx_id = match args.value_of("id") {
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -621,7 +628,10 @@ pub fn wallet_command(
|
||||||
command::cancel(inst_wallet(), a)
|
command::cancel(inst_wallet(), a)
|
||||||
}
|
}
|
||||||
("restore", Some(_)) => command::restore(inst_wallet()),
|
("restore", Some(_)) => command::restore(inst_wallet()),
|
||||||
("check", Some(_)) => command::check_repair(inst_wallet()),
|
("check", Some(args)) => {
|
||||||
|
let a = arg_parse!(parse_check_args(&args));
|
||||||
|
command::check_repair(inst_wallet(), a)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let msg = format!("Unknown wallet command, use 'grin help wallet' for details");
|
let msg = format!("Unknown wallet command, use 'grin help wallet' for details");
|
||||||
return Err(ErrorKind::ArgumentError(msg).into());
|
return Err(ErrorKind::ArgumentError(msg).into());
|
||||||
|
|
|
@ -456,7 +456,7 @@ mod wallet_tests {
|
||||||
];
|
];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
let arg_vec = vec!["grin-wallet", "-p", "password", "check"];
|
let arg_vec = vec!["grin-wallet", "-p", "password", "check", "-d"];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
// Another file exchange, cancel this time
|
// Another file exchange, cancel this time
|
||||||
|
|
|
@ -236,3 +236,9 @@ subcommands:
|
||||||
about: Restores a wallet contents from a seed file
|
about: Restores a wallet contents from a seed file
|
||||||
- check:
|
- check:
|
||||||
about: Checks a wallet's outputs against a live node, repairing and restoring missing outputs if required
|
about: Checks a wallet's outputs against a live node, repairing and restoring missing outputs if required
|
||||||
|
args:
|
||||||
|
- delete_unconfirmed:
|
||||||
|
help: Delete any unconfirmed outputsm unlock any locked outputs and delete associated transactions while doing the check.
|
||||||
|
short: d
|
||||||
|
long: delete_unconfirmed
|
||||||
|
takes_value: false
|
||||||
|
|
Loading…
Reference in a new issue