Problem: can't merge "required_signatures is static"

This is because other changes on master are in conflict.

Solution: merge and resolve conflicts

Merge remote-tracking branch 'origin/master' into required-signatures
This commit is contained in:
Yurii Rashkovskii 2018-06-04 14:27:13 -07:00
commit d4de1c824f
No known key found for this signature in database
GPG Key ID: 1D60D7CFD80845FF
17 changed files with 542 additions and 258 deletions

18
Cargo.lock generated
View File

@ -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)" = "<none>"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View File

@ -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

20
RELEASE_NOTES.md Normal file
View File

@ -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

View File

@ -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 = []

View File

@ -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

View File

@ -31,13 +31,13 @@ pub struct Connections<T> where T: Transport {
}
impl Connections<Http> {
pub fn new_http(handle: &Handle, home: &str, foreign: &str) -> Result<Self, Error> {
pub fn new_http(handle: &Handle, home: &str, home_concurrent_connections: usize, foreign: &str, foreign_concurrent_connections: usize) -> Result<Self, Error> {
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<Http> {
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 {

View File

@ -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<T: Transport> {
Wait,
/// Relaying deposits in progress.
RelayDeposits {
future: Collect<Buffered<IterOk<::std::vec::IntoIter<NonceCheck<T, SendRawTransaction<T>>>, Error>>>,
future: Collect<FuturesUnordered<NonceCheck<T, SendRawTransaction<T>>>>,
block: u64,
},
/// All deposits till given block has been relayed.
@ -72,7 +73,7 @@ pub struct DepositRelay<T: Transport> {
}
impl<T: Transport> Stream for DepositRelay<T> {
type Item = u64;
type Item = BridgeChecked;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@ -115,7 +116,7 @@ impl<T: Transport> Stream for DepositRelay<T> {
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<T: Transport> Stream for DepositRelay<T> {
},
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;

View File

@ -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<F> {
Initial,
WaitingForResponse(Timeout<Box<Future<Item = Chunk, Error = Error>>>),
WaitingForResponse(Timeout<F>),
Yield(Option<u64>),
}
pub struct GasPriceStream {
state: State,
client: Client<HttpsConnector<HttpConnector>>,
pub trait Retriever {
type Item: AsRef<[u8]>;
type Future: Future<Item = Self::Item, Error = Error>;
fn retrieve(&self, uri: &Uri) -> Self::Future;
}
impl<C, B> Retriever for Client<C, B> where C: Connect, B: Stream<Item = Chunk, Error = HyperError> + 'static {
type Item = Chunk;
type Future = Box<Future<Item = Self::Item, Error = Error>>;
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<Box<Future<Item = Chunk, Error = Error>>, Client<HttpsConnector<HttpConnector>>, Chunk>;
pub struct GasPriceStream<F, R, I> where I: AsRef<[u8]>, F: Future<Item = I, Error = Error>, R: Retriever<Future = F, Item = F::Item> {
state: State<F>,
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<F, R, I> GasPriceStream<F, R, I> where I: AsRef<[u8]>, F: Future<Item = I, Error = Error>, R: Retriever<Future = F, Item = F::Item> {
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<F, R, I> Stream for GasPriceStream<F, R, I> where I: AsRef<[u8]>, F: Future<Item = I, Error = Error>, R: Retriever<Future = F, Item = F::Item> {
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<Future<Item = Chunk, Error = Error>> =
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<String, json::Value> = 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::<HashMap<String, json::Value>>(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<u8>;
type Future = FutureResult<Self::Item, Error>;
fn retrieve(&self, _uri: &Uri) -> <Self as Retriever>::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<Self::Item, Error>;
fn retrieve(&self, _uri: &Uri) -> <Self as Retriever>::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<Self::Item, Error>;
fn retrieve(&self, _uri: &Uri) -> <Self as Retriever>::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<Self::Item, Error>;
fn retrieve(&self, _uri: &Uri) -> <Self as Retriever>::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<Self::Item, Error>;
fn retrieve(&self, _uri: &Uri) -> <Self as Retriever>::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"),
_ => (),
}
}
}
}

View File

@ -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<BridgeChecked>) -> Result<()>;
}
pub struct FileBackend {
pub struct Bridge<ES: Stream<Item = BridgeChecked>> {
path: PathBuf,
database: Database,
event_stream: ES,
}
impl BridgeBackend for FileBackend {
fn save(&mut self, checks: Vec<BridgeChecked>) -> 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<ES: Stream<Item = BridgeChecked, Error = Error>> Stream for Bridge<ES> {
type Item = ();
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, 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<T: Transport + Clone>(app: Arc<App<T>>, init: &Database, handle: &Handle, home_chain_id: u64, foreign_chain_id: u64) -> Bridge<T, FileBackend> {
let backend = FileBackend {
pub fn create_bridge<'a, T: Transport + 'a + Clone>(app: Arc<App<T>>, init: &Database, handle: &Handle, home_chain_id: u64, foreign_chain_id: u64) -> Bridge<BridgeEventStream<'a, T>> {
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<T: Transport + Clone, F: BridgeBackend>(app: Arc<App<T>>, init: &Database, backend: F, handle: &Handle, home_chain_id: u64, foreign_chain_id: u64) -> Bridge<T, F> {
pub fn create_bridge_event_stream<'a, T: Transport + 'a + Clone>(app: Arc<App<T>>, 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<T: Transport + Clone, F: BridgeBackend>(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<T: Transport + Clone, F: BridgeBackend>(app: Arc<
}
}
pub struct Bridge<T: Transport, F> {
enum BridgeStatus {
Init,
Wait,
NextItem(Option<BridgeChecked>),
}
pub struct BridgeEventStream<'a, T: Transport + 'a> {
home_balance_check: BalanceCheck<T>,
foreign_balance_check: BalanceCheck<T>,
home_balance: Arc<RwLock<Option<U256>>>,
foreign_balance: Arc<RwLock<Option<U256>>>,
deposit_relay: DepositRelay<T>,
withdraw_relay: WithdrawRelay<T>,
withdraw_confirm: WithdrawConfirm<T>,
bridge: Box<Stream<Item = BridgeChecked, Error = Error> + 'a>,
state: BridgeStatus,
backend: F,
running: Arc<AtomicBool>,
home_gas_stream: Option<GasPriceStream>,
foreign_gas_stream: Option<GasPriceStream>,
home_gas_stream: Option<StandardGasPriceStream>,
foreign_gas_stream: Option<StandardGasPriceStream>,
home_gas_price: Arc<RwLock<u64>>,
foreign_gas_price: Arc<RwLock<u64>>,
}
use std::sync::atomic::{AtomicBool, Ordering};
impl<T: Transport, F: BridgeBackend> Bridge<T, F> {
impl<'a, T: Transport + 'a> BridgeEventStream<'a, T> {
fn check_balances(&mut self) -> Poll<Option<()>, Error> {
let mut home_balance = self.home_balance.write().unwrap();
let mut foreign_balance = self.foreign_balance.write().unwrap();
@ -178,72 +181,36 @@ impl<T: Transport, F: BridgeBackend> Bridge<T, F> {
}
}
impl<T: Transport, F: BridgeBackend> Stream for Bridge<T, F> {
type Item = ();
impl<'a, T: Transport + 'a> Stream for BridgeEventStream<'a, T> {
type Item = BridgeChecked;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, 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);
}
}

View File

@ -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<T: Transport> {
Wait,
/// Confirming withdraws.
ConfirmWithdraws {
future: Collect<Buffered<IterOk<::std::vec::IntoIter<NonceCheck<T, SendRawTransaction<T>>>, Error>>>,
future: Collect<FuturesUnordered<NonceCheck<T, SendRawTransaction<T>>>>,
block: u64,
},
/// All withdraws till given block has been confirmed.
@ -68,7 +69,7 @@ pub struct WithdrawConfirm<T: Transport> {
}
impl<T: Transport> Stream for WithdrawConfirm<T> {
type Item = u64;
type Item = BridgeChecked;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@ -139,7 +140,7 @@ impl<T: Transport> Stream for WithdrawConfirm<T> {
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<T: Transport> Stream for WithdrawConfirm<T> {
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;

View File

@ -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<T: Transport> {
block: u64,
},
RelayWithdraws {
future: Collect<Buffered<IterOk<::std::vec::IntoIter<NonceCheck<T, SendRawTransaction<T>>>, Error>>>,
future: Collect<FuturesUnordered<NonceCheck<T, SendRawTransaction<T>>>>,
block: u64,
},
Yield(Option<u64>),
@ -111,7 +112,7 @@ pub struct WithdrawRelay<T: Transport> {
}
impl<T: Transport> Stream for WithdrawRelay<T> {
type Item = u64;
type Item = BridgeChecked;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@ -240,7 +241,7 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
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<T: Transport> Stream for WithdrawRelay<T> {
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;

View File

@ -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<Node, Error> {
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<Address>,
#[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<Self, Self::Err> {
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<String>,
pub gas_price_timeout: Option<u64>,
pub default_gas_price: Option<u64>,
pub concurrent_http_requests: Option<usize>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Transactions {
#[cfg(feature = "deploy")]
pub home_deploy: Option<TransactionConfig>,
@ -297,7 +304,6 @@ mod load {
pub struct TransactionConfig {
pub gas: Option<u64>,
pub gas_price: Option<u64>,
pub concurrency: Option<usize>,
}
#[derive(Deserialize)]
@ -308,6 +314,8 @@ mod load {
#[derive(Deserialize)]
pub struct Authorities {
#[cfg(feature = "deploy")]
#[serde(default)]
pub accounts: Vec<Address>,
#[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(),
};

View File

@ -61,6 +61,10 @@ error_chain! {
description("contextualized error")
display("{:?} in {}", err, context)
}
OtherError(error: String) {
description("other error")
display("{}", error)
}
}
}

View File

@ -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 = []

12
cli/build.rs Normal file
View File

@ -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
}

View File

@ -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 <config> --database <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<S, I>(command: I, running: Arc<AtomicBool>) -> Result<String, UserFac
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(command).deserialize()).map_err(|e| e.to_string())?;
if args.flag_version {
return Ok(version!().into())
}
info!(target: "bridge", "Loading config");
let config = Config::load(args.arg_config)?;

View File

@ -6,19 +6,21 @@ required_confirmations = 0
rpc_host = "http://rpc.host.for.home"
rpc_port = 8545
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"
required_confirmations = 0
rpc_host = "https://rpc.host.for.foreign"
rpc_port = 443
default_gas_price = 5_000_000_000 # 5 GWEI
[authorities]
accounts = [
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
]
required_signatures = 1
[transactions]
home_deploy = { gas = 1000000, gas_price = 1000000000 }
foreign_deploy = { gas = 3000000, gas_price = 1000000000 }
deposit_relay = { gas = 100000, gas_price = 1000000000 }
home_deploy = { gas = 1000000 }
foreign_deploy = { gas = 3000000 }
deposit_relay = { gas = 100000 }