[WIP] Wallet API Structure (#1133)

Wallet API Structure
This commit is contained in:
Yeastplume 2018-06-06 15:36:29 +01:00 committed by GitHub
parent 1815a6a8bf
commit 8f66016557
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 966 additions and 674 deletions

View file

@ -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();

View file

@ -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,

View file

@ -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"),
});
}
}

View file

@ -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

View file

@ -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> {

View file

@ -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, "")))
}
}
}

View file

@ -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};

View file

@ -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
View 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)
}
}

View file

@ -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)

View 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, ""))),
}
}

View file

@ -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

View 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;

View file

@ -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,

View file

@ -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,

View file

@ -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))
}

View file

@ -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};

View file

@ -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))
}

View file

@ -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,
}

View file

@ -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?)"
);
}
}

View file

@ -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))
}

View file

@ -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"),
};
}

View file

@ -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
}

View file

@ -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;

View file

@ -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);