mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 05:11:02 +00:00
Solidity contract that accepts unverified substrate headers (#65)
* solidity contract * continue * upd * cargo update * fixes * ehtereum_headers -> headers * extracted some common stuff * ethereum_sync.rs -> sync.rs * make sync generic * continue extracting * continue * add eth-contract argument * continue * some fixes * contract v2 * continue * more fixes * more fixes * deal with duplicated params * removed multiple call_rpc variants * bail_on_error!() * fn submit_ethereum_transaction * more fixes * cargo fmt --all * fix * bail_on_arg_error!() * fix * fix * remove async_extra stuff * substrate-bridge.json -> substrate-bridge-abi.json * get rid of substrate transactions hashes * get rid of ethereum transactions hashes * extracted contract bytecode to separate file * cargo fmt --all * avoid duplicate import in contracts * removed Default::default() * swapped configurations for sub2eth && eth2sub * fix compilation * do not double gas limit when submitting Substrate headers * cargo fmt --all * solidity contract removed * consts * extracted solc compilation details to separate file * removed (obsolete in future Vec<u8> justification) * fixed cli option description * fix typos * fix grumble * extracted constants * log decoded header * cargo fmt --all * comment
This commit is contained in:
committed by
Bastian Köcher
parent
50d6ed186f
commit
3d15ac7c90
@@ -14,14 +14,22 @@
|
||||
// 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_types::{EthereumHeaderId, Header, Receipt, H256, U64};
|
||||
use crate::sync_types::MaybeConnectionError;
|
||||
use crate::ethereum_types::{Address, Bytes, EthereumHeaderId, Header, Receipt, TransactionHash, H256, U256, U64};
|
||||
use crate::substrate_types::{Hash as SubstrateHash, QueuedSubstrateHeader, SubstrateHeaderId};
|
||||
use crate::sync_types::{HeaderId, MaybeConnectionError};
|
||||
use crate::{bail_on_arg_error, bail_on_error};
|
||||
use codec::{Decode, Encode};
|
||||
use ethabi::FunctionOutputDecoder;
|
||||
use jsonrpsee::common::Params;
|
||||
use jsonrpsee::raw::{RawClient, RawClientError};
|
||||
use jsonrpsee::transport::http::{HttpTransportClient, RequestError};
|
||||
use serde::de::DeserializeOwned;
|
||||
use parity_crypto::publickey::KeyPair;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde_json::{from_value, to_value};
|
||||
|
||||
// to encode/decode contract calls
|
||||
ethabi_contract::use_contract!(bridge_contract, "res/substrate-bridge-abi.json");
|
||||
|
||||
/// Proof of hash serialization success.
|
||||
const HASH_SERIALIZATION_PROOF: &'static str = "hash serialization never fails; qed";
|
||||
/// Proof of integer serialization success.
|
||||
@@ -29,24 +37,83 @@ const INT_SERIALIZATION_PROOF: &'static str = "integer serialization never fails
|
||||
/// Proof of bool serialization success.
|
||||
const BOOL_SERIALIZATION_PROOF: &'static str = "bool serialization never fails; qed";
|
||||
|
||||
/// Ethereum connection params.
|
||||
#[derive(Debug)]
|
||||
pub struct EthereumConnectionParams {
|
||||
/// Ethereum RPC host.
|
||||
pub host: String,
|
||||
/// Ethereum RPC port.
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl Default for EthereumConnectionParams {
|
||||
fn default() -> Self {
|
||||
EthereumConnectionParams {
|
||||
host: "localhost".into(),
|
||||
port: 8545,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ethereum signing params.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EthereumSigningParams {
|
||||
/// Ethereum chain id.
|
||||
pub chain_id: u64,
|
||||
/// Ethereum transactions signer.
|
||||
pub signer: KeyPair,
|
||||
/// Gas price we agree to pay.
|
||||
pub gas_price: U256,
|
||||
}
|
||||
|
||||
impl Default for EthereumSigningParams {
|
||||
fn default() -> Self {
|
||||
EthereumSigningParams {
|
||||
chain_id: 0x11, // Parity dev chain
|
||||
// account that has a lot of ether when we run instant seal engine
|
||||
// address: 0x00a329c0648769a73afac7f9381e08fb43dbea72
|
||||
// secret: 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
|
||||
signer: KeyPair::from_secret_slice(
|
||||
&hex::decode("4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7")
|
||||
.expect("secret is hardcoded, thus valid; qed"),
|
||||
)
|
||||
.expect("secret is hardcoded, thus valid; qed"),
|
||||
gas_price: 8_000_000_000u64.into(), // 8 Gwei
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ethereum client type.
|
||||
pub type Client = RawClient<HttpTransportClient>;
|
||||
|
||||
/// Ethereum contract call request.
|
||||
#[derive(Debug, Default, PartialEq, Serialize)]
|
||||
pub struct CallRequest {
|
||||
/// Contract address.
|
||||
pub to: Option<Address>,
|
||||
/// Call data.
|
||||
pub data: Option<Bytes>,
|
||||
}
|
||||
|
||||
/// All possible errors that can occur during interacting with Ethereum node.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Request start failed.
|
||||
StartRequestFailed(RequestError),
|
||||
/// Error serializing request.
|
||||
RequestSerialization(serde_json::Error),
|
||||
/// Request not found (should never occur?).
|
||||
RequestNotFound,
|
||||
/// Failed to receive response.
|
||||
ResponseRetrievalFailed(RawClientError<RequestError>),
|
||||
/// Failed to parse response.
|
||||
ResponseParseFailed(serde_json::Error),
|
||||
ResponseParseFailed(String),
|
||||
/// We have received header with missing number and hash fields.
|
||||
IncompleteHeader,
|
||||
/// We have received receipt with missing gas_used field.
|
||||
IncompleteReceipt,
|
||||
/// Invalid Substrate block number received from Ethereum node.
|
||||
InvalidSubstrateBlockNumber,
|
||||
}
|
||||
|
||||
impl MaybeConnectionError for Error {
|
||||
@@ -59,8 +126,9 @@ impl MaybeConnectionError for Error {
|
||||
}
|
||||
|
||||
/// Returns client that is able to call RPCs on Ethereum node.
|
||||
pub fn client(uri: &str) -> Client {
|
||||
let transport = HttpTransportClient::new(uri);
|
||||
pub fn client(params: EthereumConnectionParams) -> Client {
|
||||
let uri = format!("http://{}:{}", params.host, params.port);
|
||||
let transport = HttpTransportClient::new(&uri);
|
||||
RawClient::new(transport)
|
||||
}
|
||||
|
||||
@@ -118,15 +186,11 @@ pub async fn header_by_hash(client: Client, hash: H256) -> (Client, Result<Heade
|
||||
pub async fn transactions_receipts(
|
||||
mut client: Client,
|
||||
id: EthereumHeaderId,
|
||||
transacactions: Vec<H256>,
|
||||
transactions: Vec<H256>,
|
||||
) -> (Client, Result<(EthereumHeaderId, Vec<Receipt>), Error>) {
|
||||
let mut transactions_receipts = Vec::with_capacity(transacactions.len());
|
||||
for transacaction in transacactions {
|
||||
let (next_client, transaction_receipt) = transaction_receipt(client, transacaction).await;
|
||||
let transaction_receipt = match transaction_receipt {
|
||||
Ok(transaction_receipt) => transaction_receipt,
|
||||
Err(error) => return (next_client, Err(error)),
|
||||
};
|
||||
let mut transactions_receipts = Vec::with_capacity(transactions.len());
|
||||
for transaction in transactions {
|
||||
let (next_client, transaction_receipt) = bail_on_error!(transaction_receipt(client, transaction).await);
|
||||
transactions_receipts.push(transaction_receipt);
|
||||
client = next_client;
|
||||
}
|
||||
@@ -150,6 +214,172 @@ async fn transaction_receipt(client: Client, hash: H256) -> (Client, Result<Rece
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns best Substrate block that PoA chain knows of.
|
||||
pub async fn best_substrate_block(
|
||||
client: Client,
|
||||
contract_address: Address,
|
||||
) -> (Client, Result<SubstrateHeaderId, Error>) {
|
||||
let (encoded_call, call_decoder) = bridge_contract::functions::best_known_header::call();
|
||||
let call_request = bail_on_arg_error!(
|
||||
to_value(CallRequest {
|
||||
to: Some(contract_address),
|
||||
data: Some(encoded_call.into()),
|
||||
})
|
||||
.map_err(|e| Error::RequestSerialization(e)),
|
||||
client
|
||||
);
|
||||
let (client, call_result) =
|
||||
bail_on_error!(call_rpc::<Bytes>(client, "eth_call", Params::Array(vec![call_request]),).await);
|
||||
let (number, raw_hash) = match call_decoder.decode(&call_result.0) {
|
||||
Ok((raw_number, raw_hash)) => (raw_number, raw_hash),
|
||||
Err(error) => return (client, Err(Error::ResponseParseFailed(format!("{}", error)))),
|
||||
};
|
||||
let hash = match SubstrateHash::decode(&mut &raw_hash[..]) {
|
||||
Ok(hash) => hash,
|
||||
Err(error) => return (client, Err(Error::ResponseParseFailed(format!("{}", error)))),
|
||||
};
|
||||
|
||||
if number != number.low_u32().into() {
|
||||
return (client, Err(Error::InvalidSubstrateBlockNumber));
|
||||
}
|
||||
|
||||
(client, Ok(HeaderId(number.low_u32(), hash)))
|
||||
}
|
||||
|
||||
/// Returns true if Substrate header is known to Ethereum node.
|
||||
pub async fn substrate_header_known(
|
||||
client: Client,
|
||||
contract_address: Address,
|
||||
id: SubstrateHeaderId,
|
||||
) -> (Client, Result<(SubstrateHeaderId, bool), Error>) {
|
||||
let (encoded_call, call_decoder) = bridge_contract::functions::is_known_header::call(id.1);
|
||||
let call_request = bail_on_arg_error!(
|
||||
to_value(CallRequest {
|
||||
to: Some(contract_address),
|
||||
data: Some(encoded_call.into()),
|
||||
})
|
||||
.map_err(|e| Error::RequestSerialization(e)),
|
||||
client
|
||||
);
|
||||
let (client, call_result) =
|
||||
bail_on_error!(call_rpc::<Bytes>(client, "eth_call", Params::Array(vec![call_request]),).await);
|
||||
match call_decoder.decode(&call_result.0) {
|
||||
Ok(is_known_block) => (client, Ok((id, is_known_block))),
|
||||
Err(error) => (client, Err(Error::ResponseParseFailed(format!("{}", error)))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Submits Substrate headers to Ethereum contract.
|
||||
pub async fn submit_substrate_headers(
|
||||
client: Client,
|
||||
params: EthereumSigningParams,
|
||||
contract_address: Address,
|
||||
headers: Vec<QueuedSubstrateHeader>,
|
||||
) -> (Client, Result<Vec<SubstrateHeaderId>, Error>) {
|
||||
let (mut client, mut nonce) =
|
||||
bail_on_error!(account_nonce(client, params.signer.address().as_fixed_bytes().into()).await);
|
||||
|
||||
let ids = headers.iter().map(|header| header.id()).collect();
|
||||
for header in headers {
|
||||
client = bail_on_error!(
|
||||
submit_ethereum_transaction(
|
||||
client,
|
||||
¶ms,
|
||||
Some(contract_address),
|
||||
Some(nonce),
|
||||
false,
|
||||
bridge_contract::functions::import_header::encode_input(header.extract().0.encode(),),
|
||||
)
|
||||
.await
|
||||
)
|
||||
.0;
|
||||
|
||||
nonce += 1.into();
|
||||
}
|
||||
|
||||
(client, Ok(ids))
|
||||
}
|
||||
|
||||
/// Deploy bridge contract.
|
||||
pub async fn deploy_bridge_contract(
|
||||
client: Client,
|
||||
params: &EthereumSigningParams,
|
||||
contract_code: Vec<u8>,
|
||||
initial_header: Vec<u8>,
|
||||
initial_set_id: u64,
|
||||
initial_authorities: Vec<u8>,
|
||||
) -> (Client, Result<(), Error>) {
|
||||
submit_ethereum_transaction(
|
||||
client,
|
||||
params,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
bridge_contract::constructor(contract_code, initial_header, initial_set_id, initial_authorities),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Submit ethereum transaction.
|
||||
async fn submit_ethereum_transaction(
|
||||
client: Client,
|
||||
params: &EthereumSigningParams,
|
||||
contract_address: Option<Address>,
|
||||
nonce: Option<U256>,
|
||||
double_gas: bool,
|
||||
encoded_call: Vec<u8>,
|
||||
) -> (Client, Result<(), Error>) {
|
||||
let (client, nonce) = match nonce {
|
||||
Some(nonce) => (client, nonce),
|
||||
None => bail_on_error!(account_nonce(client, params.signer.address().as_fixed_bytes().into()).await),
|
||||
};
|
||||
let (client, gas) = bail_on_error!(
|
||||
estimate_gas(
|
||||
client,
|
||||
CallRequest {
|
||||
to: contract_address,
|
||||
data: Some(encoded_call.clone().into()),
|
||||
}
|
||||
)
|
||||
.await
|
||||
);
|
||||
let raw_transaction = ethereum_tx_sign::RawTransaction {
|
||||
nonce,
|
||||
to: contract_address,
|
||||
value: U256::zero(),
|
||||
gas: if double_gas { gas.saturating_mul(2.into()) } else { gas },
|
||||
gas_price: params.gas_price,
|
||||
data: encoded_call,
|
||||
}
|
||||
.sign(¶ms.signer.secret().as_fixed_bytes().into(), ¶ms.chain_id);
|
||||
let transaction = bail_on_arg_error!(
|
||||
to_value(Bytes(raw_transaction)).map_err(|e| Error::RequestSerialization(e)),
|
||||
client
|
||||
);
|
||||
let (client, _) = bail_on_error!(
|
||||
call_rpc::<TransactionHash>(client, "eth_submitTransaction", Params::Array(vec![transaction])).await
|
||||
);
|
||||
(client, Ok(()))
|
||||
}
|
||||
|
||||
/// Get account nonce.
|
||||
async fn account_nonce(client: Client, caller_address: Address) -> (Client, Result<U256, Error>) {
|
||||
let caller_address = bail_on_arg_error!(
|
||||
to_value(caller_address).map_err(|e| Error::RequestSerialization(e)),
|
||||
client
|
||||
);
|
||||
call_rpc(client, "eth_getTransactionCount", Params::Array(vec![caller_address])).await
|
||||
}
|
||||
|
||||
/// Estimate gas usage for call.
|
||||
async fn estimate_gas(client: Client, call_request: CallRequest) -> (Client, Result<U256, Error>) {
|
||||
let call_request = bail_on_arg_error!(
|
||||
to_value(call_request).map_err(|e| Error::RequestSerialization(e)),
|
||||
client
|
||||
);
|
||||
call_rpc(client, "eth_estimateGas", Params::Array(vec![call_request])).await
|
||||
}
|
||||
|
||||
/// Calls RPC on Ethereum node.
|
||||
async fn call_rpc<T: DeserializeOwned>(
|
||||
mut client: Client,
|
||||
@@ -172,7 +402,7 @@ async fn call_rpc<T: DeserializeOwned>(
|
||||
.ok_or(Error::RequestNotFound)?
|
||||
.await
|
||||
.map_err(Error::ResponseRetrievalFailed)?;
|
||||
from_value(response).map_err(Error::ResponseParseFailed)
|
||||
from_value(response).map_err(|e| Error::ResponseParseFailed(format!("{}", e)))
|
||||
}
|
||||
|
||||
let result = do_call_rpc(&mut client, method, params).await;
|
||||
|
||||
Reference in New Issue
Block a user