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:
Ignotus Peverell
2017-06-12 16:41:27 -07:00
parent eb9cc7ef13
commit 6523966f9e
13 changed files with 107 additions and 24 deletions
+8 -2
View File
@@ -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
View File
@@ -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| {
+1
View File
@@ -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
View File
@@ -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 {