mirror of
https://github.com/mimblewimble/grin.git
synced 2025-04-30 14:21:14 +03:00
Factor out wallet communications (#1142)
* move http calls out from libwallet internal * rustfmt * start to think about wallet communication traits * rustfmt * start of factoring out wallet client trait * rustfmt * move node_url trait fn into walletclient * rustfmt * comms factored out (with exception of wallet restore) * rustfmt * fix test * rustfmt * further test fix
This commit is contained in:
parent
fffee377dd
commit
ebee05591b
15 changed files with 498 additions and 344 deletions
|
@ -239,13 +239,7 @@ fn get_coinbase(
|
|||
return burn_reward(block_fees);
|
||||
}
|
||||
Some(wallet_listener_url) => {
|
||||
// Get the wallet coinbase
|
||||
let url = format!(
|
||||
"{}/v1/wallet/foreign/build_coinbase",
|
||||
wallet_listener_url.as_str()
|
||||
);
|
||||
|
||||
let res = wallet::libwallet::client::create_coinbase(&url, &block_fees)?;
|
||||
let res = wallet::create_coinbase(&wallet_listener_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();
|
||||
|
@ -258,7 +252,6 @@ fn get_coinbase(
|
|||
};
|
||||
|
||||
debug!(LOGGER, "get_coinbase: {:?}", block_fees);
|
||||
|
||||
return Ok((output, kernel, block_fees));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -326,29 +326,32 @@ impl LocalServerContainer {
|
|||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
let max_outputs = 500;
|
||||
|
||||
let mut wallet = FileWallet::new(config.clone(), "grin_test")
|
||||
let mut wallet = FileWallet::new(config.clone(), "")
|
||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||
wallet.keychain = Some(keychain);
|
||||
let result = wallet::libwallet::internal::tx::issue_send_tx(
|
||||
&mut wallet,
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
max_outputs,
|
||||
selection_strategy == "all",
|
||||
fluff,
|
||||
);
|
||||
match result {
|
||||
Ok(_) => println!(
|
||||
"Tx sent: {} grin to {} (strategy '{}')",
|
||||
core::core::amount_to_hr_string(amount),
|
||||
dest,
|
||||
selection_strategy,
|
||||
),
|
||||
Err(e) => {
|
||||
println!("Tx not sent to {}: {:?}", dest, e);
|
||||
}
|
||||
};
|
||||
let _ =
|
||||
wallet::controller::owner_single_use(&mut wallet, |api| {
|
||||
let result = api.issue_send_tx(
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
max_outputs,
|
||||
selection_strategy == "all",
|
||||
fluff,
|
||||
);
|
||||
match result {
|
||||
Ok(_) => println!(
|
||||
"Tx sent: {} grin to {} (strategy '{}')",
|
||||
core::core::amount_to_hr_string(amount),
|
||||
dest,
|
||||
selection_strategy,
|
||||
),
|
||||
Err(e) => {
|
||||
println!("Tx not sent to {}: {:?}", dest, e);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}).unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config));
|
||||
}
|
||||
|
||||
/// Stops the running wallet server
|
||||
|
|
222
wallet/src/client.rs
Normal file
222
wallet/src/client.rs
Normal file
|
@ -0,0 +1,222 @@
|
|||
// 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.
|
||||
|
||||
//! Client functions, implementations of the WalletClient trait
|
||||
//! specific to the FileWallet
|
||||
|
||||
use failure::ResultExt;
|
||||
use libwallet::types::*;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use futures::{Future, Stream};
|
||||
use hyper;
|
||||
use hyper::header::ContentType;
|
||||
use hyper::{Method, Request};
|
||||
use serde_json;
|
||||
use tokio_core::reactor;
|
||||
|
||||
use api;
|
||||
use error::{Error, ErrorKind};
|
||||
use libtx::slate::Slate;
|
||||
use util::secp::pedersen;
|
||||
use util::{self, LOGGER};
|
||||
|
||||
/// Call the wallet API to create a coinbase output for the given block_fees.
|
||||
/// Will retry based on default "retry forever with backoff" behavior.
|
||||
pub fn create_coinbase(dest: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let url = format!("{}/v1/wallet/foreign/build_coinbase", dest);
|
||||
match single_create_coinbase(&url, &block_fees) {
|
||||
Err(e) => {
|
||||
error!(
|
||||
LOGGER,
|
||||
"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_tx_slate(dest: &str, slate: &Slate) -> Result<Slate, Error> {
|
||||
if &dest[..4] != "http" {
|
||||
error!(
|
||||
LOGGER,
|
||||
"dest formatted as {} but send -d expected stdout or http://IP:port", dest
|
||||
);
|
||||
Err(ErrorKind::Node)?
|
||||
}
|
||||
let url = format!("{}/v1/wallet/foreign/receive_tx", dest);
|
||||
debug!(LOGGER, "Posting transaction slate to {}", url);
|
||||
|
||||
let mut core = reactor::Core::new().context(ErrorKind::Hyper)?;
|
||||
let client = hyper::Client::new(&core.handle());
|
||||
|
||||
let url_pool = url.to_owned();
|
||||
|
||||
let mut req = Request::new(
|
||||
Method::Post,
|
||||
url_pool.parse::<hyper::Uri>().context(ErrorKind::Hyper)?,
|
||||
);
|
||||
req.headers_mut().set(ContentType::json());
|
||||
let json = serde_json::to_string(&slate).context(ErrorKind::Hyper)?;
|
||||
req.set_body(json);
|
||||
|
||||
let work = client.request(req).and_then(|res| {
|
||||
res.body().concat2().and_then(move |body| {
|
||||
let slate: Slate =
|
||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(slate)
|
||||
})
|
||||
});
|
||||
let res = core.run(work).context(ErrorKind::Hyper)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Makes a single request to the wallet API to create a new coinbase output.
|
||||
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let mut core =
|
||||
reactor::Core::new().context(ErrorKind::GenericError("Could not create reactor"))?;
|
||||
let client = hyper::Client::new(&core.handle());
|
||||
|
||||
let mut req = Request::new(
|
||||
Method::Post,
|
||||
url.parse::<hyper::Uri>().context(ErrorKind::Uri)?,
|
||||
);
|
||||
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)
|
||||
})
|
||||
});
|
||||
|
||||
let res = core.run(work)
|
||||
.context(ErrorKind::GenericError("Could not run core"))?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Posts a tranaction to a grin node
|
||||
pub fn post_tx(dest: &str, tx: &TxWrapper, fluff: bool) -> Result<(), Error> {
|
||||
let url;
|
||||
if fluff {
|
||||
url = format!("{}/v1/pool/push?fluff", dest);
|
||||
} else {
|
||||
url = format!("{}/v1/pool/push", dest);
|
||||
}
|
||||
let res = api::client::post(url.as_str(), tx).context(ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Return the chain tip from a given node
|
||||
pub fn get_chain_height(addr: &str) -> Result<u64, Error> {
|
||||
let url = format!("{}/v1/chain", addr);
|
||||
let res = api::client::get::<api::Tip>(url.as_str()).context(ErrorKind::Node)?;
|
||||
Ok(res.height)
|
||||
}
|
||||
|
||||
/// Retrieve outputs from node
|
||||
pub fn get_outputs_from_node(
|
||||
addr: &str,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<HashMap<pedersen::Commitment, String>, Error> {
|
||||
// build the necessary query params -
|
||||
// ?id=xxx&id=yyy&id=zzz
|
||||
let query_params: Vec<String> = wallet_outputs
|
||||
.iter()
|
||||
.map(|commit| format!("id={}", util::to_hex(commit.as_ref().to_vec())))
|
||||
.collect();
|
||||
|
||||
// build a map of api outputs by commit so we can look them up efficiently
|
||||
let mut api_outputs: HashMap<pedersen::Commitment, String> = HashMap::new();
|
||||
|
||||
for query_chunk in query_params.chunks(1000) {
|
||||
let url = format!("{}/v1/chain/outputs/byids?{}", addr, query_chunk.join("&"),);
|
||||
|
||||
match api::client::get::<Vec<api::Output>>(url.as_str()) {
|
||||
Ok(outputs) => for out in outputs {
|
||||
api_outputs.insert(out.commit.commit(), util::to_hex(out.commit.to_vec()));
|
||||
},
|
||||
Err(e) => {
|
||||
// if we got anything other than 200 back from server, don't attempt to refresh
|
||||
// the wallet data after
|
||||
return Err(e).context(ErrorKind::Node)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(api_outputs)
|
||||
}
|
||||
|
||||
/// Get any missing block hashes from node
|
||||
pub fn get_missing_block_hashes_from_node(
|
||||
addr: &str,
|
||||
height: u64,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<
|
||||
(
|
||||
HashMap<pedersen::Commitment, (u64, BlockIdentifier)>,
|
||||
HashMap<pedersen::Commitment, MerkleProofWrapper>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let id_params: Vec<String> = wallet_outputs
|
||||
.iter()
|
||||
.map(|commit| format!("id={}", util::to_hex(commit.as_ref().to_vec())))
|
||||
.collect();
|
||||
|
||||
let height_params = [format!("start_height={}&end_height={}", 0, height)];
|
||||
let mut api_blocks: HashMap<pedersen::Commitment, (u64, BlockIdentifier)> = HashMap::new();
|
||||
let mut api_merkle_proofs: HashMap<pedersen::Commitment, MerkleProofWrapper> = HashMap::new();
|
||||
|
||||
// Split up into separate requests, to avoid hitting http limits
|
||||
for mut query_chunk in id_params.chunks(1000) {
|
||||
let url = format!(
|
||||
"{}/v1/chain/outputs/byheight?{}",
|
||||
addr,
|
||||
[&height_params, query_chunk].concat().join("&"),
|
||||
);
|
||||
|
||||
match api::client::get::<Vec<api::BlockOutputs>>(url.as_str()) {
|
||||
Ok(blocks) => for block in blocks {
|
||||
for out in block.outputs {
|
||||
api_blocks.insert(
|
||||
out.commit,
|
||||
(
|
||||
block.header.height,
|
||||
BlockIdentifier::from_hex(&block.header.hash).unwrap(),
|
||||
),
|
||||
);
|
||||
if let Some(merkle_proof) = out.merkle_proof {
|
||||
let wrapper = MerkleProofWrapper(merkle_proof);
|
||||
api_merkle_proofs.insert(out.commit, wrapper);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// if we got anything other than 200 back from server, bye
|
||||
return Err(e).context(ErrorKind::Node)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((api_blocks, api_merkle_proofs))
|
||||
}
|
|
@ -31,8 +31,12 @@ use failure::ResultExt;
|
|||
use keychain::{self, Keychain};
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
use util::secp::pedersen;
|
||||
|
||||
use error::{Error, ErrorKind};
|
||||
|
||||
use client;
|
||||
use libtx::slate::Slate;
|
||||
use libwallet;
|
||||
use libwallet::types::*;
|
||||
|
||||
|
@ -205,11 +209,6 @@ impl WalletBackend for FileWallet {
|
|||
self.keychain.as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Return URL for check node
|
||||
fn node_url(&self) -> &str {
|
||||
&self.config.check_node_api_http_addr
|
||||
}
|
||||
|
||||
/// Return the outputs directly
|
||||
fn outputs(&mut self) -> &mut HashMap<String, OutputData> {
|
||||
&mut self.outputs
|
||||
|
@ -399,6 +398,81 @@ impl WalletBackend for FileWallet {
|
|||
}
|
||||
}
|
||||
|
||||
impl WalletClient for FileWallet {
|
||||
/// Return URL for check node
|
||||
fn node_url(&self) -> &str {
|
||||
&self.config.check_node_api_http_addr
|
||||
}
|
||||
|
||||
/// Call the wallet API to create a coinbase transaction
|
||||
fn create_coinbase(
|
||||
&self,
|
||||
dest: &str,
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<CbData, libwallet::Error> {
|
||||
let res =
|
||||
client::create_coinbase(dest, block_fees).context(libwallet::ErrorKind::WalletComms)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Send a transaction slate to another listening wallet and return result
|
||||
fn send_tx_slate(&self, dest: &str, slate: &Slate) -> Result<Slate, libwallet::Error> {
|
||||
let res = client::send_tx_slate(dest, slate).context(libwallet::ErrorKind::WalletComms)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Posts a tranaction to a grin node
|
||||
fn post_tx(&self, dest: &str, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> {
|
||||
let res = client::post_tx(dest, tx, fluff).context(libwallet::ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// retrieves the current tip from the specified grin node
|
||||
fn get_chain_height(&self, addr: &str) -> Result<u64, libwallet::Error> {
|
||||
let res = client::get_chain_height(addr).context(libwallet::ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// retrieve a list of outputs from the specified grin node
|
||||
/// need "by_height" and "by_id" variants
|
||||
fn get_outputs_from_node(
|
||||
&self,
|
||||
addr: &str,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<HashMap<pedersen::Commitment, String>, libwallet::Error> {
|
||||
let res = client::get_outputs_from_node(addr, wallet_outputs)
|
||||
.context(libwallet::ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get any missing block hashes from node
|
||||
fn get_missing_block_hashes_from_node(
|
||||
&self,
|
||||
addr: &str,
|
||||
height: u64,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<
|
||||
(
|
||||
HashMap<pedersen::Commitment, (u64, BlockIdentifier)>,
|
||||
HashMap<pedersen::Commitment, MerkleProofWrapper>,
|
||||
),
|
||||
libwallet::Error,
|
||||
> {
|
||||
let res = client::get_missing_block_hashes_from_node(addr, height, wallet_outputs)
|
||||
.context(libwallet::ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// retrieve merkle proof for a commit from a node
|
||||
fn get_merkle_proof_for_commit(
|
||||
&self,
|
||||
addr: &str,
|
||||
commit: &str,
|
||||
) -> Result<MerkleProofWrapper, libwallet::Error> {
|
||||
Err(libwallet::ErrorKind::GenericError("Not Implemented"))?
|
||||
}
|
||||
}
|
||||
|
||||
impl FileWallet {
|
||||
/// Create a new FileWallet instance
|
||||
pub fn new(config: WalletConfig, passphrase: &str) -> Result<Self, Error> {
|
||||
|
|
|
@ -46,12 +46,14 @@ extern crate grin_core as core;
|
|||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_util as util;
|
||||
|
||||
mod client;
|
||||
pub mod display;
|
||||
mod error;
|
||||
pub mod file_wallet;
|
||||
pub mod libtx;
|
||||
pub mod libwallet;
|
||||
|
||||
pub use client::create_coinbase;
|
||||
pub use error::{Error, ErrorKind};
|
||||
pub use file_wallet::{FileWallet, WalletConfig, WalletSeed};
|
||||
pub use libwallet::controller;
|
||||
|
|
|
@ -20,13 +20,17 @@
|
|||
use libtx::slate::Slate;
|
||||
use libwallet::Error;
|
||||
use libwallet::internal::{tx, updater};
|
||||
use libwallet::types::{BlockFees, CbData, OutputData, WalletBackend, WalletInfo};
|
||||
use libwallet::types::{BlockFees, CbData, OutputData, TxWrapper, WalletBackend, WalletClient,
|
||||
WalletInfo};
|
||||
|
||||
use core::ser;
|
||||
use util::{self, LOGGER};
|
||||
|
||||
/// 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,
|
||||
W: 'a + WalletBackend + WalletClient,
|
||||
{
|
||||
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
||||
/// perhaps)
|
||||
|
@ -35,7 +39,7 @@ where
|
|||
|
||||
impl<'a, W> APIOwner<'a, W>
|
||||
where
|
||||
W: 'a + WalletBackend,
|
||||
W: 'a + WalletBackend + WalletClient,
|
||||
{
|
||||
/// Create new API instance
|
||||
pub fn new(wallet_in: &'a mut W) -> APIOwner<'a, W> {
|
||||
|
@ -62,7 +66,6 @@ where
|
|||
}
|
||||
|
||||
/// 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,
|
||||
|
@ -72,15 +75,35 @@ where
|
|||
selection_strategy_is_use_all: bool,
|
||||
fluff: bool,
|
||||
) -> Result<(), Error> {
|
||||
tx::issue_send_tx(
|
||||
let (slate, context, lock_fn) = tx::create_send_tx(
|
||||
self.wallet,
|
||||
amount,
|
||||
minimum_confirmations,
|
||||
dest,
|
||||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
fluff,
|
||||
)
|
||||
)?;
|
||||
|
||||
let mut slate = match self.wallet.send_tx_slate(dest, &slate) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!(
|
||||
LOGGER,
|
||||
"Communication with receiver failed on SenderInitiation send. Aborting transaction"
|
||||
);
|
||||
return Err(e)?;
|
||||
}
|
||||
};
|
||||
|
||||
tx::complete_tx(self.wallet, &mut slate, &context)?;
|
||||
|
||||
// All good here, so let's post it
|
||||
let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap());
|
||||
self.wallet
|
||||
.post_tx(self.wallet.node_url(), &TxWrapper { tx_hex: tx_hex }, fluff)?;
|
||||
|
||||
// All good here, lock our inputs
|
||||
lock_fn(self.wallet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Issue a burn TX
|
||||
|
@ -90,7 +113,11 @@ where
|
|||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
) -> Result<(), Error> {
|
||||
tx::issue_burn_tx(self.wallet, amount, minimum_confirmations, max_outputs)
|
||||
let tx_burn = tx::issue_burn_tx(self.wallet, amount, minimum_confirmations, max_outputs)?;
|
||||
let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap());
|
||||
self.wallet
|
||||
.post_tx(self.wallet.node_url(), &TxWrapper { tx_hex: tx_hex }, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt to restore contents of wallet
|
||||
|
@ -100,8 +127,8 @@ where
|
|||
|
||||
/// 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)),
|
||||
match self.wallet.get_chain_height(self.wallet.node_url()) {
|
||||
Ok(height) => Ok((height, true)),
|
||||
Err(_) => {
|
||||
let outputs = self.retrieve_outputs(true)?;
|
||||
let height = match outputs.1.iter().map(|out| out.height).max() {
|
||||
|
@ -126,7 +153,7 @@ where
|
|||
/// with other parties
|
||||
pub struct APIForeign<'a, W>
|
||||
where
|
||||
W: 'a + WalletBackend,
|
||||
W: 'a + WalletBackend + WalletClient,
|
||||
{
|
||||
/// Wallet, contains its keychain (TODO: Split these up into 2 traits
|
||||
/// perhaps)
|
||||
|
@ -135,7 +162,7 @@ where
|
|||
|
||||
impl<'a, W> APIForeign<'a, W>
|
||||
where
|
||||
W: 'a + WalletBackend,
|
||||
W: 'a + WalletBackend + WalletClient,
|
||||
{
|
||||
/// Create new API instance
|
||||
pub fn new(wallet_in: &'a mut W) -> APIForeign<'a, W> {
|
||||
|
|
|
@ -1,106 +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.
|
||||
|
||||
//! Client functions: TODO: doesn't really belong here or needs to be
|
||||
//! traited out
|
||||
|
||||
use failure::ResultExt;
|
||||
use futures::{Future, Stream};
|
||||
use hyper;
|
||||
use hyper::header::ContentType;
|
||||
use hyper::{Method, Request};
|
||||
use libtx::slate::Slate;
|
||||
use serde_json;
|
||||
use tokio_core::reactor;
|
||||
|
||||
use error::{Error, ErrorKind};
|
||||
use libwallet::types::*;
|
||||
use std::io;
|
||||
use util::LOGGER;
|
||||
|
||||
/// Call the wallet API to create a coinbase output for the given block_fees.
|
||||
/// Will retry based on default "retry forever with backoff" behavior.
|
||||
pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
match single_create_coinbase(&url, &block_fees) {
|
||||
Err(e) => {
|
||||
error!(
|
||||
LOGGER,
|
||||
"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());
|
||||
|
||||
// In case we want to do an express send
|
||||
let mut url_pool = url.to_owned();
|
||||
if fluff {
|
||||
url_pool = format!("{}{}", url, "?fluff");
|
||||
}
|
||||
|
||||
let mut req = Request::new(
|
||||
Method::Post,
|
||||
url_pool.parse::<hyper::Uri>().context(ErrorKind::Hyper)?,
|
||||
);
|
||||
req.headers_mut().set(ContentType::json());
|
||||
let json = serde_json::to_string(&slate).context(ErrorKind::Hyper)?;
|
||||
req.set_body(json);
|
||||
|
||||
let work = client.request(req).and_then(|res| {
|
||||
res.body().concat2().and_then(move |body| {
|
||||
let slate: Slate =
|
||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(slate)
|
||||
})
|
||||
});
|
||||
let res = core.run(work).context(ErrorKind::Hyper)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Makes a single request to the wallet API to create a new coinbase output.
|
||||
fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let mut core =
|
||||
reactor::Core::new().context(ErrorKind::GenericError("Could not create reactor"))?;
|
||||
let client = hyper::Client::new(&core.handle());
|
||||
|
||||
let mut req = Request::new(
|
||||
Method::Post,
|
||||
url.parse::<hyper::Uri>().context(ErrorKind::Uri)?,
|
||||
);
|
||||
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)
|
||||
})
|
||||
});
|
||||
|
||||
let res = core.run(work)
|
||||
.context(ErrorKind::GenericError("Could not run core"))?;
|
||||
Ok(res)
|
||||
}
|
|
@ -29,7 +29,7 @@ use failure::Fail;
|
|||
|
||||
use libtx::slate::Slate;
|
||||
use libwallet::api::{APIForeign, APIOwner};
|
||||
use libwallet::types::{BlockFees, CbData, OutputData, WalletBackend, WalletInfo};
|
||||
use libwallet::types::{BlockFees, CbData, OutputData, WalletBackend, WalletClient, WalletInfo};
|
||||
use libwallet::{Error, ErrorKind};
|
||||
|
||||
use util::LOGGER;
|
||||
|
@ -38,7 +38,7 @@ use util::LOGGER;
|
|||
/// 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,
|
||||
T: WalletBackend + WalletClient,
|
||||
F: FnOnce(&mut APIOwner<T>) -> Result<(), Error>,
|
||||
{
|
||||
wallet.open_with_credentials()?;
|
||||
|
@ -51,7 +51,7 @@ where
|
|||
/// 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,
|
||||
T: WalletBackend + WalletClient,
|
||||
F: FnOnce(&mut APIForeign<T>) -> Result<(), Error>,
|
||||
{
|
||||
wallet.open_with_credentials()?;
|
||||
|
@ -91,7 +91,7 @@ where
|
|||
/// port and wrapping the calls
|
||||
pub fn foreign_listener<T>(wallet: T, addr: &str) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
ForeignAPIHandler<T>: Handler,
|
||||
{
|
||||
let api_handler = ForeignAPIHandler {
|
||||
|
@ -125,7 +125,7 @@ where
|
|||
|
||||
impl<T> OwnerAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
fn retrieve_outputs(
|
||||
&self,
|
||||
|
@ -177,7 +177,7 @@ where
|
|||
|
||||
impl<T> Handler for OwnerAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend + Send + Sync + 'static,
|
||||
T: WalletBackend + WalletClient + Send + Sync + 'static,
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
// every request should open with stored credentials,
|
||||
|
@ -201,7 +201,7 @@ where
|
|||
|
||||
pub struct ForeignAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
/// Wallet instance
|
||||
pub wallet: Arc<Mutex<T>>,
|
||||
|
@ -209,7 +209,7 @@ where
|
|||
|
||||
impl<T> ForeignAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
fn build_coinbase(&self, req: &mut Request, api: &mut APIForeign<T>) -> Result<CbData, Error> {
|
||||
let struct_body = req.get::<bodyparser::Struct<BlockFees>>();
|
||||
|
@ -258,7 +258,7 @@ where
|
|||
|
||||
impl<T> Handler for ForeignAPIHandler<T>
|
||||
where
|
||||
T: WalletBackend + Send + Sync + 'static,
|
||||
T: WalletBackend + WalletClient + Send + Sync + 'static,
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
// every request should open with stored credentials,
|
||||
|
|
|
@ -93,6 +93,10 @@ pub enum ErrorKind {
|
|||
#[fail(display = "Node API error")]
|
||||
Node,
|
||||
|
||||
/// Error contacting wallet API
|
||||
#[fail(display = "Wallet communication error")]
|
||||
WalletComms,
|
||||
|
||||
/// Error originating from hyper.
|
||||
#[fail(display = "Hyper error")]
|
||||
Hyper,
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
//! Functions to restore a wallet's outputs from just the master seed
|
||||
|
||||
/// TODO: Remove api
|
||||
use api;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use core::core::transaction::ProofMessageElements;
|
||||
|
@ -26,24 +27,6 @@ use util;
|
|||
use util::LOGGER;
|
||||
use util::secp::pedersen;
|
||||
|
||||
fn get_chain_height(node_addr: &str) -> Result<u64, Error> {
|
||||
let url = format!("{}/v1/chain", node_addr);
|
||||
|
||||
match api::client::get::<api::Tip>(url.as_str()) {
|
||||
Ok(tip) => Ok(tip.height),
|
||||
Err(e) => {
|
||||
// if we got anything other than 200 back from server, bye
|
||||
error!(
|
||||
LOGGER,
|
||||
"get_chain_height: Restore failed... unable to contact API {}. Error: {}",
|
||||
node_addr,
|
||||
e
|
||||
);
|
||||
Err(e.context(ErrorKind::Node).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_merkle_proof_for_commit(node_addr: &str, commit: &str) -> Result<MerkleProofWrapper, Error> {
|
||||
let url = format!("{}/v1/txhashset/merkleproof?id={}", node_addr, commit);
|
||||
|
||||
|
@ -70,7 +53,7 @@ fn coinbase_status(output: &api::OutputPrintable) -> bool {
|
|||
|
||||
fn outputs_batch<T>(wallet: &T, start_height: u64, max: u64) -> Result<api::OutputListing, Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
let query_param = format!("start_index={}&max={}", start_height, max);
|
||||
|
||||
|
@ -92,7 +75,7 @@ where
|
|||
}
|
||||
|
||||
// TODO - wrap the many return values in a struct
|
||||
fn find_outputs_with_key<T: WalletBackend>(
|
||||
fn find_outputs_with_key<T: WalletBackend + WalletClient>(
|
||||
wallet: &mut T,
|
||||
outputs: Vec<api::OutputPrintable>,
|
||||
found_key_index: &mut Vec<u32>,
|
||||
|
@ -120,7 +103,7 @@ fn find_outputs_with_key<T: WalletBackend>(
|
|||
let max_derivations = 1_000_000;
|
||||
|
||||
info!(LOGGER, "Scanning {} outputs", outputs.len(),);
|
||||
let current_chain_height = get_chain_height(wallet.node_url()).unwrap();
|
||||
let current_chain_height = wallet.get_chain_height(wallet.node_url()).unwrap();
|
||||
|
||||
// skey doesn't matter in this case
|
||||
let skey = wallet.keychain().derive_key_id(1).unwrap();
|
||||
|
@ -242,7 +225,7 @@ fn find_outputs_with_key<T: WalletBackend>(
|
|||
}
|
||||
|
||||
/// Restore a wallet
|
||||
pub fn restore<T: WalletBackend>(wallet: &mut T) -> Result<(), Error> {
|
||||
pub fn restore<T: WalletBackend + WalletClient>(wallet: &mut T) -> Result<(), Error> {
|
||||
// Don't proceed if wallet.dat has anything in it
|
||||
let is_empty = wallet
|
||||
.read_wallet(|wallet_data| Ok(wallet_data.outputs().len() == 0))
|
||||
|
|
|
@ -12,19 +12,15 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Transaction buinding functions
|
||||
//! Transaction building functions
|
||||
|
||||
use api;
|
||||
use core::ser;
|
||||
use failure::ResultExt;
|
||||
use core::core::Transaction;
|
||||
use keychain::{Identifier, Keychain};
|
||||
use libtx::slate::Slate;
|
||||
use libtx::{build, tx_fee};
|
||||
use libwallet::client;
|
||||
use libwallet::internal::{selection, updater};
|
||||
use libwallet::types::{TxWrapper, WalletBackend};
|
||||
use libwallet::internal::{selection, sigcontext, updater};
|
||||
use libwallet::types::{WalletBackend, WalletClient};
|
||||
use libwallet::{Error, ErrorKind};
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
|
||||
/// Receive a tranaction, modifying the slate accordingly (which can then be
|
||||
|
@ -53,32 +49,22 @@ pub fn receive_tx<T: WalletBackend>(wallet: &mut T, slate: &mut Slate) -> Result
|
|||
|
||||
/// 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>(
|
||||
pub fn create_send_tx<T: WalletBackend + WalletClient>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
dest: &str,
|
||||
max_outputs: usize,
|
||||
selection_strategy_is_use_all: bool,
|
||||
fluff: bool,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: Stdout option, probably in a separate implementation
|
||||
if &dest[..4] != "http" {
|
||||
panic!(
|
||||
"dest formatted as {} but send -d expected stdout or http://IP:port",
|
||||
dest
|
||||
);
|
||||
}
|
||||
|
||||
updater::refresh_outputs(wallet)?;
|
||||
|
||||
) -> Result<
|
||||
(
|
||||
Slate,
|
||||
sigcontext::Context,
|
||||
impl FnOnce(&mut T) -> Result<(), Error>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
// Get lock height
|
||||
let chain_tip = updater::get_tip_from_node(wallet.node_url())?;
|
||||
let current_height = chain_tip.height;
|
||||
let current_height = wallet.get_chain_height(wallet.node_url())?;
|
||||
// ensure outputs we're selecting are up to date
|
||||
updater::refresh_outputs(wallet)?;
|
||||
|
||||
|
@ -112,50 +98,34 @@ pub fn issue_send_tx<T: WalletBackend>(
|
|||
0,
|
||||
)?;
|
||||
|
||||
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).context(ErrorKind::Node) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!(
|
||||
LOGGER,
|
||||
"Communication with receiver failed on SenderInitiation send. Aborting transaction"
|
||||
);
|
||||
return Err(e)?;
|
||||
}
|
||||
};
|
||||
Ok((slate, context, sender_lock_fn))
|
||||
}
|
||||
|
||||
/// Complete a transaction as the sender
|
||||
pub fn complete_tx<T: WalletBackend>(
|
||||
wallet: &mut T,
|
||||
slate: &mut Slate,
|
||||
context: &sigcontext::Context,
|
||||
) -> Result<(), Error> {
|
||||
let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 0)?;
|
||||
|
||||
// Final transaction can be built by anyone at this stage
|
||||
slate.finalize(wallet.keychain())?;
|
||||
|
||||
// So let's post it
|
||||
let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap());
|
||||
let url;
|
||||
if fluff {
|
||||
url = format!("{}/v1/pool/push?fluff", wallet.node_url(),);
|
||||
} else {
|
||||
url = format!("{}/v1/pool/push", wallet.node_url());
|
||||
let res = slate.finalize(wallet.keychain());
|
||||
if let Err(e) = res {
|
||||
Err(ErrorKind::LibTX(e.kind()))?
|
||||
}
|
||||
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
|
||||
|
||||
// All good so, lock our inputs
|
||||
sender_lock_fn(wallet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Issue a burn tx
|
||||
pub fn issue_burn_tx<T: WalletBackend>(
|
||||
pub fn issue_burn_tx<T: WalletBackend + WalletClient>(
|
||||
wallet: &mut T,
|
||||
amount: u64,
|
||||
minimum_confirmations: u64,
|
||||
max_outputs: usize,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<Transaction, Error> {
|
||||
let keychain = &Keychain::burn_enabled(wallet.keychain(), &Identifier::zero());
|
||||
|
||||
let chain_tip = updater::get_tip_from_node(wallet.node_url())?;
|
||||
let current_height = chain_tip.height;
|
||||
let current_height = wallet.get_chain_height(wallet.node_url())?;
|
||||
|
||||
let _ = updater::refresh_outputs(wallet);
|
||||
|
||||
|
@ -184,12 +154,7 @@ pub fn issue_burn_tx<T: WalletBackend>(
|
|||
// finalize the burn transaction and send
|
||||
let tx_burn = build::transaction(parts, &keychain)?;
|
||||
tx_burn.validate()?;
|
||||
|
||||
let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap());
|
||||
let url = format!("{}/v1/pool/push", wallet.node_url());
|
||||
let _: () =
|
||||
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?;
|
||||
Ok(())
|
||||
Ok(tx_burn)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -19,7 +19,6 @@ use failure::ResultExt;
|
|||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use api;
|
||||
use core::consensus::reward;
|
||||
use core::core::{Output, TxKernel};
|
||||
use core::global;
|
||||
|
@ -69,24 +68,26 @@ pub fn retrieve_outputs<T: WalletBackend>(
|
|||
/// from a node
|
||||
pub fn refresh_outputs<T>(wallet: &mut T) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
let tip = get_tip_from_node(&wallet.node_url())?;
|
||||
refresh_output_state(wallet, &tip)?;
|
||||
refresh_missing_block_hashes(wallet, &tip)?;
|
||||
let height = wallet.get_chain_height(wallet.node_url())?;
|
||||
refresh_output_state(wallet, height)?;
|
||||
refresh_missing_block_hashes(wallet, height)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO - this might be slow if we have really old outputs that have never been
|
||||
// refreshed
|
||||
fn refresh_missing_block_hashes<T>(wallet: &mut T, tip: &api::Tip) -> Result<(), Error>
|
||||
fn refresh_missing_block_hashes<T>(wallet: &mut T, height: u64) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
// build a local map of wallet outputs keyed by commit
|
||||
// and a list of outputs we want to query the node for
|
||||
let wallet_outputs = map_wallet_outputs_missing_block(wallet)?;
|
||||
|
||||
let wallet_output_keys = wallet_outputs.keys().map(|commit| commit.clone()).collect();
|
||||
|
||||
// nothing to do so return (otherwise we hit the api with a monster query...)
|
||||
if wallet_outputs.is_empty() {
|
||||
return Ok(());
|
||||
|
@ -98,40 +99,8 @@ where
|
|||
wallet_outputs.len(),
|
||||
);
|
||||
|
||||
let id_params: Vec<String> = wallet_outputs
|
||||
.keys()
|
||||
.map(|commit| format!("id={}", util::to_hex(commit.as_ref().to_vec())))
|
||||
.collect();
|
||||
|
||||
let height_params = [format!("start_height={}&end_height={}", 0, tip.height)];
|
||||
|
||||
let mut api_blocks: HashMap<pedersen::Commitment, api::BlockHeaderInfo> = HashMap::new();
|
||||
let mut api_merkle_proofs: HashMap<pedersen::Commitment, MerkleProofWrapper> = HashMap::new();
|
||||
|
||||
// Split up into separate requests, to avoid hitting http limits
|
||||
for mut query_chunk in id_params.chunks(1000) {
|
||||
let url = format!(
|
||||
"{}/v1/chain/outputs/byheight?{}",
|
||||
wallet.node_url(),
|
||||
[&height_params, query_chunk].concat().join("&"),
|
||||
);
|
||||
|
||||
match api::client::get::<Vec<api::BlockOutputs>>(url.as_str()) {
|
||||
Ok(blocks) => for block in blocks {
|
||||
for out in block.outputs {
|
||||
api_blocks.insert(out.commit, block.header.clone());
|
||||
if let Some(merkle_proof) = out.merkle_proof {
|
||||
let wrapper = MerkleProofWrapper(merkle_proof);
|
||||
api_merkle_proofs.insert(out.commit, wrapper);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// if we got anything other than 200 back from server, bye
|
||||
return Err(e).context(ErrorKind::Node)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let (api_blocks, api_merkle_proofs) =
|
||||
wallet.get_missing_block_hashes_from_node(wallet.node_url(), height, wallet_output_keys)?;
|
||||
|
||||
// now for each commit, find the output in the wallet and
|
||||
// the corresponding api output (if it exists)
|
||||
|
@ -143,8 +112,8 @@ where
|
|||
if let Entry::Occupied(mut output) = wallet_data.outputs().entry(id.to_hex()) {
|
||||
if let Some(b) = api_blocks.get(&commit) {
|
||||
let output = output.get_mut();
|
||||
output.block = Some(BlockIdentifier::from_hex(&b.hash).unwrap());
|
||||
output.height = b.height;
|
||||
output.height = b.0;
|
||||
output.block = Some(b.1.clone());
|
||||
if let Some(merkle_proof) = api_merkle_proofs.get(&commit) {
|
||||
output.merkle_proof = Some(merkle_proof.clone());
|
||||
}
|
||||
|
@ -206,7 +175,7 @@ where
|
|||
pub fn apply_api_outputs<T>(
|
||||
wallet: &mut T,
|
||||
wallet_outputs: &HashMap<pedersen::Commitment, Identifier>,
|
||||
api_outputs: &HashMap<pedersen::Commitment, api::Output>,
|
||||
api_outputs: &HashMap<pedersen::Commitment, String>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
|
@ -229,9 +198,9 @@ where
|
|||
|
||||
/// Builds a single api query to retrieve the latest output data from the node.
|
||||
/// So we can refresh the local wallet outputs.
|
||||
fn refresh_output_state<T>(wallet: &mut T, tip: &api::Tip) -> Result<(), Error>
|
||||
fn refresh_output_state<T>(wallet: &mut T, height: u64) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
debug!(LOGGER, "Refreshing wallet outputs");
|
||||
|
||||
|
@ -239,73 +208,41 @@ where
|
|||
// and a list of outputs we want to query the node for
|
||||
let wallet_outputs = map_wallet_outputs(wallet)?;
|
||||
|
||||
// build the necessary query params -
|
||||
// ?id=xxx&id=yyy&id=zzz
|
||||
let query_params: Vec<String> = wallet_outputs
|
||||
.keys()
|
||||
.map(|commit| format!("id={}", util::to_hex(commit.as_ref().to_vec())))
|
||||
.collect();
|
||||
|
||||
// build a map of api outputs by commit so we can look them up efficiently
|
||||
let mut api_outputs: HashMap<pedersen::Commitment, api::Output> = HashMap::new();
|
||||
|
||||
for query_chunk in query_params.chunks(1000) {
|
||||
let url = format!(
|
||||
"{}/v1/chain/outputs/byids?{}",
|
||||
wallet.node_url(),
|
||||
query_chunk.join("&"),
|
||||
);
|
||||
|
||||
match api::client::get::<Vec<api::Output>>(url.as_str()) {
|
||||
Ok(outputs) => for out in outputs {
|
||||
api_outputs.insert(out.commit.commit(), out);
|
||||
},
|
||||
Err(e) => {
|
||||
// if we got anything other than 200 back from server, don't attempt to refresh
|
||||
// the wallet data after
|
||||
return Err(e).context(ErrorKind::Node)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let wallet_output_keys = wallet_outputs.keys().map(|commit| commit.clone()).collect();
|
||||
|
||||
let api_outputs = wallet.get_outputs_from_node(wallet.node_url(), wallet_output_keys)?;
|
||||
apply_api_outputs(wallet, &wallet_outputs, &api_outputs)?;
|
||||
clean_old_unconfirmed(wallet, tip)?;
|
||||
clean_old_unconfirmed(wallet, height)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clean_old_unconfirmed<T>(wallet: &mut T, tip: &api::Tip) -> Result<(), Error>
|
||||
fn clean_old_unconfirmed<T>(wallet: &mut T, height: u64) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
{
|
||||
if tip.height < 500 {
|
||||
if height < 500 {
|
||||
return Ok(());
|
||||
}
|
||||
wallet.with_wallet(|wallet_data| {
|
||||
wallet_data.outputs().retain(|_, ref mut out| {
|
||||
!(out.status == OutputStatus::Unconfirmed && out.height > 0
|
||||
&& out.height < tip.height - 500)
|
||||
&& out.height < height - 500)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the chain tip from a given node
|
||||
pub fn get_tip_from_node(addr: &str) -> Result<api::Tip, Error> {
|
||||
let url = format!("{}/v1/chain", addr);
|
||||
api::client::get::<api::Tip>(url.as_str())
|
||||
.context(ErrorKind::Node)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Retrieve summar info about the wallet
|
||||
/// Retrieve summary info about the wallet
|
||||
pub fn retrieve_info<T>(wallet: &mut T) -> Result<WalletInfo, Error>
|
||||
where
|
||||
T: WalletBackend,
|
||||
T: WalletBackend + WalletClient,
|
||||
{
|
||||
let result = refresh_outputs(wallet);
|
||||
|
||||
let height_res = wallet.get_chain_height(&wallet.node_url());
|
||||
|
||||
let ret_val = wallet.read_wallet(|wallet_data| {
|
||||
let (current_height, from) = match get_tip_from_node(&wallet_data.node_url()) {
|
||||
Ok(tip) => (tip.height, "from server node"),
|
||||
let (current_height, from) = match height_res {
|
||||
Ok(height) => (height, "from server node"),
|
||||
Err(_) => match wallet_data.outputs().values().map(|out| out.height).max() {
|
||||
Some(height) => (height, "from wallet"),
|
||||
None => (0, "node/wallet unavailable"),
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#![warn(missing_docs)]
|
||||
|
||||
pub mod api;
|
||||
pub mod client;
|
||||
pub mod controller;
|
||||
mod error;
|
||||
pub mod internal;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
//! Types and traits that should be provided by a wallet
|
||||
//! implementation
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
|
@ -26,8 +27,11 @@ use core::core::pmmr::MerkleProof;
|
|||
|
||||
use keychain::{Identifier, Keychain};
|
||||
|
||||
use libtx::slate::Slate;
|
||||
use libwallet::error::{Error, ErrorKind};
|
||||
|
||||
use util::secp::pedersen;
|
||||
|
||||
/// TODO:
|
||||
/// Wallets should implement this backend for their storage. All functions
|
||||
/// here expect that the wallet instance has instantiated itself or stored
|
||||
|
@ -42,9 +46,6 @@ pub trait WalletBackend {
|
|||
/// Return the keychain being used
|
||||
fn keychain(&mut self) -> &mut Keychain;
|
||||
|
||||
/// Return the URL of the check node
|
||||
fn node_url(&self) -> &str;
|
||||
|
||||
/// Return the outputs directly
|
||||
fn outputs(&mut self) -> &mut HashMap<String, OutputData>;
|
||||
|
||||
|
@ -90,6 +91,55 @@ pub trait WalletBackend {
|
|||
fn restore(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Encapsulate all communication functions. No functions within libwallet
|
||||
/// should care about communication details
|
||||
pub trait WalletClient {
|
||||
/// Return the URL of the check node
|
||||
fn node_url(&self) -> &str;
|
||||
|
||||
/// Call the wallet API to create a coinbase transaction
|
||||
fn create_coinbase(&self, dest: &str, block_fees: &BlockFees) -> Result<CbData, Error>;
|
||||
|
||||
/// Send a transaction slate to another listening wallet and return result
|
||||
/// TODO: Probably need a slate wrapper type
|
||||
fn send_tx_slate(&self, dest: &str, slate: &Slate) -> Result<Slate, Error>;
|
||||
|
||||
/// Posts a tranaction to a grin node
|
||||
fn post_tx(&self, dest: &str, tx: &TxWrapper, fluff: bool) -> Result<(), Error>;
|
||||
|
||||
/// retrieves the current tip from the specified grin node
|
||||
fn get_chain_height(&self, addr: &str) -> Result<u64, Error>;
|
||||
|
||||
/// retrieve a list of outputs from the specified grin node
|
||||
/// need "by_height" and "by_id" variants
|
||||
fn get_outputs_from_node(
|
||||
&self,
|
||||
addr: &str,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<HashMap<pedersen::Commitment, String>, Error>;
|
||||
|
||||
/// Get any missing block hashes from node
|
||||
fn get_missing_block_hashes_from_node(
|
||||
&self,
|
||||
addr: &str,
|
||||
height: u64,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<
|
||||
(
|
||||
HashMap<pedersen::Commitment, (u64, BlockIdentifier)>,
|
||||
HashMap<pedersen::Commitment, MerkleProofWrapper>,
|
||||
),
|
||||
Error,
|
||||
>;
|
||||
|
||||
/// retrieve merkle proof for a commit from a node
|
||||
fn get_merkle_proof_for_commit(
|
||||
&self,
|
||||
addr: &str,
|
||||
commit: &str,
|
||||
) -> Result<MerkleProofWrapper, Error>;
|
||||
}
|
||||
|
||||
/// Information about an output that's being tracked by the wallet. Must be
|
||||
/// enough to reconstruct the commitment associated with the ouput when the
|
||||
/// root private key is known.*/
|
||||
|
|
|
@ -32,6 +32,7 @@ use wallet::libwallet::internal::updater;
|
|||
use wallet::libwallet::types::*;
|
||||
use wallet::libwallet::{Error, ErrorKind};
|
||||
|
||||
use util;
|
||||
use util::secp::pedersen;
|
||||
|
||||
/// Mostly for testing, refreshes output state against a local chain instance
|
||||
|
@ -48,11 +49,11 @@ pub fn refresh_output_state_local<T: WalletBackend>(
|
|||
Ok(k) => Some(k),
|
||||
})
|
||||
.collect();
|
||||
let mut api_outputs: HashMap<pedersen::Commitment, api::Output> = HashMap::new();
|
||||
let mut api_outputs: HashMap<pedersen::Commitment, String> = HashMap::new();
|
||||
for out in chain_outputs {
|
||||
match out {
|
||||
Some(o) => {
|
||||
api_outputs.insert(o.commit.commit(), o);
|
||||
api_outputs.insert(o.commit.commit(), util::to_hex(o.commit.to_vec()));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue