From 3d15ac7c902efcbe1dc2ad4d874ddd7771eeb7bc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 29 Apr 2020 10:09:13 +0300 Subject: [PATCH] Solidity contract that accepts unverified substrate headers (#65) * solidity contract * continue * upd * cargo update * fixes * ehtereum_headers -> headers * extracted some common stuff * ethereum_sync.rs -> sync.rs * make sync generic * continue extracting * continue * add eth-contract argument * continue * some fixes * contract v2 * continue * more fixes * more fixes * deal with duplicated params * removed multiple call_rpc variants * bail_on_error!() * fn submit_ethereum_transaction * more fixes * cargo fmt --all * fix * bail_on_arg_error!() * fix * fix * remove async_extra stuff * substrate-bridge.json -> substrate-bridge-abi.json * get rid of substrate transactions hashes * get rid of ethereum transactions hashes * extracted contract bytecode to separate file * cargo fmt --all * avoid duplicate import in contracts * removed Default::default() * swapped configurations for sub2eth && eth2sub * fix compilation * do not double gas limit when submitting Substrate headers * cargo fmt --all * solidity contract removed * consts * extracted solc compilation details to separate file * removed (obsolete in future Vec justification) * fixed cli option description * fix typos * fix grumble * extracted constants * log decoded header * cargo fmt --all * comment --- .../ethereum-contract/builtin/Cargo.toml | 20 ++ .../ethereum-contract/builtin/src/lib.rs | 79 ++++++ bridges/relays/ethereum/Cargo.toml | 6 + .../ethereum/res/substrate-bridge-abi.json | 119 ++++++++ .../res/substrate-bridge-bytecode.hex | 1 + .../res/substrate-bridge-metadata.txt | 5 + bridges/relays/ethereum/src/cli.yml | 79 ++++++ .../relays/ethereum/src/ethereum_client.rs | 260 ++++++++++++++++- .../ethereum/src/ethereum_deploy_contract.rs | 136 +++++++++ .../relays/ethereum/src/ethereum_sync_loop.rs | 96 ++++--- bridges/relays/ethereum/src/ethereum_types.rs | 5 +- bridges/relays/ethereum/src/headers.rs | 36 ++- bridges/relays/ethereum/src/main.rs | 104 ++++++- .../relays/ethereum/src/substrate_client.rs | 261 ++++++++++++------ .../ethereum/src/substrate_sync_loop.rs | 202 ++++++++++++++ .../relays/ethereum/src/substrate_types.rs | 47 +++- bridges/relays/ethereum/src/sync.rs | 34 ++- bridges/relays/ethereum/src/sync_loop.rs | 13 +- bridges/relays/ethereum/src/utils.rs | 37 +++ 19 files changed, 1347 insertions(+), 193 deletions(-) create mode 100644 bridges/modules/ethereum-contract/builtin/Cargo.toml create mode 100644 bridges/modules/ethereum-contract/builtin/src/lib.rs create mode 100644 bridges/relays/ethereum/res/substrate-bridge-abi.json create mode 100644 bridges/relays/ethereum/res/substrate-bridge-bytecode.hex create mode 100644 bridges/relays/ethereum/res/substrate-bridge-metadata.txt create mode 100644 bridges/relays/ethereum/src/ethereum_deploy_contract.rs create mode 100644 bridges/relays/ethereum/src/substrate_sync_loop.rs create mode 100644 bridges/relays/ethereum/src/utils.rs diff --git a/bridges/modules/ethereum-contract/builtin/Cargo.toml b/bridges/modules/ethereum-contract/builtin/Cargo.toml new file mode 100644 index 0000000000..be2b1c7f68 --- /dev/null +++ b/bridges/modules/ethereum-contract/builtin/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ethereum-contract-builtin" +description = "Small crate that helps Solidity contract to verify finality proof." +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] + +# General dependencies + +codec = { package = "parity-scale-codec", version = "1.0.0" } +finality-grandpa = { version = "0.11.2", features = ["derive-codec"] } +sp-blockchain = "2.0.0-alpha.5" +sp-finality-grandpa = "2.0.0-alpha.5" +sp-runtime = "2.0.0-alpha.5" + +# Runtime/chain specific dependencies + +bridge-node-runtime = { path = "../../../bin/node/runtime" } diff --git a/bridges/modules/ethereum-contract/builtin/src/lib.rs b/bridges/modules/ethereum-contract/builtin/src/lib.rs new file mode 100644 index 0000000000..3249307ac9 --- /dev/null +++ b/bridges/modules/ethereum-contract/builtin/src/lib.rs @@ -0,0 +1,79 @@ +// 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 . + +use bridge_node_runtime::{BlockNumber, Hash, Header as RuntimeHeader}; +use codec::Decode; +use sp_blockchain::Error as ClientError; + +/// Builtin errors. +#[derive(Debug)] +pub enum Error { + /// Failed to decode Substrate header. + HeaderDecode(codec::Error), + /// Failed to decode best voters set. + BestVotersDecode(codec::Error), + /// Failed to decode finality proof. + FinalityProofDecode(codec::Error), + /// Failed to verify justification. + JustificationVerify(ClientError), +} + +/// Substrate header. +#[derive(Debug)] +pub struct Header { + /// Header hash. + pub hash: Hash, + /// Parent header hash. + pub parent_hash: Hash, + /// Header number. + pub number: BlockNumber, + /// GRANDPA validators change signal. + pub signal: Option, +} + +/// GRANDPA validators set change signal. +#[derive(Debug)] +pub struct ValidatorsSetSignal { + /// Signal delay. + pub delay: BlockNumber, + /// New validators set. + pub validators: Vec, +} + +/// Parse Substrate header. +pub fn parse_substrate_header(raw_header: &[u8]) -> Result { + RuntimeHeader::decode(&mut &raw_header[..]) + .map(|header| Header { + hash: header.hash(), + parent_hash: header.parent_hash, + number: header.number, + signal: None, // TODO: parse me + }) + .map_err(Error::HeaderDecode) +} + +/// Verify GRANDPA finality proof. +pub fn verify_substrate_finality_proof( + _best_set_id: u64, + _raw_best_voters: &[u8], + _raw_best_header: &[u8], + _raw_headers: &[&[u8]], + _raw_finality_proof: &[u8], +) -> Result<(usize, usize), Error> { + Err(Error::JustificationVerify(ClientError::Msg( + "Not yet implemented".into(), + ))) // TODO: implement me +} diff --git a/bridges/relays/ethereum/Cargo.toml b/bridges/relays/ethereum/Cargo.toml index 02698ef37c..d7e337dada 100644 --- a/bridges/relays/ethereum/Cargo.toml +++ b/bridges/relays/ethereum/Cargo.toml @@ -11,11 +11,17 @@ async-stream = "0.2.0" clap = { version = "2.33.0", features = ["yaml"] } codec = { package = "parity-scale-codec", version = "1.0.0" } env_logger = "0.7.0" +ethabi = "12.0" +ethabi-contract = "11.0" +ethabi-derive = "11.0" +ethereum-tx-sign = { git = "https://github.com/svyatonik/ethereum-tx-sign.git", branch = "up-ethereum-types" } futures = "0.3.1" +hex = "0.4" jsonrpsee = { git = "https://github.com/paritytech/jsonrpsee.git", default-features = false, features = ["http"] } linked-hash-map = "0.5.2" log = "0.4.8" num-traits = "0.2" +parity-crypto = { version = "0.6", features = ["publickey"] } parking_lot = "0.10.2" rustc-hex = "2.0.1" serde = { version = "1.0.106", features = ["derive"] } diff --git a/bridges/relays/ethereum/res/substrate-bridge-abi.json b/bridges/relays/ethereum/res/substrate-bridge-abi.json new file mode 100644 index 0000000000..4ff57ab656 --- /dev/null +++ b/bridges/relays/ethereum/res/substrate-bridge-abi.json @@ -0,0 +1,119 @@ +[ + { + "inputs": [ + { + "internalType": "bytes", + "name": "rawInitialHeader", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "initialValidatorsSetId", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "initialValidatorsSet", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "inputs": [], + "name": "bestKnownHeader", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "finalityTargetNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "finalityTargetHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "rawFinalityProof", + "type": "bytes" + } + ], + "name": "importFinalityProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "rawHeader", + "type": "bytes" + } + ], + "name": "importHeader", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "headerHash", + "type": "bytes32" + } + ], + "name": "isFinalityProofRequired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "headerHash", + "type": "bytes32" + } + ], + "name": "isKnownHeader", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/bridges/relays/ethereum/res/substrate-bridge-bytecode.hex b/bridges/relays/ethereum/res/substrate-bridge-bytecode.hex new file mode 100644 index 0000000000..54c252b65d --- /dev/null +++ b/bridges/relays/ethereum/res/substrate-bridge-bytecode.hex @@ -0,0 +1 @@ +60806040523480156200001157600080fd5b5060405162000e6738038062000e67833981810160405260608110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b506040818152602083015192018051929491939192846401000000008211156200010f57600080fd5b9083019060208201858111156200012557600080fd5b82516401000000008111828201881017156200014057600080fd5b82525081516020918201929091019080838360005b838110156200016f57818101518382015260200162000155565b50505050905090810190601f1680156200019d5780820380516001836020036101000a031916815260200191505b50604052505050620001ae620003d4565b620001c2846001600160e01b03620002dc16565b805160008181556002918255604080840180516001908155825160e08101845281815260208088015181830190815293518286019081526080808a0151606085019081526001600160401b038e169185019190915260a0840188905260c084018890528951885260058352959096208251815460ff191690151517815593519284019290925593519482019490945590518051949550919390926200026f92600385019291019062000409565b506080820151600482810180546001600160401b03199081166001600160401b039485161790915560a0850151600585015560c09094015160069093019290925560038054909316908616179091558251620002d19190602085019062000409565b5050505050620004ae565b620002e6620003d4565b600080600080600060608751602089016040516020810160208101602081016020810160a08588886010600019fa6200031e57600080fd5b93519251915190519351929b50909950975090955093505082159050620003a757816001600160401b03811180156200035657600080fd5b506040519080825280601f01601f19166020018201604052801562000382576020820181803683370190505b50905087516020890160208301848184846011600019fa620003a357600080fd5b5050505b6040805160a081018252968752602087019590955293850192909252606084015250608082015292915050565b6040518060a0016040528060008019168152602001600080191681526020016000815260200160008152602001606081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200044c57805160ff19168380011785556200047c565b828001600101855582156200047c579182015b828111156200047c5782518255916020019190600101906200045f565b506200048a9291506200048e565b5090565b620004ab91905b808211156200048a576000815560010162000495565b90565b6109a980620004be6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063871ebe181461005c578063a98bfaad1461008d578063d96a2deb146100aa578063e7af0779146100cb578063fae71ae814610173575b600080fd5b6100796004803603602081101561007257600080fd5b5035610225565b604080519115158252519081900360200190f35b610079600480360360208110156100a357600080fd5b503561023a565b6100b2610297565b6040805192835260208301919091528051918290030190f35b610171600480360360208110156100e157600080fd5b8101906020810181356401000000008111156100fc57600080fd5b82018360208201111561010e57600080fd5b8035906020019184600183028401116401000000008311171561013057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506102af945050505050565b005b6101716004803603606081101561018957600080fd5b8135916020810135918101906060810160408201356401000000008111156101b057600080fd5b8201836020820111156101c257600080fd5b803590602001918460018302840111640100000000831117156101e457600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061058e945050505050565b60009081526005602052604090205460ff1690565b6000818152600560205260408120805460ff16801561025e57506001548160020154115b8015610271575080600601548160020154145b80156102905750600354600482015467ffffffffffffffff9081169116145b9392505050565b60008054808252600560205260409091206002015491565b6102b76107a8565b6102c0826106ac565b805160009081526005602052604090205490915060ff1615610329576040805162461bcd60e51b815260206004820152601760248201527f48656164657220697320616c7265616479206b6e6f776e000000000000000000604482015290519081900360640190fd5b6020808201516000908152600590915260409020805460ff168015610358575060018260400151038160020154145b6103935760405162461bcd60e51b81526004018080602001828103825260268152602001806108ee6026913960400191505060405180910390fd5b6006810154158015906103ad575080600201548160060154145b156103f6578160200151600254146103f65760405162461bcd60e51b81526004018080602001828103825260318152602001806109436031913960400191505060405180910390fd5b60068101546080830151511561046b578260400151811061045e576040805162461bcd60e51b815260206004820152601960248201527f4f7665726c617070696e67207369676e616c7320666f756e6400000000000000604482015290519081900360640190fd5b5060608201516040830151015b60048201546005830154600384015467ffffffffffffffff9092169160026000196001831615610100020190911604156104ac575060208401516001909101905b6040805160e0810182526001808252602088810151818401908152898501518486019081526080808c01516060870190815267ffffffffffffffff8a169187019190915260a0860188905260c086018a90528b51600090815260058552969096208551815460ff191690151517815591519382019390935591516002830155925180519293919261054392600385019201906107dd565b50608082015160048201805467ffffffffffffffff191667ffffffffffffffff90921691909117905560a0820151600582015560c09091015160069091015550509151600055505050565b60008281526005602052604090206002015483146105dd5760405162461bcd60e51b815260040180806020018281038252602f815260200180610914602f913960400191505060405180910390fd5b60025460006105ed85858561079e565b600081815260056020526040902060028281558101546001559091505b8282146106a457506000908152600560205260409020600181015460068201546002830154919291141561069f576005818101546000908152602091909152604090206003805467ffffffffffffffff198116600167ffffffffffffffff9283168101909216178255908201805461069892600492916002610100928216159290920260001901160461085b565b50506106a4565b61060a565b505050505050565b6106b46107a8565b600080600080600060608751602089016040516020810160208101602081016020810160a08588886010600019fa6106eb57600080fd5b93519251915190519351929b50909950975090955093505082159050610771578167ffffffffffffffff8111801561072257600080fd5b506040519080825280601f01601f19166020018201604052801561074d576020820181803683370190505b50905087516020890160208301848184846011600019fa61076d57600080fd5b5050505b6040805160a081018252968752602087019590955293850192909252606084015250608082015292915050565b6002549392505050565b6040518060a0016040528060008019168152602001600080191681526020016000815260200160008152602001606081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061081e57805160ff191683800117855561084b565b8280016001018555821561084b579182015b8281111561084b578251825591602001919060010190610830565b506108579291506108d0565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10610894578054855561084b565b8280016001018555821561084b57600052602060002091601f016020900482015b8281111561084b5782548255916001019190600101906108b5565b6108ea91905b8082111561085757600081556001016108d6565b9056fe4d697373696e6720706172656e74206865616465722066726f6d207468652073746f726167654d697373696e672066696e616c69747920746172676574206865616465722066726f6d207468652073746f726167654d697373696e672072657175697265642066696e616c6974792070726f6f6620666f7220706172656e7420686561646572a26469706673582212206cc35a10288e85e37e14250b2d4af37dc4fbcd59635965d9ac0421cb8db1f66c64736f6c63430006060033 \ No newline at end of file diff --git a/bridges/relays/ethereum/res/substrate-bridge-metadata.txt b/bridges/relays/ethereum/res/substrate-bridge-metadata.txt new file mode 100644 index 0000000000..0dc5d853f2 --- /dev/null +++ b/bridges/relays/ethereum/res/substrate-bridge-metadata.txt @@ -0,0 +1,5 @@ +Last Change Date: 2020-04-28 +Solc version: 0.6.6+commit.6c089d02 +Source hash (keccak256): 0xdc46aff04e37129265223e507d17f1407a70cb1ecea3230e1eaa77a17586724d +Source gist: https://gist.github.com/svyatonik/876b388f9507a8de242cb2db9547c4f0 +Compiler flags used (command to produce the file): `docker run -i ethereum/solc:0.6.6 --optimize --bin - < substrate-bridge.sol` \ No newline at end of file diff --git a/bridges/relays/ethereum/src/cli.yml b/bridges/relays/ethereum/src/cli.yml index fad56da722..4cb33fd7b9 100644 --- a/bridges/relays/ethereum/src/cli.yml +++ b/bridges/relays/ethereum/src/cli.yml @@ -43,3 +43,82 @@ subcommands: long: sub-signer-password value_name: SUB_SIGNER_PASSWORD help: The password for the SURI of secret key to use when transactions are submitted to the Substrate node. + - sub-to-eth: + about: Synchronize headers from Substrate node to Ethereum node. + args: + - eth-host: + long: eth-host + value_name: ETH_HOST + help: Connect to Ethereum node at given host. + takes_value: true + - eth-port: + long: eth-port + value_name: ETH_PORT + help: Connect to Ethereum node at given port. + takes_value: true + - eth-contract: + long: eth-contract + value_name: ETH_CONTRACT + help: Address of deployed bridge contract. + takes_value: true + - eth-signer: + long: eth-signer + value_name: ETH_SIGNER + help: Hex-encoded secret to use when transactions are submitted to the Ethereum node. + - sub-host: + long: sub-host + value_name: SUB_HOST + help: Connect to Substrate node at given host. + takes_value: true + - sub-port: + long: sub-port + value_name: SUB_PORT + help: Connect to Substrate node at given port. + takes_value: true + - eth-deploy-contract: + about: Deploy Bridge contract on Ethereum node. + args: + - eth-host: + long: eth-host + value_name: ETH_HOST + help: Connect to Ethereum node at given host. + takes_value: true + - eth-port: + long: eth-port + value_name: ETH_PORT + help: Connect to Ethereum node at given port. + takes_value: true + - eth-signer: + long: eth-signer + value_name: ETH_SIGNER + help: Hex-encoded secret to use when transactions are submitted to the Ethereum node. + - eth-contract-code: + long: eth-contract-code + value_name: ETH_CONTRACT_CODE + help: Bytecode of bridge contract. + takes_value: true + - sub-host: + long: sub-host + value_name: SUB_HOST + help: Connect to Substrate node at given host. + takes_value: true + - sub-port: + long: sub-port + value_name: SUB_PORT + help: Connect to Substrate node at given port. + takes_value: true + - sub-authorities-set-id: + long: sub-authorities-set-id + value_name: SUB_AUTHORITIES_SET_ID + help: ID of initial GRANDPA authorities set. + takes_value: true + - sub-authorities-set: + long: sub-authorities-set + value_name: SUB_AUTHORITIES_SET + help: Encoded initial GRANDPA authorities set. + takes_value: true + - sub-initial-header: + long: sub-initial-header + value_name: SUB_INITIAL_HEADER + help: Encoded initial Substrate header. + takes_value: true diff --git a/bridges/relays/ethereum/src/ethereum_client.rs b/bridges/relays/ethereum/src/ethereum_client.rs index cddbc8bc14..8cc6078123 100644 --- a/bridges/relays/ethereum/src/ethereum_client.rs +++ b/bridges/relays/ethereum/src/ethereum_client.rs @@ -14,14 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::ethereum_types::{EthereumHeaderId, Header, Receipt, H256, U64}; -use crate::sync_types::MaybeConnectionError; +use crate::ethereum_types::{Address, Bytes, EthereumHeaderId, Header, Receipt, TransactionHash, H256, U256, U64}; +use crate::substrate_types::{Hash as SubstrateHash, QueuedSubstrateHeader, SubstrateHeaderId}; +use crate::sync_types::{HeaderId, MaybeConnectionError}; +use crate::{bail_on_arg_error, bail_on_error}; +use codec::{Decode, Encode}; +use ethabi::FunctionOutputDecoder; use jsonrpsee::common::Params; use jsonrpsee::raw::{RawClient, RawClientError}; use jsonrpsee::transport::http::{HttpTransportClient, RequestError}; -use serde::de::DeserializeOwned; +use parity_crypto::publickey::KeyPair; +use serde::{de::DeserializeOwned, Serialize}; use serde_json::{from_value, to_value}; +// to encode/decode contract calls +ethabi_contract::use_contract!(bridge_contract, "res/substrate-bridge-abi.json"); + /// Proof of hash serialization success. const HASH_SERIALIZATION_PROOF: &'static str = "hash serialization never fails; qed"; /// Proof of integer serialization success. @@ -29,24 +37,83 @@ const INT_SERIALIZATION_PROOF: &'static str = "integer serialization never fails /// Proof of bool serialization success. const BOOL_SERIALIZATION_PROOF: &'static str = "bool serialization never fails; qed"; +/// Ethereum connection params. +#[derive(Debug)] +pub struct EthereumConnectionParams { + /// Ethereum RPC host. + pub host: String, + /// Ethereum RPC port. + pub port: u16, +} + +impl Default for EthereumConnectionParams { + fn default() -> Self { + EthereumConnectionParams { + host: "localhost".into(), + port: 8545, + } + } +} + +/// Ethereum signing params. +#[derive(Clone, Debug)] +pub struct EthereumSigningParams { + /// Ethereum chain id. + pub chain_id: u64, + /// Ethereum transactions signer. + pub signer: KeyPair, + /// Gas price we agree to pay. + pub gas_price: U256, +} + +impl Default for EthereumSigningParams { + fn default() -> Self { + EthereumSigningParams { + chain_id: 0x11, // Parity dev chain + // account that has a lot of ether when we run instant seal engine + // address: 0x00a329c0648769a73afac7f9381e08fb43dbea72 + // secret: 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7 + signer: KeyPair::from_secret_slice( + &hex::decode("4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7") + .expect("secret is hardcoded, thus valid; qed"), + ) + .expect("secret is hardcoded, thus valid; qed"), + gas_price: 8_000_000_000u64.into(), // 8 Gwei + } + } +} + /// Ethereum client type. pub type Client = RawClient; +/// Ethereum contract call request. +#[derive(Debug, Default, PartialEq, Serialize)] +pub struct CallRequest { + /// Contract address. + pub to: Option
, + /// Call data. + pub data: Option, +} + /// All possible errors that can occur during interacting with Ethereum node. #[derive(Debug)] pub enum Error { /// Request start failed. StartRequestFailed(RequestError), + /// Error serializing request. + RequestSerialization(serde_json::Error), /// Request not found (should never occur?). RequestNotFound, /// Failed to receive response. ResponseRetrievalFailed(RawClientError), /// Failed to parse response. - ResponseParseFailed(serde_json::Error), + ResponseParseFailed(String), /// We have received header with missing number and hash fields. IncompleteHeader, /// We have received receipt with missing gas_used field. IncompleteReceipt, + /// Invalid Substrate block number received from Ethereum node. + InvalidSubstrateBlockNumber, } impl MaybeConnectionError for Error { @@ -59,8 +126,9 @@ impl MaybeConnectionError for Error { } /// Returns client that is able to call RPCs on Ethereum node. -pub fn client(uri: &str) -> Client { - let transport = HttpTransportClient::new(uri); +pub fn client(params: EthereumConnectionParams) -> Client { + let uri = format!("http://{}:{}", params.host, params.port); + let transport = HttpTransportClient::new(&uri); RawClient::new(transport) } @@ -118,15 +186,11 @@ pub async fn header_by_hash(client: Client, hash: H256) -> (Client, Result, + transactions: Vec, ) -> (Client, Result<(EthereumHeaderId, Vec), Error>) { - let mut transactions_receipts = Vec::with_capacity(transacactions.len()); - for transacaction in transacactions { - let (next_client, transaction_receipt) = transaction_receipt(client, transacaction).await; - let transaction_receipt = match transaction_receipt { - Ok(transaction_receipt) => transaction_receipt, - Err(error) => return (next_client, Err(error)), - }; + let mut transactions_receipts = Vec::with_capacity(transactions.len()); + for transaction in transactions { + let (next_client, transaction_receipt) = bail_on_error!(transaction_receipt(client, transaction).await); transactions_receipts.push(transaction_receipt); client = next_client; } @@ -150,6 +214,172 @@ async fn transaction_receipt(client: Client, hash: H256) -> (Client, Result (Client, Result) { + let (encoded_call, call_decoder) = bridge_contract::functions::best_known_header::call(); + let call_request = bail_on_arg_error!( + to_value(CallRequest { + to: Some(contract_address), + data: Some(encoded_call.into()), + }) + .map_err(|e| Error::RequestSerialization(e)), + client + ); + let (client, call_result) = + bail_on_error!(call_rpc::(client, "eth_call", Params::Array(vec![call_request]),).await); + let (number, raw_hash) = match call_decoder.decode(&call_result.0) { + Ok((raw_number, raw_hash)) => (raw_number, raw_hash), + Err(error) => return (client, Err(Error::ResponseParseFailed(format!("{}", error)))), + }; + let hash = match SubstrateHash::decode(&mut &raw_hash[..]) { + Ok(hash) => hash, + Err(error) => return (client, Err(Error::ResponseParseFailed(format!("{}", error)))), + }; + + if number != number.low_u32().into() { + return (client, Err(Error::InvalidSubstrateBlockNumber)); + } + + (client, Ok(HeaderId(number.low_u32(), hash))) +} + +/// Returns true if Substrate header is known to Ethereum node. +pub async fn substrate_header_known( + client: Client, + contract_address: Address, + id: SubstrateHeaderId, +) -> (Client, Result<(SubstrateHeaderId, bool), Error>) { + let (encoded_call, call_decoder) = bridge_contract::functions::is_known_header::call(id.1); + let call_request = bail_on_arg_error!( + to_value(CallRequest { + to: Some(contract_address), + data: Some(encoded_call.into()), + }) + .map_err(|e| Error::RequestSerialization(e)), + client + ); + let (client, call_result) = + bail_on_error!(call_rpc::(client, "eth_call", Params::Array(vec![call_request]),).await); + match call_decoder.decode(&call_result.0) { + Ok(is_known_block) => (client, Ok((id, is_known_block))), + Err(error) => (client, Err(Error::ResponseParseFailed(format!("{}", error)))), + } +} + +/// Submits Substrate headers to Ethereum contract. +pub async fn submit_substrate_headers( + client: Client, + params: EthereumSigningParams, + contract_address: Address, + headers: Vec, +) -> (Client, Result, Error>) { + let (mut client, mut nonce) = + bail_on_error!(account_nonce(client, params.signer.address().as_fixed_bytes().into()).await); + + let ids = headers.iter().map(|header| header.id()).collect(); + for header in headers { + client = bail_on_error!( + submit_ethereum_transaction( + client, + ¶ms, + Some(contract_address), + Some(nonce), + false, + bridge_contract::functions::import_header::encode_input(header.extract().0.encode(),), + ) + .await + ) + .0; + + nonce += 1.into(); + } + + (client, Ok(ids)) +} + +/// Deploy bridge contract. +pub async fn deploy_bridge_contract( + client: Client, + params: &EthereumSigningParams, + contract_code: Vec, + initial_header: Vec, + initial_set_id: u64, + initial_authorities: Vec, +) -> (Client, Result<(), Error>) { + submit_ethereum_transaction( + client, + params, + None, + None, + false, + bridge_contract::constructor(contract_code, initial_header, initial_set_id, initial_authorities), + ) + .await +} + +/// Submit ethereum transaction. +async fn submit_ethereum_transaction( + client: Client, + params: &EthereumSigningParams, + contract_address: Option
, + nonce: Option, + double_gas: bool, + encoded_call: Vec, +) -> (Client, Result<(), Error>) { + let (client, nonce) = match nonce { + Some(nonce) => (client, nonce), + None => bail_on_error!(account_nonce(client, params.signer.address().as_fixed_bytes().into()).await), + }; + let (client, gas) = bail_on_error!( + estimate_gas( + client, + CallRequest { + to: contract_address, + data: Some(encoded_call.clone().into()), + } + ) + .await + ); + let raw_transaction = ethereum_tx_sign::RawTransaction { + nonce, + to: contract_address, + value: U256::zero(), + gas: if double_gas { gas.saturating_mul(2.into()) } else { gas }, + gas_price: params.gas_price, + data: encoded_call, + } + .sign(¶ms.signer.secret().as_fixed_bytes().into(), ¶ms.chain_id); + let transaction = bail_on_arg_error!( + to_value(Bytes(raw_transaction)).map_err(|e| Error::RequestSerialization(e)), + client + ); + let (client, _) = bail_on_error!( + call_rpc::(client, "eth_submitTransaction", Params::Array(vec![transaction])).await + ); + (client, Ok(())) +} + +/// Get account nonce. +async fn account_nonce(client: Client, caller_address: Address) -> (Client, Result) { + let caller_address = bail_on_arg_error!( + to_value(caller_address).map_err(|e| Error::RequestSerialization(e)), + client + ); + call_rpc(client, "eth_getTransactionCount", Params::Array(vec![caller_address])).await +} + +/// Estimate gas usage for call. +async fn estimate_gas(client: Client, call_request: CallRequest) -> (Client, Result) { + let call_request = bail_on_arg_error!( + to_value(call_request).map_err(|e| Error::RequestSerialization(e)), + client + ); + call_rpc(client, "eth_estimateGas", Params::Array(vec![call_request])).await +} + /// Calls RPC on Ethereum node. async fn call_rpc( mut client: Client, @@ -172,7 +402,7 @@ async fn call_rpc( .ok_or(Error::RequestNotFound)? .await .map_err(Error::ResponseRetrievalFailed)?; - from_value(response).map_err(Error::ResponseParseFailed) + from_value(response).map_err(|e| Error::ResponseParseFailed(format!("{}", e))) } let result = do_call_rpc(&mut client, method, params).await; diff --git a/bridges/relays/ethereum/src/ethereum_deploy_contract.rs b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs new file mode 100644 index 0000000000..eb9f66be92 --- /dev/null +++ b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs @@ -0,0 +1,136 @@ +// 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 . + +use crate::ethereum_client::{self, EthereumConnectionParams, EthereumSigningParams}; +use crate::substrate_client::{self, SubstrateConnectionParams}; +use crate::substrate_types::{Hash as SubstrateHash, Header as SubstrateHeader}; +use codec::{Decode, Encode}; +use num_traits::Zero; + +/// Ethereum synchronization parameters. +#[derive(Debug)] +pub struct EthereumDeployContractParams { + /// Ethereum connection params. + pub eth: EthereumConnectionParams, + /// Ethereum signing params. + pub eth_sign: EthereumSigningParams, + /// Ethereum contract bytecode. + pub eth_contract_code: Vec, + /// Substrate connection params. + pub sub: SubstrateConnectionParams, + /// Initial authorities set id. + pub sub_initial_authorities_set_id: Option, + /// Initial authorities set. + pub sub_initial_authorities_set: Option>, + /// Initial header. + pub sub_initial_header: Option>, +} + +impl Default for EthereumDeployContractParams { + fn default() -> Self { + EthereumDeployContractParams { + eth: Default::default(), + eth_sign: Default::default(), + eth_contract_code: hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")) + .expect("code is hardcoded, thus valid; qed"), + sub: Default::default(), + sub_initial_authorities_set_id: None, + sub_initial_authorities_set: None, + sub_initial_header: None, + } + } +} + +/// Deploy Bridge contract on Ethereum chain. +pub fn run(params: EthereumDeployContractParams) { + let mut local_pool = futures::executor::LocalPool::new(); + + let result = local_pool.run_until(async move { + let eth_client = ethereum_client::client(params.eth); + let sub_client = substrate_client::client(params.sub); + + let (sub_client, initial_header) = prepare_initial_header(sub_client, params.sub_initial_header).await; + let (initial_header_hash, initial_header) = initial_header?; + let initial_set_id = params.sub_initial_authorities_set_id.unwrap_or(0); + let (_, initial_set) = prepare_initial_authorities_set( + sub_client, + initial_header_hash, + params.sub_initial_authorities_set, + ).await; + let initial_set = initial_set?; + + log::info!( + target: "bridge", + "Deploying Ethereum contract.\r\n\tInitial header: {:?}\r\n\tInitial header encoded: {}\r\n\tInitial authorities set ID: {}\r\n\tInitial authorities set: {}", + initial_header, + hex::encode(&initial_header), + initial_set_id, + hex::encode(&initial_set), + ); + + ethereum_client::deploy_bridge_contract( + eth_client, + ¶ms.eth_sign, + params.eth_contract_code, + initial_header, + initial_set_id, + initial_set, + ).await.1.map_err(|error| format!("Error deploying contract: {:?}", error)) + }); + + if let Err(error) = result { + log::error!(target: "bridge", "{}", error); + } +} + +/// Prepare initial header. +async fn prepare_initial_header( + sub_client: substrate_client::Client, + sub_initial_header: Option>, +) -> (substrate_client::Client, Result<(SubstrateHash, Vec), String>) { + match sub_initial_header { + Some(raw_initial_header) => match SubstrateHeader::decode(&mut &raw_initial_header[..]) { + Ok(initial_header) => (sub_client, Ok((initial_header.hash(), raw_initial_header))), + Err(error) => (sub_client, Err(format!("Error decoding initial header: {}", error))), + }, + None => { + let (sub_client, initial_header) = substrate_client::header_by_number(sub_client, Zero::zero()).await; + ( + sub_client, + initial_header + .map(|header| (header.hash(), header.encode())) + .map_err(|error| format!("Error reading Substrate genesis header: {:?}", error)), + ) + } + } +} + +/// Prepare initial GRANDPA authorities set. +async fn prepare_initial_authorities_set( + sub_client: substrate_client::Client, + sub_initial_header_hash: SubstrateHash, + sub_initial_authorities_set: Option>, +) -> (substrate_client::Client, Result, String>) { + let (sub_client, initial_authorities_set) = match sub_initial_authorities_set { + Some(initial_authorities_set) => (sub_client, Ok(initial_authorities_set)), + None => substrate_client::grandpa_authorities_set(sub_client, sub_initial_header_hash).await, + }; + + ( + sub_client, + initial_authorities_set.map_err(|error| format!("Error reading GRANDPA authorities set: {:?}", error)), + ) +} diff --git a/bridges/relays/ethereum/src/ethereum_sync_loop.rs b/bridges/relays/ethereum/src/ethereum_sync_loop.rs index 705d78fe9c..7c1743a59e 100644 --- a/bridges/relays/ethereum/src/ethereum_sync_loop.rs +++ b/bridges/relays/ethereum/src/ethereum_sync_loop.rs @@ -14,57 +14,58 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::ethereum_client; +use crate::ethereum_client::{self, EthereumConnectionParams}; use crate::ethereum_types::{EthereumHeaderId, EthereumHeadersSyncPipeline, Header, QueuedEthereumHeader, Receipt}; -use crate::substrate_client; +use crate::substrate_client::{self, SubstrateConnectionParams, SubstrateSigningParams}; use crate::sync::{HeadersSyncParams, TargetTransactionMode}; use crate::sync_loop::{SourceClient, TargetClient}; use futures::future::FutureExt; use std::{future::Future, pin::Pin}; -pub use web3::types::H256; +use web3::types::H256; /// Interval (in ms) at which we check new Ethereum headers when we are synced/almost synced. const ETHEREUM_TICK_INTERVAL_MS: u64 = 10_000; /// Interval (in ms) at which we check new Substrate blocks. const SUBSTRATE_TICK_INTERVAL_MS: u64 = 5_000; +/// Max number of headers in single submit transaction. +const MAX_HEADERS_IN_SINGLE_SUBMIT: usize = 32; +/// Max total size of headers in single submit transaction. This only affects signed +/// submissions, when several headers are submitted at once. 4096 is the maximal **expected** +/// size of the Ethereum header + transactions receipts (if they're required). +const MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT: usize = MAX_HEADERS_IN_SINGLE_SUBMIT * 4096; +/// Max Ethereum headers we want to have in all 'before-submitted' states. +const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 128; +/// Max Ethereum headers count we want to have in 'submitted' state. +const MAX_SUBMITTED_HEADERS: usize = 128; +/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). +const PRUNE_DEPTH: u32 = 4096; /// Ethereum synchronization parameters. pub struct EthereumSyncParams { - /// Ethereum RPC host. - pub eth_host: String, - /// Ethereum RPC port. - pub eth_port: u16, - /// Substrate RPC host. - pub sub_host: String, - /// Substrate RPC port. - pub sub_port: u16, - /// Substrate transactions signer. - pub sub_signer: sp_core::sr25519::Pair, + /// Ethereum connection params. + pub eth: EthereumConnectionParams, + /// Substrate connection params. + pub sub: SubstrateConnectionParams, + /// Substrate signing params. + pub sub_sign: SubstrateSigningParams, /// Synchronization parameters. pub sync_params: HeadersSyncParams, } -impl std::fmt::Debug for EthereumSyncParams { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("EthereumSyncParams") - .field("eth_host", &self.eth_host) - .field("eth_port", &self.eth_port) - .field("sub_host", &self.sub_port) - .field("sub_port", &self.sub_port) - .field("sync_params", &self.sync_params) - .finish() - } -} - impl Default for EthereumSyncParams { fn default() -> Self { EthereumSyncParams { - eth_host: "localhost".into(), - eth_port: 8545, - sub_host: "localhost".into(), - sub_port: 9933, - sub_signer: sp_keyring::AccountKeyring::Alice.pair(), - sync_params: Default::default(), + eth: Default::default(), + sub: Default::default(), + sub_sign: Default::default(), + 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, + }, } } } @@ -112,10 +113,10 @@ impl SourceClient for EthereumHeadersSource { struct SubstrateHeadersTarget { /// Substrate node client. client: substrate_client::Client, - /// Substrate transactions signer. - signer: sp_core::sr25519::Pair, /// Whether we want to submit signed (true), or unsigned (false) transactions. sign_transactions: bool, + /// Substrate signing params. + sign_params: SubstrateSigningParams, } impl TargetClient for SubstrateHeadersTarget { @@ -126,14 +127,14 @@ impl TargetClient for SubstrateHeadersTarget { type SubmitHeadersFuture = Pin, Self::Error>)>>>; fn best_header_id(self) -> Self::BestHeaderIdFuture { - let (signer, sign_transactions) = (self.signer, self.sign_transactions); + let (sign_transactions, sign_params) = (self.sign_transactions, self.sign_params); substrate_client::best_ethereum_block(self.client) .map(move |(client, result)| { ( SubstrateHeadersTarget { client, - signer, sign_transactions, + sign_params, }, result, ) @@ -142,14 +143,14 @@ impl TargetClient for SubstrateHeadersTarget { } fn is_known_header(self, id: EthereumHeaderId) -> Self::IsKnownHeaderFuture { - let (signer, sign_transactions) = (self.signer, self.sign_transactions); + let (sign_transactions, sign_params) = (self.sign_transactions, self.sign_params); substrate_client::ethereum_header_known(self.client, id) .map(move |(client, result)| { ( SubstrateHeadersTarget { client, - signer, sign_transactions, + sign_params, }, result, ) @@ -161,14 +162,14 @@ impl TargetClient for SubstrateHeadersTarget { // we can minimize number of receipts_check calls by checking header // logs bloom here, but it may give us false positives (when authorities // source is contract, we never need any logs) - let (signer, sign_transactions) = (self.signer, self.sign_transactions); + let (sign_transactions, sign_params) = (self.sign_transactions, self.sign_params); substrate_client::ethereum_receipts_required(self.client, header.clone()) .map(move |(client, result)| { ( SubstrateHeadersTarget { client, - signer, sign_transactions, + sign_params, }, result, ) @@ -177,16 +178,16 @@ impl TargetClient for SubstrateHeadersTarget { } fn submit_headers(self, headers: Vec) -> Self::SubmitHeadersFuture { - let (signer, sign_transactions) = (self.signer, self.sign_transactions); - substrate_client::submit_ethereum_headers(self.client, signer.clone(), headers, sign_transactions) + let (sign_transactions, sign_params) = (self.sign_transactions, self.sign_params); + substrate_client::submit_ethereum_headers(self.client, sign_params.clone(), headers, sign_transactions) .map(move |(client, result)| { ( SubstrateHeadersTarget { client, - signer, sign_transactions, + sign_params, }, - result.map(|(_, submitted_headers)| submitted_headers), + result, ) }) .boxed() @@ -195,12 +196,9 @@ impl TargetClient for SubstrateHeadersTarget { /// Run Ethereum headers synchronization. pub fn run(params: EthereumSyncParams) { - let eth_uri = format!("http://{}:{}", params.eth_host, params.eth_port); - let eth_client = ethereum_client::client(ð_uri); + let eth_client = ethereum_client::client(params.eth); + let sub_client = substrate_client::client(params.sub); - let sub_uri = format!("http://{}:{}", params.sub_host, params.sub_port); - let sub_client = substrate_client::client(&sub_uri); - let sub_signer = params.sub_signer; let sign_sub_transactions = match params.sync_params.target_tx_mode { TargetTransactionMode::Signed | TargetTransactionMode::Backup => true, TargetTransactionMode::Unsigned => false, @@ -211,8 +209,8 @@ pub fn run(params: EthereumSyncParams) { ETHEREUM_TICK_INTERVAL_MS, SubstrateHeadersTarget { client: sub_client, - signer: sub_signer, sign_transactions: sign_sub_transactions, + sign_params: params.sub_sign, }, SUBSTRATE_TICK_INTERVAL_MS, params.sync_params, diff --git a/bridges/relays/ethereum/src/ethereum_types.rs b/bridges/relays/ethereum/src/ethereum_types.rs index 93c47da4f7..d996ac7c29 100644 --- a/bridges/relays/ethereum/src/ethereum_types.rs +++ b/bridges/relays/ethereum/src/ethereum_types.rs @@ -18,7 +18,7 @@ use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethe use crate::sync_types::{HeaderId, HeadersSyncPipeline, QueuedHeader, SourceHeader}; use codec::Encode; -pub use web3::types::{Bytes, H256, U128, U64}; +pub use web3::types::{Address, Bytes, H256, U128, U256, U64}; /// When header is just received from the Ethereum node, we check that it has /// both number and hash fields filled. @@ -28,6 +28,9 @@ pub const HEADER_ID_PROOF: &'static str = "checked on retrieval; qed"; /// gas_used field filled. pub const RECEIPT_GAS_USED_PROOF: &'static str = "checked on retrieval; qed"; +/// Ethereum transaction hash type. +pub type TransactionHash = H256; + /// Ethereum header type. pub type Header = web3::types::Block; diff --git a/bridges/relays/ethereum/src/headers.rs b/bridges/relays/ethereum/src/headers.rs index 841743ce1d..0e34b9a98f 100644 --- a/bridges/relays/ethereum/src/headers.rs +++ b/bridges/relays/ethereum/src/headers.rs @@ -117,7 +117,10 @@ impl QueuedHeaders

{ self.maybe_extra.keys().next_back().cloned().unwrap_or_else(Zero::zero), std::cmp::max( self.extra.keys().next_back().cloned().unwrap_or_else(Zero::zero), - self.ready.keys().next_back().cloned().unwrap_or_else(Zero::zero), + std::cmp::max( + self.ready.keys().next_back().cloned().unwrap_or_else(Zero::zero), + self.submitted.keys().next_back().cloned().unwrap_or_else(Zero::zero), + ), ), ), ), @@ -226,6 +229,7 @@ impl QueuedHeaders

{ // all ancestors of this header are now synced => let's remove them from // queues let mut current = *id; + let mut id_processed = false; loop { let header = match self.status(¤t) { HeaderStatus::Unknown => break, @@ -253,22 +257,26 @@ impl QueuedHeaders

{ .entry(current.1) .or_insert(HeaderStatus::Synced) = HeaderStatus::Synced; current = header.parent_id(); + id_processed = true; } // remember that the header is synced - log::debug!( - target: "bridge", - "{} header {:?} is now {:?}", - P::SOURCE_NAME, - id, - HeaderStatus::Synced, - ); - *self - .known_headers - .entry(id.0) - .or_default() - .entry(id.1) - .or_insert(HeaderStatus::Synced) = HeaderStatus::Synced; + if !id_processed { + // to avoid duplicate log message + log::debug!( + target: "bridge", + "{} header {:?} is now {:?}", + P::SOURCE_NAME, + id, + HeaderStatus::Synced, + ); + *self + .known_headers + .entry(id.0) + .or_default() + .entry(id.1) + .or_insert(HeaderStatus::Synced) = HeaderStatus::Synced; + } // now let's move all descendants from maybe_orphan && orphan queues to // maybe_extra queue diff --git a/bridges/relays/ethereum/src/main.rs b/bridges/relays/ethereum/src/main.rs index 8d897d51e4..ae46468f8f 100644 --- a/bridges/relays/ethereum/src/main.rs +++ b/bridges/relays/ethereum/src/main.rs @@ -17,17 +17,25 @@ #![recursion_limit = "1024"] mod ethereum_client; +mod ethereum_deploy_contract; mod ethereum_sync_loop; mod ethereum_types; mod headers; mod substrate_client; +mod substrate_sync_loop; mod substrate_types; mod sync; mod sync_loop; mod sync_types; +mod utils; +use ethereum_client::{EthereumConnectionParams, EthereumSigningParams}; +use ethereum_sync_loop::EthereumSyncParams; +use parity_crypto::publickey::{KeyPair, Secret}; use sp_core::crypto::Pair; use std::io::Write; +use substrate_client::{SubstrateConnectionParams, SubstrateSigningParams}; +use substrate_sync_loop::SubstrateSyncParams; fn main() { initialize(); @@ -44,6 +52,24 @@ fn main() { } }); } + ("sub-to-eth", Some(sub_to_eth_matches)) => { + 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; + } + }); + } + ("eth-deploy-contract", Some(eth_deploy_matches)) => { + ethereum_deploy_contract::run(match ethereum_deploy_contract_params(ð_deploy_matches) { + Ok(ethereum_deploy_matches) => ethereum_deploy_matches, + Err(err) => { + log::error!(target: "bridge", "Error parsing parameters: {}", err); + return; + } + }); + } ("", _) => { log::error!(target: "bridge", "No subcommand specified"); return; @@ -89,25 +115,58 @@ fn initialize() { builder.init(); } -fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result { - let mut eth_sync_params = ethereum_sync_loop::EthereumSyncParams::default(); +fn ethereum_connection_params(matches: &clap::ArgMatches) -> Result { + let mut params = EthereumConnectionParams::default(); if let Some(eth_host) = matches.value_of("eth-host") { - eth_sync_params.eth_host = eth_host.into(); + params.host = eth_host.into(); } if let Some(eth_port) = matches.value_of("eth-port") { - eth_sync_params.eth_port = eth_port.parse().map_err(|e| format!("{}", e))?; + 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 = eth_signer + .parse::() + .map_err(|e| format!("Failed to parse eth-signer: {}", e)) + .and_then(|secret| KeyPair::from_secret(secret).map_err(|e| format!("Invalid eth-signer: {}", 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") { - eth_sync_params.sub_host = sub_host.into(); + params.host = sub_host.into(); } if let Some(sub_port) = matches.value_of("sub-port") { - eth_sync_params.sub_port = sub_port.parse().map_err(|e| format!("{}", e))?; + params.port = sub_port + .parse() + .map_err(|e| format!("Failed to parse sub-port: {}", e))?; } + Ok(params) +} + +fn substrate_signing_params(matches: &clap::ArgMatches) -> Result { + let mut params = SubstrateSigningParams::default(); if let Some(sub_signer) = matches.value_of("sub-signer") { let sub_signer_password = matches.value_of("sub-signer-password"); - eth_sync_params.sub_signer = - sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password).map_err(|e| format!("{:?}", e))?; + params.signer = 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 { + let mut eth_sync_params = EthereumSyncParams::default(); + eth_sync_params.eth = ethereum_connection_params(matches)?; + eth_sync_params.sub = substrate_connection_params(matches)?; + eth_sync_params.sub_sign = substrate_signing_params(matches)?; match matches.value_of("sub-tx-mode") { Some("signed") => eth_sync_params.sync_params.target_tx_mode = sync::TargetTransactionMode::Signed, @@ -124,3 +183,32 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result Result { + let mut sub_sync_params = SubstrateSyncParams::default(); + sub_sync_params.eth = ethereum_connection_params(matches)?; + sub_sync_params.eth_sign = ethereum_signing_params(matches)?; + sub_sync_params.sub = substrate_connection_params(matches)?; + + if let Some(eth_contract) = matches.value_of("eth-contract") { + sub_sync_params.eth_contract_address = eth_contract.parse().map_err(|e| format!("{}", e))?; + } + + Ok(sub_sync_params) +} + +fn ethereum_deploy_contract_params( + matches: &clap::ArgMatches, +) -> Result { + let mut eth_deploy_params = ethereum_deploy_contract::EthereumDeployContractParams::default(); + eth_deploy_params.eth = ethereum_connection_params(matches)?; + eth_deploy_params.eth_sign = ethereum_signing_params(matches)?; + eth_deploy_params.sub = substrate_connection_params(matches)?; + + if let Some(eth_contract_code) = matches.value_of("eth-contract-code") { + eth_deploy_params.eth_contract_code = + hex::decode(ð_contract_code).map_err(|e| format!("Failed to parse eth-contract-code: {}", e))?; + } + + Ok(eth_deploy_params) +} diff --git a/bridges/relays/ethereum/src/substrate_client.rs b/bridges/relays/ethereum/src/substrate_client.rs index 40a9047c5e..3d74dec250 100644 --- a/bridges/relays/ethereum/src/substrate_client.rs +++ b/bridges/relays/ethereum/src/substrate_client.rs @@ -15,16 +15,60 @@ // along with Parity Bridges Common. If not, see . use crate::ethereum_types::{Bytes, EthereumHeaderId, QueuedEthereumHeader, H256}; -use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts, TransactionHash}; +use crate::substrate_types::{ + into_substrate_ethereum_header, into_substrate_ethereum_receipts, Hash, Header as SubstrateHeader, Number, +}; use crate::sync_types::{HeaderId, MaybeConnectionError, SourceHeader}; +use crate::{bail_on_arg_error, bail_on_error}; use codec::{Decode, Encode}; use jsonrpsee::common::Params; use jsonrpsee::raw::{RawClient, RawClientError}; use jsonrpsee::transport::http::{HttpTransportClient, RequestError}; -use serde_json::{from_value, to_value}; +use num_traits::Zero; +use serde::de::DeserializeOwned; +use serde_json::{from_value, to_value, Value}; use sp_core::crypto::Pair; use sp_runtime::traits::IdentifyAccount; +/// Substrate connection params. +#[derive(Debug)] +pub struct SubstrateConnectionParams { + /// Substrate RPC host. + pub host: String, + /// Substrate RPC port. + pub port: u16, +} + +impl Default for SubstrateConnectionParams { + fn default() -> Self { + SubstrateConnectionParams { + host: "localhost".into(), + port: 9933, + } + } +} + +/// Substrate signing params. +#[derive(Clone)] +pub struct SubstrateSigningParams { + /// Substrate transactions signer. + pub signer: sp_core::sr25519::Pair, +} + +impl std::fmt::Debug for SubstrateSigningParams { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.signer.public()) + } +} + +impl Default for SubstrateSigningParams { + fn default() -> Self { + SubstrateSigningParams { + signer: sp_keyring::AccountKeyring::Alice.pair(), + } + } +} + /// Substrate client type. pub struct Client { /// Substrate RPC client. @@ -38,6 +82,8 @@ pub struct Client { pub enum Error { /// Request start failed. StartRequestFailed(RequestError), + /// Error serializing request. + RequestSerialization(serde_json::Error), /// Request not found (should never occur?). RequestNotFound, /// Failed to receive response. @@ -56,23 +102,42 @@ impl MaybeConnectionError for Error { } /// Returns client that is able to call RPCs on Substrate node. -pub fn client(uri: &str) -> Client { - let transport = HttpTransportClient::new(uri); +pub fn client(params: SubstrateConnectionParams) -> Client { + let uri = format!("http://{}:{}", params.host, params.port); + let transport = HttpTransportClient::new(&uri); Client { rpc_client: RawClient::new(transport), genesis_hash: None, } } +/// Returns best Substrate header. +pub async fn best_header(client: Client) -> (Client, Result) { + call_rpc(client, "chain_getHeader", Params::None, rpc_returns_value).await +} + +/// Returns Substrate header by hash. +pub async fn header_by_hash(client: Client, hash: Hash) -> (Client, Result) { + let hash = bail_on_arg_error!(to_value(hash).map_err(|e| Error::RequestSerialization(e)), client); + call_rpc(client, "chain_getHeader", Params::Array(vec![hash]), rpc_returns_value).await +} + +/// Returns Substrate header by number. +pub async fn header_by_number(client: Client, number: Number) -> (Client, Result) { + let (client, hash) = bail_on_error!(block_hash_by_number(client, number).await); + header_by_hash(client, hash).await +} + /// Returns best Ethereum block that Substrate runtime knows of. pub async fn best_ethereum_block(client: Client) -> (Client, Result) { - let (client, result) = call_rpc::<(u64, H256)>( + let (client, result) = call_rpc( client, "state_call", Params::Array(vec![ - to_value("EthereumHeadersApi_best_block").unwrap(), - to_value("0x").unwrap(), + serde_json::Value::String("EthereumHeadersApi_best_block".into()), + serde_json::Value::String("0x".into()), ]), + rpc_returns_encoded_value, ) .await; (client, result.map(|(num, hash)| HeaderId(num, hash))) @@ -85,14 +150,18 @@ pub async fn ethereum_receipts_required( ) -> (Client, Result<(EthereumHeaderId, bool), Error>) { let id = header.header().id(); let header = into_substrate_ethereum_header(header.header()); - let encoded_header = header.encode(); + let encoded_header = bail_on_arg_error!( + to_value(Bytes(header.encode())).map_err(|e| Error::RequestSerialization(e)), + client + ); let (client, receipts_required) = call_rpc( client, "state_call", Params::Array(vec![ - to_value("EthereumHeadersApi_is_import_requires_receipts").unwrap(), - to_value(Bytes(encoded_header)).unwrap(), + serde_json::Value::String("EthereumHeadersApi_is_import_requires_receipts".into()), + encoded_header, ]), + rpc_returns_encoded_value, ) .await; ( @@ -112,14 +181,18 @@ pub async fn ethereum_header_known( // But when we'll read best header from Substrate next time, we will know that // there's a better header => this Orphan will either be marked as synced, or // eventually pruned. - let encoded_id = id.1.encode(); + let encoded_id = bail_on_arg_error!( + to_value(Bytes(id.1.encode())).map_err(|e| Error::RequestSerialization(e)), + client + ); let (client, is_known_block) = call_rpc( client, "state_call", Params::Array(vec![ - to_value("EthereumHeadersApi_is_known_block").unwrap(), - to_value(Bytes(encoded_id)).unwrap(), + serde_json::Value::String("EthereumHeadersApi_is_known_block".into()), + encoded_id, ]), + rpc_returns_encoded_value, ) .await; (client, is_known_block.map(|is_known_block| (id, is_known_block))) @@ -128,12 +201,12 @@ pub async fn ethereum_header_known( /// Submits Ethereum header to Substrate runtime. pub async fn submit_ethereum_headers( client: Client, - signer: sp_core::sr25519::Pair, + params: SubstrateSigningParams, headers: Vec, sign_transactions: bool, -) -> (Client, Result<(Vec, Vec), Error>) { +) -> (Client, Result, Error>) { match sign_transactions { - true => submit_signed_ethereum_headers(client, signer, headers).await, + true => submit_signed_ethereum_headers(client, params, headers).await, false => submit_unsigned_ethereum_headers(client, headers).await, } } @@ -141,78 +214,89 @@ pub async fn submit_ethereum_headers( /// Submits signed Ethereum header to Substrate runtime. pub async fn submit_signed_ethereum_headers( client: Client, - signer: sp_core::sr25519::Pair, + params: SubstrateSigningParams, headers: Vec, -) -> (Client, Result<(Vec, Vec), Error>) { +) -> (Client, Result, Error>) { let ids = headers.iter().map(|header| header.id()).collect(); let (client, genesis_hash) = match client.genesis_hash { Some(genesis_hash) => (client, genesis_hash), None => { - let (mut client, genesis_hash) = block_hash_by_number(client, 0).await; - let genesis_hash = match genesis_hash { - Ok(genesis_hash) => genesis_hash, - Err(err) => return (client, Err(err)), - }; + let (mut client, genesis_hash) = bail_on_error!(block_hash_by_number(client, Zero::zero()).await); client.genesis_hash = Some(genesis_hash); (client, genesis_hash) } }; - let account_id = signer.public().as_array_ref().clone().into(); - let (client, nonce) = next_account_index(client, account_id).await; - let nonce = match nonce { - Ok(nonce) => nonce, - Err(err) => return (client, Err(err)), - }; + let account_id = params.signer.public().as_array_ref().clone().into(); + let (client, nonce) = bail_on_error!(next_account_index(client, account_id).await); - let transaction = create_signed_submit_transaction(headers, &signer, nonce, genesis_hash); - let encoded_transaction = transaction.encode(); - let (client, transaction_hash) = call_rpc( + let transaction = create_signed_submit_transaction(headers, ¶ms.signer, nonce, genesis_hash); + let encoded_transaction = bail_on_arg_error!( + to_value(Bytes(transaction.encode())).map_err(|e| Error::RequestSerialization(e)), + client + ); + let (client, _) = call_rpc( client, "author_submitExtrinsic", - Params::Array(vec![to_value(Bytes(encoded_transaction)).unwrap()]), + Params::Array(vec![encoded_transaction]), + |_| Ok(()), ) .await; - ( - client, - transaction_hash.map(|transaction_hash| (vec![transaction_hash], ids)), - ) + (client, Ok(ids)) } /// Submits unsigned Ethereum header to Substrate runtime. pub async fn submit_unsigned_ethereum_headers( mut client: Client, headers: Vec, -) -> (Client, Result<(Vec, Vec), Error>) { +) -> (Client, Result, Error>) { let ids = headers.iter().map(|header| header.id()).collect(); - let mut transactions_hashes = Vec::new(); for header in headers { let transaction = create_unsigned_submit_transaction(header); - let encoded_transaction = transaction.encode(); - let (used_client, transaction_hash) = call_rpc( - client, - "author_submitExtrinsic", - Params::Array(vec![to_value(Bytes(encoded_transaction)).unwrap()]), - ) - .await; + let encoded_transaction = bail_on_arg_error!( + to_value(Bytes(transaction.encode())).map_err(|e| Error::RequestSerialization(e)), + client + ); + let (used_client, _) = bail_on_error!( + call_rpc( + client, + "author_submitExtrinsic", + Params::Array(vec![encoded_transaction]), + |_| Ok(()), + ) + .await + ); client = used_client; - transactions_hashes.push(match transaction_hash { - Ok(transaction_hash) => transaction_hash, - Err(error) => return (client, Err(error)), - }); } - (client, Ok((transactions_hashes, ids))) + (client, Ok(ids)) +} + +/// Get GRANDPA authorities set at given block. +pub async fn grandpa_authorities_set(client: Client, block: Hash) -> (Client, Result, Error>) { + let block = bail_on_arg_error!(to_value(block).map_err(|e| Error::RequestSerialization(e)), client); + call_rpc( + client, + "state_call", + Params::Array(vec![ + serde_json::Value::String("GrandpaApi_grandpa_authorities".into()), + block, + ]), + rpc_returns_bytes, + ) + .await } /// Get Substrate block hash by its number. -async fn block_hash_by_number(client: Client, number: u64) -> (Client, Result) { +async fn block_hash_by_number(client: Client, number: Number) -> (Client, Result) { + let number = bail_on_arg_error!(to_value(number).map_err(|e| Error::RequestSerialization(e)), client); call_rpc( client, "chain_getBlockHash", - Params::Array(vec![to_value(number).unwrap()]), + Params::Array(vec![number]), + rpc_returns_value, ) .await } @@ -224,18 +308,30 @@ async fn next_account_index( ) -> (Client, Result) { use sp_core::crypto::Ss58Codec; - let (client, index) = call_rpc_u64( - client, - "system_accountNextIndex", - Params::Array(vec![to_value(account.to_ss58check()).unwrap()]), - ) + let account = bail_on_arg_error!( + to_value(account.to_ss58check()).map_err(|e| Error::RequestSerialization(e)), + client + ); + let (client, index) = call_rpc(client, "system_accountNextIndex", Params::Array(vec![account]), |v| { + rpc_returns_value::(v) + }) .await; (client, index.map(|index| index as _)) } /// Calls RPC on Substrate node that returns Bytes. -async fn call_rpc(mut client: Client, method: &'static str, params: Params) -> (Client, Result) { - async fn do_call_rpc(client: &mut Client, method: &'static str, params: Params) -> Result { +async fn call_rpc( + mut client: Client, + method: &'static str, + params: Params, + decode_value: impl Fn(Value) -> Result, +) -> (Client, Result) { + async fn do_call_rpc( + client: &mut Client, + method: &'static str, + params: Params, + decode_value: impl Fn(Value) -> Result, + ) -> Result { let request_id = client .rpc_client .start_request(method, params) @@ -249,34 +345,10 @@ async fn call_rpc(mut client: Client, method: &'static str, params: P .ok_or(Error::RequestNotFound)? .await .map_err(Error::ResponseRetrievalFailed)?; - let encoded_response: Bytes = from_value(response).map_err(|_| Error::ResponseParseFailed)?; - Decode::decode(&mut &encoded_response.0[..]).map_err(|_| Error::ResponseParseFailed) + decode_value(response) } - let result = do_call_rpc(&mut client, method, params).await; - (client, result) -} - -/// Calls RPC on Substrate node that returns u64. -async fn call_rpc_u64(mut client: Client, method: &'static str, params: Params) -> (Client, Result) { - async fn do_call_rpc(client: &mut Client, method: &'static str, params: Params) -> Result { - let request_id = client - .rpc_client - .start_request(method, params) - .await - .map_err(Error::StartRequestFailed)?; - // WARN: if there'll be need for executing >1 request at a time, we should avoid - // calling request_by_id - let response = client - .rpc_client - .request_by_id(request_id) - .ok_or(Error::RequestNotFound)? - .await - .map_err(Error::ResponseRetrievalFailed)?; - response.as_u64().ok_or(Error::ResponseParseFailed) - } - - let result = do_call_rpc(&mut client, method, params).await; + let result = do_call_rpc(&mut client, method, params, decode_value).await; (client, result) } @@ -341,3 +413,20 @@ fn create_unsigned_submit_transaction(header: QueuedEthereumHeader) -> bridge_no bridge_node_runtime::UncheckedExtrinsic::new_unsigned(function) } + +/// When RPC method returns encoded value. +fn rpc_returns_encoded_value(value: Value) -> Result { + let encoded_response: Bytes = from_value(value).map_err(|_| Error::ResponseParseFailed)?; + Decode::decode(&mut &encoded_response.0[..]).map_err(|_| Error::ResponseParseFailed) +} + +/// When RPC method returns value. +fn rpc_returns_value(value: Value) -> Result { + from_value(value).map_err(|_| Error::ResponseParseFailed) +} + +/// When RPC method returns raw bytes. +fn rpc_returns_bytes(value: Value) -> Result, Error> { + let encoded_response: Bytes = from_value(value).map_err(|_| Error::ResponseParseFailed)?; + Ok(encoded_response.0) +} diff --git a/bridges/relays/ethereum/src/substrate_sync_loop.rs b/bridges/relays/ethereum/src/substrate_sync_loop.rs new file mode 100644 index 0000000000..2c194dac01 --- /dev/null +++ b/bridges/relays/ethereum/src/substrate_sync_loop.rs @@ -0,0 +1,202 @@ +// 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 . + +use crate::ethereum_client::{self, EthereumConnectionParams, EthereumSigningParams}; +use crate::ethereum_types::Address; +use crate::substrate_client::{self, SubstrateConnectionParams}; +use crate::substrate_types::{ + Hash, Header, Number, QueuedSubstrateHeader, SubstrateHeaderId, SubstrateHeadersSyncPipeline, +}; +use crate::sync::{HeadersSyncParams, TargetTransactionMode}; +use crate::sync_loop::{SourceClient, TargetClient}; +use crate::sync_types::SourceHeader; +use futures::future::{ready, FutureExt, Ready}; +use std::{future::Future, pin::Pin}; + +/// Interval (in ms) at which we check new Substrate headers when we are synced/almost synced. +const SUBSTRATE_TICK_INTERVAL_MS: u64 = 10_000; +/// Interval (in ms) at which we check new Ethereum blocks. +const ETHEREUM_TICK_INTERVAL_MS: u64 = 5_000; +/// Max Ethereum headers we want to have in all 'before-submitted' states. +const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 8; +/// Max Ethereum headers count we want to have in 'submitted' state. +const MAX_SUBMITTED_HEADERS: usize = 4; +/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). +const PRUNE_DEPTH: u32 = 256; + +/// Substrate synchronization parameters. +#[derive(Debug)] +pub struct SubstrateSyncParams { + /// Ethereum connection params. + pub eth: EthereumConnectionParams, + /// Ethereum signing params. + pub eth_sign: EthereumSigningParams, + /// Ethereum bridge contract address. + pub eth_contract_address: Address, + /// Substrate connection params. + pub sub: SubstrateConnectionParams, + /// Synchronization parameters. + pub sync_params: HeadersSyncParams, +} + +impl Default for SubstrateSyncParams { + fn default() -> Self { + SubstrateSyncParams { + eth: Default::default(), + eth_sign: Default::default(), + // the address 0x731a10897d267e19b34503ad902d0a29173ba4b1 is the address + // of the contract that is deployed by default signer and 0 nonce + eth_contract_address: "731a10897d267e19b34503ad902d0a29173ba4b1" + .parse() + .expect("address is hardcoded, thus valid; qed"), + sub: Default::default(), + sync_params: HeadersSyncParams { + max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, + max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, + // since we always have single Substrate header in separate Ethereum transaction, + // all max_**_in_single_submit aren't important here + max_headers_in_single_submit: 4, + max_headers_size_in_single_submit: std::usize::MAX, + prune_depth: PRUNE_DEPTH, + target_tx_mode: TargetTransactionMode::Signed, + }, + } + } +} + +/// Substrate client as headers source. +struct SubstrateHeadersSource { + /// Substrate node client. + client: substrate_client::Client, +} + +impl SourceClient for SubstrateHeadersSource { + type Error = substrate_client::Error; + type BestBlockNumberFuture = Pin)>>>; + type HeaderByHashFuture = Pin)>>>; + type HeaderByNumberFuture = Pin)>>>; + type HeaderExtraFuture = Ready<(Self, Result<(SubstrateHeaderId, ()), Self::Error>)>; + + fn best_block_number(self) -> Self::BestBlockNumberFuture { + substrate_client::best_header(self.client) + .map(|(client, result)| (SubstrateHeadersSource { client }, result.map(|header| header.number))) + .boxed() + } + + fn header_by_hash(self, hash: Hash) -> Self::HeaderByHashFuture { + substrate_client::header_by_hash(self.client, hash) + .map(|(client, result)| (SubstrateHeadersSource { client }, result)) + .boxed() + } + + fn header_by_number(self, number: Number) -> Self::HeaderByNumberFuture { + substrate_client::header_by_number(self.client, number) + .map(|(client, result)| (SubstrateHeadersSource { client }, result)) + .boxed() + } + + fn header_extra(self, id: SubstrateHeaderId, _header: &Header) -> Self::HeaderExtraFuture { + ready((self, Ok((id, ())))) + } +} + +/// Ethereum client as Substrate headers target. +struct EthereumHeadersTarget { + /// Ethereum node client. + client: ethereum_client::Client, + /// Bridge contract address. + contract: Address, + /// Ethereum signing params. + sign_params: EthereumSigningParams, +} + +impl TargetClient for EthereumHeadersTarget { + type Error = ethereum_client::Error; + type BestHeaderIdFuture = Pin)>>>; + type IsKnownHeaderFuture = Pin)>>>; + type RequiresExtraFuture = Ready<(Self, Result<(SubstrateHeaderId, bool), Self::Error>)>; + type SubmitHeadersFuture = Pin, Self::Error>)>>>; + + fn best_header_id(self) -> Self::BestHeaderIdFuture { + let (contract, sign_params) = (self.contract, self.sign_params); + ethereum_client::best_substrate_block(self.client, contract) + .map(move |(client, result)| { + ( + EthereumHeadersTarget { + client, + contract, + sign_params, + }, + result, + ) + }) + .boxed() + } + + fn is_known_header(self, id: SubstrateHeaderId) -> Self::IsKnownHeaderFuture { + let (contract, sign_params) = (self.contract, self.sign_params); + ethereum_client::substrate_header_known(self.client, contract, id) + .map(move |(client, result)| { + ( + EthereumHeadersTarget { + client, + contract, + sign_params, + }, + result, + ) + }) + .boxed() + } + + fn requires_extra(self, header: &QueuedSubstrateHeader) -> Self::RequiresExtraFuture { + ready((self, Ok((header.header().id(), false)))) + } + + fn submit_headers(self, headers: Vec) -> Self::SubmitHeadersFuture { + let (contract, sign_params) = (self.contract, self.sign_params); + ethereum_client::submit_substrate_headers(self.client, sign_params.clone(), contract, headers) + .map(move |(client, result)| { + ( + EthereumHeadersTarget { + client, + contract, + sign_params, + }, + result, + ) + }) + .boxed() + } +} + +/// Run Substrate headers synchronization. +pub fn run(params: SubstrateSyncParams) { + let eth_client = ethereum_client::client(params.eth); + let sub_client = substrate_client::client(params.sub); + + crate::sync_loop::run( + SubstrateHeadersSource { client: sub_client }, + SUBSTRATE_TICK_INTERVAL_MS, + EthereumHeadersTarget { + client: eth_client, + contract: params.eth_contract_address, + sign_params: params.eth_sign, + }, + ETHEREUM_TICK_INTERVAL_MS, + params.sync_params, + ); +} diff --git a/bridges/relays/ethereum/src/substrate_types.rs b/bridges/relays/ethereum/src/substrate_types.rs index f056b71011..d657f0269d 100644 --- a/bridges/relays/ethereum/src/substrate_types.rs +++ b/bridges/relays/ethereum/src/substrate_types.rs @@ -14,16 +14,61 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -pub use crate::ethereum_types::H256 as TransactionHash; use crate::ethereum_types::{ Header as EthereumHeader, Receipt as EthereumReceipt, HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF, RECEIPT_GAS_USED_PROOF as ETHEREUM_RECEIPT_GAS_USED_PROOF, }; +use crate::sync_types::{HeaderId, HeadersSyncPipeline, QueuedHeader, SourceHeader}; +use codec::Encode; pub use sp_bridge_eth_poa::{ Address, Bloom, Bytes, Header as SubstrateEthereumHeader, LogEntry as SubstrateEthereumLogEntry, Receipt as SubstrateEthereumReceipt, TransactionOutcome as SubstrateEthereumTransactionOutcome, H256, U256, }; +/// Substrate header hash. +pub type Hash = bridge_node_runtime::Hash; + +/// Substrate header number. +pub type Number = bridge_node_runtime::BlockNumber; + +/// Substrate header type. +pub type Header = bridge_node_runtime::Header; + +/// Substrate header ID. +pub type SubstrateHeaderId = HeaderId; + +/// Queued substrate header ID. +pub type QueuedSubstrateHeader = QueuedHeader; + +/// Substrate synchronization pipeline. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct SubstrateHeadersSyncPipeline; + +impl HeadersSyncPipeline for SubstrateHeadersSyncPipeline { + const SOURCE_NAME: &'static str = "Substrate"; + const TARGET_NAME: &'static str = "Ethereum"; + + type Hash = bridge_node_runtime::Hash; + type Number = bridge_node_runtime::BlockNumber; + type Header = Header; + type Extra = (); + + fn estimate_size(source: &QueuedHeader) -> usize { + source.header().encode().len() + } +} + +impl SourceHeader for Header { + fn id(&self) -> SubstrateHeaderId { + HeaderId(self.number, self.hash()) + } + + fn parent_id(&self) -> SubstrateHeaderId { + HeaderId(self.number - 1, self.parent_hash) + } +} + /// Convert Ethereum header into Ethereum header for Substrate. pub fn into_substrate_ethereum_header(header: &EthereumHeader) -> SubstrateEthereumHeader { SubstrateEthereumHeader { diff --git a/bridges/relays/ethereum/src/sync.rs b/bridges/relays/ethereum/src/sync.rs index 768a7a7932..e1fc372e14 100644 --- a/bridges/relays/ethereum/src/sync.rs +++ b/bridges/relays/ethereum/src/sync.rs @@ -204,19 +204,6 @@ impl HeadersSync

{ } } -impl Default for HeadersSyncParams { - fn default() -> Self { - HeadersSyncParams { - max_future_headers_to_download: 128, - max_headers_in_submitted_status: 128, - max_headers_in_single_submit: 32, - max_headers_size_in_single_submit: 131_072, - prune_depth: 4096, - target_tx_mode: TargetTransactionMode::Signed, - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -228,9 +215,20 @@ mod tests { H256::from_low_u64_le(1000 + number) } + fn default_sync_params() -> HeadersSyncParams { + HeadersSyncParams { + max_future_headers_to_download: 128, + max_headers_in_submitted_status: 128, + max_headers_in_single_submit: 32, + max_headers_size_in_single_submit: 131_072, + prune_depth: 4096, + target_tx_mode: TargetTransactionMode::Signed, + } + } + #[test] fn select_new_header_to_download_works() { - let mut eth_sync = HeadersSync::::new(Default::default()); + let mut eth_sync = HeadersSync::::new(default_sync_params()); // both best && target headers are unknown assert_eq!(eth_sync.select_new_header_to_download(), None); @@ -261,7 +259,7 @@ mod tests { #[test] fn sync_without_reorgs_works() { - let mut eth_sync = HeadersSync::new(Default::default()); + let mut eth_sync = HeadersSync::new(default_sync_params()); eth_sync.params.max_headers_in_submitted_status = 1; // ethereum reports best header #102 @@ -310,7 +308,7 @@ mod tests { #[test] fn sync_with_orphan_headers_work() { - let mut eth_sync = HeadersSync::new(Default::default()); + let mut eth_sync = HeadersSync::new(default_sync_params()); // ethereum reports best header #102 eth_sync.source_best_header_number_response(102); @@ -359,7 +357,7 @@ mod tests { #[test] fn pruning_happens_on_target_best_header_response() { - let mut eth_sync = HeadersSync::::new(Default::default()); + let mut eth_sync = HeadersSync::::new(default_sync_params()); eth_sync.params.prune_depth = 50; eth_sync.target_best_header_response(id(100)); assert_eq!(eth_sync.headers.prune_border(), 50); @@ -367,7 +365,7 @@ mod tests { #[test] fn only_submitting_headers_in_backup_mode_when_stalled() { - let mut eth_sync = HeadersSync::new(Default::default()); + let mut eth_sync = HeadersSync::new(default_sync_params()); eth_sync.params.target_tx_mode = TargetTransactionMode::Backup; // ethereum reports best header #102 diff --git a/bridges/relays/ethereum/src/sync_loop.rs b/bridges/relays/ethereum/src/sync_loop.rs index 803cfaf0c2..b2e97c9794 100644 --- a/bridges/relays/ethereum/src/sync_loop.rs +++ b/bridges/relays/ethereum/src/sync_loop.rs @@ -17,7 +17,7 @@ use crate::sync::HeadersSyncParams; use crate::sync_types::{HeaderId, HeaderStatus, HeadersSyncPipeline, MaybeConnectionError, QueuedHeader}; use futures::{future::FutureExt, stream::StreamExt}; -use num_traits::Saturating; +use num_traits::{Saturating, Zero}; use std::future::Future; /// When we submit headers to target node, but see no updates of best @@ -365,6 +365,17 @@ pub fn run( // for Orphan we actually ask for parent' header let parent_id = header.parent_id(); + // if we have end up with orphan header#0, then we are misconfigured + if parent_id.0.is_zero() { + log::error!( + target: "bridge", + "Misconfiguration. Genesis {} header is considered orphan by {} node", + P::SOURCE_NAME, + P::TARGET_NAME, + ); + return; + } + log::debug!( target: "bridge", "Going to download orphan header from {} node: {:?}", diff --git a/bridges/relays/ethereum/src/utils.rs b/bridges/relays/ethereum/src/utils.rs new file mode 100644 index 0000000000..4f022c8047 --- /dev/null +++ b/bridges/relays/ethereum/src/utils.rs @@ -0,0 +1,37 @@ +// 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 . + +/// Macro that returns (client, Err(error)) tuple from function if result is Err(error). +#[macro_export] +macro_rules! bail_on_error { + ($result: expr) => { + match $result { + (client, Ok(result)) => (client, result), + (client, Err(error)) => return (client, Err(error)), + } + }; +} + +/// Macro that returns (client, Err(error)) tuple from function if result is Err(error). +#[macro_export] +macro_rules! bail_on_arg_error { + ($result: expr, $client: ident) => { + match $result { + Ok(result) => result, + Err(error) => return ($client, Err(error)), + } + }; +}