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:
commit
d4de1c824f
|
@ -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"
|
||||
|
|
24
README.md
24
README.md
|
@ -82,6 +82,9 @@ rpc_host = "http://localhost"
|
|||
rpc_port = 8545
|
||||
required_confirmations = 0
|
||||
password = "home_password.txt"
|
||||
gas_price_oracle_url = "https://gasprice.poa.network"
|
||||
gas_price_speed = "instant"
|
||||
default_gas_price = 10_000_000_000 # 10 GWEI
|
||||
|
||||
[foreign]
|
||||
account = "0x006e27b6a72e1f34c626762f3c4761547aff1421"
|
||||
|
@ -91,16 +94,12 @@ required_confirmations = 0
|
|||
password = "foreign_password.txt"
|
||||
|
||||
[authorities]
|
||||
accounts = [
|
||||
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
|
||||
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
|
||||
"0x006e27b6a72e1f34c626762f3c4761547aff1421"
|
||||
]
|
||||
required_signatures = 2
|
||||
|
||||
[transactions]
|
||||
deposit_relay = { gas = 3000000, gas_price = 1000000000 }
|
||||
withdraw_relay = { gas = 3000000, gas_price = 1000000000 }
|
||||
withdraw_confirm = { gas = 3000000, gas_price = 1000000000 }
|
||||
deposit_relay = { gas = 3000000 }
|
||||
withdraw_relay = { gas = 3000000 }
|
||||
withdraw_confirm = { gas = 3000000 }
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
@ -118,8 +117,9 @@ withdraw_confirm = { gas = 3000000, gas_price = 1000000000 }
|
|||
- `home/foreign.password` - path to the file containing a password for the validator's account (to decrypt the key from the keystore)
|
||||
- `home/foreign.gas_price_oracle_url` - the URL used to query the current gas-price for the home and foreign nodes, this service is known as the gas-price Oracle. This config option defaults to `None` if not supplied in the User's config TOML file. If this config value is `None`, no Oracle gas-price querying will occur, resulting in the config value for `home/foreign.default_gas_price` being used for all gas-prices.
|
||||
- `home/foreign.gas_price_timeout` - the number of seconds to wait for an HTTP response from the gas price oracle before using the default gas price. Defaults to `10 seconds`.
|
||||
- `home/foreign.gas_price_speed_type` - retrieve the gas-price corresponding to this speed when querying from an Oracle. Defaults to `fast`. The available values are: "instant", "fast", "standard", and "slow".
|
||||
- `home/foreign.gas_price_speed` - retrieve the gas-price corresponding to this speed when querying from an Oracle. Defaults to `fast`. The available values are: "instant", "fast", "standard", and "slow".
|
||||
- `home/foreign.default_gas_price` - the default gas price (in WEI) used in transactions with the home or foreign nodes. The `default_gas_price` is used when the Oracle cannot be reached. The default value is `15_000_000_000` WEI (ie. 15 GWEI).
|
||||
- `home/foreign.concurrent_http_requests` - the number of concurrent HTTP requests allowed in-flight (default: **64**)
|
||||
|
||||
#### authorities options
|
||||
|
||||
|
@ -128,14 +128,8 @@ withdraw_confirm = { gas = 3000000, gas_price = 1000000000 }
|
|||
#### transaction options
|
||||
|
||||
- `transaction.deposit_relay.gas` - specify how much gas should be consumed by deposit relay
|
||||
- `transaction.deposit_relay.gas_price` - specify gas price for deposit relay
|
||||
- `transaction.deposit_relay.concurrency` - how many concurrent transactions can be sent (default: **100**)
|
||||
- `transaction.withdraw_confirm.gas` - specify how much gas should be consumed by withdraw confirm
|
||||
- `transaction.withdraw_confirm.gas_price` - specify gas price for withdraw confirm
|
||||
- `transaction.withdraw_confirm.concurrency` - how many concurrent transactions can be sent (default: **100**)
|
||||
- `transaction.withdraw_relay.gas` - specify how much gas should be consumed by withdraw relay
|
||||
- `transaction.withdraw_relay.gas_price` - specify gas price for withdraw relay
|
||||
- `transaction.withdraw_relay.concurrency` - how many concurrent transactions can be sent (default: **100**)
|
||||
|
||||
### Database file format
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
|
||||
|
|
|
@ -61,6 +61,10 @@ error_chain! {
|
|||
description("contextualized error")
|
||||
display("{:?} in {}", err, context)
|
||||
}
|
||||
OtherError(error: String) {
|
||||
description("other error")
|
||||
display("{}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)?;
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue