2018-06-06 17:36:29 +03:00
|
|
|
// 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
|
2018-12-05 21:37:11 +03:00
|
|
|
use adapters::{FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter};
|
2018-10-02 10:49:36 +03:00
|
|
|
use api::{ApiServer, BasicAuthMiddleware, Handler, ResponseFuture, Router, TLSConfig};
|
2018-11-19 22:47:40 +03:00
|
|
|
use core::core;
|
2018-09-22 10:34:28 +03:00
|
|
|
use core::core::Transaction;
|
2018-12-06 15:04:02 +03:00
|
|
|
use core::libtx::slate::Slate;
|
2018-09-22 10:34:28 +03:00
|
|
|
use failure::ResultExt;
|
2018-08-01 12:44:07 +03:00
|
|
|
use futures::future::{err, ok};
|
|
|
|
use futures::{Future, Stream};
|
|
|
|
use hyper::{Body, Request, Response, StatusCode};
|
2018-06-08 08:21:54 +03:00
|
|
|
use keychain::Keychain;
|
2018-06-06 17:36:29 +03:00
|
|
|
use libwallet::api::{APIForeign, APIOwner};
|
2018-07-09 20:01:19 +03:00
|
|
|
use libwallet::types::{
|
2018-11-19 22:47:40 +03:00
|
|
|
CbData, NodeClient, OutputData, SendTXArgs, TxLogEntry, WalletBackend, WalletInfo,
|
2018-07-09 20:01:19 +03:00
|
|
|
};
|
2018-06-06 17:36:29 +03:00
|
|
|
use libwallet::{Error, ErrorKind};
|
2018-09-22 10:34:28 +03:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use serde_json;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
use std::net::SocketAddr;
|
2018-10-20 03:13:07 +03:00
|
|
|
use std::sync::Arc;
|
2018-08-01 12:44:07 +03:00
|
|
|
use url::form_urlencoded;
|
2018-08-17 14:15:06 +03:00
|
|
|
use util::secp::pedersen;
|
2018-10-21 23:30:56 +03:00
|
|
|
use util::to_base64;
|
2018-10-22 00:56:42 +03:00
|
|
|
use util::Mutex;
|
2018-06-06 17:36:29 +03:00
|
|
|
|
|
|
|
/// Instantiate wallet Owner API for a single-use (command line) call
|
|
|
|
/// Return a function containing a loaded API context to call
|
2018-11-19 22:47:40 +03:00
|
|
|
pub fn owner_single_use<F, T: ?Sized, C, K>(wallet: Arc<Mutex<T>>, f: F) -> Result<(), Error>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K>,
|
|
|
|
F: FnOnce(&mut APIOwner<T, C, K>) -> Result<(), Error>,
|
|
|
|
C: NodeClient,
|
2018-06-08 08:21:54 +03:00
|
|
|
K: Keychain,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
2018-07-12 18:49:37 +03:00
|
|
|
f(&mut APIOwner::new(wallet.clone()))?;
|
2018-06-06 17:36:29 +03:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Instantiate wallet Foreign API for a single-use (command line) call
|
|
|
|
/// Return a function containing a loaded API context to call
|
2018-11-19 22:47:40 +03:00
|
|
|
pub fn foreign_single_use<F, T: ?Sized, C, K>(wallet: Arc<Mutex<T>>, f: F) -> Result<(), Error>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K>,
|
|
|
|
F: FnOnce(&mut APIForeign<T, C, K>) -> Result<(), Error>,
|
|
|
|
C: NodeClient,
|
2018-06-08 08:21:54 +03:00
|
|
|
K: Keychain,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
2018-07-12 18:49:37 +03:00
|
|
|
f(&mut APIForeign::new(wallet.clone()))?;
|
2018-06-06 17:36:29 +03:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Listener version, providing same API but listening for requests on a
|
|
|
|
/// port and wrapping the calls
|
2018-11-19 22:47:40 +03:00
|
|
|
pub fn owner_listener<T: ?Sized, C, K>(
|
2018-11-11 14:56:42 +03:00
|
|
|
wallet: Arc<Mutex<T>>,
|
2018-09-26 23:38:44 +03:00
|
|
|
addr: &str,
|
|
|
|
api_secret: Option<String>,
|
2018-10-02 10:49:36 +03:00
|
|
|
tls_config: Option<TLSConfig>,
|
2018-09-26 23:38:44 +03:00
|
|
|
) -> Result<(), Error>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
OwnerAPIHandler<T, C, K>: Handler,
|
|
|
|
C: NodeClient + 'static,
|
2018-08-01 12:44:07 +03:00
|
|
|
K: Keychain + 'static,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
2018-11-11 14:56:42 +03:00
|
|
|
let api_handler = OwnerAPIHandler::new(wallet);
|
2018-08-01 12:44:07 +03:00
|
|
|
|
2018-09-19 18:10:52 +03:00
|
|
|
let mut router = Router::new();
|
2018-09-26 23:38:44 +03:00
|
|
|
if api_secret.is_some() {
|
|
|
|
let api_basic_auth =
|
|
|
|
"Basic ".to_string() + &to_base64(&("grin:".to_string() + &api_secret.unwrap()));
|
|
|
|
let basic_realm = "Basic realm=GrinOwnerAPI".to_string();
|
|
|
|
let basic_auth_middleware = Arc::new(BasicAuthMiddleware::new(api_basic_auth, basic_realm));
|
|
|
|
router.add_middleware(basic_auth_middleware);
|
|
|
|
}
|
2018-09-19 18:10:52 +03:00
|
|
|
router
|
2018-09-21 20:57:59 +03:00
|
|
|
.add_route("/v1/wallet/owner/**", Arc::new(api_handler))
|
2018-08-01 12:44:07 +03:00
|
|
|
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
|
|
|
|
2018-09-19 18:10:52 +03:00
|
|
|
let mut apis = ApiServer::new();
|
2018-10-21 23:30:56 +03:00
|
|
|
info!("Starting HTTP Owner API server at {}.", addr);
|
2018-09-19 18:10:52 +03:00
|
|
|
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
|
2018-10-18 13:23:22 +03:00
|
|
|
let api_thread =
|
|
|
|
apis.start(socket_addr, router, tls_config)
|
|
|
|
.context(ErrorKind::GenericError(
|
|
|
|
"API thread failed to start".to_string(),
|
|
|
|
))?;
|
2018-09-22 10:34:28 +03:00
|
|
|
api_thread
|
|
|
|
.join()
|
2018-09-26 23:38:44 +03:00
|
|
|
.map_err(|e| ErrorKind::GenericError(format!("API thread panicked :{:?}", e)).into())
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Listener version, providing same API but listening for requests on a
|
|
|
|
/// port and wrapping the calls
|
2018-11-19 22:47:40 +03:00
|
|
|
pub fn foreign_listener<T: ?Sized, C, K>(
|
2018-11-11 14:56:42 +03:00
|
|
|
wallet: Arc<Mutex<T>>,
|
2018-10-02 10:49:36 +03:00
|
|
|
addr: &str,
|
|
|
|
tls_config: Option<TLSConfig>,
|
|
|
|
) -> Result<(), Error>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
C: NodeClient + 'static,
|
2018-08-01 12:44:07 +03:00
|
|
|
K: Keychain + 'static,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
2018-11-11 14:56:42 +03:00
|
|
|
let api_handler = ForeignAPIHandler::new(wallet);
|
2018-06-06 17:36:29 +03:00
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
let mut router = Router::new();
|
|
|
|
router
|
2018-09-21 20:57:59 +03:00
|
|
|
.add_route("/v1/wallet/foreign/**", Arc::new(api_handler))
|
2018-08-01 12:44:07 +03:00
|
|
|
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
|
|
|
|
2018-09-19 18:10:52 +03:00
|
|
|
let mut apis = ApiServer::new();
|
2018-10-21 23:30:56 +03:00
|
|
|
info!("Starting HTTP Foreign API server at {}.", addr);
|
2018-09-19 18:10:52 +03:00
|
|
|
let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address");
|
2018-10-18 13:23:22 +03:00
|
|
|
let api_thread =
|
|
|
|
apis.start(socket_addr, router, tls_config)
|
|
|
|
.context(ErrorKind::GenericError(
|
|
|
|
"API thread failed to start".to_string(),
|
|
|
|
))?;
|
2018-09-22 10:34:28 +03:00
|
|
|
|
|
|
|
api_thread
|
|
|
|
.join()
|
2018-09-27 22:45:48 +03:00
|
|
|
.map_err(|e| ErrorKind::GenericError(format!("API thread panicked :{:?}", e)).into())
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
type WalletResponseFuture = Box<Future<Item = Response<Body>, Error = Error> + Send>;
|
|
|
|
|
|
|
|
/// API Handler/Wrapper for owner functions
|
2018-11-19 22:47:40 +03:00
|
|
|
pub struct OwnerAPIHandler<T: ?Sized, C, K>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
C: NodeClient + 'static,
|
2018-08-01 12:44:07 +03:00
|
|
|
K: Keychain + 'static,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
|
|
|
/// Wallet instance
|
2018-11-11 14:56:42 +03:00
|
|
|
pub wallet: Arc<Mutex<T>>,
|
2018-06-08 08:21:54 +03:00
|
|
|
phantom: PhantomData<K>,
|
2018-07-10 11:18:24 +03:00
|
|
|
phantom_c: PhantomData<C>,
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-11-19 22:47:40 +03:00
|
|
|
impl<T: ?Sized, C, K> OwnerAPIHandler<T, C, K>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
C: NodeClient + 'static,
|
2018-08-01 12:44:07 +03:00
|
|
|
K: Keychain + 'static,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
2018-07-02 02:23:24 +03:00
|
|
|
/// Create a new owner API handler for GET methods
|
2018-11-19 22:47:40 +03:00
|
|
|
pub fn new(wallet: Arc<Mutex<T>>) -> OwnerAPIHandler<T, C, K> {
|
2018-08-01 12:44:07 +03:00
|
|
|
OwnerAPIHandler {
|
2018-06-08 08:21:54 +03:00
|
|
|
wallet,
|
|
|
|
phantom: PhantomData,
|
2018-07-10 11:18:24 +03:00
|
|
|
phantom_c: PhantomData,
|
2018-06-08 08:21:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn retrieve_outputs(
|
2018-06-06 17:36:29 +03:00
|
|
|
&self,
|
2018-08-01 12:44:07 +03:00
|
|
|
req: &Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
api: APIOwner<T, C, K>,
|
2018-08-17 14:15:06 +03:00
|
|
|
) -> Result<(bool, Vec<(OutputData, pedersen::Commitment)>), Error> {
|
2018-06-13 23:58:45 +03:00
|
|
|
let mut update_from_node = false;
|
2018-07-27 11:39:01 +03:00
|
|
|
let mut id = None;
|
|
|
|
let mut show_spent = false;
|
2018-08-01 12:44:07 +03:00
|
|
|
let params = parse_params(req);
|
|
|
|
|
|
|
|
if let Some(_) = params.get("refresh") {
|
|
|
|
update_from_node = true;
|
|
|
|
}
|
|
|
|
if let Some(_) = params.get("show_spent") {
|
|
|
|
show_spent = true;
|
|
|
|
}
|
|
|
|
if let Some(ids) = params.get("tx_id") {
|
2018-11-20 14:17:03 +03:00
|
|
|
if let Some(x) = ids.first() {
|
|
|
|
id = Some(x.parse().unwrap());
|
2018-07-27 11:39:01 +03:00
|
|
|
}
|
2018-06-13 23:58:45 +03:00
|
|
|
}
|
2018-07-27 11:39:01 +03:00
|
|
|
api.retrieve_outputs(show_spent, update_from_node, id)
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn retrieve_txs(
|
2018-07-24 22:49:29 +03:00
|
|
|
&self,
|
2018-08-01 12:44:07 +03:00
|
|
|
req: &Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
api: APIOwner<T, C, K>,
|
2018-07-24 22:49:29 +03:00
|
|
|
) -> Result<(bool, Vec<TxLogEntry>), Error> {
|
2018-11-12 21:18:54 +03:00
|
|
|
let mut tx_id = None;
|
|
|
|
let mut tx_slate_id = None;
|
2018-07-24 22:49:29 +03:00
|
|
|
let mut update_from_node = false;
|
2018-08-01 12:44:07 +03:00
|
|
|
|
|
|
|
let params = parse_params(req);
|
|
|
|
|
|
|
|
if let Some(_) = params.get("refresh") {
|
|
|
|
update_from_node = true;
|
|
|
|
}
|
|
|
|
if let Some(ids) = params.get("id") {
|
2018-11-20 14:17:03 +03:00
|
|
|
if let Some(x) = ids.first() {
|
|
|
|
tx_id = Some(x.parse().unwrap());
|
2018-11-12 21:18:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(tx_slate_ids) = params.get("tx_id") {
|
2018-11-20 14:17:03 +03:00
|
|
|
if let Some(x) = tx_slate_ids.first() {
|
|
|
|
tx_slate_id = Some(x.parse().unwrap());
|
2018-07-27 11:39:01 +03:00
|
|
|
}
|
2018-07-24 22:49:29 +03:00
|
|
|
}
|
2018-11-12 21:18:54 +03:00
|
|
|
api.retrieve_txs(update_from_node, tx_id, tx_slate_id)
|
2018-07-24 22:49:29 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn retrieve_stored_tx(
|
2018-09-11 05:18:10 +03:00
|
|
|
&self,
|
|
|
|
req: &Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
api: APIOwner<T, C, K>,
|
2018-11-28 18:31:23 +03:00
|
|
|
) -> Result<(bool, Option<Transaction>), Error> {
|
2018-09-11 05:18:10 +03:00
|
|
|
let params = parse_params(req);
|
|
|
|
if let Some(id_string) = params.get("id") {
|
|
|
|
match id_string[0].parse() {
|
2018-11-28 18:31:23 +03:00
|
|
|
Ok(id) => match api.retrieve_txs(true, Some(id), None) {
|
|
|
|
Ok((_, txs)) => Ok((txs[0].confirmed, txs[0].get_stored_tx())),
|
2018-09-11 05:18:10 +03:00
|
|
|
Err(e) => {
|
2018-11-28 18:31:23 +03:00
|
|
|
error!("retrieve_stored_tx: failed with error: {}", e);
|
2018-09-11 05:18:10 +03:00
|
|
|
Err(e)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
2018-11-28 18:31:23 +03:00
|
|
|
error!("retrieve_stored_tx: could not parse id: {}", e);
|
2018-09-11 05:18:10 +03:00
|
|
|
Err(ErrorKind::TransactionDumpError(
|
2018-11-28 18:31:23 +03:00
|
|
|
"retrieve_stored_tx: cannot dump transaction. Could not parse id in request.",
|
2018-09-11 05:18:10 +03:00
|
|
|
).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(ErrorKind::TransactionDumpError(
|
2018-11-28 18:31:23 +03:00
|
|
|
"retrieve_stored_tx: Cannot retrieve transaction. Missing id param in request.",
|
2018-09-11 05:18:10 +03:00
|
|
|
).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn retrieve_summary_info(
|
2018-06-06 17:36:29 +03:00
|
|
|
&self,
|
2018-08-01 12:44:07 +03:00
|
|
|
req: &Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
mut api: APIOwner<T, C, K>,
|
2018-06-14 19:02:05 +03:00
|
|
|
) -> Result<(bool, WalletInfo), Error> {
|
2018-11-20 14:17:03 +03:00
|
|
|
let mut minimum_confirmations = 1; // TODO - default needed here
|
|
|
|
let params = parse_params(req);
|
|
|
|
let update_from_node = params.get("refresh").is_some();
|
|
|
|
|
|
|
|
if let Some(confs) = params.get("minimum_confirmations") {
|
|
|
|
if let Some(x) = confs.first() {
|
|
|
|
minimum_confirmations = x.parse().unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
api.retrieve_summary_info(update_from_node, minimum_confirmations)
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn node_height(
|
2018-06-13 23:58:45 +03:00
|
|
|
&self,
|
2018-08-01 12:44:07 +03:00
|
|
|
_req: &Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
mut api: APIOwner<T, C, K>,
|
2018-06-13 23:58:45 +03:00
|
|
|
) -> Result<(u64, bool), Error> {
|
|
|
|
api.node_height()
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
fn handle_get_request(&self, req: &Request<Body>) -> Result<Response<Body>, Error> {
|
|
|
|
let api = APIOwner::new(self.wallet.clone());
|
2018-09-27 22:45:48 +03:00
|
|
|
|
2018-08-17 14:15:06 +03:00
|
|
|
Ok(match req
|
|
|
|
.uri()
|
2018-08-01 12:44:07 +03:00
|
|
|
.path()
|
|
|
|
.trim_right_matches("/")
|
|
|
|
.rsplit("/")
|
|
|
|
.next()
|
|
|
|
.unwrap()
|
|
|
|
{
|
|
|
|
"retrieve_outputs" => json_response(&self.retrieve_outputs(req, api)?),
|
|
|
|
"retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)?),
|
|
|
|
"node_height" => json_response(&self.node_height(req, api)?),
|
|
|
|
"retrieve_txs" => json_response(&self.retrieve_txs(req, api)?),
|
2018-11-28 18:31:23 +03:00
|
|
|
"retrieve_stored_tx" => json_response(&self.retrieve_stored_tx(req, api)?),
|
2018-08-01 12:44:07 +03:00
|
|
|
_ => response(StatusCode::BAD_REQUEST, ""),
|
|
|
|
})
|
2018-06-13 23:58:45 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn issue_send_tx(
|
2018-07-13 17:27:16 +03:00
|
|
|
&self,
|
2018-08-01 12:44:07 +03:00
|
|
|
req: Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
mut api: APIOwner<T, C, K>,
|
2018-08-01 12:44:07 +03:00
|
|
|
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
|
|
|
Box::new(parse_body(req).and_then(move |args: SendTXArgs| {
|
2018-11-19 22:47:40 +03:00
|
|
|
let result = api.initiate_tx(
|
|
|
|
None,
|
|
|
|
args.amount,
|
|
|
|
args.minimum_confirmations,
|
|
|
|
args.max_outputs,
|
|
|
|
args.num_change_outputs,
|
|
|
|
args.selection_strategy_is_use_all,
|
2018-11-29 15:49:00 +03:00
|
|
|
args.message,
|
2018-11-19 22:47:40 +03:00
|
|
|
);
|
|
|
|
let (mut slate, lock_fn) = match result {
|
|
|
|
Ok(s) => {
|
|
|
|
info!(
|
|
|
|
"Tx created: {} grin to {} (strategy '{}')",
|
|
|
|
core::amount_to_hr_string(args.amount, false),
|
|
|
|
&args.dest,
|
|
|
|
args.selection_strategy_is_use_all,
|
|
|
|
);
|
|
|
|
s
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Tx not created: {}", e);
|
|
|
|
match e.kind() {
|
|
|
|
// user errors, don't backtrace
|
|
|
|
ErrorKind::NotEnoughFunds { .. } => {}
|
|
|
|
ErrorKind::FeeDispute { .. } => {}
|
|
|
|
ErrorKind::FeeExceedsAmount { .. } => {}
|
|
|
|
_ => {
|
|
|
|
// otherwise give full dump
|
|
|
|
error!("Backtrace: {}", e.backtrace().unwrap());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
};
|
2018-09-11 05:18:10 +03:00
|
|
|
if args.method == "http" {
|
2018-11-19 22:47:40 +03:00
|
|
|
let adapter = HTTPWalletCommAdapter::new();
|
|
|
|
slate = adapter.send_tx_sync(&args.dest, &slate)?;
|
2018-11-28 14:02:29 +03:00
|
|
|
api.tx_lock_outputs(&slate, lock_fn)?;
|
2018-11-19 22:47:40 +03:00
|
|
|
api.finalize_tx(&mut slate)?;
|
2018-09-11 05:18:10 +03:00
|
|
|
} else if args.method == "file" {
|
2018-11-19 22:47:40 +03:00
|
|
|
let adapter = FileWalletCommAdapter::new();
|
|
|
|
adapter.send_tx_async(&args.dest, &slate)?;
|
2018-11-28 14:02:29 +03:00
|
|
|
api.tx_lock_outputs(&slate, lock_fn)?;
|
2018-12-05 21:37:11 +03:00
|
|
|
} else if args.method == "keybase" {
|
|
|
|
let adapter = KeybaseWalletCommAdapter::new();
|
|
|
|
adapter.send_tx_sync(&args.dest, &slate)?;
|
2018-09-11 05:18:10 +03:00
|
|
|
} else {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("unsupported payment method: {}", args.method);
|
2018-09-11 05:18:10 +03:00
|
|
|
return Err(ErrorKind::ClientCallback("unsupported payment method"))?;
|
|
|
|
}
|
2018-11-19 22:47:40 +03:00
|
|
|
Ok(slate)
|
2018-08-01 12:44:07 +03:00
|
|
|
}))
|
2018-06-13 23:58:45 +03:00
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn finalize_tx(
|
2018-09-11 05:18:10 +03:00
|
|
|
&self,
|
|
|
|
req: Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
mut api: APIOwner<T, C, K>,
|
2018-09-11 05:18:10 +03:00
|
|
|
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
|
|
|
Box::new(
|
|
|
|
parse_body(req).and_then(move |mut slate| match api.finalize_tx(&mut slate) {
|
|
|
|
Ok(_) => ok(slate.clone()),
|
|
|
|
Err(e) => {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("finalize_tx: failed with error: {}", e);
|
2018-09-11 05:18:10 +03:00
|
|
|
err(e)
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn cancel_tx(
|
2018-09-11 05:18:10 +03:00
|
|
|
&self,
|
|
|
|
req: Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
mut api: APIOwner<T, C, K>,
|
2018-09-11 05:18:10 +03:00
|
|
|
) -> Box<Future<Item = (), Error = Error> + Send> {
|
|
|
|
let params = parse_params(&req);
|
|
|
|
if let Some(id_string) = params.get("id") {
|
|
|
|
Box::new(match id_string[0].parse() {
|
2018-11-13 13:53:23 +03:00
|
|
|
Ok(id) => match api.cancel_tx(Some(id), None) {
|
2018-09-11 05:18:10 +03:00
|
|
|
Ok(_) => ok(()),
|
|
|
|
Err(e) => {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("cancel_tx: failed with error: {}", e);
|
2018-09-11 05:18:10 +03:00
|
|
|
err(e)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("cancel_tx: could not parse id: {}", e);
|
2018-09-11 05:18:10 +03:00
|
|
|
err(ErrorKind::TransactionCancellationError(
|
2018-10-10 12:11:01 +03:00
|
|
|
"cancel_tx: cannot cancel transaction. Could not parse id in request.",
|
2018-09-11 05:18:10 +03:00
|
|
|
).into())
|
|
|
|
}
|
|
|
|
})
|
2018-11-13 13:53:23 +03:00
|
|
|
} else if let Some(tx_id_string) = params.get("tx_id") {
|
|
|
|
Box::new(match tx_id_string[0].parse() {
|
|
|
|
Ok(tx_id) => match api.cancel_tx(None, Some(tx_id)) {
|
|
|
|
Ok(_) => ok(()),
|
|
|
|
Err(e) => {
|
|
|
|
error!("cancel_tx: failed with error: {}", e);
|
|
|
|
err(e)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
error!("cancel_tx: could not parse tx_id: {}", e);
|
|
|
|
err(ErrorKind::TransactionCancellationError(
|
|
|
|
"cancel_tx: cannot cancel transaction. Could not parse tx_id in request.",
|
|
|
|
).into())
|
|
|
|
}
|
|
|
|
})
|
2018-09-11 05:18:10 +03:00
|
|
|
} else {
|
|
|
|
Box::new(err(ErrorKind::TransactionCancellationError(
|
2018-11-13 13:53:23 +03:00
|
|
|
"cancel_tx: Cannot cancel transaction. Missing id or tx_id param in request.",
|
2018-09-11 05:18:10 +03:00
|
|
|
).into()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-29 15:49:00 +03:00
|
|
|
pub fn post_tx(
|
2018-10-21 23:26:35 +03:00
|
|
|
&self,
|
|
|
|
req: Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
api: APIOwner<T, C, K>,
|
2018-10-21 23:26:35 +03:00
|
|
|
) -> Box<Future<Item = (), Error = Error> + Send> {
|
|
|
|
let params = match req.uri().query() {
|
|
|
|
Some(query_string) => form_urlencoded::parse(query_string.as_bytes())
|
|
|
|
.into_owned()
|
|
|
|
.fold(HashMap::new(), |mut hm, (k, v)| {
|
|
|
|
hm.entry(k).or_insert(vec![]).push(v);
|
|
|
|
hm
|
|
|
|
}),
|
|
|
|
None => HashMap::new(),
|
|
|
|
};
|
|
|
|
let fluff = params.get("fluff").is_some();
|
2018-12-05 21:08:42 +03:00
|
|
|
Box::new(parse_body(req).and_then(
|
|
|
|
move |slate: Slate| match api.post_tx(&slate.tx, fluff) {
|
2018-10-21 23:26:35 +03:00
|
|
|
Ok(_) => ok(()),
|
|
|
|
Err(e) => {
|
2018-10-22 00:56:42 +03:00
|
|
|
error!("post_tx: failed with error: {}", e);
|
2018-10-21 23:26:35 +03:00
|
|
|
err(e)
|
|
|
|
}
|
2018-12-05 21:08:42 +03:00
|
|
|
},
|
|
|
|
))
|
2018-10-21 23:26:35 +03:00
|
|
|
}
|
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
|
|
|
let api = APIOwner::new(self.wallet.clone());
|
2018-08-17 14:15:06 +03:00
|
|
|
match req
|
|
|
|
.uri()
|
2018-08-01 12:44:07 +03:00
|
|
|
.path()
|
|
|
|
.trim_right_matches("/")
|
|
|
|
.rsplit("/")
|
|
|
|
.next()
|
|
|
|
.unwrap()
|
|
|
|
{
|
|
|
|
"issue_send_tx" => Box::new(
|
|
|
|
self.issue_send_tx(req, api)
|
|
|
|
.and_then(|slate| ok(json_response_pretty(&slate))),
|
|
|
|
),
|
2018-09-11 05:18:10 +03:00
|
|
|
"finalize_tx" => Box::new(
|
|
|
|
self.finalize_tx(req, api)
|
|
|
|
.and_then(|slate| ok(json_response_pretty(&slate))),
|
|
|
|
),
|
|
|
|
"cancel_tx" => Box::new(
|
|
|
|
self.cancel_tx(req, api)
|
|
|
|
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
|
|
|
),
|
2018-10-21 23:26:35 +03:00
|
|
|
"post_tx" => Box::new(
|
|
|
|
self.post_tx(req, api)
|
|
|
|
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
2018-08-01 12:44:07 +03:00
|
|
|
),
|
|
|
|
_ => Box::new(err(ErrorKind::GenericError(
|
2018-07-12 18:49:37 +03:00
|
|
|
"Unknown error handling post request".to_owned(),
|
2018-08-01 12:44:07 +03:00
|
|
|
).into())),
|
2018-06-13 23:58:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-19 22:47:40 +03:00
|
|
|
impl<T: ?Sized, C, K> Handler for OwnerAPIHandler<T, C, K>
|
2018-06-13 23:58:45 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
C: NodeClient + 'static,
|
2018-06-13 23:58:45 +03:00
|
|
|
K: Keychain + 'static,
|
|
|
|
{
|
2018-08-01 12:44:07 +03:00
|
|
|
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
|
|
|
match self.handle_get_request(&req) {
|
|
|
|
Ok(r) => Box::new(ok(r)),
|
2018-06-13 23:58:45 +03:00
|
|
|
Err(e) => {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("Request Error: {:?}", e);
|
2018-08-01 12:44:07 +03:00
|
|
|
Box::new(ok(create_error_response(e)))
|
2018-06-13 23:58:45 +03:00
|
|
|
}
|
2018-08-01 12:44:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
|
|
|
Box::new(
|
|
|
|
self.handle_post_request(req)
|
|
|
|
.and_then(|r| ok(r))
|
|
|
|
.or_else(|e| {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("Request Error: {:?}", e);
|
2018-08-01 12:44:07 +03:00
|
|
|
ok(create_error_response(e))
|
|
|
|
}),
|
|
|
|
)
|
2018-06-13 23:58:45 +03:00
|
|
|
}
|
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
fn options(&self, _req: Request<Body>) -> ResponseFuture {
|
|
|
|
Box::new(ok(create_ok_response("{}")))
|
2018-06-13 23:58:45 +03:00
|
|
|
}
|
|
|
|
}
|
2018-08-01 12:44:07 +03:00
|
|
|
|
2018-06-06 17:36:29 +03:00
|
|
|
/// API Handler/Wrapper for foreign functions
|
|
|
|
|
2018-11-19 22:47:40 +03:00
|
|
|
pub struct ForeignAPIHandler<T: ?Sized, C, K>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
C: NodeClient + 'static,
|
2018-08-01 12:44:07 +03:00
|
|
|
K: Keychain + 'static,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
|
|
|
/// Wallet instance
|
2018-11-11 14:56:42 +03:00
|
|
|
pub wallet: Arc<Mutex<T>>,
|
2018-06-08 08:21:54 +03:00
|
|
|
phantom: PhantomData<K>,
|
2018-07-10 11:18:24 +03:00
|
|
|
phantom_c: PhantomData<C>,
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-11-19 22:47:40 +03:00
|
|
|
impl<T: ?Sized, C, K> ForeignAPIHandler<T, C, K>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
C: NodeClient + 'static,
|
2018-08-01 12:44:07 +03:00
|
|
|
K: Keychain + 'static,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
2018-06-27 18:57:40 +03:00
|
|
|
/// create a new api handler
|
2018-11-19 22:47:40 +03:00
|
|
|
pub fn new(wallet: Arc<Mutex<T>>) -> ForeignAPIHandler<T, C, K> {
|
2018-06-08 08:21:54 +03:00
|
|
|
ForeignAPIHandler {
|
|
|
|
wallet,
|
|
|
|
phantom: PhantomData,
|
2018-07-10 11:18:24 +03:00
|
|
|
phantom_c: PhantomData,
|
2018-06-08 08:21:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_coinbase(
|
|
|
|
&self,
|
2018-08-01 12:44:07 +03:00
|
|
|
req: Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
mut api: APIForeign<T, C, K>,
|
2018-08-01 12:44:07 +03:00
|
|
|
) -> Box<Future<Item = CbData, Error = Error> + Send> {
|
|
|
|
Box::new(parse_body(req).and_then(move |block_fees| api.build_coinbase(&block_fees)))
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
fn receive_tx(
|
|
|
|
&self,
|
|
|
|
req: Request<Body>,
|
2018-11-19 22:47:40 +03:00
|
|
|
mut api: APIForeign<T, C, K>,
|
2018-08-01 12:44:07 +03:00
|
|
|
) -> Box<Future<Item = Slate, Error = Error> + Send> {
|
2018-11-19 22:47:40 +03:00
|
|
|
Box::new(parse_body(req).and_then(
|
2018-11-29 15:49:00 +03:00
|
|
|
//TODO: No way to insert a message from the params
|
|
|
|
move |mut slate| match api.receive_tx(&mut slate, None, None) {
|
2018-08-01 12:44:07 +03:00
|
|
|
Ok(_) => ok(slate.clone()),
|
2018-08-20 16:48:05 +03:00
|
|
|
Err(e) => {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("receive_tx: failed with error: {}", e);
|
2018-08-20 16:48:05 +03:00
|
|
|
err(e)
|
|
|
|
}
|
2018-11-19 22:47:40 +03:00
|
|
|
},
|
|
|
|
))
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
fn handle_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
|
|
|
let api = *APIForeign::new(self.wallet.clone());
|
2018-08-17 14:15:06 +03:00
|
|
|
match req
|
|
|
|
.uri()
|
2018-08-01 12:44:07 +03:00
|
|
|
.path()
|
|
|
|
.trim_right_matches("/")
|
|
|
|
.rsplit("/")
|
|
|
|
.next()
|
|
|
|
.unwrap()
|
|
|
|
{
|
|
|
|
"build_coinbase" => Box::new(
|
|
|
|
self.build_coinbase(req, api)
|
|
|
|
.and_then(|res| ok(json_response(&res))),
|
|
|
|
),
|
|
|
|
"receive_tx" => Box::new(
|
|
|
|
self.receive_tx(req, api)
|
|
|
|
.and_then(|res| ok(json_response(&res))),
|
|
|
|
),
|
|
|
|
_ => Box::new(ok(response(StatusCode::BAD_REQUEST, "unknown action"))),
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-19 22:47:40 +03:00
|
|
|
impl<T: ?Sized, C, K> Handler for ForeignAPIHandler<T, C, K>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
2018-11-19 22:47:40 +03:00
|
|
|
T: WalletBackend<C, K> + Send + Sync + 'static,
|
|
|
|
C: NodeClient + Send + Sync + 'static,
|
2018-06-08 08:21:54 +03:00
|
|
|
K: Keychain + 'static,
|
2018-06-06 17:36:29 +03:00
|
|
|
{
|
2018-08-01 12:44:07 +03:00
|
|
|
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
|
|
|
Box::new(self.handle_request(req).and_then(|r| ok(r)).or_else(|e| {
|
2018-10-21 23:30:56 +03:00
|
|
|
error!("Request Error: {:?}", e);
|
2018-08-01 12:44:07 +03:00
|
|
|
ok(create_error_response(e))
|
|
|
|
}))
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-01 12:44:07 +03:00
|
|
|
// Utility to serialize a struct into JSON and produce a sensible Response
|
2018-06-06 17:36:29 +03:00
|
|
|
// out of it.
|
2018-08-01 12:44:07 +03:00
|
|
|
fn json_response<T>(s: &T) -> Response<Body>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
|
|
|
T: Serialize,
|
|
|
|
{
|
|
|
|
match serde_json::to_string(s) {
|
2018-08-01 12:44:07 +03:00
|
|
|
Ok(json) => response(StatusCode::OK, json),
|
|
|
|
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// pretty-printed version of above
|
2018-08-01 12:44:07 +03:00
|
|
|
fn json_response_pretty<T>(s: &T) -> Response<Body>
|
2018-06-06 17:36:29 +03:00
|
|
|
where
|
|
|
|
T: Serialize,
|
|
|
|
{
|
|
|
|
match serde_json::to_string_pretty(s) {
|
2018-08-01 12:44:07 +03:00
|
|
|
Ok(json) => response(StatusCode::OK, json),
|
|
|
|
Err(_) => response(StatusCode::INTERNAL_SERVER_ERROR, ""),
|
2018-06-06 17:36:29 +03:00
|
|
|
}
|
|
|
|
}
|
2018-08-01 12:44:07 +03:00
|
|
|
|
|
|
|
fn create_error_response(e: Error) -> Response<Body> {
|
|
|
|
Response::builder()
|
|
|
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
|
|
.header("access-control-allow-origin", "*")
|
|
|
|
.header("access-control-allow-headers", "Content-Type")
|
2018-09-19 01:11:58 +03:00
|
|
|
.body(format!("{}", e).into())
|
2018-08-01 12:44:07 +03:00
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_ok_response(json: &str) -> Response<Body> {
|
|
|
|
Response::builder()
|
|
|
|
.status(StatusCode::OK)
|
|
|
|
.header("access-control-allow-origin", "*")
|
|
|
|
.body(json.to_string().into())
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn response<T: Into<Body>>(status: StatusCode, text: T) -> Response<Body> {
|
|
|
|
Response::builder()
|
|
|
|
.status(status)
|
|
|
|
.header("access-control-allow-origin", "*")
|
|
|
|
.body(text.into())
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_params(req: &Request<Body>) -> HashMap<String, Vec<String>> {
|
|
|
|
match req.uri().query() {
|
|
|
|
Some(query_string) => form_urlencoded::parse(query_string.as_bytes())
|
|
|
|
.into_owned()
|
|
|
|
.fold(HashMap::new(), |mut hm, (k, v)| {
|
|
|
|
hm.entry(k).or_insert(vec![]).push(v);
|
|
|
|
hm
|
|
|
|
}),
|
|
|
|
None => HashMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_body<T>(req: Request<Body>) -> Box<Future<Item = T, Error = Error> + Send>
|
|
|
|
where
|
|
|
|
for<'de> T: Deserialize<'de> + Send + 'static,
|
|
|
|
{
|
|
|
|
Box::new(
|
|
|
|
req.into_body()
|
|
|
|
.concat2()
|
|
|
|
.map_err(|_| ErrorKind::GenericError("Failed to read request".to_owned()).into())
|
|
|
|
.and_then(|body| match serde_json::from_reader(&body.to_vec()[..]) {
|
|
|
|
Ok(obj) => ok(obj),
|
2018-09-19 01:11:58 +03:00
|
|
|
Err(e) => {
|
|
|
|
err(ErrorKind::GenericError(format!("Invalid request body: {}", e)).into())
|
|
|
|
}
|
2018-08-01 12:44:07 +03:00
|
|
|
}),
|
|
|
|
)
|
|
|
|
}
|