2017-10-27 20:36:03 +03:00
|
|
|
// Copyright 2017 The Grin Developers
|
2017-10-25 20:57:48 +03:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2017-11-01 02:32:33 +03:00
|
|
|
use std::io::Read;
|
|
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
use std::thread;
|
2017-10-25 20:57:48 +03:00
|
|
|
|
|
|
|
use iron::prelude::*;
|
|
|
|
use iron::Handler;
|
|
|
|
use iron::status;
|
|
|
|
use urlencoded::UrlEncodedQuery;
|
2017-11-01 02:32:33 +03:00
|
|
|
use serde::Serialize;
|
2017-10-25 20:57:48 +03:00
|
|
|
use serde_json;
|
|
|
|
|
|
|
|
use chain;
|
2017-11-01 02:32:33 +03:00
|
|
|
use core::core::Transaction;
|
|
|
|
use core::ser;
|
|
|
|
use pool;
|
2017-11-02 19:49:33 +03:00
|
|
|
use p2p;
|
2017-10-25 20:57:48 +03:00
|
|
|
use rest::*;
|
2017-11-01 02:20:55 +03:00
|
|
|
use util::secp::pedersen::Commitment;
|
2017-11-01 02:32:33 +03:00
|
|
|
use types::*;
|
2017-10-25 20:57:48 +03:00
|
|
|
use util;
|
|
|
|
use util::LOGGER;
|
|
|
|
|
|
|
|
|
2017-11-01 02:32:33 +03:00
|
|
|
// 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>,
|
2017-10-25 20:57:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UtxoHandler {
|
|
|
|
fn get_utxo(&self, id: &str) -> Result<Output, Error> {
|
|
|
|
debug!(LOGGER, "getting utxo: {}", id);
|
2017-11-01 02:32:33 +03:00
|
|
|
let c = util::from_hex(String::from(id)).map_err(|_| {
|
|
|
|
Error::Argument(format!("Not a valid commitment: {}", id))
|
|
|
|
})?;
|
2017-10-25 20:57:48 +03:00
|
|
|
let commit = Commitment::from_vec(c);
|
|
|
|
|
2017-11-01 02:32:33 +03:00
|
|
|
let out = self.chain
|
|
|
|
.get_unspent(&commit)
|
2017-10-25 20:57:48 +03:00
|
|
|
.map_err(|_| Error::NotFound)?;
|
|
|
|
|
|
|
|
let header = self.chain
|
|
|
|
.get_block_header_by_output_commit(&commit)
|
|
|
|
.map_err(|_| Error::NotFound)?;
|
|
|
|
|
|
|
|
Ok(Output::from_output(&out, &header))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Handler for UtxoHandler {
|
|
|
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
|
|
|
let mut commitments: Vec<&str> = vec![];
|
|
|
|
if let Ok(params) = req.get_ref::<UrlEncodedQuery>() {
|
|
|
|
if let Some(ids) = params.get("id") {
|
|
|
|
for id in ids {
|
|
|
|
for id in id.split(",") {
|
|
|
|
commitments.push(id.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut utxos: Vec<Output> = vec![];
|
|
|
|
for commit in commitments {
|
|
|
|
if let Ok(out) = self.get_utxo(commit) {
|
|
|
|
utxos.push(out);
|
|
|
|
}
|
|
|
|
}
|
2017-11-01 02:32:33 +03:00
|
|
|
json_response(&utxos)
|
2017-10-25 20:57:48 +03:00
|
|
|
}
|
|
|
|
}
|
2017-10-28 00:57:04 +03:00
|
|
|
|
2017-11-01 02:32:33 +03:00
|
|
|
// 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>,
|
2017-10-28 00:57:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SumTreeHandler {
|
2017-11-01 02:32:33 +03:00
|
|
|
// gets roots
|
2017-10-28 00:57:04 +03:00
|
|
|
fn get_roots(&self) -> SumTrees {
|
|
|
|
SumTrees::from_head(self.chain.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
// gets last n utxos inserted in to the tree
|
2017-11-01 02:32:33 +03:00
|
|
|
fn get_last_n_utxo(&self, distance: u64) -> Vec<SumTreeNode> {
|
2017-10-28 00:57:04 +03:00
|
|
|
SumTreeNode::get_last_n_utxo(self.chain.clone(), distance)
|
|
|
|
}
|
|
|
|
|
|
|
|
// gets last n utxos inserted in to the tree
|
2017-11-01 02:32:33 +03:00
|
|
|
fn get_last_n_rangeproof(&self, distance: u64) -> Vec<SumTreeNode> {
|
2017-10-28 00:57:04 +03:00
|
|
|
SumTreeNode::get_last_n_rangeproof(self.chain.clone(), distance)
|
|
|
|
}
|
|
|
|
|
|
|
|
// gets last n utxos inserted in to the tree
|
2017-11-01 02:32:33 +03:00
|
|
|
fn get_last_n_kernel(&self, distance: u64) -> Vec<SumTreeNode> {
|
2017-10-28 00:57:04 +03:00
|
|
|
SumTreeNode::get_last_n_kernel(self.chain.clone(), distance)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Handler for SumTreeHandler {
|
|
|
|
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
|
|
|
let url = req.url.clone();
|
|
|
|
let mut path_elems = url.path();
|
|
|
|
if *path_elems.last().unwrap() == "" {
|
|
|
|
path_elems.pop();
|
|
|
|
}
|
2017-11-01 02:32:33 +03:00
|
|
|
// TODO: probably need to set a reasonable max limit here
|
|
|
|
let mut last_n = 10;
|
2017-10-28 00:57:04 +03:00
|
|
|
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) {
|
2017-11-01 02:32:33 +03:00
|
|
|
last_n = n;
|
2017-10-28 00:57:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-01 02:32:33 +03:00
|
|
|
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, ""))),
|
2017-10-28 00:57:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-02 19:49:33 +03:00
|
|
|
pub struct PeersAllHandler {
|
|
|
|
pub peer_store: Arc<p2p::PeerStore>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Handler for PeersAllHandler {
|
|
|
|
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
|
|
|
let peers = &self.peer_store.all_peers();
|
|
|
|
json_response(&peers)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct PeersConnectedHandler {
|
|
|
|
pub p2p_server: Arc<p2p::Server>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Handler for PeersConnectedHandler {
|
|
|
|
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
|
|
|
let mut peers = vec![];
|
|
|
|
for p in &self.p2p_server.all_peers() {
|
|
|
|
let peer_info = p.info.clone();
|
|
|
|
peers.push(peer_info);
|
|
|
|
}
|
|
|
|
json_response(&peers)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-01 02:32:33 +03:00
|
|
|
// Chain handler. Get the head details.
|
|
|
|
// GET /v1/chain
|
2017-10-28 00:57:04 +03:00
|
|
|
pub struct ChainHandler {
|
|
|
|
pub chain: Arc<chain::Chain>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ChainHandler {
|
|
|
|
fn get_tip(&self) -> Tip {
|
|
|
|
Tip::from_tip(self.chain.head().unwrap())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Handler for ChainHandler {
|
|
|
|
fn handle(&self, _req: &mut Request) -> IronResult<Response> {
|
2017-11-01 02:32:33 +03:00
|
|
|
json_response(&self.get_tip())
|
2017-10-28 00:57:04 +03:00
|
|
|
}
|
|
|
|
}
|
2017-11-01 02:32:33 +03:00
|
|
|
|
|
|
|
// 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>>>,
|
2017-11-02 19:49:33 +03:00
|
|
|
p2p_server: Arc<p2p::Server>,
|
|
|
|
peer_store: Arc<p2p::PeerStore>,
|
2017-11-01 02:32:33 +03:00
|
|
|
) 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(),
|
|
|
|
};
|
2017-11-02 19:49:33 +03:00
|
|
|
let peers_all_handler = PeersAllHandler {
|
|
|
|
peer_store: peer_store.clone(),
|
|
|
|
};
|
|
|
|
let peers_connected_handler = PeersConnectedHandler {
|
|
|
|
p2p_server: p2p_server.clone(),
|
|
|
|
};
|
2017-11-01 02:32:33 +03:00
|
|
|
|
|
|
|
let router = router!(
|
|
|
|
chain_tip: get "/chain" => chain_tip_handler,
|
|
|
|
chain_utxos: get "/chain/utxos" => utxo_handler,
|
|
|
|
sumtree_roots: get "/sumtrees/*" => sumtree_handler,
|
2017-11-02 19:49:33 +03:00
|
|
|
pool_info: get "/pool" => pool_info_handler,
|
|
|
|
pool_push: post "/pool/push" => pool_push_handler,
|
|
|
|
peers_all: get "/peers/all" => peers_all_handler,
|
|
|
|
peers_connected: get "/peers/connected" => peers_connected_handler,
|
2017-11-01 02:32:33 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|