// Copyright 2019-2021 Parity Technologies (UK) Ltd. // This file is part of Parity Bridges Common. // Parity Bridges Common is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Bridges Common is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . #![recursion_limit = "1024"] mod ethereum_client; mod ethereum_deploy_contract; mod ethereum_exchange; mod ethereum_exchange_submit; mod ethereum_sync_loop; mod instances; mod rialto_client; mod rpc_errors; mod substrate_sync_loop; mod substrate_types; use ethereum_deploy_contract::EthereumDeployContractParams; use ethereum_exchange::EthereumExchangeParams; use ethereum_exchange_submit::EthereumExchangeSubmitParams; use ethereum_sync_loop::EthereumSyncParams; use headers_relay::sync::TargetTransactionMode; use hex_literal::hex; use instances::{BridgeInstance, Kovan, RialtoPoA}; use relay_utils::{ initialize::initialize_relay, metrics::{MetricsAddress, MetricsParams}, }; use secp256k1::SecretKey; use sp_core::crypto::Pair; use substrate_sync_loop::SubstrateSyncParams; use headers_relay::sync::HeadersSyncParams; use relay_ethereum_client::{ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams}; use relay_rialto_client::SigningParams as RialtoSigningParams; use relay_substrate_client::ConnectionParams as SubstrateConnectionParams; use std::sync::Arc; fn main() { initialize_relay(); let yaml = clap::load_yaml!("cli.yml"); let matches = clap::App::from_yaml(yaml).get_matches(); async_std::task::block_on(run_command(&matches)); } async fn run_command(matches: &clap::ArgMatches<'_>) { match matches.subcommand() { ("eth-to-sub", Some(eth_to_sub_matches)) => { log::info!(target: "bridge", "Starting ETH ➡ SUB relay."); if ethereum_sync_loop::run(match ethereum_sync_params(eth_to_sub_matches) { Ok(ethereum_sync_params) => ethereum_sync_params, Err(err) => { log::error!(target: "bridge", "Error parsing parameters: {}", err); return; } }) .await .is_err() { log::error!(target: "bridge", "Unable to get Substrate genesis block for Ethereum sync."); }; } ("sub-to-eth", Some(sub_to_eth_matches)) => { log::info!(target: "bridge", "Starting SUB ➡ ETH relay."); if substrate_sync_loop::run(match substrate_sync_params(sub_to_eth_matches) { Ok(substrate_sync_params) => substrate_sync_params, Err(err) => { log::error!(target: "bridge", "Error parsing parameters: {}", err); return; } }) .await .is_err() { log::error!(target: "bridge", "Unable to get Substrate genesis block for Substrate sync."); }; } ("eth-deploy-contract", Some(eth_deploy_matches)) => { log::info!(target: "bridge", "Deploying ETH contracts."); ethereum_deploy_contract::run(match ethereum_deploy_contract_params(eth_deploy_matches) { Ok(ethereum_deploy_params) => ethereum_deploy_params, Err(err) => { log::error!(target: "bridge", "Error during contract deployment: {}", err); return; } }) .await; } ("eth-submit-exchange-tx", Some(eth_exchange_submit_matches)) => { log::info!(target: "bridge", "Submitting ETH ➡ SUB exchange transaction."); ethereum_exchange_submit::run(match ethereum_exchange_submit_params(eth_exchange_submit_matches) { Ok(eth_exchange_submit_params) => eth_exchange_submit_params, Err(err) => { log::error!(target: "bridge", "Error submitting Eethereum exchange transaction: {}", err); return; } }) .await; } ("eth-exchange-sub", Some(eth_exchange_matches)) => { log::info!(target: "bridge", "Starting ETH ➡ SUB exchange transactions relay."); ethereum_exchange::run(match ethereum_exchange_params(eth_exchange_matches) { Ok(eth_exchange_params) => eth_exchange_params, Err(err) => { log::error!(target: "bridge", "Error relaying Ethereum transactions proofs: {}", err); return; } }) .await; } ("", _) => { log::error!(target: "bridge", "No subcommand specified"); } _ => unreachable!("all possible subcommands are checked above; qed"), } } fn ethereum_connection_params(matches: &clap::ArgMatches) -> Result { let mut params = EthereumConnectionParams::default(); if let Some(eth_host) = matches.value_of("eth-host") { params.host = eth_host.into(); } if let Some(eth_port) = matches.value_of("eth-port") { params.port = eth_port .parse() .map_err(|e| format!("Failed to parse eth-port: {}", e))?; } Ok(params) } fn ethereum_signing_params(matches: &clap::ArgMatches) -> Result { let mut params = EthereumSigningParams::default(); if let Some(eth_signer) = matches.value_of("eth-signer") { params.signer = SecretKey::parse_slice(&hex::decode(eth_signer).map_err(|e| format!("Failed to parse eth-signer: {}", e))?) .map_err(|e| format!("Invalid eth-signer: {}", e))?; } if let Some(eth_chain_id) = matches.value_of("eth-chain-id") { params.chain_id = eth_chain_id .parse::() .map_err(|e| format!("Failed to parse eth-chain-id: {}", e))?; } Ok(params) } fn substrate_connection_params(matches: &clap::ArgMatches) -> Result { let mut params = SubstrateConnectionParams::default(); if let Some(sub_host) = matches.value_of("sub-host") { params.host = sub_host.into(); } if let Some(sub_port) = matches.value_of("sub-port") { params.port = sub_port .parse() .map_err(|e| format!("Failed to parse sub-port: {}", e))?; } Ok(params) } fn rialto_signing_params(matches: &clap::ArgMatches) -> Result { let mut params = sp_keyring::AccountKeyring::Alice.pair(); if let Some(sub_signer) = matches.value_of("sub-signer") { let sub_signer_password = matches.value_of("sub-signer-password"); params = sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password) .map_err(|e| format!("Failed to parse sub-signer: {:?}", e))?; } Ok(params) } fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result { use crate::ethereum_sync_loop::consts::*; let mut sync_params = HeadersSyncParams { max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, max_headers_in_single_submit: MAX_HEADERS_IN_SINGLE_SUBMIT, max_headers_size_in_single_submit: MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT, prune_depth: PRUNE_DEPTH, target_tx_mode: TargetTransactionMode::Signed, }; match matches.value_of("sub-tx-mode") { Some("signed") => sync_params.target_tx_mode = TargetTransactionMode::Signed, Some("unsigned") => { sync_params.target_tx_mode = TargetTransactionMode::Unsigned; // tx pool won't accept too much unsigned transactions sync_params.max_headers_in_submitted_status = 10; } Some("backup") => sync_params.target_tx_mode = TargetTransactionMode::Backup, Some(mode) => return Err(format!("Invalid sub-tx-mode: {}", mode)), None => sync_params.target_tx_mode = TargetTransactionMode::Signed, } let params = EthereumSyncParams { eth_params: ethereum_connection_params(matches)?, sub_params: substrate_connection_params(matches)?, sub_sign: rialto_signing_params(matches)?, metrics_params: metrics_params(matches)?, instance: instance_params(matches)?, sync_params, }; log::debug!(target: "bridge", "Ethereum sync params: {:?}", params); Ok(params) } fn substrate_sync_params(matches: &clap::ArgMatches) -> Result { use crate::substrate_sync_loop::consts::*; let eth_contract_address: relay_ethereum_client::types::Address = if let Some(eth_contract) = matches.value_of("eth-contract") { eth_contract.parse().map_err(|e| format!("{}", e))? } else { "731a10897d267e19b34503ad902d0a29173ba4b1" .parse() .expect("address is hardcoded, thus valid; qed") }; let params = SubstrateSyncParams { sub_params: substrate_connection_params(matches)?, eth_params: ethereum_connection_params(matches)?, eth_sign: ethereum_signing_params(matches)?, metrics_params: metrics_params(matches)?, sync_params: HeadersSyncParams { max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, max_headers_in_single_submit: MAX_SUBMITTED_HEADERS, max_headers_size_in_single_submit: std::usize::MAX, prune_depth: PRUNE_DEPTH, target_tx_mode: TargetTransactionMode::Signed, }, eth_contract_address, }; log::debug!(target: "bridge", "Substrate sync params: {:?}", params); Ok(params) } fn ethereum_deploy_contract_params(matches: &clap::ArgMatches) -> Result { let eth_contract_code = parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| { hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")).expect("code is hardcoded, thus valid; qed") }); let sub_initial_authorities_set_id = matches .value_of("sub-authorities-set-id") .map(|set| { set.parse() .map_err(|e| format!("Failed to parse sub-authorities-set-id: {}", e)) }) .transpose()?; let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?; let sub_initial_header = parse_hex_argument(matches, "sub-initial-header")?; let params = EthereumDeployContractParams { eth_params: ethereum_connection_params(matches)?, eth_sign: ethereum_signing_params(matches)?, sub_params: substrate_connection_params(matches)?, sub_initial_authorities_set_id, sub_initial_authorities_set, sub_initial_header, eth_contract_code, }; log::debug!(target: "bridge", "Deploy params: {:?}", params); Ok(params) } fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result { let eth_nonce = matches .value_of("eth-nonce") .map(|eth_nonce| { relay_ethereum_client::types::U256::from_dec_str(eth_nonce) .map_err(|e| format!("Failed to parse eth-nonce: {}", e)) }) .transpose()?; let eth_amount = matches .value_of("eth-amount") .map(|eth_amount| { eth_amount .parse() .map_err(|e| format!("Failed to parse eth-amount: {}", e)) }) .transpose()? .unwrap_or_else(|| { // This is in Wei, represents 1 ETH 1_000_000_000_000_000_000_u64.into() }); // This is the well-known Substrate account of Ferdie let default_recepient = hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"); let sub_recipient = if let Some(sub_recipient) = matches.value_of("sub-recipient") { hex::decode(&sub_recipient) .map_err(|err| err.to_string()) .and_then(|vsub_recipient| { let expected_len = default_recepient.len(); if expected_len != vsub_recipient.len() { Err(format!("invalid length. Expected {} bytes", expected_len)) } else { let mut sub_recipient = default_recepient; sub_recipient.copy_from_slice(&vsub_recipient[..expected_len]); Ok(sub_recipient) } }) .map_err(|e| format!("Failed to parse sub-recipient: {}", e))? } else { default_recepient }; let params = EthereumExchangeSubmitParams { eth_params: ethereum_connection_params(matches)?, eth_sign: ethereum_signing_params(matches)?, eth_nonce, eth_amount, sub_recipient, }; log::debug!(target: "bridge", "Submit Ethereum exchange tx params: {:?}", params); Ok(params) } fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result { let mode = match matches.value_of("eth-tx-hash") { Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single( eth_tx_hash .parse() .map_err(|e| format!("Failed to parse eth-tx-hash: {}", e))?, ), None => ethereum_exchange::ExchangeRelayMode::Auto( matches .value_of("eth-start-with-block") .map(|eth_start_with_block| { eth_start_with_block .parse() .map_err(|e| format!("Failed to parse eth-start-with-block: {}", e)) }) .transpose()?, ), }; let params = EthereumExchangeParams { eth_params: ethereum_connection_params(matches)?, sub_params: substrate_connection_params(matches)?, sub_sign: rialto_signing_params(matches)?, metrics_params: metrics_params(matches)?, instance: instance_params(matches)?, mode, }; log::debug!(target: "bridge", "Ethereum exchange params: {:?}", params); Ok(params) } fn metrics_params(matches: &clap::ArgMatches) -> Result { if matches.is_present("no-prometheus") { return Ok(None.into()); } let mut metrics_params = MetricsAddress::default(); if let Some(prometheus_host) = matches.value_of("prometheus-host") { metrics_params.host = prometheus_host.into(); } if let Some(prometheus_port) = matches.value_of("prometheus-port") { metrics_params.port = prometheus_port .parse() .map_err(|e| format!("Failed to parse prometheus-port: {}", e))?; } Ok(Some(metrics_params).into()) } fn instance_params(matches: &clap::ArgMatches) -> Result, String> { let instance = if let Some(instance) = matches.value_of("sub-pallet-instance") { match instance.to_lowercase().as_str() { "rialto" => Arc::new(RialtoPoA) as Arc, "kovan" => Arc::new(Kovan), _ => return Err("Unsupported bridge pallet instance".to_string()), } } else { unreachable!("CLI config enforces a default instance, can never be None") }; Ok(instance) } fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> Result>, String> { match matches.value_of(arg) { Some(value) => Ok(Some( hex::decode(value).map_err(|e| format!("Failed to parse {}: {}", arg, e))?, )), None => Ok(None), } }