Remove unused PoA<>Substrate bridge (#1210)

* Decouple the PoA bridge code from Rialto
* Remove Rialto PoA bridge code
* Remove relays/bin-ethereum code
* Remove relays/client-ethereum code
* Remove modules/ethereum code
* Remove modules/ethereum-contract-builtin code
* Remove PoA bridge documentation
* Remove primitives/ethereum-poa code
* Decouple Rialto from currency-exchange
* Fix building with runtime-benchmarks
* Fix should_encode_bridge_send_message_call test
    Because we removed some runtime modules/pallets, the
    substrate2substrate bridge pallet has a different index within
    the runtime so its calls have a different encoding.
    Update the test to use the new encoding.
* Update readme - no more PoA bridge
* Remove deployments/bridges/poa-rialto
    Also removes:
    - deployments/networks/eth-poa.yml
    - deployments/networks/OpenEthereum.Dockerfile
* Remove deployments/dev/poa-config
* Update deployments readme - no more PoA bridge
* Remove eth-related scripts
    Deletes:
    - deployments/networks/eth-poa.yml
    - scripts/run-openethereum-node.sh
* Remove poa-relay from gitlab-ci
* Dockerfiles to use substrate-relay as default
* Remove modules/currency-exchange code
* Remove primitives/currency-exchange code

Signed-off-by: acatangiu <adrian@parity.io>
This commit is contained in:
Adrian Catangiu
2021-11-15 16:14:08 +02:00
committed by Bastian Köcher
parent 2d05583b15
commit 57f3f36cb1
54 changed files with 7 additions and 12181 deletions
-42
View File
@@ -1,42 +0,0 @@
[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]
anyhow = "1.0"
async-std = "1.9.0"
async-trait = "0.1.42"
clap = { version = "2.33.3", features = ["yaml"] }
codec = { package = "parity-scale-codec", version = "2.2.0" }
ethabi = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
ethabi-contract = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
ethabi-derive = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
futures = "0.3.12"
hex = "0.4"
hex-literal = "0.3"
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"] }
log = "0.4.14"
num-traits = "0.2"
serde_json = "1.0.64"
thiserror = "1.0.26"
# Bridge dependencies
bp-currency-exchange = { path = "../../primitives/currency-exchange" }
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
exchange-relay = { path = "../exchange" }
headers-relay = { path = "../headers" }
relay-ethereum-client = { path = "../client-ethereum" }
relay-rialto-client = { path = "../client-rialto" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
# Substrate Dependencies
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
-7
View File
@@ -1,7 +0,0 @@
# 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.
@@ -1,167 +0,0 @@
[
{
"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
@@ -1,5 +0,0 @@
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`
-166
View File
@@ -1,166 +0,0 @@
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: &eth-host
long: eth-host
value_name: ETH_HOST
help: Connect to Ethereum node websocket server at given host.
takes_value: true
- eth-port: &eth-port
long: eth-port
value_name: ETH_PORT
help: Connect to Ethereum node websocket server at given port.
takes_value: true
- sub-host: &sub-host
long: sub-host
value_name: SUB_HOST
help: Connect to Substrate node websocket server 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: &eth-chain-id
long: eth-chain-id
value_name: ETH_CHAIN_ID
help: Chain ID to use for signing.
- eth-signer: &eth-signer
long: eth-signer
value_name: ETH_SIGNER
help: Hex-encoded secret to use when transactions are submitted to the Ethereum node.
- sub-host: *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
-38
View File
@@ -1,38 +0,0 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::rpc_errors::RpcError;
use thiserror::Error;
/// Result type used by PoA relay.
pub type Result<T> = std::result::Result<T, Error>;
/// Ethereum PoA relay errors.
#[derive(Error, Debug)]
pub enum Error {
/// Failed to decode initial header.
#[error("Error decoding initial header: {0}")]
DecodeInitialHeader(codec::Error),
/// RPC error.
#[error("{0}")]
Rpc(#[from] RpcError),
/// Failed to read genesis header.
#[error("Error reading Substrate genesis header: {0:?}")]
ReadGenesisHeader(relay_substrate_client::Error),
/// Failed to read initial GRANDPA authorities.
#[error("Error reading GRANDPA authorities set: {0:?}")]
ReadAuthorities(relay_substrate_client::Error),
/// Failed to deploy bridge contract to Ethereum chain.
#[error("Error deploying contract: {0:?}")]
DeployContract(RpcError),
}
@@ -1,631 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{rpc_errors::RpcError, 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::EncodedJustification;
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 the 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: EncodedJustification,
) -> 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(&params.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: EncodedJustification,
) -> RpcResult<RialtoHeaderId> {
let _ = self
.submit_ethereum_transaction(
&params,
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());
}
}
@@ -1,164 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
error::{Error, Result},
ethereum_client::{bridge_contract, EthereumHighLevelRpc},
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 async fn run(params: EthereumDeployContractParams) {
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 = async move {
let eth_client = EthereumClient::try_connect(eth_params).await.map_err(RpcError::Ethereum)?;
let sub_client = SubstrateClient::<Rialto>::try_connect(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(
&eth_client,
&eth_sign,
eth_contract_code,
initial_header,
initial_set_id,
initial_set,
).await
}.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>)> {
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(Error::DecodeInitialHeader(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::ReadGenesisHeader)
},
}
}
/// 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> {
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::ReadAuthorities)
}
/// 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<()> {
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::DeployContract)
}
@@ -1,402 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Relaying proofs of PoA -> Substrate exchange transactions.
use crate::{
instances::BridgeInstance,
rialto_client::{SubmitEthereumExchangeTransactionProof, SubstrateHighLevelRpc},
rpc_errors::RpcError,
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,
},
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.
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: MetricsParams,
/// Instance of the bridge pallet being synchronized.
pub instance: Arc<dyn BridgeInstance>,
}
impl std::fmt::Debug for EthereumExchangeParams {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("EthereumExchangeParams")
.field("eth_params", &self.eth_params)
.field("sub_params", &self.sub_params)
.field("sub_sign", &sp_core::Pair::public(&self.sub_sign))
.field("mode", &self.mode)
.field("metrics_params", &self.metrics_params)
.field("instance", &self.instance)
.finish()
}
}
/// 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().await.map_err(Into::into)
}
}
#[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 async fn run(params: EthereumExchangeParams) {
match params.mode {
ExchangeRelayMode::Single(eth_tx_hash) => {
let result = run_single_transaction_relay(params, 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,
),
}
},
ExchangeRelayMode::Auto(eth_start_with_block_number) => {
let result =
run_auto_transactions_relay_loop(params, eth_start_with_block_number).await;
if let Err(err) = result {
log::error!(
target: "bridge",
"Error auto-relaying Ethereum transactions proofs to Substrate node: {}",
err,
);
}
},
}
}
/// Run single transaction proof relay and stop.
async fn run_single_transaction_relay(
params: EthereumExchangeParams,
eth_tx_hash: H256,
) -> anyhow::Result<()> {
let EthereumExchangeParams { eth_params, sub_params, sub_sign, instance, .. } = params;
let eth_client = EthereumClient::try_connect(eth_params).await.map_err(RpcError::Ethereum)?;
let sub_client = SubstrateClient::<Rialto>::try_connect(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
.map_err(Into::into)
}
async fn run_auto_transactions_relay_loop(
params: EthereumExchangeParams,
eth_start_with_block_number: Option<u64>,
) -> anyhow::Result<()> {
let EthereumExchangeParams {
eth_params, sub_params, sub_sign, metrics_params, instance, ..
} = params;
let eth_client = EthereumClient::new(eth_params).await;
let sub_client = SubstrateClient::<Rialto>::new(sub_params).await;
let eth_start_with_block_number = match eth_start_with_block_number {
Some(eth_start_with_block_number) => eth_start_with_block_number,
None =>
sub_client
.best_ethereum_finalized_block()
.await
.map_err(|err| {
anyhow::format_err!(
"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(),
)
.await?;
Ok(())
}
@@ -1,110 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Submitting Ethereum -> Substrate exchange transactions.
use anyhow::anyhow;
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 async fn run(params: EthereumExchangeSubmitParams) {
let EthereumExchangeSubmitParams { eth_params, eth_sign, eth_nonce, eth_amount, sub_recipient } =
params;
let result: anyhow::Result<_> = async move {
let eth_client = EthereumClient::try_connect(eth_params)
.await
.map_err(|err| anyhow!("error connecting to Ethereum node: {:?}", err))?;
let eth_signer_address = secret_to_address(&eth_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| anyhow!("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| anyhow!("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(&eth_sign.signer, Some(eth_sign.chain_id));
eth_client
.submit_transaction(eth_tx_signed)
.await
.map_err(|err| anyhow!("error submitting transaction: {:?}", err))?;
Ok(eth_tx_unsigned)
}
.await;
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,
);
},
}
}
@@ -1,299 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Ethereum PoA -> Rialto-Substrate synchronization.
use crate::{
ethereum_client::EthereumHighLevelRpc,
instances::BridgeInstance,
rialto_client::{SubmitEthereumHeaders, SubstrateHighLevelRpc},
rpc_errors::RpcError,
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::{collections::HashSet, fmt::Debug, 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.
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: MetricsParams,
/// Instance of the bridge pallet being synchronized.
pub instance: Arc<dyn BridgeInstance>,
}
impl Debug for EthereumSyncParams {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("EthereumSyncParams")
.field("eth_params", &self.eth_params)
.field("sub_params", &self.sub_params)
.field("sub_sign", &sp_core::Pair::public(&self.sub_sign))
.field("sync_params", &self.sync_params)
.field("metrics_params", &self.metrics_params)
.field("instance", &self.instance)
.finish()
}
}
/// 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().await.map_err(Into::into)
}
}
#[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 async 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).await;
let sub_client = 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(),
)
.await
.map_err(|e| RpcError::SyncLoop(e.to_string()))?;
Ok(())
}
@@ -1,119 +0,0 @@
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <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,
substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts},
};
use rialto_runtime::{exchange::EthereumTransactionInclusionProof as Proof, 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_with_receipts: 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 {
header: Box::new(into_substrate_ethereum_header(header.header())),
receipts: 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_with_receipts: 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 {
header: Box::new(into_substrate_ethereum_header(header.header())),
receipts: 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)
}
}
-424
View File
@@ -1,424 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
#![recursion_limit = "1024"]
mod error;
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 anyhow::anyhow;
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 libsecp256k1::SecretKey;
use relay_utils::{
initialize::initialize_relay,
metrics::{MetricsAddress, MetricsParams},
};
use sp_core::crypto::Pair;
use substrate_sync_loop::SubstrateSyncParams;
use headers_relay::sync::HeadersSyncParams;
use relay_ethereum_client::{
ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams,
};
use relay_rialto_client::SigningParams as RialtoSigningParams;
use relay_substrate_client::ConnectionParams as SubstrateConnectionParams;
use std::sync::Arc;
fn main() {
initialize_relay();
let yaml = clap::load_yaml!("cli.yml");
let matches = clap::App::from_yaml(yaml).get_matches();
async_std::task::block_on(run_command(&matches));
}
async fn run_command(matches: &clap::ArgMatches<'_>) {
match matches.subcommand() {
("eth-to-sub", Some(eth_to_sub_matches)) => {
log::info!(target: "bridge", "Starting ETH ➡ SUB relay.");
if ethereum_sync_loop::run(match ethereum_sync_params(eth_to_sub_matches) {
Ok(ethereum_sync_params) => ethereum_sync_params,
Err(err) => {
log::error!(target: "bridge", "Error parsing parameters: {}", err);
return
},
})
.await
.is_err()
{
log::error!(target: "bridge", "Unable to get Substrate genesis block for Ethereum sync.");
};
},
("sub-to-eth", Some(sub_to_eth_matches)) => {
log::info!(target: "bridge", "Starting SUB ➡ ETH relay.");
if substrate_sync_loop::run(match substrate_sync_params(sub_to_eth_matches) {
Ok(substrate_sync_params) => substrate_sync_params,
Err(err) => {
log::error!(target: "bridge", "Error parsing parameters: {}", err);
return
},
})
.await
.is_err()
{
log::error!(target: "bridge", "Unable to get Substrate genesis block for Substrate sync.");
};
},
("eth-deploy-contract", Some(eth_deploy_matches)) => {
log::info!(target: "bridge", "Deploying ETH contracts.");
ethereum_deploy_contract::run(
match ethereum_deploy_contract_params(eth_deploy_matches) {
Ok(ethereum_deploy_params) => ethereum_deploy_params,
Err(err) => {
log::error!(target: "bridge", "Error during contract deployment: {}", err);
return
},
},
)
.await;
},
("eth-submit-exchange-tx", Some(eth_exchange_submit_matches)) => {
log::info!(target: "bridge", "Submitting ETH ➡ SUB exchange transaction.");
ethereum_exchange_submit::run(
match ethereum_exchange_submit_params(eth_exchange_submit_matches) {
Ok(eth_exchange_submit_params) => eth_exchange_submit_params,
Err(err) => {
log::error!(target: "bridge", "Error submitting Eethereum exchange transaction: {}", err);
return
},
},
)
.await;
},
("eth-exchange-sub", Some(eth_exchange_matches)) => {
log::info!(target: "bridge", "Starting ETH ➡ SUB exchange transactions relay.");
ethereum_exchange::run(match ethereum_exchange_params(eth_exchange_matches) {
Ok(eth_exchange_params) => eth_exchange_params,
Err(err) => {
log::error!(target: "bridge", "Error relaying Ethereum transactions proofs: {}", err);
return
},
})
.await;
},
("", _) => {
log::error!(target: "bridge", "No subcommand specified");
},
_ => unreachable!("all possible subcommands are checked above; qed"),
}
}
fn ethereum_connection_params(
matches: &clap::ArgMatches,
) -> anyhow::Result<EthereumConnectionParams> {
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| anyhow!("Failed to parse eth-port: {}", e))?;
}
Ok(params)
}
fn ethereum_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSigningParams> {
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| anyhow!("Failed to parse eth-signer: {}", e))?,
)
.map_err(|e| anyhow!("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| anyhow!("Failed to parse eth-chain-id: {}", e))?;
}
Ok(params)
}
fn substrate_connection_params(
matches: &clap::ArgMatches,
) -> anyhow::Result<SubstrateConnectionParams> {
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| anyhow!("Failed to parse sub-port: {}", e))?;
}
Ok(params)
}
fn rialto_signing_params(matches: &clap::ArgMatches) -> anyhow::Result<RialtoSigningParams> {
let mut params = sp_keyring::AccountKeyring::Alice.pair();
if let Some(sub_signer) = matches.value_of("sub-signer") {
let sub_signer_password = matches.value_of("sub-signer-password");
params = sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password)
.map_err(|e| anyhow!("Failed to parse sub-signer: {:?}", e))?;
}
Ok(params)
}
fn ethereum_sync_params(matches: &clap::ArgMatches) -> anyhow::Result<EthereumSyncParams> {
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(anyhow!("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) -> anyhow::Result<SubstrateSyncParams> {
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()?
} 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,
) -> anyhow::Result<EthereumDeployContractParams> {
let eth_contract_code =
parse_hex_argument(matches, "eth-contract-code")?.unwrap_or_else(|| {
hex::decode(include_str!("../res/substrate-bridge-bytecode.hex"))
.expect("code is hardcoded, thus valid; qed")
});
let sub_initial_authorities_set_id = matches
.value_of("sub-authorities-set-id")
.map(|set| {
set.parse()
.map_err(|e| anyhow!("Failed to parse sub-authorities-set-id: {}", e))
})
.transpose()?;
let sub_initial_authorities_set = parse_hex_argument(matches, "sub-authorities-set")?;
let sub_initial_header = parse_hex_argument(matches, "sub-initial-header")?;
let params = EthereumDeployContractParams {
eth_params: ethereum_connection_params(matches)?,
eth_sign: ethereum_signing_params(matches)?,
sub_params: substrate_connection_params(matches)?,
sub_initial_authorities_set_id,
sub_initial_authorities_set,
sub_initial_header,
eth_contract_code,
};
log::debug!(target: "bridge", "Deploy params: {:?}", params);
Ok(params)
}
fn ethereum_exchange_submit_params(
matches: &clap::ArgMatches,
) -> anyhow::Result<EthereumExchangeSubmitParams> {
let eth_nonce = matches
.value_of("eth-nonce")
.map(|eth_nonce| {
relay_ethereum_client::types::U256::from_dec_str(eth_nonce)
.map_err(|e| anyhow!("Failed to parse eth-nonce: {}", e))
})
.transpose()?;
let eth_amount = matches
.value_of("eth-amount")
.map(|eth_amount| {
eth_amount.parse().map_err(|e| anyhow!("Failed to parse eth-amount: {}", e))
})
.transpose()?
.unwrap_or_else(|| {
// This is in Wei, represents 1 ETH
1_000_000_000_000_000_000_u64.into()
});
// This is the well-known Substrate account of Ferdie
let default_recepient =
hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c");
let sub_recipient = if let Some(sub_recipient) = matches.value_of("sub-recipient") {
hex::decode(&sub_recipient)
.map_err(|err| err.to_string())
.and_then(|vsub_recipient| {
let expected_len = default_recepient.len();
if expected_len != vsub_recipient.len() {
Err(format!("invalid length. Expected {} bytes", expected_len))
} else {
let mut sub_recipient = default_recepient;
sub_recipient.copy_from_slice(&vsub_recipient[..expected_len]);
Ok(sub_recipient)
}
})
.map_err(|e| anyhow!("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) -> anyhow::Result<EthereumExchangeParams> {
let mode = match matches.value_of("eth-tx-hash") {
Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single(
eth_tx_hash.parse().map_err(|e| anyhow!("Failed to parse eth-tx-hash: {}", e))?,
),
None => ethereum_exchange::ExchangeRelayMode::Auto(
matches
.value_of("eth-start-with-block")
.map(|eth_start_with_block| {
eth_start_with_block
.parse()
.map_err(|e| anyhow!("Failed to parse eth-start-with-block: {}", e))
})
.transpose()?,
),
};
let params = EthereumExchangeParams {
eth_params: ethereum_connection_params(matches)?,
sub_params: substrate_connection_params(matches)?,
sub_sign: rialto_signing_params(matches)?,
metrics_params: metrics_params(matches)?,
instance: instance_params(matches)?,
mode,
};
log::debug!(target: "bridge", "Ethereum exchange params: {:?}", params);
Ok(params)
}
fn metrics_params(matches: &clap::ArgMatches) -> anyhow::Result<MetricsParams> {
if matches.is_present("no-prometheus") {
return Ok(None.into())
}
let mut metrics_params = MetricsAddress::default();
if let Some(prometheus_host) = matches.value_of("prometheus-host") {
metrics_params.host = prometheus_host.into();
}
if let Some(prometheus_port) = matches.value_of("prometheus-port") {
metrics_params.port = prometheus_port
.parse()
.map_err(|e| anyhow!("Failed to parse prometheus-port: {}", e))?;
}
Ok(Some(metrics_params).into())
}
fn instance_params(matches: &clap::ArgMatches) -> anyhow::Result<Arc<dyn BridgeInstance>> {
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(anyhow!("Unsupported bridge pallet instance")),
}
} else {
unreachable!("CLI config enforces a default instance, can never be None")
};
Ok(instance)
}
fn parse_hex_argument(matches: &clap::ArgMatches, arg: &str) -> anyhow::Result<Option<Vec<u8>>> {
match matches.value_of(arg) {
Some(value) =>
Ok(Some(hex::decode(value).map_err(|e| anyhow!("Failed to parse {}: {}", arg, e))?)),
None => Ok(None),
}
}
@@ -1,300 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
ethereum_sync_loop::QueuedEthereumHeader, instances::BridgeInstance, 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, UnsignedTransaction,
};
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 the 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 transactions receipts are required for Ethereum header submission.
async fn ethereum_receipts_required(&self, header: SubstrateEthereumHeader) -> RpcResult<bool>;
/// Returns whether 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 genesis_hash = *self.genesis_hash();
let submission_result = async {
self.submit_signed_extrinsic(
(*params.public().as_array_ref()).into(),
move |_, transaction_nonce| {
Bytes(
Rialto::sign_transaction(
genesis_hash,
&params,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
instance.build_signed_header_call(headers),
transaction_nonce,
),
)
.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_unsigned_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 genesis_hash = *self.genesis_hash();
self.submit_signed_extrinsic(
(*params.public().as_array_ref()).into(),
move |_, transaction_nonce| {
Bytes(
Rialto::sign_transaction(
genesis_hash,
&params,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
instance.build_currency_exchange_call(proof),
transaction_nonce,
),
)
.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)
}
@@ -1,67 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use relay_ethereum_client::Error as EthereumNodeError;
use relay_substrate_client::Error as SubstrateNodeError;
use relay_utils::MaybeConnectionError;
use thiserror::Error;
/// Contains common errors that can occur when
/// interacting with a Substrate or Ethereum node
/// through RPC.
#[derive(Debug, Error)]
pub enum RpcError {
/// The arguments to the RPC method failed to serialize.
#[error("RPC arguments serialization failed: {0}")]
Serialization(#[from] serde_json::Error),
/// An error occurred when interacting with an Ethereum node.
#[error("Ethereum node error: {0}")]
Ethereum(#[from] EthereumNodeError),
/// An error occurred when interacting with a Substrate node.
#[error("Substrate node error: {0}")]
Substrate(#[from] SubstrateNodeError),
/// Error running relay loop.
#[error("{0}")]
SyncLoop(String),
}
impl From<RpcError> for String {
fn from(err: RpcError) -> Self {
format!("{}", 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))
}
}
@@ -1,204 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Rialto-Substrate -> Ethereum PoA synchronization.
use crate::{ethereum_client::EthereumHighLevelRpc, 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::EncodedJustification;
use std::{collections::HashSet, fmt::Debug, 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: 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 = EncodedJustification;
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().await.map_err(Into::into)
}
}
#[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: EncodedJustification,
) -> 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 async 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).await;
let sub_client = 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(),
)
.await
.map_err(|e| RpcError::SyncLoop(e.to_string()))?;
Ok(())
}
@@ -1,78 +0,0 @@
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <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,
},
}
}
@@ -345,7 +345,7 @@ mod tests {
// then
assert!(format!("{:?}", call_hex).starts_with(
"0x10030000000001000000381409000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
"0x0c030000000001000000381409000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
de39a5684e7a56da27d01d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01"
))
}
-20
View File
@@ -1,20 +0,0 @@
[package]
name = "relay-ethereum-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = "1.6.5"
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
headers-relay = { path = "../headers" }
hex-literal = "0.3"
jsonrpsee-proc-macros = "0.3.1"
jsonrpsee-ws-client = "0.3.1"
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"] }
log = "0.4.11"
relay-utils = { path = "../utils" }
tokio = "1.8"
web3 = { git = "https://github.com/svyatonik/rust-web3.git", branch = "bump-deps" }
thiserror = "1.0.26"
@@ -1,265 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
rpc::Ethereum,
types::{
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SignedRawTx,
SyncState, Transaction, TransactionHash, H256, U256,
},
ConnectionParams, Error, Result,
};
use jsonrpsee_ws_client::{WsClient as RpcClient, WsClientBuilder as RpcClientBuilder};
use relay_utils::relay_loop::RECONNECT_DELAY;
use std::{future::Future, sync::Arc};
/// Number of headers missing from the Ethereum node for us to consider node not synced.
const MAJOR_SYNC_BLOCKS: u64 = 5;
/// The client used to interact with an Ethereum node through RPC.
#[derive(Clone)]
pub struct Client {
tokio: Arc<tokio::runtime::Runtime>,
params: ConnectionParams,
client: Arc<RpcClient>,
}
impl Client {
/// Create a new Ethereum RPC Client.
///
/// This function will keep connecting to given Ethereum node until connection is established
/// and is functional. If attempt fail, it will wait for `RECONNECT_DELAY` and retry again.
pub async fn new(params: ConnectionParams) -> Self {
loop {
match Self::try_connect(params.clone()).await {
Ok(client) => return client,
Err(error) => log::error!(
target: "bridge",
"Failed to connect to Ethereum node: {:?}. Going to retry in {}s",
error,
RECONNECT_DELAY.as_secs(),
),
}
async_std::task::sleep(RECONNECT_DELAY).await;
}
}
/// Try to connect to Ethereum node. Returns Ethereum RPC client if connection has been
/// established or error otherwise.
pub async fn try_connect(params: ConnectionParams) -> Result<Self> {
let (tokio, client) = Self::build_client(&params).await?;
Ok(Self { tokio, client, params })
}
/// Build client to use in connection.
async fn build_client(
params: &ConnectionParams,
) -> Result<(Arc<tokio::runtime::Runtime>, Arc<RpcClient>)> {
let tokio = tokio::runtime::Runtime::new()?;
let uri = format!("ws://{}:{}", params.host, params.port);
let client = tokio
.spawn(async move { RpcClientBuilder::default().build(&uri).await })
.await??;
Ok((Arc::new(tokio), Arc::new(client)))
}
/// Reopen client connection.
pub async fn reconnect(&mut self) -> Result<()> {
let (tokio, client) = Self::build_client(&self.params).await?;
self.tokio = tokio;
self.client = client;
Ok(())
}
}
impl Client {
/// Returns true if client is connected to at least one peer and is in synced state.
pub async fn ensure_synced(&self) -> Result<()> {
self.jsonrpsee_execute(move |client| async move {
match Ethereum::syncing(&*client).await? {
SyncState::NotSyncing => Ok(()),
SyncState::Syncing(syncing) => {
let missing_headers =
syncing.highest_block.saturating_sub(syncing.current_block);
if missing_headers > MAJOR_SYNC_BLOCKS.into() {
return Err(Error::ClientNotSynced(missing_headers))
}
Ok(())
},
}
})
.await
}
/// Estimate gas usage for the given call.
pub async fn estimate_gas(&self, call_request: CallRequest) -> Result<U256> {
self.jsonrpsee_execute(move |client| async move {
Ok(Ethereum::estimate_gas(&*client, call_request).await?)
})
.await
}
/// Retrieve number of the best known block from the Ethereum node.
pub async fn best_block_number(&self) -> Result<u64> {
self.jsonrpsee_execute(move |client| async move {
Ok(Ethereum::block_number(&*client).await?.as_u64())
})
.await
}
/// Retrieve number of the best known block from the Ethereum node.
pub async fn header_by_number(&self, block_number: u64) -> Result<Header> {
self.jsonrpsee_execute(move |client| async move {
let get_full_tx_objects = false;
let header =
Ethereum::get_block_by_number(&*client, block_number, get_full_tx_objects).await?;
match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() {
true => Ok(header),
false => Err(Error::IncompleteHeader),
}
})
.await
}
/// Retrieve block header by its hash from Ethereum node.
pub async fn header_by_hash(&self, hash: H256) -> Result<Header> {
self.jsonrpsee_execute(move |client| async move {
let get_full_tx_objects = false;
let header = Ethereum::get_block_by_hash(&*client, hash, get_full_tx_objects).await?;
match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() {
true => Ok(header),
false => Err(Error::IncompleteHeader),
}
})
.await
}
/// Retrieve block header and its transactions by its number from Ethereum node.
pub async fn header_by_number_with_transactions(
&self,
number: u64,
) -> Result<HeaderWithTransactions> {
self.jsonrpsee_execute(move |client| async move {
let get_full_tx_objects = true;
let header = Ethereum::get_block_by_number_with_transactions(
&*client,
number,
get_full_tx_objects,
)
.await?;
let is_complete_header =
header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some();
if !is_complete_header {
return Err(Error::IncompleteHeader)
}
let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some());
if !is_complete_transactions {
return Err(Error::IncompleteTransaction)
}
Ok(header)
})
.await
}
/// Retrieve block header and its transactions by its hash from Ethereum node.
pub async fn header_by_hash_with_transactions(
&self,
hash: H256,
) -> Result<HeaderWithTransactions> {
self.jsonrpsee_execute(move |client| async move {
let get_full_tx_objects = true;
let header =
Ethereum::get_block_by_hash_with_transactions(&*client, hash, get_full_tx_objects)
.await?;
let is_complete_header =
header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some();
if !is_complete_header {
return Err(Error::IncompleteHeader)
}
let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some());
if !is_complete_transactions {
return Err(Error::IncompleteTransaction)
}
Ok(header)
})
.await
}
/// Retrieve transaction by its hash from Ethereum node.
pub async fn transaction_by_hash(&self, hash: H256) -> Result<Option<Transaction>> {
self.jsonrpsee_execute(move |client| async move {
Ok(Ethereum::transaction_by_hash(&*client, hash).await?)
})
.await
}
/// Retrieve transaction receipt by transaction hash.
pub async fn transaction_receipt(&self, transaction_hash: H256) -> Result<Receipt> {
self.jsonrpsee_execute(move |client| async move {
Ok(Ethereum::get_transaction_receipt(&*client, transaction_hash).await?)
})
.await
}
/// Get the nonce of the given account.
pub async fn account_nonce(&self, address: Address) -> Result<U256> {
self.jsonrpsee_execute(move |client| async move {
Ok(Ethereum::get_transaction_count(&*client, address).await?)
})
.await
}
/// Submit an Ethereum transaction.
///
/// The transaction must already be signed before sending it through this method.
pub async fn submit_transaction(&self, signed_raw_tx: SignedRawTx) -> Result<TransactionHash> {
self.jsonrpsee_execute(move |client| async move {
let transaction = Bytes(signed_raw_tx);
let tx_hash = Ethereum::submit_transaction(&*client, transaction).await?;
log::trace!(target: "bridge", "Sent transaction to Ethereum node: {:?}", tx_hash);
Ok(tx_hash)
})
.await
}
/// Call Ethereum smart contract.
pub async fn eth_call(&self, call_transaction: CallRequest) -> Result<Bytes> {
self.jsonrpsee_execute(move |client| async move {
Ok(Ethereum::call(&*client, call_transaction).await?)
})
.await
}
/// Execute jsonrpsee future in tokio context.
async fn jsonrpsee_execute<MF, F, T>(&self, make_jsonrpsee_future: MF) -> Result<T>
where
MF: FnOnce(Arc<RpcClient>) -> F + Send + 'static,
F: Future<Output = Result<T>> + Send,
T: Send + 'static,
{
let client = self.client.clone();
self.tokio.spawn(async move { make_jsonrpsee_future(client).await }).await?
}
}
@@ -1,82 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Ethereum node RPC errors.
use crate::types::U256;
use jsonrpsee_ws_client::types::Error as RpcError;
use relay_utils::MaybeConnectionError;
use thiserror::Error;
/// Result type used by Ethereum client.
pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can occur only when interacting with
/// an Ethereum node through RPC.
#[derive(Debug, Error)]
pub enum Error {
/// IO error.
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
/// An error that can occur when making an HTTP request to
/// an JSON-RPC client.
#[error("RPC error: {0}")]
RpcError(#[from] RpcError),
/// Failed to parse response.
#[error("Response parse failed: {0}")]
ResponseParseFailed(String),
/// We have received a header with missing fields.
#[error("Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom).")]
IncompleteHeader,
/// We have received a transaction missing a `raw` field.
#[error("Incomplete Ethereum Transaction (missing required field - raw).")]
IncompleteTransaction,
/// An invalid Substrate block number was received from
/// an Ethereum node.
#[error("Received an invalid Substrate block from Ethereum Node.")]
InvalidSubstrateBlockNumber,
/// An invalid index has been received from an Ethereum node.
#[error("Received an invalid incomplete index from Ethereum Node.")]
InvalidIncompleteIndex,
/// The client we're connected to is not synced, so we can't rely on its state. Contains
/// number of unsynced headers.
#[error("Ethereum client is not synced: syncing {0} headers.")]
ClientNotSynced(U256),
/// Custom logic error.
#[error("{0}")]
Custom(String),
}
impl From<tokio::task::JoinError> for Error {
fn from(error: tokio::task::JoinError) -> Self {
Error::Custom(format!("Failed to wait tokio task: {}", error))
}
}
impl MaybeConnectionError for Error {
fn is_connection_error(&self) -> bool {
matches!(
*self,
Error::RpcError(RpcError::Transport(_))
// right now if connection to the ws server is dropped (after it is already established),
// we're getting this error
| Error::RpcError(RpcError::Internal(_))
| Error::RpcError(RpcError::RestartNeeded(_))
| Error::ClientNotSynced(_),
)
}
}
-47
View File
@@ -1,47 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tools to interact with (Open) Ethereum node using RPC methods.
#![warn(missing_docs)]
mod client;
mod error;
mod rpc;
mod sign;
pub use crate::{
client::Client,
error::{Error, Result},
sign::{sign_and_submit_transaction, SigningParams},
};
pub mod types;
/// Ethereum-over-websocket connection params.
#[derive(Debug, Clone)]
pub struct ConnectionParams {
/// Websocket server host name.
pub host: String,
/// Websocket server TCP port.
pub port: u16,
}
impl Default for ConnectionParams {
fn default() -> Self {
ConnectionParams { host: "localhost".into(), port: 8546 }
}
}
-51
View File
@@ -1,51 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Ethereum node RPC interface.
use crate::types::{
Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SyncState, Transaction,
TransactionHash, H256, U256, U64,
};
jsonrpsee_proc_macros::rpc_client_api! {
pub(crate) Ethereum {
#[rpc(method = "eth_syncing", positional_params)]
fn syncing() -> SyncState;
#[rpc(method = "eth_estimateGas", positional_params)]
fn estimate_gas(call_request: CallRequest) -> U256;
#[rpc(method = "eth_blockNumber", positional_params)]
fn block_number() -> U64;
#[rpc(method = "eth_getBlockByNumber", positional_params)]
fn get_block_by_number(block_number: U64, full_tx_objs: bool) -> Header;
#[rpc(method = "eth_getBlockByHash", positional_params)]
fn get_block_by_hash(hash: H256, full_tx_objs: bool) -> Header;
#[rpc(method = "eth_getBlockByNumber", positional_params)]
fn get_block_by_number_with_transactions(number: U64, full_tx_objs: bool) -> HeaderWithTransactions;
#[rpc(method = "eth_getBlockByHash", positional_params)]
fn get_block_by_hash_with_transactions(hash: H256, full_tx_objs: bool) -> HeaderWithTransactions;
#[rpc(method = "eth_getTransactionByHash", positional_params)]
fn transaction_by_hash(hash: H256) -> Option<Transaction>;
#[rpc(method = "eth_getTransactionReceipt", positional_params)]
fn get_transaction_receipt(transaction_hash: H256) -> Receipt;
#[rpc(method = "eth_getTransactionCount", positional_params)]
fn get_transaction_count(address: Address) -> U256;
#[rpc(method = "eth_submitTransaction", positional_params)]
fn submit_transaction(transaction: Bytes) -> TransactionHash;
#[rpc(method = "eth_call", positional_params)]
fn call(transaction_call: CallRequest) -> Bytes;
}
}
@@ -1,87 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
types::{Address, CallRequest, U256},
Client, Result,
};
use bp_eth_poa::signatures::{secret_to_address, SignTransaction};
use hex_literal::hex;
use libsecp256k1::SecretKey;
/// Ethereum signing params.
#[derive(Clone, Debug)]
pub struct SigningParams {
/// Ethereum chain id.
pub chain_id: u64,
/// Ethereum transactions signer.
pub signer: SecretKey,
/// Gas price we agree to pay.
pub gas_price: U256,
}
impl Default for SigningParams {
fn default() -> Self {
SigningParams {
chain_id: 0x11, // Parity dev chain
// account that has a lot of ether when we run instant seal engine
// address: 0x00a329c0648769a73afac7f9381e08fb43dbea72
// secret: 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
signer: SecretKey::parse(&hex!(
"4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
))
.expect("secret is hardcoded, thus valid; qed"),
gas_price: 8_000_000_000u64.into(), // 8 Gwei
}
}
}
/// Sign and submit transaction using given Ethereum client.
pub async fn sign_and_submit_transaction(
client: &Client,
params: &SigningParams,
contract_address: Option<Address>,
nonce: Option<U256>,
double_gas: bool,
encoded_call: Vec<u8>,
) -> Result<()> {
let nonce = if let Some(n) = nonce {
n
} else {
let address: Address = secret_to_address(&params.signer);
client.account_nonce(address).await?
};
let call_request = CallRequest {
to: contract_address,
data: Some(encoded_call.clone().into()),
..Default::default()
};
let gas = client.estimate_gas(call_request).await?;
let raw_transaction = bp_eth_poa::UnsignedTransaction {
nonce,
to: contract_address,
value: U256::zero(),
gas: if double_gas { gas.saturating_mul(2.into()) } else { gas },
gas_price: params.gas_price,
payload: encoded_call,
}
.sign_by(&params.signer, Some(params.chain_id));
let _ = client.submit_transaction(raw_transaction).await?;
Ok(())
}
@@ -1,80 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Common types that are used in relay <-> Ethereum node communications.
use headers_relay::sync_types::SourceHeader;
pub use web3::types::{Address, Bytes, CallRequest, SyncState, H256, U128, U256, U64};
/// When header is just received from the Ethereum node, we check that it has
/// both number and hash fields filled.
pub const HEADER_ID_PROOF: &str = "checked on retrieval; qed";
/// Ethereum transaction hash type.
pub type HeaderHash = H256;
/// Ethereum transaction hash type.
pub type TransactionHash = H256;
/// Ethereum transaction type.
pub type Transaction = web3::types::Transaction;
/// Ethereum header type.
pub type Header = web3::types::Block<H256>;
/// Ethereum header type used in headers sync.
#[derive(Clone, Debug, PartialEq)]
pub struct SyncHeader(Header);
impl std::ops::Deref for SyncHeader {
type Target = Header;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Ethereum header with transactions type.
pub type HeaderWithTransactions = web3::types::Block<Transaction>;
/// Ethereum transaction receipt type.
pub type Receipt = web3::types::TransactionReceipt;
/// Ethereum header ID.
pub type HeaderId = relay_utils::HeaderId<H256, u64>;
/// A raw Ethereum transaction that's been signed.
pub type SignedRawTx = Vec<u8>;
impl From<Header> for SyncHeader {
fn from(header: Header) -> Self {
Self(header)
}
}
impl SourceHeader<H256, u64> for SyncHeader {
fn id(&self) -> HeaderId {
relay_utils::HeaderId(
self.number.expect(HEADER_ID_PROOF).as_u64(),
self.hash.expect(HEADER_ID_PROOF),
)
}
fn parent_id(&self) -> HeaderId {
relay_utils::HeaderId(self.number.expect(HEADER_ID_PROOF).as_u64() - 1, self.parent_hash)
}
}