diff --git a/bridge/src/app.rs b/bridge/src/app.rs index d6fbf261..be01f052 100644 --- a/bridge/src/app.rs +++ b/bridge/src/app.rs @@ -5,7 +5,7 @@ use web3::Transport; use web3::transports::ipc::Ipc; use error::{Error, ResultExt, ErrorKind}; use config::Config; -use contracts::{home, foreign}; +use contracts::{home, foreign, validator}; pub struct App where T: Transport { pub config: Config, @@ -13,6 +13,7 @@ pub struct App where T: Transport { pub connections: Connections, pub home_bridge: home::HomeBridge, pub foreign_bridge: foreign::ForeignBridge, + pub validators: validator::ValidatorSet, pub timer: Timer, } @@ -58,6 +59,7 @@ impl App { connections, home_bridge: home::HomeBridge::default(), foreign_bridge: foreign::ForeignBridge::default(), + validators: validator::ValidatorSet::default(), timer: Timer::default(), }; Ok(result) @@ -72,6 +74,7 @@ impl App { database_path: self.database_path.clone(), home_bridge: home::HomeBridge::default(), foreign_bridge: foreign::ForeignBridge::default(), + validators: validator::ValidatorSet::default(), timer: self.timer.clone(), } } diff --git a/bridge/src/authorities.rs b/bridge/src/authorities.rs new file mode 100644 index 00000000..aa0ab779 --- /dev/null +++ b/bridge/src/authorities.rs @@ -0,0 +1,73 @@ +use std::sync::Arc; +use futures::{Future, Poll, Async}; +use tokio_timer::Timeout; +use web3::Transport; +use web3::types::{Address, Bytes, H160}; + +use api::{self, ApiCall}; +use error::Result; +use config::AuthoritiesSource; +use contracts::validator; +use app::App; +use error::Error; + +#[inline] +fn get_validator_payload(validators: &validator::ValidatorSet) -> Bytes { + validators.functions().get_validators().input().into() +} + +#[inline] +fn get_validators_output(validators: &validator::ValidatorSet, output: &[u8]) -> Result> { + Ok(validators.functions().get_validators().output(output)?.into_iter().map(H160).collect()) +} + +pub fn fetch_authorities(app: Arc>) -> FetchAuthorities { + FetchAuthorities { + app, + state: FetchAuthoritiesState::Wait, + } +} + +enum FetchAuthoritiesState { + Wait, + Call(Timeout>), +} + +pub struct FetchAuthorities { + app: Arc>, + state: FetchAuthoritiesState, +} + +impl Future for FetchAuthorities { + type Item = Vec
; + type Error = Error; + + fn poll(&mut self) -> Poll { + let validator_address = match self.app.config.authorities.source { + AuthoritiesSource::Accounts(ref accounts) => return Ok(Async::Ready(accounts.clone())), + AuthoritiesSource::ValidatorSet(ref address) => address.clone(), + }; + + loop { + let next_state = match self.state { + FetchAuthoritiesState::Wait => { + let future = self.app.timer.timeout( + api::call( + &self.app.connections.foreign, + validator_address, + get_validator_payload(&self.app.validators) + ), self.app.config.foreign.request_timeout + ); + FetchAuthoritiesState::Call(future) + }, + FetchAuthoritiesState::Call(ref mut future) => { + let bytes = try_ready!(future.poll()); + let auths = get_validators_output(&self.app.validators, &bytes.0)?; + return Ok(Async::Ready(auths)); + }, + }; + + self.state = next_state; + } + } +} diff --git a/bridge/src/bridge/deploy.rs b/bridge/src/bridge/deploy.rs index 05e6a68c..799a1815 100644 --- a/bridge/src/bridge/deploy.rs +++ b/bridge/src/bridge/deploy.rs @@ -4,6 +4,7 @@ use web3::Transport; use web3::confirm::SendTransactionWithConfirmation; use web3::types::{TransactionRequest}; use app::App; +use authorities::{fetch_authorities, FetchAuthorities}; use database::Database; use error::{Error, ErrorKind}; use {api, ethabi}; @@ -17,6 +18,7 @@ pub enum Deployed { enum DeployState { CheckIfNeeded, + FetchAuthorities(FetchAuthorities), Deploying(future::Join, SendTransactionWithConfirmation>), } @@ -41,57 +43,59 @@ impl Future for Deploy { let next_state = match self.state { DeployState::CheckIfNeeded => match Database::load(&self.app.database_path).map_err(ErrorKind::from) { Ok(database) => return Ok(Deployed::Existing(database).into()), - Err(ErrorKind::MissingFile(_)) => { - let main_data = self.app.home_bridge.constructor( - self.app.config.home.contract.bin.clone().0, - ethabi::util::pad_u32(self.app.config.authorities.required_signatures), - self.app.config.authorities.accounts.iter().map(|a| a.0.clone()).collect::>() - ); - let test_data = self.app.foreign_bridge.constructor( - self.app.config.foreign.contract.bin.clone().0, - ethabi::util::pad_u32(self.app.config.authorities.required_signatures), - self.app.config.authorities.accounts.iter().map(|a| a.0.clone()).collect::>() - ); + Err(ErrorKind::MissingFile(_)) => DeployState::FetchAuthorities(fetch_authorities(self.app.clone())), + Err(err) => return Err(err.into()), + }, + DeployState::FetchAuthorities(ref mut future) => { + let authorities = try_ready!(future.poll()); + let main_data = self.app.home_bridge.constructor( + self.app.config.home.contract.bin.clone().0, + ethabi::util::pad_u32(self.app.config.authorities.required_signatures), + authorities.clone().into_iter().map(|a| a.0).collect::>(), + ); + let test_data = self.app.foreign_bridge.constructor( + self.app.config.foreign.contract.bin.clone().0, + ethabi::util::pad_u32(self.app.config.authorities.required_signatures), + authorities.into_iter().map(|a| a.0).collect::>(), + ); - let main_tx_request = TransactionRequest { - from: self.app.config.home.account, - to: None, - gas: Some(self.app.config.txs.home_deploy.gas.into()), - gas_price: Some(self.app.config.txs.home_deploy.gas_price.into()), - value: None, - data: Some(main_data.into()), - nonce: None, - condition: None, - }; + let main_tx_request = TransactionRequest { + from: self.app.config.home.account, + to: None, + gas: Some(self.app.config.txs.home_deploy.gas.into()), + gas_price: Some(self.app.config.txs.home_deploy.gas_price.into()), + value: None, + data: Some(main_data.into()), + nonce: None, + condition: None, + }; - let test_tx_request = TransactionRequest { - from: self.app.config.foreign.account, - to: None, - gas: Some(self.app.config.txs.foreign_deploy.gas.into()), - gas_price: Some(self.app.config.txs.foreign_deploy.gas_price.into()), - value: None, - data: Some(test_data.into()), - nonce: None, - condition: None, - }; + let test_tx_request = TransactionRequest { + from: self.app.config.foreign.account, + to: None, + gas: Some(self.app.config.txs.foreign_deploy.gas.into()), + gas_price: Some(self.app.config.txs.foreign_deploy.gas_price.into()), + value: None, + data: Some(test_data.into()), + nonce: None, + condition: None, + }; - let main_future = api::send_transaction_with_confirmation( - self.app.connections.home.clone(), - main_tx_request, - self.app.config.home.poll_interval, - self.app.config.home.required_confirmations - ); + let main_future = api::send_transaction_with_confirmation( + self.app.connections.home.clone(), + main_tx_request, + self.app.config.home.poll_interval, + self.app.config.home.required_confirmations + ); - let test_future = api::send_transaction_with_confirmation( - self.app.connections.foreign.clone(), - test_tx_request, - self.app.config.foreign.poll_interval, - self.app.config.foreign.required_confirmations - ); + let test_future = api::send_transaction_with_confirmation( + self.app.connections.foreign.clone(), + test_tx_request, + self.app.config.foreign.poll_interval, + self.app.config.foreign.required_confirmations + ); - DeployState::Deploying(main_future.join(test_future)) - }, - Err(err) => return Err(err.into()), + DeployState::Deploying(main_future.join(test_future)) }, DeployState::Deploying(ref mut future) => { let (main_receipt, test_receipt) = try_ready!(future.poll().map_err(ErrorKind::Web3)); diff --git a/bridge/src/config.rs b/bridge/src/config.rs index 473232a1..8ba8d0ef 100644 --- a/bridge/src/config.rs +++ b/bridge/src/config.rs @@ -38,7 +38,7 @@ impl Config { home: Node::from_load_struct(config.home)?, foreign: Node::from_load_struct(config.foreign)?, authorities: Authorities { - accounts: config.authorities.accounts, + source: AuthoritiesSource::from_load_struct(config.authorities.source), required_signatures: config.authorities.required_signatures, }, txs: config.transactions.map(Transactions::from_load_struct).unwrap_or_default(), @@ -123,10 +123,29 @@ pub struct ContractConfig { #[derive(Debug, PartialEq, Clone)] pub struct Authorities { - pub accounts: Vec
, + pub source: AuthoritiesSource, pub required_signatures: u32, } +#[derive(Debug, PartialEq, Clone)] +pub enum AuthoritiesSource { + /// Authorities source defined as a list of accounts. + Accounts(Vec
), + /// Address of the `ValidatorSet` contract used to fetch authorities. + /// + /// https://github.com/paritytech/parity/wiki/Validator-Set + ValidatorSet(Address), +} + +impl AuthoritiesSource { + fn from_load_struct(cfg: load::AuthoritiesSource) -> Self { + match cfg { + load::AuthoritiesSource::Accounts(accounts) => AuthoritiesSource::Accounts(accounts), + load::AuthoritiesSource::ValidatorSet(address) => AuthoritiesSource::ValidatorSet(address), + } + } +} + /// Some config values may not be defined in `toml` file, but they should be specified at runtime. /// `load` module separates `Config` representation in file with optional from the one used /// in application. @@ -180,16 +199,26 @@ mod load { #[derive(Deserialize)] #[serde(deny_unknown_fields)] pub struct Authorities { - pub accounts: Vec
, + pub source: AuthoritiesSource, pub required_signatures: u32, } + + #[derive(Deserialize)] + #[serde(deny_unknown_fields)] + #[serde(tag = "type", content = "value")] + pub enum AuthoritiesSource { + #[serde(rename = "accounts")] + Accounts(Vec
), + #[serde(rename = "validator_set")] + ValidatorSet(Address), + } } #[cfg(test)] mod tests { use std::time::Duration; use rustc_hex::FromHex; - use super::{Config, Node, ContractConfig, Transactions, Authorities, TransactionConfig}; + use super::{Config, Node, ContractConfig, Transactions, Authorities, TransactionConfig, AuthoritiesSource}; #[test] fn load_full_setup_from_str() { @@ -211,12 +240,15 @@ ipc = "/foreign.ipc" bin = "../contracts/ForeignBridge.bin" [authorities] -accounts = [ +required_signatures = 2 + +[authorities.source] +type = "accounts" +value = [ "0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000003" ] -required_signatures = 2 [transactions] home_deploy = { gas = 20 } @@ -245,11 +277,11 @@ home_deploy = { gas = 20 } required_confirmations: 12, }, authorities: Authorities { - accounts: vec![ + source: AuthoritiesSource::Accounts(vec![ "0x0000000000000000000000000000000000000001".parse().unwrap(), "0x0000000000000000000000000000000000000002".parse().unwrap(), "0x0000000000000000000000000000000000000003".parse().unwrap(), - ], + ]), required_signatures: 2, } }; @@ -281,12 +313,15 @@ ipc = "" bin = "../contracts/ForeignBridge.bin" [authorities] -accounts = [ +required_signatures = 2 + +[authorities.source] +type = "accounts" +value = [ "0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000003" ] -required_signatures = 2 "#; let expected = Config { txs: Transactions::default(), @@ -311,11 +346,11 @@ required_signatures = 2 required_confirmations: 12, }, authorities: Authorities { - accounts: vec![ + source: AuthoritiesSource::Accounts(vec![ "0x0000000000000000000000000000000000000001".parse().unwrap(), "0x0000000000000000000000000000000000000002".parse().unwrap(), "0x0000000000000000000000000000000000000003".parse().unwrap(), - ], + ]), required_signatures: 2, } }; diff --git a/bridge/src/contracts.rs b/bridge/src/contracts.rs index 03a29955..243d947e 100644 --- a/bridge/src/contracts.rs +++ b/bridge/src/contracts.rs @@ -1,2 +1,3 @@ use_contract!(home, "HomeBridge", "../contracts/HomeBridge.abi"); use_contract!(foreign, "ForeignBridge", "../contracts/ForeignBridge.abi"); +use_contract!(validator, "ValidatorSet", "../contracts/ValidatorSet.abi"); diff --git a/bridge/src/lib.rs b/bridge/src/lib.rs index 3b8a7cf7..d5c518e5 100644 --- a/bridge/src/lib.rs +++ b/bridge/src/lib.rs @@ -24,8 +24,9 @@ mod macros; pub mod api; pub mod app; -pub mod config; +pub mod authorities; pub mod bridge; +pub mod config; pub mod contracts; pub mod database; pub mod error; diff --git a/contracts/ValidatorSet.abi b/contracts/ValidatorSet.abi new file mode 100644 index 00000000..1a3cad94 --- /dev/null +++ b/contracts/ValidatorSet.abi @@ -0,0 +1,40 @@ +[ + { + "constant": false, + "inputs": [], + "name": "finalizeChange", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getValidators", + "outputs": [ + { + "name": "_validators", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_parent_hash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "_new_set", + "type": "address[]" + } + ], + "name": "InitiateChange", + "type": "event" + } +] diff --git a/tests/src/lib.rs b/tests/src/lib.rs index ea5328f0..ecc88fb7 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -94,8 +94,11 @@ macro_rules! test_app_stream { use self::std::time::Duration; use self::futures::{Future, Stream}; use self::bridge::app::{App, Connections}; - use self::bridge::contracts::{foreign, home}; - use self::bridge::config::{Config, Authorities, Node, ContractConfig, Transactions, TransactionConfig}; + use self::bridge::contracts::{foreign, home, validator}; + use self::bridge::config::{ + Config, Authorities, Node, ContractConfig, Transactions, TransactionConfig, + AuthoritiesSource + }; use self::bridge::database::Database; let home = $crate::MockedTransport { @@ -133,7 +136,7 @@ macro_rules! test_app_stream { required_confirmations: $foreign_conf, }, authorities: Authorities { - accounts: $authorities_accs.iter().map(|a: &&str| a.parse().unwrap()).collect(), + source: AuthoritiesSource::Accounts($authorities_accs.iter().map(|a: &&str| a.parse().unwrap()).collect()), required_signatures: $signatures, } }; @@ -147,6 +150,7 @@ macro_rules! test_app_stream { }, home_bridge: home::HomeBridge::default(), foreign_bridge: foreign::ForeignBridge::default(), + validators: validator::ValidatorSet::default(), timer: Default::default(), };