mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 18:41:03 +00:00
Adding Bridges code as git subtree. (#2515)
* Add instructions. * Squashed 'bridges/' content from commit 345e84a21 git-subtree-dir: bridges git-subtree-split: 345e84a2146b56628e9888c9f5e129cb40e868a9 * Remove bridges workspace file to avoid confusing Cargo. * Add some bridges primitives to Polkadot workspace. * Improve docs.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "ethereum-poa-relay"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12"
|
||||
async-std = "1.9.0"
|
||||
async-trait = "0.1.42"
|
||||
clap = { version = "2.33.3", features = ["yaml"] }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0" }
|
||||
env_logger = "0.8.3"
|
||||
ethabi = { git = "https://github.com/paritytech/ethabi.git", branch = "td-eth-types-11" }
|
||||
ethabi-contract = { git = "https://github.com/paritytech/ethabi.git", branch = "td-eth-types-11" }
|
||||
ethabi-derive = { git = "https://github.com/paritytech/ethabi.git", branch = "td-eth-types-11" }
|
||||
futures = "0.3.12"
|
||||
hex = "0.4"
|
||||
hex-literal = "0.3"
|
||||
libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"] }
|
||||
log = "0.4.14"
|
||||
num-traits = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.62"
|
||||
time = "0.2"
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-currency-exchange = { path = "../../primitives/currency-exchange" }
|
||||
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
|
||||
exchange-relay = { path = "../exchange-relay" }
|
||||
headers-relay = { path = "../headers-relay" }
|
||||
messages-relay = { path = "../messages-relay" }
|
||||
relay-ethereum-client = { path = "../ethereum-client" }
|
||||
relay-rialto-client = { path = "../rialto-client" }
|
||||
relay-substrate-client = { path = "../substrate-client" }
|
||||
relay-utils = { path = "../utils" }
|
||||
rialto-runtime = { path = "../../bin/rialto/runtime" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
@@ -0,0 +1,7 @@
|
||||
# PoA <> Substrate Bridge
|
||||
|
||||
**DISCLAIMER:** *we recommend not using the bridge in "production" (to bridge significant amounts) just yet.
|
||||
it's missing a code audit and should still be considered alpha. we can't rule out that there are bugs that might result in loss of the bridged amounts.
|
||||
we'll update this disclaimer once that changes*
|
||||
|
||||
These docs are very incomplete yet. Describe high-level goals here in the (near) future.
|
||||
@@ -0,0 +1,167 @@
|
||||
[
|
||||
{
|
||||
"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": "rawHeader1",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "rawHeader2",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "rawHeader3",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "rawHeader4",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "importHeaders",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "incompleteHeaders",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "rawHeader1",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "rawHeader2",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "rawHeader3",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "rawHeader4",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "isIncompleteHeaders",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"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-07-30
|
||||
Solc version: 0.6.6+commit.6c089d02.Linux.g++
|
||||
Source hash (keccak256): 0xea5d6d744f69157adc2857166792aca139c0b5b186ba89c1011358fbcad90d7e
|
||||
Source gist: https://github.com/svyatonik/substrate-bridge-sol/blob/6456d3e016c95cd5e6d5e817c23e9e69e739aa78/substrate-bridge.sol
|
||||
Compiler flags used (command to produce the file): `docker run -i ethereum/solc:0.6.6 --optimize --bin - < substrate-bridge.sol`
|
||||
@@ -0,0 +1,166 @@
|
||||
name: ethsub-bridge
|
||||
version: "0.1.0"
|
||||
author: Parity Technologies <admin@parity.io>
|
||||
about: Parity Ethereum (PoA) <-> Substrate bridge
|
||||
subcommands:
|
||||
- eth-to-sub:
|
||||
about: Synchronize headers from Ethereum node to Substrate node.
|
||||
args:
|
||||
- eth-host: ð-host
|
||||
long: eth-host
|
||||
value_name: ETH_HOST
|
||||
help: Connect to Ethereum node at given host.
|
||||
takes_value: true
|
||||
- eth-port: ð-port
|
||||
long: eth-port
|
||||
value_name: ETH_PORT
|
||||
help: Connect to Ethereum node at given port.
|
||||
takes_value: true
|
||||
- sub-host: &sub-host
|
||||
long: sub-host
|
||||
value_name: SUB_HOST
|
||||
help: Connect to Substrate node at given host.
|
||||
takes_value: true
|
||||
- sub-port: &sub-port
|
||||
long: sub-port
|
||||
value_name: SUB_PORT
|
||||
help: Connect to Substrate node websocket server at given port.
|
||||
takes_value: true
|
||||
- sub-tx-mode:
|
||||
long: sub-tx-mode
|
||||
value_name: MODE
|
||||
help: Submit headers using signed (default) or unsigned transactions. Third mode - backup - submits signed transactions only when we believe that sync has stalled.
|
||||
takes_value: true
|
||||
possible_values:
|
||||
- signed
|
||||
- unsigned
|
||||
- backup
|
||||
- sub-signer: &sub-signer
|
||||
long: sub-signer
|
||||
value_name: SUB_SIGNER
|
||||
help: The SURI of secret key to use when transactions are submitted to the Substrate node.
|
||||
- sub-signer-password: &sub-signer-password
|
||||
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-pallet-instance: &sub-pallet-instance
|
||||
long: instance
|
||||
short: i
|
||||
value_name: PALLET_INSTANCE
|
||||
help: The instance of the bridge pallet the relay should follow.
|
||||
takes_value: true
|
||||
case_insensitive: true
|
||||
possible_values:
|
||||
- Rialto
|
||||
- Kovan
|
||||
default_value: Rialto
|
||||
- no-prometheus: &no-prometheus
|
||||
long: no-prometheus
|
||||
help: Do not expose a Prometheus metric endpoint.
|
||||
- prometheus-host: &prometheus-host
|
||||
long: prometheus-host
|
||||
value_name: PROMETHEUS_HOST
|
||||
help: Expose Prometheus endpoint at given interface.
|
||||
- prometheus-port: &prometheus-port
|
||||
long: prometheus-port
|
||||
value_name: PROMETHEUS_PORT
|
||||
help: Expose Prometheus endpoint at given port.
|
||||
- sub-to-eth:
|
||||
about: Synchronize headers from Substrate node to Ethereum node.
|
||||
args:
|
||||
- eth-host: *eth-host
|
||||
- eth-port: *eth-port
|
||||
- eth-contract:
|
||||
long: eth-contract
|
||||
value_name: ETH_CONTRACT
|
||||
help: Address of deployed bridge contract.
|
||||
takes_value: true
|
||||
- eth-chain-id: ð-chain-id
|
||||
long: eth-chain-id
|
||||
value_name: ETH_CHAIN_ID
|
||||
help: Chain ID to use for signing.
|
||||
- eth-signer: ð-signer
|
||||
long: eth-signer
|
||||
value_name: ETH_SIGNER
|
||||
help: Hex-encoded secret to use when transactions are submitted to the Ethereum node.
|
||||
- sub-host: *sub-host
|
||||
- sub-port: *sub-port
|
||||
- no-prometheus: *no-prometheus
|
||||
- prometheus-host: *prometheus-host
|
||||
- prometheus-port: *prometheus-port
|
||||
- eth-deploy-contract:
|
||||
about: Deploy Bridge contract on Ethereum node.
|
||||
args:
|
||||
- eth-host: *eth-host
|
||||
- eth-port: *eth-port
|
||||
- eth-signer: *eth-signer
|
||||
- eth-chain-id: *eth-chain-id
|
||||
- eth-contract-code:
|
||||
long: eth-contract-code
|
||||
value_name: ETH_CONTRACT_CODE
|
||||
help: Bytecode of bridge contract.
|
||||
takes_value: true
|
||||
- sub-host: *sub-host
|
||||
- sub-port: *sub-port
|
||||
- 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
|
||||
- eth-submit-exchange-tx:
|
||||
about: Submit lock funds transaction to Ethereum node.
|
||||
args:
|
||||
- eth-host: *eth-host
|
||||
- eth-port: *eth-port
|
||||
- eth-nonce:
|
||||
long: eth-nonce
|
||||
value_name: ETH_NONCE
|
||||
help: Nonce that have to be used when building transaction. If not specified, read from PoA node.
|
||||
takes_value: true
|
||||
- eth-signer: *eth-signer
|
||||
- eth-chain-id: *eth-chain-id
|
||||
- eth-amount:
|
||||
long: eth-amount
|
||||
value_name: ETH_AMOUNT
|
||||
help: Amount of ETH to lock (in wei).
|
||||
takes_value: true
|
||||
- sub-recipient:
|
||||
long: sub-recipient
|
||||
value_name: SUB_RECIPIENT
|
||||
help: Hex-encoded Public key of funds recipient in Substrate chain.
|
||||
takes_value: true
|
||||
- eth-exchange-sub:
|
||||
about: Submit proof of PoA lock funds transaction to Substrate node.
|
||||
args:
|
||||
- eth-host: *eth-host
|
||||
- eth-port: *eth-port
|
||||
- eth-start-with-block:
|
||||
long: eth-start-with-block
|
||||
value_name: ETH_START_WITH_BLOCK
|
||||
help: Auto-relay transactions starting with given block number. If not specified, starts with best finalized Ethereum block (known to Substrate node) transactions.
|
||||
takes_value: true
|
||||
conflicts_with:
|
||||
- eth-tx-hash
|
||||
- eth-tx-hash:
|
||||
long: eth-tx-hash
|
||||
value_name: ETH_TX_HASH
|
||||
help: Hash of the lock funds transaction.
|
||||
takes_value: true
|
||||
- sub-host: *sub-host
|
||||
- sub-port: *sub-port
|
||||
- sub-signer: *sub-signer
|
||||
- sub-signer-password: *sub-signer-password
|
||||
- sub-pallet-instance: *sub-pallet-instance
|
||||
- no-prometheus: *no-prometheus
|
||||
- prometheus-host: *prometheus-host
|
||||
- prometheus-port: *prometheus-port
|
||||
@@ -0,0 +1,653 @@
|
||||
// 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::rpc_errors::RpcError;
|
||||
use crate::substrate_sync_loop::QueuedRialtoHeader;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_eth_poa::signatures::secret_to_address;
|
||||
use codec::{Decode, Encode};
|
||||
use ethabi::FunctionOutputDecoder;
|
||||
use headers_relay::sync_types::SubmittedHeaders;
|
||||
use relay_ethereum_client::{
|
||||
sign_and_submit_transaction,
|
||||
types::{Address, CallRequest, HeaderId as EthereumHeaderId, Receipt, H256, U256},
|
||||
Client as EthereumClient, Error as EthereumNodeError, SigningParams as EthereumSigningParams,
|
||||
};
|
||||
use relay_rialto_client::HeaderId as RialtoHeaderId;
|
||||
use relay_utils::{HeaderId, MaybeConnectionError};
|
||||
use sp_runtime::Justification;
|
||||
use std::collections::HashSet;
|
||||
|
||||
// to encode/decode contract calls
|
||||
ethabi_contract::use_contract!(bridge_contract, "res/substrate-bridge-abi.json");
|
||||
|
||||
type RpcResult<T> = std::result::Result<T, RpcError>;
|
||||
|
||||
/// A trait which contains methods that work by using multiple low-level RPCs, or more complicated
|
||||
/// interactions involving, for example, an Ethereum contract.
|
||||
#[async_trait]
|
||||
pub trait EthereumHighLevelRpc {
|
||||
/// Returns best Substrate block that PoA chain knows of.
|
||||
async fn best_substrate_block(&self, contract_address: Address) -> RpcResult<RialtoHeaderId>;
|
||||
|
||||
/// Returns true if Substrate header is known to Ethereum node.
|
||||
async fn substrate_header_known(
|
||||
&self,
|
||||
contract_address: Address,
|
||||
id: RialtoHeaderId,
|
||||
) -> RpcResult<(RialtoHeaderId, bool)>;
|
||||
|
||||
/// Submits Substrate headers to Ethereum contract.
|
||||
async fn submit_substrate_headers(
|
||||
&self,
|
||||
params: EthereumSigningParams,
|
||||
contract_address: Address,
|
||||
headers: Vec<QueuedRialtoHeader>,
|
||||
) -> SubmittedHeaders<RialtoHeaderId, RpcError>;
|
||||
|
||||
/// Returns ids of incomplete Substrate headers.
|
||||
async fn incomplete_substrate_headers(&self, contract_address: Address) -> RpcResult<HashSet<RialtoHeaderId>>;
|
||||
|
||||
/// Complete Substrate header.
|
||||
async fn complete_substrate_header(
|
||||
&self,
|
||||
params: EthereumSigningParams,
|
||||
contract_address: Address,
|
||||
id: RialtoHeaderId,
|
||||
justification: Justification,
|
||||
) -> RpcResult<RialtoHeaderId>;
|
||||
|
||||
/// Submit ethereum transaction.
|
||||
async fn submit_ethereum_transaction(
|
||||
&self,
|
||||
params: &EthereumSigningParams,
|
||||
contract_address: Option<Address>,
|
||||
nonce: Option<U256>,
|
||||
double_gas: bool,
|
||||
encoded_call: Vec<u8>,
|
||||
) -> RpcResult<()>;
|
||||
|
||||
/// Retrieve transactions receipts for given block.
|
||||
async fn transaction_receipts(
|
||||
&self,
|
||||
id: EthereumHeaderId,
|
||||
transactions: Vec<H256>,
|
||||
) -> RpcResult<(EthereumHeaderId, Vec<Receipt>)>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EthereumHighLevelRpc for EthereumClient {
|
||||
async fn best_substrate_block(&self, contract_address: Address) -> RpcResult<RialtoHeaderId> {
|
||||
let (encoded_call, call_decoder) = bridge_contract::functions::best_known_header::call();
|
||||
let call_request = CallRequest {
|
||||
to: Some(contract_address),
|
||||
data: Some(encoded_call.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let call_result = self.eth_call(call_request).await?;
|
||||
let (number, raw_hash) = call_decoder.decode(&call_result.0)?;
|
||||
let hash = rialto_runtime::Hash::decode(&mut &raw_hash[..])?;
|
||||
|
||||
if number != number.low_u32().into() {
|
||||
return Err(RpcError::Ethereum(EthereumNodeError::InvalidSubstrateBlockNumber));
|
||||
}
|
||||
|
||||
Ok(HeaderId(number.low_u32(), hash))
|
||||
}
|
||||
|
||||
async fn substrate_header_known(
|
||||
&self,
|
||||
contract_address: Address,
|
||||
id: RialtoHeaderId,
|
||||
) -> RpcResult<(RialtoHeaderId, bool)> {
|
||||
let (encoded_call, call_decoder) = bridge_contract::functions::is_known_header::call(id.1);
|
||||
let call_request = CallRequest {
|
||||
to: Some(contract_address),
|
||||
data: Some(encoded_call.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let call_result = self.eth_call(call_request).await?;
|
||||
let is_known_block = call_decoder.decode(&call_result.0)?;
|
||||
|
||||
Ok((id, is_known_block))
|
||||
}
|
||||
|
||||
async fn submit_substrate_headers(
|
||||
&self,
|
||||
params: EthereumSigningParams,
|
||||
contract_address: Address,
|
||||
headers: Vec<QueuedRialtoHeader>,
|
||||
) -> SubmittedHeaders<RialtoHeaderId, RpcError> {
|
||||
// read nonce of signer
|
||||
let address: Address = secret_to_address(¶ms.signer);
|
||||
let nonce = match self.account_nonce(address).await {
|
||||
Ok(nonce) => nonce,
|
||||
Err(error) => {
|
||||
return SubmittedHeaders {
|
||||
submitted: Vec::new(),
|
||||
incomplete: Vec::new(),
|
||||
rejected: headers.iter().rev().map(|header| header.id()).collect(),
|
||||
fatal_error: Some(error.into()),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// submit headers. Note that we're cloning self here. It is ok, because
|
||||
// cloning `jsonrpsee::Client` only clones reference to background threads
|
||||
submit_substrate_headers(
|
||||
EthereumHeadersSubmitter {
|
||||
client: self.clone(),
|
||||
params,
|
||||
contract_address,
|
||||
nonce,
|
||||
},
|
||||
headers,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn incomplete_substrate_headers(&self, contract_address: Address) -> RpcResult<HashSet<RialtoHeaderId>> {
|
||||
let (encoded_call, call_decoder) = bridge_contract::functions::incomplete_headers::call();
|
||||
let call_request = CallRequest {
|
||||
to: Some(contract_address),
|
||||
data: Some(encoded_call.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let call_result = self.eth_call(call_request).await?;
|
||||
|
||||
// Q: Is is correct to call these "incomplete_ids"?
|
||||
let (incomplete_headers_numbers, incomplete_headers_hashes) = call_decoder.decode(&call_result.0)?;
|
||||
let incomplete_ids = incomplete_headers_numbers
|
||||
.into_iter()
|
||||
.zip(incomplete_headers_hashes)
|
||||
.filter_map(|(number, hash)| {
|
||||
if number != number.low_u32().into() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(HeaderId(number.low_u32(), hash))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(incomplete_ids)
|
||||
}
|
||||
|
||||
async fn complete_substrate_header(
|
||||
&self,
|
||||
params: EthereumSigningParams,
|
||||
contract_address: Address,
|
||||
id: RialtoHeaderId,
|
||||
justification: Justification,
|
||||
) -> RpcResult<RialtoHeaderId> {
|
||||
let _ = self
|
||||
.submit_ethereum_transaction(
|
||||
¶ms,
|
||||
Some(contract_address),
|
||||
None,
|
||||
false,
|
||||
bridge_contract::functions::import_finality_proof::encode_input(id.0, id.1, justification),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn submit_ethereum_transaction(
|
||||
&self,
|
||||
params: &EthereumSigningParams,
|
||||
contract_address: Option<Address>,
|
||||
nonce: Option<U256>,
|
||||
double_gas: bool,
|
||||
encoded_call: Vec<u8>,
|
||||
) -> RpcResult<()> {
|
||||
sign_and_submit_transaction(self, params, contract_address, nonce, double_gas, encoded_call)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn transaction_receipts(
|
||||
&self,
|
||||
id: EthereumHeaderId,
|
||||
transactions: Vec<H256>,
|
||||
) -> RpcResult<(EthereumHeaderId, Vec<Receipt>)> {
|
||||
let mut transaction_receipts = Vec::with_capacity(transactions.len());
|
||||
for transaction in transactions {
|
||||
let transaction_receipt = self.transaction_receipt(transaction).await?;
|
||||
transaction_receipts.push(transaction_receipt);
|
||||
}
|
||||
Ok((id, transaction_receipts))
|
||||
}
|
||||
}
|
||||
|
||||
/// Max number of headers which can be sent to Solidity contract.
|
||||
pub const HEADERS_BATCH: usize = 4;
|
||||
|
||||
/// Substrate headers to send to the Ethereum light client.
|
||||
///
|
||||
/// The Solidity contract can only accept a fixed number of headers in one go.
|
||||
/// This struct is meant to encapsulate this limitation.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
pub struct HeadersBatch {
|
||||
pub header1: QueuedRialtoHeader,
|
||||
pub header2: Option<QueuedRialtoHeader>,
|
||||
pub header3: Option<QueuedRialtoHeader>,
|
||||
pub header4: Option<QueuedRialtoHeader>,
|
||||
}
|
||||
|
||||
impl HeadersBatch {
|
||||
/// Create new headers from given header & ids collections.
|
||||
///
|
||||
/// This method will pop `HEADERS_BATCH` items from both collections
|
||||
/// and construct `Headers` object and a vector of `RialtoHeaderId`s.
|
||||
pub fn pop_from(
|
||||
headers: &mut Vec<QueuedRialtoHeader>,
|
||||
ids: &mut Vec<RialtoHeaderId>,
|
||||
) -> Result<(Self, Vec<RialtoHeaderId>), ()> {
|
||||
if headers.len() != ids.len() {
|
||||
log::error!(target: "bridge", "Collection size mismatch ({} vs {})", headers.len(), ids.len());
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let header1 = headers.pop().ok_or(())?;
|
||||
let header2 = headers.pop();
|
||||
let header3 = headers.pop();
|
||||
let header4 = headers.pop();
|
||||
|
||||
let mut submitting_ids = Vec::with_capacity(HEADERS_BATCH);
|
||||
for _ in 0..HEADERS_BATCH {
|
||||
submitting_ids.extend(ids.pop().iter());
|
||||
}
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
header1,
|
||||
header2,
|
||||
header3,
|
||||
header4,
|
||||
},
|
||||
submitting_ids,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns unified array of headers.
|
||||
///
|
||||
/// The first element is always `Some`.
|
||||
fn headers(&self) -> [Option<&QueuedRialtoHeader>; HEADERS_BATCH] {
|
||||
[
|
||||
Some(&self.header1),
|
||||
self.header2.as_ref(),
|
||||
self.header3.as_ref(),
|
||||
self.header4.as_ref(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Encodes all headers. If header is not present an empty vector will be returned.
|
||||
pub fn encode(&self) -> [Vec<u8>; HEADERS_BATCH] {
|
||||
let encode = |h: &QueuedRialtoHeader| h.header().encode();
|
||||
let headers = self.headers();
|
||||
[
|
||||
headers[0].map(encode).unwrap_or_default(),
|
||||
headers[1].map(encode).unwrap_or_default(),
|
||||
headers[2].map(encode).unwrap_or_default(),
|
||||
headers[3].map(encode).unwrap_or_default(),
|
||||
]
|
||||
}
|
||||
/// Returns number of contained headers.
|
||||
pub fn len(&self) -> usize {
|
||||
let is_set = |h: &Option<&QueuedRialtoHeader>| if h.is_some() { 1 } else { 0 };
|
||||
self.headers().iter().map(is_set).sum()
|
||||
}
|
||||
|
||||
/// Remove headers starting from `idx` (0-based) from this collection.
|
||||
///
|
||||
/// The collection will be left with `[0, idx)` headers.
|
||||
/// Returns `Err` when `idx == 0`, since `Headers` must contain at least one header,
|
||||
/// or when `idx > HEADERS_BATCH`.
|
||||
pub fn split_off(&mut self, idx: usize) -> Result<(), ()> {
|
||||
if idx == 0 || idx > HEADERS_BATCH {
|
||||
return Err(());
|
||||
}
|
||||
let mut vals: [_; HEADERS_BATCH] = [&mut None, &mut self.header2, &mut self.header3, &mut self.header4];
|
||||
for val in vals.iter_mut().skip(idx) {
|
||||
**val = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Substrate headers submitter API.
|
||||
#[async_trait]
|
||||
trait HeadersSubmitter {
|
||||
/// Returns Ok(0) if all given not-yet-imported headers are complete.
|
||||
/// Returns Ok(index != 0) where index is 1-based index of first header that is incomplete.
|
||||
///
|
||||
/// Returns Err(()) if contract has rejected headers. This means that the contract is
|
||||
/// unable to import first header (e.g. it may already be imported).
|
||||
async fn is_headers_incomplete(&self, headers: &HeadersBatch) -> RpcResult<usize>;
|
||||
|
||||
/// Submit given headers to Ethereum node.
|
||||
async fn submit_headers(&mut self, headers: HeadersBatch) -> RpcResult<()>;
|
||||
}
|
||||
|
||||
/// Implementation of Substrate headers submitter that sends headers to running Ethereum node.
|
||||
struct EthereumHeadersSubmitter {
|
||||
client: EthereumClient,
|
||||
params: EthereumSigningParams,
|
||||
contract_address: Address,
|
||||
nonce: U256,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HeadersSubmitter for EthereumHeadersSubmitter {
|
||||
async fn is_headers_incomplete(&self, headers: &HeadersBatch) -> RpcResult<usize> {
|
||||
let [h1, h2, h3, h4] = headers.encode();
|
||||
let (encoded_call, call_decoder) = bridge_contract::functions::is_incomplete_headers::call(h1, h2, h3, h4);
|
||||
let call_request = CallRequest {
|
||||
to: Some(self.contract_address),
|
||||
data: Some(encoded_call.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let call_result = self.client.eth_call(call_request).await?;
|
||||
let incomplete_index: U256 = call_decoder.decode(&call_result.0)?;
|
||||
if incomplete_index > HEADERS_BATCH.into() {
|
||||
return Err(RpcError::Ethereum(EthereumNodeError::InvalidIncompleteIndex));
|
||||
}
|
||||
|
||||
Ok(incomplete_index.low_u32() as _)
|
||||
}
|
||||
|
||||
async fn submit_headers(&mut self, headers: HeadersBatch) -> RpcResult<()> {
|
||||
let [h1, h2, h3, h4] = headers.encode();
|
||||
let result = self
|
||||
.client
|
||||
.submit_ethereum_transaction(
|
||||
&self.params,
|
||||
Some(self.contract_address),
|
||||
Some(self.nonce),
|
||||
false,
|
||||
bridge_contract::functions::import_headers::encode_input(h1, h2, h3, h4),
|
||||
)
|
||||
.await;
|
||||
|
||||
if result.is_ok() {
|
||||
self.nonce += U256::one();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Submit multiple Substrate headers.
|
||||
async fn submit_substrate_headers(
|
||||
mut header_submitter: impl HeadersSubmitter,
|
||||
mut headers: Vec<QueuedRialtoHeader>,
|
||||
) -> SubmittedHeaders<RialtoHeaderId, RpcError> {
|
||||
let mut submitted_headers = SubmittedHeaders::default();
|
||||
|
||||
let mut ids = headers.iter().map(|header| header.id()).rev().collect::<Vec<_>>();
|
||||
headers.reverse();
|
||||
|
||||
while !headers.is_empty() {
|
||||
let (headers, submitting_ids) =
|
||||
HeadersBatch::pop_from(&mut headers, &mut ids).expect("Headers and ids are not empty; qed");
|
||||
|
||||
submitted_headers.fatal_error =
|
||||
submit_substrate_headers_batch(&mut header_submitter, &mut submitted_headers, submitting_ids, headers)
|
||||
.await;
|
||||
|
||||
if submitted_headers.fatal_error.is_some() {
|
||||
ids.reverse();
|
||||
submitted_headers.rejected.extend(ids);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
submitted_headers
|
||||
}
|
||||
|
||||
/// Submit 4 Substrate headers in single PoA transaction.
|
||||
async fn submit_substrate_headers_batch(
|
||||
header_submitter: &mut impl HeadersSubmitter,
|
||||
submitted_headers: &mut SubmittedHeaders<RialtoHeaderId, RpcError>,
|
||||
mut ids: Vec<RialtoHeaderId>,
|
||||
mut headers: HeadersBatch,
|
||||
) -> Option<RpcError> {
|
||||
debug_assert_eq!(ids.len(), headers.len(),);
|
||||
|
||||
// if parent of first header is either incomplete, or rejected, we assume that contract
|
||||
// will reject this header as well
|
||||
let parent_id = headers.header1.parent_id();
|
||||
if submitted_headers.rejected.contains(&parent_id) || submitted_headers.incomplete.contains(&parent_id) {
|
||||
submitted_headers.rejected.extend(ids);
|
||||
return None;
|
||||
}
|
||||
|
||||
// check if headers are incomplete
|
||||
let incomplete_header_index = match header_submitter.is_headers_incomplete(&headers).await {
|
||||
// All headers valid
|
||||
Ok(0) => None,
|
||||
Ok(incomplete_header_index) => Some(incomplete_header_index),
|
||||
Err(error) => {
|
||||
// contract has rejected all headers => we do not want to submit it
|
||||
submitted_headers.rejected.extend(ids);
|
||||
if error.is_connection_error() {
|
||||
return Some(error);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Modify `ids` and `headers` to only contain values that are going to be accepted.
|
||||
let rejected = if let Some(idx) = incomplete_header_index {
|
||||
let len = std::cmp::min(idx, ids.len());
|
||||
headers
|
||||
.split_off(len)
|
||||
.expect("len > 0, the case where all headers are valid is converted to None; qed");
|
||||
ids.split_off(len)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let submitted = ids;
|
||||
let submit_result = header_submitter.submit_headers(headers).await;
|
||||
match submit_result {
|
||||
Ok(_) => {
|
||||
if incomplete_header_index.is_some() {
|
||||
submitted_headers.incomplete.extend(submitted.iter().last().cloned());
|
||||
}
|
||||
submitted_headers.submitted.extend(submitted);
|
||||
submitted_headers.rejected.extend(rejected);
|
||||
None
|
||||
}
|
||||
Err(error) => {
|
||||
submitted_headers.rejected.extend(submitted);
|
||||
submitted_headers.rejected.extend(rejected);
|
||||
Some(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_runtime::traits::Header;
|
||||
|
||||
struct TestHeadersSubmitter {
|
||||
incomplete: Vec<RialtoHeaderId>,
|
||||
failed: Vec<RialtoHeaderId>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HeadersSubmitter for TestHeadersSubmitter {
|
||||
async fn is_headers_incomplete(&self, headers: &HeadersBatch) -> RpcResult<usize> {
|
||||
if self.incomplete.iter().any(|i| i.0 == headers.header1.id().0) {
|
||||
Ok(1)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
async fn submit_headers(&mut self, headers: HeadersBatch) -> RpcResult<()> {
|
||||
if self.failed.iter().any(|i| i.0 == headers.header1.id().0) {
|
||||
Err(RpcError::Ethereum(EthereumNodeError::InvalidSubstrateBlockNumber))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn header(number: rialto_runtime::BlockNumber) -> QueuedRialtoHeader {
|
||||
QueuedRialtoHeader::new(
|
||||
rialto_runtime::Header::new(
|
||||
number,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
if number == 0 {
|
||||
Default::default()
|
||||
} else {
|
||||
header(number - 1).id().1
|
||||
},
|
||||
Default::default(),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn descendants_of_incomplete_headers_are_not_submitted() {
|
||||
let submitted_headers = async_std::task::block_on(submit_substrate_headers(
|
||||
TestHeadersSubmitter {
|
||||
incomplete: vec![header(5).id()],
|
||||
failed: vec![],
|
||||
},
|
||||
vec![header(5), header(6)],
|
||||
));
|
||||
assert_eq!(submitted_headers.submitted, vec![header(5).id()]);
|
||||
assert_eq!(submitted_headers.incomplete, vec![header(5).id()]);
|
||||
assert_eq!(submitted_headers.rejected, vec![header(6).id()]);
|
||||
assert!(submitted_headers.fatal_error.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_after_fatal_error_are_not_submitted() {
|
||||
let submitted_headers = async_std::task::block_on(submit_substrate_headers(
|
||||
TestHeadersSubmitter {
|
||||
incomplete: vec![],
|
||||
failed: vec![header(9).id()],
|
||||
},
|
||||
vec![
|
||||
header(5),
|
||||
header(6),
|
||||
header(7),
|
||||
header(8),
|
||||
header(9),
|
||||
header(10),
|
||||
header(11),
|
||||
],
|
||||
));
|
||||
assert_eq!(
|
||||
submitted_headers.submitted,
|
||||
vec![header(5).id(), header(6).id(), header(7).id(), header(8).id()]
|
||||
);
|
||||
assert_eq!(submitted_headers.incomplete, vec![]);
|
||||
assert_eq!(
|
||||
submitted_headers.rejected,
|
||||
vec![header(9).id(), header(10).id(), header(11).id(),]
|
||||
);
|
||||
assert!(submitted_headers.fatal_error.is_some());
|
||||
}
|
||||
|
||||
fn headers_batch() -> HeadersBatch {
|
||||
let mut init_headers = vec![header(1), header(2), header(3), header(4), header(5)];
|
||||
init_headers.reverse();
|
||||
let mut init_ids = init_headers.iter().map(|h| h.id()).collect();
|
||||
let (headers, ids) = HeadersBatch::pop_from(&mut init_headers, &mut init_ids).unwrap();
|
||||
assert_eq!(init_headers, vec![header(5)]);
|
||||
assert_eq!(init_ids, vec![header(5).id()]);
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![header(1).id(), header(2).id(), header(3).id(), header(4).id()]
|
||||
);
|
||||
headers
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_batch_len() {
|
||||
let headers = headers_batch();
|
||||
assert_eq!(headers.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_batch_encode() {
|
||||
let headers = headers_batch();
|
||||
assert_eq!(
|
||||
headers.encode(),
|
||||
[
|
||||
header(1).header().encode(),
|
||||
header(2).header().encode(),
|
||||
header(3).header().encode(),
|
||||
header(4).header().encode(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_batch_split_off() {
|
||||
// given
|
||||
let mut headers = headers_batch();
|
||||
|
||||
// when
|
||||
assert!(headers.split_off(0).is_err());
|
||||
assert_eq!(headers.header1, header(1));
|
||||
assert!(headers.header2.is_some());
|
||||
assert!(headers.header3.is_some());
|
||||
assert!(headers.header4.is_some());
|
||||
|
||||
// when
|
||||
let mut h = headers.clone();
|
||||
h.split_off(1).unwrap();
|
||||
assert!(h.header2.is_none());
|
||||
assert!(h.header3.is_none());
|
||||
assert!(h.header4.is_none());
|
||||
|
||||
// when
|
||||
let mut h = headers.clone();
|
||||
h.split_off(2).unwrap();
|
||||
assert!(h.header2.is_some());
|
||||
assert!(h.header3.is_none());
|
||||
assert!(h.header4.is_none());
|
||||
|
||||
// when
|
||||
let mut h = headers.clone();
|
||||
h.split_off(3).unwrap();
|
||||
assert!(h.header2.is_some());
|
||||
assert!(h.header3.is_some());
|
||||
assert!(h.header4.is_none());
|
||||
|
||||
// when
|
||||
let mut h = headers;
|
||||
h.split_off(4).unwrap();
|
||||
assert!(h.header2.is_some());
|
||||
assert!(h.header3.is_some());
|
||||
assert!(h.header4.is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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::{bridge_contract, EthereumHighLevelRpc};
|
||||
use crate::rpc_errors::RpcError;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use num_traits::Zero;
|
||||
use relay_ethereum_client::{
|
||||
Client as EthereumClient, ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams,
|
||||
};
|
||||
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto};
|
||||
use relay_substrate_client::{
|
||||
Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams, OpaqueGrandpaAuthoritiesSet,
|
||||
};
|
||||
use relay_utils::HeaderId;
|
||||
|
||||
/// Ethereum synchronization parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct EthereumDeployContractParams {
|
||||
/// Ethereum connection params.
|
||||
pub eth_params: EthereumConnectionParams,
|
||||
/// Ethereum signing params.
|
||||
pub eth_sign: EthereumSigningParams,
|
||||
/// Ethereum contract bytecode.
|
||||
pub eth_contract_code: Vec<u8>,
|
||||
/// Substrate connection params.
|
||||
pub sub_params: 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>>,
|
||||
}
|
||||
|
||||
/// Deploy Bridge contract on Ethereum chain.
|
||||
pub fn run(params: EthereumDeployContractParams) {
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
|
||||
let EthereumDeployContractParams {
|
||||
eth_params,
|
||||
eth_sign,
|
||||
sub_params,
|
||||
sub_initial_authorities_set_id,
|
||||
sub_initial_authorities_set,
|
||||
sub_initial_header,
|
||||
eth_contract_code,
|
||||
} = params;
|
||||
|
||||
let result = local_pool.run_until(async move {
|
||||
let eth_client = EthereumClient::new(eth_params);
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await.map_err(RpcError::Substrate)?;
|
||||
|
||||
let (initial_header_id, initial_header) = prepare_initial_header(&sub_client, sub_initial_header).await?;
|
||||
let initial_set_id = sub_initial_authorities_set_id.unwrap_or(0);
|
||||
let initial_set = prepare_initial_authorities_set(
|
||||
&sub_client,
|
||||
initial_header_id.1,
|
||||
sub_initial_authorities_set,
|
||||
).await?;
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Deploying Ethereum contract.\r\n\tInitial header: {:?}\r\n\tInitial header id: {:?}\r\n\tInitial header encoded: {}\r\n\tInitial authorities set ID: {}\r\n\tInitial authorities set: {}",
|
||||
initial_header,
|
||||
initial_header_id,
|
||||
hex::encode(&initial_header),
|
||||
initial_set_id,
|
||||
hex::encode(&initial_set),
|
||||
);
|
||||
|
||||
deploy_bridge_contract(
|
||||
ð_client,
|
||||
ð_sign,
|
||||
eth_contract_code,
|
||||
initial_header,
|
||||
initial_set_id,
|
||||
initial_set,
|
||||
).await
|
||||
});
|
||||
|
||||
if let Err(error) = result {
|
||||
log::error!(target: "bridge", "{}", error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare initial header.
|
||||
async fn prepare_initial_header(
|
||||
sub_client: &SubstrateClient<Rialto>,
|
||||
sub_initial_header: Option<Vec<u8>>,
|
||||
) -> Result<(RialtoHeaderId, Vec<u8>), String> {
|
||||
match sub_initial_header {
|
||||
Some(raw_initial_header) => match rialto_runtime::Header::decode(&mut &raw_initial_header[..]) {
|
||||
Ok(initial_header) => Ok((
|
||||
HeaderId(initial_header.number, initial_header.hash()),
|
||||
raw_initial_header,
|
||||
)),
|
||||
Err(error) => Err(format!("Error decoding initial header: {}", error)),
|
||||
},
|
||||
None => {
|
||||
let initial_header = sub_client.header_by_number(Zero::zero()).await;
|
||||
initial_header
|
||||
.map(|header| (HeaderId(Zero::zero(), 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: &SubstrateClient<Rialto>,
|
||||
sub_initial_header_hash: rialto_runtime::Hash,
|
||||
sub_initial_authorities_set: Option<Vec<u8>>,
|
||||
) -> Result<OpaqueGrandpaAuthoritiesSet, String> {
|
||||
let initial_authorities_set = match sub_initial_authorities_set {
|
||||
Some(initial_authorities_set) => Ok(initial_authorities_set),
|
||||
None => sub_client.grandpa_authorities_set(sub_initial_header_hash).await,
|
||||
};
|
||||
|
||||
initial_authorities_set.map_err(|error| format!("Error reading GRANDPA authorities set: {:?}", error))
|
||||
}
|
||||
|
||||
/// Deploy bridge contract to Ethereum chain.
|
||||
async fn deploy_bridge_contract(
|
||||
eth_client: &EthereumClient,
|
||||
params: &EthereumSigningParams,
|
||||
contract_code: Vec<u8>,
|
||||
initial_header: Vec<u8>,
|
||||
initial_set_id: u64,
|
||||
initial_authorities: Vec<u8>,
|
||||
) -> Result<(), String> {
|
||||
eth_client
|
||||
.submit_ethereum_transaction(
|
||||
params,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
bridge_contract::constructor(contract_code, initial_header, initial_set_id, initial_authorities),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| format!("Error deploying contract: {:?}", error))
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
// 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/>.
|
||||
|
||||
//! Relaying proofs of PoA -> Substrate exchange transactions.
|
||||
|
||||
use crate::instances::BridgeInstance;
|
||||
use crate::rialto_client::{SubmitEthereumExchangeTransactionProof, SubstrateHighLevelRpc};
|
||||
use crate::rpc_errors::RpcError;
|
||||
use crate::substrate_types::into_substrate_ethereum_receipt;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_currency_exchange::MaybeLockFundsTransaction;
|
||||
use exchange_relay::exchange::{
|
||||
relay_single_transaction_proof, SourceBlock, SourceClient, SourceTransaction, TargetClient,
|
||||
TransactionProofPipeline,
|
||||
};
|
||||
use exchange_relay::exchange_loop::{run as run_loop, InMemoryStorage};
|
||||
use relay_ethereum_client::{
|
||||
types::{
|
||||
HeaderId as EthereumHeaderId, HeaderWithTransactions as EthereumHeaderWithTransactions,
|
||||
Transaction as EthereumTransaction, TransactionHash as EthereumTransactionHash, H256, HEADER_ID_PROOF,
|
||||
},
|
||||
Client as EthereumClient, ConnectionParams as EthereumConnectionParams,
|
||||
};
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{
|
||||
Chain as SubstrateChain, Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams,
|
||||
};
|
||||
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient, HeaderId};
|
||||
use rialto_runtime::exchange::EthereumTransactionInclusionProof;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
/// Interval at which we ask Ethereum node for updates.
|
||||
const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(10);
|
||||
|
||||
/// Exchange relay mode.
|
||||
#[derive(Debug)]
|
||||
pub enum ExchangeRelayMode {
|
||||
/// Relay single transaction and quit.
|
||||
Single(EthereumTransactionHash),
|
||||
/// Auto-relay transactions starting with given block.
|
||||
Auto(Option<u64>),
|
||||
}
|
||||
|
||||
/// PoA exchange transaction relay params.
|
||||
#[derive(Debug)]
|
||||
pub struct EthereumExchangeParams {
|
||||
/// Ethereum connection params.
|
||||
pub eth_params: EthereumConnectionParams,
|
||||
/// Substrate connection params.
|
||||
pub sub_params: SubstrateConnectionParams,
|
||||
/// Substrate signing params.
|
||||
pub sub_sign: RialtoSigningParams,
|
||||
/// Relay working mode.
|
||||
pub mode: ExchangeRelayMode,
|
||||
/// Metrics parameters.
|
||||
pub metrics_params: Option<MetricsParams>,
|
||||
/// Instance of the bridge pallet being synchronized.
|
||||
pub instance: Arc<dyn BridgeInstance>,
|
||||
}
|
||||
|
||||
/// Ethereum to Substrate exchange pipeline.
|
||||
struct EthereumToSubstrateExchange;
|
||||
|
||||
impl TransactionProofPipeline for EthereumToSubstrateExchange {
|
||||
const SOURCE_NAME: &'static str = "Ethereum";
|
||||
const TARGET_NAME: &'static str = "Substrate";
|
||||
|
||||
type Block = EthereumSourceBlock;
|
||||
type TransactionProof = EthereumTransactionInclusionProof;
|
||||
}
|
||||
|
||||
/// Ethereum source block.
|
||||
struct EthereumSourceBlock(EthereumHeaderWithTransactions);
|
||||
|
||||
impl SourceBlock for EthereumSourceBlock {
|
||||
type Hash = H256;
|
||||
type Number = u64;
|
||||
type Transaction = EthereumSourceTransaction;
|
||||
|
||||
fn id(&self) -> EthereumHeaderId {
|
||||
HeaderId(
|
||||
self.0.number.expect(HEADER_ID_PROOF).as_u64(),
|
||||
self.0.hash.expect(HEADER_ID_PROOF),
|
||||
)
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Vec<Self::Transaction> {
|
||||
self.0
|
||||
.transactions
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(EthereumSourceTransaction)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ethereum source transaction.
|
||||
struct EthereumSourceTransaction(EthereumTransaction);
|
||||
|
||||
impl SourceTransaction for EthereumSourceTransaction {
|
||||
type Hash = EthereumTransactionHash;
|
||||
|
||||
fn hash(&self) -> Self::Hash {
|
||||
self.0.hash
|
||||
}
|
||||
}
|
||||
|
||||
/// Ethereum node as transactions proof source.
|
||||
#[derive(Clone)]
|
||||
struct EthereumTransactionsSource {
|
||||
client: EthereumClient,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for EthereumTransactionsSource {
|
||||
type Error = RpcError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), RpcError> {
|
||||
self.client.reconnect();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SourceClient<EthereumToSubstrateExchange> for EthereumTransactionsSource {
|
||||
async fn tick(&self) {
|
||||
async_std::task::sleep(ETHEREUM_TICK_INTERVAL).await;
|
||||
}
|
||||
|
||||
async fn block_by_hash(&self, hash: H256) -> Result<EthereumSourceBlock, RpcError> {
|
||||
self.client
|
||||
.header_by_hash_with_transactions(hash)
|
||||
.await
|
||||
.map(EthereumSourceBlock)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn block_by_number(&self, number: u64) -> Result<EthereumSourceBlock, RpcError> {
|
||||
self.client
|
||||
.header_by_number_with_transactions(number)
|
||||
.await
|
||||
.map(EthereumSourceBlock)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn transaction_block(
|
||||
&self,
|
||||
hash: &EthereumTransactionHash,
|
||||
) -> Result<Option<(EthereumHeaderId, usize)>, RpcError> {
|
||||
let eth_tx = match self.client.transaction_by_hash(*hash).await? {
|
||||
Some(eth_tx) => eth_tx,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// we need transaction to be mined => check if it is included in the block
|
||||
let (eth_header_id, eth_tx_index) = match (eth_tx.block_number, eth_tx.block_hash, eth_tx.transaction_index) {
|
||||
(Some(block_number), Some(block_hash), Some(transaction_index)) => (
|
||||
HeaderId(block_number.as_u64(), block_hash),
|
||||
transaction_index.as_u64() as _,
|
||||
),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some((eth_header_id, eth_tx_index)))
|
||||
}
|
||||
|
||||
async fn transaction_proof(
|
||||
&self,
|
||||
block: &EthereumSourceBlock,
|
||||
tx_index: usize,
|
||||
) -> Result<EthereumTransactionInclusionProof, RpcError> {
|
||||
const TRANSACTION_HAS_RAW_FIELD_PROOF: &str = "RPC level checks that transactions from Ethereum\
|
||||
node are having `raw` field; qed";
|
||||
const BLOCK_HAS_HASH_FIELD_PROOF: &str = "RPC level checks that block has `hash` field; qed";
|
||||
|
||||
let mut transaction_proof = Vec::with_capacity(block.0.transactions.len());
|
||||
for tx in &block.0.transactions {
|
||||
let raw_tx_receipt = self
|
||||
.client
|
||||
.transaction_receipt(tx.hash)
|
||||
.await
|
||||
.map(|receipt| into_substrate_ethereum_receipt(&receipt))
|
||||
.map(|receipt| receipt.rlp())?;
|
||||
let raw_tx = tx.raw.clone().expect(TRANSACTION_HAS_RAW_FIELD_PROOF).0;
|
||||
transaction_proof.push((raw_tx, raw_tx_receipt));
|
||||
}
|
||||
|
||||
Ok(EthereumTransactionInclusionProof {
|
||||
block: block.0.hash.expect(BLOCK_HAS_HASH_FIELD_PROOF),
|
||||
index: tx_index as _,
|
||||
proof: transaction_proof,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Substrate node as transactions proof target.
|
||||
#[derive(Clone)]
|
||||
struct SubstrateTransactionsTarget {
|
||||
client: SubstrateClient<Rialto>,
|
||||
sign_params: RialtoSigningParams,
|
||||
bridge_instance: Arc<dyn BridgeInstance>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for SubstrateTransactionsTarget {
|
||||
type Error = RpcError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), RpcError> {
|
||||
Ok(self.client.reconnect().await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TargetClient<EthereumToSubstrateExchange> for SubstrateTransactionsTarget {
|
||||
async fn tick(&self) {
|
||||
async_std::task::sleep(Rialto::AVERAGE_BLOCK_INTERVAL).await;
|
||||
}
|
||||
|
||||
async fn is_header_known(&self, id: &EthereumHeaderId) -> Result<bool, RpcError> {
|
||||
self.client.ethereum_header_known(*id).await
|
||||
}
|
||||
|
||||
async fn is_header_finalized(&self, id: &EthereumHeaderId) -> Result<bool, RpcError> {
|
||||
// we check if header is finalized by simple comparison of the header number and
|
||||
// number of best finalized PoA header known to Substrate node.
|
||||
//
|
||||
// this may lead to failure in tx proof import if PoA reorganization has happened
|
||||
// after we have checked that our tx has been included into given block
|
||||
//
|
||||
// the fix is easy, but since this code is mostly developed for demonstration purposes,
|
||||
// I'm leaving this KISS-based design here
|
||||
let best_finalized_ethereum_block = self.client.best_ethereum_finalized_block().await?;
|
||||
Ok(id.0 <= best_finalized_ethereum_block.0)
|
||||
}
|
||||
|
||||
async fn best_finalized_header_id(&self) -> Result<EthereumHeaderId, RpcError> {
|
||||
// we can't continue to relay exchange proofs if Substrate node is out of sync, because
|
||||
// it may have already received (some of) proofs that we're going to relay
|
||||
self.client.ensure_synced().await?;
|
||||
|
||||
self.client.best_ethereum_finalized_block().await
|
||||
}
|
||||
|
||||
async fn filter_transaction_proof(&self, proof: &EthereumTransactionInclusionProof) -> Result<bool, RpcError> {
|
||||
// let's try to parse transaction locally
|
||||
let (raw_tx, raw_tx_receipt) = &proof.proof[proof.index as usize];
|
||||
let parse_result = rialto_runtime::exchange::EthTransaction::parse(raw_tx);
|
||||
if parse_result.is_err() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// now let's check if transaction is successful
|
||||
match bp_eth_poa::Receipt::is_successful_raw_receipt(raw_tx_receipt) {
|
||||
Ok(true) => (),
|
||||
_ => return Ok(false),
|
||||
}
|
||||
|
||||
// seems that transaction is relayable - let's check if runtime is able to import it
|
||||
// (we can't if e.g. header is pruned or there's some issue with tx data)
|
||||
self.client.verify_exchange_transaction_proof(proof.clone()).await
|
||||
}
|
||||
|
||||
async fn submit_transaction_proof(&self, proof: EthereumTransactionInclusionProof) -> Result<(), RpcError> {
|
||||
let (sign_params, bridge_instance) = (self.sign_params.clone(), self.bridge_instance.clone());
|
||||
self.client
|
||||
.submit_exchange_transaction_proof(sign_params, bridge_instance, proof)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Relay exchange transaction proof(s) to Substrate node.
|
||||
pub fn run(params: EthereumExchangeParams) {
|
||||
match params.mode {
|
||||
ExchangeRelayMode::Single(eth_tx_hash) => run_single_transaction_relay(params, eth_tx_hash),
|
||||
ExchangeRelayMode::Auto(eth_start_with_block_number) => {
|
||||
run_auto_transactions_relay_loop(params, eth_start_with_block_number)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Run single transaction proof relay and stop.
|
||||
fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_hash: H256) {
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
|
||||
let EthereumExchangeParams {
|
||||
eth_params,
|
||||
sub_params,
|
||||
sub_sign,
|
||||
instance,
|
||||
..
|
||||
} = params;
|
||||
|
||||
let result = local_pool.run_until(async move {
|
||||
let eth_client = EthereumClient::new(eth_params);
|
||||
let sub_client = SubstrateClient::<Rialto>::new(sub_params)
|
||||
.await
|
||||
.map_err(RpcError::Substrate)?;
|
||||
|
||||
let source = EthereumTransactionsSource { client: eth_client };
|
||||
let target = SubstrateTransactionsTarget {
|
||||
client: sub_client,
|
||||
sign_params: sub_sign,
|
||||
bridge_instance: instance,
|
||||
};
|
||||
|
||||
relay_single_transaction_proof(&source, &target, eth_tx_hash).await
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Ethereum transaction {} proof has been successfully submitted to Substrate node",
|
||||
eth_tx_hash,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Error submitting Ethereum transaction {} proof to Substrate node: {}",
|
||||
eth_tx_hash,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run auto-relay loop.
|
||||
fn run_auto_transactions_relay_loop(params: EthereumExchangeParams, eth_start_with_block_number: Option<u64>) {
|
||||
let EthereumExchangeParams {
|
||||
eth_params,
|
||||
sub_params,
|
||||
sub_sign,
|
||||
metrics_params,
|
||||
instance,
|
||||
..
|
||||
} = params;
|
||||
|
||||
let do_run_loop = move || -> Result<(), String> {
|
||||
let eth_client = EthereumClient::new(eth_params);
|
||||
let sub_client = async_std::task::block_on(SubstrateClient::<Rialto>::new(sub_params))
|
||||
.map_err(|err| format!("Error starting Substrate client: {:?}", err))?;
|
||||
|
||||
let eth_start_with_block_number = match eth_start_with_block_number {
|
||||
Some(eth_start_with_block_number) => eth_start_with_block_number,
|
||||
None => {
|
||||
async_std::task::block_on(sub_client.best_ethereum_finalized_block())
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"Error retrieving best finalized Ethereum block from Substrate node: {:?}",
|
||||
err
|
||||
)
|
||||
})?
|
||||
.0
|
||||
}
|
||||
};
|
||||
|
||||
run_loop(
|
||||
InMemoryStorage::new(eth_start_with_block_number),
|
||||
EthereumTransactionsSource { client: eth_client },
|
||||
SubstrateTransactionsTarget {
|
||||
client: sub_client,
|
||||
sign_params: sub_sign,
|
||||
bridge_instance: instance,
|
||||
},
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if let Err(err) = do_run_loop() {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Error auto-relaying Ethereum transactions proofs to Substrate node: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// 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/>.
|
||||
|
||||
//! Submitting Ethereum -> Substrate exchange transactions.
|
||||
|
||||
use bp_eth_poa::{
|
||||
signatures::{secret_to_address, SignTransaction},
|
||||
UnsignedTransaction,
|
||||
};
|
||||
use relay_ethereum_client::{
|
||||
types::{CallRequest, U256},
|
||||
Client as EthereumClient, ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams,
|
||||
};
|
||||
use rialto_runtime::exchange::LOCK_FUNDS_ADDRESS;
|
||||
|
||||
/// Ethereum exchange transaction params.
|
||||
#[derive(Debug)]
|
||||
pub struct EthereumExchangeSubmitParams {
|
||||
/// Ethereum connection params.
|
||||
pub eth_params: EthereumConnectionParams,
|
||||
/// Ethereum signing params.
|
||||
pub eth_sign: EthereumSigningParams,
|
||||
/// Ethereum signer nonce.
|
||||
pub eth_nonce: Option<U256>,
|
||||
/// Amount of Ethereum tokens to lock.
|
||||
pub eth_amount: U256,
|
||||
/// Funds recipient on Substrate side.
|
||||
pub sub_recipient: [u8; 32],
|
||||
}
|
||||
|
||||
/// Submit single Ethereum -> Substrate exchange transaction.
|
||||
pub fn run(params: EthereumExchangeSubmitParams) {
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
|
||||
let EthereumExchangeSubmitParams {
|
||||
eth_params,
|
||||
eth_sign,
|
||||
eth_nonce,
|
||||
eth_amount,
|
||||
sub_recipient,
|
||||
} = params;
|
||||
|
||||
let result: Result<_, String> = local_pool.run_until(async move {
|
||||
let eth_client = EthereumClient::new(eth_params);
|
||||
|
||||
let eth_signer_address = secret_to_address(ð_sign.signer);
|
||||
let sub_recipient_encoded = sub_recipient;
|
||||
let nonce = match eth_nonce {
|
||||
Some(eth_nonce) => eth_nonce,
|
||||
None => eth_client
|
||||
.account_nonce(eth_signer_address)
|
||||
.await
|
||||
.map_err(|err| format!("error fetching acount nonce: {:?}", err))?,
|
||||
};
|
||||
let gas = eth_client
|
||||
.estimate_gas(CallRequest {
|
||||
from: Some(eth_signer_address),
|
||||
to: Some(LOCK_FUNDS_ADDRESS.into()),
|
||||
value: Some(eth_amount),
|
||||
data: Some(sub_recipient_encoded.to_vec().into()),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|err| format!("error estimating gas requirements: {:?}", err))?;
|
||||
let eth_tx_unsigned = UnsignedTransaction {
|
||||
nonce,
|
||||
gas_price: eth_sign.gas_price,
|
||||
gas,
|
||||
to: Some(LOCK_FUNDS_ADDRESS.into()),
|
||||
value: eth_amount,
|
||||
payload: sub_recipient_encoded.to_vec(),
|
||||
};
|
||||
let eth_tx_signed = eth_tx_unsigned
|
||||
.clone()
|
||||
.sign_by(ð_sign.signer, Some(eth_sign.chain_id));
|
||||
eth_client
|
||||
.submit_transaction(eth_tx_signed)
|
||||
.await
|
||||
.map_err(|err| format!("error submitting transaction: {:?}", err))?;
|
||||
|
||||
Ok(eth_tx_unsigned)
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(eth_tx_unsigned) => {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Exchange transaction has been submitted to Ethereum node: {:?}",
|
||||
eth_tx_unsigned,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Error submitting exchange transaction to Ethereum node: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
// 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/>.
|
||||
|
||||
//! Ethereum PoA -> Rialto-Substrate synchronization.
|
||||
|
||||
use crate::ethereum_client::EthereumHighLevelRpc;
|
||||
use crate::instances::BridgeInstance;
|
||||
use crate::rialto_client::{SubmitEthereumHeaders, SubstrateHighLevelRpc};
|
||||
use crate::rpc_errors::RpcError;
|
||||
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::Encode;
|
||||
use headers_relay::{
|
||||
sync::{HeadersSyncParams, TargetTransactionMode},
|
||||
sync_loop::{SourceClient, TargetClient},
|
||||
sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader, SubmittedHeaders},
|
||||
};
|
||||
use relay_ethereum_client::{
|
||||
types::{HeaderHash, HeaderId as EthereumHeaderId, Receipt, SyncHeader as Header},
|
||||
Client as EthereumClient, ConnectionParams as EthereumConnectionParams,
|
||||
};
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{
|
||||
Chain as SubstrateChain, Client as SubstrateClient, ConnectionParams as SubstrateConnectionParams,
|
||||
};
|
||||
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient};
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
|
||||
pub mod consts {
|
||||
use super::*;
|
||||
|
||||
/// Interval at which we check new Ethereum headers when we are synced/almost synced.
|
||||
pub const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(10);
|
||||
/// Max number of headers in single submit transaction.
|
||||
pub 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).
|
||||
pub 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.
|
||||
pub const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 128;
|
||||
/// Max Ethereum headers count we want to have in 'submitted' state.
|
||||
pub const MAX_SUBMITTED_HEADERS: usize = 128;
|
||||
/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned).
|
||||
pub const PRUNE_DEPTH: u32 = 4096;
|
||||
}
|
||||
|
||||
/// Ethereum synchronization parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct EthereumSyncParams {
|
||||
/// Ethereum connection params.
|
||||
pub eth_params: EthereumConnectionParams,
|
||||
/// Substrate connection params.
|
||||
pub sub_params: SubstrateConnectionParams,
|
||||
/// Substrate signing params.
|
||||
pub sub_sign: RialtoSigningParams,
|
||||
/// Synchronization parameters.
|
||||
pub sync_params: HeadersSyncParams,
|
||||
/// Metrics parameters.
|
||||
pub metrics_params: Option<MetricsParams>,
|
||||
/// Instance of the bridge pallet being synchronized.
|
||||
pub instance: Arc<dyn BridgeInstance>,
|
||||
}
|
||||
|
||||
/// Ethereum synchronization pipeline.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct EthereumHeadersSyncPipeline;
|
||||
|
||||
impl HeadersSyncPipeline for EthereumHeadersSyncPipeline {
|
||||
const SOURCE_NAME: &'static str = "Ethereum";
|
||||
const TARGET_NAME: &'static str = "Substrate";
|
||||
|
||||
type Hash = HeaderHash;
|
||||
type Number = u64;
|
||||
type Header = Header;
|
||||
type Extra = Vec<Receipt>;
|
||||
type Completion = ();
|
||||
|
||||
fn estimate_size(source: &QueuedHeader<Self>) -> usize {
|
||||
into_substrate_ethereum_header(source.header()).encode().len()
|
||||
+ into_substrate_ethereum_receipts(source.extra())
|
||||
.map(|extra| extra.encode().len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Queued ethereum header ID.
|
||||
pub type QueuedEthereumHeader = QueuedHeader<EthereumHeadersSyncPipeline>;
|
||||
|
||||
/// Ethereum client as headers source.
|
||||
#[derive(Clone)]
|
||||
struct EthereumHeadersSource {
|
||||
/// Ethereum node client.
|
||||
client: EthereumClient,
|
||||
}
|
||||
|
||||
impl EthereumHeadersSource {
|
||||
fn new(client: EthereumClient) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for EthereumHeadersSource {
|
||||
type Error = RpcError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), RpcError> {
|
||||
self.client.reconnect();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SourceClient<EthereumHeadersSyncPipeline> for EthereumHeadersSource {
|
||||
async fn best_block_number(&self) -> Result<u64, RpcError> {
|
||||
// we **CAN** continue to relay headers if Ethereum node is out of sync, because
|
||||
// Substrate node may be missing headers that are already available at the Ethereum
|
||||
|
||||
self.client.best_block_number().await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn header_by_hash(&self, hash: HeaderHash) -> Result<Header, RpcError> {
|
||||
self.client
|
||||
.header_by_hash(hash)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn header_by_number(&self, number: u64) -> Result<Header, RpcError> {
|
||||
self.client
|
||||
.header_by_number(number)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn header_completion(&self, id: EthereumHeaderId) -> Result<(EthereumHeaderId, Option<()>), RpcError> {
|
||||
Ok((id, None))
|
||||
}
|
||||
|
||||
async fn header_extra(
|
||||
&self,
|
||||
id: EthereumHeaderId,
|
||||
header: QueuedEthereumHeader,
|
||||
) -> Result<(EthereumHeaderId, Vec<Receipt>), RpcError> {
|
||||
self.client
|
||||
.transaction_receipts(id, header.header().transactions.clone())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SubstrateHeadersTarget {
|
||||
/// Substrate node client.
|
||||
client: SubstrateClient<Rialto>,
|
||||
/// Whether we want to submit signed (true), or unsigned (false) transactions.
|
||||
sign_transactions: bool,
|
||||
/// Substrate signing params.
|
||||
sign_params: RialtoSigningParams,
|
||||
/// Bridge instance used in Ethereum to Substrate sync.
|
||||
bridge_instance: Arc<dyn BridgeInstance>,
|
||||
}
|
||||
|
||||
impl SubstrateHeadersTarget {
|
||||
fn new(
|
||||
client: SubstrateClient<Rialto>,
|
||||
sign_transactions: bool,
|
||||
sign_params: RialtoSigningParams,
|
||||
bridge_instance: Arc<dyn BridgeInstance>,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
sign_transactions,
|
||||
sign_params,
|
||||
bridge_instance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for SubstrateHeadersTarget {
|
||||
type Error = RpcError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), RpcError> {
|
||||
Ok(self.client.reconnect().await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TargetClient<EthereumHeadersSyncPipeline> for SubstrateHeadersTarget {
|
||||
async fn best_header_id(&self) -> Result<EthereumHeaderId, RpcError> {
|
||||
// we can't continue to relay headers if Substrate node is out of sync, because
|
||||
// it may have already received (some of) headers that we're going to relay
|
||||
self.client.ensure_synced().await?;
|
||||
|
||||
self.client.best_ethereum_block().await
|
||||
}
|
||||
|
||||
async fn is_known_header(&self, id: EthereumHeaderId) -> Result<(EthereumHeaderId, bool), RpcError> {
|
||||
Ok((id, self.client.ethereum_header_known(id).await?))
|
||||
}
|
||||
|
||||
async fn submit_headers(&self, headers: Vec<QueuedEthereumHeader>) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
|
||||
let (sign_params, bridge_instance, sign_transactions) = (
|
||||
self.sign_params.clone(),
|
||||
self.bridge_instance.clone(),
|
||||
self.sign_transactions,
|
||||
);
|
||||
self.client
|
||||
.submit_ethereum_headers(sign_params, bridge_instance, headers, sign_transactions)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn incomplete_headers_ids(&self) -> Result<HashSet<EthereumHeaderId>, RpcError> {
|
||||
Ok(HashSet::new())
|
||||
}
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
async fn complete_header(&self, id: EthereumHeaderId, _completion: ()) -> Result<EthereumHeaderId, RpcError> {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn requires_extra(&self, header: QueuedEthereumHeader) -> Result<(EthereumHeaderId, bool), RpcError> {
|
||||
// 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 id = header.header().id();
|
||||
let sub_eth_header = into_substrate_ethereum_header(header.header());
|
||||
Ok((id, self.client.ethereum_receipts_required(sub_eth_header).await?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Ethereum headers synchronization.
|
||||
pub fn run(params: EthereumSyncParams) -> Result<(), RpcError> {
|
||||
let EthereumSyncParams {
|
||||
eth_params,
|
||||
sub_params,
|
||||
sub_sign,
|
||||
sync_params,
|
||||
metrics_params,
|
||||
instance,
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params);
|
||||
let sub_client = async_std::task::block_on(async { SubstrateClient::<Rialto>::new(sub_params).await })?;
|
||||
|
||||
let sign_sub_transactions = match sync_params.target_tx_mode {
|
||||
TargetTransactionMode::Signed | TargetTransactionMode::Backup => true,
|
||||
TargetTransactionMode::Unsigned => false,
|
||||
};
|
||||
|
||||
let source = EthereumHeadersSource::new(eth_client);
|
||||
let target = SubstrateHeadersTarget::new(sub_client, sign_sub_transactions, sub_sign, instance);
|
||||
|
||||
headers_relay::sync_loop::run(
|
||||
source,
|
||||
consts::ETHEREUM_TICK_INTERVAL,
|
||||
target,
|
||||
Rialto::AVERAGE_BLOCK_INTERVAL,
|
||||
(),
|
||||
sync_params,
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 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/>.
|
||||
|
||||
//! The PoA Bridge Pallet provides a way to include multiple instances of itself in a runtime. When
|
||||
//! synchronizing a Substrate chain which can include multiple instances of the bridge pallet we
|
||||
//! must somehow decide which of the instances to sync.
|
||||
//!
|
||||
//! Note that each instance of the bridge pallet is coupled with an instance of the currency exchange
|
||||
//! pallet. We must also have a way to create `Call`s for the correct currency exchange instance.
|
||||
//!
|
||||
//! This module helps by preparing the correct `Call`s for each of the different pallet instances.
|
||||
|
||||
use crate::ethereum_sync_loop::QueuedEthereumHeader;
|
||||
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
|
||||
|
||||
use rialto_runtime::exchange::EthereumTransactionInclusionProof as Proof;
|
||||
use rialto_runtime::Call;
|
||||
|
||||
/// Interface for `Calls` which are needed to correctly sync the bridge.
|
||||
///
|
||||
/// Each instance of the bridge and currency exchange pallets in the bridge runtime requires similar
|
||||
/// but slightly different `Call` in order to be synchronized.
|
||||
pub trait BridgeInstance: Send + Sync + std::fmt::Debug {
|
||||
/// Used to build a `Call` for importing signed headers to a Substrate runtime.
|
||||
fn build_signed_header_call(&self, headers: Vec<QueuedEthereumHeader>) -> Call;
|
||||
/// Used to build a `Call` for importing an unsigned header to a Substrate runtime.
|
||||
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call;
|
||||
/// Used to build a `Call` for importing peer transactions to a Substrate runtime.
|
||||
fn build_currency_exchange_call(&self, proof: Proof) -> Call;
|
||||
}
|
||||
|
||||
/// Corresponds to the Rialto instance used in the bridge runtime.
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct RialtoPoA;
|
||||
|
||||
impl BridgeInstance for RialtoPoA {
|
||||
fn build_signed_header_call(&self, headers: Vec<QueuedEthereumHeader>) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_signed_headers(
|
||||
headers
|
||||
.into_iter()
|
||||
.map(|header| {
|
||||
(
|
||||
into_substrate_ethereum_header(&header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
rialto_runtime::Call::BridgeRialtoPoA(pallet_call)
|
||||
}
|
||||
|
||||
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_unsigned_header(
|
||||
into_substrate_ethereum_header(&header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
);
|
||||
|
||||
rialto_runtime::Call::BridgeRialtoPoA(pallet_call)
|
||||
}
|
||||
|
||||
fn build_currency_exchange_call(&self, proof: Proof) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeCurrencyExchangeCall::import_peer_transaction(proof);
|
||||
rialto_runtime::Call::BridgeRialtoCurrencyExchange(pallet_call)
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the Kovan instance used in the bridge runtime.
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Kovan;
|
||||
|
||||
impl BridgeInstance for Kovan {
|
||||
fn build_signed_header_call(&self, headers: Vec<QueuedEthereumHeader>) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_signed_headers(
|
||||
headers
|
||||
.into_iter()
|
||||
.map(|header| {
|
||||
(
|
||||
into_substrate_ethereum_header(header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
rialto_runtime::Call::BridgeKovan(pallet_call)
|
||||
}
|
||||
|
||||
fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeEthPoACall::import_unsigned_header(
|
||||
into_substrate_ethereum_header(header.header()),
|
||||
into_substrate_ethereum_receipts(header.extra()),
|
||||
);
|
||||
|
||||
rialto_runtime::Call::BridgeKovan(pallet_call)
|
||||
}
|
||||
|
||||
fn build_currency_exchange_call(&self, proof: Proof) -> Call {
|
||||
let pallet_call = rialto_runtime::BridgeCurrencyExchangeCall::import_peer_transaction(proof);
|
||||
rialto_runtime::Call::BridgeKovanCurrencyExchange(pallet_call)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// 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/>.
|
||||
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
mod ethereum_client;
|
||||
mod ethereum_deploy_contract;
|
||||
mod ethereum_exchange;
|
||||
mod ethereum_exchange_submit;
|
||||
mod ethereum_sync_loop;
|
||||
mod instances;
|
||||
mod rialto_client;
|
||||
mod rpc_errors;
|
||||
mod substrate_sync_loop;
|
||||
mod substrate_types;
|
||||
|
||||
use ethereum_deploy_contract::EthereumDeployContractParams;
|
||||
use ethereum_exchange::EthereumExchangeParams;
|
||||
use ethereum_exchange_submit::EthereumExchangeSubmitParams;
|
||||
use ethereum_sync_loop::EthereumSyncParams;
|
||||
use headers_relay::sync::TargetTransactionMode;
|
||||
use hex_literal::hex;
|
||||
use instances::{BridgeInstance, Kovan, RialtoPoA};
|
||||
use relay_utils::{initialize::initialize_relay, metrics::MetricsParams};
|
||||
use secp256k1::SecretKey;
|
||||
use sp_core::crypto::Pair;
|
||||
use substrate_sync_loop::SubstrateSyncParams;
|
||||
|
||||
use headers_relay::sync::HeadersSyncParams;
|
||||
use relay_ethereum_client::{ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams};
|
||||
use relay_rialto_client::SigningParams as RialtoSigningParams;
|
||||
use relay_substrate_client::ConnectionParams as SubstrateConnectionParams;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
initialize_relay();
|
||||
|
||||
let yaml = clap::load_yaml!("cli.yml");
|
||||
let matches = clap::App::from_yaml(yaml).get_matches();
|
||||
match matches.subcommand() {
|
||||
("eth-to-sub", Some(eth_to_sub_matches)) => {
|
||||
log::info!(target: "bridge", "Starting ETH ➡ SUB relay.");
|
||||
if ethereum_sync_loop::run(match ethereum_sync_params(ð_to_sub_matches) {
|
||||
Ok(ethereum_sync_params) => ethereum_sync_params,
|
||||
Err(err) => {
|
||||
log::error!(target: "bridge", "Error parsing parameters: {}", err);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
log::error!(target: "bridge", "Unable to get Substrate genesis block for Ethereum sync.");
|
||||
};
|
||||
}
|
||||
("sub-to-eth", Some(sub_to_eth_matches)) => {
|
||||
log::info!(target: "bridge", "Starting SUB ➡ ETH relay.");
|
||||
if substrate_sync_loop::run(match substrate_sync_params(&sub_to_eth_matches) {
|
||||
Ok(substrate_sync_params) => substrate_sync_params,
|
||||
Err(err) => {
|
||||
log::error!(target: "bridge", "Error parsing parameters: {}", err);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
log::error!(target: "bridge", "Unable to get Substrate genesis block for Substrate sync.");
|
||||
};
|
||||
}
|
||||
("eth-deploy-contract", Some(eth_deploy_matches)) => {
|
||||
log::info!(target: "bridge", "Deploying ETH contracts.");
|
||||
ethereum_deploy_contract::run(match ethereum_deploy_contract_params(ð_deploy_matches) {
|
||||
Ok(ethereum_deploy_params) => ethereum_deploy_params,
|
||||
Err(err) => {
|
||||
log::error!(target: "bridge", "Error during contract deployment: {}", err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
("eth-submit-exchange-tx", Some(eth_exchange_submit_matches)) => {
|
||||
log::info!(target: "bridge", "Submitting ETH ➡ SUB exchange transaction.");
|
||||
ethereum_exchange_submit::run(match ethereum_exchange_submit_params(ð_exchange_submit_matches) {
|
||||
Ok(eth_exchange_submit_params) => eth_exchange_submit_params,
|
||||
Err(err) => {
|
||||
log::error!(target: "bridge", "Error submitting Eethereum exchange transaction: {}", err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
("eth-exchange-sub", Some(eth_exchange_matches)) => {
|
||||
log::info!(target: "bridge", "Starting ETH ➡ SUB exchange transactions relay.");
|
||||
ethereum_exchange::run(match ethereum_exchange_params(ð_exchange_matches) {
|
||||
Ok(eth_exchange_params) => eth_exchange_params,
|
||||
Err(err) => {
|
||||
log::error!(target: "bridge", "Error relaying Ethereum transactions proofs: {}", err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
("", _) => {
|
||||
log::error!(target: "bridge", "No subcommand specified");
|
||||
}
|
||||
_ => unreachable!("all possible subcommands are checked above; qed"),
|
||||
}
|
||||
}
|
||||
|
||||
fn ethereum_connection_params(matches: &clap::ArgMatches) -> Result<EthereumConnectionParams, String> {
|
||||
let mut params = EthereumConnectionParams::default();
|
||||
if let Some(eth_host) = matches.value_of("eth-host") {
|
||||
params.host = eth_host.into();
|
||||
}
|
||||
if let Some(eth_port) = matches.value_of("eth-port") {
|
||||
params.port = eth_port
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse eth-port: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_signing_params(matches: &clap::ArgMatches) -> Result<EthereumSigningParams, String> {
|
||||
let mut params = EthereumSigningParams::default();
|
||||
if let Some(eth_signer) = matches.value_of("eth-signer") {
|
||||
params.signer =
|
||||
SecretKey::parse_slice(&hex::decode(eth_signer).map_err(|e| format!("Failed to parse eth-signer: {}", e))?)
|
||||
.map_err(|e| format!("Invalid eth-signer: {}", e))?;
|
||||
}
|
||||
if let Some(eth_chain_id) = matches.value_of("eth-chain-id") {
|
||||
params.chain_id = eth_chain_id
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Failed to parse eth-chain-id: {}", 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") {
|
||||
params.host = sub_host.into();
|
||||
}
|
||||
if let Some(sub_port) = matches.value_of("sub-port") {
|
||||
params.port = sub_port
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse sub-port: {}", e))?;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn rialto_signing_params(matches: &clap::ArgMatches) -> Result<RialtoSigningParams, String> {
|
||||
let mut params = RialtoSigningParams::default();
|
||||
if let Some(sub_signer) = matches.value_of("sub-signer") {
|
||||
let sub_signer_password = matches.value_of("sub-signer-password");
|
||||
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<EthereumSyncParams, String> {
|
||||
use crate::ethereum_sync_loop::consts::*;
|
||||
|
||||
let mut sync_params = HeadersSyncParams {
|
||||
max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD,
|
||||
max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS,
|
||||
max_headers_in_single_submit: MAX_HEADERS_IN_SINGLE_SUBMIT,
|
||||
max_headers_size_in_single_submit: MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT,
|
||||
prune_depth: PRUNE_DEPTH,
|
||||
target_tx_mode: TargetTransactionMode::Signed,
|
||||
};
|
||||
|
||||
match matches.value_of("sub-tx-mode") {
|
||||
Some("signed") => sync_params.target_tx_mode = TargetTransactionMode::Signed,
|
||||
Some("unsigned") => {
|
||||
sync_params.target_tx_mode = TargetTransactionMode::Unsigned;
|
||||
|
||||
// tx pool won't accept too much unsigned transactions
|
||||
sync_params.max_headers_in_submitted_status = 10;
|
||||
}
|
||||
Some("backup") => sync_params.target_tx_mode = TargetTransactionMode::Backup,
|
||||
Some(mode) => return Err(format!("Invalid sub-tx-mode: {}", mode)),
|
||||
None => sync_params.target_tx_mode = TargetTransactionMode::Signed,
|
||||
}
|
||||
|
||||
let params = EthereumSyncParams {
|
||||
eth_params: ethereum_connection_params(matches)?,
|
||||
sub_params: substrate_connection_params(matches)?,
|
||||
sub_sign: rialto_signing_params(matches)?,
|
||||
metrics_params: metrics_params(matches)?,
|
||||
instance: instance_params(matches)?,
|
||||
sync_params,
|
||||
};
|
||||
|
||||
log::debug!(target: "bridge", "Ethereum sync params: {:?}", params);
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncParams, String> {
|
||||
use crate::substrate_sync_loop::consts::*;
|
||||
|
||||
let eth_contract_address: relay_ethereum_client::types::Address =
|
||||
if let Some(eth_contract) = matches.value_of("eth-contract") {
|
||||
eth_contract.parse().map_err(|e| format!("{}", e))?
|
||||
} else {
|
||||
"731a10897d267e19b34503ad902d0a29173ba4b1"
|
||||
.parse()
|
||||
.expect("address is hardcoded, thus valid; qed")
|
||||
};
|
||||
|
||||
let params = SubstrateSyncParams {
|
||||
sub_params: substrate_connection_params(matches)?,
|
||||
eth_params: ethereum_connection_params(matches)?,
|
||||
eth_sign: ethereum_signing_params(matches)?,
|
||||
metrics_params: metrics_params(matches)?,
|
||||
sync_params: HeadersSyncParams {
|
||||
max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD,
|
||||
max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS,
|
||||
max_headers_in_single_submit: MAX_SUBMITTED_HEADERS,
|
||||
max_headers_size_in_single_submit: std::usize::MAX,
|
||||
prune_depth: PRUNE_DEPTH,
|
||||
target_tx_mode: TargetTransactionMode::Signed,
|
||||
},
|
||||
eth_contract_address,
|
||||
};
|
||||
|
||||
log::debug!(target: "bridge", "Substrate sync params: {:?}", params);
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_deploy_contract_params(matches: &clap::ArgMatches) -> Result<EthereumDeployContractParams, String> {
|
||||
let eth_contract_code = parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| {
|
||||
hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")).expect("code is hardcoded, thus valid; qed")
|
||||
});
|
||||
let sub_initial_authorities_set_id = match matches.value_of("sub-authorities-set-id") {
|
||||
Some(sub_initial_authorities_set_id) => Some(
|
||||
sub_initial_authorities_set_id
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse sub-authorities-set-id: {}", e))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?;
|
||||
let sub_initial_header = parse_hex_argument(matches, "sub-initial-header")?;
|
||||
|
||||
let params = EthereumDeployContractParams {
|
||||
eth_params: ethereum_connection_params(matches)?,
|
||||
eth_sign: ethereum_signing_params(matches)?,
|
||||
sub_params: substrate_connection_params(matches)?,
|
||||
sub_initial_authorities_set_id,
|
||||
sub_initial_authorities_set,
|
||||
sub_initial_header,
|
||||
eth_contract_code,
|
||||
};
|
||||
|
||||
log::debug!(target: "bridge", "Deploy params: {:?}", params);
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result<EthereumExchangeSubmitParams, String> {
|
||||
let eth_nonce = if let Some(eth_nonce) = matches.value_of("eth-nonce") {
|
||||
Some(
|
||||
relay_ethereum_client::types::U256::from_dec_str(ð_nonce)
|
||||
.map_err(|e| format!("Failed to parse eth-nonce: {}", e))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let eth_amount = if let Some(eth_amount) = matches.value_of("eth-amount") {
|
||||
eth_amount
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse eth-amount: {}", e))?
|
||||
} else {
|
||||
// This is in Wei, represents 1 ETH
|
||||
1_000_000_000_000_000_000_u64.into()
|
||||
};
|
||||
|
||||
// This is the well-known Substrate account of Ferdie
|
||||
let default_recepient = hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c");
|
||||
|
||||
let sub_recipient = if let Some(sub_recipient) = matches.value_of("sub-recipient") {
|
||||
hex::decode(&sub_recipient)
|
||||
.map_err(|err| err.to_string())
|
||||
.and_then(|vsub_recipient| {
|
||||
let expected_len = default_recepient.len();
|
||||
if expected_len != vsub_recipient.len() {
|
||||
Err(format!("invalid length. Expected {} bytes", expected_len))
|
||||
} else {
|
||||
let mut sub_recipient = default_recepient;
|
||||
sub_recipient.copy_from_slice(&vsub_recipient[..expected_len]);
|
||||
Ok(sub_recipient)
|
||||
}
|
||||
})
|
||||
.map_err(|e| format!("Failed to parse sub-recipient: {}", e))?
|
||||
} else {
|
||||
default_recepient
|
||||
};
|
||||
|
||||
let params = EthereumExchangeSubmitParams {
|
||||
eth_params: ethereum_connection_params(matches)?,
|
||||
eth_sign: ethereum_signing_params(matches)?,
|
||||
eth_nonce,
|
||||
eth_amount,
|
||||
sub_recipient,
|
||||
};
|
||||
|
||||
log::debug!(target: "bridge", "Submit Ethereum exchange tx params: {:?}", params);
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result<EthereumExchangeParams, String> {
|
||||
let mode = match matches.value_of("eth-tx-hash") {
|
||||
Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single(
|
||||
eth_tx_hash
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse eth-tx-hash: {}", e))?,
|
||||
),
|
||||
None => ethereum_exchange::ExchangeRelayMode::Auto(match matches.value_of("eth-start-with-block") {
|
||||
Some(eth_start_with_block) => Some(
|
||||
eth_start_with_block
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse eth-start-with-block: {}", e))?,
|
||||
),
|
||||
None => None,
|
||||
}),
|
||||
};
|
||||
|
||||
let params = EthereumExchangeParams {
|
||||
eth_params: ethereum_connection_params(matches)?,
|
||||
sub_params: substrate_connection_params(matches)?,
|
||||
sub_sign: rialto_signing_params(matches)?,
|
||||
metrics_params: metrics_params(matches)?,
|
||||
instance: instance_params(matches)?,
|
||||
mode,
|
||||
};
|
||||
|
||||
log::debug!(target: "bridge", "Ethereum exchange params: {:?}", params);
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn metrics_params(matches: &clap::ArgMatches) -> Result<Option<MetricsParams>, String> {
|
||||
if matches.is_present("no-prometheus") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut metrics_params = MetricsParams::default();
|
||||
|
||||
if let Some(prometheus_host) = matches.value_of("prometheus-host") {
|
||||
metrics_params.host = prometheus_host.into();
|
||||
}
|
||||
if let Some(prometheus_port) = matches.value_of("prometheus-port") {
|
||||
metrics_params.port = prometheus_port
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse prometheus-port: {}", e))?;
|
||||
}
|
||||
|
||||
Ok(Some(metrics_params))
|
||||
}
|
||||
|
||||
fn instance_params(matches: &clap::ArgMatches) -> Result<Arc<dyn BridgeInstance>, String> {
|
||||
let instance = if let Some(instance) = matches.value_of("sub-pallet-instance") {
|
||||
match instance.to_lowercase().as_str() {
|
||||
"rialto" => Arc::new(RialtoPoA) as Arc<dyn BridgeInstance>,
|
||||
"kovan" => Arc::new(Kovan),
|
||||
_ => return Err("Unsupported bridge pallet instance".to_string()),
|
||||
}
|
||||
} else {
|
||||
unreachable!("CLI config enforces a default instance, can never be None")
|
||||
};
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> Result<Option<Vec<u8>>, String> {
|
||||
match matches.value_of(arg) {
|
||||
Some(value) => Ok(Some(
|
||||
hex::decode(value).map_err(|e| format!("Failed to parse {}: {}", arg, e))?,
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
// Details: https://github.com/paritytech/parity-bridges-common/issues/118
|
||||
#[test]
|
||||
fn async_std_sleep_works() {
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
local_pool.run_until(async move {
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
// 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_sync_loop::QueuedEthereumHeader;
|
||||
use crate::instances::BridgeInstance;
|
||||
use crate::rpc_errors::RpcError;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_eth_poa::AuraHeader as SubstrateEthereumHeader;
|
||||
use codec::{Decode, Encode};
|
||||
use headers_relay::sync_types::SubmittedHeaders;
|
||||
use relay_ethereum_client::types::HeaderId as EthereumHeaderId;
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{Client as SubstrateClient, TransactionSignScheme};
|
||||
use relay_utils::HeaderId;
|
||||
use sp_core::{crypto::Pair, Bytes};
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
const ETH_API_IMPORT_REQUIRES_RECEIPTS: &str = "RialtoPoAHeaderApi_is_import_requires_receipts";
|
||||
const ETH_API_IS_KNOWN_BLOCK: &str = "RialtoPoAHeaderApi_is_known_block";
|
||||
const ETH_API_BEST_BLOCK: &str = "RialtoPoAHeaderApi_best_block";
|
||||
const ETH_API_BEST_FINALIZED_BLOCK: &str = "RialtoPoAHeaderApi_finalized_block";
|
||||
const EXCH_API_FILTER_TRANSACTION_PROOF: &str = "RialtoCurrencyExchangeApi_filter_transaction_proof";
|
||||
|
||||
type RpcResult<T> = std::result::Result<T, RpcError>;
|
||||
|
||||
/// A trait which contains methods that work by using multiple low-level RPCs, or more complicated
|
||||
/// interactions involving, for example, an Ethereum bridge module.
|
||||
#[async_trait]
|
||||
pub trait SubstrateHighLevelRpc {
|
||||
/// Returns best Ethereum block that Substrate runtime knows of.
|
||||
async fn best_ethereum_block(&self) -> RpcResult<EthereumHeaderId>;
|
||||
/// Returns best finalized Ethereum block that Substrate runtime knows of.
|
||||
async fn best_ethereum_finalized_block(&self) -> RpcResult<EthereumHeaderId>;
|
||||
/// Returns whether or not transactions receipts are required for Ethereum header submission.
|
||||
async fn ethereum_receipts_required(&self, header: SubstrateEthereumHeader) -> RpcResult<bool>;
|
||||
/// Returns whether or not the given Ethereum header is known to the Substrate runtime.
|
||||
async fn ethereum_header_known(&self, header_id: EthereumHeaderId) -> RpcResult<bool>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SubstrateHighLevelRpc for SubstrateClient<Rialto> {
|
||||
async fn best_ethereum_block(&self) -> RpcResult<EthereumHeaderId> {
|
||||
let call = ETH_API_BEST_BLOCK.to_string();
|
||||
let data = Bytes(Vec::new());
|
||||
|
||||
let encoded_response = self.state_call(call, data, None).await?;
|
||||
let decoded_response: (u64, bp_eth_poa::H256) = Decode::decode(&mut &encoded_response.0[..])?;
|
||||
|
||||
let best_header_id = HeaderId(decoded_response.0, decoded_response.1);
|
||||
Ok(best_header_id)
|
||||
}
|
||||
|
||||
async fn best_ethereum_finalized_block(&self) -> RpcResult<EthereumHeaderId> {
|
||||
let call = ETH_API_BEST_FINALIZED_BLOCK.to_string();
|
||||
let data = Bytes(Vec::new());
|
||||
|
||||
let encoded_response = self.state_call(call, data, None).await?;
|
||||
let decoded_response: (u64, bp_eth_poa::H256) = Decode::decode(&mut &encoded_response.0[..])?;
|
||||
|
||||
let best_header_id = HeaderId(decoded_response.0, decoded_response.1);
|
||||
Ok(best_header_id)
|
||||
}
|
||||
|
||||
async fn ethereum_receipts_required(&self, header: SubstrateEthereumHeader) -> RpcResult<bool> {
|
||||
let call = ETH_API_IMPORT_REQUIRES_RECEIPTS.to_string();
|
||||
let data = Bytes(header.encode());
|
||||
|
||||
let encoded_response = self.state_call(call, data, None).await?;
|
||||
let receipts_required: bool = Decode::decode(&mut &encoded_response.0[..])?;
|
||||
|
||||
Ok(receipts_required)
|
||||
}
|
||||
|
||||
// The Substrate module could prune old headers. So this function could return false even
|
||||
// if header is synced. And we'll mark corresponding Ethereum header as Orphan.
|
||||
//
|
||||
// But when we read the 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.
|
||||
async fn ethereum_header_known(&self, header_id: EthereumHeaderId) -> RpcResult<bool> {
|
||||
let call = ETH_API_IS_KNOWN_BLOCK.to_string();
|
||||
let data = Bytes(header_id.1.encode());
|
||||
|
||||
let encoded_response = self.state_call(call, data, None).await?;
|
||||
let is_known_block: bool = Decode::decode(&mut &encoded_response.0[..])?;
|
||||
|
||||
Ok(is_known_block)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for RPC calls which are used to submit Ethereum headers to a Substrate
|
||||
/// runtime. These are typically calls which use a combination of other low-level RPC
|
||||
/// calls.
|
||||
#[async_trait]
|
||||
pub trait SubmitEthereumHeaders {
|
||||
/// Submits Ethereum header to Substrate runtime.
|
||||
async fn submit_ethereum_headers(
|
||||
&self,
|
||||
params: RialtoSigningParams,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
headers: Vec<QueuedEthereumHeader>,
|
||||
sign_transactions: bool,
|
||||
) -> SubmittedHeaders<EthereumHeaderId, RpcError>;
|
||||
|
||||
/// Submits signed Ethereum header to Substrate runtime.
|
||||
async fn submit_signed_ethereum_headers(
|
||||
&self,
|
||||
params: RialtoSigningParams,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
headers: Vec<QueuedEthereumHeader>,
|
||||
) -> SubmittedHeaders<EthereumHeaderId, RpcError>;
|
||||
|
||||
/// Submits unsigned Ethereum header to Substrate runtime.
|
||||
async fn submit_unsigned_ethereum_headers(
|
||||
&self,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
headers: Vec<QueuedEthereumHeader>,
|
||||
) -> SubmittedHeaders<EthereumHeaderId, RpcError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SubmitEthereumHeaders for SubstrateClient<Rialto> {
|
||||
async fn submit_ethereum_headers(
|
||||
&self,
|
||||
params: RialtoSigningParams,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
headers: Vec<QueuedEthereumHeader>,
|
||||
sign_transactions: bool,
|
||||
) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
|
||||
if sign_transactions {
|
||||
self.submit_signed_ethereum_headers(params, instance, headers).await
|
||||
} else {
|
||||
self.submit_unsigned_ethereum_headers(instance, headers).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn submit_signed_ethereum_headers(
|
||||
&self,
|
||||
params: RialtoSigningParams,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
headers: Vec<QueuedEthereumHeader>,
|
||||
) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
|
||||
let ids = headers.iter().map(|header| header.id()).collect();
|
||||
let submission_result = async {
|
||||
let account_id = params.signer.public().as_array_ref().clone().into();
|
||||
let nonce = self.next_account_index(account_id).await?;
|
||||
|
||||
let call = instance.build_signed_header_call(headers);
|
||||
let transaction = Rialto::sign_transaction(self, ¶ms.signer, nonce, call);
|
||||
|
||||
let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?;
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
match submission_result {
|
||||
Ok(_) => SubmittedHeaders {
|
||||
submitted: ids,
|
||||
incomplete: Vec::new(),
|
||||
rejected: Vec::new(),
|
||||
fatal_error: None,
|
||||
},
|
||||
Err(error) => SubmittedHeaders {
|
||||
submitted: Vec::new(),
|
||||
incomplete: Vec::new(),
|
||||
rejected: ids,
|
||||
fatal_error: Some(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn submit_unsigned_ethereum_headers(
|
||||
&self,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
headers: Vec<QueuedEthereumHeader>,
|
||||
) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
|
||||
let mut ids = headers.iter().map(|header| header.id()).collect::<VecDeque<_>>();
|
||||
let mut submitted_headers = SubmittedHeaders::default();
|
||||
|
||||
for header in headers {
|
||||
let id = ids.pop_front().expect("both collections have same size; qed");
|
||||
|
||||
let call = instance.build_unsigned_header_call(header);
|
||||
let transaction = create_unsigned_submit_transaction(call);
|
||||
|
||||
match self.submit_extrinsic(Bytes(transaction.encode())).await {
|
||||
Ok(_) => submitted_headers.submitted.push(id),
|
||||
Err(error) => {
|
||||
submitted_headers.rejected.push(id);
|
||||
submitted_headers.rejected.extend(ids);
|
||||
submitted_headers.fatal_error = Some(error.into());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submitted_headers
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for RPC calls which are used to submit proof of Ethereum exchange transaction to a
|
||||
/// Substrate runtime. These are typically calls which use a combination of other low-level RPC
|
||||
/// calls.
|
||||
#[async_trait]
|
||||
pub trait SubmitEthereumExchangeTransactionProof {
|
||||
/// Pre-verify Ethereum exchange transaction proof.
|
||||
async fn verify_exchange_transaction_proof(
|
||||
&self,
|
||||
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
|
||||
) -> RpcResult<bool>;
|
||||
/// Submits Ethereum exchange transaction proof to Substrate runtime.
|
||||
async fn submit_exchange_transaction_proof(
|
||||
&self,
|
||||
params: RialtoSigningParams,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
|
||||
) -> RpcResult<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SubmitEthereumExchangeTransactionProof for SubstrateClient<Rialto> {
|
||||
async fn verify_exchange_transaction_proof(
|
||||
&self,
|
||||
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
|
||||
) -> RpcResult<bool> {
|
||||
let call = EXCH_API_FILTER_TRANSACTION_PROOF.to_string();
|
||||
let data = Bytes(proof.encode());
|
||||
|
||||
let encoded_response = self.state_call(call, data, None).await?;
|
||||
let is_allowed: bool = Decode::decode(&mut &encoded_response.0[..])?;
|
||||
|
||||
Ok(is_allowed)
|
||||
}
|
||||
|
||||
async fn submit_exchange_transaction_proof(
|
||||
&self,
|
||||
params: RialtoSigningParams,
|
||||
instance: Arc<dyn BridgeInstance>,
|
||||
proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
|
||||
) -> RpcResult<()> {
|
||||
let account_id = params.signer.public().as_array_ref().clone().into();
|
||||
let nonce = self.next_account_index(account_id).await?;
|
||||
|
||||
let call = instance.build_currency_exchange_call(proof);
|
||||
let transaction = Rialto::sign_transaction(self, ¶ms.signer, nonce, call);
|
||||
|
||||
let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create unsigned Substrate transaction for submitting Ethereum header.
|
||||
fn create_unsigned_submit_transaction(call: rialto_runtime::Call) -> rialto_runtime::UncheckedExtrinsic {
|
||||
rialto_runtime::UncheckedExtrinsic::new_unsigned(call)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 relay_ethereum_client::Error as EthereumNodeError;
|
||||
use relay_substrate_client::Error as SubstrateNodeError;
|
||||
use relay_utils::MaybeConnectionError;
|
||||
|
||||
/// Contains common errors that can occur when
|
||||
/// interacting with a Substrate or Ethereum node
|
||||
/// through RPC.
|
||||
#[derive(Debug)]
|
||||
pub enum RpcError {
|
||||
/// The arguments to the RPC method failed to serialize.
|
||||
Serialization(serde_json::Error),
|
||||
/// An error occured when interacting with an Ethereum node.
|
||||
Ethereum(EthereumNodeError),
|
||||
/// An error occured when interacting with a Substrate node.
|
||||
Substrate(SubstrateNodeError),
|
||||
}
|
||||
|
||||
impl From<RpcError> for String {
|
||||
fn from(err: RpcError) -> Self {
|
||||
match err {
|
||||
RpcError::Serialization(e) => e.to_string(),
|
||||
RpcError::Ethereum(e) => e.to_string(),
|
||||
RpcError::Substrate(e) => e.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for RpcError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
Self::Serialization(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EthereumNodeError> for RpcError {
|
||||
fn from(err: EthereumNodeError) -> Self {
|
||||
Self::Ethereum(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SubstrateNodeError> for RpcError {
|
||||
fn from(err: SubstrateNodeError) -> Self {
|
||||
Self::Substrate(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ethabi::Error> for RpcError {
|
||||
fn from(err: ethabi::Error) -> Self {
|
||||
Self::Ethereum(EthereumNodeError::ResponseParseFailed(format!("{}", err)))
|
||||
}
|
||||
}
|
||||
|
||||
impl MaybeConnectionError for RpcError {
|
||||
fn is_connection_error(&self) -> bool {
|
||||
match self {
|
||||
RpcError::Ethereum(ref error) => error.is_connection_error(),
|
||||
RpcError::Substrate(ref error) => error.is_connection_error(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<codec::Error> for RpcError {
|
||||
fn from(err: codec::Error) -> Self {
|
||||
Self::Substrate(SubstrateNodeError::ResponseParseFailed(err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// 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/>.
|
||||
|
||||
//! Rialto-Substrate -> Ethereum PoA synchronization.
|
||||
|
||||
use crate::ethereum_client::EthereumHighLevelRpc;
|
||||
use crate::rpc_errors::RpcError;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::Encode;
|
||||
use headers_relay::{
|
||||
sync::HeadersSyncParams,
|
||||
sync_loop::TargetClient,
|
||||
sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader, SubmittedHeaders},
|
||||
};
|
||||
use relay_ethereum_client::{
|
||||
types::Address, Client as EthereumClient, ConnectionParams as EthereumConnectionParams,
|
||||
SigningParams as EthereumSigningParams,
|
||||
};
|
||||
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SyncHeader as RialtoSyncHeader};
|
||||
use relay_substrate_client::{
|
||||
headers_source::HeadersSource, Chain as SubstrateChain, Client as SubstrateClient,
|
||||
ConnectionParams as SubstrateConnectionParams,
|
||||
};
|
||||
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient};
|
||||
use sp_runtime::Justification;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
|
||||
pub mod consts {
|
||||
use super::*;
|
||||
|
||||
/// Interval at which we check new Ethereum blocks.
|
||||
pub const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(5);
|
||||
/// Max Ethereum headers we want to have in all 'before-submitted' states.
|
||||
pub const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 8;
|
||||
/// Max Ethereum headers count we want to have in 'submitted' state.
|
||||
pub const MAX_SUBMITTED_HEADERS: usize = 4;
|
||||
/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned).
|
||||
pub const PRUNE_DEPTH: u32 = 256;
|
||||
}
|
||||
|
||||
/// Substrate synchronization parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct SubstrateSyncParams {
|
||||
/// Substrate connection params.
|
||||
pub sub_params: SubstrateConnectionParams,
|
||||
/// Ethereum connection params.
|
||||
pub eth_params: EthereumConnectionParams,
|
||||
/// Ethereum signing params.
|
||||
pub eth_sign: EthereumSigningParams,
|
||||
/// Ethereum bridge contract address.
|
||||
pub eth_contract_address: Address,
|
||||
/// Synchronization parameters.
|
||||
pub sync_params: HeadersSyncParams,
|
||||
/// Metrics parameters.
|
||||
pub metrics_params: Option<MetricsParams>,
|
||||
}
|
||||
|
||||
/// 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 = rialto_runtime::Hash;
|
||||
type Number = rialto_runtime::BlockNumber;
|
||||
type Header = RialtoSyncHeader;
|
||||
type Extra = ();
|
||||
type Completion = Justification;
|
||||
|
||||
fn estimate_size(source: &QueuedHeader<Self>) -> usize {
|
||||
source.header().encode().len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Queued substrate header ID.
|
||||
pub type QueuedRialtoHeader = QueuedHeader<SubstrateHeadersSyncPipeline>;
|
||||
|
||||
/// Rialto node as headers source.
|
||||
type SubstrateHeadersSource = HeadersSource<Rialto, SubstrateHeadersSyncPipeline>;
|
||||
|
||||
/// Ethereum client as Substrate headers target.
|
||||
#[derive(Clone)]
|
||||
struct EthereumHeadersTarget {
|
||||
/// Ethereum node client.
|
||||
client: EthereumClient,
|
||||
/// Bridge contract address.
|
||||
contract: Address,
|
||||
/// Ethereum signing params.
|
||||
sign_params: EthereumSigningParams,
|
||||
}
|
||||
|
||||
impl EthereumHeadersTarget {
|
||||
fn new(client: EthereumClient, contract: Address, sign_params: EthereumSigningParams) -> Self {
|
||||
Self {
|
||||
client,
|
||||
contract,
|
||||
sign_params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for EthereumHeadersTarget {
|
||||
type Error = RpcError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), RpcError> {
|
||||
self.client.reconnect();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TargetClient<SubstrateHeadersSyncPipeline> for EthereumHeadersTarget {
|
||||
async fn best_header_id(&self) -> Result<RialtoHeaderId, RpcError> {
|
||||
// we can't continue to relay headers if Ethereum node is out of sync, because
|
||||
// it may have already received (some of) headers that we're going to relay
|
||||
self.client.ensure_synced().await?;
|
||||
|
||||
self.client.best_substrate_block(self.contract).await
|
||||
}
|
||||
|
||||
async fn is_known_header(&self, id: RialtoHeaderId) -> Result<(RialtoHeaderId, bool), RpcError> {
|
||||
self.client.substrate_header_known(self.contract, id).await
|
||||
}
|
||||
|
||||
async fn submit_headers(&self, headers: Vec<QueuedRialtoHeader>) -> SubmittedHeaders<RialtoHeaderId, RpcError> {
|
||||
self.client
|
||||
.submit_substrate_headers(self.sign_params.clone(), self.contract, headers)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn incomplete_headers_ids(&self) -> Result<HashSet<RialtoHeaderId>, RpcError> {
|
||||
self.client.incomplete_substrate_headers(self.contract).await
|
||||
}
|
||||
|
||||
async fn complete_header(&self, id: RialtoHeaderId, completion: Justification) -> Result<RialtoHeaderId, RpcError> {
|
||||
self.client
|
||||
.complete_substrate_header(self.sign_params.clone(), self.contract, id, completion)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn requires_extra(&self, header: QueuedRialtoHeader) -> Result<(RialtoHeaderId, bool), RpcError> {
|
||||
Ok((header.header().id(), false))
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Substrate headers synchronization.
|
||||
pub fn run(params: SubstrateSyncParams) -> Result<(), RpcError> {
|
||||
let SubstrateSyncParams {
|
||||
sub_params,
|
||||
eth_params,
|
||||
eth_sign,
|
||||
eth_contract_address,
|
||||
sync_params,
|
||||
metrics_params,
|
||||
} = params;
|
||||
|
||||
let eth_client = EthereumClient::new(eth_params);
|
||||
let sub_client = async_std::task::block_on(async { SubstrateClient::<Rialto>::new(sub_params).await })?;
|
||||
|
||||
let target = EthereumHeadersTarget::new(eth_client, eth_contract_address, eth_sign);
|
||||
let source = SubstrateHeadersSource::new(sub_client);
|
||||
|
||||
headers_relay::sync_loop::run(
|
||||
source,
|
||||
Rialto::AVERAGE_BLOCK_INTERVAL,
|
||||
target,
|
||||
consts::ETHEREUM_TICK_INTERVAL,
|
||||
(),
|
||||
sync_params,
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 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/>.
|
||||
|
||||
//! Converting between Ethereum headers and bridge module types.
|
||||
|
||||
use bp_eth_poa::{
|
||||
AuraHeader as SubstrateEthereumHeader, LogEntry as SubstrateEthereumLogEntry, Receipt as SubstrateEthereumReceipt,
|
||||
TransactionOutcome as SubstrateEthereumTransactionOutcome,
|
||||
};
|
||||
use relay_ethereum_client::types::{
|
||||
Header as EthereumHeader, Receipt as EthereumReceipt, HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF,
|
||||
};
|
||||
|
||||
/// Convert Ethereum header into Ethereum header for Substrate.
|
||||
pub fn into_substrate_ethereum_header(header: &EthereumHeader) -> SubstrateEthereumHeader {
|
||||
SubstrateEthereumHeader {
|
||||
parent_hash: header.parent_hash,
|
||||
timestamp: header.timestamp.as_u64(),
|
||||
number: header.number.expect(ETHEREUM_HEADER_ID_PROOF).as_u64(),
|
||||
author: header.author,
|
||||
transactions_root: header.transactions_root,
|
||||
uncles_hash: header.uncles_hash,
|
||||
extra_data: header.extra_data.0.clone(),
|
||||
state_root: header.state_root,
|
||||
receipts_root: header.receipts_root,
|
||||
log_bloom: header.logs_bloom.unwrap_or_default().data().into(),
|
||||
gas_used: header.gas_used,
|
||||
gas_limit: header.gas_limit,
|
||||
difficulty: header.difficulty,
|
||||
seal: header.seal_fields.iter().map(|s| s.0.clone()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Ethereum transactions receipts into Ethereum transactions receipts for Substrate.
|
||||
pub fn into_substrate_ethereum_receipts(
|
||||
receipts: &Option<Vec<EthereumReceipt>>,
|
||||
) -> Option<Vec<SubstrateEthereumReceipt>> {
|
||||
receipts
|
||||
.as_ref()
|
||||
.map(|receipts| receipts.iter().map(into_substrate_ethereum_receipt).collect())
|
||||
}
|
||||
|
||||
/// Convert Ethereum transactions receipt into Ethereum transactions receipt for Substrate.
|
||||
pub fn into_substrate_ethereum_receipt(receipt: &EthereumReceipt) -> SubstrateEthereumReceipt {
|
||||
SubstrateEthereumReceipt {
|
||||
gas_used: receipt.cumulative_gas_used,
|
||||
log_bloom: receipt.logs_bloom.data().into(),
|
||||
logs: receipt
|
||||
.logs
|
||||
.iter()
|
||||
.map(|log_entry| SubstrateEthereumLogEntry {
|
||||
address: log_entry.address,
|
||||
topics: log_entry.topics.clone(),
|
||||
data: log_entry.data.0.clone(),
|
||||
})
|
||||
.collect(),
|
||||
outcome: match (receipt.status, receipt.root) {
|
||||
(Some(status), None) => SubstrateEthereumTransactionOutcome::StatusCode(status.as_u64() as u8),
|
||||
(None, Some(root)) => SubstrateEthereumTransactionOutcome::StateRoot(root),
|
||||
_ => SubstrateEthereumTransactionOutcome::Unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user