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]] [[package]]
name = "bridge" name = "bridge"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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-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 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_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)", "serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
@ -179,9 +180,9 @@ dependencies = [
[[package]] [[package]]
name = "bridge-cli" name = "bridge-cli"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"bridge 0.2.0", "bridge 0.3.0",
"ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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 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_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)", "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]] [[package]]
@ -1068,7 +1070,7 @@ dependencies = [
name = "integration-tests" name = "integration-tests"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bridge 0.2.0", "bridge 0.3.0",
"ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "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-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)", "ethabi-derive 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2334,7 +2336,7 @@ dependencies = [
name = "tests" name = "tests"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bridge 0.2.0", "bridge 0.3.0",
"ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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]] [[package]]
name = "version_check" name = "version_check"
version = "0.1.3" 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 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 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 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 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 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" "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 rpc_port = 8545
required_confirmations = 0 required_confirmations = 0
password = "home_password.txt" 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] [foreign]
account = "0x006e27b6a72e1f34c626762f3c4761547aff1421" account = "0x006e27b6a72e1f34c626762f3c4761547aff1421"
@ -91,16 +94,12 @@ required_confirmations = 0
password = "foreign_password.txt" password = "foreign_password.txt"
[authorities] [authorities]
accounts = [ required_signatures = 2
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
"0x006e27b6a72e1f34c626762f3c4761547aff1421"
]
[transactions] [transactions]
deposit_relay = { gas = 3000000, gas_price = 1000000000 } deposit_relay = { gas = 3000000 }
withdraw_relay = { gas = 3000000, gas_price = 1000000000 } withdraw_relay = { gas = 3000000 }
withdraw_confirm = { gas = 3000000, gas_price = 1000000000 } withdraw_confirm = { gas = 3000000 }
``` ```
#### Options #### 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.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_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_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.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 #### authorities options
@ -128,14 +128,8 @@ withdraw_confirm = { gas = 3000000, gas_price = 1000000000 }
#### transaction options #### transaction options
- `transaction.deposit_relay.gas` - specify how much gas should be consumed by deposit relay - `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` - 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` - 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 ### 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] [package]
name = "bridge" name = "bridge"
version = "0.2.0" version = "0.3.0"
[dependencies] [dependencies]
futures = "0.1" futures = "0.1"
@ -32,6 +32,9 @@ hyper-tls = "0.1.3"
tempdir = "0.3" tempdir = "0.3"
quickcheck = "0.6.1" quickcheck = "0.6.1"
[build-dependencies]
rustc_version = "0.2.2"
[features] [features]
default = [] default = []
deploy = [] deploy = []

View File

@ -1,6 +1,27 @@
extern crate rustc_version;
use std::process::Command; 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() { fn main() {
check_rustc_version();
// rerun build script if bridge contract has changed. // rerun build script if bridge contract has changed.
// without this cargo doesn't since the bridge contract // without this cargo doesn't since the bridge contract
// is outside the crate directories // is outside the crate directories

View File

@ -31,13 +31,13 @@ pub struct Connections<T> where T: Transport {
} }
impl Connections<Http> { 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(ErrorKind::Web3)
.map_err(Error::from) .map_err(Error::from)
.chain_err(||"Cannot connect to home node rpc")?; .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(ErrorKind::Web3)
.map_err(Error::from) .map_err(Error::from)
.chain_err(||"Cannot connect to foreign node rpc")?; .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 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 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 = EthStore::open(Box::new(RootDiskDirectory::at(&config.keystore))).map_err(|e| ErrorKind::KeyStore(e))?;
let keystore = AccountProvider::new(Box::new(keystore), AccountProviderSettings { let keystore = AccountProvider::new(Box::new(keystore), AccountProviderSettings {

View File

@ -1,5 +1,5 @@
use std::sync::{Arc, RwLock}; 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::Transport;
use web3::types::{U256, Address, Bytes, Log, FilterBuilder}; use web3::types::{U256, Address, Bytes, Log, FilterBuilder};
use ethabi::RawLog; use ethabi::RawLog;
@ -11,6 +11,7 @@ use util::web3_filter;
use app::App; use app::App;
use ethcore_transaction::{Transaction, Action}; use ethcore_transaction::{Transaction, Action};
use super::nonce::{NonceCheck, SendRawTransaction}; use super::nonce::{NonceCheck, SendRawTransaction};
use super::BridgeChecked;
use itertools::Itertools; use itertools::Itertools;
fn deposits_filter(home: &home::HomeBridge, address: Address) -> FilterBuilder { fn deposits_filter(home: &home::HomeBridge, address: Address) -> FilterBuilder {
@ -35,7 +36,7 @@ enum DepositRelayState<T: Transport> {
Wait, Wait,
/// Relaying deposits in progress. /// Relaying deposits in progress.
RelayDeposits { RelayDeposits {
future: Collect<Buffered<IterOk<::std::vec::IntoIter<NonceCheck<T, SendRawTransaction<T>>>, Error>>>, future: Collect<FuturesUnordered<NonceCheck<T, SendRawTransaction<T>>>>,
block: u64, block: u64,
}, },
/// All deposits till given block has been relayed. /// All deposits till given block has been relayed.
@ -72,7 +73,7 @@ pub struct DepositRelay<T: Transport> {
} }
impl<T: Transport> Stream for DepositRelay<T> { impl<T: Transport> Stream for DepositRelay<T> {
type Item = u64; type Item = BridgeChecked;
type Error = Error; type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::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); info!("relaying {} deposits", len);
DepositRelayState::RelayDeposits { DepositRelayState::RelayDeposits {
future: iter_ok(deposits).buffered(self.app.config.txs.deposit_relay.concurrency).collect(), future: futures_unordered(deposits).collect(),
block: item.to, block: item.to,
} }
}, },
@ -126,7 +127,7 @@ impl<T: Transport> Stream for DepositRelay<T> {
}, },
DepositRelayState::Yield(ref mut block) => match block.take() { DepositRelayState::Yield(ref mut block) => match block.take() {
None => DepositRelayState::Wait, None => DepositRelayState::Wait,
some => return Ok(some.into()), Some(v) => return Ok(Some(BridgeChecked::DepositRelay(v)).into()),
} }
}; };
self.state = next_state; self.state = next_state;

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use futures::{Async, Future, Poll, Stream}; 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 hyper_tls::HttpsConnector;
use serde_json as json; use serde_json as json;
use tokio_core::reactor::Handle; use tokio_core::reactor::Handle;
@ -12,43 +12,72 @@ use config::{GasPriceSpeed, Node};
use error::Error; use error::Error;
const CACHE_TIMEOUT_DURATION: Duration = Duration::from_secs(5 * 60); 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, Initial,
WaitingForResponse(Timeout<Box<Future<Item = Chunk, Error = Error>>>), WaitingForResponse(Timeout<F>),
Yield(Option<u64>), Yield(Option<u64>),
} }
pub struct GasPriceStream { pub trait Retriever {
state: State, type Item: AsRef<[u8]>;
client: Client<HttpsConnector<HttpConnector>>, 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, uri: Uri,
speed: GasPriceSpeed, speed: GasPriceSpeed,
request_timer: Timer, request_timer: Timer,
interval: Interval, interval: Interval,
last_price: u64,
request_timeout: Duration,
} }
impl GasPriceStream { impl StandardGasPriceStream {
pub fn new(node: &Node, handle: &Handle, timer: &Timer) -> Self { pub fn new(node: &Node, handle: &Handle, timer: &Timer) -> Self {
let client = Client::configure() let client = Client::configure()
.connector(HttpsConnector::new(4, handle).unwrap()) .connector(HttpsConnector::new(4, handle).unwrap())
.build(handle); .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(); let uri: Uri = node.gas_price_oracle_url.clone().unwrap().parse().unwrap();
GasPriceStream { GasPriceStream {
state: State::Initial, state: State::Initial,
client, retriever,
uri, uri,
speed: node.gas_price_speed, speed: node.gas_price_speed,
request_timer: timer.clone(), request_timer: timer.clone(),
interval: timer.interval_at(Instant::now(), CACHE_TIMEOUT_DURATION), 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 Item = u64;
type Error = Error; type Error = Error;
@ -58,15 +87,10 @@ impl Stream for GasPriceStream {
State::Initial => { State::Initial => {
let _ = try_stream!(self.interval.poll()); let _ = try_stream!(self.interval.poll());
let request: Box<Future<Item = Chunk, Error = Error>> = let request = self.retriever.retrieve(&self.uri);
Box::new(
self.client.get(self.uri.clone())
.and_then(|resp| resp.body().concat2())
.map_err(|e| e.into())
);
let request_future = self.request_timer let request_future = self.request_timer
.timeout(request, REQUEST_TIMEOUT_DURATION); .timeout(request, self.request_timeout);
State::WaitingForResponse(request_future) State::WaitingForResponse(request_future)
}, },
@ -74,21 +98,37 @@ impl Stream for GasPriceStream {
match request_future.poll() { match request_future.poll() {
Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(chunk)) => { Ok(Async::Ready(chunk)) => {
let json_obj: HashMap<String, json::Value> = json::from_slice(&chunk)?; match json::from_slice::<HashMap<String, json::Value>>(chunk.as_ref()) {
Ok(json_obj) => {
let gas_price = match json_obj.get(self.speed.as_str()) { 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, Some(json::Value::Number(price)) => State::Yield(Some((price.as_f64().unwrap() * 1_000_000_000.0).trunc() as u64)),
_ => unreachable!(), _ => {
}; 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))
State::Yield(Some(gas_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() { State::Yield(ref mut opt) => match opt.take() {
None => State::Initial, 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 web3::types::U256;
use app::App; use app::App;
use database::Database; use database::Database;
use error::{Error, ErrorKind, Result}; use error::{Error, ErrorKind};
use tokio_core::reactor::Handle; use tokio_core::reactor::Handle;
pub use self::deploy::{Deploy, Deployed, create_deploy}; 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::deposit_relay::{DepositRelay, create_deposit_relay};
pub use self::withdraw_relay::{WithdrawRelay, create_withdraw_relay}; pub use self::withdraw_relay::{WithdrawRelay, create_withdraw_relay};
pub use self::withdraw_confirm::{WithdrawConfirm, create_withdraw_confirm}; 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. /// Last block checked by the bridge components.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -34,69 +34,63 @@ pub enum BridgeChecked {
WithdrawConfirm(u64), WithdrawConfirm(u64),
} }
pub trait BridgeBackend { pub struct Bridge<ES: Stream<Item = BridgeChecked>> {
fn save(&mut self, checks: Vec<BridgeChecked>) -> Result<()>;
}
pub struct FileBackend {
path: PathBuf, path: PathBuf,
database: Database, database: Database,
event_stream: ES,
} }
impl BridgeBackend for FileBackend { impl<ES: Stream<Item = BridgeChecked, Error = Error>> Stream for Bridge<ES> {
fn save(&mut self, checks: Vec<BridgeChecked>) -> Result<()> { type Item = ();
for check in checks { type Error = Error;
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;
},
}
}
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() let file = fs::OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.open(&self.path)?; .open(&self.path)?;
self.database.save(file) self.database.save(file)?;
Ok(Async::Ready(Some(())))
} }
}
enum BridgeStatus {
Wait,
NextItem(Option<()>),
} }
/// Creates new bridge. /// 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> { 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>> {
let backend = FileBackend { Bridge {
path: app.database_path.clone(), path: app.database_path.clone(),
database: init.clone(), database: init.clone(),
}; event_stream: create_bridge_event_stream(app, init, handle, home_chain_id, foreign_chain_id),
}
create_bridge_backed_by(app, init, backend, handle, home_chain_id, foreign_chain_id)
} }
/// Creates new bridge writing to custom backend. /// 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 home_balance = Arc::new(RwLock::new(None));
let foreign_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 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) Some(stream)
} else { } else {
None None
}; };
let foreign_gas_stream = if app.config.foreign.gas_price_oracle_url.is_some() { 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) Some(stream)
} else { } else {
None 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 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)); 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()), 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()), home_balance_check: create_balance_check(app.clone(), app.connections.home.clone(), app.config.home.clone()),
foreign_balance: foreign_balance.clone(), foreign_balance: foreign_balance.clone(),
home_balance: home_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()), bridge,
withdraw_relay: create_withdraw_relay(app.clone(), init, home_balance.clone(), home_chain_id, home_gas_price.clone()), state: BridgeStatus::Init,
withdraw_confirm: create_withdraw_confirm(app.clone(), init, foreign_balance.clone(), foreign_chain_id, foreign_gas_price.clone()),
state: BridgeStatus::Wait,
backend,
running: app.running.clone(), running: app.running.clone(),
home_gas_stream, home_gas_stream,
foreign_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>, home_balance_check: BalanceCheck<T>,
foreign_balance_check: BalanceCheck<T>, foreign_balance_check: BalanceCheck<T>,
home_balance: Arc<RwLock<Option<U256>>>, home_balance: Arc<RwLock<Option<U256>>>,
foreign_balance: Arc<RwLock<Option<U256>>>, foreign_balance: Arc<RwLock<Option<U256>>>,
deposit_relay: DepositRelay<T>, bridge: Box<Stream<Item = BridgeChecked, Error = Error> + 'a>,
withdraw_relay: WithdrawRelay<T>,
withdraw_confirm: WithdrawConfirm<T>,
state: BridgeStatus, state: BridgeStatus,
backend: F,
running: Arc<AtomicBool>, running: Arc<AtomicBool>,
home_gas_stream: Option<GasPriceStream>, home_gas_stream: Option<StandardGasPriceStream>,
foreign_gas_stream: Option<GasPriceStream>, foreign_gas_stream: Option<StandardGasPriceStream>,
home_gas_price: Arc<RwLock<u64>>, home_gas_price: Arc<RwLock<u64>>,
foreign_gas_price: Arc<RwLock<u64>>, foreign_gas_price: Arc<RwLock<u64>>,
} }
use std::sync::atomic::{AtomicBool, Ordering}; 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> { fn check_balances(&mut self) -> Poll<Option<()>, Error> {
let mut home_balance = self.home_balance.write().unwrap(); let mut home_balance = self.home_balance.write().unwrap();
let mut foreign_balance = self.foreign_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> { impl<'a, T: Transport + 'a> Stream for BridgeEventStream<'a, T> {
type Item = (); type Item = BridgeChecked;
type Error = Error; type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
loop { loop {
let next_state = match self.state { let next_state = match self.state {
BridgeStatus::Init => {
match self.check_balances()? {
Async::NotReady => return Ok(Async::NotReady),
_ => (),
}
BridgeStatus::Wait
},
BridgeStatus::Wait => { BridgeStatus::Wait => {
if !self.running.load(Ordering::SeqCst) { if !self.running.load(Ordering::SeqCst) {
return Err(ErrorKind::ShutdownRequested.into()) 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 _ = self.get_gas_prices();
let d_relay = try_bridge!(self.deposit_relay.poll().map_err(|e| ErrorKind::ContextualizedError(Box::new(e), "deposit_relay"))) let item = try_stream!(self.bridge.poll());
.map(BridgeChecked::DepositRelay); BridgeStatus::NextItem(Some(item))
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(()))
}
}, },
BridgeStatus::NextItem(ref mut v) => match v.take() { BridgeStatus::NextItem(ref mut v) => match v.take() {
None => BridgeStatus::Wait, None => BridgeStatus::Init,
some => return Ok(some.into()), some => {
}, return Ok(some.into());
}
}
}; };
self.state = next_state; self.state = next_state;
@ -256,28 +223,43 @@ mod tests {
extern crate tempdir; extern crate tempdir;
use self::tempdir::TempDir; use self::tempdir::TempDir;
use database::Database; 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] #[test]
fn test_file_backend() { fn test_database_updates() {
let tempdir = TempDir::new("test_file_backend").unwrap(); let tempdir = TempDir::new("test_file_backend").unwrap();
let mut path = tempdir.path().to_owned(); let mut path = tempdir.path().to_owned();
path.push("db"); path.push("db");
let mut backend = FileBackend {
let bridge = Bridge {
path: path.clone(), path: path.clone(),
database: Database::default(), database: Database::default(),
event_stream: stream::iter_ok::<_, Error>(vec![BridgeChecked::DepositRelay(1)]),
}; };
backend.save(vec![BridgeChecked::DepositRelay(1)]).unwrap(); let mut event_loop = Core::new().unwrap();
assert_eq!(1, backend.database.checked_deposit_relay); let _ = event_loop.run(bridge.collect());
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 loaded = Database::load(path).unwrap(); let db = Database::load(&path).unwrap();
assert_eq!(backend.database, loaded); 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::sync::{Arc, RwLock};
use std::ops; 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::Transport;
use web3::types::{U256, H520, Address, Bytes, FilterBuilder}; use web3::types::{U256, H520, Address, Bytes, FilterBuilder};
use api::{self, LogStream}; use api::{self, LogStream};
@ -13,6 +13,7 @@ use message_to_mainnet::{MessageToMainnet, MESSAGE_LENGTH};
use ethcore_transaction::{Transaction, Action}; use ethcore_transaction::{Transaction, Action};
use itertools::Itertools; use itertools::Itertools;
use super::nonce::{NonceCheck, SendRawTransaction}; use super::nonce::{NonceCheck, SendRawTransaction};
use super::BridgeChecked;
fn withdraws_filter(foreign: &foreign::ForeignBridge, address: Address) -> FilterBuilder { fn withdraws_filter(foreign: &foreign::ForeignBridge, address: Address) -> FilterBuilder {
let filter = foreign.events().withdraw().create_filter(); let filter = foreign.events().withdraw().create_filter();
@ -30,7 +31,7 @@ enum WithdrawConfirmState<T: Transport> {
Wait, Wait,
/// Confirming withdraws. /// Confirming withdraws.
ConfirmWithdraws { ConfirmWithdraws {
future: Collect<Buffered<IterOk<::std::vec::IntoIter<NonceCheck<T, SendRawTransaction<T>>>, Error>>>, future: Collect<FuturesUnordered<NonceCheck<T, SendRawTransaction<T>>>>,
block: u64, block: u64,
}, },
/// All withdraws till given block has been confirmed. /// All withdraws till given block has been confirmed.
@ -68,7 +69,7 @@ pub struct WithdrawConfirm<T: Transport> {
} }
impl<T: Transport> Stream for WithdrawConfirm<T> { impl<T: Transport> Stream for WithdrawConfirm<T> {
type Item = u64; type Item = BridgeChecked;
type Error = Error; type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::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); info!("submitting {} signatures", len);
WithdrawConfirmState::ConfirmWithdraws { WithdrawConfirmState::ConfirmWithdraws {
future: iter_ok(confirmations).buffered(self.app.config.txs.withdraw_confirm.concurrency).collect(), future: futures_unordered(confirmations).collect(),
block, block,
} }
}, },
@ -153,7 +154,7 @@ impl<T: Transport> Stream for WithdrawConfirm<T> {
info!("waiting for new withdraws that should get signed"); info!("waiting for new withdraws that should get signed");
WithdrawConfirmState::Wait WithdrawConfirmState::Wait
}, },
some => return Ok(some.into()), Some(v) => return Ok(Some(BridgeChecked::WithdrawConfirm(v)).into()),
} }
}; };
self.state = next_state; self.state = next_state;

View File

@ -1,5 +1,5 @@
use std::sync::{Arc, RwLock}; 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 futures::future::{JoinAll, join_all, Join};
use tokio_timer::Timeout; use tokio_timer::Timeout;
use web3::Transport; use web3::Transport;
@ -15,6 +15,7 @@ use message_to_mainnet::MessageToMainnet;
use signature::Signature; use signature::Signature;
use ethcore_transaction::{Transaction, Action}; use ethcore_transaction::{Transaction, Action};
use super::nonce::{NonceCheck, SendRawTransaction}; use super::nonce::{NonceCheck, SendRawTransaction};
use super::BridgeChecked;
use itertools::Itertools; use itertools::Itertools;
/// returns a filter for `ForeignBridge.CollectedSignatures` events /// returns a filter for `ForeignBridge.CollectedSignatures` events
@ -72,7 +73,7 @@ pub enum WithdrawRelayState<T: Transport> {
block: u64, block: u64,
}, },
RelayWithdraws { RelayWithdraws {
future: Collect<Buffered<IterOk<::std::vec::IntoIter<NonceCheck<T, SendRawTransaction<T>>>, Error>>>, future: Collect<FuturesUnordered<NonceCheck<T, SendRawTransaction<T>>>>,
block: u64, block: u64,
}, },
Yield(Option<u64>), Yield(Option<u64>),
@ -111,7 +112,7 @@ pub struct WithdrawRelay<T: Transport> {
} }
impl<T: Transport> Stream for WithdrawRelay<T> { impl<T: Transport> Stream for WithdrawRelay<T> {
type Item = u64; type Item = BridgeChecked;
type Error = Error; type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::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); info!("relaying {} withdraws", len);
WithdrawRelayState::RelayWithdraws { WithdrawRelayState::RelayWithdraws {
future: iter_ok(relays).buffered(self.app.config.txs.withdraw_relay.concurrency).collect(), future: futures_unordered(relays).collect(),
block, block,
} }
}, },
@ -254,7 +255,7 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
info!("waiting for signed withdraws to relay"); info!("waiting for signed withdraws to relay");
WithdrawRelayState::Wait WithdrawRelayState::Wait
}, },
Some(block) => return Ok(Async::Ready(Some(block))), Some(v) => return Ok(Some(BridgeChecked::WithdrawRelay(v)).into()),
} }
}; };
self.state = next_state; self.state = next_state;

View File

@ -15,7 +15,7 @@ const DEFAULT_POLL_INTERVAL: u64 = 1;
const DEFAULT_CONFIRMATIONS: usize = 12; const DEFAULT_CONFIRMATIONS: usize = 12;
const DEFAULT_TIMEOUT: u64 = 3600; const DEFAULT_TIMEOUT: u64 = 3600;
const DEFAULT_RPC_PORT: u16 = 8545; 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_SPEED: GasPriceSpeed = GasPriceSpeed::Fast;
const DEFAULT_GAS_PRICE_TIMEOUT_SECS: u64 = 10; const DEFAULT_GAS_PRICE_TIMEOUT_SECS: u64 = 10;
const DEFAULT_GAS_PRICE_WEI: u64 = 15_000_000_000; const DEFAULT_GAS_PRICE_WEI: u64 = 15_000_000_000;
@ -50,6 +50,7 @@ impl Config {
home: Node::from_load_struct(config.home)?, home: Node::from_load_struct(config.home)?,
foreign: Node::from_load_struct(config.foreign)?, foreign: Node::from_load_struct(config.foreign)?,
authorities: Authorities { authorities: Authorities {
#[cfg(feature = "deploy")]
accounts: config.authorities.accounts, accounts: config.authorities.accounts,
#[cfg(feature = "deploy")] #[cfg(feature = "deploy")]
required_signatures: config.authorities.required_signatures, required_signatures: config.authorities.required_signatures,
@ -80,6 +81,7 @@ pub struct Node {
pub gas_price_speed: GasPriceSpeed, pub gas_price_speed: GasPriceSpeed,
pub gas_price_timeout: Duration, pub gas_price_timeout: Duration,
pub default_gas_price: u64, pub default_gas_price: u64,
pub concurrent_http_requests: usize,
} }
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
@ -107,7 +109,7 @@ impl PartialEq for NodeInfo {
impl Node { impl Node {
fn from_load_struct(node: load::Node) -> Result<Node, Error> { fn from_load_struct(node: load::Node) -> Result<Node, Error> {
let gas_price_oracle_url = node.gas_price_oracle_url.clone(); let gas_price_oracle_url = node.gas_price_oracle_url.clone();
let gas_price_speed = match node.gas_price_speed { let gas_price_speed = match node.gas_price_speed {
Some(ref s) => GasPriceSpeed::from_str(s).unwrap(), Some(ref s) => GasPriceSpeed::from_str(s).unwrap(),
None => DEFAULT_GAS_PRICE_SPEED 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 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 { let result = Node {
account: node.account, account: node.account,
@ -142,6 +145,7 @@ impl Node {
gas_price_speed, gas_price_speed,
gas_price_timeout, gas_price_timeout,
default_gas_price, default_gas_price,
concurrent_http_requests,
}; };
Ok(result) Ok(result)
@ -186,7 +190,6 @@ impl Transactions {
pub struct TransactionConfig { pub struct TransactionConfig {
pub gas: u64, pub gas: u64,
pub gas_price: u64, pub gas_price: u64,
pub concurrency: usize,
} }
impl TransactionConfig { impl TransactionConfig {
@ -194,7 +197,6 @@ impl TransactionConfig {
TransactionConfig { TransactionConfig {
gas: cfg.gas.unwrap_or_default(), gas: cfg.gas.unwrap_or_default(),
gas_price: cfg.gas_price.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)] #[derive(Debug, PartialEq, Clone)]
pub struct Authorities { pub struct Authorities {
#[cfg(feature = "deploy")]
pub accounts: Vec<Address>, pub accounts: Vec<Address>,
#[cfg(feature = "deploy")] #[cfg(feature = "deploy")]
pub required_signatures: u32, pub required_signatures: u32,
@ -222,7 +225,7 @@ pub enum GasPriceSpeed {
impl FromStr for GasPriceSpeed { impl FromStr for GasPriceSpeed {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let speed = match s { let speed = match s {
"instant" => GasPriceSpeed::Instant, "instant" => GasPriceSpeed::Instant,
@ -254,6 +257,7 @@ mod load {
use web3::types::Address; use web3::types::Address;
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config { pub struct Config {
pub home: Node, pub home: Node,
pub foreign: Node, pub foreign: Node,
@ -265,6 +269,7 @@ mod load {
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Node { pub struct Node {
pub account: Address, pub account: Address,
#[cfg(feature = "deploy")] #[cfg(feature = "deploy")]
@ -279,9 +284,11 @@ mod load {
pub gas_price_speed: Option<String>, pub gas_price_speed: Option<String>,
pub gas_price_timeout: Option<u64>, pub gas_price_timeout: Option<u64>,
pub default_gas_price: Option<u64>, pub default_gas_price: Option<u64>,
pub concurrent_http_requests: Option<usize>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Transactions { pub struct Transactions {
#[cfg(feature = "deploy")] #[cfg(feature = "deploy")]
pub home_deploy: Option<TransactionConfig>, pub home_deploy: Option<TransactionConfig>,
@ -297,7 +304,6 @@ mod load {
pub struct TransactionConfig { pub struct TransactionConfig {
pub gas: Option<u64>, pub gas: Option<u64>,
pub gas_price: Option<u64>, pub gas_price: Option<u64>,
pub concurrency: Option<usize>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -308,6 +314,8 @@ mod load {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Authorities { pub struct Authorities {
#[cfg(feature = "deploy")]
#[serde(default)]
pub accounts: Vec<Address>, pub accounts: Vec<Address>,
#[cfg(feature = "deploy")] #[cfg(feature = "deploy")]
pub required_signatures: u32, pub required_signatures: u32,
@ -330,7 +338,6 @@ mod tests {
fn load_full_setup_from_str() { fn load_full_setup_from_str() {
let toml = r#" let toml = r#"
keystore = "/keys" keystore = "/keys"
estimated_gas_cost_of_withdraw = 100000
[home] [home]
account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b" account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b"
@ -340,27 +347,16 @@ rpc_host = "127.0.0.1"
rpc_port = 8545 rpc_port = 8545
password = "password" password = "password"
[home.contract]
bin = "../compiled_contracts/HomeBridge.bin"
[foreign] [foreign]
account = "0x0000000000000000000000000000000000000001" account = "0x0000000000000000000000000000000000000001"
rpc_host = "127.0.0.1" rpc_host = "127.0.0.1"
rpc_port = 8545 rpc_port = 8545
password = "password" password = "password"
[foreign.contract]
bin = "../compiled_contracts/ForeignBridge.bin"
[authorities] [authorities]
accounts = [ required_signatures = 2
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
]
[transactions] [transactions]
home_deploy = { gas = 20 }
"#; "#;
#[allow(unused_mut)] #[allow(unused_mut)]
@ -368,10 +364,6 @@ home_deploy = { gas = 20 }
txs: Transactions::default(), txs: Transactions::default(),
home: Node { home: Node {
account: "1B68Cb0B50181FC4006Ce572cF346e596E51818b".into(), 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), poll_interval: Duration::from_secs(2),
request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT),
required_confirmations: 100, required_confirmations: 100,
@ -383,13 +375,10 @@ home_deploy = { gas = 20 }
gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_speed: DEFAULT_GAS_PRICE_SPEED,
gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS),
default_gas_price: DEFAULT_GAS_PRICE_WEI, default_gas_price: DEFAULT_GAS_PRICE_WEI,
concurrent_http_requests: DEFAULT_CONCURRENCY,
}, },
foreign: Node { foreign: Node {
account: "0000000000000000000000000000000000000001".into(), 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), poll_interval: Duration::from_secs(1),
request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT),
required_confirmations: 12, required_confirmations: 12,
@ -401,27 +390,16 @@ home_deploy = { gas = 20 }
gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_speed: DEFAULT_GAS_PRICE_SPEED,
gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS),
default_gas_price: DEFAULT_GAS_PRICE_WEI, default_gas_price: DEFAULT_GAS_PRICE_WEI,
concurrent_http_requests: DEFAULT_CONCURRENCY,
}, },
authorities: Authorities { authorities: Authorities {
#[cfg(feature = "deploy")]
accounts: vec![ accounts: vec![
"0000000000000000000000000000000000000001".into(),
"0000000000000000000000000000000000000002".into(),
"0000000000000000000000000000000000000003".into(),
], ],
}, },
#[cfg(feature = "deploy")]
estimated_gas_cost_of_withdraw: 100_000,
keystore: "/keys/".into(), 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(); let config = Config::load_from_str(toml).unwrap();
assert_eq!(expected, config); assert_eq!(expected, config);
} }
@ -430,39 +408,24 @@ home_deploy = { gas = 20 }
fn load_minimal_setup_from_str() { fn load_minimal_setup_from_str() {
let toml = r#" let toml = r#"
keystore = "/keys/" keystore = "/keys/"
estimated_gas_cost_of_withdraw = 200000000
[home] [home]
account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b" account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b"
rpc_host = "" rpc_host = ""
password = "password" password = "password"
[home.contract]
bin = "../compiled_contracts/HomeBridge.bin"
[foreign] [foreign]
account = "0x0000000000000000000000000000000000000001" account = "0x0000000000000000000000000000000000000001"
rpc_host = "" rpc_host = ""
password = "password" password = "password"
[foreign.contract]
bin = "../compiled_contracts/ForeignBridge.bin"
[authorities] [authorities]
accounts = [ required_signatures = 2
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
]
"#; "#;
let expected = Config { let expected = Config {
txs: Transactions::default(), txs: Transactions::default(),
home: Node { home: Node {
account: "1B68Cb0B50181FC4006Ce572cF346e596E51818b".into(), 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), poll_interval: Duration::from_secs(1),
request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT),
required_confirmations: 12, required_confirmations: 12,
@ -474,13 +437,10 @@ accounts = [
gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_speed: DEFAULT_GAS_PRICE_SPEED,
gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS),
default_gas_price: DEFAULT_GAS_PRICE_WEI, default_gas_price: DEFAULT_GAS_PRICE_WEI,
concurrent_http_requests: DEFAULT_CONCURRENCY,
}, },
foreign: Node { foreign: Node {
account: "0000000000000000000000000000000000000001".into(), 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), poll_interval: Duration::from_secs(1),
request_timeout: Duration::from_secs(DEFAULT_TIMEOUT), request_timeout: Duration::from_secs(DEFAULT_TIMEOUT),
required_confirmations: 12, required_confirmations: 12,
@ -492,16 +452,13 @@ accounts = [
gas_price_speed: DEFAULT_GAS_PRICE_SPEED, gas_price_speed: DEFAULT_GAS_PRICE_SPEED,
gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS), gas_price_timeout: Duration::from_secs(DEFAULT_GAS_PRICE_TIMEOUT_SECS),
default_gas_price: DEFAULT_GAS_PRICE_WEI, default_gas_price: DEFAULT_GAS_PRICE_WEI,
concurrent_http_requests: DEFAULT_CONCURRENCY,
}, },
authorities: Authorities { authorities: Authorities {
#[cfg(feature = "deploy")]
accounts: vec![ accounts: vec![
"0000000000000000000000000000000000000001".into(),
"0000000000000000000000000000000000000002".into(),
"0000000000000000000000000000000000000003".into(),
], ],
}, },
#[cfg(feature = "deploy")]
estimated_gas_cost_of_withdraw: 200_000_000,
keystore: "/keys/".into(), keystore: "/keys/".into(),
}; };

View File

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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bridge-cli" name = "bridge-cli"
version = "0.2.0" version = "0.3.0"
[[bin]] [[bin]]
name = "bridge" name = "bridge"
@ -17,6 +17,7 @@ env_logger = "0.4"
futures = "0.1.14" futures = "0.1.14"
jsonrpc-core = "8.0" jsonrpc-core = "8.0"
ctrlc = { version = "3.1", features = ["termination"] } ctrlc = { version = "3.1", features = ["termination"] }
version = "3"
[features] [features]
default = [] 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 bridge;
extern crate ctrlc; extern crate ctrlc;
extern crate jsonrpc_core as rpc; extern crate jsonrpc_core as rpc;
#[macro_use]
extern crate version;
use std::{env, fs, io}; use std::{env, fs, io};
use std::sync::Arc; use std::sync::Arc;
@ -73,15 +75,18 @@ POA-Ethereum bridge.
Usage: Usage:
bridge --config <config> --database <database> bridge --config <config> --database <database>
bridge -h | --help bridge -h | --help
bridge -v | --version
Options: Options:
-h, --help Display help message and exit. -h, --help Display help message and exit.
-v, --version Print version and exit.
"#; "#;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Args { pub struct Args {
arg_config: PathBuf, arg_config: PathBuf,
arg_database: PathBuf, arg_database: PathBuf,
flag_version: bool,
} }
use std::sync::atomic::{AtomicBool, Ordering}; 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) let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(command).deserialize()).map_err(|e| e.to_string())?; .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"); info!(target: "bridge", "Loading config");
let config = Config::load(args.arg_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_host = "http://rpc.host.for.home"
rpc_port = 8545 rpc_port = 8545
password = "home_password.txt" 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] [foreign]
account = "0x006e27b6a72e1f34c626762f3c4761547aff1421" account = "0x006e27b6a72e1f34c626762f3c4761547aff1421"
required_confirmations = 0 required_confirmations = 0
rpc_host = "https://rpc.host.for.foreign" rpc_host = "https://rpc.host.for.foreign"
rpc_port = 443 rpc_port = 443
default_gas_price = 5_000_000_000 # 5 GWEI
[authorities] [authorities]
accounts = [ required_signatures = 1
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
]
[transactions] [transactions]
home_deploy = { gas = 1000000, gas_price = 1000000000 } home_deploy = { gas = 1000000 }
foreign_deploy = { gas = 3000000, gas_price = 1000000000 } foreign_deploy = { gas = 3000000 }
deposit_relay = { gas = 100000, gas_price = 1000000000 } deposit_relay = { gas = 100000 }