mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
Save transaction as part of TxLogEntry after transaction completion (#1473)
* save wallet transaction on transaction completion * rustfmt * add resend command * rustfmt * added unit tests + fixes for grin wallet repost * add ability to dump transaction file contents * rustfmt * wallet doc update
This commit is contained in:
parent
77765796ab
commit
75f0ea6dd3
10 changed files with 284 additions and 5 deletions
|
@ -270,6 +270,22 @@ Be sure to use this command with caution, as there are many edge cases and possi
|
||||||
the recipient of a transaction. For the time being please be 100% certain that the relevant transaction is never, ever going to be posted before
|
the recipient of a transaction. For the time being please be 100% certain that the relevant transaction is never, ever going to be posted before
|
||||||
running `grin wallet cancel`
|
running `grin wallet cancel`
|
||||||
|
|
||||||
|
##### repost
|
||||||
|
|
||||||
|
If you're the sender of a posted transaction that doesn't confirm on the chain (due to a fork or full transaction pool), you can repost the copy of it that grin automatically stores in your wallet data whenever a transaction is finalized. This doesn't need to communicate with the recipient again, it just re-posts a transaction created during a previous `send` attempt.
|
||||||
|
|
||||||
|
To do this, look up the transaction id using the `grin wallet txs` command, and using the id (say 3 in this example,) enter:
|
||||||
|
|
||||||
|
`grin wallet repost -i 3`
|
||||||
|
|
||||||
|
This will attempt to repost the transaction to the chain. Note this won't attempt to send if the transaction is already marked as 'confirmed' within the wallet.
|
||||||
|
|
||||||
|
You can also use the `repost` command to dump the transaction in a raw json format with the `-m` (duMp) switch, e.g:
|
||||||
|
|
||||||
|
`grin wallet repost -i 3 -m tx_3.json`
|
||||||
|
|
||||||
|
This will create a file called tx_3.json containing your raw transaction data. Note that this formatting in the file isn't yet very user-readable.
|
||||||
|
|
||||||
##### restore
|
##### restore
|
||||||
|
|
||||||
If for some reason the wallet cancel commands above don't work, or you need to restore from a backed up `wallet.seed` file and password, you can perform a full wallet restore.
|
If for some reason the wallet cancel commands above don't work, or you need to restore from a backed up `wallet.seed` file and password, you can perform a full wallet restore.
|
||||||
|
|
|
@ -343,6 +343,50 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) {
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
("repost", Some(repost_args)) => {
|
||||||
|
let tx_id: u32 = match repost_args.value_of("id") {
|
||||||
|
None => {
|
||||||
|
error!(LOGGER, "Transaction of a completed but unconfirmed transaction required (specify with --id=[id])");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
Some(tx) => match tx.parse() {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(_) => {
|
||||||
|
panic!("Unable to parse argument 'id' as a number");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let dump_file = repost_args.value_of("dumpfile");
|
||||||
|
let fluff = repost_args.is_present("fluff");
|
||||||
|
match dump_file {
|
||||||
|
None => {
|
||||||
|
let result = api.post_stored_tx(tx_id, fluff);
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
info!(LOGGER, "Reposted transaction at {}", tx_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "Tranasction reposting failed: {}", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(f) => {
|
||||||
|
let result = api.dump_stored_tx(tx_id, f);
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
warn!(LOGGER, "Dumped transaction data for tx {} to {}", tx_id, f);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(LOGGER, "Tranasction reposting failed: {}", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
("cancel", Some(tx_args)) => {
|
("cancel", Some(tx_args)) => {
|
||||||
let tx_id = tx_args
|
let tx_id = tx_args
|
||||||
.value_of("id")
|
.value_of("id")
|
||||||
|
|
|
@ -222,6 +222,11 @@ fn main() {
|
||||||
.help("Fluff the transaction (ignore Dandelion relay protocol)")
|
.help("Fluff the transaction (ignore Dandelion relay protocol)")
|
||||||
.short("f")
|
.short("f")
|
||||||
.long("fluff")))
|
.long("fluff")))
|
||||||
|
.arg(Arg::with_name("stored_tx")
|
||||||
|
.help("If present, use the previously stored Unconfirmed transaction with given id.")
|
||||||
|
.short("t")
|
||||||
|
.long("stored_tx")
|
||||||
|
.takes_value(true))
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("receive")
|
.subcommand(SubCommand::with_name("receive")
|
||||||
.about("Processes a transaction file to accept a transfer from a sender.")
|
.about("Processes a transaction file to accept a transfer from a sender.")
|
||||||
|
@ -268,6 +273,23 @@ fn main() {
|
||||||
.long("id")
|
.long("id")
|
||||||
.takes_value(true)))
|
.takes_value(true)))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("repost")
|
||||||
|
.about("Reposts a stored, completed but unconfirmed transaction to the chain, or dumps it to a file")
|
||||||
|
.arg(Arg::with_name("id")
|
||||||
|
.help("Transaction ID Containing the stored completed transaction")
|
||||||
|
.short("i")
|
||||||
|
.long("id")
|
||||||
|
.takes_value(true))
|
||||||
|
.arg(Arg::with_name("dumpfile")
|
||||||
|
.help("File name to duMp the tranaction to instead of posting")
|
||||||
|
.short("m")
|
||||||
|
.long("dumpfile")
|
||||||
|
.takes_value(true))
|
||||||
|
.arg(Arg::with_name("fluff")
|
||||||
|
.help("Fluff the transaction (ignore Dandelion relay protocol)")
|
||||||
|
.short("f")
|
||||||
|
.long("fluff")))
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("cancel")
|
.subcommand(SubCommand::with_name("cancel")
|
||||||
.about("Cancels an previously created transaction, freeing previously locked outputs for use again")
|
.about("Cancels an previously created transaction, freeing previously locked outputs for use again")
|
||||||
.arg(Arg::with_name("id")
|
.arg(Arg::with_name("id")
|
||||||
|
|
|
@ -114,6 +114,7 @@ pub fn txs(
|
||||||
bMG->"Amount Debited",
|
bMG->"Amount Debited",
|
||||||
bMG->"Fee",
|
bMG->"Fee",
|
||||||
bMG->"Net Difference",
|
bMG->"Net Difference",
|
||||||
|
bMG->"Tx Data",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for t in txs {
|
for t in txs {
|
||||||
|
@ -145,6 +146,10 @@ pub fn txs(
|
||||||
core::amount_to_hr_string(t.amount_debited - t.amount_credited, true)
|
core::amount_to_hr_string(t.amount_debited - t.amount_credited, true)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let tx_data = match t.tx_hex {
|
||||||
|
Some(_) => format!("Exists"),
|
||||||
|
None => "None".to_owned(),
|
||||||
|
};
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
bFC->id,
|
bFC->id,
|
||||||
bFC->entry_type,
|
bFC->entry_type,
|
||||||
|
@ -158,6 +163,7 @@ pub fn txs(
|
||||||
bFR->amount_debited_str,
|
bFR->amount_debited_str,
|
||||||
bFR->fee,
|
bFR->fee,
|
||||||
bFY->net_diff,
|
bFY->net_diff,
|
||||||
|
bFb->tx_data,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ use std::sync::{Arc, Mutex};
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
|
|
||||||
use core::core::hash::Hashed;
|
use core::core::hash::Hashed;
|
||||||
|
use core::core::Transaction;
|
||||||
use core::ser;
|
use core::ser;
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
use libtx::slate::Slate;
|
use libtx::slate::Slate;
|
||||||
|
@ -174,9 +175,10 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
tx::complete_tx(&mut **w, &mut slate_out, &context)?;
|
tx::complete_tx(&mut **w, &mut slate_out, &context)?;
|
||||||
|
let tx_hex = util::to_hex(ser::ser_vec(&slate_out.tx).unwrap());
|
||||||
|
|
||||||
// lock our inputs
|
// lock our inputs
|
||||||
lock_fn_out(&mut **w)?;
|
lock_fn_out(&mut **w, &tx_hex)?;
|
||||||
w.close()?;
|
w.close()?;
|
||||||
Ok(slate_out)
|
Ok(slate_out)
|
||||||
}
|
}
|
||||||
|
@ -214,8 +216,10 @@ where
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap());
|
||||||
|
|
||||||
// lock our inputs
|
// lock our inputs
|
||||||
lock_fn(&mut **w)?;
|
lock_fn(&mut **w, &tx_hex)?;
|
||||||
w.close()?;
|
w.close()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -337,6 +341,80 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes stored transaction data to a given file
|
||||||
|
pub fn dump_stored_tx(&self, tx_id: u32, dest: &str) -> Result<(), Error> {
|
||||||
|
let (confirmed, tx_hex) = {
|
||||||
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
let res = tx::retrieve_tx_hex(&mut **w, tx_id)?;
|
||||||
|
w.close()?;
|
||||||
|
res
|
||||||
|
};
|
||||||
|
if confirmed {
|
||||||
|
warn!(
|
||||||
|
LOGGER,
|
||||||
|
"api: dump_stored_tx: transaction at {} is already confirmed.", tx_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if tx_hex.is_none() {
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"api: dump_stored_tx: completed transaction at {} does not exist.", tx_id
|
||||||
|
);
|
||||||
|
return Err(ErrorKind::TransactionBuildingNotCompleted(tx_id))?;
|
||||||
|
}
|
||||||
|
let tx_bin = util::from_hex(tx_hex.unwrap()).unwrap();
|
||||||
|
let tx = ser::deserialize::<Transaction>(&mut &tx_bin[..])?;
|
||||||
|
let mut tx_file = File::create(dest)?;
|
||||||
|
tx_file.write_all(json::to_string(&tx).unwrap().as_bytes())?;
|
||||||
|
tx_file.sync_all()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (Re)Posts a transaction that's already been stored to the chain
|
||||||
|
pub fn post_stored_tx(&self, tx_id: u32, fluff: bool) -> Result<(), Error> {
|
||||||
|
let client;
|
||||||
|
let (confirmed, tx_hex) = {
|
||||||
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
w.open_with_credentials()?;
|
||||||
|
client = w.client().clone();
|
||||||
|
let res = tx::retrieve_tx_hex(&mut **w, tx_id)?;
|
||||||
|
w.close()?;
|
||||||
|
res
|
||||||
|
};
|
||||||
|
if confirmed {
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"api: repost_tx: transaction at {} is confirmed. NOT resending.", tx_id
|
||||||
|
);
|
||||||
|
return Err(ErrorKind::TransactionAlreadyConfirmed)?;
|
||||||
|
}
|
||||||
|
if tx_hex.is_none() {
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"api: repost_tx: completed transaction at {} does not exist.", tx_id
|
||||||
|
);
|
||||||
|
return Err(ErrorKind::TransactionBuildingNotCompleted(tx_id))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = client.post_tx(
|
||||||
|
&TxWrapper {
|
||||||
|
tx_hex: tx_hex.unwrap(),
|
||||||
|
},
|
||||||
|
fluff,
|
||||||
|
);
|
||||||
|
if let Err(e) = res {
|
||||||
|
error!(LOGGER, "api: repost_tx: failed with error: {}", e);
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
LOGGER,
|
||||||
|
"api: repost_tx: successfully posted tx at: {}, fluff? {}", tx_id, fluff
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to restore contents of wallet
|
/// Attempt to restore contents of wallet
|
||||||
pub fn restore(&mut self) -> Result<(), Error> {
|
pub fn restore(&mut self) -> Result<(), Error> {
|
||||||
let mut w = self.wallet.lock().unwrap();
|
let mut w = self.wallet.lock().unwrap();
|
||||||
|
|
|
@ -19,6 +19,7 @@ use std::io;
|
||||||
|
|
||||||
use failure::{Backtrace, Context, Fail};
|
use failure::{Backtrace, Context, Fail};
|
||||||
|
|
||||||
|
use core;
|
||||||
use core::core::transaction;
|
use core::core::transaction;
|
||||||
use keychain;
|
use keychain;
|
||||||
use libtx;
|
use libtx;
|
||||||
|
@ -99,6 +100,10 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "JSON format error")]
|
#[fail(display = "JSON format error")]
|
||||||
Format,
|
Format,
|
||||||
|
|
||||||
|
/// Other serialization errors
|
||||||
|
#[fail(display = "Ser/Deserialization error")]
|
||||||
|
Deser(core::ser::Error),
|
||||||
|
|
||||||
/// IO Error
|
/// IO Error
|
||||||
#[fail(display = "I/O error")]
|
#[fail(display = "I/O error")]
|
||||||
IO,
|
IO,
|
||||||
|
@ -147,6 +152,14 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "Cancellation Error: {}", _0)]
|
#[fail(display = "Cancellation Error: {}", _0)]
|
||||||
TransactionCancellationError(&'static str),
|
TransactionCancellationError(&'static str),
|
||||||
|
|
||||||
|
/// Attempt to repost a transaction that's already confirmed
|
||||||
|
#[fail(display = "Transaction already confirmed error")]
|
||||||
|
TransactionAlreadyConfirmed,
|
||||||
|
|
||||||
|
/// Attempt to repost a transaction that's not completed and stored
|
||||||
|
#[fail(display = "Transaction building not completed: {}", _0)]
|
||||||
|
TransactionBuildingNotCompleted(u32),
|
||||||
|
|
||||||
/// Other
|
/// Other
|
||||||
#[fail(display = "Generic error: {}", _0)]
|
#[fail(display = "Generic error: {}", _0)]
|
||||||
GenericError(String),
|
GenericError(String),
|
||||||
|
@ -218,3 +231,11 @@ impl From<transaction::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<core::ser::Error> for Error {
|
||||||
|
fn from(error: core::ser::Error) -> Error {
|
||||||
|
Error {
|
||||||
|
inner: Context::new(ErrorKind::Deser(error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -37,7 +37,14 @@ pub fn build_send_tx_slate<T: ?Sized, C, K>(
|
||||||
max_outputs: usize,
|
max_outputs: usize,
|
||||||
change_outputs: usize,
|
change_outputs: usize,
|
||||||
selection_strategy_is_use_all: bool,
|
selection_strategy_is_use_all: bool,
|
||||||
) -> Result<(Slate, Context, impl FnOnce(&mut T) -> Result<(), Error>), Error>
|
) -> Result<
|
||||||
|
(
|
||||||
|
Slate,
|
||||||
|
Context,
|
||||||
|
impl FnOnce(&mut T, &str) -> Result<(), Error>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
|
@ -90,12 +97,13 @@ where
|
||||||
|
|
||||||
// Return a closure to acquire wallet lock and lock the coins being spent
|
// Return a closure to acquire wallet lock and lock the coins being spent
|
||||||
// so we avoid accidental double spend attempt.
|
// so we avoid accidental double spend attempt.
|
||||||
let update_sender_wallet_fn = move |wallet: &mut T| {
|
let update_sender_wallet_fn = move |wallet: &mut T, tx_hex: &str| {
|
||||||
let mut batch = wallet.batch()?;
|
let mut batch = wallet.batch()?;
|
||||||
let log_id = batch.next_tx_log_id(root_key_id.clone())?;
|
let log_id = batch.next_tx_log_id(root_key_id.clone())?;
|
||||||
let mut t = TxLogEntry::new(TxLogEntryType::TxSent, log_id);
|
let mut t = TxLogEntry::new(TxLogEntryType::TxSent, log_id);
|
||||||
t.tx_slate_id = Some(slate_id);
|
t.tx_slate_id = Some(slate_id);
|
||||||
t.fee = Some(fee);
|
t.fee = Some(fee);
|
||||||
|
t.tx_hex = Some(tx_hex.to_owned());
|
||||||
let mut amount_debited = 0;
|
let mut amount_debited = 0;
|
||||||
t.num_inputs = lock_inputs.len();
|
t.num_inputs = lock_inputs.len();
|
||||||
for id in lock_inputs {
|
for id in lock_inputs {
|
||||||
|
|
|
@ -64,7 +64,14 @@ pub fn create_send_tx<T: ?Sized, C, K>(
|
||||||
max_outputs: usize,
|
max_outputs: usize,
|
||||||
num_change_outputs: usize,
|
num_change_outputs: usize,
|
||||||
selection_strategy_is_use_all: bool,
|
selection_strategy_is_use_all: bool,
|
||||||
) -> Result<(Slate, Context, impl FnOnce(&mut T) -> Result<(), Error>), Error>
|
) -> Result<
|
||||||
|
(
|
||||||
|
Slate,
|
||||||
|
Context,
|
||||||
|
impl FnOnce(&mut T, &str) -> Result<(), Error>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
>
|
||||||
where
|
where
|
||||||
T: WalletBackend<C, K>,
|
T: WalletBackend<C, K>,
|
||||||
C: WalletClient,
|
C: WalletClient,
|
||||||
|
@ -154,6 +161,25 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the associated stored finalised hex Transaction for a given transaction Id
|
||||||
|
/// as well as whether it's been confirmed
|
||||||
|
pub fn retrieve_tx_hex<T: ?Sized, C, K>(
|
||||||
|
wallet: &mut T,
|
||||||
|
tx_id: u32,
|
||||||
|
) -> Result<(bool, Option<String>), Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<C, K>,
|
||||||
|
C: WalletClient,
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
let tx_vec = updater::retrieve_txs(wallet, Some(tx_id))?;
|
||||||
|
if tx_vec.len() != 1 {
|
||||||
|
return Err(ErrorKind::TransactionDoesntExist(tx_id))?;
|
||||||
|
}
|
||||||
|
let tx = tx_vec[0].clone();
|
||||||
|
Ok((tx.confirmed, tx.tx_hex))
|
||||||
|
}
|
||||||
|
|
||||||
/// Issue a burn tx
|
/// Issue a burn tx
|
||||||
pub fn issue_burn_tx<T: ?Sized, C, K>(
|
pub fn issue_burn_tx<T: ?Sized, C, K>(
|
||||||
wallet: &mut T,
|
wallet: &mut T,
|
||||||
|
|
|
@ -586,6 +586,8 @@ pub struct TxLogEntry {
|
||||||
pub amount_debited: u64,
|
pub amount_debited: u64,
|
||||||
/// Fee
|
/// Fee
|
||||||
pub fee: Option<u64>,
|
pub fee: Option<u64>,
|
||||||
|
/// The transaction json itself, stored for reference or resending
|
||||||
|
pub tx_hex: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::Writeable for TxLogEntry {
|
impl ser::Writeable for TxLogEntry {
|
||||||
|
@ -616,6 +618,7 @@ impl TxLogEntry {
|
||||||
num_inputs: 0,
|
num_inputs: 0,
|
||||||
num_outputs: 0,
|
num_outputs: 0,
|
||||||
fee: None,
|
fee: None,
|
||||||
|
tx_hex: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -247,6 +247,61 @@ fn basic_transaction_api(
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Send another transaction, but don't post to chain immediately and use
|
||||||
|
// the stored transaction instead
|
||||||
|
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
|
||||||
|
// note this will increment the block count as part of the transaction "Posting"
|
||||||
|
slate = sender_api.issue_send_tx(
|
||||||
|
amount * 2, // amount
|
||||||
|
2, // minimum confirmations
|
||||||
|
"wallet2", // dest
|
||||||
|
500, // max outputs
|
||||||
|
1, // num change outputs
|
||||||
|
true, // select all outputs
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
wallet::controller::owner_single_use(wallet1.clone(), |sender_api| {
|
||||||
|
let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(true)?;
|
||||||
|
assert!(refreshed);
|
||||||
|
let (_, txs) = sender_api.retrieve_txs(true, None)?;
|
||||||
|
|
||||||
|
// find the transaction
|
||||||
|
let tx = txs
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.tx_slate_id == Some(slate.id))
|
||||||
|
.unwrap();
|
||||||
|
sender_api.post_stored_tx(tx.id, false)?;
|
||||||
|
let (_, wallet1_info) = sender_api.retrieve_summary_info(true)?;
|
||||||
|
// should be mined now
|
||||||
|
assert_eq!(
|
||||||
|
wallet1_info.total,
|
||||||
|
amount * wallet1_info.last_confirmed_height - amount * 3
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// mine a few more blocks
|
||||||
|
let _ = common::award_blocks_to_wallet(&chain, wallet1.clone(), 3);
|
||||||
|
|
||||||
|
// check wallet2 has stored transaction
|
||||||
|
wallet::controller::owner_single_use(wallet2.clone(), |api| {
|
||||||
|
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(true)?;
|
||||||
|
assert!(wallet2_refreshed);
|
||||||
|
assert_eq!(wallet2_info.amount_currently_spendable, amount * 3);
|
||||||
|
|
||||||
|
// check tx log entry is confirmed
|
||||||
|
let (refreshed, txs) = api.retrieve_txs(true, None)?;
|
||||||
|
assert!(refreshed);
|
||||||
|
let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id));
|
||||||
|
assert!(tx.is_some());
|
||||||
|
let tx = tx.unwrap();
|
||||||
|
assert!(tx.confirmed);
|
||||||
|
assert!(tx.confirmation_ts.is_some());
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
// let logging finish
|
// let logging finish
|
||||||
thread::sleep(Duration::from_millis(200));
|
thread::sleep(Duration::from_millis(200));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue