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]]
|
[[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"
|
||||||
|
|
24
README.md
24
README.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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]
|
[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 = []
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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 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)?;
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
Loading…
Reference in New Issue