Skip to content

Commit

Permalink
Command line implementation of invoice commands (#96)
Browse files Browse the repository at this point in the history
* add issue_invoice_tx command

* rustfmt

* add first pass at process_invoice command

* start of process_invoice fn

* rustfmt

* rename issue invoice and process invoice to invoice and pay

* add prompting and display information to pay invoice command

* rustfmt

* support invoice transactions in finalize command

* rustfmt
  • Loading branch information
yeastplume committed May 9, 2019
1 parent 7a39c7c commit 6f875c5
Show file tree
Hide file tree
Showing 8 changed files with 530 additions and 15 deletions.
165 changes: 159 additions & 6 deletions controller/src/command.rs
Expand Up @@ -35,7 +35,7 @@ use crate::impls::{
LMDBBackend, NullWalletCommAdapter,
};
use crate::impls::{HTTPNodeClient, WalletSeed};
use crate::libwallet::{InitTxArgs, NodeClient, WalletInst};
use crate::libwallet::{InitTxArgs, IssueInvoiceTxArgs, NodeClient, WalletInst};
use crate::{controller, display};

/// Arguments common to all wallet commands
Expand Down Expand Up @@ -377,13 +377,45 @@ pub fn finalize(
) -> Result<(), Error> {
let adapter = FileWalletCommAdapter::new();
let mut slate = adapter.receive_tx_async(&args.input)?;
controller::owner_single_use(wallet.clone(), |api| {
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
// Rather than duplicating the entire command, we'll just
// try to determine what kind of finalization this is
// based on the slate contents
// for now, we can tell this is an invoice transaction
// if the receipient (participant 1) hasn't completed sigs
let part_data = slate.participant_with_id(1);
let is_invoice = {
match part_data {
None => {
error!("Expected slate participant data missing");
return Err(ErrorKind::ArgumentError(
"Expected Slate participant data missing".into(),
))?;
}
Some(p) => !p.is_complete(),
}
slate = api.finalize_tx(&mut slate).expect("Finalize failed");
};

if is_invoice {
controller::foreign_single_use(wallet.clone(), |api| {
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.finalize_invoice_tx(&mut slate)?;
Ok(())
})?;
} else {
controller::owner_single_use(wallet.clone(), |api| {
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
slate = api.finalize_tx(&mut slate)?;
Ok(())
})?;
}

controller::owner_single_use(wallet.clone(), |api| {
let result = api.post_tx(&slate.tx, args.fluff);
match result {
Ok(_) => {
Expand All @@ -396,9 +428,130 @@ pub fn finalize(
}
}
})?;

Ok(())
}

/// Issue Invoice Args
pub struct IssueInvoiceArgs {
/// output file
pub dest: String,
/// issue invoice tx args
pub issue_args: IssueInvoiceTxArgs,
}

pub fn issue_invoice_tx(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
args: IssueInvoiceArgs,
) -> Result<(), Error> {
controller::owner_single_use(wallet.clone(), |api| {
let slate = api.issue_invoice_tx(args.issue_args)?;
let mut tx_file = File::create(args.dest.clone())?;
tx_file.write_all(json::to_string(&slate).unwrap().as_bytes())?;
tx_file.sync_all()?;
Ok(())
})?;
Ok(())
}

/// Arguments for the process_invoice command
pub struct ProcessInvoiceArgs {
pub message: Option<String>,
pub minimum_confirmations: u64,
pub selection_strategy: String,
pub method: String,
pub dest: String,
pub max_outputs: usize,
pub target_slate_version: Option<u16>,
pub input: String,
pub estimate_selection_strategies: bool,
}

/// Process invoice
pub fn process_invoice(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
args: ProcessInvoiceArgs,
dark_scheme: bool,
) -> Result<(), Error> {
let adapter = FileWalletCommAdapter::new();
let slate = adapter.receive_tx_async(&args.input)?;
controller::owner_single_use(wallet.clone(), |api| {
if args.estimate_selection_strategies {
let strategies = vec!["smallest", "all"]
.into_iter()
.map(|strategy| {
let init_args = InitTxArgs {
src_acct_name: None,
amount: slate.amount,
minimum_confirmations: args.minimum_confirmations,
max_outputs: args.max_outputs as u32,
num_change_outputs: 1u32,
selection_strategy_is_use_all: strategy == "all",
estimate_only: Some(true),
..Default::default()
};
let slate = api.init_send_tx(init_args).unwrap();
(strategy, slate.amount, slate.fee)
})
.collect();
display::estimate(slate.amount, strategies, dark_scheme);
} else {
let init_args = InitTxArgs {
src_acct_name: None,
amount: 0,
minimum_confirmations: args.minimum_confirmations,
max_outputs: args.max_outputs as u32,
num_change_outputs: 1u32,
selection_strategy_is_use_all: args.selection_strategy == "all",
message: args.message.clone(),
target_slate_version: args.target_slate_version,
send_args: None,
..Default::default()
};
if let Err(e) = api.verify_slate_messages(&slate) {
error!("Error validating participant messages: {}", e);
return Err(e);
}
let result = api.process_invoice_tx(&slate, init_args);
let mut slate = match result {
Ok(s) => {
info!(
"Invoice processed: {} grin to {} (strategy '{}')",
core::amount_to_hr_string(slate.amount, false),
args.dest,
args.selection_strategy,
);
s
}
Err(e) => {
info!("Tx not created: {}", e);
return Err(e);
}
};
let adapter = match args.method.as_str() {
"http" => HTTPWalletCommAdapter::new(),
"file" => FileWalletCommAdapter::new(),
"self" => NullWalletCommAdapter::new(),
_ => NullWalletCommAdapter::new(),
};
if adapter.supports_sync() {
slate = adapter.send_tx_sync(&args.dest, &slate)?;
api.tx_lock_outputs(&slate)?;
if args.method == "self" {
controller::foreign_single_use(wallet, |api| {
slate = api.finalize_invoice_tx(&slate)?;
Ok(())
})?;
}
} else {
adapter.send_tx_async(&args.dest, &slate)?;
api.tx_lock_outputs(&slate)?;
}
}
Ok(())
})?;
Ok(())
}
/// Info command args
pub struct InfoArgs {
pub minimum_confirmations: u64,
Expand Down
1 change: 1 addition & 0 deletions controller/tests/invoice.rs
Expand Up @@ -124,6 +124,7 @@ fn invoice_tx_impl(test_dir: &str) -> Result<(), libwallet::Error> {
..Default::default()
};
slate = api.process_invoice_tx(&slate, args)?;
api.tx_lock_outputs(&slate)?;
Ok(())
})?;

Expand Down
3 changes: 0 additions & 3 deletions libwallet/src/api_impl/owner.rs
Expand Up @@ -330,9 +330,6 @@ where
batch.commit()?;
}

// Always lock the context for now
selection::lock_tx_context(&mut *w, slate, &context)?;
tx::update_message(&mut *w, &mut ret_slate)?;
Ok(ret_slate)
}

Expand Down
45 changes: 42 additions & 3 deletions libwallet/src/slate.rs
Expand Up @@ -27,14 +27,14 @@ use crate::grin_core::core::verifier_cache::LruVerifierCache;
use crate::grin_core::libtx::{aggsig, build, secp_ser, tx_fee};
use crate::grin_core::map_vec;
use crate::grin_keychain::{BlindSum, BlindingFactor, Keychain};
use crate::grin_util::secp;
use crate::grin_util::secp::key::{PublicKey, SecretKey};
use crate::grin_util::secp::Signature;
use crate::grin_util::RwLock;
use crate::grin_util::{self, secp, RwLock};
use failure::ResultExt;
use rand::rngs::mock::StepRng;
use rand::thread_rng;
use serde_json;
use std::fmt;
use std::sync::Arc;
use uuid::Uuid;

Expand Down Expand Up @@ -113,6 +113,36 @@ impl ParticipantMessageData {
}
}

impl fmt::Display for ParticipantMessageData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "")?;
write!(f, "Participant ID {} ", self.id)?;
if self.id == 0 {
writeln!(f, "(Sender)")?;
} else {
writeln!(f, "(Recipient)")?;
}
writeln!(f, "---------------------")?;
let static_secp = grin_util::static_secp_instance();
let static_secp = static_secp.lock();
writeln!(
f,
"Public Key: {}",
&grin_util::to_hex(self.public_key.serialize_vec(&static_secp, true).to_vec())
)?;
let message = match self.message.clone() {
None => "None".to_owned(),
Some(m) => m,
};
writeln!(f, "Message: {}", message)?;
let message_sig = match self.message_sig.clone() {
None => "None".to_owned(),
Some(m) => grin_util::to_hex(m.to_raw_data().to_vec()),
};
writeln!(f, "Message Signature: {}", message_sig)
}
}

/// A 'Slate' is passed around to all parties to build up all of the public
/// transaction data needed to create a finalized transaction. Callers can pass
/// the slate around by whatever means they choose, (but we can provide some
Expand Down Expand Up @@ -344,7 +374,6 @@ impl Slate {

/// Creates the final signature, callable by either the sender or recipient
/// (after phase 3: sender confirmation)
/// TODO: Only callable by receiver at the moment
pub fn finalize<K>(&mut self, keychain: &K) -> Result<(), Error>
where
K: Keychain,
Expand All @@ -353,6 +382,16 @@ impl Slate {
self.finalize_transaction(keychain, &final_sig)
}

/// Return the participant with the given id
pub fn participant_with_id(&self, id: usize) -> Option<ParticipantData> {
for p in self.participant_data.iter() {
if p.id as usize == id {
return Some(p.clone());
}
}
None
}

/// Return the sum of public nonces
fn pub_nonce_sum(&self, secp: &secp::Secp256k1) -> Result<PublicKey, Error> {
let pub_nonces = self
Expand Down
2 changes: 1 addition & 1 deletion libwallet/src/slate_versions/v2.rs
Expand Up @@ -29,7 +29,7 @@
//! * TxKernel fields serialized as hex strings instead of arrays:
//! commit
//! signature
//! * version_info field removed
//! * version field removed
//! * VersionCompatInfo struct created with fields and added to beginning of struct
//! version: u16
//! orig_verion: u16,
Expand Down

0 comments on commit 6f875c5

Please sign in to comment.