mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
Merge 2.0.0 branch into master (#164)
* create 2.0.0 branch * V0/V1 Slate Removal + Grin Header Version Bump (#140) * Remove V0 and V1 Slates from Wallet * rustfmt * V1 API + V2 wallet to wallet impl (#144) * remove v1 API * rustfmt * convert http adapter to use V2 api * rustfmt * V2 API conversion Pt2 - Check version (#146) * call check_version on wallet before http send * rustfmt * Support new Bulletproof rewind scheme (#122) * Restore with LegacyProofBuilder * Switch to ProofBuilder at HF block * Switch proof builder for coinbase outputs at hard fork * Use valid_header_version to switch proof builder * Fix compilation errors * Use legacy proof builder for AutomatedTesting chain type * Add macro to avoid duplicate code * Read version info from server, react accordingly (#154) * read and parse version, bump hf * rustfmt * add foreign api middleware check * rustfmt * add middleware checks * rustfmt * add check for incoming pre-hf slates * api tests * Add double rewind period (#155) * Add double rewind period * Simplify restore * Fix comment * bump imported version for beta release * version bump for next (potential) release * Provide more specific error when other wallet is outdated (#162) * add appropriate error message when other wallet is out of date * rustfmt * missing cargo.lock
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grin_wallet_controller"
|
||||
version = "1.1.1-beta.1"
|
||||
version = "2.0.0-beta.2"
|
||||
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
|
||||
description = "Controllers for grin wallet instantiation"
|
||||
license = "Apache-2.0"
|
||||
@@ -32,9 +32,9 @@ chrono = { version = "0.4.4", features = ["serde"] }
|
||||
easy-jsonrpc = "0.5.1"
|
||||
lazy_static = "1"
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "1.1.1-beta.1" }
|
||||
grin_wallet_util = { path = "../util", version = "2.0.0-beta.2" }
|
||||
|
||||
grin_wallet_api = { path = "../api", version = "1.1.1-beta.1" }
|
||||
grin_wallet_impls = { path = "../impls", version = "1.1.1-beta.1" }
|
||||
grin_wallet_libwallet = { path = "../libwallet", version = "1.1.1-beta.1" }
|
||||
grin_wallet_config = { path = "../config", version = "1.1.1-beta.1" }
|
||||
grin_wallet_api = { path = "../api", version = "2.0.0-beta.2" }
|
||||
grin_wallet_impls = { path = "../impls", version = "2.0.0-beta.2" }
|
||||
grin_wallet_libwallet = { path = "../libwallet", version = "2.0.0-beta.2" }
|
||||
grin_wallet_config = { path = "../config", version = "2.0.0-beta.2" }
|
||||
|
||||
+41
-630
@@ -14,15 +14,11 @@
|
||||
|
||||
//! Controller for wallet.. instantiates and handles listeners (or single-run
|
||||
//! invocations) as needed.
|
||||
//! Still experimental
|
||||
use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig};
|
||||
use crate::core::core;
|
||||
use crate::core::core::Transaction;
|
||||
use crate::impls::{FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter};
|
||||
use crate::keychain::Keychain;
|
||||
use crate::libwallet::{
|
||||
CbData, Error, ErrorKind, InitTxArgs, NodeClient, OutputCommitMapping, SendTXArgs, Slate,
|
||||
TxLogEntry, WalletBackend, WalletInfo,
|
||||
Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, WalletBackend, CURRENT_SLATE_VERSION,
|
||||
GRIN_BLOCK_HEADER_VERSION,
|
||||
};
|
||||
use crate::util::to_base64;
|
||||
use crate::util::Mutex;
|
||||
@@ -33,14 +29,11 @@ use hyper::header::HeaderValue;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use url::form_urlencoded;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::apiwallet::{Foreign, ForeignRpc, Owner, OwnerRpc};
|
||||
use crate::apiwallet::{Foreign, ForeignCheckMiddlewareFn, ForeignRpc, Owner, OwnerRpc};
|
||||
use easy_jsonrpc;
|
||||
use easy_jsonrpc::{Handler, MaybeReply};
|
||||
|
||||
@@ -49,6 +42,36 @@ lazy_static! {
|
||||
HeaderValue::from_str("Basic realm=GrinOwnerAPI").unwrap();
|
||||
}
|
||||
|
||||
fn check_middleware(
|
||||
name: ForeignCheckMiddlewareFn,
|
||||
node_version_info: Option<NodeVersionInfo>,
|
||||
slate: Option<&Slate>,
|
||||
) -> Result<(), Error> {
|
||||
match name {
|
||||
// allow coinbases to be built regardless
|
||||
ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()),
|
||||
_ => {
|
||||
let mut bhv = 1;
|
||||
if let Some(n) = node_version_info {
|
||||
bhv = n.block_header_version;
|
||||
}
|
||||
if let Some(s) = slate {
|
||||
if s.version_info.version < CURRENT_SLATE_VERSION
|
||||
|| (bhv == 1 && s.version_info.block_header_version != 1)
|
||||
|| (bhv > 1 && s.version_info.block_header_version < GRIN_BLOCK_HEADER_VERSION)
|
||||
{
|
||||
Err(ErrorKind::Compatibility(
|
||||
"Incoming Slate is not compatible with this wallet. \
|
||||
Please upgrade the node or use a different one."
|
||||
.into(),
|
||||
))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiate wallet Owner API for a single-use (command line) call
|
||||
/// Return a function containing a loaded API context to call
|
||||
pub fn owner_single_use<F, T: ?Sized, C, K>(wallet: Arc<Mutex<T>>, f: F) -> Result<(), Error>
|
||||
@@ -71,7 +94,7 @@ where
|
||||
C: NodeClient,
|
||||
K: Keychain,
|
||||
{
|
||||
f(&mut Foreign::new(wallet.clone()))?;
|
||||
f(&mut Foreign::new(wallet.clone(), Some(check_middleware)))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -86,11 +109,9 @@ pub fn owner_listener<T: ?Sized, C, K>(
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
OwnerAPIHandler<T, C, K>: api::Handler,
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
let api_handler = OwnerAPIHandler::new(wallet.clone());
|
||||
let api_handler_v2 = OwnerAPIHandlerV2::new(wallet.clone());
|
||||
|
||||
let mut router = Router::new();
|
||||
@@ -104,10 +125,6 @@ where
|
||||
router.add_middleware(basic_auth_middleware);
|
||||
}
|
||||
|
||||
router
|
||||
.add_route("/v1/wallet/owner/**", Arc::new(api_handler))
|
||||
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||
|
||||
router
|
||||
.add_route("/v2/owner", Arc::new(api_handler_v2))
|
||||
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||
@@ -115,11 +132,6 @@ where
|
||||
// If so configured, add the foreign API to the same port
|
||||
if owner_api_include_foreign.unwrap_or(false) {
|
||||
info!("Starting HTTP Foreign API on Owner server at {}.", addr);
|
||||
let foreign_api_handler = ForeignAPIHandler::new(wallet.clone());
|
||||
router
|
||||
.add_route("/v1/wallet/foreign/**", Arc::new(foreign_api_handler))
|
||||
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||
|
||||
let foreign_api_handler_v2 = ForeignAPIHandlerV2::new(wallet.clone());
|
||||
router
|
||||
.add_route("/v2/foreign", Arc::new(foreign_api_handler_v2))
|
||||
@@ -151,13 +163,9 @@ where
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
let api_handler = ForeignAPIHandler::new(wallet.clone());
|
||||
let api_handler_v2 = ForeignAPIHandlerV2::new(wallet);
|
||||
|
||||
let mut router = Router::new();
|
||||
router
|
||||
.add_route("/v1/wallet/foreign/**", Arc::new(api_handler))
|
||||
.map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?;
|
||||
|
||||
router
|
||||
.add_route("/v2/foreign", Arc::new(api_handler_v2))
|
||||
@@ -180,475 +188,6 @@ where
|
||||
|
||||
type WalletResponseFuture = Box<dyn Future<Item = Response<Body>, Error = Error> + Send>;
|
||||
|
||||
/// API Handler/Wrapper for owner functions
|
||||
pub struct OwnerAPIHandler<T: ?Sized, C, K>
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
/// Wallet instance
|
||||
pub wallet: Arc<Mutex<T>>,
|
||||
phantom: PhantomData<K>,
|
||||
phantom_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized, C, K> OwnerAPIHandler<T, C, K>
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
/// Create a new owner API handler for GET methods
|
||||
pub fn new(wallet: Arc<Mutex<T>>) -> OwnerAPIHandler<T, C, K> {
|
||||
OwnerAPIHandler {
|
||||
wallet,
|
||||
phantom: PhantomData,
|
||||
phantom_c: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retrieve_outputs(
|
||||
&self,
|
||||
req: &Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Result<(bool, Vec<OutputCommitMapping>), Error> {
|
||||
let mut update_from_node = false;
|
||||
let mut id = None;
|
||||
let mut show_spent = false;
|
||||
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") {
|
||||
if let Some(x) = ids.first() {
|
||||
id = Some(x.parse().unwrap());
|
||||
}
|
||||
}
|
||||
api.retrieve_outputs(show_spent, update_from_node, id)
|
||||
}
|
||||
|
||||
pub fn retrieve_txs(
|
||||
&self,
|
||||
req: &Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Result<(bool, Vec<TxLogEntry>), Error> {
|
||||
let mut tx_id = None;
|
||||
let mut tx_slate_id = None;
|
||||
let mut update_from_node = false;
|
||||
|
||||
let params = parse_params(req);
|
||||
|
||||
if let Some(_) = params.get("refresh") {
|
||||
update_from_node = true;
|
||||
}
|
||||
if let Some(ids) = params.get("id") {
|
||||
if let Some(x) = ids.first() {
|
||||
tx_id = Some(x.parse().unwrap());
|
||||
}
|
||||
}
|
||||
if let Some(tx_slate_ids) = params.get("tx_id") {
|
||||
if let Some(x) = tx_slate_ids.first() {
|
||||
tx_slate_id = Some(x.parse().unwrap());
|
||||
}
|
||||
}
|
||||
api.retrieve_txs(update_from_node, tx_id, tx_slate_id)
|
||||
}
|
||||
|
||||
pub fn retrieve_stored_tx(
|
||||
&self,
|
||||
req: &Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Result<(bool, Option<Transaction>), Error> {
|
||||
let params = parse_params(req);
|
||||
if let Some(id_string) = params.get("id") {
|
||||
match id_string[0].parse() {
|
||||
Ok(id) => match api.retrieve_txs(true, Some(id), None) {
|
||||
Ok((_, txs)) => {
|
||||
let stored_tx = api.get_stored_tx(&txs[0])?;
|
||||
Ok((txs[0].confirmed, stored_tx))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("retrieve_stored_tx: failed with error: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("retrieve_stored_tx: could not parse id: {}", e);
|
||||
Err(ErrorKind::TransactionDumpError(
|
||||
"retrieve_stored_tx: cannot dump transaction. Could not parse id in request.",
|
||||
).into())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ErrorKind::TransactionDumpError(
|
||||
"retrieve_stored_tx: Cannot retrieve transaction. Missing id param in request.",
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retrieve_summary_info(
|
||||
&self,
|
||||
req: &Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Result<(bool, WalletInfo), Error> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn node_height(
|
||||
&self,
|
||||
_req: &Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Result<(u64, bool), Error> {
|
||||
let res = api.node_height()?;
|
||||
Ok((res.height, res.updated_from_node))
|
||||
}
|
||||
|
||||
fn handle_get_request(&self, req: &Request<Body>) -> Result<Response<Body>, Error> {
|
||||
let api = Owner::new(self.wallet.clone());
|
||||
|
||||
Ok(
|
||||
match req
|
||||
.uri()
|
||||
.path()
|
||||
.trim_end_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)?),
|
||||
"retrieve_stored_tx" => json_response(&self.retrieve_stored_tx(req, api)?),
|
||||
_ => response(StatusCode::BAD_REQUEST, ""),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn issue_send_tx(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Box<dyn Future<Item = Slate, Error = Error> + Send> {
|
||||
Box::new(parse_body(req).and_then(move |args: SendTXArgs| {
|
||||
let init_args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
amount: args.amount,
|
||||
minimum_confirmations: args.minimum_confirmations,
|
||||
max_outputs: args.max_outputs as u32,
|
||||
num_change_outputs: args.num_change_outputs as u32,
|
||||
selection_strategy_is_use_all: args.selection_strategy_is_use_all,
|
||||
message: args.message.clone(),
|
||||
target_slate_version: args.target_slate_version,
|
||||
send_args: None,
|
||||
..Default::default()
|
||||
};
|
||||
let result = api.init_send_tx(init_args);
|
||||
let mut slate = 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::Fee { .. } => {}
|
||||
_ => {
|
||||
// otherwise give full dump
|
||||
error!("Backtrace: {}", e.backtrace().unwrap());
|
||||
}
|
||||
};
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
match args.method.as_ref() {
|
||||
"http" => slate = HTTPWalletCommAdapter::new().send_tx_sync(&args.dest, &slate)?,
|
||||
"file" => {
|
||||
FileWalletCommAdapter::new().send_tx_async(&args.dest, &slate)?;
|
||||
}
|
||||
"keybase" => {
|
||||
//TODO: in case of keybase, the response might take 60s and leave the service hanging
|
||||
slate = KeybaseWalletCommAdapter::new().send_tx_sync(&args.dest, &slate)?;
|
||||
}
|
||||
_ => {
|
||||
error!("unsupported payment method: {}", args.method);
|
||||
return Err(ErrorKind::ClientCallback(
|
||||
"unsupported payment method".to_owned(),
|
||||
))?;
|
||||
}
|
||||
}
|
||||
api.tx_lock_outputs(&slate, 0)?;
|
||||
if args.method != "file" {
|
||||
slate = api.finalize_tx(&slate)?;
|
||||
}
|
||||
Ok(slate)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn finalize_tx(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Box<dyn Future<Item = Slate, Error = Error> + Send> {
|
||||
Box::new(
|
||||
parse_body(req).and_then(move |slate| match api.finalize_tx(&slate) {
|
||||
Ok(s) => ok(s.clone()),
|
||||
Err(e) => {
|
||||
error!("finalize_tx: failed with error: {}", e);
|
||||
err(e)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn cancel_tx(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Box<dyn 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() {
|
||||
Ok(id) => match api.cancel_tx(Some(id), None) {
|
||||
Ok(_) => ok(()),
|
||||
Err(e) => {
|
||||
error!("cancel_tx: failed with error: {}", e);
|
||||
err(e)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("cancel_tx: could not parse id: {}", e);
|
||||
err(ErrorKind::TransactionCancellationError(
|
||||
"cancel_tx: cannot cancel transaction. Could not parse id in request.",
|
||||
)
|
||||
.into())
|
||||
}
|
||||
})
|
||||
} 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())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Box::new(err(ErrorKind::TransactionCancellationError(
|
||||
"cancel_tx: Cannot cancel transaction. Missing id or tx_id param in request.",
|
||||
)
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn post_tx(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Box<dyn 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();
|
||||
Box::new(parse_body(req).and_then(
|
||||
move |slate: Slate| match api.post_tx(&slate.tx, fluff) {
|
||||
Ok(_) => ok(()),
|
||||
Err(e) => {
|
||||
error!("post_tx: failed with error: {}", e);
|
||||
err(e)
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn repost(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
api: Owner<T, C, K>,
|
||||
) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
||||
let params = parse_params(&req);
|
||||
let mut id_int: Option<u32> = None;
|
||||
let mut tx_uuid: Option<Uuid> = None;
|
||||
|
||||
if let Some(id_string) = params.get("id") {
|
||||
match id_string[0].parse() {
|
||||
Ok(id) => id_int = Some(id),
|
||||
Err(e) => {
|
||||
error!("repost: could not parse id: {}", e);
|
||||
return Box::new(err(ErrorKind::GenericError(
|
||||
"repost: cannot repost transaction. Could not parse id in request."
|
||||
.to_owned(),
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
}
|
||||
} else if let Some(tx_id_string) = params.get("tx_id") {
|
||||
match tx_id_string[0].parse() {
|
||||
Ok(tx_id) => tx_uuid = Some(tx_id),
|
||||
Err(e) => {
|
||||
error!("repost: could not parse tx_id: {}", e);
|
||||
return Box::new(err(ErrorKind::GenericError(
|
||||
"repost: cannot repost transaction. Could not parse tx_id in request."
|
||||
.to_owned(),
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Box::new(err(ErrorKind::GenericError(
|
||||
"repost: Cannot repost transaction. Missing id or tx_id param in request."
|
||||
.to_owned(),
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
|
||||
let res = api.retrieve_txs(true, id_int, tx_uuid);
|
||||
if let Err(e) = res {
|
||||
return Box::new(err(ErrorKind::GenericError(format!(
|
||||
"repost: cannot repost transaction. retrieve_txs failed, err: {:?}",
|
||||
e
|
||||
))
|
||||
.into()));
|
||||
}
|
||||
let (_, txs) = res.unwrap();
|
||||
let res = api.get_stored_tx(&txs[0]);
|
||||
if let Err(e) = res {
|
||||
return Box::new(err(ErrorKind::GenericError(format!(
|
||||
"repost: cannot repost transaction. get_stored_tx failed, err: {:?}",
|
||||
e
|
||||
))
|
||||
.into()));
|
||||
}
|
||||
let stored_tx = res.unwrap();
|
||||
if stored_tx.is_none() {
|
||||
error!(
|
||||
"Transaction with id {:?}/{:?} does not have transaction data. Not reposting.",
|
||||
id_int, tx_uuid,
|
||||
);
|
||||
return Box::new(err(ErrorKind::GenericError(
|
||||
"repost: Cannot repost transaction. Missing id or tx_id param in request."
|
||||
.to_owned(),
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
|
||||
let fluff = params.get("fluff").is_some();
|
||||
Box::new(match api.post_tx(&stored_tx.unwrap(), fluff) {
|
||||
Ok(_) => ok(()),
|
||||
Err(e) => {
|
||||
error!("repost: failed with error: {}", e);
|
||||
err(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
||||
let api = Owner::new(self.wallet.clone());
|
||||
match req
|
||||
.uri()
|
||||
.path()
|
||||
.trim_end_matches("/")
|
||||
.rsplit("/")
|
||||
.next()
|
||||
.unwrap()
|
||||
{
|
||||
"issue_send_tx" => Box::new(
|
||||
self.issue_send_tx(req, api)
|
||||
.and_then(|slate| ok(json_response_pretty(&slate))),
|
||||
),
|
||||
"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, "{}"))),
|
||||
),
|
||||
"post_tx" => Box::new(
|
||||
self.post_tx(req, api)
|
||||
.and_then(|_| ok(response(StatusCode::OK, "{}"))),
|
||||
),
|
||||
"repost" => Box::new(
|
||||
self.repost(req, api)
|
||||
.and_then(|_| ok(response(StatusCode::OK, ""))),
|
||||
),
|
||||
_ => Box::new(err(ErrorKind::GenericError(
|
||||
"Unknown error handling post request".to_owned(),
|
||||
)
|
||||
.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, C, K> api::Handler for OwnerAPIHandler<T, C, K>
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
fn get(&self, req: Request<Body>) -> ResponseFuture {
|
||||
match self.handle_get_request(&req) {
|
||||
Ok(r) => Box::new(ok(r)),
|
||||
Err(e) => {
|
||||
error!("Request Error: {:?}", e);
|
||||
Box::new(ok(create_error_response(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
||||
Box::new(
|
||||
self.handle_post_request(req)
|
||||
.and_then(|r| ok(r))
|
||||
.or_else(|e| {
|
||||
error!("Request Error: {:?}", e);
|
||||
ok(create_error_response(e))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn options(&self, _req: Request<Body>) -> ResponseFuture {
|
||||
Box::new(ok(create_ok_response("{}")))
|
||||
}
|
||||
}
|
||||
|
||||
/// V2 API Handler/Wrapper for owner functions
|
||||
pub struct OwnerAPIHandlerV2<T: ?Sized, C, K>
|
||||
where
|
||||
@@ -726,107 +265,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// API Handler/Wrapper for foreign functions
|
||||
pub struct ForeignAPIHandler<T: ?Sized, C, K>
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
/// Wallet instance
|
||||
pub wallet: Arc<Mutex<T>>,
|
||||
phantom: PhantomData<K>,
|
||||
phantom_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized, C, K> ForeignAPIHandler<T, C, K>
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
/// create a new api handler
|
||||
pub fn new(wallet: Arc<Mutex<T>>) -> ForeignAPIHandler<T, C, K> {
|
||||
ForeignAPIHandler {
|
||||
wallet,
|
||||
phantom: PhantomData,
|
||||
phantom_c: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_coinbase(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
api: Foreign<T, C, K>,
|
||||
) -> Box<dyn Future<Item = CbData, Error = Error> + Send> {
|
||||
Box::new(parse_body(req).and_then(move |block_fees| api.build_coinbase(&block_fees)))
|
||||
}
|
||||
|
||||
fn receive_tx(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
api: Foreign<T, C, K>,
|
||||
) -> Box<dyn Future<Item = Slate, Error = Error> + Send> {
|
||||
Box::new(parse_body(req).and_then(
|
||||
//TODO: No way to insert a message from the params
|
||||
move |slate_str: String| {
|
||||
let slate: Slate = Slate::deserialize_upgrade(&slate_str).unwrap();
|
||||
if let Err(e) = api.verify_slate_messages(&slate) {
|
||||
error!("Error validating participant messages: {}", e);
|
||||
err(e)
|
||||
} else {
|
||||
match api.receive_tx(&slate, None, None) {
|
||||
Ok(s) => ok(s),
|
||||
Err(e) => {
|
||||
error!("receive_tx: failed with error: {}", e);
|
||||
err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
||||
let api = Foreign::new(self.wallet.clone());
|
||||
match req
|
||||
.uri()
|
||||
.path()
|
||||
.trim_end_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"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: ?Sized, C, K> api::Handler for ForeignAPIHandler<T, C, K>
|
||||
where
|
||||
T: WalletBackend<C, K> + Send + Sync + 'static,
|
||||
C: NodeClient + Send + Sync + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
fn post(&self, req: Request<Body>) -> ResponseFuture {
|
||||
Box::new(self.handle_request(req).and_then(|r| ok(r)).or_else(|e| {
|
||||
error!("Request Error: {:?}", e);
|
||||
ok(create_error_response(e))
|
||||
}))
|
||||
}
|
||||
|
||||
fn options(&self, _req: Request<Body>) -> ResponseFuture {
|
||||
Box::new(ok(create_ok_response("{}")))
|
||||
}
|
||||
}
|
||||
|
||||
/// V2 API Handler/Wrapper for foreign functions
|
||||
pub struct ForeignAPIHandlerV2<T: ?Sized, C, K>
|
||||
where
|
||||
@@ -874,7 +312,7 @@ where
|
||||
}
|
||||
|
||||
fn handle_post_request(&self, req: Request<Body>) -> WalletResponseFuture {
|
||||
let api = Foreign::new(self.wallet.clone());
|
||||
let api = Foreign::new(self.wallet.clone(), Some(check_middleware));
|
||||
Box::new(
|
||||
self.call_api(req, api)
|
||||
.and_then(|resp| ok(json_response_pretty(&resp))),
|
||||
@@ -903,9 +341,10 @@ where
|
||||
Box::new(ok(create_ok_response("{}")))
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to serialize a struct into JSON and produce a sensible Response
|
||||
// out of it.
|
||||
fn json_response<T>(s: &T) -> Response<Body>
|
||||
fn _json_response<T>(s: &T) -> Response<Body>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
@@ -973,18 +412,6 @@ fn response<T: Into<Body>>(status: StatusCode, text: T) -> Response<Body> {
|
||||
builder.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<dyn Future<Item = T, Error = Error> + Send>
|
||||
where
|
||||
for<'de> T: Deserialize<'de> + Send + 'static,
|
||||
@@ -993,26 +420,10 @@ where
|
||||
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),
|
||||
Err(_) => {
|
||||
// try to parse as string instead, for backwards compatibility
|
||||
let replaced_str = String::from_utf8(body.to_vec().clone())
|
||||
.unwrap()
|
||||
.replace("\"", "\\\"");
|
||||
let mut str_vec = replaced_str.as_bytes().to_vec();
|
||||
str_vec.push(0x22);
|
||||
str_vec.insert(0, 0x22);
|
||||
match serde_json::from_reader(&str_vec[..]) {
|
||||
Ok(obj) => ok(obj),
|
||||
Err(e) => err(ErrorKind::GenericError(format!(
|
||||
"Invalid request body: {}",
|
||||
e
|
||||
))
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
.and_then(|body| match serde_json::from_reader(&body.to_vec()[..]) {
|
||||
Ok(obj) => ok(obj),
|
||||
Err(e) => {
|
||||
err(ErrorKind::GenericError(format!("Invalid request body: {}", e)).into())
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user