From 4bbce85b75506cffc0f1d1758960a69020e2e928 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Mon, 1 Apr 2019 11:16:49 +0100 Subject: [PATCH] V2 API Enabling (#33) * node height return value and documentation * rustfmt * add parameter struct for initiate tx functions * rustfmt * change tx estimate to use InitTxArgs * rustfmt * transaction estimate * rustfmt * initiate tx args fixed * add send args to init * rustfmt * last owner api documentation * api 2 wiring * rustfmt * wiring in V2 JSON-RPC API * rustfmt * some documentation on endpoint * shorten endpoints --- Cargo.lock | 1 + api/src/foreign_rpc.rs | 5 +- api/src/owner_rpc.rs | 5 +- controller/Cargo.toml | 1 + controller/src/controller.rs | 183 +++++++++++++++++++++++++++++++++-- 5 files changed, 186 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7685daef..053558df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -806,6 +806,7 @@ name = "grin_wallet_controller" version = "1.1.0" dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "easy-jsonrpc 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index a8721b33..7e7e48e3 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -21,7 +21,10 @@ use crate::libwallet::ErrorKind; use crate::Foreign; use easy_jsonrpc; -/// Public definition used to generate jsonrpc api for Foreign. +/// Public definition used to generate Foreign jsonrpc api. +/// * When running `grin-wallet listen` with defaults, the V2 api is available at +/// `localhost:3415/v2/foreign` +/// * The endpoint only supports POST operations, with the json-rpc request as the body #[easy_jsonrpc::rpc] pub trait ForeignRpc { /** diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index 1c9cdc2b..6d9be88b 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -26,7 +26,10 @@ use crate::libwallet::ErrorKind; use crate::Owner; use easy_jsonrpc; -/// Public definition used to generate jsonrpc api for Owner. +/// Public definition used to generate Owner jsonrpc api. +/// * When running `grin-wallet listen` with defaults, the V2 api is available at +/// `localhost:3420/v2/owner` +/// * The endpoint only supports POST operations, with the json-rpc request as the body #[easy_jsonrpc::rpc] pub trait OwnerRpc { /** diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 947e8dfd..fc8ef1c7 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -30,6 +30,7 @@ tokio-retry = "0.1" uuid = { version = "0.7", features = ["serde", "v4"] } url = "1.7.0" chrono = { version = "0.4.4", features = ["serde"] } +easy-jsonrpc = "0.4.1" grin_wallet_util = { path = "../util", version = "1.1.0" } diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 1fff2e70..8f77bc47 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -15,8 +15,7 @@ //! Controller for wallet.. instantiates and handles listeners (or single-run //! invocations) as needed. //! Still experimental -use crate::api::{ApiServer, BasicAuthMiddleware, Handler, ResponseFuture, Router, TLSConfig}; -use crate::apiwallet::{Foreign, Owner}; +use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig}; use crate::core::core; use crate::core::core::Transaction; use crate::impls::{FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter}; @@ -42,6 +41,10 @@ use std::sync::Arc; use url::form_urlencoded; use uuid::Uuid; +use crate::apiwallet::{Foreign, ForeignRpc, Owner, OwnerRpc}; +use easy_jsonrpc; +use easy_jsonrpc::Handler; + /// 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(wallet: Arc>, f: F) -> Result<(), Error> @@ -79,11 +82,12 @@ pub fn owner_listener( ) -> Result<(), Error> where T: WalletBackend + Send + Sync + 'static, - OwnerAPIHandler: Handler, + OwnerAPIHandler: 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(); if api_secret.is_some() { @@ -93,10 +97,15 @@ where let basic_auth_middleware = Arc::new(BasicAuthMiddleware::new(api_basic_auth, basic_realm)); 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()))?; + // 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); @@ -104,6 +113,11 @@ where 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)) + .map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?; } let mut apis = ApiServer::new(); @@ -131,13 +145,18 @@ where C: NodeClient + 'static, K: Keychain + 'static, { - let api_handler = ForeignAPIHandler::new(wallet); + 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)) + .map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?; + let mut apis = ApiServer::new(); warn!("Starting HTTP Foreign listener API server at {}.", addr); let socket_addr: SocketAddr = addr.parse().expect("unable to parse socket address"); @@ -592,7 +611,7 @@ where } } -impl Handler for OwnerAPIHandler +impl api::Handler for OwnerAPIHandler where T: WalletBackend + Send + Sync + 'static, C: NodeClient + 'static, @@ -624,8 +643,83 @@ where } } -/// API Handler/Wrapper for foreign functions +/// V2 API Handler/Wrapper for owner functions +pub struct OwnerAPIHandlerV2 +where + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, + K: Keychain + 'static, +{ + /// Wallet instance + pub wallet: Arc>, + phantom: PhantomData, + phantom_c: PhantomData, +} +impl OwnerAPIHandlerV2 +where + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, + K: Keychain + 'static, +{ + /// Create a new owner API handler for GET methods + pub fn new(wallet: Arc>) -> OwnerAPIHandlerV2 { + OwnerAPIHandlerV2 { + wallet, + phantom: PhantomData, + phantom_c: PhantomData, + } + } + + fn call_api( + &self, + req: Request, + api: Owner, + ) -> Box + Send> { + Box::new(parse_body(req).and_then(move |val: serde_json::Value| { + let owner_api = &api as &dyn OwnerRpc; + match owner_api.handle_request(val) { + Some(r) => ok(r), + None => { + error!("OwnerAPI: call_api: failed with error"); + err(ErrorKind::GenericError(format!("OwnerAPI Call Failed")).into()) + } + } + })) + } + + fn handle_post_request(&self, req: Request) -> WalletResponseFuture { + let api = Owner::new(self.wallet.clone()); + Box::new( + self.call_api(req, api) + .and_then(|resp| ok(json_response_pretty(&resp))), + ) + } +} + +impl api::Handler for OwnerAPIHandlerV2 +where + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, + K: Keychain + 'static, +{ + fn post(&self, req: Request) -> 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) -> ResponseFuture { + Box::new(ok(create_ok_response("{}"))) + } +} + +/// API Handler/Wrapper for foreign functions pub struct ForeignAPIHandler where T: WalletBackend + Send + Sync + 'static, @@ -710,7 +804,7 @@ where } } } -impl Handler for ForeignAPIHandler +impl api::Handler for ForeignAPIHandler where T: WalletBackend + Send + Sync + 'static, C: NodeClient + Send + Sync + 'static, @@ -728,6 +822,81 @@ where } } +/// V2 API Handler/Wrapper for foreign functions +pub struct ForeignAPIHandlerV2 +where + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, + K: Keychain + 'static, +{ + /// Wallet instance + pub wallet: Arc>, + phantom: PhantomData, + phantom_c: PhantomData, +} + +impl ForeignAPIHandlerV2 +where + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, + K: Keychain + 'static, +{ + /// Create a new foreign API handler for GET methods + pub fn new(wallet: Arc>) -> ForeignAPIHandlerV2 { + ForeignAPIHandlerV2 { + wallet, + phantom: PhantomData, + phantom_c: PhantomData, + } + } + + fn call_api( + &self, + req: Request, + api: Foreign, + ) -> Box + Send> { + Box::new(parse_body(req).and_then(move |val: serde_json::Value| { + let foreign_api = &api as &dyn ForeignRpc; + match foreign_api.handle_request(val) { + Some(r) => ok(r), + None => { + error!("ForeignAPI: call_api: failed with error"); + err(ErrorKind::GenericError(format!("ForeignAPI Call Failed")).into()) + } + } + })) + } + + fn handle_post_request(&self, req: Request) -> WalletResponseFuture { + let api = Foreign::new(self.wallet.clone()); + Box::new( + self.call_api(req, api) + .and_then(|resp| ok(json_response_pretty(&resp))), + ) + } +} + +impl api::Handler for ForeignAPIHandlerV2 +where + T: WalletBackend + Send + Sync + 'static, + C: NodeClient + 'static, + K: Keychain + 'static, +{ + fn post(&self, req: Request) -> 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) -> ResponseFuture { + Box::new(ok(create_ok_response("{}"))) + } +} // Utility to serialize a struct into JSON and produce a sensible Response // out of it. fn json_response(s: &T) -> Response