Cleanup HTTP APIs, update ports to avoid gap, rustfmt
Moved the HTTP APIs away from the REST endpoint abstraction and to simpler Hyper handlers. Re-established all routes as v1. Changed wallet receiver port to 13415 to avoid a gap in port numbers. Finally, rustfmt seems to have ignored specific files arguments, running on everything.
This commit is contained in:
+24
-31
@@ -34,37 +34,34 @@ fn refresh_output(out: &mut OutputData, api_out: &api::Output) {
|
||||
match out.status {
|
||||
OutputStatus::Unconfirmed => {
|
||||
out.status = OutputStatus::Unspent;
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Transitions a local wallet output (based on it not being in the node utxo set) -
|
||||
// Transitions a local wallet output (based on it not being in the node utxo
|
||||
// set) -
|
||||
// Unspent -> Spent
|
||||
// Locked -> Spent
|
||||
fn mark_spent_output(out: &mut OutputData) {
|
||||
match out.status {
|
||||
OutputStatus::Unspent | OutputStatus::Locked => {
|
||||
out.status = OutputStatus::Spent
|
||||
},
|
||||
OutputStatus::Unspent | OutputStatus::Locked => out.status = OutputStatus::Spent,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a single api query to retrieve the latest output data from the node.
|
||||
/// So we can refresh the local wallet outputs.
|
||||
pub fn refresh_outputs(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
) -> Result<(), Error> {
|
||||
pub fn refresh_outputs(config: &WalletConfig, keychain: &Keychain) -> Result<(), Error> {
|
||||
debug!(LOGGER, "Refreshing wallet outputs");
|
||||
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
|
||||
let mut commits: Vec<pedersen::Commitment> = vec![];
|
||||
|
||||
// build a local map of wallet outputs by commits
|
||||
// and a list of outputs we wantot query the node for
|
||||
// and a list of outputs we wantot query the node for
|
||||
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
for out in wallet_data.outputs
|
||||
for out in wallet_data
|
||||
.outputs
|
||||
.values()
|
||||
.filter(|out| out.root_key_id == keychain.root_key_id())
|
||||
.filter(|out| out.status != OutputStatus::Spent)
|
||||
@@ -77,7 +74,7 @@ pub fn refresh_outputs(
|
||||
});
|
||||
|
||||
// build the necessary query params -
|
||||
// ?id=xxx&id=yyy&id=zzz
|
||||
// ?id=xxx&id=yyy&id=zzz
|
||||
let query_params: Vec<String> = commits
|
||||
.iter()
|
||||
.map(|commit| {
|
||||
@@ -88,7 +85,7 @@ pub fn refresh_outputs(
|
||||
let query_string = query_params.join("&");
|
||||
|
||||
let url = format!(
|
||||
"{}/v2/chain/utxos?{}",
|
||||
"{}/v1/chain/utxos?{}",
|
||||
config.check_node_api_http_addr,
|
||||
query_string,
|
||||
);
|
||||
@@ -96,32 +93,28 @@ pub fn refresh_outputs(
|
||||
// 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();
|
||||
match api::client::get::<Vec<api::Output>>(url.as_str()) {
|
||||
Ok(outputs) => {
|
||||
for out in outputs {
|
||||
api_outputs.insert(out.commit, out);
|
||||
}
|
||||
Ok(outputs) => for out in outputs {
|
||||
api_outputs.insert(out.commit, out);
|
||||
},
|
||||
Err(_) => {},
|
||||
Err(_) => {}
|
||||
};
|
||||
|
||||
// now for each commit, find the output in the wallet and
|
||||
// the corresponding api output (if it exists)
|
||||
// and refresh it in-place in the wallet.
|
||||
// Note: minimizing the time we spend holding the wallet lock.
|
||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||
for commit in commits {
|
||||
let id = wallet_outputs.get(&commit).unwrap();
|
||||
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
|
||||
match api_outputs.get(&commit) {
|
||||
Some(api_output) => refresh_output(&mut output.get_mut(), api_output),
|
||||
None => mark_spent_output(&mut output.get_mut()),
|
||||
};
|
||||
}
|
||||
// the corresponding api output (if it exists)
|
||||
// and refresh it in-place in the wallet.
|
||||
// Note: minimizing the time we spend holding the wallet lock.
|
||||
WalletData::with_wallet(&config.data_file_dir, |wallet_data| for commit in commits {
|
||||
let id = wallet_outputs.get(&commit).unwrap();
|
||||
if let Entry::Occupied(mut output) = wallet_data.outputs.entry(id.to_hex()) {
|
||||
match api_outputs.get(&commit) {
|
||||
Some(api_output) => refresh_output(&mut output.get_mut(), api_output),
|
||||
None => mark_spent_output(&mut output.get_mut()),
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_tip_from_node(config: &WalletConfig) -> Result<api::Tip, Error> {
|
||||
let url = format!("{}/v2/chain", config.check_node_api_http_addr);
|
||||
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
|
||||
api::client::get::<api::Tip>(url.as_str()).map_err(|e| Error::Node(e))
|
||||
}
|
||||
|
||||
+10
-6
@@ -33,7 +33,10 @@ pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Erro
|
||||
retry_backoff_forever(|| {
|
||||
let res = single_create_coinbase(&url, &block_fees);
|
||||
if let Err(_) = res {
|
||||
error!(LOGGER, "Failed to get coinbase via wallet API (will retry)...");
|
||||
error!(
|
||||
LOGGER,
|
||||
"Failed to get coinbase via wallet API (will retry)..."
|
||||
);
|
||||
}
|
||||
res
|
||||
})
|
||||
@@ -41,11 +44,12 @@ pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, Erro
|
||||
|
||||
/// Runs the specified function wrapped in some basic retry logic.
|
||||
fn retry_backoff_forever<F, R>(f: F) -> Result<R, Error>
|
||||
where F: (FnMut() -> Result<R, Error>)
|
||||
where
|
||||
F: FnMut() -> Result<R, Error>,
|
||||
{
|
||||
let mut core = reactor::Core::new()?;
|
||||
let retry_strategy = FibonacciBackoff::from_millis(100)
|
||||
.max_delay(time::Duration::from_secs(10));
|
||||
let retry_strategy =
|
||||
FibonacciBackoff::from_millis(100).max_delay(time::Duration::from_secs(10));
|
||||
let retry_future = Retry::spawn(core.handle(), retry_strategy, f);
|
||||
let res = core.run(retry_future).unwrap();
|
||||
Ok(res)
|
||||
@@ -63,8 +67,8 @@ fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result<CbData, E
|
||||
|
||||
let work = client.request(req).and_then(|res| {
|
||||
res.body().concat2().and_then(move |body| {
|
||||
let coinbase: CbData = serde_json::from_slice(&body)
|
||||
.map_err(|e| {io::Error::new(io::ErrorKind::Other, e)})?;
|
||||
let coinbase: CbData =
|
||||
serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(coinbase)
|
||||
})
|
||||
});
|
||||
|
||||
+9
-15
@@ -33,13 +33,10 @@ pub struct CoinbaseHandler {
|
||||
|
||||
impl CoinbaseHandler {
|
||||
fn build_coinbase(&self, block_fees: &BlockFees) -> Result<CbData, Error> {
|
||||
let (out, kern, block_fees) = receive_coinbase(
|
||||
&self.config,
|
||||
&self.keychain,
|
||||
block_fees,
|
||||
).map_err(|e| {
|
||||
api::Error::Internal(format!("Error building coinbase: {:?}", e))
|
||||
})?;
|
||||
let (out, kern, block_fees) = receive_coinbase(&self.config, &self.keychain, block_fees)
|
||||
.map_err(|e| {
|
||||
api::Error::Internal(format!("Error building coinbase: {:?}", e))
|
||||
})?;
|
||||
|
||||
let out_bin = ser::ser_vec(&out).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing output: {:?}", e))
|
||||
@@ -50,13 +47,9 @@ impl CoinbaseHandler {
|
||||
})?;
|
||||
|
||||
let key_id_bin = match block_fees.key_id {
|
||||
Some(key_id) => {
|
||||
ser::ser_vec(&key_id).map_err(|e| {
|
||||
api::Error::Internal(
|
||||
format!("Error serializing kernel: {:?}", e),
|
||||
)
|
||||
})?
|
||||
}
|
||||
Some(key_id) => ser::ser_vec(&key_id).map_err(|e| {
|
||||
api::Error::Internal(format!("Error serializing kernel: {:?}", e))
|
||||
})?,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
@@ -68,7 +61,8 @@ impl CoinbaseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - error handling - what to return if we fail to get the wallet lock for some reason...
|
||||
// TODO - error handling - what to return if we fail to get the wallet lock for
|
||||
// some reason...
|
||||
impl Handler for CoinbaseHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let struct_body = req.get::<bodyparser::Struct<BlockFees>>();
|
||||
|
||||
+5
-8
@@ -22,17 +22,14 @@ pub fn show_info(config: &WalletConfig, keychain: &Keychain) {
|
||||
|
||||
// just read the wallet here, no need for a write lock
|
||||
let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||
|
||||
// get the current height via the api
|
||||
// if we cannot get the current height use the max height known to the wallet
|
||||
// if we cannot get the current height use the max height known to the wallet
|
||||
let current_height = match checker::get_tip_from_node(config) {
|
||||
Ok(tip) => tip.height,
|
||||
Err(_) => {
|
||||
match wallet_data.outputs.values().map(|out| out.height).max() {
|
||||
Some(height) => height,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() {
|
||||
Some(height) => height,
|
||||
None => 0,
|
||||
},
|
||||
};
|
||||
|
||||
println!("Outputs - ");
|
||||
|
||||
+7
-7
@@ -14,24 +14,24 @@
|
||||
|
||||
//! Library module for the main wallet functionalities provided by Grin.
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate blake2_rfc as blake2;
|
||||
#[macro_use]
|
||||
extern crate slog;
|
||||
extern crate byteorder;
|
||||
extern crate rand;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate slog;
|
||||
|
||||
extern crate bodyparser;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_retry;
|
||||
extern crate hyper;
|
||||
extern crate iron;
|
||||
#[macro_use]
|
||||
extern crate router;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_retry;
|
||||
|
||||
extern crate grin_api as api;
|
||||
extern crate grin_core as core;
|
||||
@@ -48,6 +48,6 @@ pub mod client;
|
||||
pub mod server;
|
||||
|
||||
pub use info::show_info;
|
||||
pub use receiver::{WalletReceiver, receive_json_tx};
|
||||
pub use sender::{issue_send_tx, issue_burn_tx};
|
||||
pub use receiver::{receive_json_tx, WalletReceiver};
|
||||
pub use sender::{issue_burn_tx, issue_send_tx};
|
||||
pub use types::{BlockFees, CbData, Error, WalletConfig, WalletReceiveRequest, WalletSeed};
|
||||
|
||||
+42
-88
@@ -15,45 +15,18 @@
|
||||
//! 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.
|
||||
//!
|
||||
//! The API looks like this:
|
||||
//!
|
||||
//! POST /v1/wallet/receive
|
||||
//! > {
|
||||
//! > "amount": 10,
|
||||
//! > "blind_sum": "a12b7f...",
|
||||
//! > "tx": "f083de...",
|
||||
//! > }
|
||||
//!
|
||||
//! < {
|
||||
//! < "tx": "f083de...",
|
||||
//! < "status": "ok"
|
||||
//! < }
|
||||
//!
|
||||
//! POST /v1/wallet/finalize
|
||||
//! > {
|
||||
//! > "tx": "f083de...",
|
||||
//! > }
|
||||
//!
|
||||
//! POST /v1/wallet/receive_coinbase
|
||||
//! > {
|
||||
//! > "amount": 1,
|
||||
//! > }
|
||||
//!
|
||||
//! < {
|
||||
//! < "output": "8a90bc...",
|
||||
//! < "kernel": "f083de...",
|
||||
//! < }
|
||||
//!
|
||||
//! Note that while at this point the finalize call is completely unecessary, a
|
||||
//! double-exchange will be required as soon as we support Schnorr signatures.
|
||||
//! So we may as well have it in place already.
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
use core::consensus::reward;
|
||||
use core::core::{Block, Transaction, TxKernel, Output, build};
|
||||
use core::core::{build, Block, Output, Transaction, TxKernel};
|
||||
use core::ser;
|
||||
use api::{self, ApiEndpoint, Operation, ApiResult};
|
||||
use api;
|
||||
use iron::prelude::*;
|
||||
use iron::Handler;
|
||||
use iron::status;
|
||||
use keychain::{BlindingFactor, Identifier, Keychain};
|
||||
use serde_json;
|
||||
use types::*;
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
@@ -77,8 +50,8 @@ pub fn receive_json_tx(
|
||||
let tx_hex = util::to_hex(ser::ser_vec(&final_tx).unwrap());
|
||||
|
||||
let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
|
||||
let _: () = api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex })
|
||||
.map_err(|e| Error::Node(e))?;
|
||||
let _: () =
|
||||
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).map_err(|e| Error::Node(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -90,46 +63,27 @@ pub struct WalletReceiver {
|
||||
pub config: WalletConfig,
|
||||
}
|
||||
|
||||
impl ApiEndpoint for WalletReceiver {
|
||||
type ID = String;
|
||||
type T = String;
|
||||
type OP_IN = WalletReceiveRequest;
|
||||
type OP_OUT = CbData;
|
||||
impl Handler for WalletReceiver {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let receive: WalletReceiveRequest = serde_json::from_reader(req.body.by_ref())
|
||||
.map_err(|e| IronError::new(e, status::BadRequest))?;
|
||||
|
||||
fn operations(&self) -> Vec<Operation> {
|
||||
vec![Operation::Custom("receive_json_tx".to_string())]
|
||||
}
|
||||
match receive {
|
||||
WalletReceiveRequest::PartialTransaction(partial_tx_str) => {
|
||||
debug!(LOGGER, "Receive with transaction {}", &partial_tx_str,);
|
||||
receive_json_tx(&self.config, &self.keychain, &partial_tx_str)
|
||||
.map_err(|e| {
|
||||
api::Error::Internal(
|
||||
format!("Error processing partial transaction: {:?}", e),
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
fn operation(&self, op: String, input: WalletReceiveRequest) -> ApiResult<CbData> {
|
||||
match op.as_str() {
|
||||
"receive_json_tx" => {
|
||||
match input {
|
||||
WalletReceiveRequest::PartialTransaction(partial_tx_str) => {
|
||||
debug!(
|
||||
LOGGER,
|
||||
"Operation {} with transaction {}",
|
||||
op,
|
||||
&partial_tx_str,
|
||||
);
|
||||
receive_json_tx(&self.config, &self.keychain, &partial_tx_str)
|
||||
.map_err(|e| {
|
||||
api::Error::Internal(
|
||||
format!("Error processing partial transaction: {:?}", e),
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// TODO: Return emptiness for now, should be a proper enum return type
|
||||
Ok(CbData {
|
||||
output: String::from(""),
|
||||
kernel: String::from(""),
|
||||
key_id: String::from(""),
|
||||
})
|
||||
}
|
||||
_ => Err(api::Error::Argument(format!("Incorrect request data: {}", op))),
|
||||
}
|
||||
Ok(Response::with(status::Ok))
|
||||
}
|
||||
_ => Err(api::Error::Argument(format!("Unknown operation: {}", op))),
|
||||
_ => Ok(Response::with(
|
||||
(status::BadRequest, format!("Incorrect request data.")),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +122,7 @@ fn next_available_key(
|
||||
pub fn receive_coinbase(
|
||||
config: &WalletConfig,
|
||||
keychain: &Keychain,
|
||||
block_fees: &BlockFees
|
||||
block_fees: &BlockFees,
|
||||
) -> Result<(Output, TxKernel, BlockFees), Error> {
|
||||
let root_key_id = keychain.root_key_id();
|
||||
let key_id = block_fees.key_id();
|
||||
@@ -208,11 +162,7 @@ pub fn receive_coinbase(
|
||||
|
||||
debug!(LOGGER, "block_fees updated - {:?}", block_fees);
|
||||
|
||||
let (out, kern) = Block::reward_output(
|
||||
&keychain,
|
||||
&key_id,
|
||||
block_fees.fees,
|
||||
)?;
|
||||
let (out, kern) = Block::reward_output(&keychain, &key_id, block_fees.fees)?;
|
||||
Ok((out, kern, block_fees))
|
||||
}
|
||||
|
||||
@@ -229,8 +179,8 @@ fn receive_transaction(
|
||||
let (key_id, derivation) = next_available_key(config, keychain)?;
|
||||
|
||||
// double check the fee amount included in the partial tx
|
||||
// we don't necessarily want to just trust the sender
|
||||
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
|
||||
// we don't necessarily want to just trust the sender
|
||||
// we could just overwrite the fee here (but we won't) due to the ecdsa sig
|
||||
let fee = tx_fee(partial.inputs.len(), partial.outputs.len() + 1, None);
|
||||
if fee != partial.fee {
|
||||
return Err(Error::FeeDispute {
|
||||
@@ -241,14 +191,18 @@ fn receive_transaction(
|
||||
|
||||
let out_amount = amount - fee;
|
||||
|
||||
let (tx_final, _) = build::transaction(vec![
|
||||
build::initial_tx(partial),
|
||||
build::with_excess(blinding),
|
||||
build::output(out_amount, key_id.clone()),
|
||||
let (tx_final, _) = build::transaction(
|
||||
vec![
|
||||
build::initial_tx(partial),
|
||||
build::with_excess(blinding),
|
||||
build::output(out_amount, key_id.clone()),
|
||||
// build::with_fee(fee_amount),
|
||||
], keychain)?;
|
||||
],
|
||||
keychain,
|
||||
)?;
|
||||
|
||||
// make sure the resulting transaction is valid (could have been lied to on excess).
|
||||
// make sure the resulting transaction is valid (could have been lied to on
|
||||
// excess).
|
||||
tx_final.validate(&keychain.secp())?;
|
||||
|
||||
// operate within a lock on wallet data
|
||||
|
||||
+14
-13
@@ -14,9 +14,9 @@
|
||||
|
||||
use api;
|
||||
use checker;
|
||||
use core::core::{Transaction, build};
|
||||
use core::core::{build, Transaction};
|
||||
use core::ser;
|
||||
use keychain::{BlindingFactor, Keychain, Identifier};
|
||||
use keychain::{BlindingFactor, Identifier, Keychain};
|
||||
use receiver::TxWrapper;
|
||||
use types::*;
|
||||
use util::LOGGER;
|
||||
@@ -55,7 +55,7 @@ pub fn issue_send_tx(
|
||||
if dest == "stdout" {
|
||||
println!("{}", json_tx);
|
||||
} else if &dest[..4] == "http" {
|
||||
let url = format!("{}/v1/receive/receive_json_tx", &dest);
|
||||
let url = format!("{}/v1/receive/transaction", &dest);
|
||||
debug!(LOGGER, "Posting partial transaction to {}", url);
|
||||
let request = WalletReceiveRequest::PartialTransaction(json_tx);
|
||||
let _: CbData = api::client::post(url.as_str(), &request).expect(&format!(
|
||||
@@ -90,7 +90,7 @@ fn build_send_tx(
|
||||
let mut parts = inputs_and_change(&coins, config, keychain, key_id, amount)?;
|
||||
|
||||
// This is more proof of concept than anything but here we set lock_height
|
||||
// on tx being sent (based on current chain height via api).
|
||||
// on tx being sent (based on current chain height via api).
|
||||
parts.push(build::with_lock_height(lock_height));
|
||||
|
||||
let (tx, blind) = build::transaction(parts, &keychain)?;
|
||||
@@ -130,8 +130,8 @@ pub fn issue_burn_tx(
|
||||
|
||||
let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap());
|
||||
let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str());
|
||||
let _: () = api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex })
|
||||
.map_err(|e| Error::Node(e))?;
|
||||
let _: () =
|
||||
api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).map_err(|e| Error::Node(e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -165,15 +165,15 @@ fn inputs_and_change(
|
||||
}
|
||||
|
||||
// sender is responsible for setting the fee on the partial tx
|
||||
// recipient should double check the fee calculation and not blindly trust the
|
||||
// sender
|
||||
// recipient should double check the fee calculation and not blindly trust the
|
||||
// sender
|
||||
let fee = tx_fee(coins.len(), 2, None);
|
||||
parts.push(build::with_fee(fee));
|
||||
|
||||
// if we are spending 10,000 coins to send 1,000 then our change will be 9,000
|
||||
// the fee will come out of the amount itself
|
||||
// if the fee is 80 then the recipient will only receive 920
|
||||
// but our change will still be 9,000
|
||||
// the fee will come out of the amount itself
|
||||
// if the fee is 80 then the recipient will only receive 920
|
||||
// but our change will still be 9,000
|
||||
let change = total - amount;
|
||||
|
||||
// build inputs using the appropriate derived key_ids
|
||||
@@ -200,7 +200,8 @@ fn inputs_and_change(
|
||||
is_coinbase: false,
|
||||
});
|
||||
|
||||
// now lock the ouputs we're spending so we avoid accidental double spend attempt
|
||||
// now lock the ouputs we're spending so we avoid accidental double spend
|
||||
// attempt
|
||||
for coin in coins {
|
||||
wallet_data.lock_output(coin);
|
||||
}
|
||||
@@ -216,7 +217,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
// demonstrate that input.commitment == referenced output.commitment
|
||||
// based on the public key and amount begin spent
|
||||
// based on the public key and amount begin spent
|
||||
fn output_commitment_equals_input_commitment_on_spend() {
|
||||
let keychain = Keychain::from_random_seed().unwrap();
|
||||
let key_id1 = keychain.derive_key_id(1).unwrap();
|
||||
|
||||
+8
-14
@@ -27,28 +27,22 @@ pub fn start_rest_apis(wallet_config: WalletConfig, keychain: Keychain) {
|
||||
wallet_config.api_http_addr
|
||||
);
|
||||
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
|
||||
apis.register_endpoint(
|
||||
"/receive".to_string(),
|
||||
WalletReceiver {
|
||||
config: wallet_config.clone(),
|
||||
keychain: keychain.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
let receive_tx_handler = WalletReceiver {
|
||||
config: wallet_config.clone(),
|
||||
keychain: keychain.clone(),
|
||||
};
|
||||
let coinbase_handler = CoinbaseHandler {
|
||||
config: wallet_config.clone(),
|
||||
keychain: keychain.clone(),
|
||||
};
|
||||
// let tx_handler = TxHandler{};
|
||||
|
||||
let router = router!(
|
||||
receive_tx: get "/receive/transaction" => receive_tx_handler,
|
||||
receive_coinbase: post "/receive/coinbase" => coinbase_handler,
|
||||
// receive_tx: post "/receive/tx" => tx_handler,
|
||||
);
|
||||
apis.register_handler("/v2", router);
|
||||
);
|
||||
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_handler(router);
|
||||
apis.start(wallet_config.api_http_addr).unwrap_or_else(|e| {
|
||||
error!(LOGGER, "Failed to start Grin wallet receiver: {}.", e);
|
||||
});
|
||||
|
||||
+53
-60
@@ -14,7 +14,7 @@
|
||||
|
||||
use blake2;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::{fmt, num, error};
|
||||
use std::{error, fmt, num};
|
||||
use std::convert::From;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Write};
|
||||
@@ -32,7 +32,7 @@ use tokio_retry::strategy::FibonacciBackoff;
|
||||
|
||||
|
||||
use api;
|
||||
use core::core::{Transaction, transaction};
|
||||
use core::core::{transaction, Transaction};
|
||||
use core::ser;
|
||||
use keychain;
|
||||
use util;
|
||||
@@ -62,7 +62,7 @@ pub fn tx_fee(input_len: usize, output_len: usize, base_fee: Option<u64>) -> u64
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NotEnoughFunds(u64),
|
||||
FeeDispute{sender_fee: u64, recipient_fee: u64},
|
||||
FeeDispute { sender_fee: u64, recipient_fee: u64 },
|
||||
Keychain(keychain::Error),
|
||||
Transaction(transaction::Error),
|
||||
Secp(secp::Error),
|
||||
@@ -166,7 +166,7 @@ impl Default for WalletConfig {
|
||||
fn default() -> WalletConfig {
|
||||
WalletConfig {
|
||||
enable_wallet: false,
|
||||
api_http_addr: "0.0.0.0:13416".to_string(),
|
||||
api_http_addr: "0.0.0.0:13415".to_string(),
|
||||
check_node_api_http_addr: "http://127.0.0.1:13413".to_string(),
|
||||
data_file_dir: ".".to_string(),
|
||||
}
|
||||
@@ -226,8 +226,10 @@ impl OutputData {
|
||||
}
|
||||
|
||||
/// How many confirmations has this output received?
|
||||
/// If height == 0 then we are either Unconfirmed or the output was cut-through
|
||||
/// so we do not actually know how many confirmations this output had (and never will).
|
||||
/// If height == 0 then we are either Unconfirmed or the output was
|
||||
/// cut-through
|
||||
/// so we do not actually know how many confirmations this output had (and
|
||||
/// never will).
|
||||
pub fn num_confirmations(&self, current_height: u64) -> u64 {
|
||||
if self.status == OutputStatus::Unconfirmed {
|
||||
0
|
||||
@@ -239,21 +241,16 @@ impl OutputData {
|
||||
}
|
||||
|
||||
/// Check if output is eligible for spending based on state and height.
|
||||
pub fn eligible_to_spend(
|
||||
&self,
|
||||
current_height: u64,
|
||||
minimum_confirmations: u64
|
||||
) -> bool {
|
||||
if [
|
||||
OutputStatus::Spent,
|
||||
OutputStatus::Locked,
|
||||
].contains(&self.status) {
|
||||
pub fn eligible_to_spend(&self, current_height: u64, minimum_confirmations: u64) -> bool {
|
||||
if [OutputStatus::Spent, OutputStatus::Locked].contains(&self.status) {
|
||||
return false;
|
||||
} else if self.status == OutputStatus::Unconfirmed && self.is_coinbase {
|
||||
return false;
|
||||
} else if self.lock_height > current_height {
|
||||
return false;
|
||||
} else if self.status == OutputStatus::Unspent && self.height + minimum_confirmations <= current_height {
|
||||
} else if self.status == OutputStatus::Unspent
|
||||
&& self.height + minimum_confirmations <= current_height
|
||||
{
|
||||
return true;
|
||||
} else if self.status == OutputStatus::Unconfirmed && minimum_confirmations == 0 {
|
||||
return true;
|
||||
@@ -306,11 +303,7 @@ impl WalletSeed {
|
||||
SEED_FILE,
|
||||
);
|
||||
|
||||
debug!(
|
||||
LOGGER,
|
||||
"Generating wallet seed file at: {}",
|
||||
seed_file_path,
|
||||
);
|
||||
debug!(LOGGER, "Generating wallet seed file at: {}", seed_file_path,);
|
||||
|
||||
if Path::new(seed_file_path).exists() {
|
||||
panic!("wallet seed file already exists");
|
||||
@@ -333,11 +326,7 @@ impl WalletSeed {
|
||||
SEED_FILE,
|
||||
);
|
||||
|
||||
debug!(
|
||||
LOGGER,
|
||||
"Using wallet seed file at: {}",
|
||||
seed_file_path,
|
||||
);
|
||||
debug!(LOGGER, "Using wallet seed file at: {}", seed_file_path,);
|
||||
|
||||
if Path::new(seed_file_path).exists() {
|
||||
let mut file = File::open(seed_file_path)?;
|
||||
@@ -369,10 +358,11 @@ pub struct WalletData {
|
||||
}
|
||||
|
||||
impl WalletData {
|
||||
|
||||
/// Allows for reading wallet data (without needing to acquire the write lock).
|
||||
/// Allows for reading wallet data (without needing to acquire the write
|
||||
/// lock).
|
||||
pub fn read_wallet<T, F>(data_file_dir: &str, f: F) -> Result<T, Error>
|
||||
where F: FnOnce(&WalletData) -> T
|
||||
where
|
||||
F: FnOnce(&WalletData) -> T,
|
||||
{
|
||||
// open the wallet readonly and do what needs to be done with it
|
||||
let data_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, DAT_FILE);
|
||||
@@ -388,7 +378,8 @@ impl WalletData {
|
||||
/// across operating systems, this just creates a lock file with a "should
|
||||
/// not exist" option.
|
||||
pub fn with_wallet<T, F>(data_file_dir: &str, f: F) -> Result<T, Error>
|
||||
where F: FnOnce(&mut WalletData) -> T
|
||||
where
|
||||
F: FnOnce(&mut WalletData) -> T,
|
||||
{
|
||||
// create directory if it doesn't exist
|
||||
fs::create_dir_all(data_file_dir).unwrap_or_else(|why| {
|
||||
@@ -415,7 +406,7 @@ impl WalletData {
|
||||
let retry_result = core.run(retry_future);
|
||||
|
||||
match retry_result {
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
error!(
|
||||
LOGGER,
|
||||
@@ -448,31 +439,33 @@ impl WalletData {
|
||||
WalletData::read(data_file_path)
|
||||
} else {
|
||||
// just create a new instance, it will get written afterward
|
||||
Ok(WalletData { outputs: HashMap::new() })
|
||||
Ok(WalletData {
|
||||
outputs: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the wallet data from disk.
|
||||
fn read(data_file_path: &str) -> Result<WalletData, Error> {
|
||||
let data_file =
|
||||
File::open(data_file_path)
|
||||
.map_err(|e| Error::WalletData(format!("Could not open {}: {}", data_file_path, e)))?;
|
||||
serde_json::from_reader(data_file)
|
||||
.map_err(|e| Error::WalletData(format!("Error reading {}: {}", data_file_path, e)))
|
||||
let data_file = File::open(data_file_path).map_err(|e| {
|
||||
Error::WalletData(format!("Could not open {}: {}", data_file_path, e))
|
||||
})?;
|
||||
serde_json::from_reader(data_file).map_err(|e| {
|
||||
Error::WalletData(format!("Error reading {}: {}", data_file_path, e))
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the wallet data to disk.
|
||||
fn write(&self, data_file_path: &str) -> Result<(), Error> {
|
||||
let mut data_file =
|
||||
File::create(data_file_path)
|
||||
.map_err(|e| {
|
||||
Error::WalletData(format!("Could not create {}: {}", data_file_path, e))
|
||||
})?;
|
||||
let res_json = serde_json::to_vec_pretty(self)
|
||||
.map_err(|e| Error::WalletData(format!("Error serializing wallet data: {}", e)))?;
|
||||
data_file
|
||||
.write_all(res_json.as_slice())
|
||||
.map_err(|e| Error::WalletData(format!("Error writing {}: {}", data_file_path, e)))
|
||||
let mut data_file = File::create(data_file_path).map_err(|e| {
|
||||
Error::WalletData(format!("Could not create {}: {}", data_file_path, e))
|
||||
})?;
|
||||
let res_json = serde_json::to_vec_pretty(self).map_err(|e| {
|
||||
Error::WalletData(format!("Error serializing wallet data: {}", e))
|
||||
})?;
|
||||
data_file.write_all(res_json.as_slice()).map_err(|e| {
|
||||
Error::WalletData(format!("Error writing {}: {}", data_file_path, e))
|
||||
})
|
||||
}
|
||||
|
||||
/// Append a new output data to the wallet data.
|
||||
@@ -503,7 +496,6 @@ impl WalletData {
|
||||
current_height: u64,
|
||||
minimum_confirmations: u64,
|
||||
) -> Vec<OutputData> {
|
||||
|
||||
self.outputs
|
||||
.values()
|
||||
.filter(|out| {
|
||||
@@ -537,10 +529,11 @@ struct JSONPartialTx {
|
||||
|
||||
/// Encodes the information for a partial transaction (not yet completed by the
|
||||
/// receiver) into JSON.
|
||||
pub fn partial_tx_to_json(receive_amount: u64,
|
||||
blind_sum: keychain::BlindingFactor,
|
||||
tx: Transaction)
|
||||
-> String {
|
||||
pub fn partial_tx_to_json(
|
||||
receive_amount: u64,
|
||||
blind_sum: keychain::BlindingFactor,
|
||||
tx: Transaction,
|
||||
) -> String {
|
||||
let partial_tx = JSONPartialTx {
|
||||
amount: receive_amount,
|
||||
blind_sum: util::to_hex(blind_sum.secret_key().as_ref().to_vec()),
|
||||
@@ -551,22 +544,22 @@ pub fn partial_tx_to_json(receive_amount: u64,
|
||||
|
||||
/// Reads a partial transaction encoded as JSON into the amount, sum of blinding
|
||||
/// factors and the transaction itself.
|
||||
pub fn partial_tx_from_json(keychain: &keychain::Keychain,
|
||||
json_str: &str)
|
||||
-> Result<(u64, keychain::BlindingFactor, Transaction), Error> {
|
||||
pub fn partial_tx_from_json(
|
||||
keychain: &keychain::Keychain,
|
||||
json_str: &str,
|
||||
) -> Result<(u64, keychain::BlindingFactor, Transaction), Error> {
|
||||
let partial_tx: JSONPartialTx = serde_json::from_str(json_str)?;
|
||||
|
||||
let blind_bin = util::from_hex(partial_tx.blind_sum)?;
|
||||
|
||||
// TODO - turn some data into a blinding factor here somehow
|
||||
// let blinding = SecretKey::from_slice(&secp, &blind_bin[..])?;
|
||||
// let blinding = SecretKey::from_slice(&secp, &blind_bin[..])?;
|
||||
let blinding = keychain::BlindingFactor::from_slice(keychain.secp(), &blind_bin[..])?;
|
||||
|
||||
let tx_bin = util::from_hex(partial_tx.tx)?;
|
||||
let tx = ser::deserialize(&mut &tx_bin[..])
|
||||
.map_err(|_| {
|
||||
Error::Format("Could not deserialize transaction, invalid format.".to_string())
|
||||
})?;
|
||||
let tx = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
|
||||
Error::Format("Could not deserialize transaction, invalid format.".to_string())
|
||||
})?;
|
||||
|
||||
Ok((partial_tx.amount, blinding, tx))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user