Slatepack Workflow (#423)

* debugging tor sends

* use fixed yeast version of hyper-socks2

* send command working according to slatepack

* changes to handling proof address

* modifications of recieve command to work with slatepack and to attempt to return to sender via TOR

* finalize command tested

* attempting to add invoice processing

* fixes to tests, propagation of test mode where needed

* modify return values of api functions that can send as sync

* cleanup, testing and post_tx function

* revert changes to API, many test fixes

* deprecate http on the command-line warning
This commit is contained in:
Yeastplume
2020-06-03 08:39:23 +01:00
committed by GitHub
parent 4fe6bf1c23
commit 60ab3728ab
28 changed files with 1425 additions and 738 deletions
+77 -19
View File
@@ -14,12 +14,14 @@
//! Foreign API External Definition
use crate::config::TorConfig;
use crate::keychain::Keychain;
use crate::libwallet::api_impl::foreign;
use crate::libwallet::{
BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst,
WalletLCProvider,
};
use crate::try_slatepack_sync_workflow;
use crate::util::secp::key::SecretKey;
use crate::util::Mutex;
use std::sync::Arc;
@@ -38,8 +40,10 @@ pub enum ForeignCheckMiddlewareFn {
VerifySlateMessages,
/// receive_tx
ReceiveTx,
/// finalize_invoice_tx
/// finalize_invoice_tx (delete HF3)
FinalizeInvoiceTx,
/// finalize_tx
FinalizeTx,
}
/// Main interface into all wallet API functions.
@@ -70,6 +74,9 @@ where
middleware: Option<ForeignCheckMiddleware>,
/// Stored keychain mask (in case the stored wallet seed is tokenized)
keychain_mask: Option<SecretKey>,
/// Optional TOR configuration, holding address of sender and
/// data directory
tor_config: Mutex<Option<TorConfig>>,
}
impl<'a, L, C, K> Foreign<'a, L, C, K>
@@ -158,7 +165,7 @@ where
/// // All wallet functions operate on an Arc::Mutex to allow multithreading where needed
/// let mut wallet = Arc::new(Mutex::new(wallet));
///
/// let api_foreign = Foreign::new(wallet.clone(), None, None);
/// let api_foreign = Foreign::new(wallet.clone(), None, None, false);
/// // .. perform wallet operations
///
/// ```
@@ -167,15 +174,30 @@ where
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
keychain_mask: Option<SecretKey>,
middleware: Option<ForeignCheckMiddleware>,
doctest_mode: bool,
) -> Self {
Foreign {
wallet_inst,
doctest_mode: false,
doctest_mode,
middleware,
keychain_mask,
tor_config: Mutex::new(None),
}
}
/// Set the TOR configuration for this instance of the ForeignAPI, used during
/// `recieve_tx` when a return address is specified
///
/// # Arguments
/// * `tor_config` - The optional [TorConfig](#) to use
/// # Returns
/// * Nothing
pub fn set_tor_config(&self, tor_config: Option<TorConfig>) {
let mut lock = self.tor_config.lock();
*lock = tor_config;
}
/// Return the version capabilities of the running ForeignApi Node
/// # Arguments
/// None
@@ -186,7 +208,7 @@ where
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config);
///
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None);
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false);
///
/// let version_info = api_foreign.check_version();
/// // check and proceed accordingly
@@ -238,7 +260,7 @@ where
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config);
///
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None);
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false);
///
/// let block_fees = BlockFees {
/// fees: 800000,
@@ -294,6 +316,9 @@ where
/// excess value).
/// * `dest_acct_name` - The name of the account into which the slate should be received. If
/// `None`, the default account is used.
/// * `r_addr` - If included, attempt to send the slate back to the sender using the slatepack sync
/// send (TOR). If providing this argument, check the `state` field of the slate to see if the
/// sync_send was successful (it should be S3 if the synced send sent successfully).
///
/// # Returns
/// * a result containing:
@@ -310,12 +335,12 @@ where
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config);
///
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None);
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false);
/// # let slate = Slate::blank(2, false);
///
/// // . . .
/// // Obtain a sent slate somehow
/// let result = api_foreign.receive_tx(&slate, None);
/// let result = api_foreign.receive_tx(&slate, None, None);
///
/// if let Ok(slate) = result {
/// // Send back to recipient somehow
@@ -323,7 +348,12 @@ where
/// }
/// ```
pub fn receive_tx(&self, slate: &Slate, dest_acct_name: Option<&str>) -> Result<Slate, Error> {
pub fn receive_tx(
&self,
slate: &Slate,
dest_acct_name: Option<&str>,
r_addr: Option<String>,
) -> Result<Slate, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
if let Some(m) = self.middleware.as_ref() {
@@ -333,23 +363,41 @@ where
Some(slate),
)?;
}
foreign::receive_tx(
let ret_slate = foreign::receive_tx(
&mut **w,
(&self.keychain_mask).as_ref(),
slate,
dest_acct_name,
self.doctest_mode,
)
)?;
match r_addr {
Some(a) => {
let tor_config_lock = self.tor_config.lock();
let res = try_slatepack_sync_workflow(
&ret_slate,
&a,
tor_config_lock.clone(),
None,
true,
self.doctest_mode,
);
match res {
Ok(s) => return Ok(s.unwrap()),
Err(_) => return Ok(ret_slate),
}
}
None => Ok(ret_slate),
}
}
/// Finalizes an invoice transaction initiated by this wallet's Owner api.
/// Finalizes a (standard or invoice) transaction initiated by this wallet's Owner api.
/// This step assumes the paying party has completed round 1 and 2 of slate
/// creation, and added their partial signatures. The invoicer will verify
/// creation, and added their partial signatures. This wallet will verify
/// and add their partial sig, then create the finalized transaction,
/// ready to post to a node.
///
/// Note that this function DOES NOT POST the transaction to a node
/// for validation. This is done in separately via the
/// This function posts to the node if the `post_automatically`
/// argument is sent to true. Posting can be done in separately via the
/// [`post_tx`](struct.Owner.html#method.post_tx) function.
///
/// This function also stores the final transaction in the user's wallet files for retrieval
@@ -357,7 +405,8 @@ where
///
/// # Arguments
/// * `slate` - The transaction [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html). The
/// payer should have filled in round 1 and 2.
/// * `post_automatically` - If true, post the finalized transaction to the configured listening
/// node
///
/// # Returns
/// * Ok([`slate`](../grin_wallet_libwallet/slate/struct.Slate.html)) if successful,
@@ -370,7 +419,7 @@ where
/// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config);
///
/// let mut api_owner = Owner::new(wallet.clone(), None);
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None);
/// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false);
///
/// // . . .
/// // Issue the invoice tx via the owner API
@@ -385,11 +434,11 @@ where
/// // ...
/// # let slate = Slate::blank(2, true);
///
/// let slate = api_foreign.finalize_invoice_tx(&slate);
/// let slate = api_foreign.finalize_tx(&slate, true);
/// // if okay, then post via the owner API
/// ```
pub fn finalize_invoice_tx(&self, slate: &Slate) -> Result<Slate, Error> {
pub fn finalize_tx(&self, slate: &Slate, post_automatically: bool) -> Result<Slate, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
if let Some(m) = self.middleware.as_ref() {
@@ -399,7 +448,16 @@ where
Some(slate),
)?;
}
foreign::finalize_invoice_tx(&mut **w, (&self.keychain_mask).as_ref(), slate)
let post_automatically = match self.doctest_mode {
true => false,
false => post_automatically,
};
foreign::finalize_tx(
&mut **w,
(&self.keychain_mask).as_ref(),
slate,
post_automatically,
)
}
}
+19 -9
View File
@@ -183,13 +183,12 @@ pub trait ForeignRpc {
&self,
slate: VersionedSlate,
dest_acct_name: Option<String>,
//TODO: Remove post-HF3
message: Option<String>,
dest: Option<String>,
) -> Result<VersionedSlate, ErrorKind>;
/**
Networked version of [Foreign::finalize_invoice_tx](struct.Foreign.html#method.finalize_invoice_tx).
Networked version of [Foreign::finalize_tx](struct.Foreign.html#method.finalize_tx).
# Json rpc example
@@ -198,7 +197,7 @@ pub trait ForeignRpc {
# r#"
{
"jsonrpc": "2.0",
"method": "finalize_invoice_tx",
"method": "finalize_tx",
"id": 1,
"params": [{
"ver": "4:2",
@@ -279,6 +278,9 @@ pub trait ForeignRpc {
# ,false, 5, false, true);
```
*/
fn finalize_tx(&self, slate: VersionedSlate) -> Result<VersionedSlate, ErrorKind>;
/// For backwards-compatibility. Remove HF3
fn finalize_invoice_tx(&self, slate: VersionedSlate) -> Result<VersionedSlate, ErrorKind>;
}
@@ -301,8 +303,7 @@ where
&self,
in_slate: VersionedSlate,
dest_acct_name: Option<String>,
//TODO: Remove post HF3
_message: Option<String>,
dest: Option<String>,
) -> Result<VersionedSlate, ErrorKind> {
let version = in_slate.version();
let slate_from = Slate::from(in_slate);
@@ -310,15 +311,24 @@ where
self,
&slate_from,
dest_acct_name.as_ref().map(String::as_str),
dest,
)
.map_err(|e| e.kind())?;
Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?)
}
fn finalize_tx(&self, in_slate: VersionedSlate) -> Result<VersionedSlate, ErrorKind> {
let version = in_slate.version();
let out_slate =
Foreign::finalize_tx(self, &Slate::from(in_slate), true).map_err(|e| e.kind())?;
Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?)
}
//TODO: Delete HF3
fn finalize_invoice_tx(&self, in_slate: VersionedSlate) -> Result<VersionedSlate, ErrorKind> {
let version = in_slate.version();
let out_slate =
Foreign::finalize_invoice_tx(self, &Slate::from(in_slate)).map_err(|e| e.kind())?;
Foreign::finalize_tx(self, &Slate::from(in_slate), false).map_err(|e| e.kind())?;
Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?)
}
}
@@ -513,8 +523,8 @@ pub fn run_doctest_foreign(
}
let mut api_foreign = match init_invoice_tx {
false => Foreign::new(wallet1, mask1, Some(test_check_middleware)),
true => Foreign::new(wallet2, mask2, Some(test_check_middleware)),
false => Foreign::new(wallet1, mask1, Some(test_check_middleware), true),
true => Foreign::new(wallet2, mask2, Some(test_check_middleware), true),
};
api_foreign.doctest_mode = true;
let foreign_api = &api_foreign as &dyn ForeignRpc;
+1 -1
View File
@@ -47,7 +47,7 @@ mod types;
pub use crate::foreign::{Foreign, ForeignCheckMiddleware, ForeignCheckMiddlewareFn};
pub use crate::foreign_rpc::ForeignRpc;
pub use crate::owner::Owner;
pub use crate::owner::{try_slatepack_sync_workflow, Owner};
pub use crate::owner_rpc::OwnerRpc;
pub use crate::foreign_rpc::foreign_rpc as foreign_rpc_client;
+169 -34
View File
@@ -21,18 +21,21 @@ use uuid::Uuid;
use crate::config::{TorConfig, WalletConfig};
use crate::core::core::Transaction;
use crate::core::global;
use crate::impls::create_sender;
use crate::impls::HttpSlateSender;
use crate::impls::SlateSender as _;
use crate::keychain::{Identifier, Keychain};
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
use crate::libwallet::api_impl::{owner, owner_updater};
use crate::libwallet::{
AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress,
TxLogEntry, WalletInfo, WalletInst, WalletLCProvider,
AcctPathMapping, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult,
OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, WalletInfo,
WalletInst, WalletLCProvider,
};
use crate::util::logger::LoggingConfig;
use crate::util::secp::key::SecretKey;
use crate::util::{from_hex, static_secp_instance, Mutex, ZeroingString};
use grin_wallet_util::OnionV3Address;
use std::convert::TryFrom;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Sender};
use std::sync::Arc;
@@ -63,6 +66,8 @@ where
pub wallet_inst: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
/// Flag to normalize some output during testing. Can mostly be ignored.
pub doctest_mode: bool,
/// retail TLD during doctest
pub doctest_retain_tld: bool,
/// Share ECDH key
pub shared_key: Arc<Mutex<Option<SecretKey>>>,
/// Update thread
@@ -189,6 +194,7 @@ where
Owner {
wallet_inst,
doctest_mode: false,
doctest_retain_tld: false,
shared_key: Arc::new(Mutex::new(None)),
updater,
updater_running,
@@ -586,9 +592,10 @@ where
///
/// If the `send_args` [`InitTxSendArgs`](../grin_wallet_libwallet/types/struct.InitTxSendArgs.html),
/// of the [`args`](../grin_wallet_libwallet/types/struct.InitTxArgs.html), field is Some, this
/// function will attempt to perform a synchronous send to the recipient specified in the `dest`
/// field according to the `method` field, and will also finalize and post the transaction if
/// the `finalize` field is set.
/// function will attempt to send the slate back to the sender using the slatepack sync
/// send (TOR). If providing this argument, check the `state` field of the slate to see if the
/// sync_send was successful (it should be S2 if the sync sent successfully). It will also post
/// the transction if the `post_tx` field is set.
///
/// # Arguments
/// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if
@@ -648,39 +655,47 @@ where
args: InitTxArgs,
) -> Result<Slate, Error> {
let send_args = args.send_args.clone();
let mut slate = {
let slate = {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::init_send_tx(&mut **w, keychain_mask, args, self.doctest_mode)?
};
// Helper functionality. If send arguments exist, attempt to send
// Helper functionality. If send arguments exist, attempt to send sync and
// finalize
match send_args {
Some(sa) => {
//TODO: in case of keybase, the response might take 60s and leave the service hanging
match sa.method.as_ref() {
"http" | "keybase" => {}
_ => {
error!("unsupported payment method: {}", sa.method);
return Err(ErrorKind::ClientCallback(
"unsupported payment method".to_owned(),
)
.into());
}
};
let tor_config_lock = self.tor_config.lock();
let comm_adapter = create_sender(&sa.method, &sa.dest, tor_config_lock.clone())
.map_err(|e| ErrorKind::GenericError(format!("{}", e)))?;
slate = comm_adapter.send_tx(&slate)?;
self.tx_lock_outputs(keychain_mask, &slate)?;
let slate = match sa.finalize {
true => self.finalize_tx(keychain_mask, &slate)?,
false => slate,
};
if sa.post_tx {
self.post_tx(keychain_mask, &slate, sa.fluff)?;
let res = try_slatepack_sync_workflow(
&slate,
&sa.dest,
tor_config_lock.clone(),
None,
false,
self.doctest_mode,
);
match res {
Ok(Some(s)) => {
if sa.post_tx {
self.tx_lock_outputs(keychain_mask, &s)?;
let ret_slate = self.finalize_tx(keychain_mask, &s)?;
let result = self.post_tx(keychain_mask, &ret_slate, sa.fluff);
match result {
Ok(_) => {
info!("Tx sent ok",);
return Ok(ret_slate);
}
Err(e) => {
error!("Tx sent fail: {}", e);
return Err(e);
}
}
} else {
return Ok(slate);
}
}
Ok(None) => Ok(slate),
Err(_) => Ok(slate),
}
Ok(slate)
}
None => Ok(slate),
}
@@ -742,6 +757,12 @@ where
/// it is up to the caller to present the request for payment to the user
/// and verify that payment should go ahead.
///
/// If the `send_args` [`InitTxSendArgs`](../grin_wallet_libwallet/types/struct.InitTxSendArgs.html),
/// of the [`args`](../grin_wallet_libwallet/types/struct.InitTxArgs.html), field is Some, this
/// function will attempt to send the slate back to the initiator using the slatepack sync
/// send (TOR). If providing this argument, check the `state` field of the slate to see if the
/// sync_send was successful (it should be I3 if the sync sent successfully).
///
/// This function also stores the final transaction in the user's wallet files for retrieval
/// via the [`get_stored_tx`](struct.Owner.html#method.get_stored_tx) function.
///
@@ -794,7 +815,28 @@ where
) -> Result<Slate, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
owner::process_invoice_tx(&mut **w, keychain_mask, slate, args, self.doctest_mode)
let send_args = args.send_args.clone();
let slate =
owner::process_invoice_tx(&mut **w, keychain_mask, slate, args, self.doctest_mode)?;
// Helper functionality. If send arguments exist, attempt to send
match send_args {
Some(sa) => {
let tor_config_lock = self.tor_config.lock();
let res = try_slatepack_sync_workflow(
&slate,
&sa.dest,
tor_config_lock.clone(),
None,
true,
self.doctest_mode,
);
match res {
Ok(s) => Ok(s.unwrap()),
Err(_) => Ok(slate),
}
}
None => Ok(slate),
}
}
/// Locks the outputs associated with the inputs to the transaction in the given
@@ -1277,7 +1319,7 @@ where
pub fn get_top_level_directory(&self) -> Result<String, Error> {
let mut w_lock = self.wallet_inst.lock();
let lc = w_lock.lc_provider()?;
if self.doctest_mode {
if self.doctest_mode && !self.doctest_retain_tld {
Ok("/doctest/dir".to_owned())
} else {
lc.get_top_level_directory()
@@ -2249,6 +2291,99 @@ where
}
}
/// attempt to send slate synchronously, starting with TOR and downgrading to HTTP
pub fn try_slatepack_sync_workflow(
slate: &Slate,
dest: &str,
tor_config: Option<TorConfig>,
tor_sender: Option<HttpSlateSender>,
send_to_finalize: bool,
test_mode: bool,
) -> Result<Option<Slate>, libwallet::Error> {
let mut ret_slate = Slate::blank(2, false);
let mut send_sync = |mut sender: HttpSlateSender, method_str: &str| match sender
.send_tx(&slate, send_to_finalize)
{
Ok(s) => {
ret_slate = s;
return Ok(());
}
Err(e) => {
debug!(
"Send ({}): Could not send Slate via {}: {}",
method_str, method_str, e
);
return Err(e);
}
};
// First, try TOR
match SlatepackAddress::try_from(dest) {
Ok(address) => {
let tor_addr = OnionV3Address::try_from(&address).unwrap();
// Try sending to the destination via TOR
let sender = match tor_sender {
None => {
if test_mode {
None
} else {
match HttpSlateSender::with_socks_proxy(
&tor_addr.to_http_str(),
&tor_config.as_ref().unwrap().socks_proxy_addr,
&tor_config.as_ref().unwrap().send_config_dir,
) {
Ok(s) => Some(s),
Err(e) => {
debug!("Send (TOR): Cannot create TOR Slate sender {:?}", e);
None
}
}
}
}
Some(s) => {
if test_mode {
None
} else {
Some(s)
}
}
};
if let Some(s) = sender {
warn!("Attempting to send transaction via TOR");
match send_sync(s, "TOR") {
Ok(_) => return Ok(Some(ret_slate)),
Err(e) => {
debug!("Unable to send via TOR: {}", e);
warn!("Unable to send transaction via TOR. Attempting alternate methods.");
}
}
}
}
Err(e) => {
debug!("Send (TOR): Destination is not SlatepackAddress {:?}", e);
}
}
// Try Fallback to HTTP for deprecation period
match HttpSlateSender::new(&dest) {
Ok(sender) => {
println!("Attempting to send transaction via HTTP (deprecated)");
match send_sync(sender, "HTTP") {
Ok(_) => return Ok(Some(ret_slate)),
Err(e) => {
debug!("Unable to send via HTTP: {}", e);
warn!("Unable to send transaction via HTTP. Will output Slatepack.");
return Ok(None);
}
}
}
Err(e) => {
debug!("Send (HTTP): Cannot create HTTP Slate sender {:?}", e);
return Ok(None);
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! doctest_helper_setup_doc_env {