Bug fixes in wallet and related API
Fixes a few loose ends in the full cycle of sending coins, accepting them, pushing that transaction to the pool and having it mined. More specifically: * The API output endpoint needs to be a UTXO endpoint, as the server can't make any guarantee about having a spent output. * Bubbling up HTTP not found errors. * Wallet output status checker now handles spent outputs. * Transaction pool validates the transaction before accepting it. * Fixed the operation API routes. * Fixed too greedy wallet coin selection loop.
This commit is contained in:
+8
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
use hyper;
|
||||
use hyper::client::Response;
|
||||
use hyper::status::StatusClass;
|
||||
use hyper::status::{StatusClass, StatusCode};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde_json;
|
||||
|
||||
@@ -59,7 +59,13 @@ 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 => 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."))),
|
||||
}
|
||||
}
|
||||
|
||||
+39
-7
@@ -21,7 +21,7 @@
|
||||
// }
|
||||
// }
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
|
||||
use core::core::{Transaction, Output};
|
||||
@@ -61,6 +61,7 @@ impl ApiEndpoint for ChainApi {
|
||||
pub struct OutputApi {
|
||||
/// data store access
|
||||
chain_store: Arc<chain::ChainStore>,
|
||||
chain_head: Arc<Mutex<chain::Tip>>,
|
||||
}
|
||||
|
||||
impl ApiEndpoint for OutputApi {
|
||||
@@ -76,10 +77,34 @@ impl ApiEndpoint for OutputApi {
|
||||
fn get(&self, id: String) -> ApiResult<Output> {
|
||||
debug!("GET output {}", id);
|
||||
let c = util::from_hex(id.clone()).map_err(|e| Error::Argument(format!("Not a valid commitment: {}", id)))?;
|
||||
let out = self.chain_store
|
||||
.get_output_by_commit(&Commitment::from_vec(c))
|
||||
.map_err(|e| Error::Internal(e.to_string()));
|
||||
out
|
||||
let commitment = Commitment::from_vec(c);
|
||||
|
||||
// TODO use an actual UTXO tree
|
||||
// in the meantime doing it the *very* expensive way:
|
||||
// 1. check the output exists
|
||||
// 2. run the chain back from the head to check it hasn't been spent
|
||||
if let Ok(out) = self.chain_store.get_output_by_commit(&commitment) {
|
||||
let mut block_h: Hash;
|
||||
{
|
||||
let chain_head = self.chain_head.clone();
|
||||
let head = chain_head.lock().unwrap();
|
||||
block_h = head.last_block_h;
|
||||
}
|
||||
loop {
|
||||
let b = self.chain_store.get_block(&block_h)?;
|
||||
for input in b.inputs {
|
||||
if input.commitment() == commitment {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
}
|
||||
if b.header.height == 1 {
|
||||
return Ok(out);
|
||||
} else {
|
||||
block_h = b.header.previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +155,9 @@ impl<T> ApiEndpoint for PoolApi<T>
|
||||
debug_name: "push-api".to_string(),
|
||||
identifier: "?.?.?.?".to_string(),
|
||||
};
|
||||
debug!("Pushing transaction with {} inputs and {} outputs to pool.",
|
||||
tx.inputs.len(),
|
||||
tx.outputs.len());
|
||||
self.tx_pool
|
||||
.write()
|
||||
.unwrap()
|
||||
@@ -149,6 +177,7 @@ struct TxWrapper {
|
||||
/// instance and runs the corresponding HTTP server.
|
||||
pub fn start_rest_apis<T>(addr: String,
|
||||
chain_store: Arc<chain::ChainStore>,
|
||||
chain_head: Arc<Mutex<chain::Tip>>,
|
||||
tx_pool: Arc<RwLock<pool::TransactionPool<T>>>)
|
||||
where T: pool::BlockChain + Clone + Send + Sync + 'static
|
||||
{
|
||||
@@ -157,8 +186,11 @@ pub fn start_rest_apis<T>(addr: String,
|
||||
let mut apis = ApiServer::new("/v1".to_string());
|
||||
apis.register_endpoint("/chain".to_string(),
|
||||
ChainApi { chain_store: chain_store.clone() });
|
||||
apis.register_endpoint("/chain/output".to_string(),
|
||||
OutputApi { chain_store: chain_store.clone() });
|
||||
apis.register_endpoint("/chain/utxo".to_string(),
|
||||
OutputApi {
|
||||
chain_store: chain_store.clone(),
|
||||
chain_head: chain_head.clone(),
|
||||
});
|
||||
apis.register_endpoint("/pool".to_string(), PoolApi { tx_pool: tx_pool });
|
||||
|
||||
apis.start(&addr[..]).unwrap_or_else(|e| {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_store as store;
|
||||
extern crate grin_util as util;
|
||||
extern crate secp256k1zkp as secp;
|
||||
|
||||
|
||||
+16
-1
@@ -34,11 +34,14 @@ use serde::{Serialize, Deserialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json;
|
||||
|
||||
use store;
|
||||
|
||||
/// Errors that can be returned by an ApiEndpoint implementation.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Internal(String),
|
||||
Argument(String),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
@@ -46,6 +49,7 @@ impl Display for Error {
|
||||
match *self {
|
||||
Error::Argument(ref s) => write!(f, "Bad arguments: {}", s),
|
||||
Error::Internal(ref s) => write!(f, "Internal error: {}", s),
|
||||
Error::NotFound => write!(f, "Not found."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +59,7 @@ impl error::Error for Error {
|
||||
match *self {
|
||||
Error::Argument(_) => "Bad arguments.",
|
||||
Error::Internal(_) => "Internal error.",
|
||||
Error::NotFound => "Not found.",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +69,16 @@ impl From<Error> for IronError {
|
||||
match e {
|
||||
Error::Argument(_) => IronError::new(e, status::Status::BadRequest),
|
||||
Error::Internal(_) => IronError::new(e, status::Status::InternalServerError),
|
||||
Error::NotFound => IronError::new(e, status::Status::NotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<store::Error> for Error {
|
||||
fn from(e: store::Error) -> Error {
|
||||
match e {
|
||||
store::Error::NotFoundErr => Error::NotFound,
|
||||
_ => Error::Internal(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,7 +265,7 @@ impl ApiServer {
|
||||
operation: op_s.clone(),
|
||||
endpoint: endpoint.clone(),
|
||||
};
|
||||
let full_path = format!("{}", root.clone());
|
||||
let full_path = format!("{}/{}", root.clone(), op_s.clone());
|
||||
self.router.route(op.to_method(), full_path.clone(), wrapper, route_name);
|
||||
info!("route: POST {}", full_path);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user