diff --git a/Cargo.lock b/Cargo.lock index d02dfe2..d486cd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,7 +147,7 @@ dependencies = [ [[package]] name = "bridge" -version = "0.2.0" +version = "0.3.0" dependencies = [ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -167,6 +167,7 @@ dependencies = [ "quickcheck 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.1 (git+http://github.com/paritytech/parity?rev=991f0ca)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -179,9 +180,9 @@ dependencies = [ [[package]] name = "bridge-cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ - "bridge 0.2.0", + "bridge 0.3.0", "ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -191,6 +192,7 @@ dependencies = [ "serde 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "version 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1068,7 +1070,7 @@ dependencies = [ name = "integration-tests" version = "0.1.0" dependencies = [ - "bridge 0.2.0", + "bridge 0.3.0", "ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi-contract 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi-derive 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2334,7 +2336,7 @@ dependencies = [ name = "tests" version = "0.1.0" dependencies = [ - "bridge 0.2.0", + "bridge 0.3.0", "ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.11.0 (git+http://github.com/paritytech/parity?rev=991f0ca)", "ethereum-types 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2750,6 +2752,11 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "version" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "version_check" version = "0.1.3" @@ -3220,6 +3227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380" "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" +"checksum version 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3a449064fee414fcc201356a3e6c1510f6c8829ed28bb06b91c54ebe208ce065" "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" "checksum vm 0.1.0 (git+http://github.com/paritytech/parity?rev=991f0ca)" = "" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/README.md b/README.md index 988bf91..58713df 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ rpc_host = "http://localhost" rpc_port = 8545 required_confirmations = 0 password = "home_password.txt" +gas_price_oracle_url = "https://gasprice.poa.network" +gas_price_speed = "instant" +default_gas_price = 10_000_000_000 # 10 GWEI [foreign] account = "0x006e27b6a72e1f34c626762f3c4761547aff1421" @@ -91,16 +94,12 @@ required_confirmations = 0 password = "foreign_password.txt" [authorities] -accounts = [ - "0x006e27b6a72e1f34c626762f3c4761547aff1421", - "0x006e27b6a72e1f34c626762f3c4761547aff1421", - "0x006e27b6a72e1f34c626762f3c4761547aff1421" -] +required_signatures = 2 [transactions] -deposit_relay = { gas = 3000000, gas_price = 1000000000 } -withdraw_relay = { gas = 3000000, gas_price = 1000000000 } -withdraw_confirm = { gas = 3000000, gas_price = 1000000000 } +deposit_relay = { gas = 3000000 } +withdraw_relay = { gas = 3000000 } +withdraw_confirm = { gas = 3000000 } ``` #### Options @@ -118,8 +117,9 @@ withdraw_confirm = { gas = 3000000, gas_price = 1000000000 } - `home/foreign.password` - path to the file containing a password for the validator's account (to decrypt the key from the keystore) - `home/foreign.gas_price_oracle_url` - the URL used to query the current gas-price for the home and foreign nodes, this service is known as the gas-price Oracle. This config option defaults to `None` if not supplied in the User's config TOML file. If this config value is `None`, no Oracle gas-price querying will occur, resulting in the config value for `home/foreign.default_gas_price` being used for all gas-prices. - `home/foreign.gas_price_timeout` - the number of seconds to wait for an HTTP response from the gas price oracle before using the default gas price. Defaults to `10 seconds`. -- `home/foreign.gas_price_speed_type` - retrieve the gas-price corresponding to this speed when querying from an Oracle. Defaults to `fast`. The available values are: "instant", "fast", "standard", and "slow". +- `home/foreign.gas_price_speed` - retrieve the gas-price corresponding to this speed when querying from an Oracle. Defaults to `fast`. The available values are: "instant", "fast", "standard", and "slow". - `home/foreign.default_gas_price` - the default gas price (in WEI) used in transactions with the home or foreign nodes. The `default_gas_price` is used when the Oracle cannot be reached. The default value is `15_000_000_000` WEI (ie. 15 GWEI). +- `home/foreign.concurrent_http_requests` - the number of concurrent HTTP requests allowed in-flight (default: **64**) #### authorities options @@ -128,14 +128,8 @@ withdraw_confirm = { gas = 3000000, gas_price = 1000000000 } #### transaction options - `transaction.deposit_relay.gas` - specify how much gas should be consumed by deposit relay -- `transaction.deposit_relay.gas_price` - specify gas price for deposit relay -- `transaction.deposit_relay.concurrency` - how many concurrent transactions can be sent (default: **100**) - `transaction.withdraw_confirm.gas` - specify how much gas should be consumed by withdraw confirm -- `transaction.withdraw_confirm.gas_price` - specify gas price for withdraw confirm -- `transaction.withdraw_confirm.concurrency` - how many concurrent transactions can be sent (default: **100**) - `transaction.withdraw_relay.gas` - specify how much gas should be consumed by withdraw relay -- `transaction.withdraw_relay.gas_price` - specify gas price for withdraw relay -- `transaction.withdraw_relay.concurrency` - how many concurrent transactions can be sent (default: **100**) ### Database file format diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..f82e14e --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,20 @@ +# 0.2.1 + +This release contains a number of bugfixes and a change in handling gas price. +It is no longer set statically but rather dynamically using an external oracle +(see [config example](examples/config.toml)) + +# 0.2.0 + +This release, most notably, fixes a condition in which not all logs might be +retrieved from an RPC endpoint, resulting in the bridge not being able to +see all relevant events. + +It also improves the performance by introducing concurrent transaction batching. + +On the operations side, it'll now print the context of an occurring error +before exiting to help investigating that error. + +# 0.1.0 + +Initial release diff --git a/bridge/Cargo.toml b/bridge/Cargo.toml index 9b94044..9bbfbc1 100644 --- a/bridge/Cargo.toml +++ b/bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bridge" -version = "0.2.0" +version = "0.3.0" [dependencies] futures = "0.1" @@ -32,6 +32,9 @@ hyper-tls = "0.1.3" tempdir = "0.3" quickcheck = "0.6.1" +[build-dependencies] +rustc_version = "0.2.2" + [features] default = [] deploy = [] diff --git a/bridge/build.rs b/bridge/build.rs index 30168db..3f00990 100644 --- a/bridge/build.rs +++ b/bridge/build.rs @@ -1,6 +1,27 @@ +extern crate rustc_version; + use std::process::Command; +use rustc_version::{version as get_rustc_version, Version}; + +fn check_rustc_version() { + let minimum_required_version = Version::new(1, 26, 0); + + if let Ok(version) = get_rustc_version() { + if version < minimum_required_version { + panic!( + "Invalid rustc version, `poa-bridge` requires \ + rustc >= {}, found version: {}", + minimum_required_version, + version + ); + } + } +} + fn main() { + check_rustc_version(); + // rerun build script if bridge contract has changed. // without this cargo doesn't since the bridge contract // is outside the crate directories diff --git a/bridge/src/app.rs b/bridge/src/app.rs index a999cde..70f6980 100644 --- a/bridge/src/app.rs +++ b/bridge/src/app.rs @@ -31,13 +31,13 @@ pub struct Connections where T: Transport { } impl Connections { - pub fn new_http(handle: &Handle, home: &str, foreign: &str) -> Result { + pub fn new_http(handle: &Handle, home: &str, home_concurrent_connections: usize, foreign: &str, foreign_concurrent_connections: usize) -> Result { - let home = Http::with_event_loop(home, handle,1) + let home = Http::with_event_loop(home, handle,home_concurrent_connections) .map_err(ErrorKind::Web3) .map_err(Error::from) .chain_err(||"Cannot connect to home node rpc")?; - let foreign = Http::with_event_loop(foreign, handle, 1) + let foreign = Http::with_event_loop(foreign, handle, foreign_concurrent_connections) .map_err(ErrorKind::Web3) .map_err(Error::from) .chain_err(||"Cannot connect to foreign node rpc")?; @@ -64,7 +64,7 @@ impl App { let home_url:String = format!("{}:{}", config.home.rpc_host, config.home.rpc_port); let foreign_url:String = format!("{}:{}", config.foreign.rpc_host, config.foreign.rpc_port); - let connections = Connections::new_http(handle, home_url.as_ref(), foreign_url.as_ref())?; + let connections = Connections::new_http(handle, home_url.as_ref(), config.home.concurrent_http_requests, foreign_url.as_ref(), config.foreign.concurrent_http_requests)?; let keystore = EthStore::open(Box::new(RootDiskDirectory::at(&config.keystore))).map_err(|e| ErrorKind::KeyStore(e))?; let keystore = AccountProvider::new(Box::new(keystore), AccountProviderSettings { diff --git a/bridge/src/bridge/deposit_relay.rs b/bridge/src/bridge/deposit_relay.rs index 845245a..797e843 100644 --- a/bridge/src/bridge/deposit_relay.rs +++ b/bridge/src/bridge/deposit_relay.rs @@ -1,5 +1,5 @@ use std::sync::{Arc, RwLock}; -use futures::{self, Future, Stream, stream::{Collect, iter_ok, IterOk, Buffered}, Poll}; +use futures::{self, Future, Stream, stream::{Collect, FuturesUnordered, futures_unordered}, Poll}; use web3::Transport; use web3::types::{U256, Address, Bytes, Log, FilterBuilder}; use ethabi::RawLog; @@ -11,6 +11,7 @@ use util::web3_filter; use app::App; use ethcore_transaction::{Transaction, Action}; use super::nonce::{NonceCheck, SendRawTransaction}; +use super::BridgeChecked; use itertools::Itertools; fn deposits_filter(home: &home::HomeBridge, address: Address) -> FilterBuilder { @@ -35,7 +36,7 @@ enum DepositRelayState { Wait, /// Relaying deposits in progress. RelayDeposits { - future: Collect>>, Error>>>, + future: Collect>>>, block: u64, }, /// All deposits till given block has been relayed. @@ -72,7 +73,7 @@ pub struct DepositRelay { } impl Stream for DepositRelay { - type Item = u64; + type Item = BridgeChecked; type Error = Error; fn poll(&mut self) -> Poll, Self::Error> { @@ -115,7 +116,7 @@ impl Stream for DepositRelay { info!("relaying {} deposits", len); DepositRelayState::RelayDeposits { - future: iter_ok(deposits).buffered(self.app.config.txs.deposit_relay.concurrency).collect(), + future: futures_unordered(deposits).collect(), block: item.to, } }, @@ -126,7 +127,7 @@ impl Stream for DepositRelay { }, DepositRelayState::Yield(ref mut block) => match block.take() { None => DepositRelayState::Wait, - some => return Ok(some.into()), + Some(v) => return Ok(Some(BridgeChecked::DepositRelay(v)).into()), } }; self.state = next_state; diff --git a/bridge/src/bridge/gas_price.rs b/bridge/src/bridge/gas_price.rs index d6efc3d..7988635 100644 --- a/bridge/src/bridge/gas_price.rs +++ b/bridge/src/bridge/gas_price.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::time::{Duration, Instant}; use futures::{Async, Future, Poll, Stream}; -use hyper::{Chunk, client::HttpConnector, Client, Uri}; +use hyper::{Chunk, client::{HttpConnector, Connect}, Client, Uri, Error as HyperError}; use hyper_tls::HttpsConnector; use serde_json as json; use tokio_core::reactor::Handle; @@ -12,43 +12,72 @@ use config::{GasPriceSpeed, Node}; use error::Error; const CACHE_TIMEOUT_DURATION: Duration = Duration::from_secs(5 * 60); -const REQUEST_TIMEOUT_DURATION: Duration = Duration::from_secs(30); -enum State { +enum State { Initial, - WaitingForResponse(Timeout>>), + WaitingForResponse(Timeout), Yield(Option), } -pub struct GasPriceStream { - state: State, - client: Client>, +pub trait Retriever { + type Item: AsRef<[u8]>; + type Future: Future; + fn retrieve(&self, uri: &Uri) -> Self::Future; +} + +impl Retriever for Client where C: Connect, B: Stream + 'static { + type Item = Chunk; + type Future = Box>; + + fn retrieve(&self, uri: &Uri) -> Self::Future { + Box::new( + self.get(uri.clone()) + .and_then(|resp| resp.body().concat2()) + .map_err(|e| e.into()) + ) + } +} + +pub type StandardGasPriceStream = GasPriceStream>, Client>, Chunk>; + +pub struct GasPriceStream where I: AsRef<[u8]>, F: Future, R: Retriever { + state: State, + retriever: R, uri: Uri, speed: GasPriceSpeed, request_timer: Timer, interval: Interval, + last_price: u64, + request_timeout: Duration, } -impl GasPriceStream { - pub fn new(node: &Node, handle: &Handle, timer: &Timer) -> Self { - let client = Client::configure() - .connector(HttpsConnector::new(4, handle).unwrap()) - .build(handle); +impl StandardGasPriceStream { + pub fn new(node: &Node, handle: &Handle, timer: &Timer) -> Self { + let client = Client::configure() + .connector(HttpsConnector::new(4, handle).unwrap()) + .build(handle); + GasPriceStream::new_with_retriever(node, client, timer) + } +} +impl GasPriceStream where I: AsRef<[u8]>, F: Future, R: Retriever { + pub fn new_with_retriever(node: &Node, retriever: R, timer: &Timer) -> Self { let uri: Uri = node.gas_price_oracle_url.clone().unwrap().parse().unwrap(); GasPriceStream { state: State::Initial, - client, + retriever, uri, speed: node.gas_price_speed, request_timer: timer.clone(), interval: timer.interval_at(Instant::now(), CACHE_TIMEOUT_DURATION), + last_price: node.default_gas_price, + request_timeout: node.gas_price_timeout, } } } -impl Stream for GasPriceStream { +impl Stream for GasPriceStream where I: AsRef<[u8]>, F: Future, R: Retriever { type Item = u64; type Error = Error; @@ -58,15 +87,10 @@ impl Stream for GasPriceStream { State::Initial => { let _ = try_stream!(self.interval.poll()); - let request: Box> = - Box::new( - self.client.get(self.uri.clone()) - .and_then(|resp| resp.body().concat2()) - .map_err(|e| e.into()) - ); + let request = self.retriever.retrieve(&self.uri); let request_future = self.request_timer - .timeout(request, REQUEST_TIMEOUT_DURATION); + .timeout(request, self.request_timeout); State::WaitingForResponse(request_future) }, @@ -74,21 +98,37 @@ impl Stream for GasPriceStream { match request_future.poll() { Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::Ready(chunk)) => { - let json_obj: HashMap = json::from_slice(&chunk)?; - - let gas_price = match json_obj.get(self.speed.as_str()) { - Some(json::Value::Number(price)) => (price.as_f64().unwrap() * 1_000_000_000.0).trunc() as u64, - _ => unreachable!(), - }; - - State::Yield(Some(gas_price)) + match json::from_slice::>(chunk.as_ref()) { + Ok(json_obj) => { + match json_obj.get(self.speed.as_str()) { + Some(json::Value::Number(price)) => State::Yield(Some((price.as_f64().unwrap() * 1_000_000_000.0).trunc() as u64)), + _ => { + error!("Invalid or missing gas price ({}) in the gas price oracle response: {}", self.speed.as_str(), String::from_utf8_lossy(&*chunk.as_ref())); + State::Yield(Some(self.last_price)) + }, + } + }, + Err(e) => { + error!("Error while parsing response from gas price oracle: {:?} {}", e, String::from_utf8_lossy(&*chunk.as_ref())); + State::Yield(Some(self.last_price)) + } + } + }, + Err(e) => { + error!("Error while fetching gas price: {:?}", e); + State::Yield(Some(self.last_price)) }, - Err(e) => panic!(e), } }, State::Yield(ref mut opt) => match opt.take() { None => State::Initial, - price => return Ok(Async::Ready(price)), + Some(price) => { + if price != self.last_price { + info!("Gas price: {} gwei", (price as f64) / 1_000_000_000.0); + self.last_price = price; + } + return Ok(Async::Ready(Some(price))) + }, } }; @@ -96,3 +136,231 @@ impl Stream for GasPriceStream { } } } + +#[cfg(test)] +mod tests { + + use super::*; + use error::{Error, ErrorKind}; + use futures::{Async, future::{err, ok, FutureResult}}; + use config::{Node, NodeInfo, DEFAULT_CONCURRENCY}; + use tokio_timer::Timer; + use std::time::Duration; + use std::path::PathBuf; + use web3::types::Address; + use std::str::FromStr; + + struct ErroredRequest; + + impl Retriever for ErroredRequest { + type Item = Vec; + type Future = FutureResult; + + fn retrieve(&self, _uri: &Uri) -> ::Future { + err(ErrorKind::OtherError("something went wrong".into()).into()) + } + } + + #[test] + fn errored_request() { + let node = Node { + account: Address::new(), + request_timeout: Duration::from_secs(5), + poll_interval: Duration::from_secs(1), + required_confirmations: 0, + rpc_host: "https://rpc".into(), + rpc_port: 443, + password: PathBuf::from("password"), + info: NodeInfo::default(), + gas_price_oracle_url: Some("https://gas.price".into()), + gas_price_speed: GasPriceSpeed::from_str("fast").unwrap(), + gas_price_timeout: Duration::from_secs(5), + default_gas_price: 15_000_000_000, + concurrent_http_requests: DEFAULT_CONCURRENCY, + }; + let timer = Timer::default(); + let mut stream = GasPriceStream::new_with_retriever(&node, ErroredRequest, &timer); + loop { + match stream.poll() { + Ok(Async::Ready(Some(v))) => { + assert_eq!(v, node.default_gas_price); + break; + }, + Err(_) => panic!("should not error out"), + _ => (), + } + } + } + + + struct BadJson; + + impl Retriever for BadJson { + type Item = String; + type Future = FutureResult; + + fn retrieve(&self, _uri: &Uri) -> ::Future { + ok("bad json".into()) + } + } + + #[test] + fn bad_json() { + let node = Node { + account: Address::new(), + request_timeout: Duration::from_secs(5), + poll_interval: Duration::from_secs(1), + required_confirmations: 0, + rpc_host: "https://rpc".into(), + rpc_port: 443, + password: PathBuf::from("password"), + info: NodeInfo::default(), + gas_price_oracle_url: Some("https://gas.price".into()), + gas_price_speed: GasPriceSpeed::from_str("fast").unwrap(), + gas_price_timeout: Duration::from_secs(5), + default_gas_price: 15_000_000_000, + concurrent_http_requests: DEFAULT_CONCURRENCY, + }; + let timer = Timer::default(); + let mut stream = GasPriceStream::new_with_retriever(&node, BadJson, &timer); + loop { + match stream.poll() { + Ok(Async::Ready(Some(v))) => { + assert_eq!(v, node.default_gas_price); + break; + }, + Err(_) => panic!("should not error out"), + _ => (), + } + } + } + + + struct UnexpectedJson; + + impl Retriever for UnexpectedJson { + type Item = String; + type Future = FutureResult; + + fn retrieve(&self, _uri: &Uri) -> ::Future { + ok(r#"{"cow": "moo"}"#.into()) + } + } + + #[test] + fn unexpected_json() { + let node = Node { + account: Address::new(), + request_timeout: Duration::from_secs(5), + poll_interval: Duration::from_secs(1), + required_confirmations: 0, + rpc_host: "https://rpc".into(), + rpc_port: 443, + password: PathBuf::from("password"), + info: NodeInfo::default(), + gas_price_oracle_url: Some("https://gas.price".into()), + gas_price_speed: GasPriceSpeed::from_str("fast").unwrap(), + gas_price_timeout: Duration::from_secs(5), + default_gas_price: 15_000_000_000, + concurrent_http_requests: DEFAULT_CONCURRENCY, + }; + let timer = Timer::default(); + let mut stream = GasPriceStream::new_with_retriever(&node, UnexpectedJson, &timer); + loop { + match stream.poll() { + Ok(Async::Ready(Some(v))) => { + assert_eq!(v, node.default_gas_price); + break; + }, + Err(_) => panic!("should not error out"), + _ => (), + } + } + } + + struct NonObjectJson; + + impl Retriever for NonObjectJson { + type Item = String; + type Future = FutureResult; + + fn retrieve(&self, _uri: &Uri) -> ::Future { + ok("3".into()) + } + } + + #[test] + fn non_object_json() { + let node = Node { + account: Address::new(), + request_timeout: Duration::from_secs(5), + poll_interval: Duration::from_secs(1), + required_confirmations: 0, + rpc_host: "https://rpc".into(), + rpc_port: 443, + password: PathBuf::from("password"), + info: NodeInfo::default(), + gas_price_oracle_url: Some("https://gas.price".into()), + gas_price_speed: GasPriceSpeed::from_str("fast").unwrap(), + gas_price_timeout: Duration::from_secs(5), + default_gas_price: 15_000_000_000, + concurrent_http_requests: DEFAULT_CONCURRENCY, + }; + let timer = Timer::default(); + let mut stream = GasPriceStream::new_with_retriever(&node, NonObjectJson, &timer); + loop { + match stream.poll() { + Ok(Async::Ready(Some(v))) => { + assert_eq!(v, node.default_gas_price); + break; + }, + Err(_) => panic!("should not error out"), + _ => (), + } + } + } + + struct CorrectJson; + + impl Retriever for CorrectJson { + type Item = String; + type Future = FutureResult; + + fn retrieve(&self, _uri: &Uri) -> ::Future { + ok(r#"{"fast": 12.0}"#.into()) + } + } + + #[test] + fn correct_json() { + let node = Node { + account: Address::new(), + request_timeout: Duration::from_secs(5), + poll_interval: Duration::from_secs(1), + required_confirmations: 0, + rpc_host: "https://rpc".into(), + rpc_port: 443, + password: PathBuf::from("password"), + info: NodeInfo::default(), + gas_price_oracle_url: Some("https://gas.price".into()), + gas_price_speed: GasPriceSpeed::from_str("fast").unwrap(), + gas_price_timeout: Duration::from_secs(5), + default_gas_price: 15_000_000_000, + concurrent_http_requests: DEFAULT_CONCURRENCY, + }; + let timer = Timer::default(); + let mut stream = GasPriceStream::new_with_retriever(&node, CorrectJson, &timer); + loop { + match stream.poll() { + Ok(Async::Ready(Some(v))) => { + assert_eq!(v, 12_000_000_000); + break; + }, + Err(_) => panic!("should not error out"), + _ => (), + } + } + } + +} + diff --git a/bridge/src/bridge/mod.rs b/bridge/src/bridge/mod.rs index 5f598ab..251b441 100644 --- a/bridge/src/bridge/mod.rs +++ b/bridge/src/bridge/mod.rs @@ -15,7 +15,7 @@ use web3::Transport; use web3::types::U256; use app::App; use database::Database; -use error::{Error, ErrorKind, Result}; +use error::{Error, ErrorKind}; use tokio_core::reactor::Handle; pub use self::deploy::{Deploy, Deployed, create_deploy}; @@ -24,7 +24,7 @@ pub use self::chain_id::{ChainIdRetrieval, create_chain_id_retrieval}; pub use self::deposit_relay::{DepositRelay, create_deposit_relay}; pub use self::withdraw_relay::{WithdrawRelay, create_withdraw_relay}; pub use self::withdraw_confirm::{WithdrawConfirm, create_withdraw_confirm}; -pub use self::gas_price::GasPriceStream; +pub use self::gas_price::StandardGasPriceStream; /// Last block checked by the bridge components. #[derive(Clone, Copy)] @@ -34,69 +34,63 @@ pub enum BridgeChecked { WithdrawConfirm(u64), } -pub trait BridgeBackend { - fn save(&mut self, checks: Vec) -> Result<()>; -} - -pub struct FileBackend { +pub struct Bridge> { path: PathBuf, database: Database, + event_stream: ES, } -impl BridgeBackend for FileBackend { - fn save(&mut self, checks: Vec) -> Result<()> { - for check in checks { - match check { - BridgeChecked::DepositRelay(n) => { - self.database.checked_deposit_relay = n; - }, - BridgeChecked::WithdrawRelay(n) => { - self.database.checked_withdraw_relay = n; - }, - BridgeChecked::WithdrawConfirm(n) => { - self.database.checked_withdraw_confirm = n; - }, - } - } +impl> Stream for Bridge { + type Item = (); + type Error = Error; + fn poll(&mut self) -> Poll, Self::Error> { + let check = try_stream!(self.event_stream.poll()); + match check { + BridgeChecked::DepositRelay(n) => { + self.database.checked_deposit_relay = n; + }, + BridgeChecked::WithdrawRelay(n) => { + self.database.checked_withdraw_relay = n; + }, + BridgeChecked::WithdrawConfirm(n) => { + self.database.checked_withdraw_confirm = n; + }, + } let file = fs::OpenOptions::new() .write(true) .create(true) .open(&self.path)?; - self.database.save(file) + self.database.save(file)?; + Ok(Async::Ready(Some(()))) } - } - -enum BridgeStatus { - Wait, - NextItem(Option<()>), } + /// Creates new bridge. -pub fn create_bridge(app: Arc>, init: &Database, handle: &Handle, home_chain_id: u64, foreign_chain_id: u64) -> Bridge { - let backend = FileBackend { +pub fn create_bridge<'a, T: Transport + 'a + Clone>(app: Arc>, init: &Database, handle: &Handle, home_chain_id: u64, foreign_chain_id: u64) -> Bridge> { + Bridge { path: app.database_path.clone(), database: init.clone(), - }; - - create_bridge_backed_by(app, init, backend, handle, home_chain_id, foreign_chain_id) + event_stream: create_bridge_event_stream(app, init, handle, home_chain_id, foreign_chain_id), + } } /// Creates new bridge writing to custom backend. -pub fn create_bridge_backed_by(app: Arc>, init: &Database, backend: F, handle: &Handle, home_chain_id: u64, foreign_chain_id: u64) -> Bridge { +pub fn create_bridge_event_stream<'a, T: Transport + 'a + Clone>(app: Arc>, init: &Database, handle: &Handle, home_chain_id: u64, foreign_chain_id: u64) -> BridgeEventStream<'a, T> { let home_balance = Arc::new(RwLock::new(None)); let foreign_balance = Arc::new(RwLock::new(None)); let home_gas_stream = if app.config.home.gas_price_oracle_url.is_some() { - let stream = GasPriceStream::new(&app.config.home, handle, &app.timer); + let stream = StandardGasPriceStream::new(&app.config.home, handle, &app.timer); Some(stream) } else { None }; let foreign_gas_stream = if app.config.foreign.gas_price_oracle_url.is_some() { - let stream = GasPriceStream::new(&app.config.foreign, handle, &app.timer); + let stream = StandardGasPriceStream::new(&app.config.foreign, handle, &app.timer); Some(stream) } else { None @@ -105,16 +99,22 @@ pub fn create_bridge_backed_by(app: Arc< let home_gas_price = Arc::new(RwLock::new(app.config.home.default_gas_price)); let foreign_gas_price = Arc::new(RwLock::new(app.config.foreign.default_gas_price)); - Bridge { + let deposit_relay = create_deposit_relay(app.clone(), init, foreign_balance.clone(), foreign_chain_id, foreign_gas_price.clone()) + .map_err(|e| ErrorKind::ContextualizedError(Box::new(e), "deposit_relay").into()); + let withdraw_relay = create_withdraw_relay(app.clone(), init, home_balance.clone(), home_chain_id, home_gas_price.clone()) + .map_err(|e| ErrorKind::ContextualizedError(Box::new(e), "withdraw_relay").into()); + let withdraw_confirm = create_withdraw_confirm(app.clone(), init, foreign_balance.clone(), foreign_chain_id, foreign_gas_price.clone()) + .map_err(|e| ErrorKind::ContextualizedError(Box::new(e), "withdraw_confirm").into()); + + let bridge = Box::new(deposit_relay.select(withdraw_relay).select(withdraw_confirm)); + + BridgeEventStream { foreign_balance_check: create_balance_check(app.clone(), app.connections.foreign.clone(), app.config.foreign.clone()), home_balance_check: create_balance_check(app.clone(), app.connections.home.clone(), app.config.home.clone()), foreign_balance: foreign_balance.clone(), home_balance: home_balance.clone(), - deposit_relay: create_deposit_relay(app.clone(), init, foreign_balance.clone(), foreign_chain_id, foreign_gas_price.clone()), - withdraw_relay: create_withdraw_relay(app.clone(), init, home_balance.clone(), home_chain_id, home_gas_price.clone()), - withdraw_confirm: create_withdraw_confirm(app.clone(), init, foreign_balance.clone(), foreign_chain_id, foreign_gas_price.clone()), - state: BridgeStatus::Wait, - backend, + bridge, + state: BridgeStatus::Init, running: app.running.clone(), home_gas_stream, foreign_gas_stream, @@ -123,26 +123,29 @@ pub fn create_bridge_backed_by(app: Arc< } } -pub struct Bridge { +enum BridgeStatus { + Init, + Wait, + NextItem(Option), +} + +pub struct BridgeEventStream<'a, T: Transport + 'a> { home_balance_check: BalanceCheck, foreign_balance_check: BalanceCheck, home_balance: Arc>>, foreign_balance: Arc>>, - deposit_relay: DepositRelay, - withdraw_relay: WithdrawRelay, - withdraw_confirm: WithdrawConfirm, + bridge: Box + 'a>, state: BridgeStatus, - backend: F, running: Arc, - home_gas_stream: Option, - foreign_gas_stream: Option, + home_gas_stream: Option, + foreign_gas_stream: Option, home_gas_price: Arc>, foreign_gas_price: Arc>, } use std::sync::atomic::{AtomicBool, Ordering}; -impl Bridge { +impl<'a, T: Transport + 'a> BridgeEventStream<'a, T> { fn check_balances(&mut self) -> Poll, Error> { let mut home_balance = self.home_balance.write().unwrap(); let mut foreign_balance = self.foreign_balance.write().unwrap(); @@ -178,72 +181,36 @@ impl Bridge { } } -impl Stream for Bridge { - type Item = (); +impl<'a, T: Transport + 'a> Stream for BridgeEventStream<'a, T> { + type Item = BridgeChecked; type Error = Error; fn poll(&mut self) -> Poll, Self::Error> { loop { let next_state = match self.state { + BridgeStatus::Init => { + match self.check_balances()? { + Async::NotReady => return Ok(Async::NotReady), + _ => (), + } + BridgeStatus::Wait + }, BridgeStatus::Wait => { if !self.running.load(Ordering::SeqCst) { return Err(ErrorKind::ShutdownRequested.into()) } - // Intended to be used upon startup - let balance_is_absent = { - let mut home_balance = self.home_balance.read().unwrap(); - let mut foreign_balance = self.foreign_balance.read().unwrap(); - home_balance.is_none() || foreign_balance.is_none() - }; - if balance_is_absent { - match self.check_balances()? { - Async::NotReady => return Ok(Async::NotReady), - _ => (), - } - } - let _ = self.get_gas_prices(); - let d_relay = try_bridge!(self.deposit_relay.poll().map_err(|e| ErrorKind::ContextualizedError(Box::new(e), "deposit_relay"))) - .map(BridgeChecked::DepositRelay); - - if d_relay.is_some() { - self.check_balances()?; - } - - - let w_relay = try_bridge!(self.withdraw_relay.poll().map_err(|e| ErrorKind::ContextualizedError(Box::new(e), "withdraw_relay"))). - map(BridgeChecked::WithdrawRelay); - - if w_relay.is_some() { - self.check_balances()?; - } - - - let w_confirm = try_bridge!(self.withdraw_confirm.poll().map_err(|e| ErrorKind::ContextualizedError(Box::new(e), "withdraw_confirm"))). - map(BridgeChecked::WithdrawConfirm); - - if w_confirm.is_some() { - self.check_balances()?; - } - - let result: Vec<_> = [d_relay, w_relay, w_confirm] - .into_iter() - .filter_map(|c| *c) - .collect(); - - if result.is_empty() { - return Ok(Async::NotReady); - } else { - self.backend.save(result)?; - BridgeStatus::NextItem(Some(())) - } + let item = try_stream!(self.bridge.poll()); + BridgeStatus::NextItem(Some(item)) }, BridgeStatus::NextItem(ref mut v) => match v.take() { - None => BridgeStatus::Wait, - some => return Ok(some.into()), - }, + None => BridgeStatus::Init, + some => { + return Ok(some.into()); + } + } }; self.state = next_state; @@ -256,28 +223,43 @@ mod tests { extern crate tempdir; use self::tempdir::TempDir; use database::Database; - use super::{BridgeBackend, FileBackend, BridgeChecked}; + use super::{Bridge, BridgeChecked}; + use error::Error; + use tokio_core::reactor::Core; + use futures::{Stream, stream}; #[test] - fn test_file_backend() { + fn test_database_updates() { let tempdir = TempDir::new("test_file_backend").unwrap(); let mut path = tempdir.path().to_owned(); path.push("db"); - let mut backend = FileBackend { + + let bridge = Bridge { path: path.clone(), database: Database::default(), + event_stream: stream::iter_ok::<_, Error>(vec![BridgeChecked::DepositRelay(1)]), }; - backend.save(vec![BridgeChecked::DepositRelay(1)]).unwrap(); - assert_eq!(1, backend.database.checked_deposit_relay); - assert_eq!(0, backend.database.checked_withdraw_confirm); - assert_eq!(0, backend.database.checked_withdraw_relay); - backend.save(vec![BridgeChecked::DepositRelay(2), BridgeChecked::WithdrawConfirm(3), BridgeChecked::WithdrawRelay(2)]).unwrap(); - assert_eq!(2, backend.database.checked_deposit_relay); - assert_eq!(3, backend.database.checked_withdraw_confirm); - assert_eq!(2, backend.database.checked_withdraw_relay); + let mut event_loop = Core::new().unwrap(); + let _ = event_loop.run(bridge.collect()); - let loaded = Database::load(path).unwrap(); - assert_eq!(backend.database, loaded); + let db = Database::load(&path).unwrap(); + assert_eq!(1, db.checked_deposit_relay); + assert_eq!(0, db.checked_withdraw_confirm); + assert_eq!(0, db.checked_withdraw_relay); + + let bridge = Bridge { + path: path.clone(), + database: Database::default(), + event_stream: stream::iter_ok::<_, Error>(vec![BridgeChecked::DepositRelay(2), BridgeChecked::WithdrawConfirm(3), BridgeChecked::WithdrawRelay(2)]), + }; + + let mut event_loop = Core::new().unwrap(); + let _ = event_loop.run(bridge.collect()); + + let db = Database::load(&path).unwrap(); + assert_eq!(2, db.checked_deposit_relay); + assert_eq!(3, db.checked_withdraw_confirm); + assert_eq!(2, db.checked_withdraw_relay); } } diff --git a/bridge/src/bridge/withdraw_confirm.rs b/bridge/src/bridge/withdraw_confirm.rs index dafdf37..676915b 100644 --- a/bridge/src/bridge/withdraw_confirm.rs +++ b/bridge/src/bridge/withdraw_confirm.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, RwLock}; use std::ops; -use futures::{self, Future, Stream, stream::{Collect, IterOk, iter_ok, Buffered}, Poll}; +use futures::{self, Future, Stream, stream::{Collect, FuturesUnordered, futures_unordered}, Poll}; use web3::Transport; use web3::types::{U256, H520, Address, Bytes, FilterBuilder}; use api::{self, LogStream}; @@ -13,6 +13,7 @@ use message_to_mainnet::{MessageToMainnet, MESSAGE_LENGTH}; use ethcore_transaction::{Transaction, Action}; use itertools::Itertools; use super::nonce::{NonceCheck, SendRawTransaction}; +use super::BridgeChecked; fn withdraws_filter(foreign: &foreign::ForeignBridge, address: Address) -> FilterBuilder { let filter = foreign.events().withdraw().create_filter(); @@ -30,7 +31,7 @@ enum WithdrawConfirmState { Wait, /// Confirming withdraws. ConfirmWithdraws { - future: Collect>>, Error>>>, + future: Collect>>>, block: u64, }, /// All withdraws till given block has been confirmed. @@ -68,7 +69,7 @@ pub struct WithdrawConfirm { } impl Stream for WithdrawConfirm { - type Item = u64; + type Item = BridgeChecked; type Error = Error; fn poll(&mut self) -> Poll, Self::Error> { @@ -139,7 +140,7 @@ impl Stream for WithdrawConfirm { info!("submitting {} signatures", len); WithdrawConfirmState::ConfirmWithdraws { - future: iter_ok(confirmations).buffered(self.app.config.txs.withdraw_confirm.concurrency).collect(), + future: futures_unordered(confirmations).collect(), block, } }, @@ -153,7 +154,7 @@ impl Stream for WithdrawConfirm { info!("waiting for new withdraws that should get signed"); WithdrawConfirmState::Wait }, - some => return Ok(some.into()), + Some(v) => return Ok(Some(BridgeChecked::WithdrawConfirm(v)).into()), } }; self.state = next_state; diff --git a/bridge/src/bridge/withdraw_relay.rs b/bridge/src/bridge/withdraw_relay.rs index a02b15e..a5a7b5b 100644 --- a/bridge/src/bridge/withdraw_relay.rs +++ b/bridge/src/bridge/withdraw_relay.rs @@ -1,5 +1,5 @@ use std::sync::{Arc, RwLock}; -use futures::{self, Async, Future, Stream, stream::{Collect, iter_ok, IterOk, Buffered}, Poll}; +use futures::{self, Future, Stream, stream::{Collect, FuturesUnordered, futures_unordered}, Poll}; use futures::future::{JoinAll, join_all, Join}; use tokio_timer::Timeout; use web3::Transport; @@ -15,6 +15,7 @@ use message_to_mainnet::MessageToMainnet; use signature::Signature; use ethcore_transaction::{Transaction, Action}; use super::nonce::{NonceCheck, SendRawTransaction}; +use super::BridgeChecked; use itertools::Itertools; /// returns a filter for `ForeignBridge.CollectedSignatures` events @@ -72,7 +73,7 @@ pub enum WithdrawRelayState { block: u64, }, RelayWithdraws { - future: Collect>>, Error>>>, + future: Collect>>>, block: u64, }, Yield(Option), @@ -111,7 +112,7 @@ pub struct WithdrawRelay { } impl Stream for WithdrawRelay { - type Item = u64; + type Item = BridgeChecked; type Error = Error; fn poll(&mut self) -> Poll, Self::Error> { @@ -240,7 +241,7 @@ impl Stream for WithdrawRelay { info!("relaying {} withdraws", len); WithdrawRelayState::RelayWithdraws { - future: iter_ok(relays).buffered(self.app.config.txs.withdraw_relay.concurrency).collect(), + future: futures_unordered(relays).collect(), block, } }, @@ -254,7 +255,7 @@ impl Stream for WithdrawRelay { info!("waiting for signed withdraws to relay"); WithdrawRelayState::Wait }, - Some(block) => return Ok(Async::Ready(Some(block))), + Some(v) => return Ok(Some(BridgeChecked::WithdrawRelay(v)).into()), } }; self.state = next_state; diff --git a/bridge/src/config.rs b/bridge/src/config.rs index 70fca92..bbad1b0 100644 --- a/bridge/src/config.rs +++ b/bridge/src/config.rs @@ -15,7 +15,7 @@ const DEFAULT_POLL_INTERVAL: u64 = 1; const DEFAULT_CONFIRMATIONS: usize = 12; const DEFAULT_TIMEOUT: u64 = 3600; const DEFAULT_RPC_PORT: u16 = 8545; -const DEFAULT_CONCURRENCY: usize = 100; +pub(crate) const DEFAULT_CONCURRENCY: usize = 64; const DEFAULT_GAS_PRICE_SPEED: GasPriceSpeed = GasPriceSpeed::Fast; const DEFAULT_GAS_PRICE_TIMEOUT_SECS: u64 = 10; const DEFAULT_GAS_PRICE_WEI: u64 = 15_000_000_000; @@ -50,6 +50,7 @@ impl Config { home: Node::from_load_struct(config.home)?, foreign: Node::from_load_struct(config.foreign)?, authorities: Authorities { + #[cfg(feature = "deploy")] accounts: config.authorities.accounts, #[cfg(feature = "deploy")] required_signatures: config.authorities.required_signatures, @@ -80,6 +81,7 @@ pub struct Node { pub gas_price_speed: GasPriceSpeed, pub gas_price_timeout: Duration, pub default_gas_price: u64, + pub concurrent_http_requests: usize, } use std::sync::{Arc, RwLock}; @@ -107,7 +109,7 @@ impl PartialEq for NodeInfo { impl Node { fn from_load_struct(node: load::Node) -> Result { let gas_price_oracle_url = node.gas_price_oracle_url.clone(); - + let gas_price_speed = match node.gas_price_speed { Some(ref s) => GasPriceSpeed::from_str(s).unwrap(), None => DEFAULT_GAS_PRICE_SPEED @@ -119,6 +121,7 @@ impl Node { }; let default_gas_price = node.default_gas_price.unwrap_or(DEFAULT_GAS_PRICE_WEI); + let concurrent_http_requests = node.concurrent_http_requests.unwrap_or(DEFAULT_CONCURRENCY); let result = Node { account: node.account, @@ -142,6 +145,7 @@ impl Node { gas_price_speed, gas_price_timeout, default_gas_price, + concurrent_http_requests, }; Ok(result) @@ -186,7 +190,6 @@ impl Transactions { pub struct TransactionConfig { pub gas: u64, pub gas_price: u64, - pub concurrency: usize, } impl TransactionConfig { @@ -194,7 +197,6 @@ impl TransactionConfig { TransactionConfig { gas: cfg.gas.unwrap_or_default(), gas_price: cfg.gas_price.unwrap_or_default(), - concurrency: cfg.concurrency.unwrap_or(DEFAULT_CONCURRENCY), } } } @@ -207,6 +209,7 @@ pub struct ContractConfig { #[derive(Debug, PartialEq, Clone)] pub struct Authorities { + #[cfg(feature = "deploy")] pub accounts: Vec
, #[cfg(feature = "deploy")] pub required_signatures: u32, @@ -222,7 +225,7 @@ pub enum GasPriceSpeed { impl FromStr for GasPriceSpeed { type Err = (); - + fn from_str(s: &str) -> Result { let speed = match s { "instant" => GasPriceSpeed::Instant, @@ -254,6 +257,7 @@ mod load { use web3::types::Address; #[derive(Deserialize)] + #[serde(deny_unknown_fields)] pub struct Config { pub home: Node, pub foreign: Node, @@ -265,6 +269,7 @@ mod load { } #[derive(Deserialize)] + #[serde(deny_unknown_fields)] pub struct Node { pub account: Address, #[cfg(feature = "deploy")] @@ -279,9 +284,11 @@ mod load { pub gas_price_speed: Option, pub gas_price_timeout: Option, pub default_gas_price: Option, + pub concurrent_http_requests: Option, } #[derive(Deserialize)] + #[serde(deny_unknown_fields)] pub struct Transactions { #[cfg(feature = "deploy")] pub home_deploy: Option, @@ -297,7 +304,6 @@ mod load { pub struct TransactionConfig { pub gas: Option, pub gas_price: Option, - pub concurrency: Option, } #[derive(Deserialize)] @@ -308,6 +314,8 @@ mod load { #[derive(Deserialize)] pub struct Authorities { + #[cfg(feature = "deploy")] + #[serde(default)] pub accounts: Vec
, #[cfg(feature = "deploy")] pub required_signatures: u32, @@ -330,7 +338,6 @@ mod tests { fn load_full_setup_from_str() { let toml = r#" keystore = "/keys" -estimated_gas_cost_of_withdraw = 100000 [home] account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b" @@ -340,27 +347,16 @@ rpc_host = "127.0.0.1" rpc_port = 8545 password = "password" -[home.contract] -bin = "../compiled_contracts/HomeBridge.bin" - [foreign] account = "0x0000000000000000000000000000000000000001" rpc_host = "127.0.0.1" rpc_port = 8545 password = "password" -[foreign.contract] -bin = "../compiled_contracts/ForeignBridge.bin" - [authorities] -accounts = [ - "0x0000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000003" -] +required_signatures = 2 [transactions] -home_deploy = { gas = 20 } "#; #[allow(unused_mut)] @@ -368,10 +364,6 @@ home_deploy = { gas = 20 } txs: Transactions::default(), home: Node { account: "1B68Cb0B50181FC4006Ce572cF346e596E51818b".into(), - #[cfg(feature = "deploy")] - contract: ContractConfig { - bin: include_str!("../../compiled_contracts/HomeBridge.bin").from_hex().unwrap().into(), - }, poll_interval: Duration::from_secs(2), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), required_confirmations: 100, @@ -383,13 +375,10 @@ home_deploy = { gas = 20 } gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), default_gas_price: DEFAULT_GAS_PRICE_WEI, + concurrent_http_requests: DEFAULT_CONCURRENCY, }, foreign: Node { account: "0000000000000000000000000000000000000001".into(), - #[cfg(feature = "deploy")] - contract: ContractConfig { - bin: include_str!("../../compiled_contracts/ForeignBridge.bin").from_hex().unwrap().into(), - }, poll_interval: Duration::from_secs(1), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), required_confirmations: 12, @@ -401,27 +390,16 @@ home_deploy = { gas = 20 } gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), default_gas_price: DEFAULT_GAS_PRICE_WEI, + concurrent_http_requests: DEFAULT_CONCURRENCY, }, authorities: Authorities { + #[cfg(feature = "deploy")] accounts: vec![ - "0000000000000000000000000000000000000001".into(), - "0000000000000000000000000000000000000002".into(), - "0000000000000000000000000000000000000003".into(), ], }, - #[cfg(feature = "deploy")] - estimated_gas_cost_of_withdraw: 100_000, keystore: "/keys/".into(), }; - #[cfg(feature = "deploy")] { - expected.txs.home_deploy = TransactionConfig { - gas: 20, - gas_price: 0, - concurrency: DEFAULT_CONCURRENCY, - }; - } - let config = Config::load_from_str(toml).unwrap(); assert_eq!(expected, config); } @@ -430,39 +408,24 @@ home_deploy = { gas = 20 } fn load_minimal_setup_from_str() { let toml = r#" keystore = "/keys/" -estimated_gas_cost_of_withdraw = 200000000 [home] account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b" rpc_host = "" password = "password" -[home.contract] -bin = "../compiled_contracts/HomeBridge.bin" - [foreign] account = "0x0000000000000000000000000000000000000001" rpc_host = "" password = "password" -[foreign.contract] -bin = "../compiled_contracts/ForeignBridge.bin" - [authorities] -accounts = [ - "0x0000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000003" -] +required_signatures = 2 "#; let expected = Config { txs: Transactions::default(), home: Node { account: "1B68Cb0B50181FC4006Ce572cF346e596E51818b".into(), - #[cfg(feature = "deploy")] - contract: ContractConfig { - bin: include_str!("../../compiled_contracts/HomeBridge.bin").from_hex().unwrap().into(), - }, poll_interval: Duration::from_secs(1), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), required_confirmations: 12, @@ -474,13 +437,10 @@ accounts = [ gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), default_gas_price: DEFAULT_GAS_PRICE_WEI, + concurrent_http_requests: DEFAULT_CONCURRENCY, }, foreign: Node { account: "0000000000000000000000000000000000000001".into(), - #[cfg(feature = "deploy")] - contract: ContractConfig { - bin: include_str!("../../compiled_contracts/ForeignBridge.bin").from_hex().unwrap().into(), - }, poll_interval: Duration::from_secs(1), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), required_confirmations: 12, @@ -492,16 +452,13 @@ accounts = [ gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), default_gas_price: DEFAULT_GAS_PRICE_WEI, + concurrent_http_requests: DEFAULT_CONCURRENCY, }, authorities: Authorities { + #[cfg(feature = "deploy")] accounts: vec![ - "0000000000000000000000000000000000000001".into(), - "0000000000000000000000000000000000000002".into(), - "0000000000000000000000000000000000000003".into(), ], }, - #[cfg(feature = "deploy")] - estimated_gas_cost_of_withdraw: 200_000_000, keystore: "/keys/".into(), }; diff --git a/bridge/src/error.rs b/bridge/src/error.rs index 01af16a..3c53f35 100644 --- a/bridge/src/error.rs +++ b/bridge/src/error.rs @@ -61,6 +61,10 @@ error_chain! { description("contextualized error") display("{:?} in {}", err, context) } + OtherError(error: String) { + description("other error") + display("{}", error) + } } } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c866912..159ab9c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bridge-cli" -version = "0.2.0" +version = "0.3.0" [[bin]] name = "bridge" @@ -17,6 +17,7 @@ env_logger = "0.4" futures = "0.1.14" jsonrpc-core = "8.0" ctrlc = { version = "3.1", features = ["termination"] } +version = "3" [features] default = [] diff --git a/cli/build.rs b/cli/build.rs new file mode 100644 index 0000000..c363065 --- /dev/null +++ b/cli/build.rs @@ -0,0 +1,12 @@ +use std::process::Command; + +fn main() { + let cmd = Command::new("git").args(&["describe", "--long", "--tags", "--always", "--dirty=-modified"]).output().unwrap(); + if cmd.status.success() { + // if we're successful, use this as a version + let ver = std::str::from_utf8(&cmd.stdout[1..]).unwrap().trim(); // drop "v" in the front + println!("cargo:rustc-env={}={}", "CARGO_PKG_VERSION", ver); + } + // otherwise, whatever is specified in Cargo manifest + println!("cargo:rerun-if-changed=nonexistentfile"); // always rerun build.rs +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 38d48d6..a24b24c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -10,6 +10,8 @@ extern crate env_logger; extern crate bridge; extern crate ctrlc; extern crate jsonrpc_core as rpc; +#[macro_use] +extern crate version; use std::{env, fs, io}; use std::sync::Arc; @@ -73,15 +75,18 @@ POA-Ethereum bridge. Usage: bridge --config --database bridge -h | --help + bridge -v | --version Options: -h, --help Display help message and exit. + -v, --version Print version and exit. "#; #[derive(Debug, Deserialize)] pub struct Args { arg_config: PathBuf, arg_database: PathBuf, + flag_version: bool, } use std::sync::atomic::{AtomicBool, Ordering}; @@ -118,6 +123,10 @@ fn execute(command: I, running: Arc) -> Result