Skip to content

Commit

Permalink
Auto relay exchange transactions (paritytech#227)
Browse files Browse the repository at this point in the history
* auto relay exchange transactions

* docker + auto-relay-tx

* clippy

* jsonrpsee in Cargo.lock ???

* fix tests compilation

* Show sccache

* mute clippy

* move

* Update relays/ethereum/src/exchange.rs

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* finish comment

* (bool, String) -> StringifiedMaybeConnectionError

* Update deployments/rialto/docker-compose.yml

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

Co-authored-by: Denis S. Soldatov aka General-Beck <general.beck@gmail.com>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
  • Loading branch information
3 people authored and serban300 committed Apr 9, 2024
1 parent 9765d55 commit 6356852
Show file tree
Hide file tree
Showing 17 changed files with 1,163 additions and 229 deletions.
6 changes: 6 additions & 0 deletions bridges/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,12 @@ impl_runtime_apis! {
}
}

impl sp_currency_exchange::CurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
BridgeCurrencyExchange::filter_transaction_proof(&proof)
}
}

impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
fn validate_transaction(
source: TransactionSource,
Expand Down
72 changes: 52 additions & 20 deletions bridges/modules/currency-exchange/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,36 +105,21 @@ decl_module! {
) -> DispatchResult {
let submitter = frame_system::ensure_signed(origin)?;

// ensure that transaction is included in finalized block that we know of
let transaction = <T as Trait>::PeerBlockchain::verify_transaction_inclusion_proof(
&proof,
).ok_or_else(|| Error::<T>::UnfinalizedTransaction)?;

// parse transaction
let transaction = <T as Trait>::PeerMaybeLockFundsTransaction::parse(&transaction)
.map_err(Error::<T>::from)?;
let transfer_id = transaction.id;
ensure!(
!Transfers::<T>::contains_key(&transfer_id),
Error::<T>::AlreadyClaimed
);

// grant recipient
let recipient = T::RecipientsMap::map(transaction.recipient).map_err(Error::<T>::from)?;
let amount = T::CurrencyConverter::convert(transaction.amount).map_err(Error::<T>::from)?;
// verify and parse transaction proof
let deposit = prepare_deposit_details::<T>(&proof)?;

// make sure to update the mapping if we deposit successfully to avoid double spending,
// i.e. whenever `deposit_into` is successful we MUST update `Transfers`.
{
// if any changes were made to the storage, we can't just return error here, because
// otherwise the same proof may be imported again
let deposit_result = T::DepositInto::deposit_into(recipient, amount);
let deposit_result = T::DepositInto::deposit_into(deposit.recipient, deposit.amount);
match deposit_result {
Ok(_) => (),
Err(ExchangeError::DepositPartiallyFailed) => (),
Err(error) => return Err(Error::<T>::from(error).into()),
}
Transfers::<T>::insert(&transfer_id, ())
Transfers::<T>::insert(&deposit.transfer_id, ())
}

// reward submitter for providing valid message
Expand All @@ -143,7 +128,7 @@ decl_module! {
frame_support::debug::trace!(
target: "runtime",
"Completed currency exchange: {:?}",
transfer_id,
deposit.transfer_id,
);

Ok(())
Expand All @@ -158,6 +143,18 @@ decl_storage! {
}
}

impl<T: Trait> Module<T> {
/// Returns true if currency exchange module is able to import given transaction proof in
/// its current state.
pub fn filter_transaction_proof(proof: &<T::PeerBlockchain as Blockchain>::TransactionInclusionProof) -> bool {
if prepare_deposit_details::<T>(proof).is_err() {
return false;
}

true
}
}

impl<T: Trait> From<ExchangeError> for Error<T> {
fn from(error: ExchangeError) -> Self {
match error {
Expand All @@ -176,6 +173,41 @@ impl<AccountId> OnTransactionSubmitted<AccountId> for () {
fn on_valid_transaction_submitted(_: AccountId) {}
}

/// Exchange deposit details.
struct DepositDetails<T: Trait> {
/// Transfer id.
pub transfer_id: <T::PeerMaybeLockFundsTransaction as MaybeLockFundsTransaction>::Id,
/// Transfer recipient.
pub recipient: <T::RecipientsMap as RecipientsMap>::Recipient,
/// Transfer amount.
pub amount: <T::CurrencyConverter as CurrencyConverter>::TargetAmount,
}

/// Verify and parse transaction proof, preparing everything required for importing
/// this transaction proof.
fn prepare_deposit_details<T: Trait>(
proof: &<<T as Trait>::PeerBlockchain as Blockchain>::TransactionInclusionProof,
) -> Result<DepositDetails<T>, Error<T>> {
// ensure that transaction is included in finalized block that we know of
let transaction = <T as Trait>::PeerBlockchain::verify_transaction_inclusion_proof(proof)
.ok_or_else(|| Error::<T>::UnfinalizedTransaction)?;

// parse transaction
let transaction = <T as Trait>::PeerMaybeLockFundsTransaction::parse(&transaction).map_err(Error::<T>::from)?;
let transfer_id = transaction.id;
ensure!(!Transfers::<T>::contains_key(&transfer_id), Error::<T>::AlreadyClaimed);

// grant recipient
let recipient = T::RecipientsMap::map(transaction.recipient).map_err(Error::<T>::from)?;
let amount = T::CurrencyConverter::convert(transaction.amount).map_err(Error::<T>::from)?;

Ok(DepositDetails {
transfer_id,
recipient,
amount,
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
7 changes: 7 additions & 0 deletions bridges/primitives/currency-exchange/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ codec = { package = "parity-scale-codec", version = "1.3.1", default-features =

# Substrate Based Dependencies

[dependencies.sp-api]
version = "2.0.0-rc4"
tag = 'v2.0.0-rc4'
default-features = false
git = "https://github.com/paritytech/substrate.git"

[dependencies.sp-std]
version = "2.0.0-rc4"
tag = 'v2.0.0-rc4'
Expand All @@ -27,6 +33,7 @@ git = "https://github.com/paritytech/substrate.git"
default = ["std"]
std = [
"codec/std",
"sp-api/std",
"sp-std/std",
"frame-support/std",
]
16 changes: 15 additions & 1 deletion bridges/primitives/currency-exchange/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

#![cfg_attr(not(feature = "std"), no_std)]
// RuntimeApi generated functions
#![allow(clippy::too_many_arguments)]
// Generated by `DecodeLimit::decode_with_depth_limit`
#![allow(clippy::unnecessary_mut_passed)]

use codec::{Decode, Encode, EncodeLike};
use frame_support::RuntimeDebug;
use frame_support::{Parameter, RuntimeDebug};
use sp_api::decl_runtime_apis;
use sp_std::marker::PhantomData;

/// All errors that may happen during exchange.
Expand Down Expand Up @@ -127,3 +132,12 @@ impl<Amount> CurrencyConverter for IdentityCurrencyConverter<Amount> {
Ok(currency)
}
}

decl_runtime_apis! {
/// API for exchange transactions submitters.
pub trait CurrencyExchangeApi<Proof: Parameter> {
/// Returns true if currency exchange module is able to import transaction proof in
/// its current state.
fn filter_transaction_proof(proof: Proof) -> bool;
}
}
1 change: 1 addition & 0 deletions bridges/relays/ethereum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ rustc-hex = "2.0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.57"
sysinfo = "0.15"
sp-currency-exchange = { path = "../../primitives/currency-exchange" }
sp-bridge-eth-poa = { path = "../../primitives/ethereum-poa" }
time = "0.2"
web3 = "0.13"
Expand Down
8 changes: 7 additions & 1 deletion bridges/relays/ethereum/src/cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,18 @@ subcommands:
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
required: true
- sub-host: *sub-host
- sub-port: *sub-port
- sub-signer: *sub-signer
Expand Down
17 changes: 17 additions & 0 deletions bridges/relays/ethereum/src/ethereum_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,23 @@ impl EthereumRpc for EthereumRpcClient {
}
}

async fn header_by_number_with_transactions(&self, number: u64) -> Result<HeaderWithTransactions> {
let get_full_tx_objects = true;
let header = Ethereum::get_block_by_number_with_transactions(&self.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(RpcError::Ethereum(EthereumNodeError::IncompleteHeader));
}

let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some());
if !is_complete_transactions {
return Err(RpcError::Ethereum(EthereumNodeError::IncompleteTransaction));
}

Ok(header)
}

async fn header_by_hash_with_transactions(&self, hash: H256) -> Result<HeaderWithTransactions> {
let get_full_tx_objects = true;
let header = Ethereum::get_block_by_hash_with_transactions(&self.client, hash, get_full_tx_objects).await?;
Expand Down
Loading

0 comments on commit 6356852

Please sign in to comment.