Refactor API handlers (#2572)

Also add some API tests
This commit is contained in:
hashmap 2019-02-15 22:17:00 +01:00 committed by GitHub
parent aad0e9402a
commit ac6ed71abd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 236 additions and 175 deletions

View file

@ -52,6 +52,15 @@ where
} }
} }
/// Helper function to easily issue a HTTP GET request
/// on a given URL that returns nothing. Handles request
/// building and response code checking.
pub fn get_no_ret(url: &str, api_secret: Option<String>) -> Result<(), Error> {
let req = build_request(url, "GET", api_secret, None)?;
send_request(req)?;
Ok(())
}
/// Helper function to easily issue a HTTP POST request with the provided JSON /// Helper function to easily issue a HTTP POST request with the provided JSON
/// object as body on a given URL that returns a JSON object. Handles request /// object as body on a given URL that returns a JSON object. Handles request
/// building, JSON serialization and deserialization, and response code /// building, JSON serialization and deserialization, and response code

View file

@ -11,7 +11,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use super::utils::{get_output, w}; use super::utils::{get_output, w};
use crate::chain; use crate::chain;
use crate::core::core::hash::Hash; use crate::core::core::hash::Hash;
@ -68,10 +67,7 @@ impl HeaderHandler {
impl Handler for HeaderHandler { impl Handler for HeaderHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture { fn get(&self, req: Request<Body>) -> ResponseFuture {
let el = match req.uri().path().trim_right_matches('/').rsplit('/').next() { let el = right_path_element!(req);
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
Some(el) => el,
};
result_to_response(self.get_header(el.to_string())) result_to_response(self.get_header(el.to_string()))
} }
} }
@ -130,11 +126,7 @@ fn check_block_param(input: &String) -> Result<(), Error> {
impl Handler for BlockHandler { impl Handler for BlockHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture { fn get(&self, req: Request<Body>) -> ResponseFuture {
let el = match req.uri().path().trim_right_matches('/').rsplit('/').next() { let el = right_path_element!(req);
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
Some(el) => el,
};
let h = match self.parse_input(el.to_string()) { let h = match self.parse_input(el.to_string()) {
Err(e) => { Err(e) => {
return response( return response(

View file

@ -22,9 +22,7 @@ use crate::util;
use crate::util::secp::pedersen::Commitment; use crate::util::secp::pedersen::Commitment;
use crate::web::*; use crate::web::*;
use hyper::{Body, Request, StatusCode}; use hyper::{Body, Request, StatusCode};
use std::collections::HashMap;
use std::sync::Weak; use std::sync::Weak;
use url::form_urlencoded;
/// Chain handler. Get the head details. /// Chain handler. Get the head details.
/// GET /v1/chain /// GET /v1/chain
@ -101,21 +99,9 @@ impl OutputHandler {
fn outputs_by_ids(&self, req: &Request<Body>) -> Result<Vec<Output>, Error> { fn outputs_by_ids(&self, req: &Request<Body>) -> Result<Vec<Output>, Error> {
let mut commitments: Vec<String> = vec![]; let mut commitments: Vec<String> = vec![];
let query = match req.uri().query() { let query = must_get_query!(req);
Some(q) => q, let params = QueryParams::from(query);
None => return Err(ErrorKind::RequestError("no query string".to_owned()))?, params.process_multival_param("id", |id| commitments.push(id.to_owned()));
};
let params = form_urlencoded::parse(query.as_bytes())
.into_owned()
.collect::<Vec<(String, String)>>();
for (k, id) in params {
if k == "id" {
for id in id.split(',') {
commitments.push(id.to_owned());
}
}
}
let mut outputs: Vec<Output> = vec![]; let mut outputs: Vec<Output> = vec![];
for x in commitments { for x in commitments {
@ -159,49 +145,17 @@ impl OutputHandler {
// returns outputs for a specified range of blocks // returns outputs for a specified range of blocks
fn outputs_block_batch(&self, req: &Request<Body>) -> Result<Vec<BlockOutputs>, Error> { fn outputs_block_batch(&self, req: &Request<Body>) -> Result<Vec<BlockOutputs>, Error> {
let mut commitments: Vec<Commitment> = vec![]; let mut commitments: Vec<Commitment> = vec![];
let mut start_height = 1;
let mut end_height = 1;
let mut include_rp = false;
let query = match req.uri().query() { let query = must_get_query!(req);
Some(q) => q, let params = QueryParams::from(query);
None => return Err(ErrorKind::RequestError("no query string".to_owned()))?, params.process_multival_param("id", |id| {
}; if let Ok(x) = util::from_hex(String::from(id)) {
commitments.push(Commitment::from_vec(x));
let params = form_urlencoded::parse(query.as_bytes()).into_owned().fold(
HashMap::new(),
|mut hm, (k, v)| {
hm.entry(k).or_insert(vec![]).push(v);
hm
},
);
if let Some(ids) = params.get("id") {
for id in ids {
for id in id.split(',') {
if let Ok(x) = util::from_hex(String::from(id)) {
commitments.push(Commitment::from_vec(x));
}
}
} }
} });
if let Some(heights) = params.get("start_height") { let start_height = parse_param!(params, "start_height", 1);
for height in heights { let end_height = parse_param!(params, "end_height", 1);
start_height = height let include_rp = params.get("include_rp").is_some();
.parse()
.map_err(|_| ErrorKind::RequestError("invalid start_height".to_owned()))?;
}
}
if let Some(heights) = params.get("end_height") {
for height in heights {
end_height = height
.parse()
.map_err(|_| ErrorKind::RequestError("invalid end_height".to_owned()))?;
}
}
if let Some(_) = params.get("include_rp") {
include_rp = true;
}
debug!( debug!(
"outputs_block_batch: {}-{}, {:?}, {:?}", "outputs_block_batch: {}-{}, {:?}, {:?}",
@ -223,11 +177,7 @@ impl OutputHandler {
impl Handler for OutputHandler { impl Handler for OutputHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture { fn get(&self, req: Request<Body>) -> ResponseFuture {
let command = match req.uri().path().trim_right_matches('/').rsplit('/').next() { match right_path_element!(req) {
Some(c) => c,
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
};
match command {
"byids" => result_to_response(self.outputs_by_ids(&req)), "byids" => result_to_response(self.outputs_by_ids(&req)),
"byheight" => result_to_response(self.outputs_block_batch(&req)), "byheight" => result_to_response(self.outputs_block_batch(&req)),
_ => response(StatusCode::BAD_REQUEST, ""), _ => response(StatusCode::BAD_REQUEST, ""),

View file

@ -37,11 +37,11 @@ pub struct PeersConnectedHandler {
impl Handler for PeersConnectedHandler { impl Handler for PeersConnectedHandler {
fn get(&self, _req: Request<Body>) -> ResponseFuture { fn get(&self, _req: Request<Body>) -> ResponseFuture {
let mut peers: Vec<PeerInfoDisplay> = vec![]; let peers: Vec<PeerInfoDisplay> = w(&self.peers)
for p in &w(&self.peers).connected_peers() { .connected_peers()
let peer_info = p.info.clone(); .iter()
peers.push(peer_info.into()); .map(|p| p.info.clone().into())
} .collect();
json_response(&peers) json_response(&peers)
} }
} }
@ -56,10 +56,7 @@ pub struct PeerHandler {
impl Handler for PeerHandler { impl Handler for PeerHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture { fn get(&self, req: Request<Body>) -> ResponseFuture {
let command = match req.uri().path().trim_right_matches('/').rsplit('/').next() { let command = right_path_element!(req);
Some(c) => c,
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
};
if let Ok(addr) = command.parse() { if let Ok(addr) = command.parse() {
match w(&self.peers).get_peer(addr) { match w(&self.peers).get_peer(addr) {
Ok(peer) => json_response(&peer), Ok(peer) => json_response(&peer),

View file

@ -23,12 +23,11 @@ use crate::types::*;
use crate::util; use crate::util;
use crate::util::RwLock; use crate::util::RwLock;
use crate::web::*; use crate::web::*;
use failure::ResultExt;
use futures::future::ok; use futures::future::ok;
use futures::Future; use futures::Future;
use hyper::{Body, Request, StatusCode}; use hyper::{Body, Request, StatusCode};
use std::collections::HashMap;
use std::sync::Weak; use std::sync::Weak;
use url::form_urlencoded;
/// Get basic information about the transaction pool. /// Get basic information about the transaction pool.
/// GET /v1/pool /// GET /v1/pool
@ -61,15 +60,7 @@ pub struct PoolPushHandler {
impl PoolPushHandler { impl PoolPushHandler {
fn update_pool(&self, req: Request<Body>) -> Box<dyn Future<Item = (), Error = Error> + Send> { fn update_pool(&self, req: Request<Body>) -> Box<dyn Future<Item = (), Error = Error> + Send> {
let params = match req.uri().query() { let params = QueryParams::from(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(); let fluff = params.get("fluff").is_some();
let pool_arc = w(&self.tx_pool).clone(); let pool_arc = w(&self.tx_pool).clone();
@ -99,13 +90,14 @@ impl PoolPushHandler {
// Push to tx pool. // Push to tx pool.
let mut tx_pool = pool_arc.write(); let mut tx_pool = pool_arc.write();
let header = tx_pool.blockchain.chain_head().unwrap(); let header = tx_pool
tx_pool .blockchain
.chain_head()
.context(ErrorKind::Internal("Failed to get chain head".to_owned()))?;
let res = tx_pool
.add_to_pool(source, tx, !fluff, &header) .add_to_pool(source, tx, !fluff, &header)
.map_err(|e| { .context(ErrorKind::Internal("Failed to update pool".to_owned()))?;
error!("update_pool: failed with error: {:?}", e); Ok(res)
ErrorKind::Internal(format!("Failed to update pool: {:?}", e)).into()
})
}), }),
) )
} }

View file

@ -22,9 +22,7 @@ use crate::util::secp::pedersen::Commitment;
use crate::web::*; use crate::web::*;
use failure::ResultExt; use failure::ResultExt;
use hyper::{Body, Request, StatusCode}; use hyper::{Body, Request, StatusCode};
use std::collections::HashMap;
use std::sync::Weak; use std::sync::Weak;
use url::form_urlencoded;
// Sum tree handler. Retrieve the roots: // Sum tree handler. Retrieve the roots:
// GET /v1/txhashset/roots // GET /v1/txhashset/roots
@ -114,59 +112,14 @@ impl TxHashSetHandler {
impl Handler for TxHashSetHandler { impl Handler for TxHashSetHandler {
fn get(&self, req: Request<Body>) -> ResponseFuture { fn get(&self, req: Request<Body>) -> ResponseFuture {
let mut start_index = 1;
let mut max = 100;
let mut id = "".to_owned();
// TODO: probably need to set a reasonable max limit here // TODO: probably need to set a reasonable max limit here
let mut last_n = 10; let params = QueryParams::from(req.uri().query());
if let Some(query_string) = req.uri().query() { let last_n = parse_param_no_err!(params, "n", 10);
let params = form_urlencoded::parse(query_string.as_bytes()) let start_index = parse_param_no_err!(params, "start_index", 1);
.into_owned() let max = parse_param_no_err!(params, "max", 100);
.fold(HashMap::new(), |mut hm, (k, v)| { let id = parse_param_no_err!(params, "id", "".to_owned());
hm.entry(k).or_insert(vec![]).push(v);
hm
});
if let Some(nums) = params.get("n") {
for num in nums {
if let Ok(n) = str::parse(num) {
last_n = n;
}
}
}
if let Some(start_indexes) = params.get("start_index") {
for si in start_indexes {
if let Ok(s) = str::parse(si) {
start_index = s;
}
}
}
if let Some(maxes) = params.get("max") {
for ma in maxes {
if let Ok(m) = str::parse(ma) {
max = m;
}
}
}
if let Some(ids) = params.get("id") {
if !ids.is_empty() {
id = ids.last().unwrap().to_owned();
}
}
}
let command = match req
.uri()
.path()
.trim_right()
.trim_right_matches("/")
.rsplit("/")
.next()
{
Some(c) => c,
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
};
match command { match right_path_element!(req) {
"roots" => json_response_pretty(&self.get_roots()), "roots" => json_response_pretty(&self.get_roots()),
"lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)), "lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)),
"lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)), "lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)),

View file

@ -48,12 +48,15 @@ pub fn get_output(
OutputIdentifier::new(OutputFeatures::Coinbase, &commit), OutputIdentifier::new(OutputFeatures::Coinbase, &commit),
]; ];
for x in outputs.iter() { for x in outputs.iter().filter(|x| w(chain).is_unspent(x).is_ok()) {
if let Ok(_) = w(chain).is_unspent(&x) { let block_height = w(chain)
let block_height = w(chain).get_header_for_output(&x).unwrap().height; .get_header_for_output(&x)
let output_pos = w(chain).get_output_pos(&x.commit).unwrap_or(0); .context(ErrorKind::Internal(
return Ok((Output::new(&commit, block_height, output_pos), x.clone())); "Can't get header for output".to_owned(),
} ))?
.height;
let output_pos = w(chain).get_output_pos(&x.commit).unwrap_or(0);
return Ok((Output::new(&commit, block_height, output_pos), x.clone()));
} }
Err(ErrorKind::NotFound)? Err(ErrorKind::NotFound)?
} }

View file

@ -30,13 +30,14 @@ extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use]
mod web;
pub mod auth; pub mod auth;
pub mod client; pub mod client;
mod handlers; mod handlers;
mod rest; mod rest;
mod router; mod router;
mod types; mod types;
mod web;
pub use crate::auth::BasicAuthMiddleware; pub use crate::auth::BasicAuthMiddleware;
pub use crate::handlers::start_rest_apis; pub use crate::handlers::start_rest_apis;

View file

@ -5,7 +5,9 @@ use futures::{Future, Stream};
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json; use serde_json;
use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use url::form_urlencoded;
/// Parse request body /// Parse request body
pub fn parse_body<T>(req: Request<Body>) -> Box<dyn Future<Item = T, Error = Error> + Send> pub fn parse_body<T>(req: Request<Body>) -> Box<dyn Future<Item = T, Error = Error> + Send>
@ -81,3 +83,100 @@ pub fn just_response<T: Into<Body> + Debug>(status: StatusCode, text: T) -> Resp
pub fn response<T: Into<Body> + Debug>(status: StatusCode, text: T) -> ResponseFuture { pub fn response<T: Into<Body> + Debug>(status: StatusCode, text: T) -> ResponseFuture {
Box::new(ok(just_response(status, text))) Box::new(ok(just_response(status, text)))
} }
pub struct QueryParams {
params: HashMap<String, Vec<String>>,
}
impl QueryParams {
pub fn process_multival_param<F>(&self, name: &str, mut f: F)
where
F: FnMut(&str),
{
if let Some(ids) = self.params.get(name) {
for id in ids {
for id in id.split(',') {
f(id);
}
}
}
}
pub fn get(&self, name: &str) -> Option<&String> {
match self.params.get(name) {
None => None,
Some(v) => v.first(),
}
}
}
impl From<&str> for QueryParams {
fn from(query_string: &str) -> Self {
let params = 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
});
QueryParams { params }
}
}
impl From<Option<&str>> for QueryParams {
fn from(query_string: Option<&str>) -> Self {
match query_string {
Some(query_string) => Self::from(query_string),
None => QueryParams {
params: HashMap::new(),
},
}
}
}
impl From<Request<Body>> for QueryParams {
fn from(req: Request<Body>) -> Self {
Self::from(req.uri().query())
}
}
#[macro_export]
macro_rules! right_path_element(
($req: expr) =>(
match $req.uri().path().trim_right_matches('/').rsplit('/').next() {
None => return response(StatusCode::BAD_REQUEST, "invalid url"),
Some(el) => el,
};
));
#[macro_export]
macro_rules! must_get_query(
($req: expr) =>(
match $req.uri().query() {
Some(q) => q,
None => return Err(ErrorKind::RequestError("no query string".to_owned()))?,
}
));
#[macro_export]
macro_rules! parse_param(
($param: expr, $name: expr, $default: expr) =>(
match $param.get($name) {
None => $default,
Some(val) => match val.parse() {
Ok(val) => val,
Err(_) => return Err(ErrorKind::RequestError(format!("invalid value of parameter {}", $name)))?,
}
}
));
#[macro_export]
macro_rules! parse_param_no_err(
($param: expr, $name: expr, $default: expr) =>(
match $param.get($name) {
None => $default,
Some(val) => match val.parse() {
Ok(val) => val,
Err(_) => $default,
}
}
));

View file

@ -18,8 +18,7 @@ extern crate log;
mod framework; mod framework;
use self::core::global::{self, ChainTypes}; use self::core::global::{self, ChainTypes};
use self::util::init_test_logger; use self::util::{init_test_logger, to_hex, Mutex};
use self::util::Mutex;
use crate::framework::{LocalServerContainer, LocalServerContainerConfig}; use crate::framework::{LocalServerContainer, LocalServerContainerConfig};
use grin_api as api; use grin_api as api;
use grin_core as core; use grin_core as core;
@ -79,6 +78,7 @@ fn simple_server_wallet() {
warn!("Testing chain handler"); warn!("Testing chain handler");
let tip = get_tip(&base_addr, api_server_port); let tip = get_tip(&base_addr, api_server_port);
assert!(tip.is_ok()); assert!(tip.is_ok());
assert!(validate_chain(&base_addr, api_server_port).is_ok());
warn!("Testing status handler"); warn!("Testing status handler");
let status = get_status(&base_addr, api_server_port); let status = get_status(&base_addr, api_server_port);
@ -94,17 +94,30 @@ fn simple_server_wallet() {
warn!("Testing block handler"); warn!("Testing block handler");
let last_block_by_height = get_block_by_height(&base_addr, api_server_port, current_tip.height); let last_block_by_height = get_block_by_height(&base_addr, api_server_port, current_tip.height);
assert!(last_block_by_height.is_ok()); assert!(last_block_by_height.is_ok());
let block_hash = current_tip.last_block_pushed;
let last_block_by_height_compact = let last_block_by_height_compact =
get_block_by_height_compact(&base_addr, api_server_port, current_tip.height); get_block_by_height_compact(&base_addr, api_server_port, current_tip.height);
assert!(last_block_by_height_compact.is_ok()); assert!(last_block_by_height_compact.is_ok());
let block_hash = current_tip.last_block_pushed; let unspent_commit = get_unspent_output(&last_block_by_height.unwrap()).unwrap();
let last_block_by_hash = get_block_by_hash(&base_addr, api_server_port, &block_hash); let last_block_by_hash = get_block_by_hash(&base_addr, api_server_port, &block_hash);
assert!(last_block_by_hash.is_ok()); assert!(last_block_by_hash.is_ok());
let last_block_by_hash_compact = let last_block_by_hash_compact =
get_block_by_hash_compact(&base_addr, api_server_port, &block_hash); get_block_by_hash_compact(&base_addr, api_server_port, &block_hash);
assert!(last_block_by_hash_compact.is_ok()); assert!(last_block_by_hash_compact.is_ok());
warn!("Testing header handler");
let last_header_by_height =
get_header_by_height(&base_addr, api_server_port, current_tip.height);
assert!(last_header_by_height.is_ok());
let last_header_by_hash = get_header_by_hash(&base_addr, api_server_port, &block_hash);
assert!(last_header_by_hash.is_ok());
let last_header_by_commit = get_header_by_commit(&base_addr, api_server_port, &unspent_commit);
assert!(last_header_by_commit.is_ok());
warn!("Testing chain output handler"); warn!("Testing chain output handler");
let start_height = 0; let start_height = 0;
let end_height = current_tip.height; let end_height = current_tip.height;
@ -286,6 +299,20 @@ fn get_block_by_hash(
api::client::get::<api::BlockPrintable>(url.as_str(), None).map_err(|e| Error::API(e)) api::client::get::<api::BlockPrintable>(url.as_str(), None).map_err(|e| Error::API(e))
} }
fn get_header_by_commit(
base_addr: &String,
api_server_port: u16,
commit: &api::PrintableCommitment,
) -> Result<api::BlockHeaderPrintable, Error> {
let url = format!(
"http://{}:{}/v1/headers/{}",
base_addr,
api_server_port,
to_hex(commit.to_vec())
);
api::client::get::<api::BlockHeaderPrintable>(url.as_str(), None).map_err(|e| Error::API(e))
}
fn get_block_by_hash_compact( fn get_block_by_hash_compact(
base_addr: &String, base_addr: &String,
api_server_port: u16, api_server_port: u16,
@ -298,6 +325,31 @@ fn get_block_by_hash_compact(
api::client::get::<api::CompactBlockPrintable>(url.as_str(), None).map_err(|e| Error::API(e)) api::client::get::<api::CompactBlockPrintable>(url.as_str(), None).map_err(|e| Error::API(e))
} }
// Header handler functions
fn get_header_by_height(
base_addr: &String,
api_server_port: u16,
height: u64,
) -> Result<api::BlockHeaderPrintable, Error> {
let url = format!(
"http://{}:{}/v1/headers/{}",
base_addr, api_server_port, height
);
api::client::get::<api::BlockHeaderPrintable>(url.as_str(), None).map_err(|e| Error::API(e))
}
fn get_header_by_hash(
base_addr: &String,
api_server_port: u16,
header_hash: &String,
) -> Result<api::BlockHeaderPrintable, Error> {
let url = format!(
"http://{}:{}/v1/headers/{}",
base_addr, api_server_port, header_hash
);
api::client::get::<api::BlockHeaderPrintable>(url.as_str(), None).map_err(|e| Error::API(e))
}
// Chain output handler functions // Chain output handler functions
fn get_outputs_by_ids1( fn get_outputs_by_ids1(
base_addr: &String, base_addr: &String,
@ -343,6 +395,11 @@ fn get_outputs_by_height(
api::client::get::<Vec<api::BlockOutputs>>(url.as_str(), None).map_err(|e| Error::API(e)) api::client::get::<Vec<api::BlockOutputs>>(url.as_str(), None).map_err(|e| Error::API(e))
} }
fn validate_chain(base_addr: &String, api_server_port: u16) -> Result<(), Error> {
let url = format!("http://{}:{}/v1/chain/validate", base_addr, api_server_port);
api::client::get_no_ret(url.as_str(), None).map_err(|e| Error::API(e))
}
// TxHashSet handler functions // TxHashSet handler functions
fn get_txhashset_roots(base_addr: &String, api_server_port: u16) -> Result<api::TxHashSet, Error> { fn get_txhashset_roots(base_addr: &String, api_server_port: u16) -> Result<api::TxHashSet, Error> {
let url = format!( let url = format!(
@ -412,19 +469,6 @@ fn get_txhashset_lastkernels(
api::client::get::<Vec<api::TxHashSetNode>>(url.as_str(), None).map_err(|e| Error::API(e)) api::client::get::<Vec<api::TxHashSetNode>>(url.as_str(), None).map_err(|e| Error::API(e))
} }
// Helper function to get a vec of commitment output ids from a vec of block
// outputs
fn get_ids_from_block_outputs(block_outputs: Vec<api::BlockOutputs>) -> Vec<String> {
let mut ids: Vec<String> = Vec::new();
for block_output in block_outputs {
let outputs = &block_output.outputs;
for output in outputs {
ids.push(util::to_hex(output.clone().commit.0.to_vec()));
}
}
ids.into_iter().take(100).collect()
}
pub fn ban_peer(base_addr: &String, api_server_port: u16, peer_addr: &String) -> Result<(), Error> { pub fn ban_peer(base_addr: &String, api_server_port: u16, peer_addr: &String) -> Result<(), Error> {
let url = format!( let url = format!(
"http://{}:{}/v1/peers/{}/ban", "http://{}:{}/v1/peers/{}/ban",
@ -477,6 +521,27 @@ pub fn get_all_peers(
api::client::get::<Vec<p2p::PeerData>>(url.as_str(), None).map_err(|e| Error::API(e)) api::client::get::<Vec<p2p::PeerData>>(url.as_str(), None).map_err(|e| Error::API(e))
} }
// Helper function to get a vec of commitment output ids from a vec of block
// outputs
fn get_ids_from_block_outputs(block_outputs: Vec<api::BlockOutputs>) -> Vec<String> {
let mut ids: Vec<String> = Vec::new();
for block_output in block_outputs {
let outputs = &block_output.outputs;
for output in outputs {
ids.push(util::to_hex(output.clone().commit.0.to_vec()));
}
}
ids.into_iter().take(100).collect()
}
fn get_unspent_output(block: &api::BlockPrintable) -> Option<api::PrintableCommitment> {
match block.outputs.iter().find(|o| !o.spent) {
None => None,
Some(output) => Some(api::PrintableCommitment {
commit: output.commit.clone(),
}),
}
}
/// Error type wrapping underlying module errors. /// Error type wrapping underlying module errors.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {