mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
parent
1815a6a8bf
commit
8f66016557
28 changed files with 966 additions and 674 deletions
|
@ -240,9 +240,12 @@ fn get_coinbase(
|
|||
}
|
||||
Some(wallet_listener_url) => {
|
||||
// Get the wallet coinbase
|
||||
let url = format!("{}/v1/receive/coinbase", wallet_listener_url.as_str());
|
||||
let url = format!(
|
||||
"{}/v1/wallet/foreign/build_coinbase",
|
||||
wallet_listener_url.as_str()
|
||||
);
|
||||
|
||||
let res = wallet::client::create_coinbase(&url, &block_fees)?;
|
||||
let res = wallet::libwallet::client::create_coinbase(&url, &block_fees)?;
|
||||
let out_bin = util::from_hex(res.output).unwrap();
|
||||
let kern_bin = util::from_hex(res.kernel).unwrap();
|
||||
let key_id_bin = util::from_hex(res.key_id).unwrap();
|
||||
|
|
|
@ -268,21 +268,21 @@ impl LocalServerContainer {
|
|||
//panic!("Error initting wallet seed: {}", e);
|
||||
}
|
||||
|
||||
let wallet_seed = wallet::WalletSeed::from_file(&self.wallet_config)
|
||||
.expect("Failed to read wallet seed file.");
|
||||
|
||||
let keychain = wallet_seed
|
||||
.derive_keychain("grin_test")
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
|
||||
let wallet = FileWallet::new(self.wallet_config.clone(), keychain).unwrap_or_else(|e| {
|
||||
let wallet = FileWallet::new(self.wallet_config.clone(), "").unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error creating wallet: {:?} Config: {:?}",
|
||||
e, self.wallet_config
|
||||
)
|
||||
});
|
||||
|
||||
wallet::server::start_rest_apis(wallet, &self.wallet_config.api_listen_addr());
|
||||
wallet::controller::foreign_listener(wallet, &self.wallet_config.api_listen_addr())
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error creating wallet listener: {:?} Config: {:?}",
|
||||
e, self.wallet_config
|
||||
)
|
||||
});
|
||||
|
||||
self.wallet_is_running = true;
|
||||
}
|
||||
|
||||
|
@ -299,11 +299,12 @@ impl LocalServerContainer {
|
|||
wallet_seed: &wallet::WalletSeed,
|
||||
) -> wallet::WalletInfo {
|
||||
let keychain = wallet_seed
|
||||
.derive_keychain("grin_test")
|
||||
.derive_keychain("")
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
let mut wallet = FileWallet::new(config.clone(), keychain)
|
||||
let mut wallet = FileWallet::new(config.clone(), "")
|
||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||
wallet::libwallet::updater::retrieve_info(&mut wallet).unwrap()
|
||||
wallet.keychain = Some(keychain);
|
||||
wallet::libwallet::internal::updater::retrieve_info(&mut wallet).unwrap()
|
||||
}
|
||||
|
||||
pub fn send_amount_to(
|
||||
|
@ -321,16 +322,18 @@ impl LocalServerContainer {
|
|||
wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file.");
|
||||
|
||||
let keychain = wallet_seed
|
||||
.derive_keychain("grin_test")
|
||||
.derive_keychain("")
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
let max_outputs = 500;
|
||||
let mut wallet = FileWallet::new(config.clone(), keychain)
|
||||
|
||||
let mut wallet = FileWallet::new(config.clone(), "grin_test")
|
||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||
let result = wallet::issue_send_tx(
|
||||
wallet.keychain = Some(keychain);
|
||||
let result = wallet::libwallet::internal::tx::issue_send_tx(
|
||||
&mut wallet,
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest.to_string(),
|
||||
dest,
|
||||
max_outputs,
|
||||
selection_strategy == "all",
|
||||
fluff,
|
||||
|
|
247
src/bin/grin.rs
247
src/bin/grin.rs
|
@ -216,6 +216,9 @@ fn main() {
|
|||
.help("Port on which to run the wallet listener")
|
||||
.takes_value(true)))
|
||||
|
||||
.subcommand(SubCommand::with_name("owner_api")
|
||||
.about("Runs the wallet's local web API."))
|
||||
|
||||
.subcommand(SubCommand::with_name("receive")
|
||||
.about("Processes a JSON transaction file.")
|
||||
.arg(Arg::with_name("input")
|
||||
|
@ -411,22 +414,24 @@ fn server_command(server_args: Option<&ArgMatches>, mut global_config: GlobalCon
|
|||
|
||||
if let Some(true) = server_config.run_wallet_listener {
|
||||
let mut wallet_config = global_config.members.unwrap().wallet;
|
||||
let wallet_seed = match wallet::WalletSeed::from_file(&wallet_config) {
|
||||
Ok(ws) => ws,
|
||||
Err(_) => wallet::WalletSeed::init_file(&wallet_config)
|
||||
.expect("Failed to create wallet seed file."),
|
||||
if let Err(_) = wallet::WalletSeed::from_file(&wallet_config) {
|
||||
wallet::WalletSeed::init_file(&wallet_config)
|
||||
.expect("Failed to create wallet seed file.");
|
||||
};
|
||||
let mut keychain = wallet_seed
|
||||
.derive_keychain("")
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
|
||||
let _ = thread::Builder::new()
|
||||
.name("wallet_listener".to_string())
|
||||
.spawn(move || {
|
||||
let wallet = FileWallet::new(wallet_config.clone(), keychain).unwrap_or_else(|e| {
|
||||
let wallet = FileWallet::new(wallet_config.clone(), "").unwrap_or_else(|e| {
|
||||
panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)
|
||||
});
|
||||
wallet::server::start_rest_apis(wallet, &wallet_config.api_listen_addr());
|
||||
wallet::controller::foreign_listener(wallet, &wallet_config.api_listen_addr())
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error creating wallet listener: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -524,117 +529,159 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
|||
// Generate the initial wallet seed if we are running "wallet init".
|
||||
if let ("init", Some(_)) = wallet_args.subcommand() {
|
||||
wallet::WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file.");
|
||||
|
||||
// we are done here with creating the wallet, so just return
|
||||
return;
|
||||
}
|
||||
|
||||
let wallet_seed =
|
||||
wallet::WalletSeed::from_file(&wallet_config).expect("Failed to read wallet seed file.");
|
||||
let passphrase = wallet_args
|
||||
.value_of("pass")
|
||||
.expect("Failed to read passphrase.");
|
||||
let keychain = wallet_seed
|
||||
.derive_keychain(&passphrase)
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
let mut wallet = FileWallet::new(wallet_config.clone(), keychain)
|
||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
|
||||
|
||||
match wallet_args.subcommand() {
|
||||
("listen", Some(listen_args)) => {
|
||||
if let Some(port) = listen_args.value_of("port") {
|
||||
wallet_config.api_listen_port = port.parse().unwrap();
|
||||
// Handle listener startup commands
|
||||
{
|
||||
let wallet = FileWallet::new(wallet_config.clone(), passphrase).unwrap_or_else(|e| {
|
||||
panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)
|
||||
});
|
||||
match wallet_args.subcommand() {
|
||||
("listen", Some(listen_args)) => {
|
||||
if let Some(port) = listen_args.value_of("port") {
|
||||
wallet_config.api_listen_port = port.parse().unwrap();
|
||||
}
|
||||
wallet::controller::foreign_listener(wallet, &wallet_config.api_listen_addr())
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error creating wallet listener: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
)
|
||||
});
|
||||
}
|
||||
wallet::server::start_rest_apis(wallet, &wallet_config.api_listen_addr());
|
||||
}
|
||||
("send", Some(send_args)) => {
|
||||
let amount = send_args
|
||||
.value_of("amount")
|
||||
.expect("Amount to send required");
|
||||
let amount = core::core::amount_from_hr_string(amount)
|
||||
.expect("Could not parse amount as a number with optional decimal point.");
|
||||
let minimum_confirmations: u64 = send_args
|
||||
.value_of("minimum_confirmations")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Could not parse minimum_confirmations as a whole number.");
|
||||
let selection_strategy = send_args
|
||||
.value_of("selection_strategy")
|
||||
.expect("Selection strategy required");
|
||||
let dest = send_args
|
||||
.value_of("dest")
|
||||
.expect("Destination wallet address required");
|
||||
let mut fluff = false;
|
||||
if send_args.is_present("fluff") {
|
||||
fluff = true;
|
||||
("owner_api", Some(_api_args)) => {
|
||||
wallet::controller::owner_listener(wallet, "127.0.0.1:13420").unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error creating wallet api listener: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
)
|
||||
});
|
||||
}
|
||||
let max_outputs = 500;
|
||||
let result = wallet::issue_send_tx(
|
||||
&mut wallet,
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest.to_string(),
|
||||
max_outputs,
|
||||
selection_strategy == "all",
|
||||
fluff,
|
||||
);
|
||||
match result {
|
||||
Ok(_) => info!(
|
||||
LOGGER,
|
||||
"Tx sent: {} grin to {} (strategy '{}')",
|
||||
amount_to_hr_string(amount),
|
||||
dest,
|
||||
selection_strategy,
|
||||
),
|
||||
Err(e) => {
|
||||
error!(LOGGER, "Tx not sent: {}", e.cause());
|
||||
match e.downcast::<libwallet::Error>() {
|
||||
Ok(le) => {
|
||||
match le.kind() {
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
// Handle single-use (command line) owner commands
|
||||
{
|
||||
let mut wallet = FileWallet::new(wallet_config.clone(), passphrase).unwrap_or_else(|e| {
|
||||
panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)
|
||||
});
|
||||
let _res = wallet::controller::owner_single_use(&mut wallet, |api| {
|
||||
match wallet_args.subcommand() {
|
||||
("send", Some(send_args)) => {
|
||||
let amount = send_args
|
||||
.value_of("amount")
|
||||
.expect("Amount to send required");
|
||||
let amount = core::core::amount_from_hr_string(amount)
|
||||
.expect("Could not parse amount as a number with optional decimal point.");
|
||||
let minimum_confirmations: u64 = send_args
|
||||
.value_of("minimum_confirmations")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Could not parse minimum_confirmations as a whole number.");
|
||||
let selection_strategy = send_args
|
||||
.value_of("selection_strategy")
|
||||
.expect("Selection strategy required");
|
||||
let dest = send_args
|
||||
.value_of("dest")
|
||||
.expect("Destination wallet address required");
|
||||
let mut fluff = false;
|
||||
if send_args.is_present("fluff") {
|
||||
fluff = true;
|
||||
}
|
||||
let max_outputs = 500;
|
||||
let result = api.issue_send_tx(
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
max_outputs,
|
||||
selection_strategy == "all",
|
||||
fluff,
|
||||
);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
info!(
|
||||
LOGGER,
|
||||
"Tx sent: {} grin to {} (strategy '{}')",
|
||||
amount_to_hr_string(amount),
|
||||
dest,
|
||||
selection_strategy,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!(LOGGER, "Tx not sent: {:?}", e);
|
||||
match e.kind() {
|
||||
// user errors, don't backtrace
|
||||
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
|
||||
libwallet::ErrorKind::FeeDispute { .. } => {}
|
||||
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
|
||||
_ => {
|
||||
// otherwise give full dump
|
||||
error!(LOGGER, "Backtrace: {}", le.backtrace().unwrap());
|
||||
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
||||
}
|
||||
};
|
||||
Err(e)
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
("burn", Some(send_args)) => {
|
||||
let amount = send_args
|
||||
.value_of("amount")
|
||||
.expect("Amount to burn required");
|
||||
let amount = core::core::amount_from_hr_string(amount)
|
||||
.expect("Could not parse amount as number with optional decimal point.");
|
||||
let minimum_confirmations: u64 = send_args
|
||||
.value_of("minimum_confirmations")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Could not parse minimum_confirmations as a whole number.");
|
||||
let max_outputs = 500;
|
||||
api.issue_burn_tx(amount, minimum_confirmations, max_outputs)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Error burning tx: {:?} Config: {:?}", e, wallet_config)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
("info", Some(_)) => {
|
||||
let _res = wallet::display::info(&api.retrieve_summary_info()?.1)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error getting wallet info: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
("outputs", Some(_)) => {
|
||||
let (height, validated) = api.node_height()?;
|
||||
let (_, outputs) = api.retrieve_outputs(show_spent)?;
|
||||
let _res =
|
||||
wallet::display::outputs(height, validated, outputs).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error getting wallet outputs: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
("restore", Some(_)) => {
|
||||
let _res = api.restore().unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error getting restoring wallet: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
|
||||
}
|
||||
}
|
||||
("burn", Some(send_args)) => {
|
||||
let amount = send_args
|
||||
.value_of("amount")
|
||||
.expect("Amount to burn required");
|
||||
let amount = core::core::amount_from_hr_string(amount)
|
||||
.expect("Could not parse amount as number with optional decimal point.");
|
||||
let minimum_confirmations: u64 = send_args
|
||||
.value_of("minimum_confirmations")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Could not parse minimum_confirmations as a whole number.");
|
||||
let max_outputs = 500;
|
||||
wallet::issue_burn_tx(&mut wallet, amount, minimum_confirmations, max_outputs).unwrap();
|
||||
}
|
||||
("info", Some(_)) => {
|
||||
let res = wallet::show_info(&mut wallet);
|
||||
if let Err(e) = res {
|
||||
println!("Could not get wallet info: {}", e);
|
||||
}
|
||||
}
|
||||
("outputs", Some(_)) => {
|
||||
wallet::show_outputs(&mut wallet, show_spent);
|
||||
}
|
||||
("restore", Some(_)) => {
|
||||
let res = wallet.restore();
|
||||
if let Err(e) = res {
|
||||
println!("Could not restore wallet: {}", e);
|
||||
}
|
||||
}
|
||||
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,17 +12,70 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use core::core;
|
||||
use core::core::amount_to_hr_string;
|
||||
use libwallet::Error;
|
||||
use libwallet::types::WalletBackend;
|
||||
use libwallet::updater;
|
||||
use libwallet::types::{OutputData, WalletInfo};
|
||||
use prettytable;
|
||||
use std::io::prelude::*;
|
||||
use term;
|
||||
|
||||
pub fn show_info<T>(wallet: &mut T) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
let wallet_info = updater::retrieve_info(wallet)?;
|
||||
/// Display outputs in a pretty way
|
||||
pub fn outputs(cur_height: u64, validated: bool, outputs: Vec<OutputData>) -> Result<(), Error> {
|
||||
let title = format!("Wallet Outputs - Block Height: {}", 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->"Key Id",
|
||||
bMG->"Block Height",
|
||||
bMG->"Locked Until",
|
||||
bMG->"Status",
|
||||
bMG->"Is Coinbase?",
|
||||
bMG->"Num. of Confirmations",
|
||||
bMG->"Value"
|
||||
]);
|
||||
|
||||
for out in outputs {
|
||||
let key_id = format!("{}", out.key_id);
|
||||
let height = format!("{}", out.height);
|
||||
let lock_height = format!("{}", out.lock_height);
|
||||
let status = format!("{:?}", out.status);
|
||||
let is_coinbase = format!("{}", out.is_coinbase);
|
||||
let num_confirmations = format!("{}", out.num_confirmations(cur_height));
|
||||
let value = format!("{}", core::amount_to_hr_string(out.value));
|
||||
table.add_row(row![
|
||||
bFC->key_id,
|
||||
bFB->height,
|
||||
bFB->lock_height,
|
||||
bFR->status,
|
||||
bFY->is_coinbase,
|
||||
bFB->num_confirmations,
|
||||
bFG->value
|
||||
]);
|
||||
}
|
||||
|
||||
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 summary info in a pretty way
|
||||
pub fn info(wallet_info: &WalletInfo) -> Result<(), Error> {
|
||||
println!(
|
||||
"\n____ Wallet Summary Info at {} ({}) ____\n",
|
||||
wallet_info.current_height, wallet_info.data_confirmed_from
|
|
@ -26,7 +26,7 @@ use tokio_core::reactor;
|
|||
use tokio_retry::Retry;
|
||||
use tokio_retry::strategy::FibonacciBackoff;
|
||||
|
||||
use failure::{self, ResultExt};
|
||||
use failure::ResultExt;
|
||||
|
||||
use keychain::{self, Keychain};
|
||||
use util;
|
||||
|
@ -166,9 +166,11 @@ impl WalletSeed {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct FileWallet {
|
||||
/// Keychain
|
||||
pub keychain: Keychain,
|
||||
pub keychain: Option<Keychain>,
|
||||
/// Configuration
|
||||
pub config: WalletConfig,
|
||||
/// passphrase: TODO better ways of dealing with this other than storing
|
||||
passphrase: String,
|
||||
/// List of outputs
|
||||
pub outputs: HashMap<String, OutputData>,
|
||||
/// Data file path
|
||||
|
@ -180,9 +182,27 @@ pub struct FileWallet {
|
|||
}
|
||||
|
||||
impl WalletBackend for FileWallet {
|
||||
/// Initialise with whatever stored credentials we have
|
||||
fn open_with_credentials(&mut self) -> Result<(), libwallet::Error> {
|
||||
let wallet_seed = WalletSeed::from_file(&self.config)
|
||||
.context(libwallet::ErrorKind::CallbackImpl("Error opening wallet"))?;
|
||||
self.keychain = Some(wallet_seed.derive_keychain(&self.passphrase).context(
|
||||
libwallet::ErrorKind::CallbackImpl("Error deriving keychain"),
|
||||
)?);
|
||||
// Just blow up password for now after it's been used
|
||||
self.passphrase = String::from("");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Close wallet and remove any stored credentials (TBD)
|
||||
fn close(&mut self) -> Result<(), libwallet::Error> {
|
||||
self.keychain = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the keychain being used
|
||||
fn keychain(&mut self) -> &mut Keychain {
|
||||
&mut self.keychain
|
||||
self.keychain.as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Return URL for check node
|
||||
|
@ -371,14 +391,21 @@ impl WalletBackend for FileWallet {
|
|||
eligible.reverse();
|
||||
eligible.iter().take(max_outputs).cloned().collect()
|
||||
}
|
||||
|
||||
/// Restore wallet contents
|
||||
fn restore(&mut self) -> Result<(), libwallet::Error> {
|
||||
libwallet::internal::restore::restore(self).context(libwallet::ErrorKind::Restore)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FileWallet {
|
||||
/// Create a new FileWallet instance
|
||||
pub fn new(config: WalletConfig, keychain: Keychain) -> Result<Self, Error> {
|
||||
pub fn new(config: WalletConfig, passphrase: &str) -> Result<Self, Error> {
|
||||
let mut retval = FileWallet {
|
||||
keychain: keychain,
|
||||
keychain: None,
|
||||
config: config.clone(),
|
||||
passphrase: String::from(passphrase),
|
||||
outputs: HashMap::new(),
|
||||
data_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DAT_FILE),
|
||||
backup_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, BCK_FILE),
|
||||
|
@ -390,12 +417,6 @@ impl FileWallet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Restore wallet contents
|
||||
pub fn restore(&mut self) -> Result<(), failure::Error> {
|
||||
libwallet::restore::restore(self).context(libwallet::ErrorKind::Restore)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the wallet data or create brand files if the data
|
||||
/// files don't yet exist
|
||||
fn read_or_create_paths(&mut self) -> Result<(), Error> {
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use bodyparser;
|
||||
use iron::Handler;
|
||||
use iron::prelude::*;
|
||||
use iron::status;
|
||||
use serde_json;
|
||||
|
||||
use core::ser;
|
||||
use error::{Error, ErrorKind};
|
||||
use failure::{Fail, ResultExt};
|
||||
use libwallet::types::*;
|
||||
use receiver::receive_coinbase;
|
||||
use util;
|
||||
|
||||
pub struct CoinbaseHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
pub wallet: Arc<RwLock<T>>,
|
||||
}
|
||||
|
||||
impl<T> CoinbaseHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
fn build_coinbase(&self, wallet: &mut T, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let (out, kern, block_fees) =
|
||||
receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?;
|
||||
|
||||
let out_bin = ser::ser_vec(&out).context(ErrorKind::Node)?;
|
||||
|
||||
let kern_bin = ser::ser_vec(&kern).context(ErrorKind::Node)?;
|
||||
|
||||
let key_id_bin = match block_fees.key_id {
|
||||
Some(key_id) => ser::ser_vec(&key_id).context(ErrorKind::Node)?,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
Ok(CbData {
|
||||
output: util::to_hex(out_bin),
|
||||
kernel: util::to_hex(kern_bin),
|
||||
key_id: util::to_hex(key_id_bin),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - error handling - what to return if we fail to get the wallet lock for
|
||||
// some reason...
|
||||
impl<T> Handler for CoinbaseHandler<T>
|
||||
where
|
||||
T: WalletBackend + Send + Sync + 'static,
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let struct_body = req.get::<bodyparser::Struct<BlockFees>>();
|
||||
let mut wallet = self.wallet.write().unwrap();
|
||||
if let Ok(Some(block_fees)) = struct_body {
|
||||
let coinbase = self.build_coinbase(&mut wallet, &block_fees)
|
||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
||||
if let Ok(json) = serde_json::to_string(&coinbase) {
|
||||
Ok(Response::with((status::Ok, json)))
|
||||
} else {
|
||||
Ok(Response::with((status::BadRequest, "")))
|
||||
}
|
||||
} else {
|
||||
Ok(Response::with((status::BadRequest, "")))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,22 +46,13 @@ extern crate grin_core as core;
|
|||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_util as util;
|
||||
|
||||
pub mod client;
|
||||
pub mod display;
|
||||
mod error;
|
||||
pub mod file_wallet;
|
||||
mod handlers;
|
||||
mod info;
|
||||
pub mod libtx;
|
||||
pub mod libwallet;
|
||||
mod outputs;
|
||||
pub mod receiver;
|
||||
mod sender;
|
||||
pub mod server;
|
||||
|
||||
pub use error::{Error, ErrorKind};
|
||||
pub use file_wallet::{FileWallet, WalletConfig, WalletSeed};
|
||||
pub use info::show_info;
|
||||
pub use libwallet::controller;
|
||||
pub use libwallet::types::{BlockFees, CbData, WalletInfo};
|
||||
pub use outputs::show_outputs;
|
||||
pub use receiver::WalletReceiver;
|
||||
pub use sender::{issue_burn_tx, issue_send_tx};
|
||||
|
|
|
@ -24,8 +24,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
|
||||
|
|
154
wallet/src/libwallet/api.rs
Normal file
154
wallet/src/libwallet/api.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Wrappers around library functions, intended to split functions
|
||||
//! into external and internal APIs (i.e. functions for the local wallet
|
||||
//! vs. functions to interact with someone else)
|
||||
//! Still experimental, not sure this is the best way to do this
|
||||
|
||||
use libtx::slate::Slate;
|
||||
use libwallet::Error;
|
||||
use libwallet::internal::{tx, updater};
|
||||
use libwallet::types::{BlockFees, CbData, OutputData, WalletBackend, WalletInfo};
|
||||
|
||||
/// Wrapper around internal API functions, containing a reference to
|
||||
/// the wallet/keychain that they're acting upon
|
||||
pub struct APIOwner<'a, W>
|
||||
where
|
||||
W: 'a + WalletBackend,
|
||||
{
|
||||
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
||||
/// perhaps)
|
||||
pub wallet: &'a mut W,
|
||||
}
|
||||
|
||||
impl<'a, W> APIOwner<'a, W>
|
||||
where
|
||||
W: 'a + WalletBackend,
|
||||
{
|
||||
/// Create new API instance
|
||||
pub fn new(wallet_in: &'a mut W) -> APIOwner<'a, W> {
|
||||
APIOwner { wallet: wallet_in }
|
||||
}
|
||||
|
||||
/// Attempt to update and retrieve outputs
|
||||
/// Return (whether the outputs were validated against a node, OutputData)
|
||||
pub fn retrieve_outputs(
|
||||
&mut self,
|
||||
include_spent: bool,
|
||||
) -> Result<(bool, Vec<OutputData>), Error> {
|
||||
let validated = self.update_outputs();
|
||||
Ok((
|
||||
validated,
|
||||
updater::retrieve_outputs(self.wallet, include_spent)?,
|
||||
))
|
||||
}
|
||||
|
||||
/// 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)?))
|
||||
}
|
||||
|
||||
/// Issues a send transaction and sends to recipient
|
||||
/// (TODO: Split into separate functions, create tx, send, complete tx)
|
||||
pub fn issue_send_tx(
|
||||
&mut self,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
dest: &str,
|
||||
max_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
fluff: bool,
|
||||
) -> Result<(), Error> {
|
||||
tx::issue_send_tx(
|
||||
self.wallet,
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
fluff,
|
||||
)
|
||||
}
|
||||
|
||||
/// Issue a burn TX
|
||||
pub fn issue_burn_tx(
|
||||
&mut self,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
) -> Result<(), Error> {
|
||||
tx::issue_burn_tx(self.wallet, amount, minimum_confirmations, max_outputs)
|
||||
}
|
||||
|
||||
/// Attempt to restore contents of wallet
|
||||
pub fn restore(&mut self) -> Result<(), Error> {
|
||||
self.wallet.restore()
|
||||
}
|
||||
|
||||
/// Retrieve current height from node
|
||||
pub fn node_height(&mut self) -> Result<(u64, bool), Error> {
|
||||
match updater::get_tip_from_node(self.wallet.node_url()) {
|
||||
Ok(tip) => Ok((tip.height, true)),
|
||||
Err(_) => {
|
||||
let outputs = self.retrieve_outputs(true)?;
|
||||
let height = match outputs.1.iter().map(|out| out.height).max() {
|
||||
Some(height) => height,
|
||||
None => 0,
|
||||
};
|
||||
Ok((height, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to update outputs in wallet, return whether it was successful
|
||||
fn update_outputs(&mut self) -> bool {
|
||||
match updater::refresh_outputs(self.wallet) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around external API functions, intended to communicate
|
||||
/// with other parties
|
||||
pub struct APIForeign<'a, W>
|
||||
where
|
||||
W: 'a + WalletBackend,
|
||||
{
|
||||
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
||||
/// perhaps)
|
||||
pub wallet: &'a mut W,
|
||||
}
|
||||
|
||||
impl<'a, W> APIForeign<'a, W>
|
||||
where
|
||||
W: 'a + WalletBackend,
|
||||
{
|
||||
/// Create new API instance
|
||||
pub fn new(wallet_in: &'a mut W) -> APIForeign<'a, W> {
|
||||
APIForeign { wallet: wallet_in }
|
||||
}
|
||||
|
||||
/// Build a new (potential) coinbase transaction in the wallet
|
||||
pub fn build_coinbase(&mut self, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
updater::build_coinbase(self.wallet, block_fees)
|
||||
}
|
||||
|
||||
/// Receive a transaction from a sender
|
||||
pub fn receive_tx(&mut self, slate: &mut Slate) -> Result<(), Error> {
|
||||
tx::receive_tx(self.wallet, slate)
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Client functions: TODO: doesn't really belong here or needs to be
|
||||
//! traited out
|
||||
|
||||
use failure::ResultExt;
|
||||
use futures::{Future, Stream};
|
||||
use hyper;
|
||||
|
@ -33,14 +36,17 @@ pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Erro
|
|||
Err(e) => {
|
||||
error!(
|
||||
LOGGER,
|
||||
"Failed to get coinbase from {}. Run grin wallet listen", url
|
||||
"Failed to get coinbase from {}. Run grin wallet listen?", url
|
||||
);
|
||||
error!(LOGGER, "Underlying Error: {}", e.cause().unwrap());
|
||||
error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap());
|
||||
Err(e)
|
||||
}
|
||||
Ok(res) => Ok(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the slate to a listening wallet instance
|
||||
pub fn send_slate(url: &str, slate: &Slate, fluff: bool) -> Result<Slate, Error> {
|
||||
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
|
||||
let client = hyper::Client::new(&core.handle());
|
||||
|
@ -82,10 +88,12 @@ fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, E
|
|||
);
|
||||
req.headers_mut().set(ContentType::json());
|
||||
let json = serde_json::to_string(&block_fees).context(ErrorKind::Format)?;
|
||||
trace!(LOGGER, "Sending coinbase request: {:?}", json);
|
||||
req.set_body(json);
|
||||
|
||||
let work = client.request(req).and_then(|res| {
|
||||
res.body().concat2().and_then(move |body| {
|
||||
trace!(LOGGER, "Returned Body: {:?}", body);
|
||||
let coinbase: CbData =
|
||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(coinbase)
|
302
wallet/src/libwallet/controller.rs
Normal file
302
wallet/src/libwallet/controller.rs
Normal file
|
@ -0,0 +1,302 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Controller for wallet.. instantiates and handles listeners (or single-run
|
||||
//! invocations) as needed.
|
||||
//! Still experimental
|
||||
use api::ApiServer;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use bodyparser;
|
||||
use iron::Handler;
|
||||
use iron::prelude::*;
|
||||
use iron::status;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
||||
use failure::Fail;
|
||||
|
||||
use libtx::slate::Slate;
|
||||
use libwallet::api::{APIForeign, APIOwner};
|
||||
use libwallet::types::{BlockFees, CbData, OutputData, WalletBackend, WalletInfo};
|
||||
use libwallet::{Error, ErrorKind};
|
||||
|
||||
use util::LOGGER;
|
||||
|
||||
/// Instantiate wallet Owner API for a single-use (command line) call
|
||||
/// Return a function containing a loaded API context to call
|
||||
pub fn owner_single_use<F, T>(wallet: &mut T, f: F) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
F: FnOnce(&mut APIOwner<T>) -> Result<(), Error>,
|
||||
{
|
||||
wallet.open_with_credentials()?;
|
||||
f(&mut APIOwner::new(wallet))?;
|
||||
wallet.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Instantiate wallet Foreign API for a single-use (command line) call
|
||||
/// Return a function containing a loaded API context to call
|
||||
pub fn foreign_single_use<F, T>(wallet: &mut T, f: F) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
F: FnOnce(&mut APIForeign<T>) -> Result<(), Error>,
|
||||
{
|
||||
wallet.open_with_credentials()?;
|
||||
f(&mut APIForeign::new(wallet))?;
|
||||
wallet.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Listener version, providing same API but listening for requests on a
|
||||
/// port and wrapping the calls
|
||||
pub fn owner_listener<T>(wallet: T, addr: &str) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
OwnerAPIHandler<T>: Handler,
|
||||
{
|
||||
let api_handler = OwnerAPIHandler {
|
||||
wallet: Arc::new(Mutex::new(wallet)),
|
||||
};
|
||||
|
||||
let router = router!(
|
||||
receive_tx: get "/wallet/owner/*" => api_handler,
|
||||
);
|
||||
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_handler(router);
|
||||
match apis.start(addr) {
|
||||
Err(e) => error!(
|
||||
LOGGER,
|
||||
"Failed to start Grin wallet owner API listener: {}.", e
|
||||
),
|
||||
Ok(_) => info!(LOGGER, "Grin wallet owner API listener started at {}", addr),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Listener version, providing same API but listening for requests on a
|
||||
/// port and wrapping the calls
|
||||
pub fn foreign_listener<T>(wallet: T, addr: &str) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
ForeignAPIHandler<T>: Handler,
|
||||
{
|
||||
let api_handler = ForeignAPIHandler {
|
||||
wallet: Arc::new(Mutex::new(wallet)),
|
||||
};
|
||||
|
||||
let router = router!(
|
||||
receive_tx: post "/wallet/foreign/*" => api_handler,
|
||||
);
|
||||
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_handler(router);
|
||||
match apis.start(addr) {
|
||||
Err(e) => error!(
|
||||
LOGGER,
|
||||
"Failed to start Grin wallet foreign listener: {}.", e
|
||||
),
|
||||
Ok(_) => info!(LOGGER, "Grin wallet foreign listener started at {}", addr),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
/// API Handler/Wrapper for owner functions
|
||||
|
||||
pub struct OwnerAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
/// Wallet instance
|
||||
pub wallet: Arc<Mutex<T>>,
|
||||
}
|
||||
|
||||
impl<T> OwnerAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
fn retrieve_outputs(
|
||||
&self,
|
||||
req: &mut Request,
|
||||
api: &mut APIOwner<T>,
|
||||
) -> Result<Vec<OutputData>, Error> {
|
||||
let res = api.retrieve_outputs(false)?;
|
||||
Ok(res.1)
|
||||
}
|
||||
|
||||
fn retrieve_summary_info(
|
||||
&self,
|
||||
req: &mut Request,
|
||||
api: &mut APIOwner<T>,
|
||||
) -> Result<WalletInfo, Error> {
|
||||
let res = api.retrieve_summary_info()?;
|
||||
Ok(res.1)
|
||||
}
|
||||
|
||||
fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner<T>) -> 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>) -> Result<(), Error> {
|
||||
// TODO: Args
|
||||
api.issue_burn_tx(60, 10, 1000)
|
||||
}
|
||||
|
||||
fn handle_request(&self, req: &mut Request, api: &mut APIOwner<T>) -> 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)
|
||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
||||
"retrieve_summary_info" => json_response_pretty(&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)
|
||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
||||
_ => Err(IronError::new(
|
||||
Fail::compat(ErrorKind::Hyper),
|
||||
status::BadRequest,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Handler for OwnerAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend + Send + Sync + '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_json = self.handle_request(req, &mut api);
|
||||
api.wallet
|
||||
.close()
|
||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
||||
resp_json
|
||||
}
|
||||
}
|
||||
|
||||
/// API Handler/Wrapper for foreign functions
|
||||
|
||||
pub struct ForeignAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
/// Wallet instance
|
||||
pub wallet: Arc<Mutex<T>>,
|
||||
}
|
||||
|
||||
impl<T> ForeignAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
fn build_coinbase(&self, req: &mut Request, api: &mut APIForeign<T>) -> Result<CbData, Error> {
|
||||
let struct_body = req.get::<bodyparser::Struct<BlockFees>>();
|
||||
match struct_body {
|
||||
Ok(Some(block_fees)) => api.build_coinbase(&block_fees),
|
||||
Ok(None) => {
|
||||
error!(LOGGER, "Missing request body: build_coinbase");
|
||||
Err(ErrorKind::GenericError(
|
||||
"Invalid request body: build_coinbase",
|
||||
))?
|
||||
}
|
||||
Err(e) => {
|
||||
error!(LOGGER, "Invalid request body: build_coinbase: {:?}", e);
|
||||
Err(ErrorKind::GenericError(
|
||||
"Invalid request body: build_coinbase",
|
||||
))?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn receive_tx(&self, req: &mut Request, api: &mut APIForeign<T>) -> Result<Slate, Error> {
|
||||
let struct_body = req.get::<bodyparser::Struct<Slate>>();
|
||||
if let Ok(Some(mut slate)) = struct_body {
|
||||
api.receive_tx(&mut slate)?;
|
||||
Ok(slate.clone())
|
||||
} else {
|
||||
Err(ErrorKind::GenericError("Invalid request body: receive_tx"))?
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request(&self, req: &mut Request, api: &mut APIForeign<T>) -> IronResult<Response> {
|
||||
let url = req.url.clone();
|
||||
let path_elems = url.path();
|
||||
match *path_elems.last().unwrap() {
|
||||
"build_coinbase" => json_response(&self.build_coinbase(req, api)
|
||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
||||
"receive_tx" => json_response(&self.receive_tx(req, api)
|
||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?),
|
||||
_ => Err(IronError::new(
|
||||
Fail::compat(ErrorKind::Hyper),
|
||||
status::BadRequest,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Handler for ForeignAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend + Send + Sync + '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 = APIForeign::new(&mut *wallet);
|
||||
let resp_json = self.handle_request(req, &mut api);
|
||||
api.wallet
|
||||
.close()
|
||||
.map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?;
|
||||
resp_json
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to serialize a struct into JSON and produce a sensible IronResult
|
||||
// out of it.
|
||||
fn json_response<T>(s: &T) -> IronResult<Response>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
match serde_json::to_string(s) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
|
||||
}
|
||||
}
|
||||
|
||||
// pretty-printed version of above
|
||||
fn json_response_pretty<T>(s: &T) -> IronResult<Response>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
match serde_json::to_string_pretty(s) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ pub enum ErrorKind {
|
|||
Secp,
|
||||
|
||||
/// Callback implementation error conversion
|
||||
#[fail(display = "Callback Implementation error")]
|
||||
#[fail(display = "Trait Implementation error")]
|
||||
CallbackImpl(&'static str),
|
||||
|
||||
/// Callback implementation error conversion
|
||||
|
|
29
wallet/src/libwallet/internal/mod.rs
Normal file
29
wallet/src/libwallet/internal/mod.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! lower-level wallet functions which build upon libtx to perform wallet
|
||||
//! operations
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
#![deny(non_snake_case)]
|
||||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod keys;
|
||||
pub mod restore;
|
||||
pub mod selection;
|
||||
pub mod sigcontext;
|
||||
pub mod tx;
|
||||
pub mod updater;
|
|
@ -17,8 +17,8 @@
|
|||
use keychain::Identifier;
|
||||
use libtx::{build, tx_fee, slate::Slate};
|
||||
use libwallet::error::{Error, ErrorKind};
|
||||
use libwallet::internal::{keys, sigcontext};
|
||||
use libwallet::types::*;
|
||||
use libwallet::{keys, sigcontext};
|
||||
|
||||
/// Initialise a transaction on the sender side, returns a corresponding
|
||||
/// libwallet transaction slate with the appropriate inputs selected,
|
|
@ -12,32 +12,60 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Transaction buinding functions
|
||||
|
||||
use api;
|
||||
use client;
|
||||
use core::ser;
|
||||
use error::{Error, ErrorKind};
|
||||
use failure::{self, ResultExt};
|
||||
use failure::ResultExt;
|
||||
use keychain::{Identifier, Keychain};
|
||||
use libtx::slate::Slate;
|
||||
use libtx::{build, tx_fee};
|
||||
use libwallet::types::WalletBackend;
|
||||
use libwallet::{selection, updater};
|
||||
use receiver::TxWrapper;
|
||||
use libwallet::client;
|
||||
use libwallet::internal::{selection, updater};
|
||||
use libwallet::types::{TxWrapper, WalletBackend};
|
||||
use libwallet::{Error, ErrorKind};
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
|
||||
/// Receive a tranaction, modifying the slate accordingly (which can then be
|
||||
/// sent back to sender for posting)
|
||||
pub fn receive_tx<T: WalletBackend>(wallet: &mut T, slate: &mut Slate) -> Result<(), Error> {
|
||||
// create an output using the amount in the slate
|
||||
let (_, mut context, receiver_create_fn) =
|
||||
selection::build_recipient_output_with_slate(wallet, slate).unwrap();
|
||||
|
||||
// fill public keys
|
||||
let _ = slate.fill_round_1(
|
||||
wallet.keychain(),
|
||||
&mut context.sec_key,
|
||||
&context.sec_nonce,
|
||||
1,
|
||||
)?;
|
||||
|
||||
// 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);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Issue a new transaction to the provided sender by spending some of our
|
||||
/// wallet
|
||||
/// Outputs. The destination can be "stdout" (for command line) (currently
|
||||
/// disabled) or a URL to the recipients wallet receiver (to be implemented).
|
||||
/// TBD: this just does a straight http request to recipient.. split this out
|
||||
/// somehow
|
||||
pub fn issue_send_tx<T: WalletBackend>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
dest: String,
|
||||
dest: &str,
|
||||
max_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
fluff: bool,
|
||||
) -> Result<(), failure::Error> {
|
||||
) -> Result<(), Error> {
|
||||
// TODO: Stdout option, probably in a separate implementation
|
||||
if &dest[..4] != "http" {
|
||||
panic!(
|
||||
|
@ -84,9 +112,9 @@ pub fn issue_send_tx<T: WalletBackend>(
|
|||
0,
|
||||
)?;
|
||||
|
||||
let url = format!("{}/v1/receive/transaction", &dest);
|
||||
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
||||
debug!(LOGGER, "Posting partial transaction to {}", url);
|
||||
let mut slate = match client::send_slate(&url, &slate, fluff) {
|
||||
let mut slate = match client::send_slate(&url, &slate, fluff).context(ErrorKind::Node) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!(
|
||||
|
@ -117,6 +145,7 @@ pub fn issue_send_tx<T: WalletBackend>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Issue a burn tx
|
||||
pub fn issue_burn_tx<T: WalletBackend>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
|
@ -20,13 +20,51 @@ use std::collections::HashMap;
|
|||
use std::collections::hash_map::Entry;
|
||||
|
||||
use api;
|
||||
use core::consensus::reward;
|
||||
use core::core::{Output, TxKernel};
|
||||
use core::global;
|
||||
use core::ser;
|
||||
use keychain::Identifier;
|
||||
use libtx::reward;
|
||||
use libwallet::error::{Error, ErrorKind};
|
||||
use libwallet::internal::keys;
|
||||
use libwallet::types::*;
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
use util::secp::pedersen;
|
||||
|
||||
/// Retrieve all of the outputs (doesn't attempt to update from node)
|
||||
pub fn retrieve_outputs<T: WalletBackend>(
|
||||
wallet: &mut T,
|
||||
show_spent: bool,
|
||||
) -> Result<Vec<OutputData>, Error> {
|
||||
let root_key_id = wallet.keychain().clone().root_key_id();
|
||||
|
||||
let mut outputs = vec![];
|
||||
|
||||
// just read the wallet here, no need for a write lock
|
||||
let _ = wallet.read_wallet(|wallet_data| {
|
||||
outputs = wallet_data
|
||||
.outputs()
|
||||
.values()
|
||||
.filter(|out| out.root_key_id == root_key_id)
|
||||
.filter(|out| {
|
||||
if show_spent {
|
||||
true
|
||||
} else {
|
||||
out.status != OutputStatus::Spent
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.iter()
|
||||
.map(|&o| o.clone())
|
||||
.collect();
|
||||
outputs.sort_by_key(|out| out.n_child);
|
||||
Ok(())
|
||||
});
|
||||
Ok(outputs)
|
||||
}
|
||||
|
||||
/// Refreshes the outputs in a wallet with the latest information
|
||||
/// from a node
|
||||
pub fn refresh_outputs<T>(wallet: &mut T) -> Result<(), Error>
|
||||
|
@ -314,3 +352,87 @@ where
|
|||
});
|
||||
ret_val
|
||||
}
|
||||
|
||||
/// Build a coinbase output and insert into wallet
|
||||
pub fn build_coinbase<T>(wallet: &mut T, block_fees: &BlockFees) -> Result<CbData, Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
let (out, kern, block_fees) = receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?;
|
||||
|
||||
let out_bin = ser::ser_vec(&out).context(ErrorKind::Node)?;
|
||||
|
||||
let kern_bin = ser::ser_vec(&kern).context(ErrorKind::Node)?;
|
||||
|
||||
let key_id_bin = match block_fees.key_id {
|
||||
Some(key_id) => ser::ser_vec(&key_id).context(ErrorKind::Node)?,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
Ok(CbData {
|
||||
output: util::to_hex(out_bin),
|
||||
kernel: util::to_hex(kern_bin),
|
||||
key_id: util::to_hex(key_id_bin),
|
||||
})
|
||||
}
|
||||
|
||||
//TODO: Split up the output creation and the wallet insertion
|
||||
/// Build a coinbase output and the corresponding kernel
|
||||
pub fn receive_coinbase<T>(
|
||||
wallet: &mut T,
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<(Output, TxKernel, BlockFees), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
let root_key_id = wallet.keychain().root_key_id();
|
||||
|
||||
let height = block_fees.height;
|
||||
let lock_height = height + global::coinbase_maturity();
|
||||
|
||||
// Now acquire the wallet lock and write the new output.
|
||||
let (key_id, derivation) = wallet.with_wallet(|wallet_data| {
|
||||
let key_id = block_fees.key_id();
|
||||
let (key_id, derivation) = match key_id {
|
||||
Some(key_id) => keys::retrieve_existing_key(wallet_data, key_id),
|
||||
None => keys::next_available_key(wallet_data),
|
||||
};
|
||||
|
||||
// track the new output and return the stuff needed for reward
|
||||
wallet_data.add_output(OutputData {
|
||||
root_key_id: root_key_id.clone(),
|
||||
key_id: key_id.clone(),
|
||||
n_child: derivation,
|
||||
value: reward(block_fees.fees),
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: height,
|
||||
lock_height: lock_height,
|
||||
is_coinbase: true,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
});
|
||||
|
||||
(key_id, derivation)
|
||||
})?;
|
||||
|
||||
debug!(
|
||||
LOGGER,
|
||||
"receive_coinbase: built candidate output - {:?}, {}",
|
||||
key_id.clone(),
|
||||
derivation,
|
||||
);
|
||||
|
||||
let mut block_fees = block_fees.clone();
|
||||
block_fees.key_id = Some(key_id.clone());
|
||||
|
||||
debug!(LOGGER, "receive_coinbase: {:?}", block_fees);
|
||||
|
||||
let (out, kern) = reward::output(
|
||||
&wallet.keychain(),
|
||||
&key_id,
|
||||
block_fees.fees,
|
||||
block_fees.height,
|
||||
).unwrap();
|
||||
/* .context(ErrorKind::Keychain)?; */
|
||||
Ok((out, kern, block_fees))
|
||||
}
|
|
@ -12,12 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Library specific to the Grin wallet implementation, as distinct from
|
||||
//! libwallet, which should build transactions without any knowledge of the
|
||||
//! wallet implementation.
|
||||
|
||||
// TODO: Once this is working, extract a set of traits that wallet
|
||||
// implementations would need to provide
|
||||
//! Higher level wallet functions which can be used by callers to operate
|
||||
//! on the wallet, as well as helpers to invoke and instantiate wallets
|
||||
//! and listeners
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
|
@ -25,12 +22,11 @@
|
|||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod api;
|
||||
pub mod client;
|
||||
pub mod controller;
|
||||
mod error;
|
||||
pub mod keys;
|
||||
pub mod restore;
|
||||
pub mod selection;
|
||||
pub mod sigcontext;
|
||||
pub mod internal;
|
||||
pub mod types;
|
||||
pub mod updater;
|
||||
|
||||
pub use libwallet::error::{Error, ErrorKind};
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Builds the blinded output and related signature proof for the block
|
||||
//! reward.
|
||||
use keychain;
|
||||
|
||||
use core::consensus::reward;
|
||||
use core::core::KernelFeatures;
|
||||
use core::core::{Output, OutputFeatures, ProofMessageElements, TxKernel};
|
||||
use libtx::error::Error;
|
||||
use libtx::{aggsig, proof};
|
||||
use util::{kernel_sig_msg, secp, static_secp_instance, LOGGER};
|
||||
|
||||
/// output a reward output
|
||||
pub fn output(
|
||||
keychain: &keychain::Keychain,
|
||||
key_id: &keychain::Identifier,
|
||||
fees: u64,
|
||||
height: u64,
|
||||
) -> Result<(Output, TxKernel), Error> {
|
||||
let value = reward(fees);
|
||||
let commit = keychain.commit(value, key_id)?;
|
||||
let msg = ProofMessageElements::new(value, key_id);
|
||||
|
||||
trace!(LOGGER, "Block reward - Pedersen Commit is: {:?}", commit,);
|
||||
|
||||
let rproof = proof::create(
|
||||
keychain,
|
||||
value,
|
||||
key_id,
|
||||
commit,
|
||||
None,
|
||||
msg.to_proof_message(),
|
||||
)?;
|
||||
|
||||
let output = Output {
|
||||
features: OutputFeatures::COINBASE_OUTPUT,
|
||||
commit: commit,
|
||||
proof: rproof,
|
||||
};
|
||||
|
||||
let secp = static_secp_instance();
|
||||
let secp = secp.lock().unwrap();
|
||||
let over_commit = secp.commit_value(reward(fees))?;
|
||||
let out_commit = output.commitment();
|
||||
let excess = secp.commit_sum(vec![out_commit], vec![over_commit])?;
|
||||
|
||||
// NOTE: Remember we sign the fee *and* the lock_height.
|
||||
// For a coinbase output the fee is 0 and the lock_height is
|
||||
// the lock_height of the coinbase output itself,
|
||||
// not the lock_height of the tx (there is no tx for a coinbase output).
|
||||
// This output will not be spendable earlier than lock_height (and we sign this
|
||||
// here).
|
||||
let msg = secp::Message::from_slice(&kernel_sig_msg(0, height))?;
|
||||
let sig = aggsig::sign_from_key_id(&secp, keychain, &msg, &key_id)?;
|
||||
|
||||
let proof = TxKernel {
|
||||
features: KernelFeatures::COINBASE_KERNEL,
|
||||
excess: excess,
|
||||
excess_sig: sig,
|
||||
fee: 0,
|
||||
// lock_height here is the height of the block (tx should be valid immediately)
|
||||
// *not* the lock_height of the coinbase output (only spendable 1,000 blocks later)
|
||||
lock_height: height,
|
||||
};
|
||||
Ok((output, proof))
|
||||
}
|
|
@ -33,6 +33,12 @@ use libwallet::error::{Error, ErrorKind};
|
|||
/// here expect that the wallet instance has instantiated itself or stored
|
||||
/// whatever credentials it needs
|
||||
pub trait WalletBackend {
|
||||
/// Initialise with whatever stored credentials we have
|
||||
fn open_with_credentials(&mut self) -> Result<(), Error>;
|
||||
|
||||
/// Close wallet and remove any stored credentials (TBD)
|
||||
fn close(&mut self) -> Result<(), Error>;
|
||||
|
||||
/// Return the keychain being used
|
||||
fn keychain(&mut self) -> &mut Keychain;
|
||||
|
||||
|
@ -79,6 +85,9 @@ pub trait WalletBackend {
|
|||
max_outputs: usize,
|
||||
select_all: bool,
|
||||
) -> Vec<OutputData>;
|
||||
|
||||
/// Attempt to restore the contents of a wallet from seed
|
||||
fn restore(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Information about an output that's being tracked by the wallet. Must be
|
||||
|
@ -354,3 +363,10 @@ pub struct WalletInfo {
|
|||
/// node confirming the data
|
||||
pub data_confirmed_from: String,
|
||||
}
|
||||
|
||||
/// Dummy wrapper for the hex-encoded serialized transaction.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TxWrapper {
|
||||
/// hex representation of transaction
|
||||
pub tx_hex: String,
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use core::core;
|
||||
use libwallet::types::{OutputStatus, WalletBackend};
|
||||
use libwallet::updater;
|
||||
use prettytable;
|
||||
use std::io::prelude::*;
|
||||
use term;
|
||||
|
||||
pub fn show_outputs<T: WalletBackend>(wallet: &mut T, show_spent: bool) {
|
||||
let root_key_id = wallet.keychain().clone().root_key_id();
|
||||
let result = updater::refresh_outputs(wallet);
|
||||
|
||||
// just read the wallet here, no need for a write lock
|
||||
let _ = wallet.read_wallet(|wallet_data| {
|
||||
// get the current height via the api
|
||||
// if we cannot get the current height use the max height known to the wallet
|
||||
let current_height = match updater::get_tip_from_node(wallet_data.node_url()) {
|
||||
Ok(tip) => tip.height,
|
||||
Err(_) => match wallet_data.outputs().values().map(|out| out.height).max() {
|
||||
Some(height) => height,
|
||||
None => 0,
|
||||
},
|
||||
};
|
||||
|
||||
let mut outputs = wallet_data
|
||||
.outputs()
|
||||
.values()
|
||||
.filter(|out| out.root_key_id == root_key_id)
|
||||
.filter(|out| {
|
||||
if show_spent {
|
||||
true
|
||||
} else {
|
||||
out.status != OutputStatus::Spent
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
outputs.sort_by_key(|out| out.n_child);
|
||||
|
||||
let title = format!("Wallet Outputs - Block Height: {}", current_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->"Key Id",
|
||||
bMG->"Block Height",
|
||||
bMG->"Locked Until",
|
||||
bMG->"Status",
|
||||
bMG->"Is Coinbase?",
|
||||
bMG->"Num. of Confirmations",
|
||||
bMG->"Value"
|
||||
]);
|
||||
|
||||
for out in outputs {
|
||||
let key_id = format!("{}", out.key_id);
|
||||
let height = format!("{}", out.height);
|
||||
let lock_height = format!("{}", out.lock_height);
|
||||
let status = format!("{:?}", out.status);
|
||||
let is_coinbase = format!("{}", out.is_coinbase);
|
||||
let num_confirmations = format!("{}", out.num_confirmations(current_height));
|
||||
let value = format!("{}", core::amount_to_hr_string(out.value));
|
||||
table.add_row(row![
|
||||
bFC->key_id,
|
||||
bFB->height,
|
||||
bFB->lock_height,
|
||||
bFR->status,
|
||||
bFY->is_coinbase,
|
||||
bFB->num_confirmations,
|
||||
bFG->value
|
||||
]);
|
||||
}
|
||||
|
||||
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
|
||||
table.printstd();
|
||||
println!();
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(_) = result {
|
||||
println!(
|
||||
"\nWARNING: Wallet failed to verify data. \
|
||||
The above is from local cache and possibly invalid! \
|
||||
(is your `grin server` offline or broken?)"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Provides the JSON/HTTP API for wallets to receive payments. Because
|
||||
//! receiving money in MimbleWimble requires an interactive exchange, a
|
||||
//! wallet server that's running at all time is required in many cases.
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use bodyparser;
|
||||
use iron::Handler;
|
||||
use iron::prelude::*;
|
||||
use iron::status;
|
||||
use serde_json;
|
||||
|
||||
use api;
|
||||
use core::consensus::reward;
|
||||
use core::core::{Output, TxKernel};
|
||||
use core::global;
|
||||
use error::Error;
|
||||
use failure::Fail;
|
||||
use libtx::{reward, slate::Slate};
|
||||
use libwallet::types::*;
|
||||
use libwallet::{keys, selection};
|
||||
use util::LOGGER;
|
||||
|
||||
/// Dummy wrapper for the hex-encoded serialized transaction.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TxWrapper {
|
||||
pub tx_hex: String,
|
||||
}
|
||||
|
||||
/// Component used to receive coins, implements all the receiving end of the
|
||||
/// wallet REST API as well as some of the command-line operations.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletReceiver<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
pub wallet: Arc<RwLock<T>>,
|
||||
}
|
||||
|
||||
impl<T> WalletReceiver<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
fn handle_send(&self, wallet: &mut T, slate: &mut Slate) -> Result<(), Error> {
|
||||
// create an output using the amount in the slate
|
||||
let (_, mut context, receiver_create_fn) =
|
||||
selection::build_recipient_output_with_slate(wallet, slate).unwrap();
|
||||
|
||||
// fill public keys
|
||||
let _ = slate.fill_round_1(
|
||||
wallet.keychain(),
|
||||
&mut context.sec_key,
|
||||
&context.sec_nonce,
|
||||
1,
|
||||
)?;
|
||||
|
||||
// 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);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Handler for WalletReceiver<T>
|
||||
where
|
||||
T: WalletBackend + Send + Sync + 'static,
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let struct_body = req.get::<bodyparser::Struct<Slate>>();
|
||||
let mut wallet = self.wallet.write().unwrap();
|
||||
|
||||
if let Ok(Some(mut slate)) = struct_body {
|
||||
let _ = self.handle_send(&mut wallet, &mut slate)
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
LOGGER,
|
||||
"Handling send -> Problematic slate, looks like this: {:?}", slate
|
||||
);
|
||||
e.context(api::ErrorKind::Internal(
|
||||
"Error processing partial transaction".to_owned(),
|
||||
))
|
||||
})
|
||||
.unwrap();
|
||||
let json = serde_json::to_string(&slate).unwrap();
|
||||
Ok(Response::with((status::Ok, json)))
|
||||
} else {
|
||||
Ok(Response::with((status::BadRequest, "")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Split up the output creation and the wallet insertion
|
||||
/// Build a coinbase output and the corresponding kernel
|
||||
pub fn receive_coinbase<T>(
|
||||
wallet: &mut T,
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<(Output, TxKernel, BlockFees), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
let root_key_id = wallet.keychain().root_key_id();
|
||||
|
||||
let height = block_fees.height;
|
||||
let lock_height = height + global::coinbase_maturity();
|
||||
|
||||
// Now acquire the wallet lock and write the new output.
|
||||
let (key_id, derivation) = wallet.with_wallet(|wallet_data| {
|
||||
let key_id = block_fees.key_id();
|
||||
let (key_id, derivation) = match key_id {
|
||||
Some(key_id) => keys::retrieve_existing_key(wallet_data, key_id),
|
||||
None => keys::next_available_key(wallet_data),
|
||||
};
|
||||
|
||||
// track the new output and return the stuff needed for reward
|
||||
wallet_data.add_output(OutputData {
|
||||
root_key_id: root_key_id.clone(),
|
||||
key_id: key_id.clone(),
|
||||
n_child: derivation,
|
||||
value: reward(block_fees.fees),
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: height,
|
||||
lock_height: lock_height,
|
||||
is_coinbase: true,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
});
|
||||
|
||||
(key_id, derivation)
|
||||
})?;
|
||||
|
||||
debug!(
|
||||
LOGGER,
|
||||
"receive_coinbase: built candidate output - {:?}, {}",
|
||||
key_id.clone(),
|
||||
derivation,
|
||||
);
|
||||
|
||||
let mut block_fees = block_fees.clone();
|
||||
block_fees.key_id = Some(key_id.clone());
|
||||
|
||||
debug!(LOGGER, "receive_coinbase: {:?}", block_fees);
|
||||
|
||||
let (out, kern) = reward::output(
|
||||
&wallet.keychain(),
|
||||
&key_id,
|
||||
block_fees.fees,
|
||||
block_fees.height,
|
||||
).unwrap();
|
||||
/* .context(ErrorKind::Keychain)?; */
|
||||
Ok((out, kern, block_fees))
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::ApiServer;
|
||||
use handlers::CoinbaseHandler;
|
||||
use iron::Handler;
|
||||
use libwallet::types::WalletBackend;
|
||||
use receiver::WalletReceiver;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use util::LOGGER;
|
||||
|
||||
pub fn start_rest_apis<T>(in_wallet: T, api_listen_addr: &str)
|
||||
where
|
||||
T: WalletBackend,
|
||||
CoinbaseHandler<T>: Handler,
|
||||
WalletReceiver<T>: Handler,
|
||||
{
|
||||
info!(
|
||||
LOGGER,
|
||||
"Starting the Grin wallet receiving daemon at {}...", api_listen_addr
|
||||
);
|
||||
|
||||
let wallet = Arc::new(RwLock::new(in_wallet));
|
||||
|
||||
let receive_tx_handler = WalletReceiver {
|
||||
wallet: wallet.clone(),
|
||||
};
|
||||
let coinbase_handler = CoinbaseHandler {
|
||||
wallet: wallet.clone(),
|
||||
};
|
||||
|
||||
let router = router!(
|
||||
receive_tx: post "/receive/transaction" => receive_tx_handler,
|
||||
receive_coinbase: post "/receive/coinbase" => coinbase_handler,
|
||||
);
|
||||
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_handler(router);
|
||||
match apis.start(api_listen_addr) {
|
||||
Err(e) => error!(LOGGER, "Failed to start Grin wallet listener: {}.", e),
|
||||
Ok(_) => info!(LOGGER, "Wallet listener started"),
|
||||
};
|
||||
}
|
|
@ -28,8 +28,8 @@ use core::core::hash::Hashed;
|
|||
use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel};
|
||||
use core::{consensus, global, pow};
|
||||
use wallet::file_wallet::*;
|
||||
use wallet::libwallet::internal::updater;
|
||||
use wallet::libwallet::types::*;
|
||||
use wallet::libwallet::updater;
|
||||
use wallet::libwallet::{Error, ErrorKind};
|
||||
|
||||
use util::secp::pedersen;
|
||||
|
@ -162,7 +162,7 @@ pub fn award_block_to_wallet<T: WalletBackend>(
|
|||
key_id: None,
|
||||
height: prev.height + 1,
|
||||
};
|
||||
let coinbase_tx = wallet::receiver::receive_coinbase(wallet, &fees);
|
||||
let coinbase_tx = wallet::libwallet::internal::updater::receive_coinbase(wallet, &fees);
|
||||
let (coinbase_tx, fees) = match coinbase_tx {
|
||||
Ok(t) => ((t.0, t.1), t.2),
|
||||
Err(e) => {
|
||||
|
@ -197,10 +197,14 @@ pub fn award_blocks_to_wallet<T: WalletBackend>(chain: &Chain, wallet: &mut T, n
|
|||
pub fn create_wallet(dir: &str) -> FileWallet {
|
||||
let mut wallet_config = WalletConfig::default();
|
||||
wallet_config.data_file_dir = String::from(dir);
|
||||
let wallet_seed = wallet::WalletSeed::init_file(&wallet_config).unwrap();
|
||||
let keychain = wallet_seed
|
||||
.derive_keychain("")
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
FileWallet::new(wallet_config.clone(), keychain)
|
||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config))
|
||||
wallet::WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file.");
|
||||
let mut wallet = FileWallet::new(wallet_config.clone(), "")
|
||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
|
||||
wallet.open_with_credentials().unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error initializing wallet: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
)
|
||||
});
|
||||
wallet
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use util::secp::key::{PublicKey, SecretKey};
|
|||
use util::secp::pedersen::ProofMessage;
|
||||
use util::{kernel_sig_msg, secp};
|
||||
use wallet::libtx::{aggsig, proof};
|
||||
use wallet::libwallet::sigcontext;
|
||||
use wallet::libwallet::internal::sigcontext;
|
||||
|
||||
use rand::thread_rng;
|
||||
|
||||
|
|
|
@ -29,12 +29,12 @@ 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;
|
||||
use wallet::libwallet::selection;
|
||||
use wallet::libwallet::internal::selection;
|
||||
|
||||
fn clean_output_dir(test_dir: &str) {
|
||||
let _ = fs::remove_dir_all(test_dir);
|
||||
|
@ -100,7 +100,7 @@ fn build_transaction() {
|
|||
// information to the slate
|
||||
let _ = slate
|
||||
.fill_round_1(
|
||||
&wallet1.keychain,
|
||||
wallet1.keychain.as_ref().unwrap(),
|
||||
&mut sender_context.sec_key,
|
||||
&sender_context.sec_nonce,
|
||||
0,
|
||||
|
@ -120,7 +120,7 @@ fn build_transaction() {
|
|||
|
||||
let _ = slate
|
||||
.fill_round_1(
|
||||
&wallet2.keychain,
|
||||
wallet2.keychain.as_ref().unwrap(),
|
||||
&mut recp_context.sec_key,
|
||||
&recp_context.sec_nonce,
|
||||
1,
|
||||
|
@ -132,7 +132,7 @@ fn build_transaction() {
|
|||
|
||||
let _ = slate
|
||||
.fill_round_2(
|
||||
&wallet1.keychain,
|
||||
&wallet1.keychain.as_ref().unwrap(),
|
||||
&recp_context.sec_key,
|
||||
&recp_context.sec_nonce,
|
||||
1,
|
||||
|
@ -149,7 +149,7 @@ fn build_transaction() {
|
|||
// SENDER Part 3: Sender confirmation
|
||||
let _ = slate
|
||||
.fill_round_2(
|
||||
&wallet1.keychain,
|
||||
&wallet1.keychain.as_ref().unwrap(),
|
||||
&sender_context.sec_key,
|
||||
&sender_context.sec_nonce,
|
||||
0,
|
||||
|
@ -161,7 +161,7 @@ fn build_transaction() {
|
|||
debug!(LOGGER, "{:?}", slate);
|
||||
|
||||
// Final transaction can be built by anyone at this stage
|
||||
let res = slate.finalize(&wallet1.keychain);
|
||||
let res = slate.finalize(&wallet1.keychain.as_ref().unwrap());
|
||||
|
||||
if let Err(e) = res {
|
||||
panic!("Error creating final tx: {:?}", e);
|
||||
|
|
Loading…
Reference in a new issue