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:
+20
-17
@@ -17,7 +17,7 @@
|
||||
use hyper;
|
||||
use hyper::client::Response;
|
||||
use hyper::status::{StatusClass, StatusCode};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
|
||||
use rest::Error;
|
||||
@@ -26,12 +26,14 @@ use rest::Error;
|
||||
/// returns a JSON object. Handles request building, JSON deserialization and
|
||||
/// response code checking.
|
||||
pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
|
||||
where for<'de> T: Deserialize<'de>
|
||||
where
|
||||
for<'de> T: Deserialize<'de>,
|
||||
{
|
||||
let client = hyper::Client::new();
|
||||
let res = check_error(client.get(url).send())?;
|
||||
serde_json::from_reader(res)
|
||||
.map_err(|e| Error::Internal(format!("Server returned invalid JSON: {}", e)))
|
||||
serde_json::from_reader(res).map_err(|e| {
|
||||
Error::Internal(format!("Server returned invalid JSON: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to easily issue a HTTP POST request with the provided JSON
|
||||
@@ -39,15 +41,18 @@ pub fn get<'a, T>(url: &'a str) -> Result<T, Error>
|
||||
/// building, JSON serialization and deserialization, and response code
|
||||
/// checking.
|
||||
pub fn post<'a, IN, OUT>(url: &'a str, input: &IN) -> Result<OUT, Error>
|
||||
where IN: Serialize,
|
||||
for<'de> OUT: Deserialize<'de>
|
||||
where
|
||||
IN: Serialize,
|
||||
for<'de> OUT: Deserialize<'de>,
|
||||
{
|
||||
let in_json = serde_json::to_string(input)
|
||||
.map_err(|e| Error::Internal(format!("Could not serialize data to JSON: {}", e)))?;
|
||||
let in_json = serde_json::to_string(input).map_err(|e| {
|
||||
Error::Internal(format!("Could not serialize data to JSON: {}", e))
|
||||
})?;
|
||||
let client = hyper::Client::new();
|
||||
let res = check_error(client.post(url).body(&mut in_json.as_bytes()).send())?;
|
||||
serde_json::from_reader(res)
|
||||
.map_err(|e| Error::Internal(format!("Server returned invalid JSON: {}", e)))
|
||||
serde_json::from_reader(res).map_err(|e| {
|
||||
Error::Internal(format!("Server returned invalid JSON: {}", e))
|
||||
})
|
||||
}
|
||||
|
||||
// convert hyper error and check for non success response codes
|
||||
@@ -59,13 +64,11 @@ fn check_error(res: hyper::Result<Response>) -> Result<Response, Error> {
|
||||
match response.status.class() {
|
||||
StatusClass::Success => Ok(response),
|
||||
StatusClass::ServerError => Err(Error::Internal(format!("Server error."))),
|
||||
StatusClass::ClientError => {
|
||||
if response.status == StatusCode::NotFound {
|
||||
Err(Error::NotFound)
|
||||
} else {
|
||||
Err(Error::Argument(format!("Argument error")))
|
||||
}
|
||||
}
|
||||
StatusClass::ClientError => if response.status == StatusCode::NotFound {
|
||||
Err(Error::NotFound)
|
||||
} else {
|
||||
Err(Error::Argument(format!("Argument error")))
|
||||
},
|
||||
_ => Err(Error::Internal(format!("Unrecognized error."))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use chain;
|
||||
use core::core::Transaction;
|
||||
use core::ser;
|
||||
use pool;
|
||||
use handlers::{UtxoHandler, ChainHandler, SumTreeHandler};
|
||||
use rest::*;
|
||||
use types::*;
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
|
||||
/// ApiEndpoint implementation for the transaction pool, to check its status
|
||||
/// and size as well as push new transactions.
|
||||
#[derive(Clone)]
|
||||
pub struct PoolApi<T> {
|
||||
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
|
||||
}
|
||||
|
||||
impl<T> ApiEndpoint for PoolApi<T>
|
||||
where
|
||||
T: pool::BlockChain + Clone + Send + Sync + 'static,
|
||||
{
|
||||
type ID = String;
|
||||
type T = PoolInfo;
|
||||
type OP_IN = TxWrapper;
|
||||
type OP_OUT = ();
|
||||
|
||||
fn operations(&self) -> Vec<Operation> {
|
||||
vec![Operation::Get, Operation::Custom("push".to_string())]
|
||||
}
|
||||
|
||||
fn get(&self, _: String) -> ApiResult<PoolInfo> {
|
||||
let pool = self.tx_pool.read().unwrap();
|
||||
Ok(PoolInfo {
|
||||
pool_size: pool.pool_size(),
|
||||
orphans_size: pool.orphans_size(),
|
||||
total_size: pool.total_size(),
|
||||
})
|
||||
}
|
||||
|
||||
fn operation(&self, _: String, input: TxWrapper) -> ApiResult<()> {
|
||||
let tx_bin = util::from_hex(input.tx_hex).map_err(|_| {
|
||||
Error::Argument(format!("Invalid hex in transaction wrapper."))
|
||||
})?;
|
||||
|
||||
let tx: Transaction = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
|
||||
Error::Argument(
|
||||
"Could not deserialize transaction, invalid format.".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let source = pool::TxSource {
|
||||
debug_name: "push-api".to_string(),
|
||||
identifier: "?.?.?.?".to_string(),
|
||||
};
|
||||
info!(
|
||||
LOGGER,
|
||||
"Pushing transaction with {} inputs and {} outputs to pool.",
|
||||
tx.inputs.len(),
|
||||
tx.outputs.len()
|
||||
);
|
||||
self.tx_pool
|
||||
.write()
|
||||
.unwrap()
|
||||
.add_to_memory_pool(source, tx)
|
||||
.map_err(|e| {
|
||||
Error::Internal(format!("Addition to transaction pool failed: {:?}", e))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy wrapper for the hex-encoded serialized transaction.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TxWrapper {
|
||||
tx_hex: String,
|
||||
}
|
||||
|
||||
/// Start all server REST APIs. Just register all of them on a ApiServer
|
||||
/// instance and runs the corresponding HTTP server.
|
||||
pub fn start_rest_apis<T>(
|
||||
addr: String,
|
||||
chain: Arc<chain::Chain>,
|
||||
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
|
||||
) where
|
||||
T: pool::BlockChain + Clone + Send + Sync + 'static,
|
||||
{
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_endpoint("/pool".to_string(), PoolApi {tx_pool: tx_pool});
|
||||
|
||||
// register a nested router at "/v2" for flexibility
|
||||
// so we can experiment with raw iron handlers
|
||||
let utxo_handler = UtxoHandler {chain: chain.clone()};
|
||||
let chain_tip_handler = ChainHandler {chain: chain.clone()};
|
||||
let sumtree_handler = SumTreeHandler {chain: chain.clone()};
|
||||
let router = router!(
|
||||
chain_tip: get "/chain" => chain_tip_handler,
|
||||
chain_utxos: get "/chain/utxos" => utxo_handler,
|
||||
sumtree_roots: get "/sumtrees/*" => sumtree_handler,
|
||||
);
|
||||
apis.register_handler("/v2", router);
|
||||
|
||||
apis.start(&addr[..]).unwrap_or_else(|e| {
|
||||
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
|
||||
});
|
||||
});
|
||||
}
|
||||
+171
-72
@@ -12,37 +12,45 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::io::Read;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use iron::prelude::*;
|
||||
use iron::Handler;
|
||||
use iron::status;
|
||||
use urlencoded::UrlEncodedQuery;
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
||||
use chain;
|
||||
use core::core::Transaction;
|
||||
use core::ser;
|
||||
use pool;
|
||||
use rest::*;
|
||||
use types::*;
|
||||
use util::secp::pedersen::Commitment;
|
||||
use types::*;
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
|
||||
|
||||
pub struct UtxoHandler {
|
||||
pub chain: Arc<chain::Chain>,
|
||||
// Supports retrieval of multiple outputs in a single request -
|
||||
// GET /v1/chain/utxos?id=xxx,yyy,zzz
|
||||
// GET /v1/chain/utxos?id=xxx&id=yyy&id=zzz
|
||||
struct UtxoHandler {
|
||||
chain: Arc<chain::Chain>,
|
||||
}
|
||||
|
||||
impl UtxoHandler {
|
||||
fn get_utxo(&self, id: &str) -> Result<Output, Error> {
|
||||
debug!(LOGGER, "getting utxo: {}", id);
|
||||
let c = util::from_hex(String::from(id))
|
||||
.map_err(|_| {
|
||||
Error::Argument(format!("Not a valid commitment: {}", id))
|
||||
})?;
|
||||
let c = util::from_hex(String::from(id)).map_err(|_| {
|
||||
Error::Argument(format!("Not a valid commitment: {}", id))
|
||||
})?;
|
||||
let commit = Commitment::from_vec(c);
|
||||
|
||||
let out = self.chain.get_unspent(&commit)
|
||||
let out = self.chain
|
||||
.get_unspent(&commit)
|
||||
.map_err(|_| Error::NotFound)?;
|
||||
|
||||
let header = self.chain
|
||||
@@ -53,11 +61,6 @@ impl UtxoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Supports retrieval of multiple outputs in a single request -
|
||||
// GET /v2/chain/utxos?id=xxx,yyy,zzz
|
||||
// GET /v2/chain/utxos?id=xxx&id=yyy&id=zzz
|
||||
//
|
||||
impl Handler for UtxoHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let mut commitments: Vec<&str> = vec![];
|
||||
@@ -72,60 +75,49 @@ impl Handler for UtxoHandler {
|
||||
}
|
||||
|
||||
let mut utxos: Vec<Output> = vec![];
|
||||
|
||||
for commit in commitments {
|
||||
if let Ok(out) = self.get_utxo(commit) {
|
||||
utxos.push(out);
|
||||
}
|
||||
}
|
||||
|
||||
match serde_json::to_string(&utxos) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::BadRequest, ""))),
|
||||
}
|
||||
json_response(&utxos)
|
||||
}
|
||||
}
|
||||
|
||||
// Sum tree handler
|
||||
|
||||
pub struct SumTreeHandler {
|
||||
pub chain: Arc<chain::Chain>,
|
||||
// Sum tree handler. Retrieve the roots:
|
||||
// GET /v1/sumtrees/roots
|
||||
//
|
||||
// Last inserted nodes::
|
||||
// GET /v1/sumtrees/lastutxos (gets last 10)
|
||||
// GET /v1/sumtrees/lastutxos?n=5
|
||||
// GET /v1/sumtrees/lastrangeproofs
|
||||
// GET /v1/sumtrees/lastkernels
|
||||
struct SumTreeHandler {
|
||||
chain: Arc<chain::Chain>,
|
||||
}
|
||||
|
||||
impl SumTreeHandler {
|
||||
//gets roots
|
||||
// gets roots
|
||||
fn get_roots(&self) -> SumTrees {
|
||||
SumTrees::from_head(self.chain.clone())
|
||||
}
|
||||
|
||||
// gets last n utxos inserted in to the tree
|
||||
fn get_last_n_utxo(&self, distance:u64) -> Vec<SumTreeNode> {
|
||||
fn get_last_n_utxo(&self, distance: u64) -> Vec<SumTreeNode> {
|
||||
SumTreeNode::get_last_n_utxo(self.chain.clone(), distance)
|
||||
}
|
||||
|
||||
// gets last n utxos inserted in to the tree
|
||||
fn get_last_n_rangeproof(&self, distance:u64) -> Vec<SumTreeNode> {
|
||||
fn get_last_n_rangeproof(&self, distance: u64) -> Vec<SumTreeNode> {
|
||||
SumTreeNode::get_last_n_rangeproof(self.chain.clone(), distance)
|
||||
}
|
||||
|
||||
// gets last n utxos inserted in to the tree
|
||||
fn get_last_n_kernel(&self, distance:u64) -> Vec<SumTreeNode> {
|
||||
fn get_last_n_kernel(&self, distance: u64) -> Vec<SumTreeNode> {
|
||||
SumTreeNode::get_last_n_kernel(self.chain.clone(), distance)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve the roots:
|
||||
// GET /v2/sumtrees/roots
|
||||
//
|
||||
// Last inserted nodes::
|
||||
// GET /v2/sumtrees/lastutxos (gets last 10)
|
||||
// GET /v2/sumtrees/lastutxos?n=5
|
||||
// GET /v2/sumtrees/lastrangeproofs
|
||||
// GET /v2/sumtrees/lastkernels
|
||||
//
|
||||
|
||||
impl Handler for SumTreeHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let url = req.url.clone();
|
||||
@@ -133,40 +125,29 @@ impl Handler for SumTreeHandler {
|
||||
if *path_elems.last().unwrap() == "" {
|
||||
path_elems.pop();
|
||||
}
|
||||
//TODO: probably need to set a reasonable max limit here
|
||||
let mut last_n=10;
|
||||
// TODO: probably need to set a reasonable max limit here
|
||||
let mut last_n = 10;
|
||||
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
||||
if let Some(nums) = params.get("n") {
|
||||
for num in nums {
|
||||
if let Ok(n) = str::parse(num) {
|
||||
last_n=n;
|
||||
last_n = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match *path_elems.last().unwrap(){
|
||||
"roots" => match serde_json::to_string_pretty(&self.get_roots()) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::BadRequest, ""))),
|
||||
},
|
||||
"lastutxos" => match serde_json::to_string_pretty(&self.get_last_n_utxo(last_n)) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::BadRequest, ""))),
|
||||
},
|
||||
"lastrangeproofs" => match serde_json::to_string_pretty(&self.get_last_n_rangeproof(last_n)) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::BadRequest, ""))),
|
||||
},
|
||||
"lastkernels" => match serde_json::to_string_pretty(&self.get_last_n_kernel(last_n)) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::BadRequest, ""))),
|
||||
},_ => Ok(Response::with((status::BadRequest, "")))
|
||||
match *path_elems.last().unwrap() {
|
||||
"roots" => json_response(&self.get_roots()),
|
||||
"lastutxos" => json_response(&self.get_last_n_utxo(last_n)),
|
||||
"lastrangeproofs" => json_response(&self.get_last_n_rangeproof(last_n)),
|
||||
"lastkernels" => json_response(&self.get_last_n_kernel(last_n)),
|
||||
_ => Ok(Response::with((status::BadRequest, ""))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chain Handler
|
||||
|
||||
// Chain handler. Get the head details.
|
||||
// GET /v1/chain
|
||||
pub struct ChainHandler {
|
||||
pub chain: Arc<chain::Chain>,
|
||||
}
|
||||
@@ -177,16 +158,134 @@ impl ChainHandler {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Get the head details
|
||||
// GET /v2/chain
|
||||
//
|
||||
|
||||
impl Handler for ChainHandler {
|
||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
||||
match serde_json::to_string_pretty(&self.get_tip()) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::BadRequest, ""))),
|
||||
}
|
||||
json_response(&self.get_tip())
|
||||
}
|
||||
}
|
||||
|
||||
// Get basic information about the transaction pool.
|
||||
struct PoolInfoHandler<T> {
|
||||
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
|
||||
}
|
||||
|
||||
impl<T> Handler for PoolInfoHandler<T>
|
||||
where
|
||||
T: pool::BlockChain + Send + Sync + 'static,
|
||||
{
|
||||
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
||||
let pool = self.tx_pool.read().unwrap();
|
||||
json_response(&PoolInfo {
|
||||
pool_size: pool.pool_size(),
|
||||
orphans_size: pool.orphans_size(),
|
||||
total_size: pool.total_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy wrapper for the hex-encoded serialized transaction.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct TxWrapper {
|
||||
tx_hex: String,
|
||||
}
|
||||
|
||||
// Push new transactions to our transaction pool, that should broadcast it
|
||||
// to the network if valid.
|
||||
struct PoolPushHandler<T> {
|
||||
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
|
||||
}
|
||||
|
||||
impl<T> Handler for PoolPushHandler<T>
|
||||
where
|
||||
T: pool::BlockChain + Send + Sync + 'static,
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let wrapper: TxWrapper = serde_json::from_reader(req.body.by_ref())
|
||||
.map_err(|e| IronError::new(e, status::BadRequest))?;
|
||||
|
||||
let tx_bin = util::from_hex(wrapper.tx_hex).map_err(|_| {
|
||||
Error::Argument(format!("Invalid hex in transaction wrapper."))
|
||||
})?;
|
||||
|
||||
let tx: Transaction = ser::deserialize(&mut &tx_bin[..]).map_err(|_| {
|
||||
Error::Argument("Could not deserialize transaction, invalid format.".to_string())
|
||||
})?;
|
||||
|
||||
let source = pool::TxSource {
|
||||
debug_name: "push-api".to_string(),
|
||||
identifier: "?.?.?.?".to_string(),
|
||||
};
|
||||
info!(
|
||||
LOGGER,
|
||||
"Pushing transaction with {} inputs and {} outputs to pool.",
|
||||
tx.inputs.len(),
|
||||
tx.outputs.len()
|
||||
);
|
||||
self.tx_pool
|
||||
.write()
|
||||
.unwrap()
|
||||
.add_to_memory_pool(source, tx)
|
||||
.map_err(|e| {
|
||||
Error::Internal(format!("Addition to transaction pool failed: {:?}", e))
|
||||
})?;
|
||||
|
||||
Ok(Response::with(status::Ok))
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to serialize a struct into JSON and produce a sensible IronResult
|
||||
// out of it.
|
||||
fn json_response<T>(s: &T) -> IronResult<Response>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
match serde_json::to_string_pretty(s) {
|
||||
Ok(json) => Ok(Response::with((status::Ok, json))),
|
||||
Err(_) => Ok(Response::with((status::InternalServerError, ""))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start all server HTTP handlers. Register all of them with Iron
|
||||
/// and runs the corresponding HTTP server.
|
||||
pub fn start_rest_apis<T>(
|
||||
addr: String,
|
||||
chain: Arc<chain::Chain>,
|
||||
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>,
|
||||
) where
|
||||
T: pool::BlockChain + Send + Sync + 'static,
|
||||
{
|
||||
thread::spawn(move || {
|
||||
// build handlers and register them under the appropriate endpoint
|
||||
let utxo_handler = UtxoHandler {
|
||||
chain: chain.clone(),
|
||||
};
|
||||
let chain_tip_handler = ChainHandler {
|
||||
chain: chain.clone(),
|
||||
};
|
||||
let sumtree_handler = SumTreeHandler {
|
||||
chain: chain.clone(),
|
||||
};
|
||||
let pool_info_handler = PoolInfoHandler {
|
||||
tx_pool: tx_pool.clone(),
|
||||
};
|
||||
let pool_push_handler = PoolPushHandler {
|
||||
tx_pool: tx_pool.clone(),
|
||||
};
|
||||
|
||||
let router = router!(
|
||||
chain_tip: get "/chain" => chain_tip_handler,
|
||||
chain_utxos: get "/chain/utxos" => utxo_handler,
|
||||
sumtree_roots: get "/sumtrees/*" => sumtree_handler,
|
||||
pool_info: get "/pool" => pool_info_handler,
|
||||
pool_push: post "/pool/push" => pool_push_handler,
|
||||
);
|
||||
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_handler(router);
|
||||
|
||||
info!(LOGGER, "Starting HTTP API server at {}.", addr);
|
||||
apis.start(&addr[..]).unwrap_or_else(|e| {
|
||||
error!(LOGGER, "Failed to start API HTTP server: {}.", e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+6
-7
@@ -12,31 +12,30 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_store as store;
|
||||
extern crate grin_util as util;
|
||||
|
||||
extern crate hyper;
|
||||
#[macro_use]
|
||||
extern crate slog;
|
||||
extern crate iron;
|
||||
extern crate urlencoded;
|
||||
extern crate mount;
|
||||
#[macro_use]
|
||||
extern crate router;
|
||||
extern crate mount;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate slog;
|
||||
extern crate urlencoded;
|
||||
|
||||
pub mod client;
|
||||
mod endpoints;
|
||||
mod handlers;
|
||||
mod rest;
|
||||
mod types;
|
||||
|
||||
pub use endpoints::start_rest_apis;
|
||||
pub use handlers::start_rest_apis;
|
||||
pub use types::*;
|
||||
pub use rest::*;
|
||||
|
||||
+4
-279
@@ -19,26 +19,18 @@
|
||||
//! register them on a ApiServer.
|
||||
|
||||
use std::error;
|
||||
use std::fmt::{self, Display, Debug, Formatter};
|
||||
use std::io::Read;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::string::ToString;
|
||||
use std::str::FromStr;
|
||||
use std::mem;
|
||||
|
||||
use iron::prelude::*;
|
||||
use iron::{status, headers, Listening};
|
||||
use iron::method::Method;
|
||||
use iron::modifiers::Header;
|
||||
use iron::{status, Listening};
|
||||
use iron::middleware::Handler;
|
||||
use router::Router;
|
||||
use mount::Mount;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json;
|
||||
|
||||
use store;
|
||||
use util::LOGGER;
|
||||
|
||||
/// Errors that can be returned by an ApiEndpoint implementation.
|
||||
#[derive(Debug)]
|
||||
@@ -87,161 +79,6 @@ impl From<store::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Operation {
|
||||
Create,
|
||||
Delete,
|
||||
Update,
|
||||
Get,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
fn to_method(&self) -> Method {
|
||||
match *self {
|
||||
Operation::Create => Method::Post,
|
||||
Operation::Delete => Method::Delete,
|
||||
Operation::Update => Method::Put,
|
||||
Operation::Get => Method::Get,
|
||||
Operation::Custom(_) => Method::Post,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ApiResult<T> = ::std::result::Result<T, Error>;
|
||||
|
||||
/// Trait to implement to expose a service as a RESTful HTTP endpoint. Each
|
||||
/// method corresponds to a specific relative URL and HTTP method following
|
||||
/// basic REST principles:
|
||||
///
|
||||
/// * create: POST /
|
||||
/// * get: GET /:id
|
||||
/// * update: PUT /:id
|
||||
/// * delete: DELETE /:id
|
||||
///
|
||||
/// The methods method defines which operation the endpoint implements, they're
|
||||
/// all optional by default. It also allows the framework to automatically
|
||||
/// define the OPTIONS HTTP method.
|
||||
///
|
||||
/// The type accepted by create and update, and returned by get, must implement
|
||||
/// the serde Serialize and Deserialize traits. The identifier type returned by
|
||||
/// create and accepted by all other methods must have a string representation.
|
||||
pub trait ApiEndpoint: Clone + Send + Sync + 'static {
|
||||
type ID: ToString + FromStr;
|
||||
type T: Serialize + DeserializeOwned;
|
||||
type OP_IN: Serialize + DeserializeOwned;
|
||||
type OP_OUT: Serialize + DeserializeOwned;
|
||||
|
||||
fn operations(&self) -> Vec<Operation>;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn create(&self, o: Self::T) -> ApiResult<Self::ID> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn delete(&self, id: Self::ID) -> ApiResult<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn update(&self, id: Self::ID, o: Self::T) -> ApiResult<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn get(&self, id: Self::ID) -> ApiResult<Self::T> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn operation(&self, op: String, input: Self::OP_IN) -> ApiResult<Self::OP_OUT> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper required to define the implementation below, Rust doesn't let us
|
||||
// define the parametric implementation for trait from another crate.
|
||||
struct ApiWrapper<E>(E);
|
||||
|
||||
impl<E> Handler for ApiWrapper<E>
|
||||
where E: ApiEndpoint,
|
||||
<<E as ApiEndpoint>::ID as FromStr>::Err: Debug + Send + error::Error
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
match req.method {
|
||||
Method::Get => {
|
||||
let res = self.0.get(extract_param(req, "id")?)?;
|
||||
let res_json = serde_json::to_string(&res)
|
||||
.map_err(|e| IronError::new(e, status::InternalServerError))?;
|
||||
Ok(Response::with((status::Ok, res_json)))
|
||||
}
|
||||
Method::Put => {
|
||||
let id = extract_param(req, "id")?;
|
||||
let t: E::T = serde_json::from_reader(req.body.by_ref())
|
||||
.map_err(|e| IronError::new(e, status::BadRequest))?;
|
||||
self.0.update(id, t)?;
|
||||
Ok(Response::with(status::NoContent))
|
||||
}
|
||||
Method::Delete => {
|
||||
let id = extract_param(req, "id")?;
|
||||
self.0.delete(id)?;
|
||||
Ok(Response::with(status::NoContent))
|
||||
}
|
||||
Method::Post => {
|
||||
let t: E::T = serde_json::from_reader(req.body.by_ref())
|
||||
.map_err(|e| IronError::new(e, status::BadRequest))?;
|
||||
let id = self.0.create(t)?;
|
||||
Ok(Response::with((status::Created, id.to_string())))
|
||||
}
|
||||
_ => Ok(Response::with(status::MethodNotAllowed)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OpWrapper<E> {
|
||||
operation: String,
|
||||
endpoint: E,
|
||||
}
|
||||
|
||||
impl<E> Handler for OpWrapper<E>
|
||||
where E: ApiEndpoint
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let t: E::OP_IN = serde_json::from_reader(req.body.by_ref()).map_err(|e| {
|
||||
IronError::new(e, status::BadRequest)
|
||||
})?;
|
||||
let res = self.endpoint.operation(self.operation.clone(), t);
|
||||
match res {
|
||||
Ok(resp) => {
|
||||
let res_json = serde_json::to_string(&resp).map_err(|e| {
|
||||
IronError::new(e, status::InternalServerError)
|
||||
})?;
|
||||
Ok(Response::with((status::Ok, res_json)))
|
||||
}
|
||||
Err(e) => {
|
||||
error!(LOGGER, "API operation: {:?}", e);
|
||||
Err(IronError::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_param<ID>(req: &mut Request, param: &'static str) -> IronResult<ID>
|
||||
where ID: ToString + FromStr,
|
||||
<ID as FromStr>::Err: Debug + Send + error::Error + 'static
|
||||
{
|
||||
|
||||
let id = req.extensions
|
||||
.get::<Router>()
|
||||
.unwrap()
|
||||
.find(param)
|
||||
.unwrap_or("");
|
||||
id.parse::<ID>()
|
||||
.map_err(|e| IronError::new(e, status::BadRequest))
|
||||
}
|
||||
|
||||
/// HTTP server allowing the registration of ApiEndpoint implementations.
|
||||
pub struct ApiServer {
|
||||
root: String,
|
||||
@@ -281,119 +118,7 @@ impl ApiServer {
|
||||
}
|
||||
|
||||
/// Registers an iron handler (via mount)
|
||||
pub fn register_handler<H: Handler>(&mut self, route: &str, handler: H) -> &mut Mount {
|
||||
self.mount.mount(route, handler)
|
||||
}
|
||||
|
||||
/// Register a new API endpoint, providing a relative URL for the new
|
||||
/// endpoint.
|
||||
pub fn register_endpoint<E>(&mut self, subpath: String, endpoint: E)
|
||||
where E: ApiEndpoint,
|
||||
<<E as ApiEndpoint>::ID as FromStr>::Err: Debug + Send + error::Error
|
||||
{
|
||||
assert_eq!(subpath.chars().nth(0).unwrap(), '/');
|
||||
|
||||
// declare a route for each method actually implemented by the endpoint
|
||||
let route_postfix = &subpath[1..];
|
||||
let root = self.root.clone() + &subpath;
|
||||
for op in endpoint.operations() {
|
||||
let route_name = format!("{:?}_{}", op, route_postfix);
|
||||
|
||||
// special case of custom operations
|
||||
if let Operation::Custom(op_s) = op.clone() {
|
||||
let wrapper = OpWrapper {
|
||||
operation: op_s.clone(),
|
||||
endpoint: endpoint.clone(),
|
||||
};
|
||||
let full_path = format!("{}/{}", root.clone(), op_s.clone());
|
||||
self.router
|
||||
.route(op.to_method(), full_path.clone(), wrapper, route_name);
|
||||
info!(LOGGER, "route: POST {}", full_path);
|
||||
} else {
|
||||
|
||||
// regular REST operations
|
||||
let full_path = match op.clone() {
|
||||
Operation::Get => root.clone() + "/:id",
|
||||
Operation::Update => root.clone() + "/:id",
|
||||
Operation::Delete => root.clone() + "/:id",
|
||||
Operation::Create => root.clone(),
|
||||
_ => panic!("unreachable"),
|
||||
};
|
||||
let wrapper = ApiWrapper(endpoint.clone());
|
||||
self.router
|
||||
.route(op.to_method(), full_path.clone(), wrapper, route_name);
|
||||
info!(LOGGER, "route: {} {}", op.to_method(), full_path);
|
||||
}
|
||||
}
|
||||
|
||||
// support for the HTTP Options method by differentiating what's on the
|
||||
// root resource vs the id resource
|
||||
let (root_opts, sub_opts) = endpoint
|
||||
.operations()
|
||||
.iter()
|
||||
.fold((vec![], vec![]), |mut acc, op| {
|
||||
let m = op.to_method();
|
||||
if m == Method::Post {
|
||||
acc.0.push(m);
|
||||
} else {
|
||||
acc.1.push(m);
|
||||
}
|
||||
acc
|
||||
});
|
||||
self.router.options(
|
||||
root.clone(),
|
||||
move |_: &mut Request| {
|
||||
Ok(Response::with((status::Ok, Header(headers::Allow(root_opts.clone())))))
|
||||
},
|
||||
"option_".to_string() + route_postfix,
|
||||
);
|
||||
self.router.options(
|
||||
root.clone() + "/:id",
|
||||
move |_: &mut Request| {
|
||||
Ok(Response::with((status::Ok, Header(headers::Allow(sub_opts.clone())))))
|
||||
},
|
||||
"option_id_".to_string() + route_postfix,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Animal {
|
||||
name: String,
|
||||
legs: u32,
|
||||
lethal: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestApi;
|
||||
|
||||
impl ApiEndpoint for TestApi {
|
||||
type ID = String;
|
||||
type T = Animal;
|
||||
type OP_IN = ();
|
||||
type OP_OUT = ();
|
||||
|
||||
fn operations(&self) -> Vec<Operation> {
|
||||
vec![Operation::Get]
|
||||
}
|
||||
|
||||
fn get(&self, name: String) -> ApiResult<Animal> {
|
||||
Ok(Animal {
|
||||
name: name,
|
||||
legs: 4,
|
||||
lethal: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn req_chain_json() {
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_endpoint("/animal".to_string(), TestApi);
|
||||
pub fn register_handler<H: Handler>(&mut self, handler: H) -> &mut Mount {
|
||||
self.mount.mount(&self.root, handler)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-16
@@ -44,7 +44,7 @@ impl Tip {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sumtrees
|
||||
/// Sumtrees
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SumTrees {
|
||||
/// UTXO Root Hash
|
||||
@@ -59,7 +59,7 @@ pub struct SumTrees {
|
||||
|
||||
impl SumTrees {
|
||||
pub fn from_head(head: Arc<chain::Chain>) -> SumTrees {
|
||||
let roots=head.get_sumtree_roots();
|
||||
let roots = head.get_sumtree_roots();
|
||||
SumTrees {
|
||||
utxo_root_hash: util::to_hex(roots.0.hash.to_vec()),
|
||||
utxo_root_sum: util::to_hex(roots.0.sum.commit.0.to_vec()),
|
||||
@@ -80,14 +80,13 @@ pub struct SumTreeNode {
|
||||
}
|
||||
|
||||
impl SumTreeNode {
|
||||
|
||||
pub fn get_last_n_utxo(chain: Arc<chain::Chain>, distance:u64) -> Vec<SumTreeNode> {
|
||||
pub fn get_last_n_utxo(chain: Arc<chain::Chain>, distance: u64) -> Vec<SumTreeNode> {
|
||||
let mut return_vec = Vec::new();
|
||||
let last_n = chain.get_last_n_utxo(distance);
|
||||
for elem_output in last_n {
|
||||
let header = chain
|
||||
.get_block_header_by_output_commit(&elem_output.1.commit)
|
||||
.map_err(|_| Error::NotFound);
|
||||
.get_block_header_by_output_commit(&elem_output.1.commit)
|
||||
.map_err(|_| Error::NotFound);
|
||||
// Need to call further method to check if output is spent
|
||||
let mut output = OutputPrintable::from_output(&elem_output.1, &header.unwrap());
|
||||
if let Ok(_) = chain.get_unspent(&elem_output.1.commit) {
|
||||
@@ -101,7 +100,7 @@ impl SumTreeNode {
|
||||
return_vec
|
||||
}
|
||||
|
||||
pub fn get_last_n_rangeproof(head: Arc<chain::Chain>, distance:u64) -> Vec<SumTreeNode> {
|
||||
pub fn get_last_n_rangeproof(head: Arc<chain::Chain>, distance: u64) -> Vec<SumTreeNode> {
|
||||
let mut return_vec = Vec::new();
|
||||
let last_n = head.get_last_n_rangeproof(distance);
|
||||
for elem in last_n {
|
||||
@@ -113,7 +112,7 @@ impl SumTreeNode {
|
||||
return_vec
|
||||
}
|
||||
|
||||
pub fn get_last_n_kernel(head: Arc<chain::Chain>, distance:u64) -> Vec<SumTreeNode> {
|
||||
pub fn get_last_n_kernel(head: Arc<chain::Chain>, distance: u64) -> Vec<SumTreeNode> {
|
||||
let mut return_vec = Vec::new();
|
||||
let last_n = head.get_last_n_kernel(distance);
|
||||
for elem in last_n {
|
||||
@@ -149,9 +148,10 @@ pub struct Output {
|
||||
impl Output {
|
||||
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader) -> Output {
|
||||
let (output_type, lock_height) = match output.features {
|
||||
x if x.contains(core::transaction::COINBASE_OUTPUT) => {
|
||||
(OutputType::Coinbase, block_header.height + global::coinbase_maturity())
|
||||
}
|
||||
x if x.contains(core::transaction::COINBASE_OUTPUT) => (
|
||||
OutputType::Coinbase,
|
||||
block_header.height + global::coinbase_maturity(),
|
||||
),
|
||||
_ => (OutputType::Transaction, 0),
|
||||
};
|
||||
|
||||
@@ -165,12 +165,13 @@ impl Output {
|
||||
}
|
||||
}
|
||||
|
||||
//As above, except formatted a bit better for human viewing
|
||||
// As above, except formatted a bit better for human viewing
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct OutputPrintable {
|
||||
/// The type of output Coinbase|Transaction
|
||||
pub output_type: OutputType,
|
||||
/// The homomorphic commitment representing the output's amount (as hex string)
|
||||
/// The homomorphic commitment representing the output's amount (as hex
|
||||
/// string)
|
||||
pub commit: String,
|
||||
/// The height of the block creating this output
|
||||
pub height: u64,
|
||||
@@ -185,9 +186,10 @@ pub struct OutputPrintable {
|
||||
impl OutputPrintable {
|
||||
pub fn from_output(output: &core::Output, block_header: &core::BlockHeader) -> OutputPrintable {
|
||||
let (output_type, lock_height) = match output.features {
|
||||
x if x.contains(core::transaction::COINBASE_OUTPUT) => {
|
||||
(OutputType::Coinbase, block_header.height + global::coinbase_maturity())
|
||||
}
|
||||
x if x.contains(core::transaction::COINBASE_OUTPUT) => (
|
||||
OutputType::Coinbase,
|
||||
block_header.height + global::coinbase_maturity(),
|
||||
),
|
||||
_ => (OutputType::Transaction, 0),
|
||||
};
|
||||
OutputPrintable {
|
||||
|
||||
Reference in New Issue
Block a user