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<u8> justification)

* fixed cli option description

* fix typos

* fix grumble

* extracted constants

* log decoded header

* cargo fmt --all

* comment
This commit is contained in:
Svyatoslav Nikolsky
2020-04-29 10:09:13 +03:00
committed by Bastian Köcher
parent 50d6ed186f
commit 3d15ac7c90
19 changed files with 1347 additions and 193 deletions
@@ -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 <admin@parity.io>"]
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" }
@@ -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 <http://www.gnu.org/licenses/>.
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<ValidatorsSetSignal>,
}
/// GRANDPA validators set change signal.
#[derive(Debug)]
pub struct ValidatorsSetSignal {
/// Signal delay.
pub delay: BlockNumber,
/// New validators set.
pub validators: Vec<u8>,
}
/// Parse Substrate header.
pub fn parse_substrate_header(raw_header: &[u8]) -> Result<Header, Error> {
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
}
+6
View File
@@ -11,11 +11,17 @@ async-stream = "0.2.0"
clap = { version = "2.33.0", features = ["yaml"] } clap = { version = "2.33.0", features = ["yaml"] }
codec = { package = "parity-scale-codec", version = "1.0.0" } codec = { package = "parity-scale-codec", version = "1.0.0" }
env_logger = "0.7.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" futures = "0.3.1"
hex = "0.4"
jsonrpsee = { git = "https://github.com/paritytech/jsonrpsee.git", default-features = false, features = ["http"] } jsonrpsee = { git = "https://github.com/paritytech/jsonrpsee.git", default-features = false, features = ["http"] }
linked-hash-map = "0.5.2" linked-hash-map = "0.5.2"
log = "0.4.8" log = "0.4.8"
num-traits = "0.2" num-traits = "0.2"
parity-crypto = { version = "0.6", features = ["publickey"] }
parking_lot = "0.10.2" parking_lot = "0.10.2"
rustc-hex = "2.0.1" rustc-hex = "2.0.1"
serde = { version = "1.0.106", features = ["derive"] } serde = { version = "1.0.106", features = ["derive"] }
@@ -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"
}
]
File diff suppressed because one or more lines are too long
@@ -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`
+79
View File
@@ -43,3 +43,82 @@ subcommands:
long: sub-signer-password long: sub-signer-password
value_name: 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. 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
+245 -15
View File
@@ -14,14 +14,22 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::ethereum_types::{EthereumHeaderId, Header, Receipt, H256, U64}; use crate::ethereum_types::{Address, Bytes, EthereumHeaderId, Header, Receipt, TransactionHash, H256, U256, U64};
use crate::sync_types::MaybeConnectionError; 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::common::Params;
use jsonrpsee::raw::{RawClient, RawClientError}; use jsonrpsee::raw::{RawClient, RawClientError};
use jsonrpsee::transport::http::{HttpTransportClient, RequestError}; 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}; 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. /// Proof of hash serialization success.
const HASH_SERIALIZATION_PROOF: &'static str = "hash serialization never fails; qed"; const HASH_SERIALIZATION_PROOF: &'static str = "hash serialization never fails; qed";
/// Proof of integer serialization success. /// Proof of integer serialization success.
@@ -29,24 +37,83 @@ const INT_SERIALIZATION_PROOF: &'static str = "integer serialization never fails
/// Proof of bool serialization success. /// Proof of bool serialization success.
const BOOL_SERIALIZATION_PROOF: &'static str = "bool serialization never fails; qed"; 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. /// Ethereum client type.
pub type Client = RawClient<HttpTransportClient>; pub type Client = RawClient<HttpTransportClient>;
/// Ethereum contract call request.
#[derive(Debug, Default, PartialEq, Serialize)]
pub struct CallRequest {
/// Contract address.
pub to: Option<Address>,
/// Call data.
pub data: Option<Bytes>,
}
/// All possible errors that can occur during interacting with Ethereum node. /// All possible errors that can occur during interacting with Ethereum node.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Request start failed. /// Request start failed.
StartRequestFailed(RequestError), StartRequestFailed(RequestError),
/// Error serializing request.
RequestSerialization(serde_json::Error),
/// Request not found (should never occur?). /// Request not found (should never occur?).
RequestNotFound, RequestNotFound,
/// Failed to receive response. /// Failed to receive response.
ResponseRetrievalFailed(RawClientError<RequestError>), ResponseRetrievalFailed(RawClientError<RequestError>),
/// Failed to parse response. /// Failed to parse response.
ResponseParseFailed(serde_json::Error), ResponseParseFailed(String),
/// We have received header with missing number and hash fields. /// We have received header with missing number and hash fields.
IncompleteHeader, IncompleteHeader,
/// We have received receipt with missing gas_used field. /// We have received receipt with missing gas_used field.
IncompleteReceipt, IncompleteReceipt,
/// Invalid Substrate block number received from Ethereum node.
InvalidSubstrateBlockNumber,
} }
impl MaybeConnectionError for Error { impl MaybeConnectionError for Error {
@@ -59,8 +126,9 @@ impl MaybeConnectionError for Error {
} }
/// Returns client that is able to call RPCs on Ethereum node. /// Returns client that is able to call RPCs on Ethereum node.
pub fn client(uri: &str) -> Client { pub fn client(params: EthereumConnectionParams) -> Client {
let transport = HttpTransportClient::new(uri); let uri = format!("http://{}:{}", params.host, params.port);
let transport = HttpTransportClient::new(&uri);
RawClient::new(transport) RawClient::new(transport)
} }
@@ -118,15 +186,11 @@ pub async fn header_by_hash(client: Client, hash: H256) -> (Client, Result<Heade
pub async fn transactions_receipts( pub async fn transactions_receipts(
mut client: Client, mut client: Client,
id: EthereumHeaderId, id: EthereumHeaderId,
transacactions: Vec<H256>, transactions: Vec<H256>,
) -> (Client, Result<(EthereumHeaderId, Vec<Receipt>), Error>) { ) -> (Client, Result<(EthereumHeaderId, Vec<Receipt>), Error>) {
let mut transactions_receipts = Vec::with_capacity(transacactions.len()); let mut transactions_receipts = Vec::with_capacity(transactions.len());
for transacaction in transacactions { for transaction in transactions {
let (next_client, transaction_receipt) = transaction_receipt(client, transacaction).await; let (next_client, transaction_receipt) = bail_on_error!(transaction_receipt(client, transaction).await);
let transaction_receipt = match transaction_receipt {
Ok(transaction_receipt) => transaction_receipt,
Err(error) => return (next_client, Err(error)),
};
transactions_receipts.push(transaction_receipt); transactions_receipts.push(transaction_receipt);
client = next_client; client = next_client;
} }
@@ -150,6 +214,172 @@ async fn transaction_receipt(client: Client, hash: H256) -> (Client, Result<Rece
) )
} }
/// Returns best Substrate block that PoA chain knows of.
pub async fn best_substrate_block(
client: Client,
contract_address: Address,
) -> (Client, Result<SubstrateHeaderId, Error>) {
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::<Bytes>(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::<Bytes>(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<QueuedSubstrateHeader>,
) -> (Client, Result<Vec<SubstrateHeaderId>, 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,
&params,
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<u8>,
initial_header: Vec<u8>,
initial_set_id: u64,
initial_authorities: Vec<u8>,
) -> (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<Address>,
nonce: Option<U256>,
double_gas: bool,
encoded_call: Vec<u8>,
) -> (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(&params.signer.secret().as_fixed_bytes().into(), &params.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::<TransactionHash>(client, "eth_submitTransaction", Params::Array(vec![transaction])).await
);
(client, Ok(()))
}
/// Get account nonce.
async fn account_nonce(client: Client, caller_address: Address) -> (Client, Result<U256, Error>) {
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<U256, Error>) {
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. /// Calls RPC on Ethereum node.
async fn call_rpc<T: DeserializeOwned>( async fn call_rpc<T: DeserializeOwned>(
mut client: Client, mut client: Client,
@@ -172,7 +402,7 @@ async fn call_rpc<T: DeserializeOwned>(
.ok_or(Error::RequestNotFound)? .ok_or(Error::RequestNotFound)?
.await .await
.map_err(Error::ResponseRetrievalFailed)?; .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; let result = do_call_rpc(&mut client, method, params).await;
@@ -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 <http://www.gnu.org/licenses/>.
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<u8>,
/// Substrate connection params.
pub sub: SubstrateConnectionParams,
/// Initial authorities set id.
pub sub_initial_authorities_set_id: Option<u64>,
/// Initial authorities set.
pub sub_initial_authorities_set: Option<Vec<u8>>,
/// Initial header.
pub sub_initial_header: Option<Vec<u8>>,
}
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,
&params.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<Vec<u8>>,
) -> (substrate_client::Client, Result<(SubstrateHash, Vec<u8>), 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<Vec<u8>>,
) -> (substrate_client::Client, Result<Vec<u8>, 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)),
)
}
@@ -14,57 +14,58 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::ethereum_client; use crate::ethereum_client::{self, EthereumConnectionParams};
use crate::ethereum_types::{EthereumHeaderId, EthereumHeadersSyncPipeline, Header, QueuedEthereumHeader, Receipt}; 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::{HeadersSyncParams, TargetTransactionMode};
use crate::sync_loop::{SourceClient, TargetClient}; use crate::sync_loop::{SourceClient, TargetClient};
use futures::future::FutureExt; use futures::future::FutureExt;
use std::{future::Future, pin::Pin}; 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. /// Interval (in ms) at which we check new Ethereum headers when we are synced/almost synced.
const ETHEREUM_TICK_INTERVAL_MS: u64 = 10_000; const ETHEREUM_TICK_INTERVAL_MS: u64 = 10_000;
/// Interval (in ms) at which we check new Substrate blocks. /// Interval (in ms) at which we check new Substrate blocks.
const SUBSTRATE_TICK_INTERVAL_MS: u64 = 5_000; 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. /// Ethereum synchronization parameters.
pub struct EthereumSyncParams { pub struct EthereumSyncParams {
/// Ethereum RPC host. /// Ethereum connection params.
pub eth_host: String, pub eth: EthereumConnectionParams,
/// Ethereum RPC port. /// Substrate connection params.
pub eth_port: u16, pub sub: SubstrateConnectionParams,
/// Substrate RPC host. /// Substrate signing params.
pub sub_host: String, pub sub_sign: SubstrateSigningParams,
/// Substrate RPC port.
pub sub_port: u16,
/// Substrate transactions signer.
pub sub_signer: sp_core::sr25519::Pair,
/// Synchronization parameters. /// Synchronization parameters.
pub sync_params: HeadersSyncParams, 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 { impl Default for EthereumSyncParams {
fn default() -> Self { fn default() -> Self {
EthereumSyncParams { EthereumSyncParams {
eth_host: "localhost".into(), eth: Default::default(),
eth_port: 8545, sub: Default::default(),
sub_host: "localhost".into(), sub_sign: Default::default(),
sub_port: 9933, sync_params: HeadersSyncParams {
sub_signer: sp_keyring::AccountKeyring::Alice.pair(), max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD,
sync_params: Default::default(), 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<EthereumHeadersSyncPipeline> for EthereumHeadersSource {
struct SubstrateHeadersTarget { struct SubstrateHeadersTarget {
/// Substrate node client. /// Substrate node client.
client: substrate_client::Client, client: substrate_client::Client,
/// Substrate transactions signer.
signer: sp_core::sr25519::Pair,
/// Whether we want to submit signed (true), or unsigned (false) transactions. /// Whether we want to submit signed (true), or unsigned (false) transactions.
sign_transactions: bool, sign_transactions: bool,
/// Substrate signing params.
sign_params: SubstrateSigningParams,
} }
impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget { impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
@@ -126,14 +127,14 @@ impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
type SubmitHeadersFuture = Pin<Box<dyn Future<Output = (Self, Result<Vec<EthereumHeaderId>, Self::Error>)>>>; type SubmitHeadersFuture = Pin<Box<dyn Future<Output = (Self, Result<Vec<EthereumHeaderId>, Self::Error>)>>>;
fn best_header_id(self) -> Self::BestHeaderIdFuture { 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) substrate_client::best_ethereum_block(self.client)
.map(move |(client, result)| { .map(move |(client, result)| {
( (
SubstrateHeadersTarget { SubstrateHeadersTarget {
client, client,
signer,
sign_transactions, sign_transactions,
sign_params,
}, },
result, result,
) )
@@ -142,14 +143,14 @@ impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
} }
fn is_known_header(self, id: EthereumHeaderId) -> Self::IsKnownHeaderFuture { 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) substrate_client::ethereum_header_known(self.client, id)
.map(move |(client, result)| { .map(move |(client, result)| {
( (
SubstrateHeadersTarget { SubstrateHeadersTarget {
client, client,
signer,
sign_transactions, sign_transactions,
sign_params,
}, },
result, result,
) )
@@ -161,14 +162,14 @@ impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
// we can minimize number of receipts_check calls by checking header // we can minimize number of receipts_check calls by checking header
// logs bloom here, but it may give us false positives (when authorities // logs bloom here, but it may give us false positives (when authorities
// source is contract, we never need any logs) // 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()) substrate_client::ethereum_receipts_required(self.client, header.clone())
.map(move |(client, result)| { .map(move |(client, result)| {
( (
SubstrateHeadersTarget { SubstrateHeadersTarget {
client, client,
signer,
sign_transactions, sign_transactions,
sign_params,
}, },
result, result,
) )
@@ -177,16 +178,16 @@ impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
} }
fn submit_headers(self, headers: Vec<QueuedEthereumHeader>) -> Self::SubmitHeadersFuture { fn submit_headers(self, headers: Vec<QueuedEthereumHeader>) -> Self::SubmitHeadersFuture {
let (signer, sign_transactions) = (self.signer, self.sign_transactions); let (sign_transactions, sign_params) = (self.sign_transactions, self.sign_params);
substrate_client::submit_ethereum_headers(self.client, signer.clone(), headers, sign_transactions) substrate_client::submit_ethereum_headers(self.client, sign_params.clone(), headers, sign_transactions)
.map(move |(client, result)| { .map(move |(client, result)| {
( (
SubstrateHeadersTarget { SubstrateHeadersTarget {
client, client,
signer,
sign_transactions, sign_transactions,
sign_params,
}, },
result.map(|(_, submitted_headers)| submitted_headers), result,
) )
}) })
.boxed() .boxed()
@@ -195,12 +196,9 @@ impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
/// Run Ethereum headers synchronization. /// Run Ethereum headers synchronization.
pub fn run(params: EthereumSyncParams) { pub fn run(params: EthereumSyncParams) {
let eth_uri = format!("http://{}:{}", params.eth_host, params.eth_port); let eth_client = ethereum_client::client(params.eth);
let eth_client = ethereum_client::client(&eth_uri); 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 { let sign_sub_transactions = match params.sync_params.target_tx_mode {
TargetTransactionMode::Signed | TargetTransactionMode::Backup => true, TargetTransactionMode::Signed | TargetTransactionMode::Backup => true,
TargetTransactionMode::Unsigned => false, TargetTransactionMode::Unsigned => false,
@@ -211,8 +209,8 @@ pub fn run(params: EthereumSyncParams) {
ETHEREUM_TICK_INTERVAL_MS, ETHEREUM_TICK_INTERVAL_MS,
SubstrateHeadersTarget { SubstrateHeadersTarget {
client: sub_client, client: sub_client,
signer: sub_signer,
sign_transactions: sign_sub_transactions, sign_transactions: sign_sub_transactions,
sign_params: params.sub_sign,
}, },
SUBSTRATE_TICK_INTERVAL_MS, SUBSTRATE_TICK_INTERVAL_MS,
params.sync_params, params.sync_params,
@@ -18,7 +18,7 @@ use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethe
use crate::sync_types::{HeaderId, HeadersSyncPipeline, QueuedHeader, SourceHeader}; use crate::sync_types::{HeaderId, HeadersSyncPipeline, QueuedHeader, SourceHeader};
use codec::Encode; 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 /// When header is just received from the Ethereum node, we check that it has
/// both number and hash fields filled. /// 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. /// gas_used field filled.
pub const RECEIPT_GAS_USED_PROOF: &'static str = "checked on retrieval; qed"; pub const RECEIPT_GAS_USED_PROOF: &'static str = "checked on retrieval; qed";
/// Ethereum transaction hash type.
pub type TransactionHash = H256;
/// Ethereum header type. /// Ethereum header type.
pub type Header = web3::types::Block<H256>; pub type Header = web3::types::Block<H256>;
+22 -14
View File
@@ -117,7 +117,10 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
self.maybe_extra.keys().next_back().cloned().unwrap_or_else(Zero::zero), self.maybe_extra.keys().next_back().cloned().unwrap_or_else(Zero::zero),
std::cmp::max( std::cmp::max(
self.extra.keys().next_back().cloned().unwrap_or_else(Zero::zero), 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<P: HeadersSyncPipeline> QueuedHeaders<P> {
// all ancestors of this header are now synced => let's remove them from // all ancestors of this header are now synced => let's remove them from
// queues // queues
let mut current = *id; let mut current = *id;
let mut id_processed = false;
loop { loop {
let header = match self.status(&current) { let header = match self.status(&current) {
HeaderStatus::Unknown => break, HeaderStatus::Unknown => break,
@@ -253,22 +257,26 @@ impl<P: HeadersSyncPipeline> QueuedHeaders<P> {
.entry(current.1) .entry(current.1)
.or_insert(HeaderStatus::Synced) = HeaderStatus::Synced; .or_insert(HeaderStatus::Synced) = HeaderStatus::Synced;
current = header.parent_id(); current = header.parent_id();
id_processed = true;
} }
// remember that the header is synced // remember that the header is synced
log::debug!( if !id_processed {
target: "bridge", // to avoid duplicate log message
"{} header {:?} is now {:?}", log::debug!(
P::SOURCE_NAME, target: "bridge",
id, "{} header {:?} is now {:?}",
HeaderStatus::Synced, P::SOURCE_NAME,
); id,
*self HeaderStatus::Synced,
.known_headers );
.entry(id.0) *self
.or_default() .known_headers
.entry(id.1) .entry(id.0)
.or_insert(HeaderStatus::Synced) = HeaderStatus::Synced; .or_default()
.entry(id.1)
.or_insert(HeaderStatus::Synced) = HeaderStatus::Synced;
}
// now let's move all descendants from maybe_orphan && orphan queues to // now let's move all descendants from maybe_orphan && orphan queues to
// maybe_extra queue // maybe_extra queue
+96 -8
View File
@@ -17,17 +17,25 @@
#![recursion_limit = "1024"] #![recursion_limit = "1024"]
mod ethereum_client; mod ethereum_client;
mod ethereum_deploy_contract;
mod ethereum_sync_loop; mod ethereum_sync_loop;
mod ethereum_types; mod ethereum_types;
mod headers; mod headers;
mod substrate_client; mod substrate_client;
mod substrate_sync_loop;
mod substrate_types; mod substrate_types;
mod sync; mod sync;
mod sync_loop; mod sync_loop;
mod sync_types; 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 sp_core::crypto::Pair;
use std::io::Write; use std::io::Write;
use substrate_client::{SubstrateConnectionParams, SubstrateSigningParams};
use substrate_sync_loop::SubstrateSyncParams;
fn main() { fn main() {
initialize(); 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(&eth_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"); log::error!(target: "bridge", "No subcommand specified");
return; return;
@@ -89,25 +115,58 @@ fn initialize() {
builder.init(); builder.init();
} }
fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<ethereum_sync_loop::EthereumSyncParams, String> { fn ethereum_connection_params(matches: &clap::ArgMatches) -> Result<EthereumConnectionParams, String> {
let mut eth_sync_params = ethereum_sync_loop::EthereumSyncParams::default(); let mut params = EthereumConnectionParams::default();
if let Some(eth_host) = matches.value_of("eth-host") { 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") { 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<EthereumSigningParams, String> {
let mut params = EthereumSigningParams::default();
if let Some(eth_signer) = matches.value_of("eth-signer") {
params.signer = eth_signer
.parse::<Secret>()
.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<SubstrateConnectionParams, String> {
let mut params = SubstrateConnectionParams::default();
if let Some(sub_host) = matches.value_of("sub-host") { 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") { 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<SubstrateSigningParams, String> {
let mut params = SubstrateSigningParams::default();
if let Some(sub_signer) = matches.value_of("sub-signer") { if let Some(sub_signer) = matches.value_of("sub-signer") {
let sub_signer_password = matches.value_of("sub-signer-password"); let sub_signer_password = matches.value_of("sub-signer-password");
eth_sync_params.sub_signer = params.signer = sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password)
sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password).map_err(|e| format!("{:?}", e))?; .map_err(|e| format!("Failed to parse sub-signer: {:?}", e))?;
} }
Ok(params)
}
fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams, String> {
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") { match matches.value_of("sub-tx-mode") {
Some("signed") => eth_sync_params.sync_params.target_tx_mode = sync::TargetTransactionMode::Signed, 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<ethereum_sync_loop
Ok(eth_sync_params) Ok(eth_sync_params)
} }
fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncParams, String> {
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<ethereum_deploy_contract::EthereumDeployContractParams, String> {
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(&eth_contract_code).map_err(|e| format!("Failed to parse eth-contract-code: {}", e))?;
}
Ok(eth_deploy_params)
}
+175 -86
View File
@@ -15,16 +15,60 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::ethereum_types::{Bytes, EthereumHeaderId, QueuedEthereumHeader, H256}; 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::sync_types::{HeaderId, MaybeConnectionError, SourceHeader};
use crate::{bail_on_arg_error, bail_on_error};
use codec::{Decode, Encode}; use codec::{Decode, Encode};
use jsonrpsee::common::Params; use jsonrpsee::common::Params;
use jsonrpsee::raw::{RawClient, RawClientError}; use jsonrpsee::raw::{RawClient, RawClientError};
use jsonrpsee::transport::http::{HttpTransportClient, RequestError}; 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_core::crypto::Pair;
use sp_runtime::traits::IdentifyAccount; 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. /// Substrate client type.
pub struct Client { pub struct Client {
/// Substrate RPC client. /// Substrate RPC client.
@@ -38,6 +82,8 @@ pub struct Client {
pub enum Error { pub enum Error {
/// Request start failed. /// Request start failed.
StartRequestFailed(RequestError), StartRequestFailed(RequestError),
/// Error serializing request.
RequestSerialization(serde_json::Error),
/// Request not found (should never occur?). /// Request not found (should never occur?).
RequestNotFound, RequestNotFound,
/// Failed to receive response. /// Failed to receive response.
@@ -56,23 +102,42 @@ impl MaybeConnectionError for Error {
} }
/// Returns client that is able to call RPCs on Substrate node. /// Returns client that is able to call RPCs on Substrate node.
pub fn client(uri: &str) -> Client { pub fn client(params: SubstrateConnectionParams) -> Client {
let transport = HttpTransportClient::new(uri); let uri = format!("http://{}:{}", params.host, params.port);
let transport = HttpTransportClient::new(&uri);
Client { Client {
rpc_client: RawClient::new(transport), rpc_client: RawClient::new(transport),
genesis_hash: None, genesis_hash: None,
} }
} }
/// Returns best Substrate header.
pub async fn best_header(client: Client) -> (Client, Result<SubstrateHeader, Error>) {
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<SubstrateHeader, Error>) {
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<SubstrateHeader, Error>) {
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. /// Returns best Ethereum block that Substrate runtime knows of.
pub async fn best_ethereum_block(client: Client) -> (Client, Result<EthereumHeaderId, Error>) { pub async fn best_ethereum_block(client: Client) -> (Client, Result<EthereumHeaderId, Error>) {
let (client, result) = call_rpc::<(u64, H256)>( let (client, result) = call_rpc(
client, client,
"state_call", "state_call",
Params::Array(vec![ Params::Array(vec![
to_value("EthereumHeadersApi_best_block").unwrap(), serde_json::Value::String("EthereumHeadersApi_best_block".into()),
to_value("0x").unwrap(), serde_json::Value::String("0x".into()),
]), ]),
rpc_returns_encoded_value,
) )
.await; .await;
(client, result.map(|(num, hash)| HeaderId(num, hash))) (client, result.map(|(num, hash)| HeaderId(num, hash)))
@@ -85,14 +150,18 @@ pub async fn ethereum_receipts_required(
) -> (Client, Result<(EthereumHeaderId, bool), Error>) { ) -> (Client, Result<(EthereumHeaderId, bool), Error>) {
let id = header.header().id(); let id = header.header().id();
let header = into_substrate_ethereum_header(header.header()); 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( let (client, receipts_required) = call_rpc(
client, client,
"state_call", "state_call",
Params::Array(vec![ Params::Array(vec![
to_value("EthereumHeadersApi_is_import_requires_receipts").unwrap(), serde_json::Value::String("EthereumHeadersApi_is_import_requires_receipts".into()),
to_value(Bytes(encoded_header)).unwrap(), encoded_header,
]), ]),
rpc_returns_encoded_value,
) )
.await; .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 // 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 // there's a better header => this Orphan will either be marked as synced, or
// eventually pruned. // 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( let (client, is_known_block) = call_rpc(
client, client,
"state_call", "state_call",
Params::Array(vec![ Params::Array(vec![
to_value("EthereumHeadersApi_is_known_block").unwrap(), serde_json::Value::String("EthereumHeadersApi_is_known_block".into()),
to_value(Bytes(encoded_id)).unwrap(), encoded_id,
]), ]),
rpc_returns_encoded_value,
) )
.await; .await;
(client, is_known_block.map(|is_known_block| (id, is_known_block))) (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. /// Submits Ethereum header to Substrate runtime.
pub async fn submit_ethereum_headers( pub async fn submit_ethereum_headers(
client: Client, client: Client,
signer: sp_core::sr25519::Pair, params: SubstrateSigningParams,
headers: Vec<QueuedEthereumHeader>, headers: Vec<QueuedEthereumHeader>,
sign_transactions: bool, sign_transactions: bool,
) -> (Client, Result<(Vec<TransactionHash>, Vec<EthereumHeaderId>), Error>) { ) -> (Client, Result<Vec<EthereumHeaderId>, Error>) {
match sign_transactions { 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, 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. /// Submits signed Ethereum header to Substrate runtime.
pub async fn submit_signed_ethereum_headers( pub async fn submit_signed_ethereum_headers(
client: Client, client: Client,
signer: sp_core::sr25519::Pair, params: SubstrateSigningParams,
headers: Vec<QueuedEthereumHeader>, headers: Vec<QueuedEthereumHeader>,
) -> (Client, Result<(Vec<TransactionHash>, Vec<EthereumHeaderId>), Error>) { ) -> (Client, Result<Vec<EthereumHeaderId>, Error>) {
let ids = headers.iter().map(|header| header.id()).collect(); let ids = headers.iter().map(|header| header.id()).collect();
let (client, genesis_hash) = match client.genesis_hash { let (client, genesis_hash) = match client.genesis_hash {
Some(genesis_hash) => (client, genesis_hash), Some(genesis_hash) => (client, genesis_hash),
None => { None => {
let (mut client, genesis_hash) = block_hash_by_number(client, 0).await; let (mut client, genesis_hash) = bail_on_error!(block_hash_by_number(client, Zero::zero()).await);
let genesis_hash = match genesis_hash {
Ok(genesis_hash) => genesis_hash,
Err(err) => return (client, Err(err)),
};
client.genesis_hash = Some(genesis_hash); client.genesis_hash = Some(genesis_hash);
(client, genesis_hash) (client, genesis_hash)
} }
}; };
let account_id = signer.public().as_array_ref().clone().into(); let account_id = params.signer.public().as_array_ref().clone().into();
let (client, nonce) = next_account_index(client, account_id).await; let (client, nonce) = bail_on_error!(next_account_index(client, account_id).await);
let nonce = match nonce {
Ok(nonce) => nonce,
Err(err) => return (client, Err(err)),
};
let transaction = create_signed_submit_transaction(headers, &signer, nonce, genesis_hash); let transaction = create_signed_submit_transaction(headers, &params.signer, nonce, genesis_hash);
let encoded_transaction = transaction.encode(); let encoded_transaction = bail_on_arg_error!(
let (client, transaction_hash) = call_rpc( to_value(Bytes(transaction.encode())).map_err(|e| Error::RequestSerialization(e)),
client
);
let (client, _) = call_rpc(
client, client,
"author_submitExtrinsic", "author_submitExtrinsic",
Params::Array(vec![to_value(Bytes(encoded_transaction)).unwrap()]), Params::Array(vec![encoded_transaction]),
|_| Ok(()),
) )
.await; .await;
( (client, Ok(ids))
client,
transaction_hash.map(|transaction_hash| (vec![transaction_hash], ids)),
)
} }
/// Submits unsigned Ethereum header to Substrate runtime. /// Submits unsigned Ethereum header to Substrate runtime.
pub async fn submit_unsigned_ethereum_headers( pub async fn submit_unsigned_ethereum_headers(
mut client: Client, mut client: Client,
headers: Vec<QueuedEthereumHeader>, headers: Vec<QueuedEthereumHeader>,
) -> (Client, Result<(Vec<TransactionHash>, Vec<EthereumHeaderId>), Error>) { ) -> (Client, Result<Vec<EthereumHeaderId>, Error>) {
let ids = headers.iter().map(|header| header.id()).collect(); let ids = headers.iter().map(|header| header.id()).collect();
let mut transactions_hashes = Vec::new();
for header in headers { for header in headers {
let transaction = create_unsigned_submit_transaction(header); let transaction = create_unsigned_submit_transaction(header);
let encoded_transaction = transaction.encode(); let encoded_transaction = bail_on_arg_error!(
let (used_client, transaction_hash) = call_rpc( to_value(Bytes(transaction.encode())).map_err(|e| Error::RequestSerialization(e)),
client, client
"author_submitExtrinsic", );
Params::Array(vec![to_value(Bytes(encoded_transaction)).unwrap()]), let (used_client, _) = bail_on_error!(
) call_rpc(
.await; client,
"author_submitExtrinsic",
Params::Array(vec![encoded_transaction]),
|_| Ok(()),
)
.await
);
client = used_client; 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<Vec<u8>, 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. /// Get Substrate block hash by its number.
async fn block_hash_by_number(client: Client, number: u64) -> (Client, Result<H256, Error>) { async fn block_hash_by_number(client: Client, number: Number) -> (Client, Result<Hash, Error>) {
let number = bail_on_arg_error!(to_value(number).map_err(|e| Error::RequestSerialization(e)), client);
call_rpc( call_rpc(
client, client,
"chain_getBlockHash", "chain_getBlockHash",
Params::Array(vec![to_value(number).unwrap()]), Params::Array(vec![number]),
rpc_returns_value,
) )
.await .await
} }
@@ -224,18 +308,30 @@ async fn next_account_index(
) -> (Client, Result<node_primitives::Index, Error>) { ) -> (Client, Result<node_primitives::Index, Error>) {
use sp_core::crypto::Ss58Codec; use sp_core::crypto::Ss58Codec;
let (client, index) = call_rpc_u64( let account = bail_on_arg_error!(
client, to_value(account.to_ss58check()).map_err(|e| Error::RequestSerialization(e)),
"system_accountNextIndex", client
Params::Array(vec![to_value(account.to_ss58check()).unwrap()]), );
) let (client, index) = call_rpc(client, "system_accountNextIndex", Params::Array(vec![account]), |v| {
rpc_returns_value::<u64>(v)
})
.await; .await;
(client, index.map(|index| index as _)) (client, index.map(|index| index as _))
} }
/// Calls RPC on Substrate node that returns Bytes. /// Calls RPC on Substrate node that returns Bytes.
async fn call_rpc<T: Decode>(mut client: Client, method: &'static str, params: Params) -> (Client, Result<T, Error>) { async fn call_rpc<T>(
async fn do_call_rpc<T: Decode>(client: &mut Client, method: &'static str, params: Params) -> Result<T, Error> { mut client: Client,
method: &'static str,
params: Params,
decode_value: impl Fn(Value) -> Result<T, Error>,
) -> (Client, Result<T, Error>) {
async fn do_call_rpc<T>(
client: &mut Client,
method: &'static str,
params: Params,
decode_value: impl Fn(Value) -> Result<T, Error>,
) -> Result<T, Error> {
let request_id = client let request_id = client
.rpc_client .rpc_client
.start_request(method, params) .start_request(method, params)
@@ -249,34 +345,10 @@ async fn call_rpc<T: Decode>(mut client: Client, method: &'static str, params: P
.ok_or(Error::RequestNotFound)? .ok_or(Error::RequestNotFound)?
.await .await
.map_err(Error::ResponseRetrievalFailed)?; .map_err(Error::ResponseRetrievalFailed)?;
let encoded_response: Bytes = from_value(response).map_err(|_| Error::ResponseParseFailed)?; decode_value(response)
Decode::decode(&mut &encoded_response.0[..]).map_err(|_| 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)
}
/// Calls RPC on Substrate node that returns u64.
async fn call_rpc_u64(mut client: Client, method: &'static str, params: Params) -> (Client, Result<u64, Error>) {
async fn do_call_rpc(client: &mut Client, method: &'static str, params: Params) -> Result<u64, Error> {
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;
(client, result) (client, result)
} }
@@ -341,3 +413,20 @@ fn create_unsigned_submit_transaction(header: QueuedEthereumHeader) -> bridge_no
bridge_node_runtime::UncheckedExtrinsic::new_unsigned(function) bridge_node_runtime::UncheckedExtrinsic::new_unsigned(function)
} }
/// When RPC method returns encoded value.
fn rpc_returns_encoded_value<T: Decode>(value: Value) -> Result<T, Error> {
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<T: DeserializeOwned>(value: Value) -> Result<T, Error> {
from_value(value).map_err(|_| Error::ResponseParseFailed)
}
/// When RPC method returns raw bytes.
fn rpc_returns_bytes(value: Value) -> Result<Vec<u8>, Error> {
let encoded_response: Bytes = from_value(value).map_err(|_| Error::ResponseParseFailed)?;
Ok(encoded_response.0)
}
@@ -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 <http://www.gnu.org/licenses/>.
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<SubstrateHeadersSyncPipeline> for SubstrateHeadersSource {
type Error = substrate_client::Error;
type BestBlockNumberFuture = Pin<Box<dyn Future<Output = (Self, Result<Number, Self::Error>)>>>;
type HeaderByHashFuture = Pin<Box<dyn Future<Output = (Self, Result<Header, Self::Error>)>>>;
type HeaderByNumberFuture = Pin<Box<dyn Future<Output = (Self, Result<Header, Self::Error>)>>>;
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<SubstrateHeadersSyncPipeline> for EthereumHeadersTarget {
type Error = ethereum_client::Error;
type BestHeaderIdFuture = Pin<Box<dyn Future<Output = (Self, Result<SubstrateHeaderId, Self::Error>)>>>;
type IsKnownHeaderFuture = Pin<Box<dyn Future<Output = (Self, Result<(SubstrateHeaderId, bool), Self::Error>)>>>;
type RequiresExtraFuture = Ready<(Self, Result<(SubstrateHeaderId, bool), Self::Error>)>;
type SubmitHeadersFuture = Pin<Box<dyn Future<Output = (Self, Result<Vec<SubstrateHeaderId>, 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<QueuedSubstrateHeader>) -> 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,
);
}
+46 -1
View File
@@ -14,16 +14,61 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
pub use crate::ethereum_types::H256 as TransactionHash;
use crate::ethereum_types::{ use crate::ethereum_types::{
Header as EthereumHeader, Receipt as EthereumReceipt, HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF, Header as EthereumHeader, Receipt as EthereumReceipt, HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF,
RECEIPT_GAS_USED_PROOF as ETHEREUM_RECEIPT_GAS_USED_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::{ pub use sp_bridge_eth_poa::{
Address, Bloom, Bytes, Header as SubstrateEthereumHeader, LogEntry as SubstrateEthereumLogEntry, Address, Bloom, Bytes, Header as SubstrateEthereumHeader, LogEntry as SubstrateEthereumLogEntry,
Receipt as SubstrateEthereumReceipt, TransactionOutcome as SubstrateEthereumTransactionOutcome, H256, U256, 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<bridge_node_runtime::Hash, bridge_node_runtime::BlockNumber>;
/// Queued substrate header ID.
pub type QueuedSubstrateHeader = QueuedHeader<SubstrateHeadersSyncPipeline>;
/// 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<Self>) -> usize {
source.header().encode().len()
}
}
impl SourceHeader<bridge_node_runtime::Hash, bridge_node_runtime::BlockNumber> 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. /// Convert Ethereum header into Ethereum header for Substrate.
pub fn into_substrate_ethereum_header(header: &EthereumHeader) -> SubstrateEthereumHeader { pub fn into_substrate_ethereum_header(header: &EthereumHeader) -> SubstrateEthereumHeader {
SubstrateEthereumHeader { SubstrateEthereumHeader {
+16 -18
View File
@@ -204,19 +204,6 @@ impl<P: HeadersSyncPipeline> HeadersSync<P> {
} }
} }
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -228,9 +215,20 @@ mod tests {
H256::from_low_u64_le(1000 + number) 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] #[test]
fn select_new_header_to_download_works() { fn select_new_header_to_download_works() {
let mut eth_sync = HeadersSync::<EthereumHeadersSyncPipeline>::new(Default::default()); let mut eth_sync = HeadersSync::<EthereumHeadersSyncPipeline>::new(default_sync_params());
// both best && target headers are unknown // both best && target headers are unknown
assert_eq!(eth_sync.select_new_header_to_download(), None); assert_eq!(eth_sync.select_new_header_to_download(), None);
@@ -261,7 +259,7 @@ mod tests {
#[test] #[test]
fn sync_without_reorgs_works() { 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; eth_sync.params.max_headers_in_submitted_status = 1;
// ethereum reports best header #102 // ethereum reports best header #102
@@ -310,7 +308,7 @@ mod tests {
#[test] #[test]
fn sync_with_orphan_headers_work() { 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 // ethereum reports best header #102
eth_sync.source_best_header_number_response(102); eth_sync.source_best_header_number_response(102);
@@ -359,7 +357,7 @@ mod tests {
#[test] #[test]
fn pruning_happens_on_target_best_header_response() { fn pruning_happens_on_target_best_header_response() {
let mut eth_sync = HeadersSync::<EthereumHeadersSyncPipeline>::new(Default::default()); let mut eth_sync = HeadersSync::<EthereumHeadersSyncPipeline>::new(default_sync_params());
eth_sync.params.prune_depth = 50; eth_sync.params.prune_depth = 50;
eth_sync.target_best_header_response(id(100)); eth_sync.target_best_header_response(id(100));
assert_eq!(eth_sync.headers.prune_border(), 50); assert_eq!(eth_sync.headers.prune_border(), 50);
@@ -367,7 +365,7 @@ mod tests {
#[test] #[test]
fn only_submitting_headers_in_backup_mode_when_stalled() { 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; eth_sync.params.target_tx_mode = TargetTransactionMode::Backup;
// ethereum reports best header #102 // ethereum reports best header #102
+12 -1
View File
@@ -17,7 +17,7 @@
use crate::sync::HeadersSyncParams; use crate::sync::HeadersSyncParams;
use crate::sync_types::{HeaderId, HeaderStatus, HeadersSyncPipeline, MaybeConnectionError, QueuedHeader}; use crate::sync_types::{HeaderId, HeaderStatus, HeadersSyncPipeline, MaybeConnectionError, QueuedHeader};
use futures::{future::FutureExt, stream::StreamExt}; use futures::{future::FutureExt, stream::StreamExt};
use num_traits::Saturating; use num_traits::{Saturating, Zero};
use std::future::Future; use std::future::Future;
/// When we submit headers to target node, but see no updates of best /// When we submit headers to target node, but see no updates of best
@@ -365,6 +365,17 @@ pub fn run<P: HeadersSyncPipeline>(
// for Orphan we actually ask for parent' header // for Orphan we actually ask for parent' header
let parent_id = header.parent_id(); 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!( log::debug!(
target: "bridge", target: "bridge",
"Going to download orphan header from {} node: {:?}", "Going to download orphan header from {} node: {:?}",
+37
View File
@@ -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 <http://www.gnu.org/licenses/>.
/// 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)),
}
};
}