[WIP] Updates to support web-wallet (#1160)

* updates to support web wallet workflow

* rustfmt

* functions to support wallet, error handling

* rustfmt

* rebase rustfmt

* test fix
This commit is contained in:
Yeastplume 2018-06-13 21:58:45 +01:00 committed by GitHub
parent 922b04608f
commit 88616fd341
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 260 additions and 89 deletions

View file

@ -201,7 +201,7 @@ impl OutputHandler {
);
let mut return_vec = vec![];
for i in start_height..end_height + 1 {
for i in (start_height..=end_height).rev() {
let res = self.outputs_at_height(i, commitments.clone(), include_rp);
if res.outputs.len() > 0 {
return_vec.push(res);

View file

@ -20,10 +20,10 @@ use std::fs::File;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
use core::core::Committed;
use core::core::hash::{Hash, Hashed};
use core::core::pmmr::MerkleProof;
use core::core::target::Difficulty;
use core::core::Committed;
use core::core::{Block, BlockHeader, Output, OutputIdentifier, Transaction, TxKernel};
use core::global;
use grin_store::Error::NotFoundErr;
@ -31,8 +31,8 @@ use pipe;
use store;
use txhashset;
use types::*;
use util::secp::pedersen::{Commitment, RangeProof};
use util::LOGGER;
use util::secp::pedersen::{Commitment, RangeProof};
/// Orphan pool size is limited by MAX_ORPHAN_SIZE
pub const MAX_ORPHAN_SIZE: usize = 200;

View file

@ -14,8 +14,8 @@
//! Transactions
use std::cmp::max;
use std::cmp::Ordering;
use std::cmp::max;
use std::collections::HashSet;
use std::io::Cursor;
use std::{error, fmt};
@ -26,12 +26,12 @@ use util::{kernel_sig_msg, static_secp_instance};
use consensus;
use consensus::VerifySortOrder;
use core::BlockHeader;
use core::Committed;
use core::committed;
use core::global;
use core::hash::{Hash, Hashed, ZERO_HASH};
use core::pmmr::MerkleProof;
use core::BlockHeader;
use core::Committed;
use keychain;
use keychain::BlindingFactor;
use ser::{self, read_and_verify_sorted, ser_vec, PMMRable, Readable, Reader, Writeable,

View file

@ -675,7 +675,7 @@ impl NetAdapter for Peers {
fn peer_difficulty(&self, addr: SocketAddr, diff: Difficulty, height: u64) {
if diff != self.total_difficulty() || height != self.total_height() {
debug!(
trace!(
LOGGER,
"ping/pong: {}: {} @ {} vs us: {} @ {}",
addr,

View file

@ -29,9 +29,9 @@ use std::sync::{Arc, RwLock};
use core::core::{Block, BlockHeader};
use chain::ChainStore;
use chain::txhashset;
use chain::types::Tip;
use chain::ChainStore;
use core::core::target::Difficulty;
use keychain::{ExtKeychain, Keychain};

View file

@ -35,8 +35,8 @@ use core::core::transaction::Transaction;
use p2p;
use pool;
use store;
use util::OneTime;
use util::LOGGER;
use util::OneTime;
// All adapters use `Weak` references instead of `Arc` to avoid cycles that
// can never be destroyed. These 2 functions are simple helpers to reduce the

View file

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use std::{cmp, thread};
use time;
@ -457,7 +457,7 @@ mod test {
assert_eq!(
get_locator_heights(10000),
vec![
10000, 9998, 9994, 9986, 9970, 9938, 9874, 9746, 9490, 8978, 7954, 5906, 1810, 0,
10000, 9998, 9994, 9986, 9970, 9938, 9874, 9746, 9490, 8978, 7954, 5906, 1810, 0
]
);
}

View file

@ -305,7 +305,7 @@ impl LocalServerContainer {
let mut wallet = FileWallet::new(config.clone(), "")
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
wallet.keychain = Some(keychain);
wallet::libwallet::internal::updater::retrieve_info(&mut wallet).unwrap()
wallet::libwallet::internal::updater::retrieve_info(&mut wallet, true).unwrap()
}
pub fn send_amount_to(

View file

@ -681,7 +681,7 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
Ok(())
}
("info", Some(_)) => {
let _res = wallet::display::info(&api.retrieve_summary_info()?.1)
let _res = wallet::display::info(&api.retrieve_summary_info(true)?)
.unwrap_or_else(|e| {
panic!(
"Error getting wallet info: {:?} Config: {:?}",
@ -692,7 +692,7 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
}
("outputs", Some(_)) => {
let (height, validated) = api.node_height()?;
let (_, outputs) = api.retrieve_outputs(show_spent)?;
let (_, outputs) = api.retrieve_outputs(show_spent, true)?;
let _res =
wallet::display::outputs(height, validated, outputs).unwrap_or_else(|e| {
panic!(

View file

@ -41,10 +41,8 @@
//! Adapted from https://github.com/behnam/rust-cursive-table-view
//! A basic table view implementation for [cursive](https://crates.io/crates/cursive).
#![deny(
missing_docs, missing_copy_implementations, trivial_casts, trivial_numeric_casts, unsafe_code,
unused_import_braces, unused_qualifications
)]
#![deny(missing_docs, missing_copy_implementations, trivial_casts, trivial_numeric_casts,
unsafe_code, unused_import_braces, unused_qualifications)]
// Crate Dependencies ---------------------------------------------------------
extern crate cursive;
@ -56,6 +54,7 @@ use std::hash::Hash;
use std::rc::Rc;
// External Dependencies ------------------------------------------------------
use cursive::With;
use cursive::align::HAlign;
use cursive::direction::Direction;
use cursive::event::{Callback, Event, EventResult, Key};
@ -63,7 +62,6 @@ use cursive::theme::ColorStyle;
use cursive::theme::PaletteColor::*;
use cursive::vec::Vec2;
use cursive::view::{ScrollBase, View};
use cursive::With;
use cursive::{Cursive, Printer};
/// A trait for displaying and sorting items inside a

View file

@ -14,8 +14,8 @@
//! Types specific to the UI module
use cursive::view::View;
use cursive::Cursive;
use cursive::view::View;
use servers::ServerStats;
/// Main message struct to communicate between the UI and

View file

@ -14,8 +14,8 @@
use core::core;
use core::core::amount_to_hr_string;
use libwallet::types::{OutputData, WalletInfo};
use libwallet::Error;
use libwallet::types::{OutputData, WalletInfo};
use prettytable;
use std::io::prelude::*;
use term;

View file

@ -414,15 +414,26 @@ impl<K> WalletClient for FileWallet<K> {
dest: &str,
block_fees: &BlockFees,
) -> Result<CbData, libwallet::Error> {
let res =
client::create_coinbase(dest, block_fees).context(libwallet::ErrorKind::WalletComms)?;
Ok(res)
let res = client::create_coinbase(dest, block_fees);
match res {
Ok(r) => Ok(r),
Err(e) => {
let message = format!("{}", e.cause().unwrap());
Err(libwallet::ErrorKind::WalletComms(message))?
}
}
}
/// Send a transaction slate to another listening wallet and return result
fn send_tx_slate(&self, dest: &str, slate: &Slate) -> Result<Slate, libwallet::Error> {
let res = client::send_tx_slate(dest, slate).context(libwallet::ErrorKind::WalletComms)?;
Ok(res)
let res = client::send_tx_slate(dest, slate);
match res {
Ok(r) => Ok(r),
Err(e) => {
let message = format!("{}", e.cause().unwrap());
Err(libwallet::ErrorKind::WalletComms(message))?
}
}
}
/// Posts a tranaction to a grin node

View file

@ -23,8 +23,8 @@ use keychain::{BlindSum, BlindingFactor, Keychain};
use libtx::error::{Error, ErrorKind};
use libtx::{aggsig, build, tx_fee};
use util::secp::key::{PublicKey, SecretKey};
use util::secp::Signature;
use util::secp::key::{PublicKey, SecretKey};
use util::{secp, LOGGER};
/// Public data for each participant in the slate

View file

@ -60,8 +60,12 @@ where
pub fn retrieve_outputs(
&mut self,
include_spent: bool,
refresh_from_node: bool,
) -> Result<(bool, Vec<OutputData>), Error> {
let validated = self.update_outputs();
let mut validated = false;
if refresh_from_node {
validated = self.update_outputs();
}
Ok((
validated,
updater::retrieve_outputs(self.wallet, include_spent)?,
@ -69,9 +73,12 @@ where
}
/// Retrieve summary info for wallet
pub fn retrieve_summary_info(&mut self) -> Result<(bool, WalletInfo), Error> {
let validated = self.update_outputs();
Ok((validated, updater::retrieve_info(self.wallet)?))
pub fn retrieve_summary_info(&mut self, refresh_from_node: bool) -> Result<WalletInfo, Error> {
let mut validated = false;
if refresh_from_node {
validated = self.update_outputs();
}
updater::retrieve_info(self.wallet, validated)
}
/// Issues a send transaction and sends to recipient
@ -139,7 +146,7 @@ where
match self.wallet.get_chain_height(self.wallet.node_url()) {
Ok(height) => Ok((height, true)),
Err(_) => {
let outputs = self.retrieve_outputs(true)?;
let outputs = self.retrieve_outputs(true, false)?;
let height = match outputs.1.iter().map(|out| out.height).max() {
Some(height) => height,
None => 0,

View file

@ -21,17 +21,20 @@ use std::sync::{Arc, Mutex};
use bodyparser;
use iron::Handler;
use iron::Headers;
use iron::prelude::*;
use iron::status;
use serde::Serialize;
use serde_json;
use urlencoded::UrlEncodedQuery;
use failure::Fail;
use keychain::Keychain;
use libtx::slate::Slate;
use libwallet::api::{APIForeign, APIOwner};
use libwallet::types::{BlockFees, CbData, OutputData, WalletBackend, WalletClient, WalletInfo};
use libwallet::types::{BlockFees, CbData, OutputData, SendTXArgs, WalletBackend, WalletClient,
WalletInfo};
use libwallet::{Error, ErrorKind};
use util::LOGGER;
@ -69,13 +72,19 @@ where
pub fn owner_listener<T, K>(wallet: T, addr: &str) -> Result<(), Error>
where
T: WalletBackend<K> + WalletClient,
OwnerAPIHandler<T, K>: Handler,
OwnerAPIGetHandler<T, K>: Handler,
OwnerAPIPostHandler<T, K>: Handler,
K: Keychain,
{
let api_handler = OwnerAPIHandler::new(Arc::new(Mutex::new(wallet)));
let wallet_arc = Arc::new(Mutex::new(wallet));
let api_get_handler = OwnerAPIGetHandler::new(wallet_arc.clone());
let api_post_handler = OwnerAPIPostHandler::new(wallet_arc);
let api_options_handler = OwnerAPIOptionsHandler {};
let router = router!(
receive_tx: get "/wallet/owner/*" => api_handler,
owner_options: options "/wallet/owner/*" => api_options_handler,
owner_get: get "/wallet/owner/*" => api_get_handler,
owner_post: post "/wallet/owner/*" => api_post_handler,
);
let mut apis = ApiServer::new("/v1".to_string());
@ -117,7 +126,7 @@ where
}
/// API Handler/Wrapper for owner functions
pub struct OwnerAPIHandler<T, K>
pub struct OwnerAPIGetHandler<T, K>
where
T: WalletBackend<K>,
K: Keychain,
@ -127,13 +136,13 @@ where
phantom: PhantomData<K>,
}
impl<T, K> OwnerAPIHandler<T, K>
impl<T, K> OwnerAPIGetHandler<T, K>
where
T: WalletBackend<K> + WalletClient,
K: Keychain,
{
pub fn new(wallet: Arc<Mutex<T>>) -> OwnerAPIHandler<T, K> {
OwnerAPIHandler {
pub fn new(wallet: Arc<Mutex<T>>) -> OwnerAPIGetHandler<T, K> {
OwnerAPIGetHandler {
wallet,
phantom: PhantomData,
}
@ -143,9 +152,14 @@ where
&self,
req: &mut Request,
api: &mut APIOwner<T, K>,
) -> Result<Vec<OutputData>, Error> {
let res = api.retrieve_outputs(false)?;
Ok(res.1)
) -> Result<(bool, Vec<OutputData>), Error> {
let mut update_from_node = false;
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
if let Some(_) = params.get("refresh") {
update_from_node = true;
}
}
api.retrieve_outputs(false, update_from_node)
}
fn retrieve_summary_info(
@ -153,31 +167,32 @@ where
req: &mut Request,
api: &mut APIOwner<T, K>,
) -> Result<WalletInfo, Error> {
let res = api.retrieve_summary_info()?;
Ok(res.1)
let mut update_from_node = false;
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
if let Some(_) = params.get("refresh") {
update_from_node = true;
}
}
api.retrieve_summary_info(update_from_node)
}
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> Result<(), Error> {
// TODO: Args
api.issue_send_tx(60, 10, "", 1000, true, true)
}
fn issue_burn_tx(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> Result<(), Error> {
// TODO: Args
api.issue_burn_tx(60, 10, 1000)
fn node_height(
&self,
req: &mut Request,
api: &mut APIOwner<T, K>,
) -> Result<(u64, bool), Error> {
api.node_height()
}
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> IronResult<Response> {
let url = req.url.clone();
let path_elems = url.path();
match *path_elems.last().unwrap() {
"retrieve_outputs" => json_response_pretty(&self.retrieve_outputs(req, api)
"retrieve_outputs" => json_response(&self.retrieve_outputs(req, api)
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
"retrieve_summary_info" => json_response_pretty(&self.retrieve_summary_info(req, api)
"retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
"issue_send_tx" => json_response_pretty(&self.issue_send_tx(req, api)
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
"issue_burn_tx" => json_response_pretty(&self.issue_burn_tx(req, api)
"node_height" => json_response(&self.node_height(req, api)
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
_ => Err(IronError::new(
Fail::compat(ErrorKind::Hyper),
@ -187,7 +202,7 @@ where
}
}
impl<T, K> Handler for OwnerAPIHandler<T, K>
impl<T, K> Handler for OwnerAPIGetHandler<T, K>
where
T: WalletBackend<K> + WalletClient + Send + Sync + 'static,
K: Keychain + 'static,
@ -203,11 +218,13 @@ where
})?;
let mut api = APIOwner::new(&mut *wallet);
let mut resp_json = self.handle_request(req, &mut api);
resp_json
.as_mut()
.unwrap()
.headers
.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
if !resp_json.is_err() {
resp_json
.as_mut()
.unwrap()
.headers
.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
}
api.wallet
.close()
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
@ -215,6 +232,138 @@ where
}
}
pub struct OwnerAPIPostHandler<T, K>
where
T: WalletBackend<K>,
K: Keychain,
{
/// Wallet instance
pub wallet: Arc<Mutex<T>>,
phantom: PhantomData<K>,
}
impl<T, K> OwnerAPIPostHandler<T, K>
where
T: WalletBackend<K> + WalletClient,
K: Keychain,
{
pub fn new(wallet: Arc<Mutex<T>>) -> OwnerAPIPostHandler<T, K> {
OwnerAPIPostHandler {
wallet,
phantom: PhantomData,
}
}
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> Result<(), Error> {
let struct_body = req.get::<bodyparser::Struct<SendTXArgs>>();
match struct_body {
Ok(Some(args)) => api.issue_send_tx(
args.amount,
args.minimum_confirmations,
&args.dest,
args.max_outputs,
args.selection_strategy_is_use_all,
args.fluff,
),
Ok(None) => {
error!(LOGGER, "Missing request body: issue_send_tx");
Err(ErrorKind::GenericError(
"Invalid request body: issue_send_tx",
))?
}
Err(e) => {
error!(LOGGER, "Invalid request body: issue_send_tx {:?}", e);
Err(ErrorKind::GenericError(
"Invalid request body: issue_send_tx",
))?
}
}
}
fn issue_burn_tx(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> Result<(), Error> {
// TODO: Args
api.issue_burn_tx(60, 10, 1000)
}
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T, K>) -> Result<String, Error> {
let url = req.url.clone();
let path_elems = url.path();
match *path_elems.last().unwrap() {
"issue_send_tx" => json_response_pretty(&self.issue_send_tx(req, api)?),
"issue_burn_tx" => json_response_pretty(&self.issue_burn_tx(req, api)?),
_ => Err(ErrorKind::GenericError(
"Unknown error handling post request",
))?,
}
}
fn create_error_response(&self, e: Error) -> IronResult<Response> {
let mut headers = Headers::new();
headers.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
headers.set_raw(
"access-control-allow-headers",
vec![b"Content-Type".to_vec()],
);
let message = format!("{}", e.kind());
let mut r = Response::with((status::InternalServerError, message));
r.headers = headers;
Ok(r)
}
fn create_ok_response(&self, json: &str) -> IronResult<Response> {
let mut headers = Headers::new();
headers.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
let mut r = Response::with((status::Ok, json));
r.headers = headers;
Ok(r)
}
}
impl<T, K> Handler for OwnerAPIPostHandler<T, K>
where
T: WalletBackend<K> + WalletClient + Send + Sync + 'static,
K: Keychain + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
// every request should open with stored credentials,
// do its thing and then de-init whatever secrets have been
// stored
let mut wallet = self.wallet.lock().unwrap();
wallet.open_with_credentials().map_err(|e| {
error!(LOGGER, "Error opening wallet: {:?}", e);
IronError::new(Fail::compat(e), status::BadRequest)
})?;
let mut api = APIOwner::new(&mut *wallet);
let resp = match self.handle_request(req, &mut api) {
Ok(r) => self.create_ok_response(&r),
Err(e) => {
error!(LOGGER, "Request Error: {:?}", e);
self.create_error_response(e)
}
};
api.wallet
.close()
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
resp
}
}
/// Options handler
pub struct OwnerAPIOptionsHandler {}
impl Handler for OwnerAPIOptionsHandler where {
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
let mut resp_json = Ok(Response::with((status::Ok, "{}")));
let mut headers = Headers::new();
headers.set_raw("access-control-allow-origin", vec![b"*".to_vec()]);
headers.set_raw(
"access-control-allow-headers",
vec![b"Content-Type".to_vec()],
);
resp_json.as_mut().unwrap().headers = headers;
resp_json
}
}
/// API Handler/Wrapper for foreign functions
pub struct ForeignAPIHandler<T, K>
@ -291,7 +440,6 @@ where
}
}
}
impl<T, K> Handler for ForeignAPIHandler<T, K>
where
T: WalletBackend<K> + WalletClient + Send + Sync + 'static,
@ -328,12 +476,12 @@ where
}
// pretty-printed version of above
fn json_response_pretty<T>(s: &T) -> IronResult<Response>
fn json_response_pretty<T>(s: &T) -> Result<String, Error>
where
T: Serialize,
{
match serde_json::to_string_pretty(s) {
Ok(json) => Ok(Response::with((status::Ok, json))),
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
Ok(json) => Ok(json),
Err(_) => Err(ErrorKind::Format)?,
}
}

View file

@ -94,8 +94,8 @@ pub enum ErrorKind {
Node,
/// Error contacting wallet API
#[fail(display = "Wallet communication error")]
WalletComms,
#[fail(display = "Wallet Communication Error: {}", _0)]
WalletComms(String),
/// Error originating from hyper.
#[fail(display = "Hyper error")]

View file

@ -24,8 +24,8 @@ use keychain::{Identifier, Keychain};
use libtx::proof;
use libwallet::types::*;
use util;
use util::secp::pedersen;
use util::LOGGER;
use util::secp::pedersen;
fn get_merkle_proof_for_commit(node_addr: &str, commit: &str) -> Result<MerkleProofWrapper, Error> {
let url = format!("{}/v1/txhashset/merkleproof?id={}", node_addr, commit);

View file

@ -15,7 +15,7 @@
//! Selection of inputs for building transactions
use keychain::{Identifier, Keychain};
use libtx::{build, slate::Slate, tx_fee};
use libtx::{build, tx_fee, slate::Slate};
use libwallet::error::{Error, ErrorKind};
use libwallet::internal::{keys, sigcontext};
use libwallet::types::*;
@ -93,12 +93,7 @@ where
let coin = wallet_data.get_output(&id).unwrap().clone();
wallet_data.lock_output(&coin);
}
// probably just want to leave as unconfirmed for now
// or create a new status
/*for id in lock_outputs {
let coin = wallet_data.get_output(&id).unwrap().clone();
wallet_data.lock_output(&coin);
}*/ })
})
};
Ok((slate, context, update_sender_wallet_fn))

View file

@ -16,8 +16,8 @@
//! the wallet storage and update them.
use failure::ResultExt;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use core::consensus::reward;
use core::core::{Output, TxKernel};
@ -29,8 +29,8 @@ use libwallet::error::{Error, ErrorKind};
use libwallet::internal::keys;
use libwallet::types::*;
use util;
use util::secp::pedersen;
use util::LOGGER;
use util::secp::pedersen;
/// Retrieve all of the outputs (doesn't attempt to update from node)
pub fn retrieve_outputs<T, K>(wallet: &mut T, show_spent: bool) -> Result<Vec<OutputData>, Error>
@ -241,13 +241,12 @@ where
}
/// Retrieve summary info about the wallet
pub fn retrieve_info<T, K>(wallet: &mut T) -> Result<WalletInfo, Error>
/// caller should refresh first if desired
pub fn retrieve_info<T, K>(wallet: &mut T, refreshed: bool) -> Result<WalletInfo, Error>
where
T: WalletBackend<K> + WalletClient,
K: Keychain,
{
let result = refresh_outputs(wallet);
let height_res = wallet.get_chain_height(&wallet.node_url());
let ret_val = wallet.read_wallet(|wallet_data| {
@ -282,10 +281,6 @@ where
}
}
let mut data_confirmed = true;
if let Err(_) = result {
data_confirmed = false;
}
Ok(WalletInfo {
current_height: current_height,
total: unspent_total + unconfirmed_total,
@ -293,7 +288,7 @@ where
amount_confirmed_but_locked: unspent_but_locked_total,
amount_currently_spendable: unspent_total - unspent_but_locked_total,
amount_locked: locked_total,
data_confirmed: data_confirmed,
data_confirmed: refreshed,
data_confirmed_from: String::from(from),
})
});

View file

@ -423,3 +423,20 @@ pub struct TxWrapper {
/// hex representation of transaction
pub tx_hex: String,
}
/// Send TX API Args
#[derive(Clone, Serialize, Deserialize)]
pub struct SendTXArgs {
/// amount to send
pub amount: u64,
/// minimum confirmations
pub minimum_confirmations: u64,
/// destination url
pub dest: String,
/// Max number of outputs
pub max_outputs: usize,
/// whether to use all outputs (combine)
pub selection_strategy_is_use_all: bool,
/// dandelion control
pub fluff: bool,
}

View file

@ -29,8 +29,8 @@ mod common;
use std::fs;
use std::sync::Arc;
use chain::types::*;
use chain::Chain;
use chain::types::*;
use core::global::ChainTypes;
use core::{global, pow};
use util::LOGGER;