├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── check.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bridge ├── Cargo.toml ├── build.rs └── src │ ├── accept_message_from_main.rs │ ├── block_number_stream.rs │ ├── bridge.rs │ ├── config.rs │ ├── database.rs │ ├── deploy.rs │ ├── error.rs │ ├── helpers.rs │ ├── lib.rs │ ├── log_stream.rs │ ├── macros.rs │ ├── main_contract.rs │ ├── message_to_main.rs │ ├── ordered_stream.rs │ ├── relay_stream.rs │ ├── send_tx_with_receipt.rs │ ├── side_contract.rs │ ├── side_to_main_sign.rs │ ├── side_to_main_signatures.rs │ ├── signature.rs │ └── test.rs ├── cli ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── contracts ├── Cargo.toml ├── build.rs └── src │ └── lib.rs ├── deploy ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── deployment_guide.md ├── file_header.txt ├── integration-tests ├── Cargo.toml ├── bridge_config.toml ├── password.txt ├── spec.json └── tests │ └── basic_deposit_then_withdraw.rs ├── parity-ethereum-grandpa-builtin ├── Cargo.toml └── src │ └── lib.rs ├── res ├── deposit.png └── withdraw.png ├── rustfmt.toml └── srml-bridge-poa ├── Cargo.toml └── src └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=tab 4 | indent_size=4 5 | tab_width=4 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=120 10 | insert_final_newline=true 11 | 12 | [*.sol] 13 | indent_style=space 14 | indent_size=4 15 | tab_size=4 16 | 17 | [*.js] 18 | indent_style=space 19 | indent_size=2 20 | tab_size=2 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | contracts/*.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | check: 8 | name: Check 9 | strategy: 10 | matrix: 11 | toolchain: 12 | - stable 13 | - beta 14 | - nightly 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@master 19 | with: 20 | submodules: true 21 | - name: Install solc v0.5.0 22 | shell: bash 23 | run: | 24 | export SOLC_HASH="c1bb15b520f5076aebd7aa9ef4ce5fa245b6f210a91cbd2064b9e383e6510e08"; 25 | export SOLC_VERSION="v0.5.0"; 26 | curl -LO https://github.com/ethereum/solidity/releases/download/$SOLC_VERSION/solc-static-linux; 27 | echo "$SOLC_HASH solc-static-linux" | sha256sum -c; 28 | sha256sum solc-static-linux; 29 | chmod +x solc-static-linux; 30 | sudo mv solc-static-linux /usr/bin/solc; 31 | solc --version 32 | - name: Install rust-${{ matrix.toolchain }} 33 | uses: actions-rs/toolchain@v1.0.6 34 | with: 35 | toolchain: ${{ matrix.toolchain }} 36 | override: true 37 | components: rustfmt 38 | - name: Cargo check ${{ matrix.toolchain }} 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: check 42 | toolchain: ${{ matrix.toolchain }} 43 | args: --verbose --all 44 | - name: Cargo fmt ${{ matrix.toolchain }} 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: fmt 48 | args: --verbose --all -- --check 49 | - name: Cargo test ${{ matrix.toolchain }} 50 | uses: actions-rs/cargo@v1 51 | with: 52 | command: test 53 | args: --verbose --all --color=always -- --nocapture 54 | 55 | 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | *.swp 6 | *.swo 7 | *.swn 8 | *.DS_Store 9 | 10 | # Visual Studio Code stuff 11 | /.vscode 12 | 13 | # GitEye stuff 14 | /.project 15 | 16 | # idea ide 17 | .idea 18 | 19 | # truffle stuff 20 | truffle/build 21 | 22 | node_modules 23 | 24 | compiled_contracts 25 | 26 | integration-tests/tmp 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "arbitrary"] 2 | path = arbitrary 3 | url = https://github.com/parity-contracts/bridge 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | matrix: 6 | include: 7 | - language: rust 8 | rust: stable 9 | sudo: required 10 | dist: trusty 11 | cache: cargo 12 | fast_finish: false 13 | install: 14 | - sudo apt-get --yes install snapd 15 | - sudo snap install parity --stable 16 | - sudo snap install solc --stable 17 | - snap list 18 | before_script: 19 | - export PATH=/snap/bin:${PATH} 20 | - rustup component add rustfmt-preview 21 | # useful for troubleshooting 22 | - rustfmt --version 23 | script: 24 | # using --write-mode=diff instructs rustfmt to exit with an error code if the input is not formatted correctly 25 | - cargo fmt --all -- --check 26 | - env BACKTRACE=1 cargo test --all -- --nocapture 27 | - language: rust 28 | rust: beta 29 | sudo: required 30 | dist: trusty 31 | cache: cargo 32 | fast_finish: false 33 | install: 34 | - sudo apt-get --yes install snapd 35 | - sudo snap install parity --stable 36 | - sudo snap install solc --stable 37 | - snap list 38 | before_script: 39 | - export PATH=/snap/bin:${PATH} 40 | script: 41 | - env BACKTRACE=1 cargo test --all -- --nocapture 42 | - language: rust 43 | rust: nightly 44 | sudo: required 45 | dist: trusty 46 | cache: cargo 47 | fast_finish: false 48 | install: 49 | - sudo apt-get --yes install snapd 50 | - sudo snap install parity --stable 51 | - sudo snap install solc --stable 52 | - snap list 53 | before_script: 54 | - export PATH=/snap/bin:${PATH} 55 | script: 56 | - env BACKTRACE=1 cargo test --all -- --nocapture 57 | 58 | env: 59 | global: 60 | - secure: zdEco0QAPik4peDfWuLHHex67LVe3E7c5VJNx+7ygH1pt+mzgobKo8jgT7WuH70xPRA717txNaj/zYGj5EuBKLn+Tkw3feDjrISYRD7ZOXFm1urv53KDx8xh2QJld2fHOc4UWcQ1qqBOWWOR9donuOaRfdDSOpWjLhl14heMgsW3o5Q/V4HN//VPHQctzaCq6r5eerx82B6SSNQ7+42rESu37N0Plv8JtCswihCuoUsMuzbXGwGzafR8IVf5WJPB1WM1KpjdWHgZCCgIfdH6C9fJ1P4fd2Z7EQJ0PYwxRntPlONzUr5khGPldXn7Czwoq9Go4eOZaTwHizprI/KCXBXASXQ/Z7EsU2AKl90qvUHLDB9i4aa/eDrkzQGPQ+dkjNckdQaaucIKX/r8VDm7ZVefkLOgbzc1plE6/TXslAS/n0OoXUXydzueyqi8oeVEagt/nSYaR4t/8C10eC/6gjVF6X6mpgM6/p8eVrN8bltMa0KSDfRvhi3kU1Nmc5b3CWg+neWYYFPHak3GyFwh3uRC0LJroO+j+dkQZiEpSsMgthx69RBDjYvoi3T5FGwt5s/FfnOtcHM65M9sGubMW4DsVaI7OHt7FUnp5dlqxk6NGT68R/E1ZeCwr7Y4QCXr4agew5OpxTni4MK7aCVnmAtabNVLI4wKdCy2ULJWLsE= 61 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "bridge", 4 | "cli", 5 | "contracts", 6 | "deploy", 7 | "integration-tests", 8 | "parity-ethereum-grandpa-builtin", 9 | "srml-bridge-poa", 10 | ] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated Bridges 2 | 3 | ### This repo is **deprecated**. Originally it contained the ETH <> ETH-PoA bridge (see `tumski` tag). Later it was repurposed for ETH-PoA <> Substrate bridge development, however we've decided to work on the bridge from scratch, and you can find the latest version in: 4 | https://github.com/paritytech/parity-bridges-common 5 | 6 | **DISCLAIMER:** *we recommend not using the bridge in "production" (to bridge significant amounts) just yet. 7 | it's missing a code audit and should still be considered alpha. we can't rule out that there are bugs that might result in loss of the bridged amounts. 8 | we'll update this disclaimer once that changes* 9 | 10 | [![Build Status][travis-image]][travis-url] 11 | [![Solidity Coverage Status][coveralls-image]][coveralls-url] (contracts only) 12 | 13 | [travis-image]: https://travis-ci.org/paritytech/parity-bridge.svg?branch=master 14 | [travis-url]: https://travis-ci.org/paritytech/parity-bridge 15 | [coveralls-image]: https://coveralls.io/repos/github/paritytech/parity-bridge/badge.svg?branch=master 16 | [coveralls-url]: https://coveralls.io/github/paritytech/parity-bridge?branch=master 17 | 18 | Bridge is able to pass arbitrary messages between an Ethereum Proof of Authority chain and 19 | a Substrate Chain - either Standalone or Polkadot Parachain. 20 | 21 | ## TODO: Docs 22 | 23 | These docs are very incomplete yet. Describe high-level goals here in the (near) future. 24 | -------------------------------------------------------------------------------- /bridge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bridge" 3 | version = "0.5.0" 4 | authors = ["debris ", "snd "] 5 | 6 | [dependencies] 7 | bridge-contracts = { path = "../contracts" } 8 | futures = "0.1" 9 | serde = "1.0" 10 | serde_derive = "1.0" 11 | serde_json = "1.0" 12 | tokio-core = "0.1.8" 13 | tokio-timer = "0.1" 14 | toml = "0.5.5" 15 | web3 = { git = "https://github.com/tomusdrw/rust-web3" } 16 | error-chain = "0.12" 17 | ethabi = "9.0" 18 | rustc-hex = "2.0" 19 | log = "0.4" 20 | ethereum-types = "0.8" 21 | pretty_assertions = "0.6.1" 22 | tiny-keccak = { version = "2.0", features = ["keccak"] } 23 | 24 | [dev-dependencies] 25 | tempfile = "3" 26 | quickcheck = "0.9" 27 | jsonrpc-core = "14.0" 28 | -------------------------------------------------------------------------------- /bridge/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | use std::process::Command; 18 | 19 | fn main() { 20 | // rerun build script if bridge contract has changed. 21 | // without this cargo doesn't since the bridge contract 22 | // is outside the crate directories 23 | println!("cargo:rerun-if-changed=../arbitrary/contracts/bridge.sol"); 24 | 25 | // make last git commit hash (`git rev-parse HEAD`) 26 | // available via `env!("GIT_HASH")` in sources 27 | let output = Command::new("git") 28 | .args(&["rev-parse", "HEAD"]) 29 | .output() 30 | .expect("`git rev-parse HEAD` failed to run. run it yourself to verify. file an issue if this persists"); 31 | let git_hash = String::from_utf8(output.stdout).unwrap(); 32 | println!("cargo:rustc-env=GIT_HASH={}", git_hash); 33 | 34 | // make solc version used to compile contracts (`solc --version`) 35 | // available via `env!("SOLC_VERSION")` in sources 36 | let output = Command::new("solc").args(&["--version"]).output().expect( 37 | "`solc --version` failed to run. run it yourself to verify. file an issue if this persists", 38 | ); 39 | let output_string = String::from_utf8(output.stdout).unwrap(); 40 | let solc_version = output_string.lines().last().unwrap(); 41 | println!("cargo:rustc-env=SOLC_VERSION={}", solc_version); 42 | } 43 | -------------------------------------------------------------------------------- /bridge/src/accept_message_from_main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | use contracts; 18 | use error::{self, ResultExt}; 19 | use futures::{Async, Future, Poll}; 20 | use helpers::{self, AsyncCall, AsyncTransaction}; 21 | use main_contract::MainContract; 22 | use relay_stream::LogToFuture; 23 | use side_contract::SideContract; 24 | use web3::types::{Address, Log, H256}; 25 | use web3::Transport; 26 | 27 | #[derive(Clone)] 28 | pub struct LogToAcceptMessageFromMain { 29 | pub main: MainContract, 30 | pub side: SideContract, 31 | } 32 | 33 | impl LogToFuture for LogToAcceptMessageFromMain { 34 | type Future = AcceptMessageFromMain; 35 | 36 | fn log_to_future(&self, log: &Log) -> Self::Future { 37 | AcceptMessageFromMain::new(log, self.side.clone(), self.main.clone()) 38 | } 39 | } 40 | 41 | enum State { 42 | AwaitMessage(AsyncCall), 43 | AwaitAlreadyAccepted { 44 | message: Vec, 45 | future: AsyncCall< 46 | T, 47 | contracts::side::functions::has_authority_accepted_message_from_main::Decoder, 48 | >, 49 | }, 50 | AwaitTxSent(AsyncTransaction), 51 | } 52 | 53 | pub struct AcceptMessageFromMain { 54 | state: State, 55 | main_tx_hash: H256, 56 | sender: Address, 57 | recipient: Address, 58 | side: SideContract, 59 | } 60 | 61 | impl AcceptMessageFromMain { 62 | pub fn new(raw_log: &Log, side: SideContract, main: MainContract) -> Self { 63 | let main_tx_hash = raw_log 64 | .transaction_hash 65 | .expect("`log` must be mined and contain `transaction_hash`. q.e.d."); 66 | 67 | let log = helpers::parse_log(contracts::main::events::relay_message::parse_log, raw_log) 68 | .expect("`log` must be for a relay message. q.e.d."); 69 | 70 | let sender = log.sender; 71 | let recipient = log.recipient; 72 | 73 | info!( 74 | "{:?} - step 1/4 - fetch message using message_id", 75 | main_tx_hash 76 | ); 77 | let future = main.relayed_message_by_id(log.message_id); 78 | let state = State::AwaitMessage(future); 79 | 80 | AcceptMessageFromMain { 81 | state, 82 | main_tx_hash, 83 | sender, 84 | recipient, 85 | side, 86 | } 87 | } 88 | } 89 | 90 | impl Future for AcceptMessageFromMain { 91 | type Item = Option; 92 | type Error = error::Error; 93 | 94 | fn poll(&mut self) -> Poll { 95 | loop { 96 | let next_state = match self.state { 97 | State::AwaitMessage(ref mut future) => { 98 | let message = try_ready!(future 99 | .poll() 100 | .chain_err(|| "AcceptMessageFromMain: failed to fetch the message")); 101 | 102 | info!( 103 | "{:?} - 2/4 - checking if the message is already signed", 104 | self.main_tx_hash 105 | ); 106 | State::AwaitAlreadyAccepted { 107 | message: message.clone(), 108 | future: self.side.is_message_accepted_from_main( 109 | self.main_tx_hash, 110 | message, 111 | self.sender, 112 | self.recipient, 113 | ), 114 | } 115 | } 116 | State::AwaitAlreadyAccepted { 117 | ref message, 118 | ref mut future, 119 | } => { 120 | let has_already_accepted = try_ready!(future.poll().chain_err(|| { 121 | "AcceptMessageFromMain: failed to check if already accepted" 122 | })); 123 | if has_already_accepted { 124 | info!("{:?} - DONE - already accepted", self.main_tx_hash); 125 | return Ok(Async::Ready(None)); 126 | } 127 | 128 | info!("{:?} - 3/4 - accepting the message", self.main_tx_hash); 129 | State::AwaitTxSent(self.side.accept_message_from_main( 130 | self.main_tx_hash, 131 | message.clone(), 132 | self.sender, 133 | self.recipient, 134 | )) 135 | } 136 | State::AwaitTxSent(ref mut future) => { 137 | let main_tx_hash = self.main_tx_hash; 138 | let side_tx_hash = try_ready!(future.poll().chain_err(|| format!( 139 | "AcceptMessageFromMain: checking whether {} was relayed failed", 140 | main_tx_hash 141 | ))); 142 | info!("{:?} - DONE - accepted", self.main_tx_hash); 143 | return Ok(Async::Ready(Some(side_tx_hash))); 144 | } 145 | }; 146 | self.state = next_state; 147 | } 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use super::*; 154 | use contracts; 155 | use ethabi; 156 | use rustc_hex::ToHex; 157 | use tokio_core::reactor::Core; 158 | use web3::types::{Bytes, Log}; 159 | 160 | #[test] 161 | fn test_accept_message_from_main() { 162 | let topic = contracts::main::events::relay_message::filter().topic0; 163 | 164 | let log = contracts::main::logs::RelayMessage { 165 | message_id: "1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b" 166 | .parse() 167 | .unwrap(), 168 | sender: "aff3454fce5edbc8cca8697c15331677e6ebdddd".parse().unwrap(), 169 | recipient: "aff3454fce5edbc8cca8697c15331677e6ebcccc".parse().unwrap(), 170 | }; 171 | 172 | let log_data = ethabi::encode(&[ 173 | ethabi::Token::FixedBytes(log.message_id.as_bytes().to_vec()), 174 | ethabi::Token::Address(log.sender), 175 | ethabi::Token::Address(log.recipient), 176 | ]); 177 | 178 | let log_tx_hash = "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 179 | .parse() 180 | .unwrap(); 181 | 182 | let raw_log = Log { 183 | address: "0000000000000000000000000000000000000001".parse().unwrap(), 184 | topics: topic.into(), 185 | data: Bytes(log_data), 186 | transaction_hash: Some(log_tx_hash), 187 | block_hash: None, 188 | block_number: None, 189 | transaction_index: None, 190 | log_index: None, 191 | transaction_log_index: None, 192 | log_type: None, 193 | removed: None, 194 | }; 195 | 196 | let authority_address = "0000000000000000000000000000000000000001".parse().unwrap(); 197 | 198 | let tx_hash = "1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b"; 199 | let side_contract_address = "0000000000000000000000000000000000000dd1".parse().unwrap(); 200 | let main_contract_address = "0000000000000000000000000000000000000dd2".parse().unwrap(); 201 | 202 | let data: Vec = vec![0x12, 0x34]; 203 | 204 | let encoded_message = ethabi::encode(&[ethabi::Token::Bytes(data.clone())]); 205 | 206 | let get_message_call_data = 207 | contracts::main::functions::relayed_messages::encode_input(log.message_id); 208 | 209 | let has_accepted_call_data = 210 | contracts::side::functions::has_authority_accepted_message_from_main::encode_input( 211 | log_tx_hash, 212 | data.clone(), 213 | log.sender, 214 | log.recipient, 215 | authority_address, 216 | ); 217 | 218 | let accept_message_call_data = contracts::side::functions::accept_message::encode_input( 219 | log_tx_hash, 220 | data, 221 | log.sender, 222 | log.recipient, 223 | ); 224 | 225 | let main_transport = mock_transport!( 226 | "eth_call" => 227 | req => json!([{ 228 | "data": format!("0x{}", get_message_call_data.to_hex::()), 229 | "to": format!("0x{:x}", main_contract_address), 230 | }, "latest"]), 231 | res => json!(format!("0x{}", encoded_message.to_hex::())); 232 | ); 233 | 234 | let side_transport = mock_transport!( 235 | "eth_call" => 236 | req => json!([{ 237 | "data": format!("0x{}", has_accepted_call_data.to_hex::()), 238 | "to": format!("0x{:x}", side_contract_address), 239 | }, "latest"]), 240 | res => json!(format!("0x{}", ethabi::encode(&[ethabi::Token::Bool(false)]).to_hex::())); 241 | "eth_sendTransaction" => 242 | req => json!([{ 243 | "data": format!("0x{}", accept_message_call_data.to_hex::()), 244 | "from": "0x0000000000000000000000000000000000000001", 245 | "gas": "0xfd", 246 | "gasPrice": "0xa0", 247 | "to": format!("0x{:x}", side_contract_address), 248 | }]), 249 | res => json!(format!("0x{}", tx_hash)); 250 | ); 251 | 252 | let main_contract = MainContract { 253 | transport: main_transport.clone(), 254 | contract_address: main_contract_address, 255 | authority_address, 256 | submit_collected_signatures_gas: 0.into(), 257 | request_timeout: ::std::time::Duration::from_millis(0), 258 | logs_poll_interval: ::std::time::Duration::from_millis(0), 259 | required_log_confirmations: 0, 260 | }; 261 | 262 | let side_contract = SideContract { 263 | transport: side_transport.clone(), 264 | contract_address: side_contract_address, 265 | authority_address, 266 | required_signatures: 1, 267 | request_timeout: ::std::time::Duration::from_millis(0), 268 | logs_poll_interval: ::std::time::Duration::from_millis(0), 269 | required_log_confirmations: 0, 270 | sign_main_to_side_gas: 0xfd.into(), 271 | sign_main_to_side_gas_price: 0xa0.into(), 272 | sign_side_to_main_gas: 0.into(), 273 | sign_side_to_main_gas_price: 0.into(), 274 | }; 275 | 276 | let future = AcceptMessageFromMain::new(&raw_log, side_contract, main_contract); 277 | 278 | let mut event_loop = Core::new().unwrap(); 279 | let result = event_loop.run(future).unwrap(); 280 | assert_eq!(result, Some(tx_hash.parse().unwrap())); 281 | 282 | assert_eq!( 283 | side_transport.actual_requests(), 284 | side_transport.expected_requests() 285 | ); 286 | assert_eq!( 287 | main_transport.actual_requests(), 288 | main_transport.expected_requests() 289 | ); 290 | } 291 | 292 | #[test] 293 | fn test_accept_message_from_main_already_relayed() { 294 | let topic = contracts::main::events::relay_message::filter().topic0; 295 | 296 | let log = contracts::main::logs::RelayMessage { 297 | message_id: "1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b" 298 | .parse() 299 | .unwrap(), 300 | sender: "aff3454fce5edbc8cca8697c15331677e6ebdddd".parse().unwrap(), 301 | recipient: "aff3454fce5edbc8cca8697c15331677e6ebcccc".parse().unwrap(), 302 | }; 303 | 304 | let log_data = ethabi::encode(&[ 305 | ethabi::Token::FixedBytes(log.message_id.as_bytes().to_vec()), 306 | ethabi::Token::Address(log.sender), 307 | ethabi::Token::Address(log.recipient), 308 | ]); 309 | 310 | let log_tx_hash = "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 311 | .parse() 312 | .unwrap(); 313 | 314 | let raw_log = Log { 315 | address: "0000000000000000000000000000000000000001".parse().unwrap(), 316 | topics: topic.into(), 317 | data: Bytes(log_data), 318 | transaction_hash: Some(log_tx_hash), 319 | block_hash: None, 320 | block_number: None, 321 | transaction_index: None, 322 | log_index: None, 323 | transaction_log_index: None, 324 | log_type: None, 325 | removed: None, 326 | }; 327 | 328 | let authority_address = "0000000000000000000000000000000000000001".parse().unwrap(); 329 | 330 | let side_contract_address = "0000000000000000000000000000000000000dd1".parse().unwrap(); 331 | let main_contract_address = "0000000000000000000000000000000000000dd2".parse().unwrap(); 332 | 333 | let data: Vec = vec![0x12, 0x34]; 334 | 335 | let encoded_message = ethabi::encode(&[ethabi::Token::Bytes(data.clone())]); 336 | 337 | let get_message_call_data = 338 | contracts::main::functions::relayed_messages::encode_input(log.message_id); 339 | 340 | let has_accepted_call_data = 341 | contracts::side::functions::has_authority_accepted_message_from_main::encode_input( 342 | log_tx_hash, 343 | data.clone(), 344 | log.sender, 345 | log.recipient, 346 | authority_address, 347 | ); 348 | 349 | let main_transport = mock_transport!( 350 | "eth_call" => 351 | req => json!([{ 352 | "data": format!("0x{}", get_message_call_data.to_hex::()), 353 | "to": main_contract_address, 354 | }, "latest"]), 355 | res => json!(format!("0x{}", encoded_message.to_hex::())); 356 | ); 357 | 358 | let side_transport = mock_transport!( 359 | "eth_call" => 360 | req => json!([{ 361 | "data": format!("0x{}", has_accepted_call_data.to_hex::()), 362 | "to": side_contract_address, 363 | }, "latest"]), 364 | res => json!(format!("0x{}", ethabi::encode(&[ethabi::Token::Bool(true)]).to_hex::())); 365 | ); 366 | 367 | let main_contract = MainContract { 368 | transport: main_transport.clone(), 369 | contract_address: main_contract_address, 370 | authority_address, 371 | submit_collected_signatures_gas: 0.into(), 372 | request_timeout: ::std::time::Duration::from_millis(0), 373 | logs_poll_interval: ::std::time::Duration::from_millis(0), 374 | required_log_confirmations: 0, 375 | }; 376 | 377 | let side_contract = SideContract { 378 | transport: side_transport.clone(), 379 | contract_address: side_contract_address, 380 | authority_address, 381 | required_signatures: 1, 382 | request_timeout: ::std::time::Duration::from_millis(0), 383 | logs_poll_interval: ::std::time::Duration::from_millis(0), 384 | required_log_confirmations: 0, 385 | sign_main_to_side_gas: 0xfd.into(), 386 | sign_main_to_side_gas_price: 0xa0.into(), 387 | sign_side_to_main_gas: 0.into(), 388 | sign_side_to_main_gas_price: 0.into(), 389 | }; 390 | 391 | let future = AcceptMessageFromMain::new(&raw_log, side_contract, main_contract); 392 | 393 | let mut event_loop = Core::new().unwrap(); 394 | let result = event_loop.run(future).unwrap(); 395 | assert_eq!(result, None); 396 | 397 | assert_eq!( 398 | side_transport.actual_requests(), 399 | side_transport.expected_requests() 400 | ); 401 | assert_eq!( 402 | main_transport.actual_requests(), 403 | main_transport.expected_requests() 404 | ); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /bridge/src/block_number_stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | use error::{self, ResultExt}; 18 | use futures::future::FromErr; 19 | use futures::{Async, Future, Poll, Stream}; 20 | use std::time::Duration; 21 | use tokio_timer::{Interval, Timeout, Timer}; 22 | use web3; 23 | use web3::api::Namespace; 24 | use web3::helpers::CallFuture; 25 | use web3::types::U64; 26 | use web3::Transport; 27 | 28 | /// Block Number Stream state. 29 | enum State { 30 | AwaitInterval, 31 | AwaitBlockNumber(Timeout, error::Error>>), 32 | } 33 | 34 | pub struct BlockNumberStreamOptions { 35 | pub request_timeout: Duration, 36 | pub poll_interval: Duration, 37 | pub confirmations: u32, 38 | pub transport: T, 39 | pub after: u64, 40 | } 41 | 42 | /// `Stream` that repeatedly polls `eth_blockNumber` and yields new block numbers. 43 | pub struct BlockNumberStream { 44 | request_timeout: Duration, 45 | confirmations: u32, 46 | transport: T, 47 | last_checked_block: u64, 48 | timer: Timer, 49 | poll_interval: Interval, 50 | state: State, 51 | } 52 | 53 | impl BlockNumberStream { 54 | pub fn new(options: BlockNumberStreamOptions) -> Self { 55 | let timer = Timer::default(); 56 | 57 | BlockNumberStream { 58 | request_timeout: options.request_timeout, 59 | confirmations: options.confirmations, 60 | poll_interval: timer.interval(options.poll_interval), 61 | transport: options.transport, 62 | last_checked_block: options.after, 63 | timer, 64 | state: State::AwaitInterval, 65 | } 66 | } 67 | } 68 | 69 | impl Stream for BlockNumberStream { 70 | type Item = u64; 71 | type Error = error::Error; 72 | 73 | fn poll(&mut self) -> Poll, Self::Error> { 74 | loop { 75 | let (next_state, value_to_yield) = match self.state { 76 | State::AwaitInterval => { 77 | // wait until `interval` has passed 78 | let _ = try_stream!(self 79 | .poll_interval 80 | .poll() 81 | .chain_err(|| format!("BlockNumberStream polling interval failed",))); 82 | info!("BlockNumberStream polling last block number"); 83 | let future = web3::api::Eth::new(&self.transport).block_number(); 84 | let next_state = State::AwaitBlockNumber( 85 | self.timer.timeout(future.from_err(), self.request_timeout), 86 | ); 87 | (next_state, None) 88 | } 89 | State::AwaitBlockNumber(ref mut future) => { 90 | let last_block = try_ready!(future 91 | .poll() 92 | .chain_err(|| "BlockNumberStream: fetching of last block number failed")) 93 | .as_u64(); 94 | info!( 95 | "BlockNumberStream: fetched last block number {}", 96 | last_block 97 | ); 98 | // subtraction that saturates at zero 99 | let last_confirmed_block = last_block.saturating_sub(self.confirmations as u64); 100 | 101 | if self.last_checked_block < last_confirmed_block { 102 | self.last_checked_block = last_confirmed_block; 103 | (State::AwaitInterval, Some(last_confirmed_block)) 104 | } else { 105 | info!("BlockNumberStream: no blocks confirmed since we last checked. waiting some more"); 106 | (State::AwaitInterval, None) 107 | } 108 | } 109 | }; 110 | 111 | self.state = next_state; 112 | 113 | if value_to_yield.is_some() { 114 | return Ok(Async::Ready(value_to_yield)); 115 | } 116 | } 117 | } 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | use super::*; 123 | use tokio_core::reactor::Core; 124 | 125 | #[test] 126 | fn test_block_number_stream() { 127 | let transport = mock_transport!( 128 | "eth_blockNumber" => 129 | req => json!([]), 130 | res => json!("0x1011"); 131 | "eth_blockNumber" => 132 | req => json!([]), 133 | res => json!("0x1011"); 134 | "eth_blockNumber" => 135 | req => json!([]), 136 | res => json!("0x1012"); 137 | "eth_blockNumber" => 138 | req => json!([]), 139 | res => json!("0x1015"); 140 | ); 141 | 142 | let block_number_stream = BlockNumberStream::new(BlockNumberStreamOptions { 143 | request_timeout: Duration::from_secs(1), 144 | poll_interval: Duration::from_secs(0), 145 | confirmations: 12, 146 | transport: transport.clone(), 147 | after: 3, 148 | }); 149 | 150 | let mut event_loop = Core::new().unwrap(); 151 | let block_numbers = event_loop 152 | .run(block_number_stream.take(3).collect()) 153 | .unwrap(); 154 | 155 | assert_eq!(block_numbers, vec![0x1011 - 12, 0x1012 - 12, 0x1015 - 12]); 156 | assert_eq!(transport.actual_requests(), transport.expected_requests()); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /bridge/src/bridge.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use futures::{Async, Poll, Stream}; 17 | use web3::Transport; 18 | 19 | use accept_message_from_main; 20 | use database::State; 21 | use error::{self, ResultExt}; 22 | use log_stream::LogStream; 23 | use main_contract::MainContract; 24 | use relay_stream::RelayStream; 25 | use side_contract::SideContract; 26 | use side_to_main_sign; 27 | use side_to_main_signatures; 28 | 29 | /// bridge `Stream`. toplevel entity created and repeatedly polled by the `parity-bridge` executable. 30 | /// internally creates and polls a `RelayStream` for each of the 3 relays. 31 | /// a relay in this context is a specific event on chain 1 that should 32 | /// be followed by execution of a a specific on chain 2. 33 | /// if polled polls all relay streams which causes them fetch 34 | /// all pending relays and relay them 35 | /// updates the database with results returned from relay streams. 36 | /// yields new state that should be persisted 37 | pub struct Bridge { 38 | accept_message_from_main: 39 | RelayStream, accept_message_from_main::LogToAcceptMessageFromMain>, 40 | side_to_main_sign: RelayStream, side_to_main_sign::LogToSideToMainSign>, 41 | side_to_main_signatures: 42 | RelayStream, side_to_main_signatures::LogToSideToMainSignatures>, 43 | state: State, 44 | } 45 | 46 | impl Bridge { 47 | pub fn new( 48 | initial_state: State, 49 | main_contract: MainContract, 50 | side_contract: SideContract, 51 | ) -> Self { 52 | let accept_message_from_main = RelayStream::new( 53 | main_contract.main_to_side_log_stream(initial_state.last_main_to_side_sign_at_block), 54 | accept_message_from_main::LogToAcceptMessageFromMain { 55 | main: main_contract.clone(), 56 | side: side_contract.clone(), 57 | }, 58 | ); 59 | 60 | let side_to_main_sign = RelayStream::new( 61 | side_contract 62 | .side_to_main_sign_log_stream(initial_state.last_side_to_main_sign_at_block), 63 | side_to_main_sign::LogToSideToMainSign { 64 | side: side_contract.clone(), 65 | }, 66 | ); 67 | 68 | let side_to_main_signatures = RelayStream::new( 69 | side_contract.side_to_main_signatures_log_stream( 70 | initial_state.last_side_to_main_signatures_at_block, 71 | main_contract.authority_address, 72 | ), 73 | side_to_main_signatures::LogToSideToMainSignatures { 74 | main: main_contract.clone(), 75 | side: side_contract.clone(), 76 | }, 77 | ); 78 | 79 | Self { 80 | accept_message_from_main, 81 | side_to_main_sign, 82 | side_to_main_signatures, 83 | state: initial_state, 84 | } 85 | } 86 | } 87 | 88 | impl Stream for Bridge { 89 | type Item = State; 90 | type Error = error::Error; 91 | 92 | fn poll(&mut self) -> Poll, Self::Error> { 93 | loop { 94 | let maybe_main_to_side_sign = try_maybe_stream!(self 95 | .accept_message_from_main 96 | .poll() 97 | .chain_err(|| "Bridge: polling main to side sign failed")); 98 | let maybe_side_to_main_sign = try_maybe_stream!(self 99 | .side_to_main_sign 100 | .poll() 101 | .chain_err(|| "Bridge: polling side to main sign failed")); 102 | let maybe_side_to_main_signatures = try_maybe_stream!(self 103 | .side_to_main_signatures 104 | .poll() 105 | .chain_err(|| "Bridge: polling side to main signatures failed")); 106 | 107 | let mut has_state_changed = false; 108 | 109 | if let Some(main_to_side_sign) = maybe_main_to_side_sign { 110 | info!( 111 | "last block checked for main to side sign is now {}", 112 | main_to_side_sign 113 | ); 114 | self.state.last_main_to_side_sign_at_block = main_to_side_sign; 115 | has_state_changed = true; 116 | } 117 | if let Some(side_to_main_sign) = maybe_side_to_main_sign { 118 | info!( 119 | "last block checked for side to main sign is now {}", 120 | side_to_main_sign 121 | ); 122 | self.state.last_side_to_main_sign_at_block = side_to_main_sign; 123 | has_state_changed = true; 124 | } 125 | if let Some(side_to_main_signatures) = maybe_side_to_main_signatures { 126 | info!( 127 | "last block checked for side to main signatures is now {}", 128 | side_to_main_signatures 129 | ); 130 | self.state.last_side_to_main_signatures_at_block = side_to_main_signatures; 131 | has_state_changed = true; 132 | } 133 | 134 | if has_state_changed { 135 | return Ok(Async::Ready(Some(self.state.clone()))); 136 | } else { 137 | return Ok(Async::NotReady); 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /bridge/src/config.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | //! concerning reading configuration from toml files 18 | 19 | use error::{Error, ResultExt}; 20 | use ethereum_types::U256; 21 | use rustc_hex::FromHex; 22 | use std::fs; 23 | use std::io::Read; 24 | use std::path::Path; 25 | use std::time::Duration; 26 | use toml; 27 | use web3::types::{Address, Bytes}; 28 | 29 | const DEFAULT_POLL_INTERVAL: u64 = 1; 30 | const DEFAULT_TIMEOUT: u64 = 5; 31 | 32 | const DEFAULT_CONFIRMATIONS: u32 = 12; 33 | 34 | /// Application config. 35 | #[derive(Debug, PartialEq, Clone)] 36 | pub struct Config { 37 | pub address: Address, 38 | pub main: NodeConfig, 39 | pub side: NodeConfig, 40 | pub authorities: Authorities, 41 | pub txs: Transactions, 42 | pub estimated_gas_cost_of_withdraw: U256, 43 | pub max_total_main_contract_balance: U256, 44 | pub max_single_deposit_value: U256, 45 | } 46 | 47 | impl Config { 48 | pub fn load>(path: P) -> Result { 49 | let mut file = fs::File::open(path).chain_err(|| "Cannot open config")?; 50 | let mut buffer = String::new(); 51 | file.read_to_string(&mut buffer).expect("TODO"); 52 | Self::load_from_str(&buffer) 53 | } 54 | 55 | fn load_from_str(s: &str) -> Result { 56 | let config: load::Config = toml::from_str(s).chain_err(|| "Cannot parse config")?; 57 | Config::from_load_struct(config) 58 | } 59 | 60 | fn from_load_struct(config: load::Config) -> Result { 61 | let result = Config { 62 | address: config.address, 63 | main: NodeConfig::from_load_struct(config.main)?, 64 | side: NodeConfig::from_load_struct(config.side)?, 65 | authorities: Authorities { 66 | accounts: config.authorities.accounts, 67 | required_signatures: config.authorities.required_signatures, 68 | }, 69 | txs: config 70 | .transactions 71 | .map(Transactions::from_load_struct) 72 | .unwrap_or_default(), 73 | estimated_gas_cost_of_withdraw: config.estimated_gas_cost_of_withdraw, 74 | max_total_main_contract_balance: config.max_total_main_contract_balance, 75 | max_single_deposit_value: config.max_single_deposit_value, 76 | }; 77 | 78 | Ok(result) 79 | } 80 | } 81 | 82 | #[derive(Debug, PartialEq, Clone)] 83 | pub struct NodeConfig { 84 | pub contract: ContractConfig, 85 | pub http: String, 86 | pub request_timeout: Duration, 87 | pub poll_interval: Duration, 88 | pub required_confirmations: u32, 89 | } 90 | 91 | impl NodeConfig { 92 | fn from_load_struct(node: load::NodeConfig) -> Result { 93 | let result = Self { 94 | contract: ContractConfig { 95 | bin: { 96 | let mut read = String::new(); 97 | let mut file = fs::File::open(&node.contract.bin).chain_err(|| { 98 | format!( 99 | "Cannot open compiled contract file at {}", 100 | node.contract.bin.to_string_lossy() 101 | ) 102 | })?; 103 | file.read_to_string(&mut read)?; 104 | Bytes(read.from_hex()?) 105 | }, 106 | }, 107 | http: node.http, 108 | request_timeout: Duration::from_secs(node.request_timeout.unwrap_or(DEFAULT_TIMEOUT)), 109 | poll_interval: Duration::from_secs(node.poll_interval.unwrap_or(DEFAULT_POLL_INTERVAL)), 110 | required_confirmations: node.required_confirmations.unwrap_or(DEFAULT_CONFIRMATIONS), 111 | }; 112 | 113 | Ok(result) 114 | } 115 | } 116 | 117 | #[derive(Debug, PartialEq, Default, Clone)] 118 | pub struct Transactions { 119 | pub main_deploy: TransactionConfig, 120 | pub side_deploy: TransactionConfig, 121 | pub deposit_relay: TransactionConfig, 122 | pub withdraw_confirm: TransactionConfig, 123 | pub withdraw_relay: TransactionConfig, 124 | } 125 | 126 | impl Transactions { 127 | fn from_load_struct(cfg: load::Transactions) -> Self { 128 | Transactions { 129 | main_deploy: cfg 130 | .main_deploy 131 | .map(TransactionConfig::from_load_struct) 132 | .unwrap_or_default(), 133 | side_deploy: cfg 134 | .side_deploy 135 | .map(TransactionConfig::from_load_struct) 136 | .unwrap_or_default(), 137 | deposit_relay: cfg 138 | .deposit_relay 139 | .map(TransactionConfig::from_load_struct) 140 | .unwrap_or_default(), 141 | withdraw_confirm: cfg 142 | .withdraw_confirm 143 | .map(TransactionConfig::from_load_struct) 144 | .unwrap_or_default(), 145 | withdraw_relay: cfg 146 | .withdraw_relay 147 | .map(TransactionConfig::from_load_struct) 148 | .unwrap_or_default(), 149 | } 150 | } 151 | } 152 | 153 | #[derive(Debug, PartialEq, Default, Clone)] 154 | pub struct TransactionConfig { 155 | pub gas: U256, 156 | pub gas_price: U256, 157 | } 158 | 159 | impl TransactionConfig { 160 | fn from_load_struct(cfg: load::TransactionConfig) -> Self { 161 | TransactionConfig { 162 | gas: cfg.gas, 163 | gas_price: cfg.gas_price, 164 | } 165 | } 166 | } 167 | 168 | #[derive(Debug, PartialEq, Clone)] 169 | pub struct ContractConfig { 170 | pub bin: Bytes, 171 | } 172 | 173 | #[derive(Debug, PartialEq, Clone)] 174 | pub struct Authorities { 175 | pub accounts: Vec
, 176 | pub required_signatures: u32, 177 | } 178 | 179 | /// Some config values may not be defined in `toml` file, but they should be specified at runtime. 180 | /// `load` module separates `Config` representation in file with optional from the one used 181 | /// in application. 182 | mod load { 183 | use ethereum_types::U256; 184 | use helpers::deserialize_u256; 185 | use std::path::PathBuf; 186 | use web3::types::Address; 187 | 188 | #[derive(Deserialize)] 189 | #[serde(deny_unknown_fields)] 190 | pub struct Config { 191 | pub address: Address, 192 | pub main: NodeConfig, 193 | pub side: NodeConfig, 194 | pub authorities: Authorities, 195 | pub transactions: Option, 196 | #[serde(deserialize_with = "deserialize_u256")] 197 | pub estimated_gas_cost_of_withdraw: U256, 198 | #[serde(deserialize_with = "deserialize_u256")] 199 | pub max_total_main_contract_balance: U256, 200 | #[serde(deserialize_with = "deserialize_u256")] 201 | pub max_single_deposit_value: U256, 202 | } 203 | 204 | #[derive(Deserialize)] 205 | #[serde(deny_unknown_fields)] 206 | pub struct NodeConfig { 207 | pub contract: ContractConfig, 208 | pub http: String, 209 | pub request_timeout: Option, 210 | pub poll_interval: Option, 211 | pub required_confirmations: Option, 212 | } 213 | 214 | #[derive(Deserialize)] 215 | #[serde(deny_unknown_fields)] 216 | pub struct Transactions { 217 | pub main_deploy: Option, 218 | pub side_deploy: Option, 219 | pub deposit_relay: Option, 220 | pub withdraw_confirm: Option, 221 | pub withdraw_relay: Option, 222 | } 223 | 224 | #[derive(Deserialize)] 225 | #[serde(deny_unknown_fields)] 226 | pub struct TransactionConfig { 227 | #[serde(deserialize_with = "deserialize_u256")] 228 | pub gas: U256, 229 | #[serde(deserialize_with = "deserialize_u256")] 230 | pub gas_price: U256, 231 | } 232 | 233 | #[derive(Deserialize)] 234 | #[serde(deny_unknown_fields)] 235 | pub struct ContractConfig { 236 | pub bin: PathBuf, 237 | } 238 | 239 | #[derive(Deserialize)] 240 | #[serde(deny_unknown_fields)] 241 | pub struct Authorities { 242 | pub accounts: Vec
, 243 | pub required_signatures: u32, 244 | } 245 | } 246 | 247 | #[cfg(test)] 248 | mod tests { 249 | use super::{Authorities, Config, ContractConfig, NodeConfig, TransactionConfig, Transactions}; 250 | use ethereum_types::U256; 251 | use rustc_hex::FromHex; 252 | use std::time::Duration; 253 | 254 | #[test] 255 | fn load_full_setup_from_str() { 256 | let toml = r#" 257 | address = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b" 258 | estimated_gas_cost_of_withdraw = "100000" 259 | max_total_main_contract_balance = "10000000000000000000" 260 | max_single_deposit_value = "1000000000000000000" 261 | 262 | [main] 263 | http = "http://localhost:8545" 264 | poll_interval = 2 265 | required_confirmations = 100 266 | 267 | [main.contract] 268 | bin = "../compiled_contracts/Main.bin" 269 | 270 | [side] 271 | http = "http://localhost:8546" 272 | 273 | [side.contract] 274 | bin = "../compiled_contracts/Side.bin" 275 | 276 | [authorities] 277 | accounts = [ 278 | "0x0000000000000000000000000000000000000001", 279 | "0x0000000000000000000000000000000000000002", 280 | "0x0000000000000000000000000000000000000003" 281 | ] 282 | required_signatures = 2 283 | 284 | [transactions] 285 | main_deploy = { gas = "20", gas_price = "0" } 286 | "#; 287 | 288 | let mut expected = Config { 289 | address: "1B68Cb0B50181FC4006Ce572cF346e596E51818b".parse().unwrap(), 290 | txs: Transactions::default(), 291 | main: NodeConfig { 292 | http: "http://localhost:8545".into(), 293 | contract: ContractConfig { 294 | bin: include_str!("../../compiled_contracts/Main.bin") 295 | .from_hex::>() 296 | .unwrap() 297 | .into(), 298 | }, 299 | poll_interval: Duration::from_secs(2), 300 | request_timeout: Duration::from_secs(5), 301 | required_confirmations: 100, 302 | }, 303 | side: NodeConfig { 304 | contract: ContractConfig { 305 | bin: include_str!("../../compiled_contracts/Side.bin") 306 | .from_hex::>() 307 | .unwrap() 308 | .into(), 309 | }, 310 | http: "http://localhost:8546".into(), 311 | poll_interval: Duration::from_secs(1), 312 | request_timeout: Duration::from_secs(5), 313 | required_confirmations: 12, 314 | }, 315 | authorities: Authorities { 316 | accounts: vec![ 317 | "0000000000000000000000000000000000000001".parse().unwrap(), 318 | "0000000000000000000000000000000000000002".parse().unwrap(), 319 | "0000000000000000000000000000000000000003".parse().unwrap(), 320 | ], 321 | required_signatures: 2, 322 | }, 323 | estimated_gas_cost_of_withdraw: U256::from_dec_str("100000").unwrap(), 324 | max_total_main_contract_balance: U256::from_dec_str("10000000000000000000").unwrap(), 325 | max_single_deposit_value: U256::from_dec_str("1000000000000000000").unwrap(), 326 | }; 327 | 328 | expected.txs.main_deploy = TransactionConfig { 329 | gas: 20.into(), 330 | gas_price: 0.into(), 331 | }; 332 | 333 | let config = Config::load_from_str(toml).unwrap(); 334 | assert_eq!(expected, config); 335 | } 336 | 337 | #[test] 338 | fn load_minimal_setup_from_str() { 339 | let toml = r#" 340 | address = "0x0000000000000000000000000000000000000001" 341 | estimated_gas_cost_of_withdraw = "200000000" 342 | max_total_main_contract_balance = "10000000000000000000" 343 | max_single_deposit_value = "1000000000000000000" 344 | 345 | [main] 346 | http = "" 347 | 348 | [main.contract] 349 | bin = "../compiled_contracts/Main.bin" 350 | 351 | [side] 352 | http = "" 353 | 354 | [side.contract] 355 | bin = "../compiled_contracts/Side.bin" 356 | 357 | [authorities] 358 | accounts = [ 359 | "0x0000000000000000000000000000000000000001", 360 | "0x0000000000000000000000000000000000000002", 361 | "0x0000000000000000000000000000000000000003" 362 | ] 363 | required_signatures = 2 364 | "#; 365 | let expected = Config { 366 | address: "0000000000000000000000000000000000000001".parse().unwrap(), 367 | txs: Transactions::default(), 368 | main: NodeConfig { 369 | http: "".into(), 370 | contract: ContractConfig { 371 | bin: include_str!("../../compiled_contracts/Main.bin") 372 | .from_hex::>() 373 | .unwrap() 374 | .into(), 375 | }, 376 | poll_interval: Duration::from_secs(1), 377 | request_timeout: Duration::from_secs(5), 378 | required_confirmations: 12, 379 | }, 380 | side: NodeConfig { 381 | http: "".into(), 382 | contract: ContractConfig { 383 | bin: include_str!("../../compiled_contracts/Side.bin") 384 | .from_hex::>() 385 | .unwrap() 386 | .into(), 387 | }, 388 | poll_interval: Duration::from_secs(1), 389 | request_timeout: Duration::from_secs(5), 390 | required_confirmations: 12, 391 | }, 392 | authorities: Authorities { 393 | accounts: vec![ 394 | "0000000000000000000000000000000000000001".parse().unwrap(), 395 | "0000000000000000000000000000000000000002".parse().unwrap(), 396 | "0000000000000000000000000000000000000003".parse().unwrap(), 397 | ], 398 | required_signatures: 2, 399 | }, 400 | estimated_gas_cost_of_withdraw: U256::from_dec_str("200000000").unwrap(), 401 | max_total_main_contract_balance: U256::from_dec_str("10000000000000000000").unwrap(), 402 | max_single_deposit_value: U256::from_dec_str("1000000000000000000").unwrap(), 403 | }; 404 | 405 | let config = Config::load_from_str(toml).unwrap(); 406 | assert_eq!(expected, config); 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /bridge/src/database.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | //! concerning reading/writing `State` from/to toml file 18 | 19 | use error::{Error, ErrorKind, ResultExt}; 20 | use std::io::{Read, Write}; 21 | /// the state of a bridge node process and ways to persist it 22 | use std::path::{Path, PathBuf}; 23 | use std::{fmt, fs, io, str}; 24 | use toml; 25 | use web3::types::{Address, TransactionReceipt}; 26 | 27 | /// bridge process state 28 | #[derive(Debug, PartialEq, Deserialize, Serialize, Default, Clone)] 29 | pub struct State { 30 | /// Address of home contract. 31 | pub main_contract_address: Address, 32 | /// Address of foreign contract. 33 | pub side_contract_address: Address, 34 | /// Number of block at which home contract has been deployed. 35 | pub main_deployed_at_block: u64, 36 | /// Number of block at which foreign contract has been deployed. 37 | pub side_deployed_at_block: u64, 38 | /// Number of last block which has been checked for deposit relays. 39 | pub last_main_to_side_sign_at_block: u64, 40 | /// Number of last block which has been checked for withdraw relays. 41 | pub last_side_to_main_signatures_at_block: u64, 42 | /// Number of last block which has been checked for withdraw confirms. 43 | pub last_side_to_main_sign_at_block: u64, 44 | } 45 | 46 | impl State { 47 | /// creates initial state for the bridge processes 48 | /// from transaction receipts of contract deployments 49 | pub fn from_transaction_receipts( 50 | main_contract_deployment_receipt: &TransactionReceipt, 51 | side_contract_deployment_receipt: &TransactionReceipt, 52 | ) -> Self { 53 | let main_block_number = main_contract_deployment_receipt 54 | .block_number 55 | .expect("main contract creation receipt must have a block number; qed") 56 | .as_u64(); 57 | 58 | let side_block_number = side_contract_deployment_receipt 59 | .block_number 60 | .expect("main contract creation receipt must have a block number; qed") 61 | .as_u64(); 62 | 63 | Self { 64 | main_contract_address: main_contract_deployment_receipt 65 | .contract_address 66 | .expect("main contract creation receipt must have an address; qed"), 67 | side_contract_address: side_contract_deployment_receipt 68 | .contract_address 69 | .expect("side contract creation receipt must have an address; qed"), 70 | main_deployed_at_block: main_block_number, 71 | side_deployed_at_block: side_block_number, 72 | last_main_to_side_sign_at_block: main_block_number, 73 | last_side_to_main_sign_at_block: side_block_number, 74 | last_side_to_main_signatures_at_block: side_block_number, 75 | } 76 | } 77 | } 78 | 79 | impl State { 80 | /// write state to a `std::io::write` 81 | pub fn write(&self, mut write: W) -> Result<(), Error> { 82 | let serialized = toml::to_string(self).expect("serialization can't fail. q.e.d."); 83 | write.write_all(serialized.as_bytes())?; 84 | write.flush()?; 85 | Ok(()) 86 | } 87 | } 88 | 89 | impl fmt::Display for State { 90 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 91 | f.write_str(&toml::to_string(self).expect("serialization can't fail; qed")) 92 | } 93 | } 94 | 95 | /// persistence for a `State` 96 | pub trait Database { 97 | fn read(&self) -> State; 98 | /// persist `state` to the database 99 | fn write(&mut self, state: &State) -> Result<(), Error>; 100 | } 101 | 102 | /// `State` stored in a TOML file 103 | pub struct TomlFileDatabase { 104 | filepath: PathBuf, 105 | state: State, 106 | } 107 | 108 | impl TomlFileDatabase { 109 | /// create `TomlFileDatabase` backed by file at `filepath` 110 | pub fn from_path>(filepath: P) -> Result { 111 | let mut file = match fs::File::open(&filepath) { 112 | Ok(file) => file, 113 | Err(ref err) if err.kind() == io::ErrorKind::NotFound => { 114 | return Err(ErrorKind::MissingFile(format!("{:?}", filepath.as_ref())).into()) 115 | } 116 | Err(err) => return Err(err).chain_err(|| "Cannot open database"), 117 | }; 118 | 119 | let mut buffer = String::new(); 120 | file.read_to_string(&mut buffer)?; 121 | let state: State = toml::from_str(&buffer).chain_err(|| "Cannot parse database")?; 122 | Ok(Self { 123 | filepath: filepath.as_ref().to_path_buf(), 124 | state, 125 | }) 126 | } 127 | } 128 | 129 | impl Database for TomlFileDatabase { 130 | fn read(&self) -> State { 131 | self.state.clone() 132 | } 133 | 134 | fn write(&mut self, state: &State) -> Result<(), Error> { 135 | if self.state != *state { 136 | self.state = state.clone(); 137 | 138 | let file = fs::OpenOptions::new() 139 | .write(true) 140 | .create(true) 141 | .open(&self.filepath)?; 142 | 143 | self.state.write(file)?; 144 | } 145 | Ok(()) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /bridge/src/deploy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | //! concerning deployment of the bridge contracts 18 | 19 | use config::Config; 20 | use contracts; 21 | use error::{self, ResultExt}; 22 | use futures::{Future, Poll}; 23 | use rustc_hex::ToHex; 24 | use send_tx_with_receipt::{SendTransactionWithReceipt, SendTransactionWithReceiptOptions}; 25 | use std::fs; 26 | use std::fs::File; 27 | use std::io::Write; 28 | use std::path::Path; 29 | use web3::types::{TransactionReceipt, TransactionRequest}; 30 | use web3::Transport; 31 | 32 | pub enum DeployState { 33 | NotDeployed, 34 | Deploying { 35 | data: Vec, 36 | future: SendTransactionWithReceipt, 37 | }, 38 | Deployed { 39 | contract: DeployedContract, 40 | }, 41 | } 42 | 43 | pub struct DeployMain { 44 | config: Config, 45 | main_transport: T, 46 | state: DeployState, 47 | } 48 | 49 | impl DeployMain { 50 | pub fn new(config: Config, main_transport: T) -> Self { 51 | Self { 52 | config, 53 | main_transport, 54 | state: DeployState::NotDeployed, 55 | } 56 | } 57 | } 58 | 59 | impl Future for DeployMain { 60 | type Item = DeployedContract; 61 | type Error = error::Error; 62 | 63 | fn poll(&mut self) -> Poll { 64 | loop { 65 | let next_state = match self.state { 66 | DeployState::Deployed { ref contract } => return Ok(contract.clone().into()), 67 | DeployState::NotDeployed => { 68 | let data = contracts::main::constructor( 69 | self.config.main.contract.bin.clone().0, 70 | self.config.authorities.required_signatures, 71 | self.config.authorities.accounts.clone(), 72 | ); 73 | 74 | let tx_request = TransactionRequest { 75 | from: self.config.address, 76 | to: None, 77 | gas: Some(self.config.txs.main_deploy.gas.into()), 78 | gas_price: Some(self.config.txs.main_deploy.gas_price.into()), 79 | value: None, 80 | data: Some(data.clone().into()), 81 | nonce: None, 82 | condition: None, 83 | }; 84 | 85 | let future = 86 | SendTransactionWithReceipt::new(SendTransactionWithReceiptOptions { 87 | transport: self.main_transport.clone(), 88 | request_timeout: self.config.main.request_timeout, 89 | poll_interval: self.config.main.poll_interval, 90 | confirmations: self.config.main.required_confirmations, 91 | transaction: tx_request, 92 | }); 93 | 94 | info!("sending MainBridge contract deployment transaction and waiting for {} confirmations...", self.config.main.required_confirmations); 95 | 96 | DeployState::Deploying { data, future } 97 | } 98 | DeployState::Deploying { 99 | ref mut future, 100 | ref data, 101 | } => { 102 | let receipt = try_ready!(future 103 | .poll() 104 | .chain_err(|| "DeployMain: deployment transaction failed")); 105 | let address = receipt 106 | .contract_address 107 | .expect("contract creation receipt must have an address; qed"); 108 | info!("MainBridge deployment completed to {:?}", address); 109 | 110 | DeployState::Deployed { 111 | contract: DeployedContract::new( 112 | "Main".into(), 113 | include_str!("../../arbitrary/contracts/bridge.sol").into(), 114 | include_str!("../../compiled_contracts/Main.abi").into(), 115 | include_str!("../../compiled_contracts/Main.bin").into(), 116 | data.to_hex(), 117 | receipt, 118 | ), 119 | } 120 | } 121 | }; 122 | 123 | self.state = next_state; 124 | } 125 | } 126 | } 127 | 128 | pub struct DeploySide { 129 | config: Config, 130 | side_transport: T, 131 | state: DeployState, 132 | } 133 | 134 | impl DeploySide { 135 | pub fn new(config: Config, side_transport: T) -> Self { 136 | Self { 137 | config, 138 | side_transport, 139 | state: DeployState::NotDeployed, 140 | } 141 | } 142 | } 143 | 144 | impl Future for DeploySide { 145 | type Item = DeployedContract; 146 | type Error = error::Error; 147 | 148 | fn poll(&mut self) -> Poll { 149 | loop { 150 | let next_state = match self.state { 151 | DeployState::Deployed { ref contract } => return Ok(contract.clone().into()), 152 | DeployState::NotDeployed => { 153 | let data = contracts::side::constructor( 154 | self.config.side.contract.bin.clone().0, 155 | self.config.authorities.required_signatures, 156 | self.config.authorities.accounts.clone(), 157 | ); 158 | 159 | let tx_request = TransactionRequest { 160 | from: self.config.address, 161 | to: None, 162 | gas: Some(self.config.txs.side_deploy.gas.into()), 163 | gas_price: Some(self.config.txs.side_deploy.gas_price.into()), 164 | value: None, 165 | data: Some(data.clone().into()), 166 | nonce: None, 167 | condition: None, 168 | }; 169 | 170 | let future = 171 | SendTransactionWithReceipt::new(SendTransactionWithReceiptOptions { 172 | transport: self.side_transport.clone(), 173 | request_timeout: self.config.side.request_timeout, 174 | poll_interval: self.config.side.poll_interval, 175 | confirmations: self.config.side.required_confirmations, 176 | transaction: tx_request, 177 | }); 178 | 179 | info!("sending SideBridge contract deployment transaction and waiting for {} confirmations...", self.config.side.required_confirmations); 180 | 181 | DeployState::Deploying { data, future } 182 | } 183 | DeployState::Deploying { 184 | ref mut future, 185 | ref data, 186 | } => { 187 | let receipt = try_ready!(future 188 | .poll() 189 | .chain_err(|| "DeploySide: deployment transaction failed")); 190 | let address = receipt 191 | .contract_address 192 | .expect("contract creation receipt must have an address; qed"); 193 | info!("SideBridge deployment completed to {:?}", address); 194 | 195 | DeployState::Deployed { 196 | contract: DeployedContract::new( 197 | "SideBridge".into(), 198 | include_str!("../../arbitrary/contracts/bridge.sol").into(), 199 | include_str!("../../compiled_contracts/Side.abi").into(), 200 | include_str!("../../compiled_contracts/Side.bin").into(), 201 | data.to_hex(), 202 | receipt, 203 | ), 204 | } 205 | } 206 | }; 207 | 208 | self.state = next_state; 209 | } 210 | } 211 | } 212 | 213 | #[derive(Clone)] 214 | pub struct DeployedContract { 215 | pub contract_name: String, 216 | pub contract_address: String, 217 | pub contract_source: String, 218 | pub abi: String, 219 | pub bytecode_hex: String, 220 | pub contract_creation_code_hex: String, 221 | pub receipt: TransactionReceipt, 222 | } 223 | 224 | impl DeployedContract { 225 | pub fn new( 226 | contract_name: String, 227 | contract_source: String, 228 | abi: String, 229 | bytecode_hex: String, 230 | contract_creation_code_hex: String, 231 | receipt: TransactionReceipt, 232 | ) -> Self { 233 | assert_eq!( 234 | bytecode_hex, 235 | &contract_creation_code_hex[..bytecode_hex.len()], 236 | "deployed byte code is contract bytecode followed by constructor args; qed" 237 | ); 238 | 239 | Self { 240 | contract_name, 241 | contract_address: format!( 242 | "{:x}", 243 | receipt 244 | .contract_address 245 | .expect("contract creation receipt must have an address; qed") 246 | ), 247 | contract_source, 248 | abi, 249 | bytecode_hex, 250 | contract_creation_code_hex, 251 | receipt, 252 | } 253 | } 254 | 255 | /// writes useful information about the deployment into `dir`. 256 | /// REMOVES `dir` if it already exists! 257 | /// helps with troubleshooting and verification (https://ropsten.etherscan.io/verifyContract) 258 | /// of deployments. 259 | /// information includes: 260 | /// - solc version used 261 | /// - git commit 262 | /// - contract source code 263 | /// - contract address 264 | /// - hash of transaction the contract got deployed in 265 | /// - contract byte code 266 | /// - input data for contract creation transaction 267 | /// - ... 268 | pub fn dump_info>(&self, dir: P) -> Result<(), error::Error> { 269 | let dir = dir.as_ref(); 270 | 271 | if Path::new(dir).exists() { 272 | info!("{:?} exists. removing", dir); 273 | fs::remove_dir_all(dir)?; 274 | } 275 | fs::create_dir(dir)?; 276 | info!("{:?} created", dir); 277 | 278 | let mut file = File::create(dir.join("bridge_version"))?; 279 | file.write_all(env!("CARGO_PKG_VERSION").as_bytes())?; 280 | 281 | let mut file = File::create(dir.join("commit_hash"))?; 282 | file.write_all(env!("GIT_HASH").as_bytes())?; 283 | 284 | let mut file = File::create(dir.join("compiler"))?; 285 | file.write_all(env!("SOLC_VERSION").as_bytes())?; 286 | 287 | let mut file = File::create(dir.join("optimization"))?; 288 | file.write_all("yes".as_bytes())?; 289 | 290 | let mut file = File::create(dir.join("contract_name"))?; 291 | file.write_all(self.contract_name.as_bytes())?; 292 | 293 | let mut file = File::create(dir.join("contract_address"))?; 294 | file.write_all(self.contract_address.as_bytes())?; 295 | 296 | let mut file = File::create(dir.join("contract_source.sol"))?; 297 | file.write_all(self.contract_source.as_bytes())?; 298 | 299 | let mut file = File::create(dir.join("transaction_hash"))?; 300 | file.write_all(format!("{:x}", self.receipt.transaction_hash).as_bytes())?; 301 | 302 | let mut file = File::create(dir.join("deployed_bytecode"))?; 303 | file.write_all(self.bytecode_hex.as_bytes())?; 304 | 305 | let constructor_arguments_bytecode = 306 | &self.contract_creation_code_hex[self.bytecode_hex.len()..]; 307 | 308 | let mut file = File::create(dir.join("constructor_arguments_bytecode"))?; 309 | file.write_all(constructor_arguments_bytecode.as_bytes())?; 310 | 311 | File::create(dir.join("abi"))?.write_all(self.abi.as_bytes())?; 312 | 313 | Ok(()) 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /bridge/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | //! error chain 18 | 19 | use std::io; 20 | use tokio_timer::{TimeoutError, TimerError}; 21 | use {ethabi, rustc_hex, toml, web3}; 22 | 23 | error_chain! { 24 | types { 25 | Error, ErrorKind, ResultExt, Result; 26 | } 27 | 28 | foreign_links { 29 | Io(io::Error); 30 | Toml(toml::de::Error); 31 | Ethabi(ethabi::Error); 32 | Timer(TimerError); 33 | Hex(rustc_hex::FromHexError); 34 | } 35 | 36 | errors { 37 | TimedOut { 38 | description("Request timed out"), 39 | display("Request timed out"), 40 | } 41 | // workaround for error_chain not allowing to check internal error kind 42 | // https://github.com/rust-lang-nursery/error-chain/issues/206 43 | MissingFile(filename: String) { 44 | description("File not found"), 45 | display("File {} not found", filename), 46 | } 47 | // workaround for lack of web3:Error Display and Error implementations 48 | Web3(err: web3::Error) { 49 | description("web3 error"), 50 | display("{:?}", err), 51 | } 52 | } 53 | } 54 | 55 | // tokio timer `Timeout` can only wrap futures `F` whose assocaited `Error` type 56 | // satisfies `From>` 57 | // 58 | // `web3::CallResult`'s associated error type `Error` which is `web3::Error` 59 | // does not satisfy `From>`. 60 | // thus we can't use `Timeout`. 61 | // we also can't implement `From` for `web3::Error` since 62 | // we control neither of the types. 63 | // 64 | // instead we implement `TimeoutError` for `Error` and `From` 65 | // for `Error` so we can convert `web3::Error` into `Error` and then use that 66 | // with `Timeout`. 67 | 68 | impl From> for Error { 69 | fn from(err: TimeoutError) -> Self { 70 | match err { 71 | TimeoutError::Timer(_, timer_error) => timer_error.into(), 72 | TimeoutError::TimedOut(_) => ErrorKind::TimedOut.into(), 73 | } 74 | } 75 | } 76 | 77 | impl From for Error { 78 | fn from(err: web3::Error) -> Self { 79 | ErrorKind::Web3(err).into() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /bridge/src/helpers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | //! various helper functions 18 | 19 | use error::{self, ResultExt}; 20 | use ethabi::{self, FunctionOutputDecoder, RawLog}; 21 | use futures::future::FromErr; 22 | use futures::{Async, Future, Poll, Stream}; 23 | use serde::de::Error; 24 | use serde::{Deserialize, Deserializer, Serializer}; 25 | use std::time::Duration; 26 | use tokio_timer::{Timeout, Timer}; 27 | use web3::api::Namespace; 28 | use web3::helpers::CallFuture; 29 | use web3::types::{Address, Bytes, CallRequest, TransactionRequest, H256, U256}; 30 | use web3::{self, Transport}; 31 | 32 | /// attempts to convert a raw `web3_log` into the ethabi log type of a specific `event` 33 | pub fn parse_log ethabi::Result, L>( 34 | parse: T, 35 | web3_log: &web3::types::Log, 36 | ) -> ethabi::Result { 37 | let ethabi_log = RawLog { 38 | topics: web3_log.topics.iter().map(|t| t.0.into()).collect(), 39 | data: web3_log.data.0.clone(), 40 | }; 41 | parse(ethabi_log) 42 | } 43 | 44 | /// use `AsyncCall::new(transport, contract_address, timeout, output_decoder)` to 45 | /// get a `Future` that resolves with the decoded output from calling `function` 46 | /// on `contract_address`. 47 | pub struct AsyncCall { 48 | future: Timeout, error::Error>>, 49 | output_decoder: F, 50 | } 51 | 52 | impl AsyncCall { 53 | /// call `function` at `contract_address`. 54 | /// returns a `Future` that resolves with the decoded output of `function`. 55 | pub fn new( 56 | transport: &T, 57 | contract_address: Address, 58 | timeout: Duration, 59 | payload: Vec, 60 | output_decoder: F, 61 | ) -> Self { 62 | let request = CallRequest { 63 | from: None, 64 | to: contract_address, 65 | gas: None, 66 | gas_price: None, 67 | value: None, 68 | data: Some(Bytes(payload)), 69 | }; 70 | let inner_future = web3::api::Eth::new(transport) 71 | .call(request, None) 72 | .from_err(); 73 | let future = Timer::default().timeout(inner_future, timeout); 74 | Self { 75 | future, 76 | output_decoder, 77 | } 78 | } 79 | } 80 | 81 | impl Future for AsyncCall { 82 | type Item = F::Output; 83 | type Error = error::Error; 84 | 85 | fn poll(&mut self) -> Poll { 86 | let encoded = try_ready!(self 87 | .future 88 | .poll() 89 | .chain_err(|| "failed to poll inner web3 CallFuture future")); 90 | let decoded = self 91 | .output_decoder 92 | .decode(&encoded.0) 93 | .chain_err(|| format!("failed to decode response {:?}", encoded))?; 94 | Ok(Async::Ready(decoded)) 95 | } 96 | } 97 | 98 | pub struct AsyncTransaction { 99 | future: Timeout, error::Error>>, 100 | } 101 | 102 | impl AsyncTransaction { 103 | pub fn new( 104 | transport: &T, 105 | contract_address: Address, 106 | authority_address: Address, 107 | gas: U256, 108 | gas_price: U256, 109 | timeout: Duration, 110 | payload: Vec, 111 | ) -> Self { 112 | let request = TransactionRequest { 113 | from: authority_address, 114 | to: Some(contract_address), 115 | gas: Some(gas), 116 | gas_price: Some(gas_price), 117 | value: None, 118 | data: Some(Bytes(payload)), 119 | nonce: None, 120 | condition: None, 121 | }; 122 | let inner_future = web3::api::Eth::new(transport) 123 | .send_transaction(request) 124 | .from_err(); 125 | let future = Timer::default().timeout(inner_future, timeout); 126 | Self { future } 127 | } 128 | } 129 | 130 | impl Future for AsyncTransaction { 131 | type Item = H256; 132 | type Error = error::Error; 133 | fn poll(&mut self) -> Poll { 134 | self.future.poll().map_err(|x| x.into()) 135 | } 136 | } 137 | 138 | /// the toml crate parses integer literals as `i64`. 139 | /// certain config options (example: `max_total_home_contract_balance`) 140 | /// frequently don't fit into `i64`. 141 | /// workaround: put them in string literals, use this custom 142 | /// deserializer and parse them as U256. 143 | pub fn deserialize_u256<'de, D>(deserializer: D) -> Result 144 | where 145 | D: Deserializer<'de>, 146 | { 147 | let s: &str = Deserialize::deserialize(deserializer)?; 148 | U256::from_dec_str(s).map_err(|_| D::Error::custom("failed to parse U256 from dec str")) 149 | } 150 | 151 | pub fn serialize_u256(value: &U256, serializer: S) -> Result 152 | where 153 | S: Serializer, 154 | { 155 | serializer.serialize_str(&format!("{}", value)) 156 | } 157 | 158 | /// extends the `Stream` trait by the `last` function 159 | pub trait StreamExt { 160 | /// if you're interested only in the last item in a stream 161 | fn last(self) -> Last 162 | where 163 | Self: Sized; 164 | } 165 | 166 | impl StreamExt for S 167 | where 168 | S: Stream, 169 | { 170 | fn last(self) -> Last 171 | where 172 | Self: Sized, 173 | { 174 | Last { 175 | stream: self, 176 | last: None, 177 | } 178 | } 179 | } 180 | 181 | /// `Future` that wraps a `Stream` and completes with the last 182 | /// item in the stream once the stream is over. 183 | pub struct Last { 184 | stream: S, 185 | last: Option, 186 | } 187 | 188 | impl Future for Last 189 | where 190 | S: Stream, 191 | { 192 | type Item = Option; 193 | type Error = S::Error; 194 | 195 | fn poll(&mut self) -> Poll { 196 | loop { 197 | match self.stream.poll() { 198 | Err(err) => return Err(err), 199 | Ok(Async::NotReady) => return Ok(Async::NotReady), 200 | // stream is finished 201 | Ok(Async::Ready(None)) => return Ok(Async::Ready(self.last.take())), 202 | // there is more 203 | Ok(Async::Ready(item)) => self.last = item, 204 | } 205 | } 206 | } 207 | } 208 | 209 | #[cfg(test)] 210 | mod tests { 211 | use super::*; 212 | use futures; 213 | use tokio_core::reactor::Core; 214 | 215 | #[test] 216 | fn test_stream_ext_last_empty() { 217 | let stream = futures::stream::empty::<(), ()>(); 218 | let mut event_loop = Core::new().unwrap(); 219 | assert_eq!(event_loop.run(stream.last()).unwrap(), None); 220 | } 221 | 222 | #[test] 223 | fn test_stream_ext_last_once_ok() { 224 | let stream = futures::stream::once::(Ok(42)); 225 | let mut event_loop = Core::new().unwrap(); 226 | assert_eq!(event_loop.run(stream.last()).unwrap(), Some(42)); 227 | } 228 | 229 | #[test] 230 | fn test_stream_ext_last_once_err() { 231 | let stream = futures::stream::once::(Err(42)); 232 | let mut event_loop = Core::new().unwrap(); 233 | assert_eq!(event_loop.run(stream.last()).unwrap_err(), 42); 234 | } 235 | 236 | #[test] 237 | fn test_stream_ext_last_three() { 238 | let stream = futures::stream::iter_ok::<_, ()>(vec![17, 19, 3]); 239 | let mut event_loop = Core::new().unwrap(); 240 | assert_eq!(event_loop.run(stream.last()).unwrap(), Some(3)); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /bridge/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | //! the `parity-bridge` executable in `cli/src/main.rs` creates a 18 | //! `Bridge` instance, which is a `Stream`, and then endlessly polls it, 19 | //! thereby running the bridge. 20 | //! 21 | //! the `Bridge` instance internally polls three `RelayStream`s. 22 | //! these correspond to the three relay operations a bridge node is responsible for: 23 | //! 24 | //! 1. `MainToSideSign`: signing off on messages from `main` to `side`. currently that means executing `sideContract.deposit` for every `mainContract.Deposit` event. 25 | //! 2. `SideToMainSign`: signing off on messages from `side` to `main`. currently that means executing `sideContract.submitSignature` for every `sideContract.Withdraw` event. 26 | //! 3. `SideToMainSignatures`: submitting the bundle of signatures collected on `side` through `SideToMainSign` to `main`. currently that means executing `mainContract.withdraw` for every `sideContract.CollectedSignatures` event. 27 | //! 28 | //! a `RelayStream` is logic that's common to the three relay operations. 29 | //! it takes a `Stream` of logs and a `LogToFuture` that maps logs to `Futures`. 30 | //! those futures are supposed to each do one single complete relay 31 | //! (1. `MainToSideSign`, 2. `SideToMainSign`, 3. `SideToMainSignatures`). 32 | //! `RelayStream` polls the log stream, calls `LogToFuture.log_to_future` for each log, 33 | //! and yields the numbers of those blocks for which all such created futures 34 | //! have completed. these block numbers are then persisted 35 | //! so the bridge doesn't have to check logs up to them again next time it's started. 36 | //! 37 | //! a `Bridge` instance is constructed as follows (how the parts fit together): 38 | //! 39 | //! - a tokio `event_loop` is created. 40 | //! - `main_transport` and `side_transport` which are web3 http transports 41 | //! are created and each use an `event_loop` handle 42 | //! - the initial `state` is read from the database file 43 | //! - the `config` is read from the config file 44 | //! - `main_contract` (`side_contract`) which is for interaction with the main (side) bridge contract 45 | //! is created from `main_transport` (`side_transport`), `config` and `state` 46 | //! - the `Bridge` instance is created from the two contracts and `state` 47 | //! - retrieves log streams for the three events to watch 48 | //! - creates the three `RelayStream`s described above 49 | //! 50 | //! when the `Bridge` instance is polled: 51 | //! 52 | //! - it polls the three `RelayStream`s 53 | //! - each `RelayStream` polls all relay futures that are currently running as well as the log 54 | //! stream 55 | //! - if the log stream yields a log the relay stream creates the corresponding relay future 56 | //! - the relay future is responsible for the entire relay operation 57 | //! - currently relay futures check whether the specific relay has already happened, 58 | //! ignore if it has and execute the corresponding transaction otherwise 59 | //! - relay futures should (currently don't) and easily could observe whether 60 | //! the transaction succeeds, log it to help with troubleshooting and 61 | //! retry if the condition can be recovered from 62 | 63 | #[macro_use] 64 | extern crate error_chain; 65 | extern crate ethabi; 66 | extern crate ethereum_types; 67 | #[macro_use] 68 | extern crate futures; 69 | #[macro_use] 70 | extern crate log; 71 | #[macro_use] 72 | extern crate pretty_assertions; 73 | #[cfg(test)] 74 | #[macro_use] 75 | extern crate quickcheck; 76 | extern crate rustc_hex; 77 | extern crate serde; 78 | #[macro_use] 79 | extern crate serde_derive; 80 | extern crate bridge_contracts as contracts; 81 | #[cfg_attr(test, macro_use)] 82 | extern crate serde_json; 83 | extern crate tiny_keccak; 84 | extern crate tokio_core; 85 | extern crate tokio_timer; 86 | extern crate toml; 87 | extern crate web3; 88 | 89 | #[macro_use] 90 | mod macros; 91 | 92 | #[cfg(test)] 93 | #[macro_use] 94 | mod test; 95 | 96 | mod block_number_stream; 97 | mod bridge; 98 | pub use bridge::Bridge; 99 | pub mod config; 100 | pub mod database; 101 | pub mod deploy; 102 | pub mod error; 103 | mod ordered_stream; 104 | pub use ordered_stream::OrderedStream; 105 | pub mod helpers; 106 | mod main_contract; 107 | pub use main_contract::MainContract; 108 | mod accept_message_from_main; 109 | pub use accept_message_from_main::AcceptMessageFromMain; 110 | mod relay_stream; 111 | pub use relay_stream::RelayStream; 112 | mod send_tx_with_receipt; 113 | mod side_contract; 114 | pub use side_contract::SideContract; 115 | mod side_to_main_sign; 116 | pub use side_to_main_sign::SideToMainSign; 117 | mod side_to_main_signatures; 118 | pub use side_to_main_signatures::SideToMainSignatures; 119 | 120 | mod log_stream; 121 | pub use log_stream::{LogStream, LogStreamOptions}; 122 | 123 | mod signature; 124 | pub use signature::Signature; 125 | 126 | mod message_to_main; 127 | pub use message_to_main::{MessageToMain, MESSAGE_LENGTH}; 128 | 129 | #[cfg(test)] 130 | extern crate jsonrpc_core; 131 | 132 | #[cfg(test)] 133 | pub use test::MockTransport; 134 | -------------------------------------------------------------------------------- /bridge/src/log_stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | use block_number_stream::{BlockNumberStream, BlockNumberStreamOptions}; 18 | use error::{self, ResultExt}; 19 | use ethabi; 20 | use futures::future::FromErr; 21 | use futures::{Async, Future, Poll, Stream}; 22 | use std::time::Duration; 23 | use tokio_timer::{Timeout, Timer}; 24 | use web3; 25 | use web3::api::Namespace; 26 | use web3::helpers::CallFuture; 27 | use web3::types::{Address, FilterBuilder, Log, H256}; 28 | use web3::Transport; 29 | 30 | fn ethabi_topic_to_web3(topic: ðabi::Topic) -> Option> { 31 | match topic { 32 | ethabi::Topic::Any => None, 33 | ethabi::Topic::OneOf(options) => Some(options.clone()), 34 | ethabi::Topic::This(hash) => Some(vec![hash.clone()]), 35 | } 36 | } 37 | 38 | fn filter_to_builder(filter: ðabi::TopicFilter, address: Address) -> FilterBuilder { 39 | let t0 = ethabi_topic_to_web3(&filter.topic0); 40 | let t1 = ethabi_topic_to_web3(&filter.topic1); 41 | let t2 = ethabi_topic_to_web3(&filter.topic2); 42 | let t3 = ethabi_topic_to_web3(&filter.topic3); 43 | FilterBuilder::default() 44 | .address(vec![address]) 45 | .topics(t0, t1, t2, t3) 46 | } 47 | 48 | /// options for creating a `LogStream`. passed to `LogStream::new` 49 | pub struct LogStreamOptions { 50 | pub filter: ethabi::TopicFilter, 51 | pub request_timeout: Duration, 52 | pub poll_interval: Duration, 53 | pub confirmations: u32, 54 | pub transport: T, 55 | pub contract_address: Address, 56 | pub after: u64, 57 | } 58 | 59 | /// Contains all logs matching `LogStream` filter in inclusive block range `[from, to]`. 60 | #[derive(Debug, PartialEq)] 61 | pub struct LogsInBlockRange { 62 | pub from: u64, 63 | pub to: u64, 64 | pub logs: Vec, 65 | } 66 | 67 | /// Log Stream state. 68 | enum State { 69 | /// Fetching best block number. 70 | AwaitBlockNumber, 71 | /// Fetching logs for new best block. 72 | AwaitLogs { 73 | from: u64, 74 | to: u64, 75 | future: Timeout, T::Out>, error::Error>>, 76 | }, 77 | } 78 | 79 | /// `Stream` that repeatedly polls logs matching `filter_builder` from `contract_address` 80 | /// with adjustable `poll_interval` and `request_timeout`. 81 | /// yields new logs that are `confirmations` blocks deep. 82 | pub struct LogStream { 83 | block_number_stream: BlockNumberStream, 84 | request_timeout: Duration, 85 | transport: T, 86 | last_checked_block: u64, 87 | timer: Timer, 88 | state: State, 89 | filter_builder: FilterBuilder, 90 | topic: Vec, 91 | } 92 | 93 | impl LogStream { 94 | pub fn new(options: LogStreamOptions) -> Self { 95 | let timer = Timer::default(); 96 | 97 | let topic = ethabi_topic_to_web3(&options.filter.topic0) 98 | .expect("filter must have at least 1 topic. q.e.d."); 99 | let filter_builder = filter_to_builder(&options.filter, options.contract_address); 100 | 101 | let block_number_stream_options = BlockNumberStreamOptions { 102 | request_timeout: options.request_timeout, 103 | poll_interval: options.poll_interval, 104 | confirmations: options.confirmations, 105 | transport: options.transport.clone(), 106 | after: options.after, 107 | }; 108 | 109 | LogStream { 110 | block_number_stream: BlockNumberStream::new(block_number_stream_options), 111 | request_timeout: options.request_timeout, 112 | transport: options.transport, 113 | last_checked_block: options.after, 114 | timer, 115 | state: State::AwaitBlockNumber, 116 | filter_builder, 117 | topic, 118 | } 119 | } 120 | } 121 | 122 | impl Stream for LogStream { 123 | type Item = LogsInBlockRange; 124 | type Error = error::Error; 125 | 126 | fn poll(&mut self) -> Poll, Self::Error> { 127 | loop { 128 | let (next_state, value_to_yield) = match self.state { 129 | State::AwaitBlockNumber => { 130 | let last_block = try_stream!(self 131 | .block_number_stream 132 | .poll() 133 | .chain_err(|| "LogStream: fetching of last confirmed block number failed")); 134 | info!("LogStream: fetched confirmed block number {}", last_block); 135 | 136 | let from = self.last_checked_block + 1; 137 | let filter = self 138 | .filter_builder 139 | .clone() 140 | .from_block(from.into()) 141 | .to_block(last_block.into()) 142 | .build(); 143 | let future = web3::api::Eth::new(&self.transport).logs(filter); 144 | 145 | info!( 146 | "LogStream: fetching logs in blocks {} to {}", 147 | from, last_block 148 | ); 149 | 150 | let next_state = State::AwaitLogs { 151 | from: from, 152 | to: last_block, 153 | future: self.timer.timeout(future.from_err(), self.request_timeout), 154 | }; 155 | 156 | (next_state, None) 157 | } 158 | State::AwaitLogs { 159 | ref mut future, 160 | from, 161 | to, 162 | } => { 163 | let logs = try_ready!(future 164 | .poll() 165 | .chain_err(|| "LogStream: polling web3 logs failed")); 166 | info!( 167 | "LogStream (topic: {:?}): fetched {} logs from block {} to block {}", 168 | self.topic, 169 | logs.len(), 170 | from, 171 | to 172 | ); 173 | let log_range_to_yield = LogsInBlockRange { from, to, logs }; 174 | 175 | self.last_checked_block = to; 176 | (State::AwaitBlockNumber, Some(log_range_to_yield)) 177 | } 178 | }; 179 | 180 | self.state = next_state; 181 | 182 | if value_to_yield.is_some() { 183 | return Ok(Async::Ready(value_to_yield)); 184 | } 185 | } 186 | } 187 | } 188 | 189 | #[cfg(test)] 190 | mod tests { 191 | use super::*; 192 | use contracts; 193 | use rustc_hex::FromHex; 194 | use tokio_core::reactor::Core; 195 | use web3::types::{Bytes, Log}; 196 | 197 | #[test] 198 | fn test_log_stream_twice_no_logs() { 199 | let deposit_topic = contracts::main::events::relay_message::filter().topic0; 200 | 201 | let transport = mock_transport!( 202 | "eth_blockNumber" => 203 | req => json!([]), 204 | res => json!("0x1011"); 205 | "eth_getLogs" => 206 | req => json!([{ 207 | "address": "0x0000000000000000000000000000000000000001", 208 | "fromBlock": "0x4", 209 | "toBlock": "0x1005", 210 | "topics": [deposit_topic] 211 | }]), 212 | res => json!([]); 213 | "eth_blockNumber" => 214 | req => json!([]), 215 | res => json!("0x1012"); 216 | "eth_getLogs" => 217 | req => json!([{ 218 | "address": "0x0000000000000000000000000000000000000001", 219 | "fromBlock": "0x1006", 220 | "toBlock": "0x1006", 221 | "topics": [deposit_topic] 222 | }]), 223 | res => json!([]); 224 | ); 225 | 226 | let log_stream = LogStream::new(LogStreamOptions { 227 | request_timeout: Duration::from_secs(1), 228 | poll_interval: Duration::from_secs(1), 229 | confirmations: 12, 230 | transport: transport.clone(), 231 | contract_address: "0000000000000000000000000000000000000001".parse().unwrap(), 232 | after: 3, 233 | filter: contracts::main::events::relay_message::filter(), 234 | }); 235 | 236 | let mut event_loop = Core::new().unwrap(); 237 | let log_ranges = event_loop.run(log_stream.take(2).collect()).unwrap(); 238 | 239 | assert_eq!( 240 | log_ranges, 241 | vec![ 242 | LogsInBlockRange { 243 | from: 4, 244 | to: 4101, 245 | logs: vec![], 246 | }, 247 | LogsInBlockRange { 248 | from: 4102, 249 | to: 4102, 250 | logs: vec![], 251 | }, 252 | ] 253 | ); 254 | assert_eq!(transport.actual_requests(), transport.expected_requests()); 255 | } 256 | 257 | #[test] 258 | fn test_log_stream_once_one_log() { 259 | let deposit_topic = contracts::main::events::relay_message::filter().topic0; 260 | 261 | let transport = mock_transport!( 262 | "eth_blockNumber" => 263 | req => json!([]), 264 | res => json!("0x1011"); 265 | "eth_getLogs" => 266 | req => json!([{ 267 | "address": "0x0000000000000000000000000000000000000001", 268 | "fromBlock": "0x4", 269 | "toBlock": "0x1005", 270 | "topics": [deposit_topic], 271 | }]), 272 | res => json!([{ 273 | "address": "0x0000000000000000000000000000000000000cc1", 274 | "topics": [deposit_topic], 275 | "data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0", 276 | "type": "", 277 | "transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 278 | }]); 279 | ); 280 | 281 | let log_stream = LogStream::new(LogStreamOptions { 282 | request_timeout: Duration::from_secs(1), 283 | poll_interval: Duration::from_secs(1), 284 | confirmations: 12, 285 | transport: transport.clone(), 286 | contract_address: "0000000000000000000000000000000000000001".parse().unwrap(), 287 | after: 3, 288 | filter: contracts::main::events::relay_message::filter(), 289 | }); 290 | 291 | let mut event_loop = Core::new().unwrap(); 292 | let log_ranges = event_loop.run(log_stream.take(1).collect()).unwrap(); 293 | 294 | assert_eq!( 295 | log_ranges, 296 | vec![ 297 | LogsInBlockRange { from: 4, to: 4101, logs: vec![ 298 | Log { 299 | address: "0000000000000000000000000000000000000cc1".parse().unwrap(), 300 | topics: deposit_topic.into(), 301 | data: Bytes("000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0".from_hex().unwrap()), 302 | transaction_hash: Some("884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".parse().unwrap()), 303 | block_hash: None, 304 | block_number: None, 305 | transaction_index: None, 306 | log_index: None, 307 | transaction_log_index: None, 308 | log_type: None, 309 | removed: None, 310 | } 311 | ] }, 312 | ]); 313 | assert_eq!(transport.actual_requests(), transport.expected_requests()); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /bridge/src/macros.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | /// like `try_ready!` but for streams 17 | macro_rules! try_stream { 18 | ($e:expr) => { 19 | match $e { 20 | Err(err) => return Err(From::from(err)), 21 | Ok($crate::futures::Async::NotReady) => return Ok($crate::futures::Async::NotReady), 22 | Ok($crate::futures::Async::Ready(None)) => { 23 | return Ok($crate::futures::Async::Ready(None)) 24 | } 25 | Ok($crate::futures::Async::Ready(Some(value))) => value, 26 | } 27 | }; 28 | } 29 | 30 | /// like `try_stream` but returns `None` if `NotReady` 31 | macro_rules! try_maybe_stream { 32 | ($e:expr) => { 33 | match $e { 34 | Err(err) => return Err(From::from(err)), 35 | Ok($crate::futures::Async::NotReady) => None, 36 | Ok($crate::futures::Async::Ready(None)) => { 37 | return Ok($crate::futures::Async::Ready(None)) 38 | } 39 | Ok($crate::futures::Async::Ready(Some(value))) => Some(value), 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /bridge/src/main_contract.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use config::Config; 17 | use contracts; 18 | use database::State; 19 | use ethabi::FunctionOutputDecoder; 20 | use ethereum_types::{Address, H256, U256}; 21 | use helpers::{AsyncCall, AsyncTransaction}; 22 | use log_stream::{LogStream, LogStreamOptions}; 23 | use message_to_main::MessageToMain; 24 | use signature::Signature; 25 | use std::time::Duration; 26 | use web3::Transport; 27 | 28 | /// highlevel wrapper around the auto generated ethabi contract `bridge_contracts::main` 29 | #[derive(Clone)] 30 | pub struct MainContract { 31 | pub transport: T, 32 | pub contract_address: Address, 33 | pub authority_address: Address, 34 | pub submit_collected_signatures_gas: U256, 35 | pub request_timeout: Duration, 36 | pub logs_poll_interval: Duration, 37 | pub required_log_confirmations: u32, 38 | } 39 | 40 | impl MainContract { 41 | pub fn new(transport: T, config: &Config, state: &State) -> Self { 42 | Self { 43 | transport, 44 | contract_address: state.main_contract_address, 45 | authority_address: config.address, 46 | submit_collected_signatures_gas: config.estimated_gas_cost_of_withdraw, 47 | request_timeout: config.main.request_timeout, 48 | logs_poll_interval: config.main.poll_interval, 49 | required_log_confirmations: config.main.required_confirmations, 50 | } 51 | } 52 | 53 | pub fn call( 54 | &self, 55 | payload: Vec, 56 | output_decoder: F, 57 | ) -> AsyncCall { 58 | AsyncCall::new( 59 | &self.transport, 60 | self.contract_address, 61 | self.request_timeout, 62 | payload, 63 | output_decoder, 64 | ) 65 | } 66 | 67 | pub fn is_main_contract( 68 | &self, 69 | ) -> AsyncCall { 70 | let (payload, decoder) = contracts::main::functions::is_main_bridge_contract::call(); 71 | self.call(payload, decoder) 72 | } 73 | 74 | /// relay a tx from side to main by submitting message and collected signatures 75 | pub fn relay_side_to_main( 76 | &self, 77 | message: &MessageToMain, 78 | signatures: &Vec, 79 | data: Vec, 80 | ) -> AsyncTransaction { 81 | let payload = contracts::main::functions::accept_message::encode_input( 82 | signatures.iter().map(|x| x.v), 83 | signatures.iter().map(|x| x.r), 84 | signatures.iter().map(|x| x.s), 85 | message.side_tx_hash, 86 | data, 87 | message.sender, 88 | message.recipient, 89 | ); 90 | 91 | AsyncTransaction::new( 92 | &self.transport, 93 | self.contract_address, 94 | self.authority_address, 95 | self.submit_collected_signatures_gas, 96 | // TODO: 97 | //message.main_gas_price, 98 | 1000.into(), 99 | self.request_timeout, 100 | payload, 101 | ) 102 | } 103 | 104 | pub fn main_to_side_log_stream(&self, after: u64) -> LogStream { 105 | LogStream::new(LogStreamOptions { 106 | filter: contracts::main::events::relay_message::filter(), 107 | request_timeout: self.request_timeout, 108 | poll_interval: self.logs_poll_interval, 109 | confirmations: self.required_log_confirmations, 110 | transport: self.transport.clone(), 111 | contract_address: self.contract_address, 112 | after, 113 | }) 114 | } 115 | 116 | pub fn relayed_message_by_id( 117 | &self, 118 | id: H256, 119 | ) -> AsyncCall { 120 | let (payload, decoder) = contracts::main::functions::relayed_messages::call(id); 121 | self.call(payload, decoder) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /bridge/src/message_to_main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | use contracts; 18 | use error::Error; 19 | use ethabi; 20 | use ethereum_types::{Address, H256}; 21 | use helpers; 22 | use tiny_keccak::{self, Hasher}; 23 | use web3::types::Log; 24 | 25 | /// the message that is relayed from `side` to `main`. 26 | /// contains all the information required for the relay. 27 | /// bridge nodes sign off on this message in `SideToMainSign`. 28 | /// one node submits this message and signatures in `SideToMainSignatures`. 29 | #[derive(PartialEq, Debug, Clone)] 30 | pub struct MessageToMain { 31 | pub side_tx_hash: H256, 32 | pub message_id: H256, 33 | pub sender: Address, 34 | pub recipient: Address, 35 | } 36 | 37 | /// length of a `MessageToMain.to_bytes()` in bytes 38 | pub const MESSAGE_LENGTH: usize = 32 + 32 + 20 + 20; 39 | 40 | impl MessageToMain { 41 | /// parses message from a byte slice 42 | pub fn from_bytes(bytes: &[u8]) -> Result { 43 | if bytes.len() != MESSAGE_LENGTH { 44 | bail!("`bytes`.len() must be {}", MESSAGE_LENGTH); 45 | } 46 | 47 | Ok(Self { 48 | side_tx_hash: H256::from_slice(&bytes[0..32]), 49 | message_id: H256::from_slice(&bytes[32..64]), 50 | sender: Address::from_slice(&bytes[64..84]), 51 | recipient: Address::from_slice(&bytes[84..104]), 52 | }) 53 | } 54 | 55 | pub fn keccak256(&self) -> H256 { 56 | let mut output = [0u8; 32]; 57 | let mut keccak = tiny_keccak::Keccak::v256(); 58 | keccak.update(&self.to_bytes()); 59 | keccak.finalize(&mut output); 60 | H256::from_slice(&output) 61 | } 62 | 63 | /// construct a message from a `Withdraw` event that was logged on `side` 64 | pub fn from_log(raw_log: &Log) -> Result { 65 | let hash = raw_log 66 | .transaction_hash 67 | .ok_or_else(|| "`log` must be mined and contain `transaction_hash`")?; 68 | let log = helpers::parse_log(contracts::side::events::relay_message::parse_log, raw_log)?; 69 | Ok(Self { 70 | side_tx_hash: hash, 71 | message_id: log.message_id, 72 | sender: log.sender, 73 | recipient: log.recipient, 74 | }) 75 | } 76 | 77 | /// serializes message to a byte vector. 78 | /// mainly used to construct the message byte vector that is then signed 79 | /// and passed to `SideBridge.submitSignature` 80 | pub fn to_bytes(&self) -> Vec { 81 | let mut result = vec![0u8; MESSAGE_LENGTH]; 82 | result[0..32].copy_from_slice(&self.side_tx_hash.0[..]); 83 | result[32..64].copy_from_slice(&self.message_id.0[..]); 84 | result[64..84].copy_from_slice(&self.sender.0[..]); 85 | result[84..104].copy_from_slice(&self.recipient.0[..]); 86 | return result; 87 | } 88 | 89 | /// serializes message to an ethabi payload 90 | pub fn to_payload(&self) -> Vec { 91 | ethabi::encode(&[ethabi::Token::Bytes(self.to_bytes())]) 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod test { 97 | use super::*; 98 | use quickcheck::TestResult; 99 | use rustc_hex::FromHex; 100 | 101 | #[test] 102 | fn test_message_to_main_to_bytes() { 103 | let side_tx_hash: H256 = "75ebc3036b5a5a758be9a8c0e6f6ed8d46c640dda39845de99d9570ba76798e2" 104 | .parse() 105 | .unwrap(); 106 | let message_id: H256 = "75ebc3036b5a5a758be9a8c0e6f6ed8d46c640dda39845de99d9570ba76798ff" 107 | .parse() 108 | .unwrap(); 109 | let sender: Address = "eac4a655451e159313c3641e29824e77d6fcb0aa".parse().unwrap(); 110 | let recipient: Address = "eac4a655451e159313c3641e29824e77d6fcb0bb".parse().unwrap(); 111 | 112 | let message = MessageToMain { 113 | side_tx_hash, 114 | message_id, 115 | sender, 116 | recipient, 117 | }; 118 | 119 | assert_eq!(message.to_bytes(), "75ebc3036b5a5a758be9a8c0e6f6ed8d46c640dda39845de99d9570ba76798e275ebc3036b5a5a758be9a8c0e6f6ed8d46c640dda39845de99d9570ba76798ffeac4a655451e159313c3641e29824e77d6fcb0aaeac4a655451e159313c3641e29824e77d6fcb0bb".from_hex::>().unwrap()) 120 | } 121 | 122 | quickcheck! { 123 | fn quickcheck_message_to_main_roundtrips_to_bytes( 124 | side_tx_hash_raw: Vec, 125 | message_id_raw: Vec, 126 | sender_raw: Vec, 127 | recipient_raw: Vec 128 | ) -> TestResult { 129 | if side_tx_hash_raw.len() != 32 || 130 | message_id_raw.len() != 32 || 131 | sender_raw.len() != 20 || 132 | recipient_raw.len() != 20 { 133 | return TestResult::discard(); 134 | } 135 | 136 | let side_tx_hash = H256::from_slice(side_tx_hash_raw.as_slice()); 137 | let message_id = H256::from_slice(message_id_raw.as_slice()); 138 | let sender = Address::from_slice(sender_raw.as_slice()); 139 | let recipient = Address::from_slice(recipient_raw.as_slice()); 140 | 141 | let message = MessageToMain { 142 | side_tx_hash, 143 | message_id, 144 | sender, 145 | recipient, 146 | }; 147 | 148 | let bytes = message.to_bytes(); 149 | assert_eq!(message, MessageToMain::from_bytes(bytes.as_slice()).unwrap()); 150 | 151 | let payload = message.to_payload(); 152 | let mut tokens = ethabi::decode(&[ethabi::ParamType::Bytes], payload.as_slice()) 153 | .unwrap(); 154 | let decoded = tokens.pop().unwrap().to_bytes().unwrap(); 155 | assert_eq!(message, MessageToMain::from_bytes(decoded.as_slice()).unwrap()); 156 | 157 | TestResult::passed() 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /bridge/src/ordered_stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use futures::{Async, Future, Poll, Stream}; 17 | 18 | /// `OrderedStream` is a `Stream` that yields the 19 | /// values of a list of `Future`s in a predefined order which is 20 | /// independent from when the individual `Future`s complete. 21 | /// 22 | /// let's say you `insert` a future **A** with order `4` into the ordered stream and a future **B** with order `2`. 23 | /// even if **A** becomes available first the value of **B** 24 | /// is yielded first because **B**s order is lower than **A**s: 25 | /// 26 | /// ``` 27 | /// # extern crate tokio_core; 28 | /// # extern crate tokio_timer; 29 | /// # extern crate bridge; 30 | /// # extern crate futures; 31 | /// # use std::time::Duration; 32 | /// # use bridge::OrderedStream; 33 | /// # use futures::stream::Stream; 34 | /// # use futures::Future; 35 | /// let mut ordered_stream: OrderedStream>> = OrderedStream::new(); 36 | /// 37 | /// let timer = tokio_timer::Timer::default(); 38 | /// 39 | /// let a = timer.sleep(Duration::from_secs(1)).join(futures::future::ok("a")); 40 | /// let b = timer.sleep(Duration::from_secs(2)).join(futures::future::ok("b")); 41 | /// 42 | /// ordered_stream.insert(4, a); 43 | /// ordered_stream.insert(2, b); 44 | /// 45 | /// let mut event_loop = tokio_core::reactor::Core::new().unwrap(); 46 | /// 47 | /// let results = event_loop.run(ordered_stream.take(2).collect()).unwrap(); 48 | /// assert_eq!(results[0], (2, ((), "b"))); 49 | /// assert_eq!(results[1], (4, ((), "a"))); 50 | /// ``` 51 | /// 52 | /// items with the same `order` are yielded in the order they were `insert`ed. 53 | /// 54 | /// example in the context of the bridge: 55 | /// a `RelayStream` polls a Stream of logs 56 | /// calls a ... for every log and yields the block number for 57 | /// every block number 58 | /// by using the block number as `order`, 59 | /// and yielding the previous block number each time it changes 60 | /// this is easily accomplished. 61 | /// TODO 62 | 63 | pub struct OrderedStream { 64 | entries: Vec>, 65 | } 66 | 67 | impl OrderedStream { 68 | /// returns a new empty `OrderedStream` 69 | pub fn new() -> Self { 70 | Self { 71 | entries: Vec::new(), 72 | } 73 | } 74 | 75 | /// insert a `future` into this that should be yielded 76 | /// when it is completed and there are currently no 77 | /// futures inside the stream that have a smaller `order`. 78 | pub fn insert(&mut self, order: O, future: F) { 79 | self.entries.push(Entry { 80 | order, 81 | future, 82 | item_if_ready: None, 83 | }); 84 | } 85 | 86 | /// returns the count of futures that have completed but can't be 87 | /// yielded since there are futures which are not ready 88 | pub fn ready_count(&self) -> usize { 89 | self.entries 90 | .iter() 91 | .filter(|x| x.item_if_ready.is_some()) 92 | .count() 93 | } 94 | 95 | /// returns the count of futures that have not yet completed 96 | pub fn not_ready_count(&self) -> usize { 97 | self.entries 98 | .iter() 99 | .filter(|x| x.item_if_ready.is_none()) 100 | .count() 101 | } 102 | } 103 | 104 | impl Stream for OrderedStream { 105 | type Item = (O, F::Item); 106 | type Error = F::Error; 107 | 108 | /// `O(n)` where `n = self.entries.len()`. 109 | /// there's not much that can be done to improve this `O` since `poll` always must `poll` all `self.entries`. 110 | fn poll(&mut self) -> Poll, Self::Error> { 111 | // minimum of orders of entries which are not ready 112 | let mut maybe_min_not_ready: Option = None; 113 | // the index (in entries) of the completed order with the lowest order 114 | let mut maybe_min_ready: Option<(O, usize)> = None; 115 | 116 | for (index, entry) in self.entries.iter_mut().enumerate() { 117 | // poll futures which are not ready without every polling any future twice. 118 | if !entry.item_if_ready.is_some() { 119 | if let Async::Ready(item) = entry.future.poll()? { 120 | entry.item_if_ready = Some(item); 121 | } else { 122 | maybe_min_not_ready = maybe_min_not_ready 123 | .map(|x| x.min(entry.order.clone())) 124 | .or(Some(entry.order.clone())); 125 | } 126 | } 127 | 128 | if entry.item_if_ready.is_some() // item must be ready 129 | // we must initialize `maybe_min_ready` 130 | && (maybe_min_ready.is_none() 131 | // or entry is the new min 132 | || entry.order < maybe_min_ready.clone().expect("check in prev line. q.e.d.").0) 133 | { 134 | maybe_min_ready = Some((entry.order.clone(), index)); 135 | } 136 | } 137 | 138 | if maybe_min_ready.is_none() { 139 | // there is no min ready -> none are ready 140 | return Ok(Async::NotReady); 141 | } 142 | 143 | let (min_ready_order, min_ready_index) = 144 | maybe_min_ready.expect("check and early return if none above. q.e.d."); 145 | 146 | if let Some(min_not_ready_order) = maybe_min_not_ready { 147 | // some are ready but there's unready ones with lower order 148 | if min_not_ready_order < min_ready_order { 149 | // there are futures which are not ready 150 | // but must be yielded before the ones that are ready 151 | // since their `order` is lower 152 | return Ok(Async::NotReady); 153 | } 154 | } 155 | 156 | // this is O(1) 157 | let entry_to_yield = self.entries.swap_remove(min_ready_index); 158 | 159 | Ok(Async::Ready(Some(( 160 | entry_to_yield.order, 161 | entry_to_yield 162 | .item_if_ready 163 | .expect("`min_ready_index` points to index of entry with result. q.e.d."), 164 | )))) 165 | } 166 | } 167 | 168 | /// an entry in an `OrderedStream` 169 | struct Entry { 170 | order: O, 171 | future: F, 172 | item_if_ready: Option, 173 | } 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | use super::*; 178 | extern crate futures; 179 | extern crate tokio_core; 180 | extern crate tokio_timer; 181 | use futures::stream::Stream; 182 | use futures::Future; 183 | use std::time::Duration; 184 | 185 | // TODO test multiple ready at same time 186 | // 187 | // TODO all are ready. none are not ready 188 | 189 | #[test] 190 | fn test_empty_ordered_stream_is_not_ready() { 191 | let mut ordered_stream: OrderedStream< 192 | u32, 193 | futures::future::Join< 194 | tokio_timer::Sleep, 195 | futures::future::FutureResult<&str, tokio_timer::TimerError>, 196 | >, 197 | > = OrderedStream::new(); 198 | 199 | assert_eq!(ordered_stream.poll(), Ok(Async::NotReady)); 200 | assert_eq!(ordered_stream.ready_count(), 0); 201 | assert_eq!(ordered_stream.not_ready_count(), 0); 202 | } 203 | 204 | #[test] 205 | fn test_single_insert() { 206 | let mut ordered_stream: OrderedStream< 207 | u32, 208 | futures::future::Join< 209 | tokio_timer::Sleep, 210 | futures::future::FutureResult<&str, tokio_timer::TimerError>, 211 | >, 212 | > = OrderedStream::new(); 213 | 214 | assert_eq!(ordered_stream.poll(), Ok(Async::NotReady)); 215 | assert_eq!(ordered_stream.ready_count(), 0); 216 | assert_eq!(ordered_stream.not_ready_count(), 0); 217 | 218 | let timer = tokio_timer::Timer::default(); 219 | ordered_stream.insert( 220 | 10, 221 | timer 222 | .sleep(Duration::from_millis(0)) 223 | .join(futures::future::ok("f")), 224 | ); 225 | 226 | assert_eq!(ordered_stream.ready_count(), 0); 227 | assert_eq!(ordered_stream.not_ready_count(), 1); 228 | 229 | let mut event_loop = tokio_core::reactor::Core::new().unwrap(); 230 | 231 | let stream_future = ordered_stream.into_future(); 232 | let (item, ordered_stream) = if let Ok(success) = event_loop.run(stream_future) { 233 | success 234 | } else { 235 | panic!("failed to run stream_future"); 236 | }; 237 | 238 | assert_eq!(item, Some((10, ((), "f")))); 239 | 240 | assert_eq!(ordered_stream.ready_count(), 0); 241 | assert_eq!(ordered_stream.not_ready_count(), 0); 242 | } 243 | 244 | #[test] 245 | fn test_ordered_stream_7_insertions_with_some_duplicate_orders() { 246 | let mut ordered_stream: OrderedStream< 247 | u32, 248 | futures::future::Join< 249 | tokio_timer::Sleep, 250 | futures::future::FutureResult<&str, tokio_timer::TimerError>, 251 | >, 252 | > = OrderedStream::new(); 253 | 254 | let timer = tokio_timer::Timer::default(); 255 | 256 | ordered_stream.insert( 257 | 10, 258 | timer 259 | .sleep(Duration::from_millis(0)) 260 | .join(futures::future::ok("f")), 261 | ); 262 | ordered_stream.insert( 263 | 4, 264 | timer 265 | .sleep(Duration::from_millis(1)) 266 | .join(futures::future::ok("e")), 267 | ); 268 | ordered_stream.insert( 269 | 3, 270 | timer 271 | .sleep(Duration::from_millis(65)) 272 | .join(futures::future::ok("d")), 273 | ); 274 | ordered_stream.insert( 275 | 0, 276 | timer 277 | .sleep(Duration::from_millis(500)) 278 | .join(futures::future::ok("a")), 279 | ); 280 | ordered_stream.insert( 281 | 2, 282 | timer 283 | .sleep(Duration::from_millis(50)) 284 | .join(futures::future::ok("b")), 285 | ); 286 | ordered_stream.insert( 287 | 2, 288 | timer 289 | .sleep(Duration::from_millis(10)) 290 | .join(futures::future::ok("c")), 291 | ); 292 | ordered_stream.insert( 293 | 10, 294 | timer 295 | .sleep(Duration::from_millis(338)) 296 | .join(futures::future::ok("g")), 297 | ); 298 | 299 | assert_eq!(ordered_stream.ready_count(), 0); 300 | assert_eq!(ordered_stream.not_ready_count(), 7); 301 | 302 | let mut event_loop = tokio_core::reactor::Core::new().unwrap(); 303 | 304 | let results = event_loop.run(ordered_stream.take(7).collect()).unwrap(); 305 | assert_eq!( 306 | results, 307 | vec![ 308 | (0, ((), "a")), 309 | (2, ((), "b")), 310 | (2, ((), "c")), 311 | (3, ((), "d")), 312 | (4, ((), "e")), 313 | (10, ((), "f")), 314 | (10, ((), "g")), 315 | ] 316 | ); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /bridge/src/relay_stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | /// extraction of a pattern that occurred repeatedly in the codebase 17 | /// 18 | /// where a "relay" is the detection of an event on chain A 19 | /// followed by a transaction on chain B 20 | use error::{self, ResultExt}; 21 | use futures::{Async, Future, Poll, Stream}; 22 | use log_stream::LogsInBlockRange; 23 | use web3::types::Log; 24 | use OrderedStream; 25 | 26 | /// something that can create relay futures from logs. 27 | /// to be called by `RelayStream` for every log. 28 | pub trait LogToFuture { 29 | type Future: Future; 30 | 31 | fn log_to_future(&self, log: &Log) -> Self::Future; 32 | } 33 | 34 | /// a tokio `Stream` that when polled fetches all new logs from `stream_of_logs` 35 | /// calls `log_to_future` for each to obtain relay futures, waits for those 36 | /// futures to complete and yields the block numbers for which all relay 37 | /// futures have completed. 38 | /// those block numbers can then be persisted since they'll never need to be 39 | /// checked again. 40 | pub struct RelayStream, F: LogToFuture> { 41 | stream_of_logs: S, 42 | log_to_future: F, 43 | /// reorders relay futures so they are yielded in block order 44 | /// rather than the order they complete. 45 | /// this is required because relay futures are not guaranteed to 46 | /// complete in block order. 47 | ordered_stream: OrderedStream, 48 | } 49 | 50 | impl, F: LogToFuture> RelayStream { 51 | pub fn new(stream_of_logs: S, log_to_future: F) -> Self { 52 | Self { 53 | stream_of_logs, 54 | log_to_future, 55 | ordered_stream: OrderedStream::new(), 56 | } 57 | } 58 | } 59 | 60 | impl, F: LogToFuture> Stream 61 | for RelayStream 62 | { 63 | type Item = u64; 64 | type Error = error::Error; 65 | 66 | fn poll(&mut self) -> Poll, Self::Error> { 67 | // on each poll we loop until there are neither new logs 68 | // nor newly completed relays 69 | loop { 70 | let maybe_logs_in_block_range = try_maybe_stream!(self 71 | .stream_of_logs 72 | .poll() 73 | .chain_err(|| "RelayStream: fetching logs failed")); 74 | 75 | if let Some(ref logs_in_block_range) = maybe_logs_in_block_range { 76 | // if there are new logs, create futures from them 77 | // which are responsible for the relay and add them to the 78 | // ordered stream 79 | for log in &logs_in_block_range.logs { 80 | let relay_future = self.log_to_future.log_to_future(log); 81 | self.ordered_stream 82 | .insert(logs_in_block_range.to, relay_future); 83 | } 84 | } 85 | 86 | let maybe_fully_relayed_until_block = try_maybe_stream!(self 87 | .ordered_stream 88 | .poll() 89 | .chain_err(|| "RelayStream: relaying logs failed")); 90 | 91 | if let Some((fully_relayed_until_block, _)) = maybe_fully_relayed_until_block { 92 | // all relay futures for this block or before have completed 93 | // we can yield the block number which can be safely 94 | // persisted since it doesn't need to get checked again 95 | return Ok(Async::Ready(Some(fully_relayed_until_block))); 96 | } 97 | 98 | if maybe_logs_in_block_range.is_none() && maybe_fully_relayed_until_block.is_none() { 99 | // there are neither new logs nor is there a new block number 100 | // until which all relays have completed 101 | return Ok(Async::NotReady); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /bridge/src/send_tx_with_receipt.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | use error::{self, ResultExt}; 18 | use futures::future::FromErr; 19 | use futures::{Future, Poll}; 20 | use std::time::Duration; 21 | use tokio_timer::{Timeout, Timer}; 22 | use web3::api::Namespace; 23 | use web3::helpers::CallFuture; 24 | use web3::types::{TransactionReceipt, TransactionRequest, U64}; 25 | use web3::{self, Transport}; 26 | 27 | mod inner { 28 | use block_number_stream::{BlockNumberStream, BlockNumberStreamOptions}; 29 | use error::{self, ResultExt}; 30 | use futures::future::FromErr; 31 | use futures::{Async, Future, Poll, Stream}; 32 | use std::time::Duration; 33 | use tokio_timer::{Timeout, Timer}; 34 | use web3::api::Namespace; 35 | use web3::helpers::CallFuture; 36 | use web3::types::{TransactionReceipt, TransactionRequest, H256}; 37 | use web3::{self, Transport}; 38 | 39 | enum State { 40 | AwaitSendTransaction(Timeout, error::Error>>), 41 | AwaitBlockNumber(H256), 42 | AwaitTransactionReceipt { 43 | future: Timeout, T::Out>, error::Error>>, 44 | transaction_hash: H256, 45 | last_block: u64, 46 | }, 47 | } 48 | 49 | pub struct SendTransactionWithReceiptOptions { 50 | pub transport: T, 51 | pub request_timeout: Duration, 52 | pub poll_interval: Duration, 53 | pub confirmations: u32, 54 | pub transaction: TransactionRequest, 55 | pub after: u64, 56 | } 57 | 58 | pub struct SendTransactionWithReceipt { 59 | transport: T, 60 | state: State, 61 | block_number_stream: BlockNumberStream, 62 | request_timeout: Duration, 63 | timer: Timer, 64 | } 65 | 66 | impl SendTransactionWithReceipt { 67 | pub fn new(options: SendTransactionWithReceiptOptions) -> Self { 68 | let timer = Timer::default(); 69 | 70 | let block_number_stream_options = BlockNumberStreamOptions { 71 | request_timeout: options.request_timeout, 72 | poll_interval: options.poll_interval, 73 | confirmations: options.confirmations, 74 | transport: options.transport.clone(), 75 | after: options.after, 76 | }; 77 | let block_number_stream = BlockNumberStream::new(block_number_stream_options); 78 | let future = 79 | web3::api::Eth::new(&options.transport).send_transaction(options.transaction); 80 | let future = timer.timeout(future.from_err(), options.request_timeout); 81 | 82 | SendTransactionWithReceipt { 83 | transport: options.transport, 84 | state: State::AwaitSendTransaction(future), 85 | block_number_stream, 86 | request_timeout: options.request_timeout, 87 | timer, 88 | } 89 | } 90 | } 91 | 92 | impl Future for SendTransactionWithReceipt { 93 | type Item = TransactionReceipt; 94 | type Error = error::Error; 95 | 96 | fn poll(&mut self) -> Poll { 97 | loop { 98 | let next_state = match self.state { 99 | State::AwaitSendTransaction(ref mut future) => { 100 | let hash = try_ready!(future.poll().chain_err(|| { 101 | "SendTransactionWithReceipt: sending transaction failed" 102 | })); 103 | info!("SendTransactionWithReceipt: sent transaction {}", hash); 104 | State::AwaitBlockNumber(hash) 105 | } 106 | State::AwaitBlockNumber(transaction_hash) => { 107 | let last_block = match try_ready!( 108 | self.block_number_stream 109 | .poll() 110 | .chain_err(|| "SendTransactionWithReceipt: fetching of last confirmed block failed") 111 | ) { 112 | Some(last_block) => last_block, 113 | None => bail!("SendTransactionWithReceipt: fetching of last confirmed block failed"), 114 | }; 115 | 116 | info!( 117 | "SendTransactionWithReceipt: fetched confirmed block number {}", 118 | last_block 119 | ); 120 | let future = web3::api::Eth::new(&self.transport) 121 | .transaction_receipt(transaction_hash); 122 | State::AwaitTransactionReceipt { 123 | future: self.timer.timeout(future.from_err(), self.request_timeout), 124 | transaction_hash, 125 | last_block, 126 | } 127 | } 128 | State::AwaitTransactionReceipt { 129 | ref mut future, 130 | transaction_hash, 131 | last_block, 132 | } => { 133 | let maybe_receipt = try_ready!(future.poll().chain_err(|| { 134 | "SendTransactionWithReceipt: getting transaction receipt failed" 135 | })); 136 | 137 | match maybe_receipt { 138 | // transaction hasn't been mined yet 139 | None => State::AwaitBlockNumber(transaction_hash), 140 | Some(receipt) => { 141 | info!( 142 | "SendTransactionWithReceipt: got transaction receipt: {}", 143 | transaction_hash 144 | ); 145 | match receipt.block_number { 146 | // receipt comes from pending block 147 | None => State::AwaitBlockNumber(transaction_hash), 148 | Some(receipt_block_number) => { 149 | if last_block < receipt_block_number.as_u64() { 150 | // transaction does not have enough confirmations 151 | State::AwaitBlockNumber(transaction_hash) 152 | } else { 153 | return Ok(Async::Ready(receipt)); 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | }; 161 | 162 | self.state = next_state; 163 | } 164 | } 165 | } 166 | } 167 | 168 | enum State { 169 | AwaitBlockNumber { 170 | future: Timeout, error::Error>>, 171 | transaction: Option, 172 | }, 173 | AwaitReceipt(inner::SendTransactionWithReceipt), 174 | } 175 | 176 | pub struct SendTransactionWithReceiptOptions { 177 | pub transport: T, 178 | pub request_timeout: Duration, 179 | pub poll_interval: Duration, 180 | pub confirmations: u32, 181 | pub transaction: TransactionRequest, 182 | } 183 | 184 | pub struct SendTransactionWithReceipt { 185 | request_timeout: Duration, 186 | poll_interval: Duration, 187 | transport: T, 188 | state: State, 189 | confirmations: u32, 190 | } 191 | 192 | impl SendTransactionWithReceipt { 193 | pub fn new(options: SendTransactionWithReceiptOptions) -> Self { 194 | let timer = Timer::default(); 195 | 196 | let future = web3::api::Eth::new(&options.transport).block_number(); 197 | 198 | let state = State::AwaitBlockNumber { 199 | future: timer.timeout(future.from_err(), options.request_timeout), 200 | transaction: Some(options.transaction), 201 | }; 202 | 203 | SendTransactionWithReceipt { 204 | request_timeout: options.request_timeout, 205 | poll_interval: options.poll_interval, 206 | transport: options.transport, 207 | state, 208 | confirmations: options.confirmations, 209 | } 210 | } 211 | } 212 | 213 | impl Future for SendTransactionWithReceipt { 214 | type Item = TransactionReceipt; 215 | type Error = error::Error; 216 | 217 | fn poll(&mut self) -> Poll { 218 | loop { 219 | let next_state = match self.state { 220 | State::AwaitBlockNumber { 221 | ref mut future, 222 | ref mut transaction, 223 | } => { 224 | let block_number = try_ready!(future.poll().chain_err(|| { 225 | "SendTransactionWithReceipt: fetching last block number failed" 226 | })); 227 | info!( 228 | "SendTransactionWithReceipt: got last block number {}", 229 | block_number 230 | ); 231 | let transaction = transaction.take().expect( 232 | "SendTransactionWithReceipt is always created with 233 | State::AwaitBlockNumber with transaction set to Some; qed", 234 | ); 235 | 236 | let inner_options = inner::SendTransactionWithReceiptOptions { 237 | transport: self.transport.clone(), 238 | request_timeout: self.request_timeout, 239 | poll_interval: self.poll_interval, 240 | confirmations: self.confirmations, 241 | transaction, 242 | after: block_number.as_u64(), 243 | }; 244 | 245 | let future = inner::SendTransactionWithReceipt::new(inner_options); 246 | State::AwaitReceipt(future) 247 | } 248 | State::AwaitReceipt(ref mut future) => return future.poll(), 249 | }; 250 | 251 | self.state = next_state; 252 | } 253 | } 254 | } 255 | 256 | #[cfg(test)] 257 | mod tests { 258 | use super::*; 259 | use tokio_core::reactor::Core; 260 | 261 | #[test] 262 | fn test_send_tx_with_receipt() { 263 | let transport = mock_transport!( 264 | "eth_blockNumber" => 265 | req => json!([]), 266 | res => json!("0x1010"); 267 | "eth_sendTransaction" => 268 | req => json!([{ 269 | "data": "0x60", 270 | "from": "0x006b5dda44dc2606f07ad86c9190fb54fd905f6d", 271 | "gas": "0xf4240", 272 | "gasPrice": "0x0" 273 | }]), 274 | res => json!("0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34"); 275 | "eth_blockNumber" => 276 | req => json!([]), 277 | res => json!("0x1011"); 278 | "eth_blockNumber" => 279 | req => json!([]), 280 | res => json!("0x1012"); 281 | "eth_blockNumber" => 282 | req => json!([]), 283 | res => json!("0x1013"); 284 | "eth_getTransactionReceipt" => 285 | req => json!(["0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34"]), 286 | res => json!(null); 287 | "eth_blockNumber" => 288 | req => json!([]), 289 | res => json!("0x1014"); 290 | "eth_getTransactionReceipt" => 291 | req => json!(["0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34"]), 292 | res => json!({ 293 | "blockHash": null, 294 | "blockNumber": null, 295 | "contractAddress": "0xb1ac3a5584519119419a8e56422d912c782d8e5b", 296 | "cumulativeGasUsed": "0x1c1999", 297 | "gasUsed": "0xcdb5d", 298 | "logs": [], 299 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 300 | "root": null, 301 | "status": "0x1", 302 | "transactionHash": "0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34", 303 | "transactionIndex":"0x4" 304 | }); 305 | "eth_blockNumber" => 306 | req => json!([]), 307 | res => json!("0x1014"); 308 | "eth_blockNumber" => 309 | req => json!([]), 310 | res => json!("0x1015"); 311 | "eth_getTransactionReceipt" => 312 | req => json!(["0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34"]), 313 | res => json!({ 314 | "blockHash": "0xe0bdcf35b14a292d2998308d9b3fdea93a8c3d9c0b6c824c633fb9b15f9c3919", 315 | "blockNumber": "0x1015", 316 | "contractAddress": "0xb1ac3a5584519119419a8e56422d912c782d8e5b", 317 | "cumulativeGasUsed": "0x1c1999", 318 | "gasUsed": "0xcdb5d", 319 | "logs": [], 320 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 321 | "root": null, 322 | "status": "0x1", 323 | "transactionHash": "0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34", 324 | "transactionIndex":"0x4" 325 | }); 326 | "eth_blockNumber" => 327 | req => json!([]), 328 | res => json!("0x1017"); 329 | "eth_getTransactionReceipt" => 330 | req => json!(["0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34"]), 331 | res => json!({ 332 | "blockHash": "0xe0bdcf35b14a292d2998308d9b3fdea93a8c3d9c0b6c824c633fb9b15f9c3919", 333 | "blockNumber": "0x1015", 334 | "contractAddress": "0xb1ac3a5584519119419a8e56422d912c782d8e5b", 335 | "cumulativeGasUsed": "0x1c1999", 336 | "gasUsed": "0xcdb5d", 337 | "logs": [], 338 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 339 | "root": null, 340 | "status": "0x1", 341 | "transactionHash": "0x36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34", 342 | "transactionIndex":"0x4" 343 | }); 344 | ); 345 | 346 | let send_transaction_with_receipt = 347 | SendTransactionWithReceipt::new(SendTransactionWithReceiptOptions { 348 | transport: transport.clone(), 349 | request_timeout: Duration::from_secs(1), 350 | poll_interval: Duration::from_secs(0), 351 | confirmations: 2, 352 | transaction: TransactionRequest { 353 | from: "006b5dda44dc2606f07ad86c9190fb54fd905f6d".parse().unwrap(), 354 | to: None, 355 | gas: Some(0xf4240.into()), 356 | gas_price: Some(0.into()), 357 | value: None, 358 | data: Some(vec![0x60].into()), 359 | nonce: None, 360 | condition: None, 361 | }, 362 | }); 363 | 364 | let mut event_loop = Core::new().unwrap(); 365 | let receipt = event_loop.run(send_transaction_with_receipt).unwrap(); 366 | assert_eq!( 367 | receipt, 368 | TransactionReceipt { 369 | transaction_hash: 370 | "36efc16910ea67a2425a1e75f7e39e3c6a94f5763c68a47258f552481e20cd34" 371 | .parse() 372 | .unwrap(), 373 | transaction_index: 0x4.into(), 374 | block_hash: Some( 375 | "e0bdcf35b14a292d2998308d9b3fdea93a8c3d9c0b6c824c633fb9b15f9c3919" 376 | .parse() 377 | .unwrap() 378 | ), 379 | block_number: Some(0x1015.into()), 380 | cumulative_gas_used: 0x1c1999.into(), 381 | gas_used: "cdb5d".parse().ok(), 382 | contract_address: Some("b1ac3a5584519119419a8e56422d912c782d8e5b".parse().unwrap()), 383 | logs: vec![], 384 | status: Some(1.into()), 385 | logs_bloom: Default::default(), 386 | } 387 | ); 388 | assert_eq!(transport.actual_requests(), transport.expected_requests()); 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /bridge/src/side_contract.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use config::Config; 17 | use contracts; 18 | use database::State; 19 | use ethabi::FunctionOutputDecoder; 20 | use futures::future::{join_all, JoinAll}; 21 | use helpers::{AsyncCall, AsyncTransaction}; 22 | use log_stream::{LogStream, LogStreamOptions}; 23 | use message_to_main::MessageToMain; 24 | use signature::Signature; 25 | use std::time::Duration; 26 | use web3::types::{Address, H256, U256}; 27 | use web3::Transport; 28 | 29 | /// highlevel wrapper around the auto generated ethabi contract `bridge_contracts::side` 30 | #[derive(Clone)] 31 | pub struct SideContract { 32 | pub transport: T, 33 | pub contract_address: Address, 34 | pub authority_address: Address, 35 | // TODO [snd] this should get fetched from the contract 36 | pub required_signatures: u32, 37 | pub request_timeout: Duration, 38 | pub logs_poll_interval: Duration, 39 | pub required_log_confirmations: u32, 40 | pub sign_main_to_side_gas: U256, 41 | pub sign_main_to_side_gas_price: U256, 42 | pub sign_side_to_main_gas: U256, 43 | pub sign_side_to_main_gas_price: U256, 44 | } 45 | 46 | impl SideContract { 47 | pub fn new(transport: T, config: &Config, state: &State) -> Self { 48 | Self { 49 | transport, 50 | contract_address: state.side_contract_address, 51 | authority_address: config.address, 52 | required_signatures: config.authorities.required_signatures, 53 | request_timeout: config.side.request_timeout, 54 | logs_poll_interval: config.side.poll_interval, 55 | required_log_confirmations: config.side.required_confirmations, 56 | sign_main_to_side_gas: config.txs.deposit_relay.gas, 57 | sign_main_to_side_gas_price: config.txs.deposit_relay.gas_price, 58 | sign_side_to_main_gas: config.txs.withdraw_confirm.gas, 59 | sign_side_to_main_gas_price: config.txs.withdraw_confirm.gas_price, 60 | } 61 | } 62 | 63 | pub fn call( 64 | &self, 65 | payload: Vec, 66 | output_decoder: F, 67 | ) -> AsyncCall { 68 | AsyncCall::new( 69 | &self.transport, 70 | self.contract_address, 71 | self.request_timeout, 72 | payload, 73 | output_decoder, 74 | ) 75 | } 76 | 77 | pub fn is_side_contract( 78 | &self, 79 | ) -> AsyncCall { 80 | let (payload, decoder) = contracts::side::functions::is_side_bridge_contract::call(); 81 | self.call(payload, decoder) 82 | } 83 | 84 | /// returns `Future` that resolves with `bool` whether `authority` 85 | /// has signed side to main relay for `tx_hash` 86 | pub fn is_side_to_main_signed_on_side( 87 | &self, 88 | message: &MessageToMain, 89 | ) -> AsyncCall { 90 | let (payload, decoder) = contracts::side::functions::has_authority_signed_message::call( 91 | self.authority_address, 92 | message.to_bytes(), 93 | ); 94 | 95 | self.call(payload, decoder) 96 | } 97 | 98 | pub fn is_message_accepted_from_main( 99 | &self, 100 | transaction_hash: H256, 101 | data: Vec, 102 | sender: Address, 103 | recipient: Address, 104 | ) -> AsyncCall 105 | { 106 | let (payload, decoder) = 107 | contracts::side::functions::has_authority_accepted_message_from_main::call( 108 | transaction_hash, 109 | data, 110 | sender, 111 | recipient, 112 | self.authority_address, 113 | ); 114 | 115 | self.call(payload, decoder) 116 | } 117 | 118 | pub fn accept_message_from_main( 119 | &self, 120 | transaction_hash: H256, 121 | data: Vec, 122 | sender: Address, 123 | recipient: Address, 124 | ) -> AsyncTransaction { 125 | let payload = contracts::side::functions::accept_message::encode_input( 126 | transaction_hash, 127 | data, 128 | sender, 129 | recipient, 130 | ); 131 | 132 | AsyncTransaction::new( 133 | &self.transport, 134 | self.contract_address, 135 | self.authority_address, 136 | self.sign_main_to_side_gas, 137 | self.sign_main_to_side_gas_price, 138 | self.request_timeout, 139 | payload, 140 | ) 141 | } 142 | 143 | pub fn side_to_main_sign_log_stream(&self, after: u64) -> LogStream { 144 | LogStream::new(LogStreamOptions { 145 | filter: contracts::side::events::relay_message::filter(), 146 | request_timeout: self.request_timeout, 147 | poll_interval: self.logs_poll_interval, 148 | confirmations: self.required_log_confirmations, 149 | transport: self.transport.clone(), 150 | contract_address: self.contract_address, 151 | after, 152 | }) 153 | } 154 | 155 | pub fn side_to_main_signatures_log_stream(&self, after: u64, address: Address) -> LogStream { 156 | LogStream::new(LogStreamOptions { 157 | filter: contracts::side::events::signed_message::filter(address), 158 | request_timeout: self.request_timeout, 159 | poll_interval: self.logs_poll_interval, 160 | confirmations: self.required_log_confirmations, 161 | transport: self.transport.clone(), 162 | contract_address: self.contract_address, 163 | after, 164 | }) 165 | } 166 | 167 | pub fn submit_signed_message( 168 | &self, 169 | message: &MessageToMain, 170 | signature: &Signature, 171 | ) -> AsyncTransaction { 172 | let payload = contracts::side::functions::submit_signed_message::encode_input( 173 | signature.to_bytes(), 174 | message.to_bytes(), 175 | ); 176 | AsyncTransaction::new( 177 | &self.transport, 178 | self.contract_address, 179 | self.authority_address, 180 | self.sign_side_to_main_gas, 181 | self.sign_side_to_main_gas_price, 182 | self.request_timeout, 183 | payload, 184 | ) 185 | } 186 | 187 | pub fn get_signatures( 188 | &self, 189 | message_hash: H256, 190 | ) -> JoinAll>> { 191 | let futures = (0..self.required_signatures) 192 | .into_iter() 193 | .map(|index| { 194 | let (payload, decoder) = 195 | contracts::side::functions::signature::call(message_hash, index); 196 | self.call(payload, decoder) 197 | }) 198 | .collect::>(); 199 | join_all(futures) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /bridge/src/side_to_main_sign.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use contracts; 17 | use error::{self, ResultExt}; 18 | use futures::future::FromErr; 19 | use futures::{Async, Future, Poll}; 20 | use helpers::{AsyncCall, AsyncTransaction}; 21 | use message_to_main::{MessageToMain, MESSAGE_LENGTH}; 22 | use relay_stream::LogToFuture; 23 | use side_contract::SideContract; 24 | use signature::Signature; 25 | use tokio_timer::{Timeout, Timer}; 26 | use web3; 27 | use web3::api::Namespace; 28 | use web3::helpers::CallFuture; 29 | use web3::types::{Bytes, Log, H256, H520}; 30 | use web3::Transport; 31 | 32 | enum State { 33 | AwaitCheckAlreadySigned( 34 | AsyncCall, 35 | ), 36 | AwaitSignature(Timeout, error::Error>>), 37 | AwaitTransaction(AsyncTransaction), 38 | } 39 | 40 | /// `Future` that is responsible for calling `sideContract.submitSignature` 41 | /// for a single `sideContract.Withdraw` event. 42 | /// these get created by the `side_to_main_sign` `RelayStream` that's part 43 | /// of the `Bridge`. 44 | pub struct SideToMainSign { 45 | tx_hash: H256, 46 | side: SideContract, 47 | message: MessageToMain, 48 | state: State, 49 | } 50 | 51 | impl SideToMainSign { 52 | pub fn new(log: &Log, side: SideContract) -> Self { 53 | let tx_hash = log 54 | .transaction_hash 55 | .expect("`log` must be mined and contain `transaction_hash`. q.e.d."); 56 | 57 | let message = 58 | MessageToMain::from_log(log).expect("`log` must contain valid message. q.e.d."); 59 | let message_bytes = message.to_bytes(); 60 | 61 | assert_eq!( 62 | message_bytes.len(), 63 | MESSAGE_LENGTH, 64 | "SideBridge never accepts messages with len != {} bytes; qed", 65 | MESSAGE_LENGTH 66 | ); 67 | 68 | let future = side.is_side_to_main_signed_on_side(&message); 69 | let state = State::AwaitCheckAlreadySigned(future); 70 | info!("{:?} - step 1/3 - about to sign message", tx_hash); 71 | 72 | Self { 73 | side, 74 | tx_hash, 75 | message, 76 | state, 77 | } 78 | } 79 | } 80 | 81 | impl Future for SideToMainSign { 82 | /// transaction hash 83 | type Item = Option; 84 | type Error = error::Error; 85 | 86 | fn poll(&mut self) -> Poll { 87 | loop { 88 | let next_state = match self.state { 89 | State::AwaitCheckAlreadySigned(ref mut future) => { 90 | let is_already_signed = try_ready!(future 91 | .poll() 92 | .chain_err(|| "WithdrawConfirm: message signing failed")); 93 | if is_already_signed { 94 | return Ok(Async::Ready(None)); 95 | } 96 | 97 | let inner_future = web3::api::Eth::new(self.side.transport.clone()) 98 | .sign(self.side.authority_address, Bytes(self.message.to_bytes())) 99 | .from_err(); 100 | let timeout_future = 101 | Timer::default().timeout(inner_future, self.side.request_timeout); 102 | State::AwaitSignature(timeout_future) 103 | } 104 | State::AwaitSignature(ref mut future) => { 105 | let signature_bytes = try_ready!(future 106 | .poll() 107 | .chain_err(|| "WithdrawConfirm: message signing failed")); 108 | info!( 109 | "{:?} - step 2/3 - message signed. about to send transaction", 110 | self.tx_hash 111 | ); 112 | 113 | let signature = Signature::from_bytes(&signature_bytes.as_bytes())?; 114 | 115 | let future = self.side.submit_signed_message(&self.message, &signature); 116 | State::AwaitTransaction(future) 117 | } 118 | State::AwaitTransaction(ref mut future) => { 119 | let tx_hash = try_ready!(future 120 | .poll() 121 | .chain_err(|| "WithdrawConfirm: sending transaction failed")); 122 | info!( 123 | "{:?} - step 3/3 - DONE - transaction sent {:?}", 124 | self.tx_hash, tx_hash 125 | ); 126 | return Ok(Async::Ready(Some(tx_hash))); 127 | } 128 | }; 129 | self.state = next_state; 130 | } 131 | } 132 | } 133 | 134 | pub struct LogToSideToMainSign { 135 | pub side: SideContract, 136 | } 137 | 138 | /// from the options and a log a relay future can be made 139 | impl LogToFuture for LogToSideToMainSign { 140 | type Future = SideToMainSign; 141 | 142 | fn log_to_future(&self, log: &Log) -> Self::Future { 143 | SideToMainSign::new(log, self.side.clone()) 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use super::*; 150 | use contracts; 151 | use ethabi; 152 | use rustc_hex::FromHex; 153 | use rustc_hex::ToHex; 154 | use tokio_core::reactor::Core; 155 | use web3::types::{Address, Bytes, Log}; 156 | 157 | #[test] 158 | fn test_side_to_main_sign_relay_future_not_relayed() { 159 | let topic = contracts::side::events::relay_message::filter().topic0; 160 | 161 | let log = contracts::side::logs::RelayMessage { 162 | message_id: "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a94243ff" 163 | .parse() 164 | .unwrap(), 165 | sender: "aff3454fce5edbc8cca8697c15331677e6ebccff".parse().unwrap(), 166 | recipient: "aff3454fce5edbc8cca8697c15331677e6ebcccc".parse().unwrap(), 167 | }; 168 | 169 | // TODO [snd] would be nice if ethabi derived log structs implemented `encode` 170 | let log_data = ethabi::encode(&[ 171 | ethabi::Token::FixedBytes(log.message_id.as_bytes().to_vec()), 172 | ethabi::Token::Address(log.sender), 173 | ethabi::Token::Address(log.recipient), 174 | ]); 175 | 176 | let log_tx_hash = "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 177 | .parse() 178 | .unwrap(); 179 | 180 | let raw_log = Log { 181 | address: "0000000000000000000000000000000000000001".parse().unwrap(), 182 | topics: topic.into(), 183 | data: Bytes(log_data), 184 | transaction_hash: Some(log_tx_hash), 185 | block_hash: None, 186 | block_number: None, 187 | transaction_index: None, 188 | log_index: None, 189 | transaction_log_index: None, 190 | log_type: None, 191 | removed: None, 192 | }; 193 | 194 | let authority_address: Address = 195 | "0000000000000000000000000000000000000001".parse().unwrap(); 196 | 197 | let tx_hash = "1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b"; 198 | let side_contract_address = "0000000000000000000000000000000000000dd1".parse().unwrap(); 199 | 200 | let message = MessageToMain { 201 | side_tx_hash: log_tx_hash, 202 | message_id: log.message_id, 203 | recipient: log.recipient, 204 | sender: log.sender, 205 | }; 206 | 207 | let call_data = contracts::side::functions::has_authority_signed_message::encode_input( 208 | authority_address, 209 | message.to_bytes(), 210 | ); 211 | 212 | let signature = "8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc"; 213 | 214 | let tx_data = contracts::side::functions::submit_signed_message::encode_input( 215 | signature.from_hex::>().unwrap(), 216 | message.to_bytes(), 217 | ); 218 | 219 | let transport = mock_transport!( 220 | "eth_call" => 221 | req => json!([{ 222 | "data": format!("0x{}", call_data.to_hex::()), 223 | "to": format!("0x{:x}", side_contract_address), 224 | }, "latest"]), 225 | res => json!(format!("0x{}", ethabi::encode(&[ethabi::Token::Bool(false)]).to_hex::())); 226 | "eth_sign" => 227 | req => json!([ 228 | format!("0x{:x}", authority_address), 229 | format!("0x{}", message.to_bytes().to_hex::()) 230 | ]), 231 | res => json!(format!("0x{}", signature)); 232 | "eth_sendTransaction" => 233 | req => json!([{ 234 | "data": format!("0x{}", tx_data.to_hex::()), 235 | "from": format!("0x{:x}", authority_address), 236 | "gas": "0xfd", 237 | "gasPrice": "0xa0", 238 | "to": format!("0x{:x}", side_contract_address), 239 | }]), 240 | res => json!(format!("0x{}", tx_hash)); 241 | ); 242 | 243 | let side_contract = SideContract { 244 | transport: transport.clone(), 245 | contract_address: side_contract_address, 246 | authority_address, 247 | required_signatures: 1, 248 | request_timeout: ::std::time::Duration::from_millis(0), 249 | logs_poll_interval: ::std::time::Duration::from_millis(0), 250 | required_log_confirmations: 0, 251 | sign_main_to_side_gas: 0.into(), 252 | sign_main_to_side_gas_price: 0.into(), 253 | sign_side_to_main_gas: 0xfd.into(), 254 | sign_side_to_main_gas_price: 0xa0.into(), 255 | }; 256 | 257 | let future = SideToMainSign::new(&raw_log, side_contract); 258 | 259 | let mut event_loop = Core::new().unwrap(); 260 | let result = event_loop.run(future).unwrap(); 261 | assert_eq!(result, Some(tx_hash.parse().unwrap())); 262 | 263 | assert_eq!(transport.actual_requests(), transport.expected_requests()); 264 | } 265 | 266 | #[test] 267 | fn test_side_to_main_sign_relay_future_already_relayed() { 268 | let topic = contracts::side::events::relay_message::filter().topic0; 269 | 270 | let log = contracts::side::logs::RelayMessage { 271 | message_id: "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a94243ff" 272 | .parse() 273 | .unwrap(), 274 | sender: "aff3454fce5edbc8cca8697c15331677e6ebccff".parse().unwrap(), 275 | recipient: "aff3454fce5edbc8cca8697c15331677e6ebcccc".parse().unwrap(), 276 | }; 277 | 278 | // TODO [snd] would be nice if ethabi derived log structs implemented `encode` 279 | let log_data = ethabi::encode(&[ 280 | ethabi::Token::FixedBytes(log.message_id.as_bytes().to_vec()), 281 | ethabi::Token::Address(log.sender), 282 | ethabi::Token::Address(log.recipient), 283 | ]); 284 | 285 | let log_tx_hash = "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 286 | .parse() 287 | .unwrap(); 288 | 289 | let raw_log = Log { 290 | address: "0000000000000000000000000000000000000001".parse().unwrap(), 291 | topics: topic.into(), 292 | data: Bytes(log_data), 293 | transaction_hash: Some(log_tx_hash), 294 | block_hash: None, 295 | block_number: None, 296 | transaction_index: None, 297 | log_index: None, 298 | transaction_log_index: None, 299 | log_type: None, 300 | removed: None, 301 | }; 302 | 303 | let authority_address: Address = 304 | "0000000000000000000000000000000000000001".parse().unwrap(); 305 | 306 | let side_contract_address = "0000000000000000000000000000000000000dd1".parse().unwrap(); 307 | 308 | let message = MessageToMain { 309 | side_tx_hash: log_tx_hash, 310 | message_id: log.message_id, 311 | recipient: log.recipient, 312 | sender: log.sender, 313 | }; 314 | 315 | let call_data = contracts::side::functions::has_authority_signed_message::encode_input( 316 | authority_address, 317 | message.to_bytes(), 318 | ); 319 | 320 | let transport = mock_transport!( 321 | "eth_call" => 322 | req => json!([{ 323 | "data": format!("0x{}", call_data.to_hex::()), 324 | "to": side_contract_address, 325 | }, "latest"]), 326 | res => json!(format!("0x{}", ethabi::encode(&[ethabi::Token::Bool(true)]).to_hex::())); 327 | ); 328 | 329 | let side_contract = SideContract { 330 | transport: transport.clone(), 331 | contract_address: side_contract_address, 332 | authority_address, 333 | required_signatures: 1, 334 | request_timeout: ::std::time::Duration::from_millis(0), 335 | logs_poll_interval: ::std::time::Duration::from_millis(0), 336 | required_log_confirmations: 0, 337 | sign_main_to_side_gas: 0.into(), 338 | sign_main_to_side_gas_price: 0.into(), 339 | sign_side_to_main_gas: 0xfd.into(), 340 | sign_side_to_main_gas_price: 0xa0.into(), 341 | }; 342 | 343 | let future = SideToMainSign::new(&raw_log, side_contract); 344 | 345 | let mut event_loop = Core::new().unwrap(); 346 | let result = event_loop.run(future).unwrap(); 347 | assert_eq!(result, None); 348 | 349 | assert_eq!(transport.actual_requests(), transport.expected_requests()); 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /bridge/src/side_to_main_signatures.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | use contracts; 18 | use error::{self, ResultExt}; 19 | use futures::future::JoinAll; 20 | use futures::{Async, Future, Poll}; 21 | use helpers; 22 | use helpers::{AsyncCall, AsyncTransaction}; 23 | use main_contract::MainContract; 24 | use message_to_main::MessageToMain; 25 | use relay_stream::LogToFuture; 26 | use side_contract::SideContract; 27 | use signature::Signature; 28 | use web3::types::{Log, H256}; 29 | use web3::Transport; 30 | 31 | enum State { 32 | AwaitMessage(AsyncCall), 33 | AwaitIsRelayed { 34 | future: AsyncCall, 35 | message: MessageToMain, 36 | }, 37 | AwaitSignatures { 38 | future: JoinAll>>, 39 | message: MessageToMain, 40 | }, 41 | AwaitMessageData { 42 | future: AsyncCall, 43 | message: MessageToMain, 44 | signatures: Vec, 45 | }, 46 | AwaitTxSent(AsyncTransaction), 47 | } 48 | 49 | /// `Future` that completes a transfer from side to main by calling 50 | /// `mainContract.withdraw` for a single `sideContract.CollectedSignatures` 51 | /// these get created by the `side_to_main_signatures` `RelayStream` that's part 52 | /// of the `Bridge`. 53 | pub struct SideToMainSignatures { 54 | side_tx_hash: H256, 55 | main: MainContract, 56 | side: SideContract, 57 | state: State, 58 | } 59 | 60 | impl SideToMainSignatures { 61 | pub fn new(raw_log: &Log, main: MainContract, side: SideContract) -> Self { 62 | let side_tx_hash = raw_log 63 | .transaction_hash 64 | .expect("`log` must be mined and contain `transaction_hash`. q.e.d."); 65 | 66 | let log = helpers::parse_log(contracts::side::events::signed_message::parse_log, raw_log) 67 | .expect("`Log` must be a from a `CollectedSignatures` event. q.e.d."); 68 | 69 | // authority_responsible_for_relay is an indexed topic and it should be 70 | // always set up when creating the filter, so we receive only logs that 71 | // we should relay 72 | assert_eq!( 73 | log.authority_responsible_for_relay, main.authority_address, 74 | "incorrectly set up collected_signatures filter, we should only received logs where authority_responsible_for_relay == main.authority_address; qed" 75 | ); 76 | 77 | info!("{:?} - step 1/3 - about to fetch message", side_tx_hash,); 78 | let (payload, decoder) = contracts::side::functions::message::call(log.message_hash); 79 | let state = State::AwaitMessage(side.call(payload, decoder)); 80 | 81 | Self { 82 | side_tx_hash, 83 | main, 84 | side, 85 | state, 86 | } 87 | } 88 | } 89 | 90 | impl Future for SideToMainSignatures { 91 | type Item = Option; 92 | type Error = error::Error; 93 | 94 | fn poll(&mut self) -> Poll { 95 | loop { 96 | let next_state = match self.state { 97 | State::AwaitMessage(ref mut future) => { 98 | let message_bytes = try_ready!(future 99 | .poll() 100 | .chain_err(|| "SubmitSignature: fetching message failed")); 101 | let message = MessageToMain::from_bytes(&message_bytes)?; 102 | 103 | let (payload, decoder) = 104 | contracts::main::functions::accepted_messages::call(message.keccak256()); 105 | State::AwaitIsRelayed { 106 | future: self.main.call(payload, decoder), 107 | message, 108 | } 109 | } 110 | State::AwaitIsRelayed { 111 | ref mut future, 112 | ref message, 113 | } => { 114 | let is_relayed = try_ready!(future 115 | .poll() 116 | .chain_err(|| "SubmitSignature: fetching message failed")); 117 | 118 | if is_relayed { 119 | return Ok(Async::Ready(None)); 120 | } 121 | 122 | State::AwaitSignatures { 123 | future: self.side.get_signatures(message.keccak256()), 124 | message: message.clone(), 125 | } 126 | } 127 | State::AwaitSignatures { 128 | ref mut future, 129 | ref message, 130 | } => { 131 | let raw_signatures = try_ready!(future 132 | .poll() 133 | .chain_err(|| "WithdrawRelay: fetching message and signatures failed")); 134 | let signatures: Vec = raw_signatures 135 | .iter() 136 | .map(|x| Signature::from_bytes(x)) 137 | .collect::>()?; 138 | info!("{:?} - step 2/3 - message and {} signatures received. about to send transaction", self.side_tx_hash, signatures.len()); 139 | 140 | let (payload, decoder) = 141 | contracts::side::functions::relayed_messages::call(message.message_id); 142 | State::AwaitMessageData { 143 | future: self.side.call(payload, decoder), 144 | message: message.clone(), 145 | signatures, 146 | } 147 | } 148 | State::AwaitMessageData { 149 | ref mut future, 150 | ref message, 151 | ref signatures, 152 | } => { 153 | let message_data = try_ready!(future 154 | .poll() 155 | .chain_err(|| "SubmitSignature: fetching message failed")); 156 | 157 | State::AwaitTxSent(self.main.relay_side_to_main( 158 | &message, 159 | &signatures, 160 | message_data, 161 | )) 162 | } 163 | State::AwaitTxSent(ref mut future) => { 164 | let main_tx_hash = try_ready!(future 165 | .poll() 166 | .chain_err(|| "WithdrawRelay: sending transaction failed")); 167 | info!( 168 | "{:?} - step 3/3 - DONE - transaction sent {:?}", 169 | self.side_tx_hash, main_tx_hash 170 | ); 171 | return Ok(Async::Ready(Some(main_tx_hash))); 172 | } 173 | }; 174 | self.state = next_state; 175 | } 176 | } 177 | } 178 | 179 | /// options for relays from side to main 180 | pub struct LogToSideToMainSignatures { 181 | pub main: MainContract, 182 | pub side: SideContract, 183 | } 184 | 185 | /// from the options and a log a relay future can be made 186 | impl LogToFuture for LogToSideToMainSignatures { 187 | type Future = SideToMainSignatures; 188 | 189 | fn log_to_future(&self, log: &Log) -> Self::Future { 190 | SideToMainSignatures::new(log, self.main.clone(), self.side.clone()) 191 | } 192 | } 193 | 194 | #[cfg(test)] 195 | mod tests { 196 | use super::*; 197 | use contracts; 198 | use ethabi; 199 | use rustc_hex::FromHex; 200 | use rustc_hex::ToHex; 201 | use tokio_core::reactor::Core; 202 | use web3::types::{Address, Bytes, Log}; 203 | 204 | #[test] 205 | fn test_side_to_main_sign_relay_future_not_relayed_authority_responsible() { 206 | let authority_address: Address = 207 | "0000000000000000000000000000000000000001".parse().unwrap(); 208 | let authority_responsible_for_relay = authority_address; 209 | let topic = 210 | contracts::side::events::signed_message::filter(authority_responsible_for_relay); 211 | 212 | let message = MessageToMain { 213 | side_tx_hash: "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 214 | .parse() 215 | .unwrap(), 216 | message_id: "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a94243ff" 217 | .parse() 218 | .unwrap(), 219 | sender: "aff3454fce5edbc8cca8697c15331677e6ebccff".parse().unwrap(), 220 | recipient: "aff3454fce5edbc8cca8697c15331677e6ebcccc".parse().unwrap(), 221 | }; 222 | 223 | let log = contracts::side::logs::SignedMessage { 224 | authority_responsible_for_relay, 225 | message_hash: message.keccak256(), 226 | }; 227 | 228 | // TODO [snd] would be nice if ethabi derived log structs implemented `encode` 229 | let log_data = ethabi::encode(&[ethabi::Token::FixedBytes( 230 | log.message_hash.as_bytes().to_vec(), 231 | )]); 232 | 233 | let log_tx_hash: H256 = "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 234 | .parse() 235 | .unwrap(); 236 | 237 | let raw_log = Log { 238 | address: "0000000000000000000000000000000000000001".parse().unwrap(), 239 | topics: vec![topic.topic0[0], topic.topic1[0]], 240 | data: Bytes(log_data), 241 | transaction_hash: Some(log_tx_hash), 242 | block_hash: None, 243 | block_number: None, 244 | transaction_index: None, 245 | log_index: None, 246 | transaction_log_index: None, 247 | log_type: None, 248 | removed: None, 249 | }; 250 | 251 | let side_contract_address: Address = 252 | "0000000000000000000000000000000000000dd1".parse().unwrap(); 253 | let main_contract_address: Address = 254 | "0000000000000000000000000000000000000fff".parse().unwrap(); 255 | 256 | let sig: Vec = "8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc" 257 | .from_hex() 258 | .unwrap(); 259 | let signature = Signature::from_bytes(&*sig).unwrap(); 260 | 261 | let tx_hash = "1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b"; 262 | let data: Vec = vec![10, 0]; 263 | 264 | let main_transport = mock_transport!( 265 | "eth_call" => 266 | req => json!([{ 267 | "data": format!("0x{}", contracts::main::functions::accepted_messages::encode_input(log.message_hash).to_hex::()), 268 | "to": format!("0x{:x}", main_contract_address), 269 | }, "latest"]), 270 | res => json!(format!("0x{}", ethabi::encode(&[ethabi::Token::Bool(false)]).to_hex::())); 271 | "eth_sendTransaction" => 272 | req => json!([{ 273 | "data": format!( 274 | "0x{}", 275 | contracts::main::functions::accept_message::encode_input( 276 | vec![signature.v], 277 | vec![signature.r.clone()], 278 | vec![signature.s.clone()], 279 | message.side_tx_hash, 280 | data.clone(), 281 | message.sender, 282 | message.recipient, 283 | ).to_hex::() 284 | ), 285 | "from": format!("0x{:x}", authority_address), 286 | "gas": "0xfd", 287 | // TODO: fix gasPrice 288 | "gasPrice": format!("0x{:x}", 1000), 289 | "to": format!("0x{:x}", main_contract_address), 290 | }]), 291 | res => json!(format!("0x{:}", tx_hash)); 292 | ); 293 | 294 | let side_transport = mock_transport!( 295 | "eth_call" => 296 | req => json!([{ 297 | "data": format!("0x{}", 298 | contracts::side::functions::message::encode_input(log.message_hash).to_hex::()), 299 | "to": format!("0x{:x}", side_contract_address), 300 | }, "latest"]), 301 | res => json!(format!("0x{}", 302 | ethabi::encode(&[ethabi::Token::Bytes(message.to_bytes())]).to_hex::())); 303 | "eth_call" => 304 | req => json!([{ 305 | "data": format!("0x{}", contracts::side::functions::signature::encode_input(log.message_hash, 0).to_hex::()), 306 | "to": format!("0x{:x}", side_contract_address), 307 | }, "latest"]), 308 | res => json!(format!("0x{}", 309 | ethabi::encode(&[ethabi::Token::Bytes(signature.to_bytes())]).to_hex::())); 310 | "eth_call" => 311 | req => json!([{ 312 | "data": format!("0x{}", contracts::side::functions::relayed_messages::encode_input(message.message_id).to_hex::()), 313 | "to": format!("0x{:x}", side_contract_address), 314 | }, "latest"]), 315 | res => json!(format!("0x{}", ethabi::encode(&[ethabi::Token::Bytes(data)]).to_hex::())); 316 | ); 317 | 318 | let main_contract = MainContract { 319 | transport: main_transport.clone(), 320 | contract_address: main_contract_address, 321 | authority_address, 322 | request_timeout: ::std::time::Duration::from_millis(0), 323 | logs_poll_interval: ::std::time::Duration::from_millis(0), 324 | required_log_confirmations: 0, 325 | submit_collected_signatures_gas: 0xfd.into(), 326 | }; 327 | 328 | let side_contract = SideContract { 329 | transport: side_transport.clone(), 330 | contract_address: side_contract_address, 331 | authority_address, 332 | required_signatures: 1, 333 | request_timeout: ::std::time::Duration::from_millis(0), 334 | logs_poll_interval: ::std::time::Duration::from_millis(0), 335 | required_log_confirmations: 0, 336 | sign_main_to_side_gas: 0.into(), 337 | sign_main_to_side_gas_price: 0.into(), 338 | sign_side_to_main_gas: 0xfd.into(), 339 | sign_side_to_main_gas_price: 0xa0.into(), 340 | }; 341 | 342 | let future = SideToMainSignatures::new(&raw_log, main_contract, side_contract); 343 | 344 | let mut event_loop = Core::new().unwrap(); 345 | let result = event_loop.run(future).unwrap(); 346 | assert_eq!(result, Some(tx_hash.parse().unwrap())); 347 | 348 | assert_eq!( 349 | main_transport.actual_requests(), 350 | main_transport.expected_requests() 351 | ); 352 | assert_eq!( 353 | side_transport.actual_requests(), 354 | side_transport.expected_requests() 355 | ); 356 | } 357 | 358 | #[test] 359 | fn test_side_to_main_sign_relay_future_already_relayed() { 360 | let authority_address: Address = 361 | "0000000000000000000000000000000000000001".parse().unwrap(); 362 | let authority_responsible_for_relay = authority_address; 363 | let topic = 364 | contracts::side::events::signed_message::filter(authority_responsible_for_relay); 365 | 366 | let message = MessageToMain { 367 | side_tx_hash: "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 368 | .parse() 369 | .unwrap(), 370 | message_id: "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a94243ff" 371 | .parse() 372 | .unwrap(), 373 | sender: "aff3454fce5edbc8cca8697c15331677e6ebccff".parse().unwrap(), 374 | recipient: "aff3454fce5edbc8cca8697c15331677e6ebcccc".parse().unwrap(), 375 | }; 376 | 377 | let log = contracts::side::logs::SignedMessage { 378 | authority_responsible_for_relay, 379 | message_hash: message.keccak256(), 380 | }; 381 | 382 | // TODO [snd] would be nice if ethabi derived log structs implemented `encode` 383 | let log_data = ethabi::encode(&[ethabi::Token::FixedBytes( 384 | log.message_hash.as_bytes().to_vec(), 385 | )]); 386 | 387 | let log_tx_hash: H256 = "884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364" 388 | .parse() 389 | .unwrap(); 390 | 391 | let raw_log = Log { 392 | address: "0000000000000000000000000000000000000001".parse().unwrap(), 393 | topics: vec![topic.topic0[0], topic.topic1[0]], 394 | data: Bytes(log_data), 395 | transaction_hash: Some(log_tx_hash), 396 | block_hash: None, 397 | block_number: None, 398 | transaction_index: None, 399 | log_index: None, 400 | transaction_log_index: None, 401 | log_type: None, 402 | removed: None, 403 | }; 404 | 405 | let side_contract_address: Address = 406 | "0000000000000000000000000000000000000dd1".parse().unwrap(); 407 | let main_contract_address: Address = 408 | "0000000000000000000000000000000000000fff".parse().unwrap(); 409 | 410 | let main_transport = mock_transport!( 411 | "eth_call" => 412 | req => json!([{ 413 | "data": format!("0x{}", contracts::main::functions::accepted_messages::encode_input(log.message_hash).to_hex::()), 414 | "to": main_contract_address, 415 | }, "latest"]), 416 | res => json!(format!("0x{}", ethabi::encode(&[ethabi::Token::Bool(true)]).to_hex::())); 417 | ); 418 | 419 | let side_transport = mock_transport!( 420 | "eth_call" => 421 | req => json!([{ 422 | "data": format!("0x{}", 423 | contracts::side::functions::message::encode_input(log.message_hash).to_hex::()), 424 | "to": side_contract_address, 425 | }, "latest"]), 426 | res => json!(format!("0x{}", 427 | ethabi::encode(&[ethabi::Token::Bytes(message.to_bytes())]).to_hex::())); 428 | ); 429 | 430 | let main_contract = MainContract { 431 | transport: main_transport.clone(), 432 | contract_address: main_contract_address, 433 | authority_address, 434 | request_timeout: ::std::time::Duration::from_millis(0), 435 | logs_poll_interval: ::std::time::Duration::from_millis(0), 436 | required_log_confirmations: 0, 437 | submit_collected_signatures_gas: 0xfd.into(), 438 | }; 439 | 440 | let side_contract = SideContract { 441 | transport: side_transport.clone(), 442 | contract_address: side_contract_address, 443 | authority_address, 444 | required_signatures: 1, 445 | request_timeout: ::std::time::Duration::from_millis(0), 446 | logs_poll_interval: ::std::time::Duration::from_millis(0), 447 | required_log_confirmations: 0, 448 | sign_main_to_side_gas: 0.into(), 449 | sign_main_to_side_gas_price: 0.into(), 450 | sign_side_to_main_gas: 0xfd.into(), 451 | sign_side_to_main_gas_price: 0xa0.into(), 452 | }; 453 | 454 | let future = SideToMainSignatures::new(&raw_log, main_contract, side_contract); 455 | 456 | let mut event_loop = Core::new().unwrap(); 457 | let result = event_loop.run(future).unwrap(); 458 | assert_eq!(result, None); 459 | 460 | assert_eq!( 461 | main_transport.actual_requests(), 462 | main_transport.expected_requests() 463 | ); 464 | assert_eq!( 465 | side_transport.actual_requests(), 466 | side_transport.expected_requests() 467 | ); 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /bridge/src/signature.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use ethabi; 17 | /// ECDSA signatures: 18 | /// conversion from/to byte vectors. 19 | /// from/to v, r, s components. 20 | use ethereum_types::H256; 21 | 22 | use error::Error; 23 | 24 | pub const SIGNATURE_LENGTH: usize = 65; 25 | 26 | /// an ECDSA signature consisting of `v`, `r` and `s` 27 | #[derive(PartialEq, Debug)] 28 | pub struct Signature { 29 | pub v: u8, 30 | pub r: H256, 31 | pub s: H256, 32 | } 33 | 34 | impl Signature { 35 | pub fn from_bytes(bytes: &[u8]) -> Result { 36 | if bytes.len() != SIGNATURE_LENGTH { 37 | bail!("`bytes`.len() must be {}", SIGNATURE_LENGTH); 38 | } 39 | 40 | Ok(Self { 41 | v: bytes[64], 42 | r: H256::from_slice(&bytes[0..32]), 43 | s: H256::from_slice(&bytes[32..64]), 44 | }) 45 | } 46 | 47 | pub fn to_bytes(&self) -> Vec { 48 | let mut result = vec![0u8; SIGNATURE_LENGTH]; 49 | result[0..32].copy_from_slice(&self.r.0[..]); 50 | result[32..64].copy_from_slice(&self.s.0[..]); 51 | result[64] = self.v; 52 | return result; 53 | } 54 | 55 | pub fn to_payload(&self) -> Vec { 56 | ethabi::encode(&[ethabi::Token::Bytes(self.to_bytes())]) 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod test { 62 | use super::*; 63 | use quickcheck::TestResult; 64 | 65 | quickcheck! { 66 | fn quickcheck_signature_roundtrips(v: u8, r_raw: Vec, s_raw: Vec) -> TestResult { 67 | if r_raw.len() != 32 || s_raw.len() != 32 { 68 | return TestResult::discard(); 69 | } 70 | 71 | let r = H256::from_slice(r_raw.as_slice()); 72 | let s = H256::from_slice(s_raw.as_slice()); 73 | let signature = Signature { v, r, s }; 74 | assert_eq!(v, signature.v); 75 | assert_eq!(r, signature.r); 76 | assert_eq!(s, signature.s); 77 | 78 | let bytes = signature.to_bytes(); 79 | 80 | assert_eq!(signature, Signature::from_bytes(bytes.as_slice()).unwrap()); 81 | 82 | let payload = signature.to_payload(); 83 | let mut tokens = ethabi::decode(&[ethabi::ParamType::Bytes], payload.as_slice()) 84 | .unwrap(); 85 | let decoded = tokens.pop().unwrap().to_bytes().unwrap(); 86 | assert_eq!(signature, Signature::from_bytes(decoded.as_slice()).unwrap()); 87 | 88 | TestResult::passed() 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /bridge/src/test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use futures; 17 | /// helpers for testing: 18 | /// 19 | /// - mocking transports 20 | use jsonrpc_core; 21 | use serde_json; 22 | use std::cell::RefCell; 23 | use std::rc::Rc; 24 | use web3; 25 | use web3::Transport; 26 | 27 | #[derive(Debug, Clone, PartialEq)] 28 | pub struct RequestData { 29 | pub method: String, 30 | pub params: Vec, 31 | } 32 | 33 | impl From<(&'static str, serde_json::Value)> for RequestData { 34 | fn from(a: (&'static str, serde_json::Value)) -> Self { 35 | Self { 36 | method: a.0.to_owned(), 37 | params: a.1.as_array().unwrap().clone(), 38 | } 39 | } 40 | } 41 | 42 | /// a `Transport` that and will return the specified responses 43 | /// `clone`d versions have the same storage 44 | #[derive(Debug, Clone)] 45 | pub struct MockTransport { 46 | pub expected_requests: Vec, 47 | pub actual_requests: Rc>>, 48 | pub mock_responses: Vec, 49 | } 50 | 51 | impl MockTransport { 52 | pub fn expected_requests(&self) -> Vec { 53 | self.expected_requests.clone() 54 | } 55 | pub fn actual_requests(&self) -> Vec { 56 | self.actual_requests.as_ref().borrow().clone() 57 | } 58 | } 59 | 60 | impl Transport for MockTransport { 61 | type Out = web3::Result; 62 | 63 | fn prepare( 64 | &self, 65 | method: &str, 66 | params: Vec, 67 | ) -> (usize, jsonrpc_core::Call) { 68 | let current_request_index = { self.actual_requests.as_ref().borrow().len() }; 69 | assert!( 70 | current_request_index < self.expected_requests.len(), 71 | "{} requests expected but at least one more request is being executed", 72 | self.expected_requests.len() 73 | ); 74 | 75 | assert_eq!( 76 | self.expected_requests[current_request_index] 77 | .method 78 | .as_str(), 79 | method, 80 | "invalid method called" 81 | ); 82 | assert_eq!( 83 | self.expected_requests[current_request_index].params, params, 84 | "invalid method params at request #{}", 85 | current_request_index 86 | ); 87 | self.actual_requests 88 | .as_ref() 89 | .borrow_mut() 90 | .push(RequestData { 91 | method: method.to_string(), 92 | params: params.clone(), 93 | }); 94 | 95 | let request = web3::helpers::build_request(1, method, params); 96 | (current_request_index + 1, request) 97 | } 98 | 99 | fn send(&self, _id: usize, _request: jsonrpc_core::Call) -> web3::Result { 100 | let current_request_index = { self.actual_requests.as_ref().borrow().len() }; 101 | let response = self 102 | .mock_responses 103 | .iter() 104 | .nth(current_request_index - 1) 105 | .expect("missing response"); 106 | let f = futures::finished(response.clone()); 107 | Box::new(f) 108 | } 109 | } 110 | 111 | #[macro_export] 112 | macro_rules! mock_transport { 113 | ( 114 | $($method: expr => req => $req: expr, res => $res: expr ;)* 115 | ) => { 116 | $crate::MockTransport { 117 | actual_requests: Default::default(), 118 | expected_requests: vec![$($method),*] 119 | .into_iter() 120 | .zip(vec![$($req),*] 121 | .into_iter()) 122 | .map(Into::into) 123 | .collect(), 124 | mock_responses: vec![$($res),*], 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parity-bridge" 3 | version = "0.5.0" 4 | authors = ["debris ", "snd "] 5 | 6 | [[bin]] 7 | name = "parity-bridge" 8 | path = "src/main.rs" 9 | 10 | [dependencies] 11 | bridge = { path = "../bridge" } 12 | serde = "1.0" 13 | serde_derive = "1.0" 14 | tokio-core = "0.1.8" 15 | docopt = "1.0" 16 | log = "0.4" 17 | env_logger = "0.7" 18 | futures = "0.1.14" 19 | web3 = { git = "https://github.com/tomusdrw/rust-web3" } 20 | -------------------------------------------------------------------------------- /cli/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use std::process::Command; 17 | 18 | fn main() { 19 | // make last git commit hash (`git rev-parse HEAD`) 20 | // available via `env!("GIT_HASH")` in sources 21 | let output = Command::new("git") 22 | .args(&["rev-parse", "HEAD"]) 23 | .output() 24 | .expect("`git rev-parse HEAD` failed to run. run it yourself to verify. file an issue if this persists"); 25 | let git_hash = String::from_utf8(output.stdout).unwrap(); 26 | println!("cargo:rustc-env=GIT_HASH={}", git_hash); 27 | } 28 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | extern crate bridge; 17 | extern crate docopt; 18 | extern crate env_logger; 19 | extern crate futures; 20 | #[macro_use] 21 | extern crate log; 22 | extern crate serde; 23 | #[macro_use] 24 | extern crate serde_derive; 25 | extern crate tokio_core; 26 | extern crate web3; 27 | 28 | use docopt::Docopt; 29 | use futures::Stream; 30 | use std::env; 31 | use std::path::PathBuf; 32 | use tokio_core::reactor::Core; 33 | use web3::transports::http::Http; 34 | 35 | use bridge::config::Config; 36 | use bridge::database::{Database, TomlFileDatabase}; 37 | use bridge::error::{self, ResultExt}; 38 | use bridge::helpers::StreamExt; 39 | 40 | const MAX_PARALLEL_REQUESTS: usize = 10; 41 | 42 | #[derive(Debug, Deserialize)] 43 | pub struct Args { 44 | arg_config: PathBuf, 45 | arg_database: PathBuf, 46 | } 47 | 48 | fn main() { 49 | let _ = env_logger::init(); 50 | let result = execute(env::args()); 51 | 52 | match result { 53 | Ok(s) => println!("{}", s), 54 | Err(err) => print_err(err), 55 | } 56 | } 57 | 58 | fn print_err(err: error::Error) { 59 | let message = err 60 | .iter() 61 | .map(|e| e.to_string()) 62 | .collect::>() 63 | .join("\n\nCaused by:\n "); 64 | println!("{}", message); 65 | } 66 | 67 | fn execute(command: I) -> Result 68 | where 69 | I: IntoIterator, 70 | S: AsRef, 71 | { 72 | let usage = format!( 73 | r#" 74 | Parity-bridge 75 | Copyright 2017 Parity Technologies (UK) Limited 76 | Version: {} 77 | Commit: {} 78 | 79 | Usage: 80 | parity-bridge --config --database 81 | parity-bridge -h | --help 82 | 83 | Options: 84 | -h, --help Display help message and exit. 85 | "#, 86 | env!("CARGO_PKG_VERSION"), 87 | env!("GIT_HASH") 88 | ); 89 | 90 | info!("Parsing cli arguments"); 91 | let args: Args = Docopt::new(usage) 92 | .and_then(|d| d.argv(command).deserialize()) 93 | .map_err(|e| e.to_string())?; 94 | 95 | info!("Loading config from {:?}", args.arg_config); 96 | let config = Config::load(&args.arg_config)?; 97 | 98 | info!("Starting event loop"); 99 | let mut event_loop = Core::new().unwrap(); 100 | 101 | info!( 102 | "Establishing HTTP connection to parity node connected to main chain at {:?}", 103 | config.main.http 104 | ); 105 | let main_transport = Http::with_event_loop( 106 | &config.main.http, 107 | &event_loop.handle(), 108 | MAX_PARALLEL_REQUESTS, 109 | ) 110 | .chain_err(|| { 111 | format!( 112 | "Cannot connect to parity node connected to main chain at {}", 113 | config.main.http 114 | ) 115 | })?; 116 | 117 | info!( 118 | "Establishing HTTP connection to parity node connected to side chain at {:?}", 119 | config.side.http 120 | ); 121 | let side_transport = Http::with_event_loop( 122 | &config.side.http, 123 | &event_loop.handle(), 124 | MAX_PARALLEL_REQUESTS, 125 | ) 126 | .chain_err(|| { 127 | format!( 128 | "Cannot connect to parity node connected to side chain at {}", 129 | config.side.http 130 | ) 131 | })?; 132 | 133 | info!("Loading database from {:?}", args.arg_database); 134 | let mut database = TomlFileDatabase::from_path(&args.arg_database)?; 135 | 136 | info!("Reading initial state from database"); 137 | let initial_state = database.read(); 138 | 139 | let main_contract = bridge::MainContract::new(main_transport.clone(), &config, &initial_state); 140 | event_loop 141 | .run(main_contract.is_main_contract()) 142 | .chain_err(|| { 143 | format!( 144 | "call to main contract `is_main_bridge_contract` failed. this is likely due to field `main_contract_address = {}` in database file {:?} not pointing to a bridge main contract. please verify!", 145 | initial_state.main_contract_address, 146 | args.arg_database 147 | ) 148 | })?; 149 | 150 | let side_contract = bridge::SideContract::new(side_transport.clone(), &config, &initial_state); 151 | event_loop 152 | .run(side_contract.is_side_contract()) 153 | .chain_err(|| { 154 | format!( 155 | "call to side contract `is_side_bridge_contract` failed. this is likely due to field `side_contract_address = {}` in database file {:?} not pointing to a bridge side contract. please verify!", 156 | initial_state.side_contract_address, 157 | args.arg_database 158 | ) 159 | })?; 160 | 161 | let bridge_stream = bridge::Bridge::new(initial_state, main_contract, side_contract); 162 | info!("Started polling logs"); 163 | let persisted_bridge_stream = bridge_stream.and_then(|state| { 164 | database.write(&state)?; 165 | // info!("state change: {}", state); 166 | Ok(()) 167 | }); 168 | 169 | event_loop.run(persisted_bridge_stream.last())?; 170 | 171 | Ok("Done".into()) 172 | } 173 | -------------------------------------------------------------------------------- /contracts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bridge-contracts" 3 | version = "0.5.0" 4 | authors = ["debris ", "snd "] 5 | 6 | [dependencies] 7 | ethabi = "9.0" 8 | ethabi-derive = "9.0" 9 | ethabi-contract = "9.0" 10 | 11 | [build-dependencies] 12 | solc = { git = "https://github.com/paritytech/rust_solc" } 13 | 14 | [features] 15 | integration-tests = [] 16 | -------------------------------------------------------------------------------- /contracts/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | extern crate solc; 17 | 18 | use std::process::Command; 19 | 20 | fn main() { 21 | // rerun build script if bridge contract has changed. 22 | // without this cargo doesn't since the bridge contract 23 | // is outside the crate directories 24 | println!("cargo:rerun-if-changed=../arbitrary/contracts/bridge.sol"); 25 | 26 | // make last git commit hash (`git rev-parse HEAD`) 27 | // available via `env!("GIT_HASH")` in sources 28 | let output = Command::new("git") 29 | .args(&["rev-parse", "HEAD"]) 30 | .output() 31 | .expect("`git rev-parse HEAD` failed to run. run it yourself to verify. file an issue if this persists"); 32 | let git_hash = String::from_utf8(output.stdout).unwrap(); 33 | println!("cargo:rustc-env=GIT_HASH={}", git_hash); 34 | 35 | // make solc version used to compile contracts (`solc --version`) 36 | // available via `env!("SOLC_VERSION")` in sources 37 | let output = Command::new("solc").args(&["--version"]).output().expect( 38 | "`solc --version` failed to run. run it yourself to verify. file an issue if this persists", 39 | ); 40 | let output_string = String::from_utf8(output.stdout).unwrap(); 41 | let solc_version = output_string.lines().last().unwrap(); 42 | println!("cargo:rustc-env=SOLC_VERSION={}", solc_version); 43 | 44 | // compile contracts for inclusion with ethabis `use_contract!` 45 | solc::solc_compile("../arbitrary/contracts/bridge.sol", "../compiled_contracts").unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /contracts/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | extern crate ethabi; 18 | extern crate ethabi_derive; 19 | #[macro_use] 20 | extern crate ethabi_contract; 21 | 22 | use_contract!(main, "../compiled_contracts/Main.abi"); 23 | use_contract!(side, "../compiled_contracts/Side.abi"); 24 | #[cfg(feature = "integration-tests")] 25 | use_contract!(test, "../compiled_contracts/RecipientTest.abi"); 26 | -------------------------------------------------------------------------------- /deploy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parity-bridge-deploy" 3 | version = "0.5.0" 4 | authors = ["debris "] 5 | 6 | [[bin]] 7 | name = "parity-bridge-deploy" 8 | path = "src/main.rs" 9 | 10 | [dependencies] 11 | bridge = { path = "../bridge" } 12 | serde = "1.0" 13 | serde_derive = "1.0" 14 | tokio-core = "0.1.8" 15 | docopt = "1.0" 16 | log = "0.4" 17 | env_logger = "0.7" 18 | futures = "0.1.14" 19 | web3 = { git = "https://github.com/tomusdrw/rust-web3" } 20 | -------------------------------------------------------------------------------- /deploy/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | use std::process::Command; 17 | 18 | fn main() { 19 | // make last git commit hash (`git rev-parse HEAD`) 20 | // available via `env!("GIT_HASH")` in sources 21 | let output = Command::new("git") 22 | .args(&["rev-parse", "HEAD"]) 23 | .output() 24 | .unwrap(); 25 | let git_hash = String::from_utf8(output.stdout).unwrap(); 26 | println!("cargo:rustc-env=GIT_HASH={}", git_hash); 27 | } 28 | -------------------------------------------------------------------------------- /deploy/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | extern crate bridge; 17 | extern crate docopt; 18 | extern crate env_logger; 19 | extern crate futures; 20 | #[macro_use] 21 | extern crate log; 22 | extern crate serde; 23 | #[macro_use] 24 | extern crate serde_derive; 25 | extern crate tokio_core; 26 | extern crate web3; 27 | 28 | use docopt::Docopt; 29 | use std::path::PathBuf; 30 | use std::{env, fs}; 31 | use tokio_core::reactor::Core; 32 | use web3::transports::http::Http; 33 | 34 | use bridge::config::Config; 35 | use bridge::database::State; 36 | use bridge::deploy::{DeployMain, DeploySide}; 37 | use bridge::error::{self, ResultExt}; 38 | 39 | const MAX_PARALLEL_REQUESTS: usize = 10; 40 | 41 | #[derive(Debug, Deserialize)] 42 | pub struct Args { 43 | arg_config: PathBuf, 44 | arg_database: PathBuf, 45 | } 46 | 47 | fn main() { 48 | let _ = env_logger::init(); 49 | let result = execute(env::args()); 50 | 51 | match result { 52 | Ok(s) => println!("{}", s), 53 | Err(err) => print_err(err), 54 | } 55 | } 56 | 57 | fn print_err(err: error::Error) { 58 | let message = err 59 | .iter() 60 | .map(|e| e.to_string()) 61 | .collect::>() 62 | .join("\n\nCaused by:\n "); 63 | println!("{}", message); 64 | } 65 | 66 | fn execute(command: I) -> Result 67 | where 68 | I: IntoIterator, 69 | S: AsRef, 70 | { 71 | let usage = format!( 72 | r#" 73 | Parity-bridge 74 | Copyright 2017 Parity Technologies (UK) Limited 75 | Version: {} 76 | Commit: {} 77 | 78 | Usage: 79 | parity-bridge-deploy --config --database 80 | parity-bridge-deploy -h | --help 81 | 82 | Options: 83 | -h, --help Display help message and exit. 84 | "#, 85 | env!("CARGO_PKG_VERSION"), 86 | env!("GIT_HASH") 87 | ); 88 | 89 | info!(target: "parity-bridge-deploy", "Parsing cli arguments"); 90 | let args: Args = Docopt::new(usage) 91 | .and_then(|d| d.argv(command).deserialize()) 92 | .map_err(|e| e.to_string())?; 93 | 94 | info!(target: "parity-bridge-deploy", "Loading config"); 95 | let config = Config::load(args.arg_config)?; 96 | 97 | info!(target: "parity-bridge-deploy", "Starting event loop"); 98 | let mut event_loop = Core::new().unwrap(); 99 | 100 | info!( 101 | "Establishing HTTP connection to main {:?}", 102 | config.main.http 103 | ); 104 | let main_transport = Http::with_event_loop( 105 | &config.main.http, 106 | &event_loop.handle(), 107 | MAX_PARALLEL_REQUESTS, 108 | ) 109 | .chain_err(|| format!("Cannot connect to main at {}", config.main.http))?; 110 | 111 | info!( 112 | "Establishing HTTP connection to side {:?}", 113 | config.side.http 114 | ); 115 | let side_transport = Http::with_event_loop( 116 | &config.side.http, 117 | &event_loop.handle(), 118 | MAX_PARALLEL_REQUESTS, 119 | ) 120 | .chain_err(|| format!("Cannot connect to side at {}", config.side.http))?; 121 | 122 | info!(target: "parity-bridge-deploy", "Deploying MainBridge contract"); 123 | let main_deployed = event_loop.run(DeployMain::new(config.clone(), main_transport))?; 124 | info!(target: "parity-bridge-deploy", "Successfully deployed MainBridge contract"); 125 | 126 | main_deployed.dump_info(format!( 127 | "deployment-main-{}", 128 | main_deployed.contract_address 129 | ))?; 130 | 131 | info!(target: "parity-bridge-deploy", "Deploying SideBridge contract"); 132 | let side_deployed = event_loop.run(DeploySide::new(config.clone(), side_transport))?; 133 | info!(target: "parity-bridge-deploy", "Successfully deployed SideBridge contract"); 134 | 135 | side_deployed.dump_info(format!( 136 | "deployment-side-{}", 137 | side_deployed.contract_address 138 | ))?; 139 | 140 | let state = State::from_transaction_receipts(&main_deployed.receipt, &side_deployed.receipt); 141 | info!(target: "parity-bridge-deploy", "\n\n{}\n", state); 142 | state.write(fs::File::create(args.arg_database)?)?; 143 | 144 | Ok("Done".into()) 145 | } 146 | -------------------------------------------------------------------------------- /deployment_guide.md: -------------------------------------------------------------------------------- 1 | # deployment and run guide 2 | 3 | [paritytech/parity-bridge](https://github.com/paritytech/parity-bridge) 4 | 5 | this guide assumes that you are one of the authorities of 6 | a PoA chain `side` and want to use the bridge to connect 7 | `side` to another chain `main`. 8 | 9 | since all bridge authorities use the same contracts on `side` and `main` 10 | one authority has to go ahead and deploy them. 11 | 12 | let's call this the **deploying authority**. 13 | 14 | if the process is done correctly the other non-deploying authorities don't have to trust 15 | the deploying authority. 16 | 17 | upfront you must know the addresses of all authorities (`authorities`) 18 | as well as the number of `required_signatures` 19 | 20 | ## initial deployment steps for any authority (deploying and non-deploying) 21 | 22 | assuming you are authority with `authority_address`. 23 | 24 | [build and install the bridge](https://github.com/paritytech/parity-bridge/#build) 25 | 26 | install parity. 27 | we tested it with [parity 2.0.4](https://github.com/paritytech/parity/releases/tag/v2.0.4) with Byzantium fork 28 | enabled, though it should work with the latest stable release. 29 | 30 | install solidity compiler 31 | we tested it with [solc 0.5.2](https://github.com/ethereum/solidity/releases/tag/v0.5.2) 32 | 33 | start a parity node that connects to `main` chain, has `authority_address` unlocked 34 | and http enabled at `main.http`. TODO add instructions. please refer to 35 | the parity documentation for now. 36 | 37 | start a parity node that connects to `side` chain, has `authority_address` unlocked 38 | and http enabled at `side.http`. TODO add instructions. please refer to 39 | the parity documentation for now. 40 | 41 | ### configure the bridge 42 | 43 | copy [integration-tests/bridge_config.toml](https://github.com/paritytech/parity-bridge/blob/master/integration-tests/bridge_config.toml) 44 | to a local `bridge_config.toml`. 45 | 46 | within `bridge_config.toml` resolve/fill-in all the `ACTION REQUIRED`s. 47 | 48 | for help refer to the comments, [the config option documentation](https://github.com/paritytech/parity-bridge/#configuration), 49 | or [![Join the chat at https://gitter.im/paritytech/parity-bridge](https://badges.gitter.im/paritytech/parity-bridge.svg)](https://gitter.im/paritytech/parity-bridge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 50 | 51 | [if you're the deploying authority continue here](#further-deployment-steps-for-deploying-authority) 52 | 53 | [if you're a non-deploying authority continue here](#further-run-steps) 54 | 55 | ## further deployment steps for deploying authority 56 | 57 | start the bridge-deploy by executing: 58 | 59 | ``` 60 | env RUST_LOG=info parity-bridge-deploy --config bridge_config.toml --database bridge.db 61 | ``` 62 | 63 | it should eventually print something like this: 64 | 65 | ``` 66 | INFO:bridge: Deployed new bridge contracts 67 | INFO:bridge: 68 | main_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f" 69 | side_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f" 70 | main_deployed_at_block = 1 71 | side_deployed_at_block = 1 72 | last_main_to_side_sign_at_block = 1 73 | last_side_to_main_signatures_at_block = 1 74 | last_side_to_main_sign_at_block = 1 75 | ``` 76 | 77 | **congratulations! the bridge has successfully deployed its contracts on both chains** 78 | 79 | `bridge.db` should now look similar to this: 80 | 81 | ``` 82 | main_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f" 83 | side_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f" 84 | main_deployed_at_block = 1 85 | side_deployed_at_block = 1 86 | last_main_to_side_sign_at_block = 1 87 | last_side_to_main_signatures_at_block = 1 88 | last_side_to_main_sign_at_block = 1 89 | ``` 90 | 91 | (verify the contracts deployed to `main_contract_address` and 92 | `side_contract_address` using 93 | [https://etherscan.io/verifyContract](https://etherscan.io/verifyContract) so the other authorities 94 | can verify that you did an honest deploy without having to trust you.) 95 | 96 | give the `bridge.db` file to the other authorities. 97 | for example by posting it as a gist. 98 | the database file doesn't contain any sensitive information. 99 | 100 | ask the other authorities to follow **this guide you're reading**. 101 | 102 | proceed to the next step to run the bridge. 103 | 104 | ## further run steps 105 | 106 | you MUST receive a `bridge.db` from the deploying authority. 107 | 108 | it should look similar to this: 109 | 110 | ``` 111 | main_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f" 112 | side_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f" 113 | main_deployed_at_block = 1 114 | side_deployed_at_block = 1 115 | last_main_to_side_sign_at_block = 3 116 | last_side_to_main_signatures_at_block = 4 117 | last_side_to_main_sign_at_block = 4 118 | ``` 119 | 120 | (check that the contracts deployed to 121 | `main_contract_address` and `side_contract_address` are 122 | verified on [https://etherscan.io](https://etherscan.io) and that the source code matches 123 | the code in the repo.) 124 | 125 | start the bridge by executing: 126 | 127 | ``` 128 | env RUST_LOG=info bridge --config bridge_config.toml --database bridge.db 129 | ``` 130 | 131 | it should eventually print this line: 132 | 133 | ``` 134 | INFO XXXX-XX-XXTXX:XX:XXZ: parity_bridge: Started polling logs 135 | ``` 136 | 137 | **congratulations! the bridge has successfully started and joined the other authorities** 138 | 139 | ensure the process keeps running. else the bridge won't function. 140 | (outside the scope of this guide, your devops team knows what to do). 141 | -------------------------------------------------------------------------------- /file_header.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | -------------------------------------------------------------------------------- /integration-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration-tests" 3 | version = "0.1.0" 4 | authors = ["debris ", "snd "] 5 | 6 | [dependencies] 7 | bridge = { path = "../bridge" } 8 | bridge-contracts = { path = "../contracts", features = ["integration-tests"] } 9 | ethabi = "9.0" 10 | ethereum-types = "0.8" 11 | futures = "0.1" 12 | jsonrpc-core = "14.0" 13 | pretty_assertions = "0.6.1" 14 | rustc-hex = "2.0" 15 | serde_json = "1.0" 16 | tempfile = "3" 17 | tokio-core = "0.1.8" 18 | web3 = { git = "https://github.com/tomusdrw/rust-web3" } 19 | -------------------------------------------------------------------------------- /integration-tests/bridge_config.toml: -------------------------------------------------------------------------------- 1 | # READ THE CONFIG DOCUMENTATION AT: 2 | # https://github.com/paritytech/parity-bridge/#configuration 3 | 4 | # ACTION REQUIRED: set to your authority address 5 | address = "0x00bd138abd70e2f00903268f3db08f2d25677c9e" 6 | 7 | estimated_gas_cost_of_withdraw = "200000" 8 | 9 | # limits total balance on `main` and therefore total ether that could get lost 10 | # if the bridge is faulty or compromised in any way! 11 | # set to `"0"` to disable limit (not recommended at this point) 12 | # currently set to 10 ether. 13 | max_total_main_contract_balance = "10000000000000000000" 14 | 15 | # limit `msg.value` for a single deposit into the main contract. 16 | # set to `"0"` to disable limit (not recommended at this point) 17 | # currently set to 1 ether. 18 | max_single_deposit_value = "1000000000000000000" 19 | 20 | [main] 21 | # ACTION REQUIRED: set the url of the parity node that has `main.account` unlocked 22 | http = "http://localhost:8550" 23 | # ACTION REQUIRED: for test deployment set this to 12 24 | required_confirmations = 0 25 | 26 | [main.contract] 27 | # READ THE CONFIG DOCUMENTATION AT: 28 | # https://github.com/paritytech/parity-bridge/#configuration 29 | bin = "../compiled_contracts/Main.bin" 30 | 31 | [side] 32 | # ACTION REQUIRED: set the the url of the parity node that has `side.account` unlocked 33 | http = "http://localhost:8551" 34 | # ACTION REQUIRED: for test deployment set this to 12 35 | required_confirmations = 0 36 | 37 | [side.contract] 38 | # READ THE CONFIG DOCUMENTATION AT: 39 | # https://github.com/paritytech/parity-bridge/#configuration 40 | bin = "../compiled_contracts/Side.bin" 41 | 42 | [authorities] 43 | # ACTION REQUIRED: set this to the addresses of the authority list 44 | accounts = [ 45 | "0x00bd138abd70e2f00903268f3db08f2d25677c9e", 46 | ] 47 | # ACTION REQUIRED: set this to a (super-)majority of `authorities.accounts` 48 | # example: set to 3 for 5 authorities. set to 7 for 10 authorities 49 | required_signatures = 1 50 | 51 | [transactions] 52 | # `gas` below should be good defaults for test deployment. 53 | # ACTION REQUIRED: you have to set `gas_price` for each transaction 54 | # if your authority can't do free transactions on the chain. 55 | # `gas_price` might need adjustment once in a while. 56 | 57 | # these happen on `main`: 58 | main_deploy = { gas = "1500000" , gas_price = "0" } 59 | withdraw_relay = { gas = "200000" , gas_price = "0" } 60 | 61 | # these happen on `side`: 62 | side_deploy = { gas = "3000000" , gas_price = "0" } 63 | deposit_relay = { gas = "150000" , gas_price = "0" } 64 | 65 | withdraw_confirm = { gas = "300000" , gas_price = "0" } 66 | -------------------------------------------------------------------------------- /integration-tests/password.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /integration-tests/tests/basic_deposit_then_withdraw.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Parity Technologies (UK) Ltd. 2 | // This file is part of Parity-Bridge. 3 | 4 | // Parity-Bridge is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Parity-Bridge is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Parity-Bridge. If not, see . 16 | 17 | //! spins up two parity nodes with the dev chain. 18 | //! starts one bridge authority that connects the two. 19 | //! does a deposit by sending ether to the MainBridge. 20 | //! asserts that the deposit got relayed to side chain. 21 | //! does a withdraw by executing SideBridge.transferToMainViaRelay. 22 | //! asserts that the withdraw got relayed to main chain. 23 | extern crate bridge; 24 | extern crate bridge_contracts; 25 | extern crate ethabi; 26 | extern crate ethereum_types; 27 | extern crate rustc_hex; 28 | extern crate tempfile; 29 | extern crate tokio_core; 30 | extern crate web3; 31 | 32 | use std::path::Path; 33 | use std::process::Command; 34 | use std::thread; 35 | use std::time::Duration; 36 | 37 | use tokio_core::reactor::Core; 38 | 39 | use bridge::helpers::AsyncCall; 40 | use rustc_hex::FromHex; 41 | use web3::transports::http::Http; 42 | use web3::types::Address; 43 | 44 | const TMP_PATH: &str = "tmp"; 45 | const MAX_PARALLEL_REQUESTS: usize = 10; 46 | const TIMEOUT: Duration = Duration::from_secs(1); 47 | 48 | fn parity_main_command() -> Command { 49 | let mut command = Command::new("parity"); 50 | command 51 | .arg("--base-path") 52 | .arg(format!("{}/main", TMP_PATH)) 53 | .arg("--chain") 54 | .arg("./spec.json") 55 | .arg("--no-ipc") 56 | .arg("--logging") 57 | .arg("rpc=trace,miner=trace,executive=trace") 58 | .arg("--jsonrpc-port") 59 | .arg("8550") 60 | .arg("--jsonrpc-apis") 61 | .arg("all") 62 | .arg("--port") 63 | .arg("30310") 64 | .arg("--min-gas-price") 65 | .arg("0") 66 | .arg("--reseal-min-period") 67 | .arg("0") 68 | .arg("--no-ws") 69 | .arg("--no-warp"); 70 | command 71 | } 72 | 73 | fn parity_side_command() -> Command { 74 | let mut command = Command::new("parity"); 75 | command 76 | .arg("--base-path") 77 | .arg(format!("{}/side", TMP_PATH)) 78 | .arg("--chain") 79 | .arg("./spec.json") 80 | .arg("--no-ipc") 81 | .arg("--logging") 82 | .arg("rpc=trace,miner=trace,executive=trace") 83 | .arg("--jsonrpc-port") 84 | .arg("8551") 85 | .arg("--jsonrpc-apis") 86 | .arg("all") 87 | .arg("--port") 88 | .arg("30311") 89 | .arg("--min-gas-price") 90 | .arg("0") 91 | .arg("--reseal-min-period") 92 | .arg("0") 93 | .arg("--no-ws") 94 | .arg("--no-warp"); 95 | command 96 | } 97 | 98 | #[test] 99 | #[ignore] 100 | fn test_basic_deposit_then_withdraw() { 101 | if Path::new(TMP_PATH).exists() { 102 | std::fs::remove_dir_all(TMP_PATH).expect("failed to remove tmp dir"); 103 | } 104 | let _ = std::fs::create_dir_all(TMP_PATH).expect("failed to create tmp dir"); 105 | let _tmp_dir = tempfile::TempDir::new_in(TMP_PATH).expect("failed to create tmp dir"); 106 | 107 | println!("\nbuild the deploy executable so we can run it later\n"); 108 | assert!(Command::new("cargo") 109 | .env("RUST_BACKTRACE", "1") 110 | .current_dir("../deploy") 111 | .arg("build") 112 | .status() 113 | .expect("failed to build parity-bridge-deploy executable") 114 | .success()); 115 | 116 | println!("\nbuild the parity-bridge executable so we can run it later\n"); 117 | assert!(Command::new("cargo") 118 | .env("RUST_BACKTRACE", "1") 119 | .current_dir("../cli") 120 | .arg("build") 121 | .status() 122 | .expect("failed to build parity-bridge executable") 123 | .success()); 124 | 125 | // start a parity node that represents the main chain 126 | let mut parity_main = parity_main_command() 127 | .spawn() 128 | .expect("failed to spawn parity main node"); 129 | 130 | // start a parity node that represents the side chain 131 | let mut parity_side = parity_side_command() 132 | .spawn() 133 | .expect("failed to spawn parity side node"); 134 | 135 | // give the clients time to start up 136 | thread::sleep(Duration::from_millis(3000)); 137 | 138 | let user_address = "004ec07d2329997267ec62b4166639513386f32e"; 139 | let authority_address = "00bd138abd70e2f00903268f3db08f2d25677c9e"; 140 | 141 | let main_contract_address = "ebd3944af37ccc6b67ff61239ac4fef229c8f69f"; 142 | let side_contract_address = "ebd3944af37ccc6b67ff61239ac4fef229c8f69f"; 143 | // Note this has to be the first contract created by `user_address` 144 | let main_recipient_address = "5f3dba5e45909d1bf126aa0af0601b1a369dbfd7"; 145 | let side_recipient_address = "5f3dba5e45909d1bf126aa0af0601b1a369dbfd7"; 146 | 147 | let data_to_relay_to_side = vec![0u8, 1, 5]; 148 | let data_to_relay_to_main = vec![0u8, 1, 5, 7]; 149 | 150 | fn new_account(phrase: &str, port: u16) { 151 | // this is currently not supported in web3 crate so we have to use curl 152 | let exit_status = Command::new("curl") 153 | .arg("--data") 154 | .arg(format!( 155 | "{}{}{}", 156 | r#"{"jsonrpc":"2.0","method":"parity_newAccountFromPhrase","params":[""#, 157 | phrase, 158 | r#"", ""],"id":0}"#, 159 | )) 160 | .arg("-H") 161 | .arg("Content-Type: application/json") 162 | .arg("-X") 163 | .arg("POST") 164 | .arg(format!("localhost:{}", port)) 165 | .status() 166 | .expect("failed to create authority account on main"); 167 | assert!(exit_status.success()); 168 | } 169 | 170 | // create authority account on main 171 | new_account("node0", 8550); 172 | new_account("user", 8550); 173 | // TODO [snd] assert that created address matches authority_address 174 | 175 | // TODO don't shell out to curl 176 | // create authority account on side 177 | new_account("node0", 8551); 178 | new_account("user", 8551); 179 | // TODO [snd] assert that created address matches authority_address 180 | 181 | // give the operations time to complete 182 | thread::sleep(Duration::from_millis(5000)); 183 | 184 | // kill the clients so we can restart them with the accounts unlocked 185 | parity_main.kill().unwrap(); 186 | parity_side.kill().unwrap(); 187 | 188 | // wait for clients to shut down 189 | thread::sleep(Duration::from_millis(5000)); 190 | 191 | // start a parity node that represents the main chain with accounts unlocked 192 | let mut parity_main = parity_main_command() 193 | .arg("--unlock") 194 | .arg(format!("0x{},0x{}", user_address, authority_address)) 195 | .arg("--password") 196 | .arg("password.txt") 197 | .spawn() 198 | .expect("failed to spawn parity main node"); 199 | 200 | // start a parity node that represents the side chain with accounts unlocked 201 | let mut parity_side = parity_side_command() 202 | .arg("--unlock") 203 | .arg(format!("0x{},0x{}", user_address, authority_address)) 204 | .arg("--password") 205 | .arg("password.txt") 206 | .spawn() 207 | .expect("failed to spawn parity side node"); 208 | 209 | // give nodes time to start up 210 | thread::sleep(Duration::from_millis(10000)); 211 | 212 | // deploy bridge contracts 213 | 214 | println!("\ndeploying contracts\n"); 215 | assert!(Command::new("env") 216 | .arg("RUST_BACKTRACE=1") 217 | .arg("../target/debug/parity-bridge-deploy") 218 | .env("RUST_LOG", "info") 219 | .arg("--config") 220 | .arg("bridge_config.toml") 221 | .arg("--database") 222 | .arg("tmp/bridge1_db.txt") 223 | .status() 224 | .expect("failed spawn parity-bridge-deploy") 225 | .success()); 226 | 227 | // start bridge authority 1 228 | let mut bridge1 = Command::new("env") 229 | .arg("RUST_BACKTRACE=1") 230 | .arg("../target/debug/parity-bridge") 231 | .env("RUST_LOG", "info") 232 | .arg("--config") 233 | .arg("bridge_config.toml") 234 | .arg("--database") 235 | .arg("tmp/bridge1_db.txt") 236 | .spawn() 237 | .expect("failed to spawn bridge process"); 238 | 239 | let mut event_loop = Core::new().unwrap(); 240 | 241 | // connect to main 242 | let main_transport = Http::with_event_loop( 243 | "http://localhost:8550", 244 | &event_loop.handle(), 245 | MAX_PARALLEL_REQUESTS, 246 | ) 247 | .expect("failed to connect to main at http://localhost:8550"); 248 | 249 | // connect to side 250 | let side_transport = Http::with_event_loop( 251 | "http://localhost:8551", 252 | &event_loop.handle(), 253 | MAX_PARALLEL_REQUESTS, 254 | ) 255 | .expect("failed to connect to side at http://localhost:8551"); 256 | 257 | println!("\ngive authority some funds to do relay later\n"); 258 | 259 | event_loop 260 | .run(web3::confirm::send_transaction_with_confirmation( 261 | &main_transport, 262 | web3::types::TransactionRequest { 263 | from: user_address.parse().unwrap(), 264 | to: Some(authority_address.parse().unwrap()), 265 | gas: None, 266 | gas_price: None, 267 | value: Some(1000000000.into()), 268 | data: None, 269 | condition: None, 270 | nonce: None, 271 | }, 272 | Duration::from_secs(1), 273 | 0, 274 | )) 275 | .unwrap(); 276 | 277 | event_loop 278 | .run(web3::confirm::send_transaction_with_confirmation( 279 | &side_transport, 280 | web3::types::TransactionRequest { 281 | from: user_address.parse().unwrap(), 282 | to: Some(authority_address.parse().unwrap()), 283 | gas: None, 284 | gas_price: None, 285 | value: Some(1000000000.into()), 286 | data: None, 287 | condition: None, 288 | nonce: None, 289 | }, 290 | Duration::from_secs(1), 291 | 0, 292 | )) 293 | .unwrap(); 294 | 295 | println!("\ndeploy BridgeRecipient contracts\n"); 296 | 297 | event_loop 298 | .run(web3::confirm::send_transaction_with_confirmation( 299 | &main_transport, 300 | web3::types::TransactionRequest { 301 | from: user_address.parse().unwrap(), 302 | to: None, 303 | gas: None, 304 | gas_price: None, 305 | value: None, 306 | data: Some( 307 | include_str!("../../compiled_contracts/RecipientTest.bin") 308 | .from_hex::>() 309 | .unwrap() 310 | .into(), 311 | ), 312 | condition: None, 313 | nonce: None, 314 | }, 315 | Duration::from_secs(1), 316 | 0, 317 | )) 318 | .unwrap(); 319 | 320 | event_loop 321 | .run(web3::confirm::send_transaction_with_confirmation( 322 | &side_transport, 323 | web3::types::TransactionRequest { 324 | from: user_address.parse().unwrap(), 325 | to: None, 326 | gas: None, 327 | gas_price: None, 328 | value: None, 329 | data: Some( 330 | include_str!("../../compiled_contracts/RecipientTest.bin") 331 | .from_hex::>() 332 | .unwrap() 333 | .into(), 334 | ), 335 | condition: None, 336 | nonce: None, 337 | }, 338 | Duration::from_secs(1), 339 | 0, 340 | )) 341 | .unwrap(); 342 | 343 | println!("\nSend the message to main chain and wait for the relay to side\n"); 344 | 345 | let (payload, _) = bridge_contracts::main::functions::relay_message::call( 346 | data_to_relay_to_side.clone(), 347 | main_recipient_address.parse::
().unwrap(), 348 | ); 349 | 350 | event_loop 351 | .run(web3::confirm::send_transaction_with_confirmation( 352 | &main_transport, 353 | web3::types::TransactionRequest { 354 | from: user_address.parse().unwrap(), 355 | to: Some(main_contract_address.parse().unwrap()), 356 | gas: None, 357 | gas_price: None, 358 | value: None, 359 | data: Some(payload.into()), 360 | condition: None, 361 | nonce: None, 362 | }, 363 | Duration::from_secs(1), 364 | 0, 365 | )) 366 | .unwrap(); 367 | 368 | println!( 369 | "\nSending message to main complete. Give it plenty of time to get mined and relayed\n" 370 | ); 371 | thread::sleep(Duration::from_millis(10000)); 372 | 373 | let (payload, decoder) = bridge_contracts::test::functions::last_data::call(); 374 | 375 | let response = event_loop 376 | .run(AsyncCall::new( 377 | &side_transport, 378 | side_recipient_address.parse().unwrap(), 379 | TIMEOUT, 380 | payload, 381 | decoder, 382 | )) 383 | .unwrap(); 384 | 385 | assert_eq!( 386 | response, data_to_relay_to_side, 387 | "data was not relayed properly to the side chain" 388 | ); 389 | 390 | println!("\nSend the message to side chain and wait for the relay to main\n"); 391 | 392 | let (payload, _) = bridge_contracts::side::functions::relay_message::call( 393 | data_to_relay_to_main.clone(), 394 | main_recipient_address.parse::
().unwrap(), 395 | ); 396 | 397 | event_loop 398 | .run(web3::confirm::send_transaction_with_confirmation( 399 | &side_transport, 400 | web3::types::TransactionRequest { 401 | from: user_address.parse().unwrap(), 402 | to: Some(side_contract_address.parse().unwrap()), 403 | gas: None, 404 | gas_price: None, 405 | value: None, 406 | data: Some(payload.into()), 407 | condition: None, 408 | nonce: None, 409 | }, 410 | Duration::from_secs(1), 411 | 0, 412 | )) 413 | .unwrap(); 414 | 415 | println!( 416 | "\nSending message to side complete. Give it plenty of time to get mined and relayed\n" 417 | ); 418 | thread::sleep(Duration::from_millis(15000)); 419 | 420 | //dwd 421 | 422 | //let main_web3 = web3::Web3::new(&main_transport); 423 | //let code_future = main_web3.eth().code(main_recipient_address.into(), None); 424 | //let code = event_loop.run(code_future).unwrap(); 425 | //println!("code: {:?}", code); 426 | 427 | //// TODO: remove 428 | //bridge1.kill().unwrap(); 429 | 430 | //// wait for bridge to shut down 431 | //thread::sleep(Duration::from_millis(1000)); 432 | //parity_main.kill().unwrap(); 433 | //parity_side.kill().unwrap(); 434 | 435 | //assert!(false); 436 | 437 | let (payload, decoder) = bridge_contracts::test::functions::last_data::call(); 438 | 439 | let response = event_loop 440 | .run(AsyncCall::new( 441 | &main_transport, 442 | main_recipient_address.parse().unwrap(), 443 | TIMEOUT, 444 | payload, 445 | decoder, 446 | )) 447 | .unwrap(); 448 | 449 | assert_eq!( 450 | response, data_to_relay_to_main, 451 | "data was not relayed properly to the main chain" 452 | ); 453 | 454 | bridge1.kill().unwrap(); 455 | 456 | // wait for bridge to shut down 457 | thread::sleep(Duration::from_millis(1000)); 458 | 459 | parity_main.kill().unwrap(); 460 | parity_side.kill().unwrap(); 461 | } 462 | -------------------------------------------------------------------------------- /parity-ethereum-grandpa-builtin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parity-ethereum-grandpa-builtin" 3 | description = "A Parity Ethereum client builtin contract able to verify Grandpa Finality Proofs." 4 | version = "0.1.0" 5 | authors = ["Parity Technologies "] 6 | edition = "2018" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | -------------------------------------------------------------------------------- /parity-ethereum-grandpa-builtin/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /res/deposit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/parity-bridge/9f42917999d05858e9180a311b8326c777c051b3/res/deposit.png -------------------------------------------------------------------------------- /res/withdraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/parity-bridge/9f42917999d05858e9180a311b8326c777c051b3/res/withdraw.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces=4 2 | hard_tabs=true -------------------------------------------------------------------------------- /srml-bridge-poa/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "srml-bridge-poa" 3 | description = "A Substrate Runtime module that is able to verify PoA headers and their finality." 4 | version = "0.1.0" 5 | authors = ["Parity Technologies "] 6 | edition = "2018" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | -------------------------------------------------------------------------------- /srml-bridge-poa/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | --------------------------------------------------------------------------------