From fddfbb5b1cbe43ed5be6504ab4987a218404094c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 29 Jul 2020 22:04:33 +0300 Subject: [PATCH] Submit exchange transactions to PoA node (#229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * submit Eth exchange transactions * submit ethereum in docker-compose * submit Eth exchange transactions * fix duplicate message * fix relay script * lost file * cargo fmt --all * cargo +nightly clippy * Show sccache * remove test-helpers remains * what's going on with jsonrpsee + Cargo.lock? * relay-eth-submit-exchange-tx -> poa-exchange-tx-generator * Update relays/ethereum/src/main.rs Co-authored-by: Tomasz Drwięga * configuring exchange-gen loop using env variables * fixed signer account from dev chain to Arthur * improve debug prints * parse nonce from relay output * --eth-nonce= * fix compilation * cargo fmt --all * fix typo * duplicate relay output to tty * allow using from bash scripts tests * fix: U256::parse() expects hex string :/ * cargo fmt --all * BRIDGE_HASH: ${BRIDGE_HASH:-master} * script comment * generate exchange PoA transactions by Bertha * Bertha address Co-authored-by: Denis S. Soldatov aka General-Beck Co-authored-by: Tomasz Drwięga --- bridges/bin/node/runtime/Cargo.toml | 3 +- bridges/bin/node/runtime/src/exchange.rs | 2 +- bridges/modules/ethereum/Cargo.toml | 3 +- bridges/primitives/ethereum-poa/Cargo.toml | 3 - bridges/primitives/ethereum-poa/src/lib.rs | 5 +- bridges/relays/ethereum/Cargo.toml | 1 + bridges/relays/ethereum/src/cli.yml | 22 ++++ .../ethereum/src/ethereum_exchange_submit.rs | 120 ++++++++++++++++++ bridges/relays/ethereum/src/main.rs | 54 +++++++- 9 files changed, 200 insertions(+), 13 deletions(-) create mode 100644 bridges/relays/ethereum/src/ethereum_exchange_submit.rs diff --git a/bridges/bin/node/runtime/Cargo.toml b/bridges/bin/node/runtime/Cargo.toml index 755dc99b34..11f3a8f457 100644 --- a/bridges/bin/node/runtime/Cargo.toml +++ b/bridges/bin/node/runtime/Cargo.toml @@ -214,7 +214,7 @@ features = ["hmac"] [dev-dependencies.sp-bridge-eth-poa] version = "0.1.0" default-features = false -features = ["std", "test-helpers"] +features = ["std"] path = "../../../primitives/ethereum-poa" [build-dependencies.wasm-builder-runner] @@ -264,6 +264,5 @@ runtime-benchmarks = [ "libsecp256k1", "pallet-bridge-currency-exchange/runtime-benchmarks", "pallet-bridge-eth-poa/runtime-benchmarks", - "sp-bridge-eth-poa/test-helpers", "sp-runtime/runtime-benchmarks", ] diff --git a/bridges/bin/node/runtime/src/exchange.rs b/bridges/bin/node/runtime/src/exchange.rs index 8754bec089..1048c66189 100644 --- a/bridges/bin/node/runtime/src/exchange.rs +++ b/bridges/bin/node/runtime/src/exchange.rs @@ -37,7 +37,7 @@ use sp_currency_exchange::{ use sp_std::vec::Vec; /// Ethereum address where locked PoA funds must be sent to. -const LOCK_FUNDS_ADDRESS: [u8; 20] = hex!("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"); +pub const LOCK_FUNDS_ADDRESS: [u8; 20] = hex!("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"); /// Ethereum transaction inclusion proof. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)] diff --git a/bridges/modules/ethereum/Cargo.toml b/bridges/modules/ethereum/Cargo.toml index 3b14060ab7..dbe32d6177 100644 --- a/bridges/modules/ethereum/Cargo.toml +++ b/bridges/modules/ethereum/Cargo.toml @@ -59,7 +59,7 @@ features = ["hmac"] # Dev Dependencies [dev-dependencies] # TODO: Stop renaming this on import -primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/ethereum-poa", features = ["std", "test-helpers"] } +primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/ethereum-poa", features = ["std"] } libsecp256k1 = { version = "0.3.4", features = ["hmac"] } [features] @@ -78,5 +78,4 @@ std = [ runtime-benchmarks = [ "frame-benchmarking", "libsecp256k1", - "primitives/test-helpers", ] diff --git a/bridges/primitives/ethereum-poa/Cargo.toml b/bridges/primitives/ethereum-poa/Cargo.toml index 232bdf21bf..f52fb42f1d 100644 --- a/bridges/primitives/ethereum-poa/Cargo.toml +++ b/bridges/primitives/ethereum-poa/Cargo.toml @@ -47,18 +47,15 @@ default-features = false git = "https://github.com/paritytech/substrate.git" [dependencies.libsecp256k1] -optional = true version = "0.3.4" default-features = false features = ["hmac"] [dev-dependencies] hex-literal = "0.2" -libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"] } [features] default = ["std"] -test-helpers = ["libsecp256k1"] std = [ "serde/std", "serde-big-array", diff --git a/bridges/primitives/ethereum-poa/src/lib.rs b/bridges/primitives/ethereum-poa/src/lib.rs index f418a8e325..ada3fe9a1a 100644 --- a/bridges/primitives/ethereum-poa/src/lib.rs +++ b/bridges/primitives/ethereum-poa/src/lib.rs @@ -54,7 +54,6 @@ pub type RawTransactionReceipt = Vec; /// An ethereum address. pub type Address = H160; -#[cfg(any(feature = "test-helpers", test))] pub mod signatures; /// Complete header id. @@ -113,8 +112,7 @@ pub struct Transaction { } /// Unsigned portion of ethereum transaction. -#[derive(PartialEq, RuntimeDebug)] -#[cfg_attr(test, derive(Clone))] +#[derive(Clone, PartialEq, RuntimeDebug)] pub struct UnsignedTransaction { /// Sender nonce. pub nonce: U256, @@ -396,7 +394,6 @@ impl SealedEmptyStep { } /// Returns rlp for the vector of empty steps (we only do encoding in tests). - #[cfg(feature = "test-helpers")] pub fn rlp_of(empty_steps: &[SealedEmptyStep]) -> Bytes { let mut s = RlpStream::new(); s.begin_list(empty_steps.len()); diff --git a/bridges/relays/ethereum/Cargo.toml b/bridges/relays/ethereum/Cargo.toml index cd39d6ea44..972d9be945 100644 --- a/bridges/relays/ethereum/Cargo.toml +++ b/bridges/relays/ethereum/Cargo.toml @@ -20,6 +20,7 @@ ethabi-derive = "12.0" ethereum-tx-sign = "3.0" futures = "0.3.5" hex = "0.4" +hex-literal = "0.3" linked-hash-map = "0.5.3" log = "0.4.11" num-traits = "0.2" diff --git a/bridges/relays/ethereum/src/cli.yml b/bridges/relays/ethereum/src/cli.yml index 6e11c70ada..135e8da29a 100644 --- a/bridges/relays/ethereum/src/cli.yml +++ b/bridges/relays/ethereum/src/cli.yml @@ -106,6 +106,28 @@ subcommands: value_name: SUB_INITIAL_HEADER help: Encoded initial Substrate header. takes_value: true + - eth-submit-exchange-tx: + about: Submit lock funds transaction to Ethereum node. + args: + - eth-host: *eth-host + - eth-port: *eth-port + - eth-nonce: + long: eth-nonce + value_name: ETH_NONCE + help: Nonce that have to be used when building transaction. If not specified, read from PoA node. + takes_value: true + - eth-signer: *eth-signer + - eth-chain-id: *eth-chain-id + - eth-amount: + long: eth-amount + value_name: ETH_AMOUNT + help: Amount of ETH to lock (in wei). + takes_value: true + - sub-recipient: + long: sub-recipient + value_name: SUB_RECIPIENT + help: Hex-encoded Public key of funds recipient in Substrate chain. + takes_value: true - eth-exchange-sub: about: Submit proof of PoA lock funds transaction to Substrate node. args: diff --git a/bridges/relays/ethereum/src/ethereum_exchange_submit.rs b/bridges/relays/ethereum/src/ethereum_exchange_submit.rs new file mode 100644 index 0000000000..201b251db2 --- /dev/null +++ b/bridges/relays/ethereum/src/ethereum_exchange_submit.rs @@ -0,0 +1,120 @@ +// Copyright 2019-2020 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 . + +//! Submitting Ethereum -> Substrate exchange transactions. + +use crate::ethereum_client::{EthereumConnectionParams, EthereumRpcClient, EthereumSigningParams}; +use crate::ethereum_types::{CallRequest, U256}; +use crate::rpc::EthereumRpc; + +use bridge_node_runtime::exchange::LOCK_FUNDS_ADDRESS; +use hex_literal::hex; +use sp_bridge_eth_poa::{ + signatures::{SecretKey, SignTransaction}, + UnsignedTransaction, +}; + +/// Ethereum exchange transaction params. +#[derive(Debug)] +pub struct EthereumExchangeSubmitParams { + /// Ethereum connection params. + pub eth: EthereumConnectionParams, + /// Ethereum signing params. + pub eth_sign: EthereumSigningParams, + /// Ethereum signer nonce. + pub eth_nonce: Option, + /// Amount of Ethereum tokens to lock. + pub eth_amount: U256, + /// Funds recipient on Substrate side. + pub sub_recipient: [u8; 32], +} + +impl Default for EthereumExchangeSubmitParams { + fn default() -> Self { + EthereumExchangeSubmitParams { + eth: Default::default(), + eth_sign: Default::default(), + eth_nonce: None, + eth_amount: 1_000_000_000_000_000_000_u64.into(), // 1 ETH + sub_recipient: hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"), // ferdie + } + } +} + +/// Submit single Ethereum -> Substrate exchange transaction. +pub fn run(params: EthereumExchangeSubmitParams) { + let mut local_pool = futures::executor::LocalPool::new(); + + let result: Result<_, String> = local_pool.run_until(async move { + let eth_client = EthereumRpcClient::new(params.eth); + + let eth_signer_address = params.eth_sign.signer.address(); + let sub_recipient_encoded = params.sub_recipient; + let nonce = match params.eth_nonce { + Some(eth_nonce) => eth_nonce, + None => eth_client + .account_nonce(eth_signer_address) + .await + .map_err(|err| format!("error fetching acount nonce: {:?}", err))?, + }; + let gas = eth_client + .estimate_gas(CallRequest { + from: Some(eth_signer_address), + to: Some(LOCK_FUNDS_ADDRESS.into()), + value: Some(params.eth_amount), + data: Some(sub_recipient_encoded.to_vec().into()), + ..Default::default() + }) + .await + .map_err(|err| format!("error estimating gas requirements: {:?}", err))?; + let eth_tx_unsigned = UnsignedTransaction { + nonce, + gas_price: params.eth_sign.gas_price, + gas, + to: Some(LOCK_FUNDS_ADDRESS.into()), + value: params.eth_amount, + payload: sub_recipient_encoded.to_vec(), + }; + let eth_tx_signed = eth_tx_unsigned.clone().sign_by( + &SecretKey::parse(params.eth_sign.signer.secret().as_fixed_bytes()) + .expect("key is accepted by secp256k1::KeyPair and thus is valid; qed"), + Some(params.eth_sign.chain_id), + ); + eth_client + .submit_transaction(eth_tx_signed) + .await + .map_err(|err| format!("error submitting transaction: {:?}", err))?; + + Ok(eth_tx_unsigned) + }); + + match result { + Ok(eth_tx_unsigned) => { + log::info!( + target: "bridge", + "Exchange transaction has been submitted to Ethereum node: {:?}", + eth_tx_unsigned, + ); + } + Err(err) => { + log::error!( + target: "bridge", + "Error submitting exchange transaction to Ethereum node: {}", + err, + ); + } + } +} diff --git a/bridges/relays/ethereum/src/main.rs b/bridges/relays/ethereum/src/main.rs index caddcfb9ca..94d88f6e93 100644 --- a/bridges/relays/ethereum/src/main.rs +++ b/bridges/relays/ethereum/src/main.rs @@ -19,6 +19,7 @@ mod ethereum_client; mod ethereum_deploy_contract; mod ethereum_exchange; +mod ethereum_exchange_submit; mod ethereum_sync_loop; mod ethereum_types; mod exchange; @@ -85,14 +86,25 @@ fn main() { ("eth-deploy-contract", Some(eth_deploy_matches)) => { log::info!(target: "bridge", "Deploying ETH contracts."); ethereum_deploy_contract::run(match ethereum_deploy_contract_params(ð_deploy_matches) { - Ok(ethereum_deploy_matches) => ethereum_deploy_matches, + Ok(ethereum_deploy_params) => ethereum_deploy_params, Err(err) => { log::error!(target: "bridge", "Error during contract deployment: {}", err); return; } }); } + ("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(ð_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; + } + }); + } ("eth-exchange-sub", Some(eth_exchange_matches)) => { + log::info!(target: "bridge", "Starting ETH ➡ SUB exchange transactions relay."); ethereum_exchange::run(match ethereum_exchange_params(ð_exchange_matches) { Ok(eth_exchange_params) => eth_exchange_params, Err(err) => { @@ -256,6 +268,44 @@ fn ethereum_deploy_contract_params( Ok(eth_deploy_params) } +fn ethereum_exchange_submit_params( + matches: &clap::ArgMatches, +) -> Result { + let mut params = ethereum_exchange_submit::EthereumExchangeSubmitParams::default(); + params.eth = ethereum_connection_params(matches)?; + params.eth_sign = ethereum_signing_params(matches)?; + + if let Some(eth_nonce) = matches.value_of("eth-nonce") { + params.eth_nonce = Some( + ethereum_types::U256::from_dec_str(ð_nonce).map_err(|e| format!("Failed to parse eth-nonce: {}", e))?, + ); + } + if let Some(eth_amount) = matches.value_of("eth-amount") { + params.eth_amount = eth_amount + .parse() + .map_err(|e| format!("Failed to parse eth-amount: {}", e))?; + } + if let Some(sub_recipient) = matches.value_of("sub-recipient") { + params.sub_recipient = hex::decode(&sub_recipient) + .map_err(|err| err.to_string()) + .and_then(|vsub_recipient| { + let expected_len = params.sub_recipient.len(); + if expected_len != vsub_recipient.len() { + Err(format!("invalid length. Expected {} bytes", expected_len)) + } else { + let mut sub_recipient = params.sub_recipient; + sub_recipient.copy_from_slice(&vsub_recipient[..expected_len]); + Ok(sub_recipient) + } + }) + .map_err(|e| format!("Failed to parse sub-recipient: {}", e))?; + } + + log::debug!(target: "bridge", "Submit Ethereum exchange tx params: {:?}", params); + + Ok(params) +} + fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result { let mut params = ethereum_exchange::EthereumExchangeParams::default(); params.eth = ethereum_connection_params(matches)?; @@ -279,6 +329,8 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result