Skip to content

Commit

Permalink
Problem: poa-bridge is not compatible with v2 bridge-contracts
Browse files Browse the repository at this point in the history
Solution: update deposit-relay, withdraw-relay, and
message-to-mainnet to be compatible with v2 contracts, remove
withdraw-confirm, create deposit-confirm.

Closes #5
  • Loading branch information
DrPeterVanNostrand committed Jun 11, 2018
1 parent 2a69de9 commit 8fd93a6
Show file tree
Hide file tree
Showing 12 changed files with 824 additions and 654 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ Bridge forces TLS for RPC connections by default. However, in some limited scena
this might be undesirable. In this case, you can use `--allow-insecure-rpc-endpoints` option to allow non-TLS
endpoints to be used. Ensure, however, that this option is not going to be used in production.


#### Exit Status Codes

| Code | Meaning |
Expand Down Expand Up @@ -102,9 +101,9 @@ password = "foreign_password.txt"
required_signatures = 2

[transactions]
deposit_confirm = { gas = 3000000 }
deposit_relay = { gas = 3000000 }
withdraw_relay = { gas = 3000000 }
withdraw_confirm = { gas = 3000000 }
```

#### Options
Expand Down Expand Up @@ -133,24 +132,24 @@ withdraw_confirm = { gas = 3000000 }

#### transaction options

- `transaction.deposit_confirm.gas` - specify how much gas should be consumed for each call to the Home contract's `submitSignature()` function
- `transaction.deposit_relay.gas` - specify how much gas should be consumed by deposit relay
- `transaction.withdraw_confirm.gas` - specify how much gas should be consumed by withdraw confirm
- `transaction.withdraw_relay.gas` - specify how much gas should be consumed by withdraw relay

### Database file format

```toml
home_contract_address = "0x49edf201c1e139282643d5e7c6fb0c7219ad1db7"
foreign_contract_address = "0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"
checked_deposit_confirm = 120
checked_deposit_relay = 120
checked_withdraw_relay = 121
checked_withdraw_confirm = 121
```

**all fields are required**

- `home_contract_address` - address of the bridge contract on home chain
- `foreign_contract_address` - address of the bridge contract on foreign chain
- `checked_deposit_confirm` - number of the last block for which an authority has confirmed deposit
- `checked_deposit_relay` - number of the last block for which an authority has relayed deposits to the foreign
- `checked_withdraw_relay` - number of the last block for which an authority has relayed withdraws to the home
- `checked_withdraw_confirm` - number of the last block for which an authority has confirmed withdraw
2 changes: 1 addition & 1 deletion bridge/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub struct Connections<T> where T: Transport {
impl Connections<Http> {
pub fn new_http(handle: &Handle, home: &str, home_concurrent_connections: usize, foreign: &str, foreign_concurrent_connections: usize) -> Result<Self, Error> {

let home = Http::with_event_loop(home, handle,home_concurrent_connections)
let home = Http::with_event_loop(home, handle,home_concurrent_connections)
.map_err(ErrorKind::Web3)
.map_err(Error::from)
.chain_err(||"Cannot connect to home node rpc")?;
Expand Down
3 changes: 2 additions & 1 deletion bridge/src/bridge/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ impl<T: Transport + Clone> Future for Deploy<T> {
home_deploy: Some(main_receipt.block_number.low_u64()),
foreign_deploy: Some(test_receipt.block_number.low_u64()),
checked_deposit_relay: main_receipt.block_number.low_u64(),
checked_withdraw_relay: test_receipt.block_number.low_u64(),
checked_deposit_confirm: main_receipt.block_number.low_u64(),
checked_withdraw_relay: test_receipt.block_number.low_u64(),
checked_withdraw_confirm: test_receipt.block_number.low_u64(),
};
return Ok(Deployed::New(database).into())
Expand Down
228 changes: 228 additions & 0 deletions bridge/src/bridge/deposit_confirm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use std::ops::RangeFull;
use std::sync::{Arc, RwLock};

use ethcore_transaction::{Action, Transaction};
use futures::{Async, Future, Poll, Stream};
use futures::stream::{Collect, FuturesUnordered, futures_unordered};
use itertools::Itertools;
use web3::Transport;
use web3::types::{Address, Bytes, FilterBuilder, H520, U256};

use api::{eth_data_hash, LogStream, LogStreamInit, LogStreamItem, log_stream};
use app::App;
use contracts::home::HomeBridge;
use database::Database;
use error::{Error, ErrorKind};
use message_to_mainnet::MessageToMainnet;
use super::BridgeChecked;
use super::nonce::{NonceCheck, SendRawTransaction, send_transaction_with_nonce};
use util::web3_filter;

// A future representing all currently open calls to the Home
// contract's `submitSignature()` function.
type SubmitSignaturesFuture<T: Transport> =
Collect<FuturesUnordered<NonceCheck<T, SendRawTransaction<T>>>>;

fn create_deposit_filter(contract: &HomeBridge, contract_address: Address) -> FilterBuilder {
let filter = contract.events().deposit().create_filter();
web3_filter(filter, contract_address)
}

fn create_submit_signature_payload(
home_contract: &HomeBridge,
deposit_message: Vec<u8>,
signature: H520
) -> Bytes
{
home_contract.functions().submit_signature()
.input(signature.0.to_vec(), deposit_message)
.into()
}

// Represents each possible state for the `DepositConfirm`.
enum State<T: Transport> {
// Monitoring the Foreign chain for new `Deposit` events.
Initial,
// Waiting for all calls to the Home contract's `submitSignature()`
// function to finish.
WaitingOnSubmitSignatures {
future: SubmitSignaturesFuture<T>,
last_block_checked: u64,
},
// All calls to the Home Contract's `submitSignature()` function
// have finished. Yields the block number for the last block
// checked for `Deposit` events on the Foreign chain.
Yield(Option<u64>),
}

pub struct DepositConfirm<T: Transport> {
app: Arc<App<T>>,
logs: LogStream<T>,
state: State<T>,
home_contract_address: Address,
home_balance: Arc<RwLock<Option<U256>>>,
home_chain_id: u64,
home_gas_price: Arc<RwLock<u64>>,
}

pub fn create_deposit_confirm<T: Transport + Clone>(
app: Arc<App<T>>,
init: &Database,
home_balance: Arc<RwLock<Option<U256>>>,
home_chain_id: u64,
home_gas_price: Arc<RwLock<u64>>
) -> DepositConfirm<T>
{
let home_config = &app.config.home;

let deposit_event_filter = create_deposit_filter(
&app.home_bridge,
init.home_contract_address
);

let logs_init = LogStreamInit {
after: init.checked_deposit_confirm,
request_timeout: home_config.request_timeout,
poll_interval: home_config.poll_interval,
confirmations: home_config.required_confirmations,
filter: deposit_event_filter,
};

let deposit_log_stream = log_stream(
app.connections.home.clone(),
app.timer.clone(),
logs_init
);

DepositConfirm {
logs: deposit_log_stream,
home_contract_address: init.home_contract_address,
state: State::Initial,
app,
home_balance,
home_chain_id,
home_gas_price,
}
}

impl<T: Transport> Stream for DepositConfirm<T> {
type Item = BridgeChecked;
type Error = Error;

fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let app = &self.app;
let home_config = app.config.home.clone();
let home_conn = app.connections.home;
let home_contract = &app.home_bridge;
let home_contract_address = self.home_contract_address;
let home_chain_id = self.home_chain_id;
let my_home_address = app.config.home.account;
let gas = app.config.txs.deposit_confirm.gas.into();
let gas_price = U256::from(*self.home_gas_price.read().unwrap());

loop {
let next_state = match self.state {
State::Initial => {
let home_balance = self.home_balance.read().unwrap();
if home_balance.is_none() {
warn!("home contract balance is unknown");
return Ok(Async::NotReady);
}

let LogStreamItem { to: last_block_checked, logs, .. } =
try_stream!(
self.logs.poll().map_err(|e| {
let context = "polling Home contract for Depoist event logs";
ErrorKind::ContextualizedError(Box::new(e), context)
})
);

let n_new_deposits = logs.len();
info!("got {} new deposits to sign", n_new_deposits);

let mut messages: Vec<Vec<u8>> = logs.into_iter()
.map(|log| {
info!(
"deposit is ready for signature submission. tx hash {}",
log.transaction_hash.unwrap()
);

MessageToMainnet::from_home_deposit_log(log)
.map(|msg| msg.to_bytes())
})
.collect()?;

let signatures = messages.iter()
.map(|message| {
let signed_message = eth_data_hash(message.clone());
app.keystore.sign(my_home_address, None, signed_message)
})
.map_results(|sig| H520::from(sig.into_electrum()))
.fold_results(vec![], |mut acc, sig| {
acc.push(sig);
acc
})
.map_err(ErrorKind::SignError)?;

let balance_required = gas * gas_price * U256::from(signatures.len());
if balance_required > *home_balance.as_ref().unwrap() {
return Err(ErrorKind::InsufficientFunds.into());
}

let submit_signature_calls = messages.drain(RangeFull)
.zip(signatures.into_iter())
.map(|(message, signature)| create_submit_signature_payload(
home_contract,
message,
signature
))
.map(|payload| {
let tx = Transaction {
gas,
gas_price,
value: U256::zero(),
data: payload.0,
nonce: U256::zero(),
action: Action::Call(home_contract_address),
};

send_transaction_with_nonce(
home_conn.clone(),
app.clone(),
home_config,
tx,
home_chain_id,
SendRawTransaction(home_conn.clone()),
)
})
.collect_vec();

State::WaitingOnSubmitSignatures {
future: futures_unordered(submit_signature_calls).collect(),
last_block_checked,
}
},
State::WaitingOnSubmitSignatures { ref mut future, last_block_checked } => {
let _ = try_ready!(
future.poll().map_err(|e| {
let context = "sending signature submissions to home";
ErrorKind::ContextualizedError(Box::new(e), context)
})
);
info!("submitting signatures to home complete");
State::Yield(Some(last_block_checked))
},
State::Yield(ref mut block) => match block.take() {
Some(block) => {
let checked = BridgeChecked::DepositConfirm(block);
return Ok(Async::Ready(Some(checked)));
},
None => State::Initial,
},
};

self.state = next_state;
}
}
}

Loading

0 comments on commit 8fd93a6

Please sign in to comment.