218 lines
6.5 KiB
Rust
218 lines
6.5 KiB
Rust
extern crate serde;
|
|
#[macro_use]
|
|
extern crate serde_derive;
|
|
extern crate docopt;
|
|
extern crate futures;
|
|
extern crate tokio_core;
|
|
#[macro_use]
|
|
extern crate log;
|
|
extern crate env_logger;
|
|
extern crate bridge;
|
|
extern crate ctrlc;
|
|
extern crate jsonrpc_core as rpc;
|
|
|
|
use std::{env, fs, io};
|
|
use std::sync::Arc;
|
|
use std::path::PathBuf;
|
|
use docopt::Docopt;
|
|
use futures::{Stream, future};
|
|
use tokio_core::reactor::Core;
|
|
|
|
use bridge::app::App;
|
|
use bridge::bridge::{create_bridge, create_deploy, create_chain_id_retrieval, Deployed};
|
|
use bridge::config::Config;
|
|
use bridge::error::{Error, ErrorKind};
|
|
use bridge::web3;
|
|
|
|
const ERR_UNKNOWN: i32 = 1;
|
|
const ERR_IO_ERROR: i32 = 2;
|
|
const ERR_SHUTDOWN_REQUESTED: i32 = 3;
|
|
const ERR_INSUFFICIENT_FUNDS: i32 = 4;
|
|
const ERR_RPC_ERROR: i32 = 5;
|
|
const ERR_CANNOT_CONNECT: i32 = 10;
|
|
const ERR_CONNECTION_LOST: i32 = 11;
|
|
const ERR_BRIDGE_CRASH: i32 = 11;
|
|
|
|
pub struct UserFacingError(i32, Error);
|
|
|
|
impl From<Error> for UserFacingError {
|
|
fn from(err: Error) -> Self {
|
|
UserFacingError(ERR_UNKNOWN, err)
|
|
}
|
|
}
|
|
|
|
impl From<String> for UserFacingError {
|
|
fn from(err: String) -> Self {
|
|
UserFacingError(ERR_UNKNOWN, err.into())
|
|
}
|
|
}
|
|
|
|
|
|
impl From<io::Error> for UserFacingError {
|
|
fn from(err: io::Error) -> Self {
|
|
UserFacingError(ERR_IO_ERROR, err.into())
|
|
}
|
|
}
|
|
|
|
|
|
impl From<(i32, Error)> for UserFacingError {
|
|
fn from((code, err): (i32, Error)) -> Self {
|
|
UserFacingError(code, err)
|
|
}
|
|
}
|
|
|
|
|
|
const USAGE: &'static str = r#"
|
|
POA-Ethereum bridge.
|
|
Copyright 2017 Parity Technologies (UK) Limited
|
|
Copyright 2018 POA Networks Ltd.
|
|
|
|
Usage:
|
|
bridge --config <config> --database <database>
|
|
bridge -h | --help
|
|
|
|
Options:
|
|
-h, --help Display help message and exit.
|
|
"#;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct Args {
|
|
arg_config: PathBuf,
|
|
arg_database: PathBuf,
|
|
}
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
fn main() {
|
|
let _ = env_logger::init();
|
|
|
|
let running = Arc::new(AtomicBool::new(true));
|
|
|
|
let r = running.clone();
|
|
ctrlc::set_handler(move || {
|
|
r.store(false, Ordering::SeqCst);
|
|
}).expect("Error setting Ctrl-C handler");
|
|
|
|
let result = execute(env::args(), running);
|
|
|
|
match result {
|
|
Ok(s) => println!("{}", s),
|
|
Err(UserFacingError(code, err)) => {
|
|
print_err(err);
|
|
::std::process::exit(code);
|
|
},
|
|
}
|
|
}
|
|
|
|
|
|
fn print_err(err: Error) {
|
|
let message = err.iter().map(|e| e.to_string()).collect::<Vec<_>>().join("\n\nCaused by:\n ");
|
|
println!("{}", message);
|
|
}
|
|
|
|
fn execute<S, I>(command: I, running: Arc<AtomicBool>) -> Result<String, UserFacingError> where I: IntoIterator<Item=S>, S: AsRef<str> {
|
|
info!(target: "bridge", "Parsing cli arguments");
|
|
let args: Args = Docopt::new(USAGE)
|
|
.and_then(|d| d.argv(command).deserialize()).map_err(|e| e.to_string())?;
|
|
|
|
info!(target: "bridge", "Loading config");
|
|
let config = Config::load(args.arg_config)?;
|
|
|
|
info!(target: "bridge", "Starting event loop");
|
|
let mut event_loop = Core::new().unwrap();
|
|
|
|
info!(target: "bridge", "Home rpc host {}", config.clone().home.rpc_host);
|
|
|
|
info!(target: "bridge", "Establishing connection:");
|
|
|
|
info!(target:"bridge", " using RPC connection");
|
|
let app = match App::new_http(config.clone(), &args.arg_database, &event_loop.handle(), running.clone()) {
|
|
Ok(app) => app,
|
|
Err(e) => {
|
|
warn!("Can't establish an RPC connection: {:?}", e);
|
|
return Err((ERR_CANNOT_CONNECT, e).into());
|
|
},
|
|
};
|
|
|
|
let app = Arc::new(app);
|
|
|
|
info!(target: "bridge", "Acquiring home & foreign chain ids");
|
|
let home_chain_id = event_loop.run(create_chain_id_retrieval(app.connections.home.clone(), app.config.home.clone())).expect("can't retrieve home chain_id");
|
|
let foreign_chain_id = event_loop.run(create_chain_id_retrieval(app.connections.foreign.clone(), app.config.foreign.clone())).expect("can't retrieve foreign chain_id");
|
|
|
|
info!(target: "bridge", "Home chain ID: {} Foreign chain ID: {}", home_chain_id, foreign_chain_id);
|
|
|
|
{
|
|
use bridge::api;
|
|
let mut home_nonce = app.config.home.info.nonce.write().unwrap();
|
|
let mut foreign_nonce = app.config.foreign.info.nonce.write().unwrap();
|
|
|
|
*home_nonce = event_loop.run(api::eth_get_transaction_count(app.connections.home.clone(), app.config.home.account, None)).expect("can't initialize home nonce");
|
|
*foreign_nonce = event_loop.run(api::eth_get_transaction_count(app.connections.foreign.clone(), app.config.foreign.account, None)).expect("can't initialize foreign nonce");
|
|
}
|
|
|
|
#[cfg(feature = "deploy")]
|
|
info!(target: "bridge", "Deploying contracts (if needed)");
|
|
#[cfg(not(feature = "deploy"))]
|
|
info!(target: "bridge", "Reading the database");
|
|
|
|
let deployed = event_loop.run(create_deploy(app.clone(), home_chain_id, foreign_chain_id))?;
|
|
|
|
let database = match deployed {
|
|
Deployed::New(database) => {
|
|
info!(target: "bridge", "Deployed new bridge contracts");
|
|
info!(target: "bridge", "\n\n{}\n", database);
|
|
database.save(fs::File::create(&app.database_path)?)?;
|
|
database
|
|
},
|
|
Deployed::Existing(database) => {
|
|
info!(target: "bridge", "Loaded database");
|
|
database
|
|
},
|
|
};
|
|
|
|
info!(target: "bridge", "Starting listening to events");
|
|
let bridge = create_bridge(app.clone(), &database, home_chain_id, foreign_chain_id).and_then(|_| future::ok(true)).collect();
|
|
let result = event_loop.run(bridge);
|
|
match result {
|
|
Err(Error(ErrorKind::Web3(web3::error::Error(web3::error::ErrorKind::Io(e), _)), _)) => {
|
|
if e.kind() == ::std::io::ErrorKind::BrokenPipe {
|
|
warn!("Connection to a node has been severed");
|
|
return Err((ERR_CONNECTION_LOST, e.into()).into());
|
|
} else {
|
|
warn!("I/O error: {:?}", e);
|
|
return Err((ERR_IO_ERROR, e.into()).into());
|
|
}
|
|
},
|
|
Err(e @ Error(ErrorKind::ShutdownRequested, _)) => {
|
|
info!("Shutdown requested, terminating");
|
|
return Err((ERR_SHUTDOWN_REQUESTED, e.into()).into());
|
|
},
|
|
Err(e @ Error(ErrorKind::InsufficientFunds, _)) => {
|
|
info!("Insufficient funds, terminating");
|
|
return Err((ERR_INSUFFICIENT_FUNDS, e.into()).into());
|
|
},
|
|
Err(Error(ErrorKind::Web3(web3::error::Error(web3::error::ErrorKind::Rpc(e), _)), _)) => {
|
|
if e.code == rpc::ErrorCode::ServerError(-32010) {
|
|
info!("Insufficient funds, terminating");
|
|
return Err((ERR_INSUFFICIENT_FUNDS, ErrorKind::Web3(web3::error::ErrorKind::Rpc(e).into()).into()).into());
|
|
} else {
|
|
info!("RPC error {:?}", e);
|
|
return Err((ERR_RPC_ERROR, ErrorKind::Web3(web3::error::ErrorKind::Rpc(e).into()).into()).into());
|
|
}
|
|
},
|
|
Err(e) => {
|
|
warn!("Bridge crashed with {}", e);
|
|
return Err((ERR_BRIDGE_CRASH, e).into());
|
|
},
|
|
Ok(_) => (),
|
|
}
|
|
|
|
Ok("Done".into())
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
}
|