Refactor tx lib to be more Sender-Recipient agnostic (#2502)

* move slate initialisation outside of selection function

* refactor internal tx lib to support recipient (or anyone else) first models

* rustfmt

* rework init api function

* rustfmt

* refactor wallet lock closures to a defined fnmut type

* rustfmt

* comments for clarification
This commit is contained in:
Yeastplume 2019-02-01 11:35:37 +00:00 committed by GitHub
parent 7646e71810
commit 130675e017
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 187 additions and 192 deletions

View file

@ -41,8 +41,8 @@ use crate::core::ser;
use crate::keychain::{Identifier, Keychain}; use crate::keychain::{Identifier, Keychain};
use crate::libwallet::internal::{keys, tx, updater}; use crate::libwallet::internal::{keys, tx, updater};
use crate::libwallet::types::{ use crate::libwallet::types::{
AcctPathMapping, BlockFees, CbData, NodeClient, OutputData, TxLogEntry, TxLogEntryType, AcctPathMapping, BlockFees, CbData, NodeClient, OutputData, OutputLockFn, TxLogEntry,
TxWrapper, WalletBackend, WalletInfo, TxLogEntryType, TxWrapper, WalletBackend, WalletInfo,
}; };
use crate::libwallet::{Error, ErrorKind}; use crate::libwallet::{Error, ErrorKind};
use crate::util; use crate::util;
@ -623,13 +623,7 @@ where
num_change_outputs: usize, num_change_outputs: usize,
selection_strategy_is_use_all: bool, selection_strategy_is_use_all: bool,
message: Option<String>, message: Option<String>,
) -> Result< ) -> Result<(Slate, OutputLockFn<W, C, K>), Error> {
(
Slate,
impl FnOnce(&mut W, &Transaction) -> Result<(), Error>,
),
Error,
> {
let mut w = self.wallet.lock(); let mut w = self.wallet.lock();
w.open_with_credentials()?; w.open_with_credentials()?;
let parent_key_id = match src_acct_name { let parent_key_id = match src_acct_name {
@ -651,14 +645,17 @@ where
None => None, None => None,
}; };
let (slate, context, lock_fn) = tx::create_send_tx( let mut slate = tx::new_tx_slate(&mut *w, amount, 2)?;
let (context, lock_fn) = tx::add_inputs_to_slate(
&mut *w, &mut *w,
amount, &mut slate,
minimum_confirmations, minimum_confirmations,
max_outputs, max_outputs,
num_change_outputs, num_change_outputs,
selection_strategy_is_use_all, selection_strategy_is_use_all,
&parent_key_id, &parent_key_id,
0,
message, message,
)?; )?;
@ -678,11 +675,11 @@ where
pub fn tx_lock_outputs( pub fn tx_lock_outputs(
&mut self, &mut self,
slate: &Slate, slate: &Slate,
lock_fn: impl FnOnce(&mut W, &Transaction) -> Result<(), Error>, mut lock_fn: OutputLockFn<W, C, K>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut w = self.wallet.lock(); let mut w = self.wallet.lock();
w.open_with_credentials()?; w.open_with_credentials()?;
lock_fn(&mut *w, &slate.tx)?; lock_fn(&mut *w, &slate.tx, PhantomData, PhantomData)?;
Ok(()) Ok(())
} }
@ -694,7 +691,7 @@ where
let mut w = self.wallet.lock(); let mut w = self.wallet.lock();
w.open_with_credentials()?; w.open_with_credentials()?;
let context = w.get_private_context(slate.id.as_bytes())?; let context = w.get_private_context(slate.id.as_bytes())?;
tx::complete_tx(&mut *w, slate, &context)?; tx::complete_tx(&mut *w, slate, 0, &context)?;
tx::update_stored_tx(&mut *w, slate)?; tx::update_stored_tx(&mut *w, slate)?;
tx::update_message(&mut *w, slate)?; tx::update_message(&mut *w, slate)?;
{ {
@ -893,18 +890,11 @@ where
None => None, None => None,
}; };
let res = tx::receive_tx(&mut *w, slate, &parent_key_id, message); let (_, mut create_fn) =
tx::add_output_to_slate(&mut *w, slate, &parent_key_id, 1, message)?;
create_fn(&mut *w, &slate.tx, PhantomData, PhantomData)?;
tx::update_message(&mut *w, slate)?;
w.close()?; w.close()?;
if let Err(e) = res {
error!("api: receive_tx: failed with error: {}", e);
Err(e)
} else {
debug!(
"api: receive_tx: successfully received tx: {}",
slate.tx.hash()
);
Ok(()) Ok(())
} }
} }
}

View file

@ -20,60 +20,43 @@ use crate::keychain::{Identifier, Keychain};
use crate::libwallet::error::{Error, ErrorKind}; use crate::libwallet::error::{Error, ErrorKind};
use crate::libwallet::internal::keys; use crate::libwallet::internal::keys;
use crate::libwallet::types::*; use crate::libwallet::types::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData;
/// Initialize a transaction on the sender side, returns a corresponding /// Initialize a transaction on the sender side, returns a corresponding
/// libwallet transaction slate with the appropriate inputs selected, /// libwallet transaction slate with the appropriate inputs selected,
/// and saves the private wallet identifiers of our selected outputs /// and saves the private wallet identifiers of our selected outputs
/// into our transaction context /// into our transaction context
pub fn build_send_tx_slate<T: ?Sized, C, K>( pub fn build_send_tx<T: ?Sized, C, K>(
wallet: &mut T, wallet: &mut T,
num_participants: usize, slate: &mut Slate,
amount: u64,
current_height: u64,
minimum_confirmations: u64, minimum_confirmations: u64,
lock_height: u64,
max_outputs: usize, max_outputs: usize,
change_outputs: usize, change_outputs: usize,
selection_strategy_is_use_all: bool, selection_strategy_is_use_all: bool,
parent_key_id: Identifier, parent_key_id: Identifier,
) -> Result< ) -> Result<(Context, OutputLockFn<T, C, K>), Error>
(
Slate,
Context,
impl FnOnce(&mut T, &Transaction) -> Result<(), Error>,
),
Error,
>
where where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
C: NodeClient, C: NodeClient,
K: Keychain, K: Keychain,
{ {
let (elems, inputs, change_amounts_derivations, amount, fee) = select_send_tx( let (elems, inputs, change_amounts_derivations, fee) = select_send_tx(
wallet, wallet,
amount, slate.amount,
current_height, slate.height,
minimum_confirmations, minimum_confirmations,
lock_height, slate.lock_height,
max_outputs, max_outputs,
change_outputs, change_outputs,
selection_strategy_is_use_all, selection_strategy_is_use_all,
&parent_key_id, &parent_key_id,
)?; )?;
// Create public slate
let mut slate = Slate::blank(num_participants);
slate.amount = amount;
slate.height = current_height;
slate.lock_height = lock_height;
slate.fee = fee; slate.fee = fee;
let slate_id = slate.id.clone();
let keychain = wallet.keychain().clone(); let keychain = wallet.keychain().clone();
let blinding = slate.add_transaction_elements(&keychain, elems)?; let blinding = slate.add_transaction_elements(&keychain, elems)?;
// Create our own private context // Create our own private context
@ -98,18 +81,26 @@ where
); );
} }
let lock_inputs = context.get_inputs().clone(); let lock_inputs_in = context.get_inputs().clone();
let _lock_outputs = context.get_outputs().clone(); let _lock_outputs = context.get_outputs().clone();
let messages = Some(slate.participant_messages()); let messages_in = Some(slate.participant_messages());
let slate_id_in = slate.id.clone();
let height_in = slate.height;
// 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, tx: &Transaction| { let update_sender_wallet_fn =
move |wallet: &mut T, tx: &Transaction, _: PhantomData<C>, _: PhantomData<K>| {
let tx_entry = { let tx_entry = {
// These ensure the closure remains FnMut
let lock_inputs = lock_inputs_in.clone();
let messages = messages_in.clone();
let slate_id = slate_id_in.clone();
let height = height_in.clone();
let mut batch = wallet.batch()?; let mut batch = wallet.batch()?;
let log_id = batch.next_tx_log_id(&parent_key_id)?; let log_id = batch.next_tx_log_id(&parent_key_id)?;
let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxSent, log_id); let mut t = TxLogEntry::new(parent_key_id.clone(), TxLogEntryType::TxSent, log_id);
t.tx_slate_id = Some(slate_id); t.tx_slate_id = Some(slate_id.clone());
let filename = format!("{}.grintx", slate_id); let filename = format!("{}.grintx", slate_id);
t.stored_tx = Some(filename); t.stored_tx = Some(filename);
t.fee = Some(fee); t.fee = Some(fee);
@ -138,7 +129,7 @@ where
mmr_index: None, mmr_index: None,
value: change_amount.clone(), value: change_amount.clone(),
status: OutputStatus::Unconfirmed, status: OutputStatus::Unconfirmed,
height: current_height, height: height,
lock_height: 0, lock_height: 0,
is_coinbase: false, is_coinbase: false,
tx_log_entry: Some(log_id), tx_log_entry: Some(log_id),
@ -152,25 +143,18 @@ where
Ok(()) Ok(())
}; };
Ok((slate, context, update_sender_wallet_fn)) Ok((context, Box::new(update_sender_wallet_fn)))
} }
/// Creates a new output in the wallet for the recipient, /// Creates a new output in the wallet for the recipient,
/// returning the key of the fresh output and a closure /// returning the key of the fresh output and a closure
/// that actually performs the addition of the output to the /// that actually performs the addition of the output to the
/// wallet /// wallet
pub fn build_recipient_output_with_slate<T: ?Sized, C, K>( pub fn build_recipient_output<T: ?Sized, C, K>(
wallet: &mut T, wallet: &mut T,
slate: &mut Slate, slate: &mut Slate,
parent_key_id: Identifier, parent_key_id: Identifier,
) -> Result< ) -> Result<(Identifier, Context, OutputLockFn<T, C, K>), Error>
(
Identifier,
Context,
impl FnOnce(&mut T) -> Result<(), Error>,
),
Error,
>
where where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
C: NodeClient, C: NodeClient,
@ -197,11 +181,14 @@ where
); );
context.add_output(&key_id, &None); context.add_output(&key_id, &None);
let messages = Some(slate.participant_messages()); let messages_in = Some(slate.participant_messages());
// Create closure that adds the output to recipient's wallet // Create closure that adds the output to recipient's wallet
// (up to the caller to decide when to do) // (up to the caller to decide when to do)
let wallet_add_fn = move |wallet: &mut T| { let wallet_add_fn =
move |wallet: &mut T, _tx: &Transaction, _: PhantomData<C>, _: PhantomData<K>| {
// Ensure closure remains FnMut
let messages = messages_in.clone();
let commit = wallet.calc_commit_for_cache(amount, &key_id_inner)?; let commit = wallet.calc_commit_for_cache(amount, &key_id_inner)?;
let mut batch = wallet.batch()?; let mut batch = wallet.batch()?;
let log_id = batch.next_tx_log_id(&parent_key_id)?; let log_id = batch.next_tx_log_id(&parent_key_id)?;
@ -225,9 +212,11 @@ where
})?; })?;
batch.save_tx_log_entry(t, &parent_key_id)?; batch.save_tx_log_entry(t, &parent_key_id)?;
batch.commit()?; batch.commit()?;
//TODO: Check whether we want to call this
//wallet.store_tx(&format!("{}", t.tx_slate_id.unwrap()), tx)?;
Ok(()) Ok(())
}; };
Ok((key_id, context, wallet_add_fn)) Ok((key_id, context, Box::new(wallet_add_fn)))
} }
/// Builds a transaction to send to someone from the HD seed associated with the /// Builds a transaction to send to someone from the HD seed associated with the
@ -248,7 +237,6 @@ pub fn select_send_tx<T: ?Sized, C, K>(
Vec<Box<build::Append<K>>>, Vec<Box<build::Append<K>>>,
Vec<OutputData>, Vec<OutputData>,
Vec<(u64, Identifier, Option<u64>)>, // change amounts and derivations Vec<(u64, Identifier, Option<u64>)>, // change amounts and derivations
u64, // amount
u64, // fee u64, // fee
), ),
Error, Error,
@ -345,7 +333,7 @@ where
// on tx being sent (based on current chain height via api). // on tx being sent (based on current chain height via api).
parts.push(build::with_lock_height(lock_height)); parts.push(build::with_lock_height(lock_height));
Ok((parts, coins, change_amounts_derivations, amount, fee)) Ok((parts, coins, change_amounts_derivations, fee))
} }
/// Selects inputs and change for a transaction /// Selects inputs and change for a transaction

View file

@ -16,80 +16,52 @@
use uuid::Uuid; use uuid::Uuid;
use crate::core::core::Transaction;
use crate::core::libtx::slate::Slate; use crate::core::libtx::slate::Slate;
use crate::keychain::{Identifier, Keychain}; use crate::keychain::{Identifier, Keychain};
use crate::libwallet::internal::{selection, updater}; use crate::libwallet::internal::{selection, updater};
use crate::libwallet::types::{Context, NodeClient, TxLogEntryType, WalletBackend}; use crate::libwallet::types::{Context, NodeClient, OutputLockFn, TxLogEntryType, WalletBackend};
use crate::libwallet::{Error, ErrorKind}; use crate::libwallet::{Error, ErrorKind};
/// Receive a transaction, modifying the slate accordingly (which can then be /// Creates a new slate for a transaction, can be called by anyone involved in
/// sent back to sender for posting) /// the transaction (sender(s), receiver(s))
pub fn receive_tx<T: ?Sized, C, K>( pub fn new_tx_slate<T: ?Sized, C, K>(
wallet: &mut T, wallet: &mut T,
slate: &mut Slate, amount: u64,
parent_key_id: &Identifier, num_participants: usize,
message: Option<String>, ) -> Result<Slate, Error>
) -> Result<(), Error>
where where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
C: NodeClient, C: NodeClient,
K: Keychain, K: Keychain,
{ {
// create an output using the amount in the slate let current_height = wallet.w2n_client().get_chain_height()?;
let (_, mut context, receiver_create_fn) = let mut slate = Slate::blank(num_participants);
selection::build_recipient_output_with_slate(wallet, slate, parent_key_id.clone())?; slate.amount = amount;
// fill public keys slate.height = current_height;
let _ = slate.fill_round_1( slate.lock_height = current_height;
wallet.keychain(), Ok(slate)
&mut context.sec_key,
&context.sec_nonce,
1,
message,
)?;
// perform partial sig
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?;
// Save output in wallet
let _ = receiver_create_fn(wallet);
update_message(wallet, slate)?;
Ok(())
} }
/// Issue a new transaction to the provided sender by spending some of our /// Add inputs to the slate (effectively becoming the sender)
/// wallet pub fn add_inputs_to_slate<T: ?Sized, C, K>(
pub fn create_send_tx<T: ?Sized, C, K>(
wallet: &mut T, wallet: &mut T,
amount: u64, slate: &mut Slate,
minimum_confirmations: u64, minimum_confirmations: u64,
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,
parent_key_id: &Identifier, parent_key_id: &Identifier,
participant_id: usize,
message: Option<String>, message: Option<String>,
) -> Result< ) -> Result<(Context, OutputLockFn<T, C, K>), Error>
(
Slate,
Context,
impl FnOnce(&mut T, &Transaction) -> Result<(), Error>,
),
Error,
>
where where
T: WalletBackend<C, K>, T: WalletBackend<C, K>,
C: NodeClient, C: NodeClient,
K: Keychain, K: Keychain,
{ {
// Get lock height // sender should always refresh outputs
let current_height = wallet.w2n_client().get_chain_height()?;
// ensure outputs we're selecting are up to date
updater::refresh_outputs(wallet, parent_key_id, false)?; updater::refresh_outputs(wallet, parent_key_id, false)?;
let lock_height = current_height;
// Sender selects outputs into a new slate and save our corresponding keys in // Sender selects outputs into a new slate and save our corresponding keys in
// a transaction context. The secret key in our transaction context will be // a transaction context. The secret key in our transaction context will be
// randomly selected. This returns the public slate, and a closure that locks // randomly selected. This returns the public slate, and a closure that locks
@ -97,13 +69,10 @@ where
// according to plan // according to plan
// This function is just a big helper to do all of that, in theory // This function is just a big helper to do all of that, in theory
// this process can be split up in any way // this process can be split up in any way
let (mut slate, mut context, sender_lock_fn) = selection::build_send_tx_slate( let (mut context, sender_lock_fn) = selection::build_send_tx(
wallet, wallet,
2, slate,
amount,
current_height,
minimum_confirmations, minimum_confirmations,
lock_height,
max_outputs, max_outputs,
num_change_outputs, num_change_outputs,
selection_strategy_is_use_all, selection_strategy_is_use_all,
@ -117,17 +86,55 @@ where
wallet.keychain(), wallet.keychain(),
&mut context.sec_key, &mut context.sec_key,
&context.sec_nonce, &context.sec_nonce,
0, participant_id,
message, message,
)?; )?;
Ok((slate, context, sender_lock_fn)) Ok((context, sender_lock_fn))
}
/// Add outputs to the slate, becoming the recipient
pub fn add_output_to_slate<T: ?Sized, C, K>(
wallet: &mut T,
slate: &mut Slate,
parent_key_id: &Identifier,
participant_id: usize,
message: Option<String>,
) -> Result<(Context, OutputLockFn<T, C, K>), Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: Keychain,
{
// create an output using the amount in the slate
let (_, mut context, create_fn) =
selection::build_recipient_output(wallet, slate, parent_key_id.clone())?;
// fill public keys
let _ = slate.fill_round_1(
wallet.keychain(),
&mut context.sec_key,
&context.sec_nonce,
1,
message,
)?;
// perform partial sig
let _ = slate.fill_round_2(
wallet.keychain(),
&context.sec_key,
&context.sec_nonce,
participant_id,
)?;
Ok((context, create_fn))
} }
/// Complete a transaction as the sender /// Complete a transaction as the sender
pub fn complete_tx<T: ?Sized, C, K>( pub fn complete_tx<T: ?Sized, C, K>(
wallet: &mut T, wallet: &mut T,
slate: &mut Slate, slate: &mut Slate,
participant_id: usize,
context: &Context, context: &Context,
) -> Result<(), Error> ) -> Result<(), Error>
where where
@ -135,7 +142,12 @@ where
C: NodeClient, C: NodeClient,
K: Keychain, K: Keychain,
{ {
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 0)?; let _ = slate.fill_round_2(
wallet.keychain(),
&context.sec_key,
&context.sec_nonce,
participant_id,
)?;
// Final transaction can be built by anyone at this stage // Final transaction can be built by anyone at this stage
let res = slate.finalize(wallet.keychain()); let res = slate.finalize(wallet.keychain());
if let Err(e) = res { if let Err(e) = res {

View file

@ -30,8 +30,13 @@ use serde;
use serde_json; use serde_json;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::marker::PhantomData;
use uuid::Uuid; use uuid::Uuid;
/// Lock function type
pub type OutputLockFn<T, C, K> =
Box<dyn FnMut(&mut T, &Transaction, PhantomData<C>, PhantomData<K>) -> Result<(), Error>>;
/// Combined trait to allow dynamic wallet dispatch /// Combined trait to allow dynamic wallet dispatch
pub trait WalletInst<C, K>: WalletBackend<C, K> + Send + Sync + 'static pub trait WalletInst<C, K>: WalletBackend<C, K> + Send + Sync + 'static
where where