Fixing etherscan price parsing (#4202)

* Fixing etherscan price parsing

* Handling all errors
This commit is contained in:
Tomasz Drwięga 2017-01-18 17:56:21 +01:00 committed by Gav Wood
parent 3040a1c83e
commit b4ff08beb8
2 changed files with 52 additions and 31 deletions

View File

@ -148,7 +148,8 @@ impl GasPriceCalibrator {
if Instant::now() >= self.next_calibration { if Instant::now() >= self.next_calibration {
let usd_per_tx = self.options.usd_per_tx; let usd_per_tx = self.options.usd_per_tx;
trace!(target: "miner", "Getting price info"); trace!(target: "miner", "Getting price info");
let price_info = PriceInfo::get(move |price: PriceInfo| {
PriceInfo::get(move |price: PriceInfo| {
trace!(target: "miner", "Price info arrived: {:?}", price); trace!(target: "miner", "Price info arrived: {:?}", price);
let usd_per_eth = price.ethusd; let usd_per_eth = price.ethusd;
let wei_per_usd: f32 = 1.0e18 / usd_per_eth; let wei_per_usd: f32 = 1.0e18 / usd_per_eth;
@ -158,11 +159,7 @@ impl GasPriceCalibrator {
set_price(U256::from(wei_per_gas as u64)); set_price(U256::from(wei_per_gas as u64));
}); });
if price_info.is_ok() {
self.next_calibration = Instant::now() + self.options.recalibration_period; self.next_calibration = Instant::now() + self.options.recalibration_period;
} else {
warn!(target: "miner", "Unable to update Ether price.");
}
} }
} }
} }

View File

@ -21,7 +21,7 @@ use std::time::Duration;
use std::str::FromStr; use std::str::FromStr;
use std::sync::mpsc; use std::sync::mpsc;
use hyper::client::{Handler, Request, Response, Client}; use hyper::client::{Handler, Request, Response, Client};
use hyper::{Next, Encoder, Decoder}; use hyper::{Url, Next, Encoder, Decoder};
use hyper::net::HttpStream; use hyper::net::HttpStream;
#[derive(Debug)] #[derive(Debug)]
@ -29,12 +29,12 @@ pub struct PriceInfo {
pub ethusd: f32, pub ethusd: f32,
} }
pub struct SetPriceHandler<F: Fn(PriceInfo) + Sync + Send + 'static> { pub struct SetPriceHandler<F> {
set_price: F, set_price: F,
channel: mpsc::Sender<()>, channel: mpsc::Sender<()>,
} }
impl<F: Fn(PriceInfo) + Sync + Send + 'static> Drop for SetPriceHandler<F> { impl<F> Drop for SetPriceHandler<F> {
fn drop(&mut self) { fn drop(&mut self) {
let _ = self.channel.send(()); let _ = self.channel.send(());
} }
@ -47,37 +47,61 @@ impl<F: Fn(PriceInfo) + Sync + Send + 'static> Handler<HttpStream> for SetPriceH
fn on_response_readable(&mut self, r: &mut Decoder<HttpStream>) -> Next { fn on_response_readable(&mut self, r: &mut Decoder<HttpStream>) -> Next {
let mut body = String::new(); let mut body = String::new();
let _ = r.read_to_string(&mut body).ok() let info = r.read_to_string(&mut body)
.and_then(|_| Json::from_str(&body).ok()) .map_err(|e| format!("Unable to read response: {:?}", e))
.and_then(|json| json.find_path(&["result", "ethusd"]) .and_then(|_| self.process_response(&body));
.and_then(|obj| match *obj {
Json::String(ref s) => Some((self.set_price)(PriceInfo { if let Err(e) = info {
ethusd: FromStr::from_str(s) warn!("Failed to auto-update latest ETH price: {:?}", e);
.expect("Etherscan API will always return properly formatted price; qed") }
})),
_ => None,
}));
Next::end() Next::end()
} }
}
impl<F: Fn(PriceInfo) + Sync + Send + 'static> SetPriceHandler<F> {
fn process_response(&self, body: &str) -> Result<(), String> {
let json = Json::from_str(body).map_err(|e| format!("Invalid JSON returned: {:?}", e))?;
let obj = json.find_path(&["result", "ethusd"]).ok_or("USD price not found".to_owned())?;
let ethusd = match *obj {
Json::String(ref s) => FromStr::from_str(s).ok(),
_ => None,
}.ok_or("Unexpected price format.".to_owned())?;
(self.set_price)(PriceInfo {
ethusd: ethusd,
});
Ok(())
}
} }
impl PriceInfo { impl PriceInfo {
pub fn get<F: Fn(PriceInfo) + Sync + Send + 'static>(set_price: F) -> Result<(), ()> { pub fn get<F: Fn(PriceInfo) + Sync + Send + 'static>(set_price: F) {
// TODO: Handle each error type properly
let client = Client::new().map_err(|_| ())?;
thread::spawn(move || { thread::spawn(move || {
let (tx, rx) = mpsc::channel();
let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice") let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice")
.expect("string known to be a valid URL; qed"); .expect("string known to be a valid URL; qed");
let _ = client.request(
if let Err(e) = Self::request(url, set_price) {
warn!("Failed to auto-update latest ETH price: {:?}", e);
}
});
}
fn request<F: Fn(PriceInfo) + Send + Sync + 'static>(url: Url, set_price: F) -> Result<(), String> {
let (tx, rx) = mpsc::channel();
let client = Client::new().map_err(|e| format!("Unable to start client: {:?}", e))?;
client.request(
url, url,
SetPriceHandler { SetPriceHandler {
set_price: set_price, set_price: set_price,
channel: tx, channel: tx,
}).ok().and_then(|_| rx.recv().ok()); },
).map_err(|_| "Request failed.".to_owned())?;
// Wait for exit
let _ = rx.recv().map_err(|e| format!("Request interrupted: {:?}", e))?;
client.close(); client.close();
});
Ok(()) Ok(())
} }
} }
@ -93,7 +117,7 @@ fn should_get_price_info() {
let done = Arc::new((Mutex::new(PriceInfo { ethusd: 0f32 }), Condvar::new())); let done = Arc::new((Mutex::new(PriceInfo { ethusd: 0f32 }), Condvar::new()));
let rdone = done.clone(); let rdone = done.clone();
PriceInfo::get(move |price| { let mut p = rdone.0.lock(); *p = price; rdone.1.notify_one(); }).unwrap(); PriceInfo::get(move |price| { let mut p = rdone.0.lock(); *p = price; rdone.1.notify_one(); });
let mut p = done.0.lock(); let mut p = done.0.lock();
let t = done.1.wait_for(&mut p, Duration::from_millis(10000)); let t = done.1.wait_for(&mut p, Duration::from_millis(10000));
assert!(!t.timed_out()); assert!(!t.timed_out());