mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-01-20 19:11:09 +03:00
Store payment outputs in wallet database (#1)
* store receiver's output into the sender's database * rustfmt * payment output refresh * fix the test code in libwallet * rustfmt * fix wallet_command_line test * modify the warning message for self sending * a bit of unit test according to review comments * display unknown value for the case of multiple outputs on single receiver, normally it's not the case of this wallet implementation * rustfmt * fix merge missing parts * rustfmt * use PaymentCommitMapping struct instead of a tuple * rustfmt * fix the test * fix the owner api rpc test
This commit is contained in:
parent
1a01655526
commit
12909c9262
16 changed files with 597 additions and 21 deletions
|
@ -26,8 +26,8 @@ use crate::keychain::{Identifier, Keychain};
|
|||
use crate::libwallet::api_impl::owner;
|
||||
use crate::libwallet::slate::Slate;
|
||||
use crate::libwallet::types::{
|
||||
AcctPathMapping, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, TxLogEntry,
|
||||
WalletBackend, WalletInfo,
|
||||
AcctPathMapping, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping,
|
||||
PaymentCommitMapping, TxLogEntry, WalletBackend, WalletInfo,
|
||||
};
|
||||
use crate::libwallet::{Error, ErrorKind};
|
||||
|
||||
|
@ -304,6 +304,56 @@ where
|
|||
res
|
||||
}
|
||||
|
||||
/// Returns a list of payment outputs from the active account in the wallet.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `refresh_from_node` - If true, the wallet will attempt to contact
|
||||
/// a node (via the [`NodeClient`](../grin_wallet_libwallet/types/trait.NodeClient.html)
|
||||
/// provided during wallet instantiation). If `false`, the results will
|
||||
/// contain output information that may be out-of-date (from the last time
|
||||
/// the wallet's output set was refreshed against the node).
|
||||
/// * `tx_id` - If `Some(i)`, only return the outputs associated with
|
||||
/// the transaction log entry of id `i`.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `(bool, Vec<PaymentCommitMapping>)` - A tuple:
|
||||
/// * The first `bool` element indicates whether the data was successfully
|
||||
/// refreshed from the node (note this may be false even if the `refresh_from_node`
|
||||
/// argument was set to `true`.
|
||||
/// * The second element contains a vector of
|
||||
/// [PaymentCommitMapping](../grin_wallet_libwallet/types/struct.PaymentCommitMapping.html)
|
||||
/// of which each element is a mapping between the wallet's internal
|
||||
/// [PaymentData](../grin_wallet_libwallet/types/struct.Output.html)
|
||||
/// and the Output commitment
|
||||
///
|
||||
/// # Example
|
||||
/// Set up as in [`new`](struct.Owner.html#method.new) method above.
|
||||
/// ```
|
||||
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
|
||||
///
|
||||
/// let api_owner = Owner::new(wallet.clone());
|
||||
/// let update_from_node = true;
|
||||
/// let tx_id = None;
|
||||
///
|
||||
/// let result = api_owner.retrieve_payments(update_from_node, tx_id);
|
||||
///
|
||||
/// if let Ok((was_updated, payment_mappings)) = result {
|
||||
/// //...
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub fn retrieve_payments(
|
||||
&self,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<Uuid>,
|
||||
) -> Result<(bool, Vec<PaymentCommitMapping>), Error> {
|
||||
let mut w = self.wallet.lock();
|
||||
w.open_with_credentials()?;
|
||||
let res = owner::retrieve_payments(&mut *w, refresh_from_node, tx_id);
|
||||
w.close()?;
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns a list of [Transaction Log Entries](../grin_wallet_libwallet/types/struct.TxLogEntry.html)
|
||||
/// from the active account in the wallet.
|
||||
///
|
||||
|
|
|
@ -162,6 +162,7 @@ pub trait OwnerRpc {
|
|||
"mmr_index": null,
|
||||
"n_child": 0,
|
||||
"root_key_id": "0200000000000000000000000000000000",
|
||||
"slate_id": null,
|
||||
"status": "Unspent",
|
||||
"tx_log_entry": 0,
|
||||
"value": "60000000000"
|
||||
|
@ -178,6 +179,7 @@ pub trait OwnerRpc {
|
|||
"mmr_index": null,
|
||||
"n_child": 1,
|
||||
"root_key_id": "0200000000000000000000000000000000",
|
||||
"slate_id": null,
|
||||
"status": "Unspent",
|
||||
"tx_log_entry": 1,
|
||||
"value": "60000000000"
|
||||
|
|
|
@ -433,6 +433,20 @@ pub fn outputs(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn payments(
|
||||
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
||||
g_args: &GlobalArgs,
|
||||
dark_scheme: bool,
|
||||
) -> Result<(), Error> {
|
||||
controller::owner_single_use(wallet.clone(), |api| {
|
||||
let res = api.node_height()?;
|
||||
let (validated, outputs) = api.retrieve_payments(true, None)?;
|
||||
display::payments(&g_args.account, res.height, validated, outputs, dark_scheme)?;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Txs command args
|
||||
pub struct TxsArgs {
|
||||
pub id: Option<u32>,
|
||||
|
@ -462,8 +476,22 @@ pub fn txs(
|
|||
let (_, outputs) = api.retrieve_outputs(true, false, args.id)?;
|
||||
display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?;
|
||||
// should only be one here, but just in case
|
||||
for tx in txs {
|
||||
display::tx_messages(&tx, dark_scheme)?;
|
||||
for tx in &txs {
|
||||
let (_, outputs) = api.retrieve_payments(true, tx.tx_slate_id)?;
|
||||
if outputs.len() > 0 {
|
||||
display::payments(
|
||||
&g_args.account,
|
||||
res.height,
|
||||
validated,
|
||||
outputs,
|
||||
dark_scheme,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// should only be one here, but just in case
|
||||
for tx in &txs {
|
||||
display::tx_messages(tx, dark_scheme)?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
use crate::core::core::{self, amount_to_hr_string};
|
||||
use crate::core::global;
|
||||
use crate::libwallet::types::{
|
||||
AcctPathMapping, OutputCommitMapping, OutputStatus, TxLogEntry, WalletInfo,
|
||||
AcctPathMapping, OutputCommitMapping, OutputStatus, PaymentCommitMapping, TxLogEntry,
|
||||
WalletInfo,
|
||||
};
|
||||
use crate::libwallet::Error;
|
||||
use crate::util;
|
||||
|
@ -119,6 +120,89 @@ pub fn outputs(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Display payments in a pretty way
|
||||
pub fn payments(
|
||||
account: &str,
|
||||
cur_height: u64,
|
||||
validated: bool,
|
||||
outputs: Vec<PaymentCommitMapping>,
|
||||
dark_background_color_scheme: bool,
|
||||
) -> Result<(), Error> {
|
||||
let title = format!(
|
||||
"Wallet Payments - Account '{}' - Block Height: {}",
|
||||
account, cur_height
|
||||
);
|
||||
println!();
|
||||
let mut t = term::stdout().unwrap();
|
||||
t.fg(term::color::MAGENTA).unwrap();
|
||||
writeln!(t, "{}", title).unwrap();
|
||||
t.reset().unwrap();
|
||||
|
||||
let mut table = table!();
|
||||
|
||||
table.set_titles(row![
|
||||
bMG->"Output Commitment",
|
||||
bMG->"Block Height",
|
||||
bMG->"Locked Until",
|
||||
bMG->"Status",
|
||||
bMG->"# Confirms",
|
||||
bMG->"Value",
|
||||
bMG->"Shared Transaction Id"
|
||||
]);
|
||||
|
||||
for payment in outputs {
|
||||
let commit = format!("{}", util::to_hex(payment.commit.as_ref().to_vec()));
|
||||
let out = payment.output;
|
||||
|
||||
let height = format!("{}", out.height);
|
||||
let lock_height = format!("{}", out.lock_height);
|
||||
let status = format!("{}", out.status);
|
||||
|
||||
let num_confirmations = format!("{}", out.num_confirmations(cur_height));
|
||||
let value = if out.value == 0 {
|
||||
"unknown".to_owned()
|
||||
} else {
|
||||
format!("{}", core::amount_to_hr_string(out.value, false))
|
||||
};
|
||||
let slate_id = format!("{}", out.slate_id);
|
||||
|
||||
if dark_background_color_scheme {
|
||||
table.add_row(row![
|
||||
bFC->commit,
|
||||
bFB->height,
|
||||
bFB->lock_height,
|
||||
bFR->status,
|
||||
bFB->num_confirmations,
|
||||
bFG->value,
|
||||
bFC->slate_id,
|
||||
]);
|
||||
} else {
|
||||
table.add_row(row![
|
||||
bFD->commit,
|
||||
bFB->height,
|
||||
bFB->lock_height,
|
||||
bFR->status,
|
||||
bFB->num_confirmations,
|
||||
bFG->value,
|
||||
bFD->slate_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
|
||||
table.printstd();
|
||||
println!();
|
||||
|
||||
if !validated {
|
||||
println!(
|
||||
"\nWARNING: Wallet failed to verify data. \
|
||||
The above is from local cache and possibly invalid! \
|
||||
(is your `grin server` offline or broken?)"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Display transaction log in a pretty way
|
||||
pub fn txs(
|
||||
account: &str,
|
||||
|
|
|
@ -430,6 +430,16 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> {
|
|||
assert_eq!(output_mappings.len(), 3);
|
||||
assert_eq!(locked_count, 2);
|
||||
assert_eq!(unconfirmed_count, 1);
|
||||
// check the payments are as expected
|
||||
unconfirmed_count = 0;
|
||||
let (_, payments) = api.retrieve_payments(false, tx.unwrap().tx_slate_id)?;
|
||||
for p in &payments {
|
||||
if p.output.status == OutputStatus::Unconfirmed {
|
||||
unconfirmed_count = unconfirmed_count + 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(payments.len(), 1);
|
||||
assert_eq!(unconfirmed_count, 1);
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
@ -450,6 +460,16 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> {
|
|||
}
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(unconfirmed_count, 1);
|
||||
// check the payments are as expected: receiver don't have this.
|
||||
unconfirmed_count = 0;
|
||||
let (_, payments) = api.retrieve_payments(false, tx.unwrap().tx_slate_id)?;
|
||||
for p in &payments {
|
||||
if p.output.status == OutputStatus::Unconfirmed {
|
||||
unconfirmed_count = unconfirmed_count + 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(payments.len(), 0);
|
||||
assert_eq!(unconfirmed_count, 0);
|
||||
let (refreshed, wallet2_info) = api.retrieve_summary_info(true, 1)?;
|
||||
assert!(refreshed);
|
||||
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
|
||||
|
|
|
@ -42,6 +42,8 @@ pub const DB_DIR: &'static str = "db";
|
|||
pub const TX_SAVE_DIR: &'static str = "saved_txs";
|
||||
|
||||
const OUTPUT_PREFIX: u8 = 'o' as u8;
|
||||
const PAYMENT_PREFIX: u8 = 'P' as u8;
|
||||
const PAYMENT_COMMITS_PREFIX: u8 = 'Q' as u8;
|
||||
const DERIV_PREFIX: u8 = 'd' as u8;
|
||||
const CONFIRMED_HEIGHT_PREFIX: u8 = 'c' as u8;
|
||||
const PRIVATE_TX_CONTEXT_PREFIX: u8 = 'p' as u8;
|
||||
|
@ -238,6 +240,20 @@ where
|
|||
Box::new(self.db.iter(&[OUTPUT_PREFIX]).unwrap())
|
||||
}
|
||||
|
||||
fn get_payment_log_commits(&self, u: &Uuid) -> Result<Option<PaymentCommits>, Error> {
|
||||
let key = to_key(PAYMENT_COMMITS_PREFIX, &mut u.as_bytes().to_vec());
|
||||
self.db.get_ser(&key).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_payment_log_entry(&self, commit: String) -> Result<Option<PaymentData>, Error> {
|
||||
let key = to_key(PAYMENT_PREFIX, &mut commit.as_bytes().to_vec());
|
||||
self.db.get_ser(&key).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn payment_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = PaymentData> + 'a> {
|
||||
Box::new(self.db.iter(&[PAYMENT_PREFIX]).unwrap())
|
||||
}
|
||||
|
||||
fn get_tx_log_entry(&self, u: &Uuid) -> Result<Option<TxLogEntry>, Error> {
|
||||
let key = to_key(TX_LOG_ENTRY_PREFIX, &mut u.as_bytes().to_vec());
|
||||
self.db.get_ser(&key).map_err(|e| e.into())
|
||||
|
@ -380,7 +396,7 @@ where
|
|||
}
|
||||
|
||||
fn save(&mut self, out: OutputData) -> Result<(), Error> {
|
||||
// Save the output data to the db.
|
||||
// Save the self output data to the db.
|
||||
{
|
||||
let key = match out.mmr_index {
|
||||
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec(), i),
|
||||
|
@ -392,6 +408,27 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn save_payment_commits(&mut self, u: &Uuid, commits: PaymentCommits) -> Result<(), Error> {
|
||||
// Save the payment commits list data to the db.
|
||||
{
|
||||
let key = to_key(PAYMENT_COMMITS_PREFIX, &mut u.as_bytes().to_vec());
|
||||
self.db.borrow().as_ref().unwrap().put_ser(&key, &commits)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_payment(&mut self, out: PaymentData) -> Result<(), Error> {
|
||||
// Save the payment output data to the db.
|
||||
{
|
||||
let commit = out.commit.clone();
|
||||
let key = to_key(PAYMENT_PREFIX, &mut commit.as_bytes().to_vec());
|
||||
self.db.borrow().as_ref().unwrap().put_ser(&key, &out)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
|
||||
let key = match mmr_index {
|
||||
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
|
||||
|
@ -404,6 +441,24 @@ where
|
|||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_payment_commits(&self, u: &Uuid) -> Result<PaymentCommits, Error> {
|
||||
let key = to_key(PAYMENT_COMMITS_PREFIX, &mut u.as_bytes().to_vec());
|
||||
option_to_not_found(
|
||||
self.db.borrow().as_ref().unwrap().get_ser(&key),
|
||||
&format!("slate_id: {}", u.to_string()),
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_payment_log_entry(&self, commit: String) -> Result<PaymentData, Error> {
|
||||
let key = to_key(PAYMENT_PREFIX, &mut commit.as_bytes().to_vec());
|
||||
option_to_not_found(
|
||||
self.db.borrow().as_ref().unwrap().get_ser(&key),
|
||||
&format!("key: {:?}", commit),
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>> {
|
||||
Box::new(
|
||||
self.db
|
||||
|
|
|
@ -43,6 +43,8 @@ pub const DB_DIR: &'static str = "db";
|
|||
pub const TX_SAVE_DIR: &'static str = "saved_txs";
|
||||
|
||||
const OUTPUT_PREFIX: u8 = 'o' as u8;
|
||||
const PAYMENT_PREFIX: u8 = 'P' as u8;
|
||||
const PAYMENT_COMMITS_PREFIX: u8 = 'Q' as u8;
|
||||
const DERIV_PREFIX: u8 = 'd' as u8;
|
||||
const CONFIRMED_HEIGHT_PREFIX: u8 = 'c' as u8;
|
||||
const PRIVATE_TX_CONTEXT_PREFIX: u8 = 'p' as u8;
|
||||
|
@ -239,6 +241,20 @@ where
|
|||
Box::new(self.db.iter(&[OUTPUT_PREFIX]).unwrap().map(|o| o.1))
|
||||
}
|
||||
|
||||
fn get_payment_log_commits(&self, u: &Uuid) -> Result<Option<PaymentCommits>, Error> {
|
||||
let key = to_key(PAYMENT_COMMITS_PREFIX, &mut u.as_bytes().to_vec());
|
||||
self.db.get_ser(&key).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_payment_log_entry(&self, commit: String) -> Result<Option<PaymentData>, Error> {
|
||||
let key = to_key(PAYMENT_PREFIX, &mut commit.as_bytes().to_vec());
|
||||
self.db.get_ser(&key).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn payment_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = PaymentData> + 'a> {
|
||||
Box::new(self.db.iter(&[PAYMENT_PREFIX]).unwrap().map(|o| o.1))
|
||||
}
|
||||
|
||||
fn get_tx_log_entry(&self, u: &Uuid) -> Result<Option<TxLogEntry>, Error> {
|
||||
let key = to_key(TX_LOG_ENTRY_PREFIX, &mut u.as_bytes().to_vec());
|
||||
self.db.get_ser(&key).map_err(|e| e.into())
|
||||
|
@ -386,7 +402,7 @@ where
|
|||
}
|
||||
|
||||
fn save(&mut self, out: OutputData) -> Result<(), Error> {
|
||||
// Save the output data to the db.
|
||||
// Save the self output data to the db.
|
||||
{
|
||||
let key = match out.mmr_index {
|
||||
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut out.key_id.to_bytes().to_vec(), i),
|
||||
|
@ -398,6 +414,27 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn save_payment_commits(&mut self, u: &Uuid, commits: PaymentCommits) -> Result<(), Error> {
|
||||
// Save the payment commits list data to the db.
|
||||
{
|
||||
let key = to_key(PAYMENT_COMMITS_PREFIX, &mut u.as_bytes().to_vec());
|
||||
self.db.borrow().as_ref().unwrap().put_ser(&key, &commits)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_payment(&mut self, out: PaymentData) -> Result<(), Error> {
|
||||
// Save the payment output data to the db.
|
||||
{
|
||||
let commit = out.commit.clone();
|
||||
let key = to_key(PAYMENT_PREFIX, &mut commit.as_bytes().to_vec());
|
||||
self.db.borrow().as_ref().unwrap().put_ser(&key, &out)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error> {
|
||||
let key = match mmr_index {
|
||||
Some(i) => to_key_u64(OUTPUT_PREFIX, &mut id.to_bytes().to_vec(), *i),
|
||||
|
@ -410,6 +447,24 @@ where
|
|||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_payment_commits(&self, u: &Uuid) -> Result<PaymentCommits, Error> {
|
||||
let key = to_key(PAYMENT_COMMITS_PREFIX, &mut u.as_bytes().to_vec());
|
||||
option_to_not_found(
|
||||
self.db.borrow().as_ref().unwrap().get_ser(&key),
|
||||
&format!("slate_id: {}", u.to_string()),
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_payment_log_entry(&self, commit: String) -> Result<PaymentData, Error> {
|
||||
let key = to_key(PAYMENT_PREFIX, &mut commit.as_bytes().to_vec());
|
||||
option_to_not_found(
|
||||
self.db.borrow().as_ref().unwrap().get_ser(&key),
|
||||
&format!("key: {:?}", commit),
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>> {
|
||||
Box::new(
|
||||
self.db
|
||||
|
|
|
@ -25,8 +25,8 @@ use crate::grin_keychain::{Identifier, Keychain};
|
|||
use crate::internal::{keys, selection, tx, updater};
|
||||
use crate::slate::Slate;
|
||||
use crate::types::{
|
||||
AcctPathMapping, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, TxLogEntry,
|
||||
TxWrapper, WalletBackend, WalletInfo,
|
||||
AcctPathMapping, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping,
|
||||
PaymentCommitMapping, TxLogEntry, TxWrapper, WalletBackend, WalletInfo,
|
||||
};
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
|
@ -83,10 +83,29 @@ where
|
|||
|
||||
Ok((
|
||||
validated,
|
||||
updater::retrieve_outputs(&mut *w, include_spent, tx_id, Some(&parent_key_id))?,
|
||||
updater::retrieve_outputs(&mut *w, include_spent, tx_id, None, Some(&parent_key_id))?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns a list of payment outputs from the active account in the wallet.
|
||||
pub fn retrieve_payments<T: ?Sized, C, K>(
|
||||
w: &mut T,
|
||||
refresh_from_node: bool,
|
||||
tx_id: Option<Uuid>,
|
||||
) -> Result<(bool, Vec<PaymentCommitMapping>), Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
let mut validated = false;
|
||||
if refresh_from_node {
|
||||
validated = update_outputs(w, false);
|
||||
}
|
||||
|
||||
Ok((validated, updater::retrieve_payments(w, tx_id)?))
|
||||
}
|
||||
|
||||
/// Retrieve txs
|
||||
pub fn retrieve_txs<T: ?Sized, C, K>(
|
||||
w: &mut T,
|
||||
|
|
|
@ -215,6 +215,7 @@ where
|
|||
lock_height: output.lock_height,
|
||||
is_coinbase: output.is_coinbase,
|
||||
tx_log_entry: Some(log_id),
|
||||
slate_id: None,
|
||||
});
|
||||
|
||||
let max_child_index = found_parents.get(&parent_key_id).unwrap().clone();
|
||||
|
@ -283,7 +284,7 @@ where
|
|||
|
||||
// Now, get all outputs owned by this wallet (regardless of account)
|
||||
let wallet_outputs = {
|
||||
let res = updater::retrieve_outputs(&mut *wallet, true, None, None)?;
|
||||
let res = updater::retrieve_outputs(&mut *wallet, true, None, None, None)?;
|
||||
res
|
||||
};
|
||||
|
||||
|
|
|
@ -155,6 +155,7 @@ where
|
|||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
tx_log_entry: Some(log_id),
|
||||
slate_id: Some(slate_id.clone()),
|
||||
})?;
|
||||
}
|
||||
batch.save_tx_log_entry(t.clone(), &parent_key_id)?;
|
||||
|
@ -224,6 +225,7 @@ where
|
|||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
tx_log_entry: Some(log_id),
|
||||
slate_id: Some(slate_id),
|
||||
})?;
|
||||
batch.save_tx_log_entry(t, &parent_key_id)?;
|
||||
batch.commit()?;
|
||||
|
|
|
@ -17,10 +17,14 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::grin_keychain::{Identifier, Keychain};
|
||||
use crate::grin_util::secp::pedersen;
|
||||
use crate::grin_util::to_hex;
|
||||
use crate::grin_util::Mutex;
|
||||
use crate::internal::{selection, updater};
|
||||
use crate::slate::Slate;
|
||||
use crate::types::{Context, NodeClient, TxLogEntryType, WalletBackend};
|
||||
use crate::types::{
|
||||
Context, NodeClient, OutputStatus, PaymentCommits, PaymentData, TxLogEntryType, WalletBackend,
|
||||
};
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
/// static for incrementing test UUIDs
|
||||
|
@ -220,6 +224,63 @@ where
|
|||
)?;
|
||||
// Final transaction can be built by anyone at this stage
|
||||
slate.finalize(wallet.keychain())?;
|
||||
|
||||
let parent_key_id = Some(&context.parent_key_id);
|
||||
|
||||
// Get the change output/s from database
|
||||
let changes = updater::retrieve_outputs(wallet, false, None, Some(slate.id), parent_key_id)?;
|
||||
let change_commits = changes
|
||||
.iter()
|
||||
.map(|oc| oc.commit.clone())
|
||||
.collect::<Vec<pedersen::Commitment>>();
|
||||
|
||||
// Find the payment output/s
|
||||
let mut outputs: Vec<pedersen::Commitment> = Vec::new();
|
||||
for output in slate.tx.outputs() {
|
||||
if !change_commits.contains(&output.commit) {
|
||||
outputs.insert(0, output.commit.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// sender save the payment output
|
||||
let mut batch = wallet.batch()?;
|
||||
batch.save_payment_commits(
|
||||
&slate.id,
|
||||
PaymentCommits {
|
||||
commits: outputs
|
||||
.iter()
|
||||
.map(|c| to_hex(c.as_ref().to_vec()))
|
||||
.collect::<Vec<String>>(),
|
||||
slate_id: slate.id,
|
||||
},
|
||||
)?;
|
||||
// todo: multiple receivers transaction
|
||||
if outputs.len() > 1 {
|
||||
for output in outputs {
|
||||
let payment_output = to_hex(output.clone().as_ref().to_vec());
|
||||
batch.save_payment(PaymentData {
|
||||
commit: payment_output,
|
||||
value: 0, // '0' means unknown here, since '0' value is impossible for an output.
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: slate.height,
|
||||
lock_height: 0,
|
||||
slate_id: slate.id,
|
||||
})?;
|
||||
}
|
||||
} else if outputs.len() == 1 {
|
||||
let payment_output = to_hex(outputs.first().clone().unwrap().as_ref().to_vec());
|
||||
batch.save_payment(PaymentData {
|
||||
commit: payment_output,
|
||||
value: slate.amount,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: slate.height,
|
||||
lock_height: 0,
|
||||
slate_id: slate.id,
|
||||
})?;
|
||||
} else {
|
||||
warn!("complete_tx - no 'payment' output! is this a sending to self for test purpose?");
|
||||
}
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -253,7 +314,7 @@ where
|
|||
return Err(ErrorKind::TransactionNotCancellable(tx_id_string))?;
|
||||
}
|
||||
// get outputs associated with tx
|
||||
let res = updater::retrieve_outputs(wallet, false, Some(tx.id), Some(&parent_key_id))?;
|
||||
let res = updater::retrieve_outputs(wallet, false, Some(tx.id), None, Some(&parent_key_id))?;
|
||||
let outputs = res.iter().map(|m| m.output.clone()).collect();
|
||||
updater::cancel_tx_and_outputs(wallet, tx, outputs, parent_key_id)?;
|
||||
Ok(())
|
||||
|
|
|
@ -28,15 +28,16 @@ use crate::grin_util as util;
|
|||
use crate::grin_util::secp::pedersen;
|
||||
use crate::internal::keys;
|
||||
use crate::types::{
|
||||
BlockFees, CbData, NodeClient, OutputCommitMapping, OutputData, OutputStatus, TxLogEntry,
|
||||
TxLogEntryType, WalletBackend, WalletInfo,
|
||||
BlockFees, CbData, NodeClient, OutputCommitMapping, OutputData, OutputStatus,
|
||||
PaymentCommitMapping, TxLogEntry, TxLogEntryType, WalletBackend, WalletInfo,
|
||||
};
|
||||
|
||||
/// Retrieve all of the outputs (doesn't attempt to update from node)
|
||||
/// Retrieve all of the self outputs (doesn't attempt to update from node)
|
||||
pub fn retrieve_outputs<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
show_spent: bool,
|
||||
tx_id: Option<u32>,
|
||||
slate_id: Option<Uuid>,
|
||||
parent_key_id: Option<&Identifier>,
|
||||
) -> Result<Vec<OutputCommitMapping>, Error>
|
||||
where
|
||||
|
@ -58,6 +59,14 @@ where
|
|||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
// only include outputs with a given slate_id if provided
|
||||
if let Some(id) = slate_id {
|
||||
outputs = outputs
|
||||
.into_iter()
|
||||
.filter(|out| out.slate_id == Some(id))
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
if let Some(k) = parent_key_id {
|
||||
outputs = outputs
|
||||
.iter()
|
||||
|
@ -82,6 +91,38 @@ where
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
/// Retrieve all of the payment outputs (doesn't attempt to update from node)
|
||||
pub fn retrieve_payments<T: ?Sized, C, K>(
|
||||
wallet: &mut T,
|
||||
tx_id: Option<Uuid>,
|
||||
) -> Result<Vec<PaymentCommitMapping>, Error>
|
||||
where
|
||||
T: WalletBackend<C, K>,
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// just read the wallet here, no need for a write lock
|
||||
let mut outputs = wallet.payment_log_iter().collect::<Vec<_>>();
|
||||
|
||||
// only include outputs with a given tx_id if provided
|
||||
if let Some(id) = tx_id {
|
||||
outputs = outputs
|
||||
.into_iter()
|
||||
.filter(|out| out.slate_id == id)
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
let res = outputs
|
||||
.into_iter()
|
||||
.map(|output| {
|
||||
let commit =
|
||||
pedersen::Commitment::from_vec(util::from_hex(output.commit.clone()).unwrap());
|
||||
PaymentCommitMapping { output, commit }
|
||||
})
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Retrieve all of the transaction entries, or a particular entry
|
||||
/// if `parent_key_id` is set, only return entries from that key
|
||||
pub fn retrieve_txs<T: ?Sized, C, K>(
|
||||
|
@ -291,6 +332,21 @@ where
|
|||
t.confirmed = true;
|
||||
batch.save_tx_log_entry(t, &parent_key_id)?;
|
||||
}
|
||||
|
||||
// if there's a related payment output being confirmed, refresh that payment log
|
||||
if let Some(slate_id) = output.slate_id {
|
||||
if let Ok(commits) = batch.get_payment_commits(&slate_id) {
|
||||
for commit in commits.commits {
|
||||
if let Ok(mut payment) =
|
||||
batch.get_payment_log_entry(commit.clone())
|
||||
{
|
||||
payment.height = o.1;
|
||||
payment.mark_confirmed();
|
||||
batch.save_payment(payment)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output.height = o.1;
|
||||
output.mark_unspent();
|
||||
|
@ -413,6 +469,7 @@ where
|
|||
locked_total += out.value;
|
||||
}
|
||||
OutputStatus::Spent => {}
|
||||
OutputStatus::Confirmed => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -490,6 +547,7 @@ where
|
|||
lock_height: lock_height,
|
||||
is_coinbase: true,
|
||||
tx_log_entry: None,
|
||||
slate_id: None,
|
||||
})?;
|
||||
batch.commit()?;
|
||||
}
|
||||
|
|
|
@ -85,10 +85,19 @@ where
|
|||
/// return the parent path
|
||||
fn parent_key_id(&mut self) -> Identifier;
|
||||
|
||||
/// Iterate over all output data stored by the backend
|
||||
/// Iterate over all self output data stored by the backend
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = OutputData> + 'a>;
|
||||
|
||||
/// Get output data by id
|
||||
/// Get payment commits list by slate id
|
||||
fn get_payment_log_commits(&self, u: &Uuid) -> Result<Option<PaymentCommits>, Error>;
|
||||
|
||||
/// Get payment output data by commit
|
||||
fn get_payment_log_entry(&self, commit: String) -> Result<Option<PaymentData>, Error>;
|
||||
|
||||
/// Iterate over all payment output data stored by the backend
|
||||
fn payment_log_iter<'a>(&'a self) -> Box<dyn Iterator<Item = PaymentData> + 'a>;
|
||||
|
||||
/// Get self owned output data by id
|
||||
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
|
||||
|
||||
/// Get an (Optional) tx log entry by uuid
|
||||
|
@ -140,12 +149,24 @@ where
|
|||
/// Return the keychain being used
|
||||
fn keychain(&mut self) -> &mut K;
|
||||
|
||||
/// Add or update data about an output to the backend
|
||||
/// Add or update data about a self owned output to the backend
|
||||
fn save(&mut self, out: OutputData) -> Result<(), Error>;
|
||||
|
||||
/// Gets output data by id
|
||||
/// Add or update data about a payment output to the backend
|
||||
fn save_payment(&mut self, out: PaymentData) -> Result<(), Error>;
|
||||
|
||||
/// Add or update data about a payment commits list to the backend
|
||||
fn save_payment_commits(&mut self, u: &Uuid, commits: PaymentCommits) -> Result<(), Error>;
|
||||
|
||||
/// Gets self owned output data by id
|
||||
fn get(&self, id: &Identifier, mmr_index: &Option<u64>) -> Result<OutputData, Error>;
|
||||
|
||||
/// Gets payment commits list by slate id
|
||||
fn get_payment_commits(&self, u: &Uuid) -> Result<PaymentCommits, Error>;
|
||||
|
||||
/// Gets payment output data by commit
|
||||
fn get_payment_log_entry(&self, commit: String) -> Result<PaymentData, Error>;
|
||||
|
||||
/// Iterate over all output data stored by the backend
|
||||
fn iter(&self) -> Box<dyn Iterator<Item = OutputData>>;
|
||||
|
||||
|
@ -270,6 +291,8 @@ pub struct OutputData {
|
|||
pub is_coinbase: bool,
|
||||
/// Optional corresponding internal entry in tx entry log
|
||||
pub tx_log_entry: Option<u32>,
|
||||
/// Unique transaction ID, selected by sender
|
||||
pub slate_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for OutputData {
|
||||
|
@ -346,6 +369,94 @@ impl OutputData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about a payment output that's being tracked by the wallet.
|
||||
/// It belongs to the receiver, and it's paid by this wallet.
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct PaymentData {
|
||||
/// The actual commit
|
||||
pub commit: String,
|
||||
/// Value of the output
|
||||
pub value: u64,
|
||||
/// Current status of the output
|
||||
pub status: OutputStatus,
|
||||
/// Height of the output
|
||||
pub height: u64,
|
||||
/// Height we are locked until
|
||||
pub lock_height: u64,
|
||||
/// Unique transaction ID, selected by sender
|
||||
pub slate_id: Uuid,
|
||||
}
|
||||
|
||||
impl ser::Writeable for PaymentData {
|
||||
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable for PaymentData {
|
||||
fn read(reader: &mut dyn ser::Reader) -> Result<PaymentData, ser::Error> {
|
||||
let data = reader.read_bytes_len_prefix()?;
|
||||
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
|
||||
}
|
||||
}
|
||||
|
||||
impl PaymentData {
|
||||
/// How many confirmations has this output received?
|
||||
/// If height == 0 then we are either Unconfirmed or the output was
|
||||
/// cut-through
|
||||
/// so we do not actually know how many confirmations this output had (and
|
||||
/// never will).
|
||||
pub fn num_confirmations(&self, current_height: u64) -> u64 {
|
||||
if self.height > current_height {
|
||||
return 0;
|
||||
}
|
||||
if self.status == OutputStatus::Unconfirmed {
|
||||
0
|
||||
} else {
|
||||
// if an output has height n and we are at block n
|
||||
// then we have a single confirmation (the block it originated in)
|
||||
1 + (current_height - self.height)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks this output as confirmed if it was previously unconfirmed
|
||||
pub fn mark_confirmed(&mut self) {
|
||||
match self.status {
|
||||
OutputStatus::Unconfirmed => self.status = OutputStatus::Confirmed,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the payment commit/s in one tx that's being tracked by the wallet.
|
||||
/// They belong to the receiver/s, and they're paid by this wallet.
|
||||
///
|
||||
/// Note: because lmdb can't have multiple keys to same value, we have to use this to find
|
||||
/// the commit lists by the slate id, in case we support multiple receivers in one tx in the future.
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PaymentCommits {
|
||||
/// The actual commit/s
|
||||
pub commits: Vec<String>,
|
||||
/// Unique transaction ID, selected by sender
|
||||
pub slate_id: Uuid,
|
||||
}
|
||||
|
||||
impl ser::Writeable for PaymentCommits {
|
||||
fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_bytes(&serde_json::to_vec(self).map_err(|_| ser::Error::CorruptedData)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable for PaymentCommits {
|
||||
fn read(reader: &mut dyn ser::Reader) -> Result<PaymentCommits, ser::Error> {
|
||||
let data = reader.read_bytes_len_prefix()?;
|
||||
serde_json::from_slice(&data[..]).map_err(|_| ser::Error::CorruptedData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Status of an output that's being tracked by the wallet. Can either be
|
||||
/// unconfirmed, spent, unspent, or locked (when it's been used to generate
|
||||
/// a transaction but we don't have confirmation that the transaction was
|
||||
|
@ -360,6 +471,8 @@ pub enum OutputStatus {
|
|||
Locked,
|
||||
/// Spent
|
||||
Spent,
|
||||
/// Confirmed
|
||||
Confirmed,
|
||||
}
|
||||
|
||||
impl fmt::Display for OutputStatus {
|
||||
|
@ -369,6 +482,7 @@ impl fmt::Display for OutputStatus {
|
|||
OutputStatus::Unspent => write!(f, "Unspent"),
|
||||
OutputStatus::Locked => write!(f, "Locked"),
|
||||
OutputStatus::Spent => write!(f, "Spent"),
|
||||
OutputStatus::Confirmed => write!(f, "Confirmed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -853,6 +967,19 @@ pub struct OutputCommitMapping {
|
|||
pub commit: pedersen::Commitment,
|
||||
}
|
||||
|
||||
/// Map PaymentData to commits
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct PaymentCommitMapping {
|
||||
/// Payment Data
|
||||
pub output: PaymentData,
|
||||
/// The commit
|
||||
#[serde(
|
||||
serialize_with = "secp_ser::as_hex",
|
||||
deserialize_with = "secp_ser::commitment_from_hex"
|
||||
)]
|
||||
pub commit: pedersen::Commitment,
|
||||
}
|
||||
|
||||
/// Node height result
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct NodeHeightResult {
|
||||
|
|
|
@ -624,6 +624,14 @@ pub fn wallet_command(
|
|||
&global_wallet_args,
|
||||
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||
),
|
||||
("payments", Some(_)) => {
|
||||
info!("payments command received");
|
||||
command::payments(
|
||||
inst_wallet(),
|
||||
&global_wallet_args,
|
||||
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||
)
|
||||
}
|
||||
("txs", Some(args)) => {
|
||||
let a = arg_parse!(parse_txs_args(&args));
|
||||
command::txs(
|
||||
|
|
|
@ -501,6 +501,10 @@ mod wallet_tests {
|
|||
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "outputs"];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// payments
|
||||
let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "payments"];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// let logging finish
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
Ok(())
|
||||
|
|
|
@ -163,7 +163,9 @@ subcommands:
|
|||
short: f
|
||||
long: fluff
|
||||
- outputs:
|
||||
about: Raw wallet output info (list of outputs)
|
||||
about: Raw wallet self owned output info (list of outputs)
|
||||
- payments:
|
||||
about: Raw wallet payment output info (list of outputs)
|
||||
- txs:
|
||||
about: Display transaction information
|
||||
args:
|
||||
|
|
Loading…
Reference in a new issue