From e2045bf39eccf317f0fc6584053c9d281f348546 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 28 Jul 2020 13:57:25 +0300 Subject: [PATCH] Make transactions receipts part of transaction inclusion proof (#236) * make receipts part of tx proof * is_successful_raw_receipt_with_empty_data * cargo fmt --all * clippy * fix everything --- bridges/bin/node/runtime/src/exchange.rs | 58 ++++++--- bridges/bin/node/runtime/src/kovan.rs | 2 +- bridges/bin/node/runtime/src/lib.rs | 4 +- bridges/bin/node/runtime/src/rialto.rs | 2 +- bridges/modules/ethereum/src/lib.rs | 122 ++++++++++++++++-- bridges/modules/ethereum/src/test_utils.rs | 2 +- bridges/primitives/ethereum-poa/src/lib.rs | 111 ++++++++++++++-- .../primitives/ethereum-poa/src/signatures.rs | 6 +- .../relays/ethereum/src/ethereum_exchange.rs | 27 +++- .../relays/ethereum/src/substrate_types.rs | 44 ++++--- 10 files changed, 303 insertions(+), 75 deletions(-) diff --git a/bridges/bin/node/runtime/src/exchange.rs b/bridges/bin/node/runtime/src/exchange.rs index e41ec9ac40498..8754bec0893ff 100644 --- a/bridges/bin/node/runtime/src/exchange.rs +++ b/bridges/bin/node/runtime/src/exchange.rs @@ -30,7 +30,7 @@ use codec::{Decode, Encode}; use frame_support::RuntimeDebug; use hex_literal::hex; -use sp_bridge_eth_poa::{transaction_decode, RawTransaction}; +use sp_bridge_eth_poa::{transaction_decode_rlp, RawTransaction, RawTransactionReceipt}; use sp_currency_exchange::{ Error as ExchangeError, LockFundsTransaction, MaybeLockFundsTransaction, Result as ExchangeResult, }; @@ -46,8 +46,9 @@ pub struct EthereumTransactionInclusionProof { pub block: sp_core::H256, /// Index of the transaction within the block. pub index: u64, - /// The proof itself (right now it is all RLP-encoded transactions of the block). - pub proof: Vec, + /// The proof itself (right now it is all RLP-encoded transactions of the block + + /// RLP-encoded receipts of all transactions of the block). + pub proof: Vec<(RawTransaction, RawTransactionReceipt)>, } /// We uniquely identify transfer by the pair (sender, nonce). @@ -76,7 +77,7 @@ impl MaybeLockFundsTransaction for EthTransaction { fn parse( raw_tx: &Self::Transaction, ) -> ExchangeResult> { - let tx = transaction_decode(raw_tx).map_err(|_| ExchangeError::InvalidTransaction)?; + let tx = transaction_decode_rlp(raw_tx).map_err(|_| ExchangeError::InvalidTransaction)?; // we only accept transactions sending funds directly to the pre-configured address if tx.unsigned.to != Some(LOCK_FUNDS_ADDRESS.into()) { @@ -128,7 +129,7 @@ impl MaybeLockFundsTransaction for EthTransaction { /// Prepares everything required to bench claim of funds locked by given transaction. #[cfg(feature = "runtime-benchmarks")] pub(crate) fn prepare_environment_for_claim, I: pallet_bridge_eth_poa::Instance>( - transactions: &[RawTransaction], + transactions: &[(RawTransaction, RawTransactionReceipt)], ) -> sp_bridge_eth_poa::H256 { use pallet_bridge_eth_poa::{ test_utils::{insert_header, validator_utils::validator, HeaderBuilder}, @@ -138,7 +139,8 @@ pub(crate) fn prepare_environment_for_claim, let mut storage = BridgeStorage::::new(); let header = HeaderBuilder::with_parent_number_on_runtime::(0) - .with_transactions_root(compute_merkle_root(transactions.iter())) + .transactions_root(compute_merkle_root(transactions.iter().map(|(tx, _)| tx))) + .receipts_root(compute_merkle_root(transactions.iter().map(|(_, receipt)| receipt))) .sign_by(&validator(0)); let header_id = header.compute_id(); insert_header(&mut storage, header); @@ -152,8 +154,8 @@ pub(crate) fn prepare_environment_for_claim, pub(crate) fn prepare_ethereum_transaction( recipient: &crate::AccountId, editor: impl Fn(&mut sp_bridge_eth_poa::UnsignedTransaction), -) -> Vec { - use sp_bridge_eth_poa::signatures::SignTransaction; +) -> (RawTransaction, RawTransactionReceipt) { + use sp_bridge_eth_poa::{signatures::SignTransaction, Receipt, TransactionOutcome}; // prepare tx for OpenEthereum private dev chain: // chain id is 0x11 @@ -173,7 +175,16 @@ pub(crate) fn prepare_ethereum_transaction( payload: recipient_raw.to_vec(), }; editor(&mut eth_tx); - eth_tx.sign_by(&signer, Some(chain_id)) + ( + eth_tx.sign_by(&signer, Some(chain_id)), + Receipt { + outcome: TransactionOutcome::StatusCode(1), + gas_used: Default::default(), + log_bloom: Default::default(), + logs: Vec::new(), + } + .rlp(), + ) } #[cfg(test)] @@ -188,7 +199,7 @@ mod tests { #[test] fn valid_transaction_accepted() { assert_eq!( - EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |_| {})), + EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |_| {}).0), Ok(LockFundsTransaction { id: EthereumTransactionTag { account: hex!("00a329c0648769a73afac7f9381e08fb43dbea72"), @@ -211,9 +222,12 @@ mod tests { #[test] fn transaction_with_invalid_peer_recipient_rejected() { assert_eq!( - EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |tx| { - tx.to = None; - })), + EthTransaction::parse( + &prepare_ethereum_transaction(&ferdie(), |tx| { + tx.to = None; + }) + .0 + ), Err(ExchangeError::InvalidTransaction), ); } @@ -221,9 +235,12 @@ mod tests { #[test] fn transaction_with_invalid_recipient_rejected() { assert_eq!( - EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |tx| { - tx.payload.clear(); - })), + EthTransaction::parse( + &prepare_ethereum_transaction(&ferdie(), |tx| { + tx.payload.clear(); + }) + .0 + ), Err(ExchangeError::InvalidRecipient), ); } @@ -231,9 +248,12 @@ mod tests { #[test] fn transaction_with_invalid_amount_rejected() { assert_eq!( - EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |tx| { - tx.value = sp_core::U256::from(u128::max_value()) + sp_core::U256::from(1); - })), + EthTransaction::parse( + &prepare_ethereum_transaction(&ferdie(), |tx| { + tx.value = sp_core::U256::from(u128::max_value()) + sp_core::U256::from(1); + }) + .0 + ), Err(ExchangeError::InvalidAmount), ); } diff --git a/bridges/bin/node/runtime/src/kovan.rs b/bridges/bin/node/runtime/src/kovan.rs index ad641aa728a6d..005aaa8f7b961 100644 --- a/bridges/bin/node/runtime/src/kovan.rs +++ b/bridges/bin/node/runtime/src/kovan.rs @@ -149,7 +149,7 @@ impl PeerBlockchain for KovanBlockchain { return None; } - proof.proof.get(proof.index as usize).cloned() + proof.proof.get(proof.index as usize).map(|(tx, _)| tx.clone()) } } diff --git a/bridges/bin/node/runtime/src/lib.rs b/bridges/bin/node/runtime/src/lib.rs index 6c4ad00692e4b..113f32f09138c 100644 --- a/bridges/bin/node/runtime/src/lib.rs +++ b/bridges/bin/node/runtime/src/lib.rs @@ -716,7 +716,7 @@ impl_runtime_apis! { ).unwrap(); } - let transaction = crate::exchange::prepare_ethereum_transaction( + let (transaction, receipt) = crate::exchange::prepare_ethereum_transaction( &proof_params.recipient, |tx| { // our runtime only supports transactions where data is exactly 32 bytes long @@ -725,7 +725,7 @@ impl_runtime_apis! { tx.value = (ExistentialDeposit::get() * 10).into(); }, ); - let transactions = sp_std::iter::repeat(transaction.clone()) + let transactions = sp_std::iter::repeat((transaction, receipt)) .take(1 + proof_params.proof_size_factor as usize) .collect::>(); let block_hash = crate::exchange::prepare_environment_for_claim::(&transactions); diff --git a/bridges/bin/node/runtime/src/rialto.rs b/bridges/bin/node/runtime/src/rialto.rs index 6560dbb398097..195fed1693fdb 100644 --- a/bridges/bin/node/runtime/src/rialto.rs +++ b/bridges/bin/node/runtime/src/rialto.rs @@ -122,7 +122,7 @@ impl PeerBlockchain for RialtoBlockchain { return None; } - proof.proof.get(proof.index as usize).cloned() + proof.proof.get(proof.index as usize).map(|(tx, _)| tx.clone()) } } diff --git a/bridges/modules/ethereum/src/lib.rs b/bridges/modules/ethereum/src/lib.rs index de25c5aaa218b..ae6399f002fc1 100644 --- a/bridges/modules/ethereum/src/lib.rs +++ b/bridges/modules/ethereum/src/lib.rs @@ -21,7 +21,7 @@ use crate::finality::{CachedFinalityVotes, FinalityVotes}; use codec::{Decode, Encode}; use frame_support::{decl_module, decl_storage, traits::Get}; -use primitives::{Address, Header, HeaderId, RawTransaction, Receipt, H256, U256}; +use primitives::{Address, Header, HeaderId, RawTransaction, RawTransactionReceipt, Receipt, H256, U256}; use sp_runtime::{ transaction_validity::{ InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionSource, TransactionValidity, @@ -510,7 +510,11 @@ impl, I: Instance> Module { } /// Verify that transaction is included into given finalized block. - pub fn verify_transaction_finalized(block: H256, tx_index: u64, proof: &[RawTransaction]) -> bool { + pub fn verify_transaction_finalized( + block: H256, + tx_index: u64, + proof: &[(RawTransaction, RawTransactionReceipt)], + ) -> bool { crate::verify_transaction_finalized(&BridgeStorage::::new(), block, tx_index, proof) } } @@ -886,7 +890,7 @@ pub fn verify_transaction_finalized( storage: &S, block: H256, tx_index: u64, - proof: &[RawTransaction], + proof: &[(RawTransaction, RawTransactionReceipt)], ) -> bool { if tx_index >= proof.len() as _ { return false; @@ -914,7 +918,21 @@ pub fn verify_transaction_finalized( return false; } - header.verify_transactions_root(proof) + // verify that transaction is included in the block + if !header.verify_transactions_root(proof.iter().map(|(tx, _)| tx)) { + return false; + } + + // verify that transaction receipt is included in the block + if !header.verify_raw_receipts_root(proof.iter().map(|(_, r)| r)) { + return false; + } + + // check that transaction has completed successfully + matches!( + Receipt::is_successful_raw_receipt(&proof[tx_index as usize].1), + Ok(true) + ) } /// Transaction pool configuration. @@ -954,10 +972,31 @@ pub(crate) mod tests { vec![42] } + fn example_tx_receipt(success: bool) -> Vec { + Receipt { + // the only thing that we care of: + outcome: primitives::TransactionOutcome::StatusCode(if success { 1 } else { 0 }), + gas_used: Default::default(), + log_bloom: Default::default(), + logs: Vec::new(), + } + .rlp() + } + + fn example_header_with_failed_receipt() -> Header { + let mut header = Header::default(); + header.number = 3; + header.transactions_root = compute_merkle_root(vec![example_tx()].into_iter()); + header.receipts_root = compute_merkle_root(vec![example_tx_receipt(false)].into_iter()); + header.parent_hash = example_header().compute_hash(); + header + } + fn example_header() -> Header { let mut header = Header::default(); header.number = 2; header.transactions_root = compute_merkle_root(vec![example_tx()].into_iter()); + header.receipts_root = compute_merkle_root(vec![example_tx_receipt(true)].into_iter()); header.parent_hash = example_header_parent().compute_hash(); header } @@ -966,6 +1005,7 @@ pub(crate) mod tests { let mut header = Header::default(); header.number = 1; header.transactions_root = compute_merkle_root(vec![example_tx()].into_iter()); + header.receipts_root = compute_merkle_root(vec![example_tx_receipt(true)].into_iter()); header.parent_hash = genesis().compute_hash(); header } @@ -1265,7 +1305,12 @@ pub(crate) mod tests { run_test_with_genesis(example_header(), TOTAL_VALIDATORS, |_| { let storage = BridgeStorage::::new(); assert_eq!( - verify_transaction_finalized(&storage, example_header().compute_hash(), 0, &[example_tx()],), + verify_transaction_finalized( + &storage, + example_header().compute_hash(), + 0, + &[(example_tx(), example_tx_receipt(true))], + ), true, ); }); @@ -1279,7 +1324,12 @@ pub(crate) mod tests { insert_header(&mut storage, example_header()); storage.finalize_and_prune_headers(Some(example_header().compute_id()), 0); assert_eq!( - verify_transaction_finalized(&storage, example_header_parent().compute_hash(), 0, &[example_tx()],), + verify_transaction_finalized( + &storage, + example_header_parent().compute_hash(), + 0, + &[(example_tx(), example_tx_receipt(true))], + ), true, ); }); @@ -1314,7 +1364,12 @@ pub(crate) mod tests { insert_header(&mut storage, example_header_parent()); insert_header(&mut storage, example_header()); assert_eq!( - verify_transaction_finalized(&storage, example_header().compute_hash(), 0, &[example_tx()],), + verify_transaction_finalized( + &storage, + example_header().compute_hash(), + 0, + &[(example_tx(), example_tx_receipt(true))], + ), false, ); }); @@ -1333,7 +1388,12 @@ pub(crate) mod tests { insert_header(&mut storage, finalized_header_sibling); storage.finalize_and_prune_headers(Some(example_header().compute_id()), 0); assert_eq!( - verify_transaction_finalized(&storage, finalized_header_sibling_hash, 0, &[example_tx()],), + verify_transaction_finalized( + &storage, + finalized_header_sibling_hash, + 0, + &[(example_tx(), example_tx_receipt(true))], + ), false, ); }); @@ -1352,14 +1412,19 @@ pub(crate) mod tests { insert_header(&mut storage, example_header()); storage.finalize_and_prune_headers(Some(example_header().compute_id()), 0); assert_eq!( - verify_transaction_finalized(&storage, finalized_header_uncle_hash, 0, &[example_tx()],), + verify_transaction_finalized( + &storage, + finalized_header_uncle_hash, + 0, + &[(example_tx(), example_tx_receipt(true))], + ), false, ); }); } #[test] - fn verify_transaction_finalized_rejects_invalid_proof() { + fn verify_transaction_finalized_rejects_invalid_transactions_in_proof() { run_test_with_genesis(example_header(), TOTAL_VALIDATORS, |_| { let storage = BridgeStorage::::new(); assert_eq!( @@ -1367,7 +1432,42 @@ pub(crate) mod tests { &storage, example_header().compute_hash(), 0, - &[example_tx(), example_tx()], + &[ + (example_tx(), example_tx_receipt(true)), + (example_tx(), example_tx_receipt(true)) + ], + ), + false, + ); + }); + } + + #[test] + fn verify_transaction_finalized_rejects_invalid_receipts_in_proof() { + run_test_with_genesis(example_header(), TOTAL_VALIDATORS, |_| { + let storage = BridgeStorage::::new(); + assert_eq!( + verify_transaction_finalized( + &storage, + example_header().compute_hash(), + 0, + &[(example_tx(), vec![42])], + ), + false, + ); + }); + } + + #[test] + fn verify_transaction_finalized_rejects_failed_transaction() { + run_test_with_genesis(example_header_with_failed_receipt(), TOTAL_VALIDATORS, |_| { + let storage = BridgeStorage::::new(); + assert_eq!( + verify_transaction_finalized( + &storage, + example_header_with_failed_receipt().compute_hash(), + 0, + &[(example_tx(), example_tx_receipt(false))], ), false, ); diff --git a/bridges/modules/ethereum/src/test_utils.rs b/bridges/modules/ethereum/src/test_utils.rs index e6e3ac4681c22..035882450fbb5 100644 --- a/bridges/modules/ethereum/src/test_utils.rs +++ b/bridges/modules/ethereum/src/test_utils.rs @@ -195,7 +195,7 @@ impl HeaderBuilder { } /// Update transactions root field of this header. - pub fn with_transactions_root(mut self, transactions_root: H256) -> Self { + pub fn transactions_root(mut self, transactions_root: H256) -> Self { self.header.transactions_root = transactions_root; self } diff --git a/bridges/primitives/ethereum-poa/src/lib.rs b/bridges/primitives/ethereum-poa/src/lib.rs index 444af8cfa3638..f418a8e325b80 100644 --- a/bridges/primitives/ethereum-poa/src/lib.rs +++ b/bridges/primitives/ethereum-poa/src/lib.rs @@ -48,6 +48,9 @@ impl_fixed_hash_serde!(H520, 65); /// Raw (RLP-encoded) ethereum transaction. pub type RawTransaction = Vec; +/// Raw (RLP-encoded) ethereum transaction receipt. +pub type RawTransactionReceipt = Vec; + /// An ethereum address. pub type Address = H160; @@ -208,9 +211,14 @@ impl Header { verify_merkle_proof(self.receipts_root, receipts.iter().map(|r| r.rlp())) } + /// Check if passed raw transactions receipts are matching receipts root in this header. + pub fn verify_raw_receipts_root<'a>(&self, receipts: impl IntoIterator) -> bool { + verify_merkle_proof(self.receipts_root, receipts.into_iter()) + } + /// Check if passed transactions are matching transactions root in this header. - pub fn verify_transactions_root(&self, transactions: &[RawTransaction]) -> bool { - verify_merkle_proof(self.transactions_root, transactions.iter()) + pub fn verify_transactions_root<'a>(&self, transactions: impl IntoIterator) -> bool { + verify_merkle_proof(self.transactions_root, transactions.into_iter()) } /// Gets the seal hash of this header. @@ -277,7 +285,7 @@ impl Header { impl UnsignedTransaction { /// Decode unsigned portion of raw transaction RLP. - pub fn decode(raw_tx: &[u8]) -> Result { + pub fn decode_rlp(raw_tx: &[u8]) -> Result { let tx_rlp = Rlp::new(raw_tx); let to = tx_rlp.at(3)?; Ok(UnsignedTransaction { @@ -325,6 +333,25 @@ impl UnsignedTransaction { } impl Receipt { + /// Decode status from raw transaction receipt RLP. + pub fn is_successful_raw_receipt(raw_receipt: &[u8]) -> Result { + let rlp = Rlp::new(raw_receipt); + if rlp.item_count()? == 3 { + // no outcome - invalid tx? + Ok(false) + } else { + let first = rlp.at(0)?; + if first.is_data() && first.data()?.len() <= 1 { + // EIP-658 transaction - status of successful transaction is 1 + let status: u8 = first.as_val()?; + Ok(status == 1) + } else { + // pre-EIP-658 transaction - we do not support this kind of transactions + Ok(false) + } + } + } + /// Returns receipt RLP. pub fn rlp(&self) -> Bytes { let mut s = RlpStream::new(); @@ -436,9 +463,9 @@ impl std::fmt::Debug for Bloom { } /// Decode Ethereum transaction. -pub fn transaction_decode(raw_tx: &[u8]) -> Result { +pub fn transaction_decode_rlp(raw_tx: &[u8]) -> Result { // parse transaction fields - let unsigned = UnsignedTransaction::decode(raw_tx)?; + let unsigned = UnsignedTransaction::decode_rlp(raw_tx)?; let tx_rlp = Rlp::new(raw_tx); let v: u64 = tx_rlp.val_at(6)?; let r: U256 = tx_rlp.val_at(7)?; @@ -548,7 +575,7 @@ mod tests { // https://etherscan.io/getRawTx?tx=0xb9d4ad5408f53eac8627f9ccd840ba8fb3469d55cd9cc2a11c6e049f1eef4edd let raw_tx = hex!("f86c0a85046c7cfe0083016dea94d1310c1e038bc12865d3d3997275b3e4737c6302880b503be34d9fe80080269fc7eaaa9c21f59adf8ad43ed66cf5ef9ee1c317bd4d32cd65401e7aaca47cfaa0387d79c65b90be6260d09dcfb780f29dd8133b9b1ceb20b83b7e442b4bfc30cb"); assert_eq!( - transaction_decode(&raw_tx), + transaction_decode_rlp(&raw_tx), Ok(Transaction { sender: hex!("67835910d32600471f388a137bbff3eb07993c04").into(), unsigned: UnsignedTransaction { @@ -567,7 +594,7 @@ mod tests { // https://kovan.etherscan.io/getRawTx?tx=0x3b4b7bd41c1178045ccb4753aa84c1ef9864b4d712fa308b228917cd837915da let raw_tx = hex!("f86a822816808252089470c1ccde719d6f477084f07e4137ab0e55f8369f8930cf46e92063afd8008078a00e4d1f4d8aa992bda3c105ff3d6e9b9acbfd99facea00985e2131029290adbdca028ea29a46a4b66ec65b454f0706228e3768cb0ecf755f67c50ddd472f11d5994"); assert_eq!( - transaction_decode(&raw_tx), + transaction_decode_rlp(&raw_tx), Ok(Transaction { sender: hex!("faadface3fbd81ce37b0e19c0b65ff4234148132").into(), unsigned: UnsignedTransaction { @@ -589,7 +616,7 @@ mod tests { // https://etherscan.io/getRawTx?tx=0xdc2b996b4d1d6922bf6dba063bfd70913279cb6170967c9bb80252aeb061cf65 let raw_tx = hex!("f8aa76850430e234008301500094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000e08f35f66867a454835b25118f1e490e7f9e9a7400000000000000000000000000000000000000000000000000000000004c4b4025a0964e023999621dc3d4d831c43c71f7555beb6d1192dee81a3674b3f57e310f21a00f229edd86f841d1ee4dc48cc16667e2283817b1d39bae16ced10cd206ae4fd4"); assert_eq!( - transaction_decode(&raw_tx), + transaction_decode_rlp(&raw_tx), Ok(Transaction { sender: hex!("2b9a4d37bdeecdf994c4c9ad7f3cf8dc632f7d70").into(), unsigned: UnsignedTransaction { @@ -608,7 +635,7 @@ mod tests { // https://kovan.etherscan.io/getRawTx?tx=0x2904b4451d23665492239016b78da052d40d55fdebc7304b38e53cf6a37322cf let raw_tx = hex!("f8ac8302200b843b9aca00830271009484dd11eb2a29615303d18149c0dbfa24167f896680b844a9059cbb00000000000000000000000001503dfc5ad81bf630d83697e98601871bb211b600000000000000000000000000000000000000000000000000000000000027101ba0ce126d2cca81f5e245f292ff84a0d915c0a4ac52af5c51219db1e5d36aa8da35a0045298b79dac631907403888f9b04c2ab5509fe0cc31785276d30a40b915fcf9"); assert_eq!( - transaction_decode(&raw_tx), + transaction_decode_rlp(&raw_tx), Ok(Transaction { sender: hex!("617da121abf03d4c1af572f5a4e313e26bef7bdc").into(), unsigned: UnsignedTransaction { @@ -622,4 +649,70 @@ mod tests { }), ); } + + #[test] + fn is_successful_raw_receipt_works() { + assert!(Receipt::is_successful_raw_receipt(&[]).is_err()); + + assert_eq!( + Receipt::is_successful_raw_receipt( + &Receipt { + outcome: TransactionOutcome::Unknown, + gas_used: Default::default(), + log_bloom: Default::default(), + logs: Vec::new(), + } + .rlp() + ), + Ok(false), + ); + assert_eq!( + Receipt::is_successful_raw_receipt( + &Receipt { + outcome: TransactionOutcome::StateRoot(Default::default()), + gas_used: Default::default(), + log_bloom: Default::default(), + logs: Vec::new(), + } + .rlp() + ), + Ok(false), + ); + assert_eq!( + Receipt::is_successful_raw_receipt( + &Receipt { + outcome: TransactionOutcome::StatusCode(0), + gas_used: Default::default(), + log_bloom: Default::default(), + logs: Vec::new(), + } + .rlp() + ), + Ok(false), + ); + assert_eq!( + Receipt::is_successful_raw_receipt( + &Receipt { + outcome: TransactionOutcome::StatusCode(1), + gas_used: Default::default(), + log_bloom: Default::default(), + logs: Vec::new(), + } + .rlp() + ), + Ok(true), + ); + } + + #[test] + fn is_successful_raw_receipt_with_empty_data() { + let mut stream = RlpStream::new(); + stream.begin_list(4); + stream.append_empty_data(); + stream.append(&1u64); + stream.append(&2u64); + stream.append(&3u64); + + assert_eq!(Receipt::is_successful_raw_receipt(&stream.out()), Ok(false),); + } } diff --git a/bridges/primitives/ethereum-poa/src/signatures.rs b/bridges/primitives/ethereum-poa/src/signatures.rs index 309297e1ccbc5..4d97fbcbfbd1a 100644 --- a/bridges/primitives/ethereum-poa/src/signatures.rs +++ b/bridges/primitives/ethereum-poa/src/signatures.rs @@ -98,7 +98,7 @@ pub fn secret_to_address(secret: &SecretKey) -> Address { #[cfg(test)] mod tests { use super::*; - use crate::{transaction_decode, Transaction}; + use crate::{transaction_decode_rlp, Transaction}; #[test] fn transaction_signed_properly() { @@ -115,7 +115,7 @@ mod tests { }; let raw_tx = unsigned.clone().sign_by(&signer, Some(42)); assert_eq!( - transaction_decode(&raw_tx), + transaction_decode_rlp(&raw_tx), Ok(Transaction { sender: signer_address, unsigned, @@ -133,7 +133,7 @@ mod tests { }; let raw_tx = unsigned.clone().sign_by(&signer, None); assert_eq!( - transaction_decode(&raw_tx), + transaction_decode_rlp(&raw_tx), Ok(Transaction { sender: signer_address, unsigned, diff --git a/bridges/relays/ethereum/src/ethereum_exchange.rs b/bridges/relays/ethereum/src/ethereum_exchange.rs index d27f3345e8a6a..62e2d3eefb6c5 100644 --- a/bridges/relays/ethereum/src/ethereum_exchange.rs +++ b/bridges/relays/ethereum/src/ethereum_exchange.rs @@ -31,6 +31,7 @@ use crate::rpc_errors::RpcError; use crate::substrate_client::{ SubmitEthereumExchangeTransactionProof, SubstrateConnectionParams, SubstrateRpcClient, SubstrateSigningParams, }; +use crate::substrate_types::into_substrate_ethereum_receipt; use crate::sync_types::HeaderId; use async_trait::async_trait; @@ -169,12 +170,17 @@ impl SourceClient for EthereumTransactionsSource { node are having `raw` field; qed"; const BLOCK_HAS_HASH_FIELD_PROOF: &str = "RPC level checks that block has `hash` field; qed"; - let transaction_proof = block - .0 - .transactions - .iter() - .map(|tx| tx.raw.clone().expect(TRANSACTION_HAS_RAW_FIELD_PROOF).0) - .collect(); + 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), @@ -221,11 +227,18 @@ impl TargetClient for SubstrateTransactionsTarget { async fn filter_transaction_proof(&self, proof: &EthereumTransactionInclusionProof) -> Result { // let's try to parse transaction locally - let parse_result = bridge_node_runtime::exchange::EthTransaction::parse(&proof.proof[proof.index as usize]); + let (raw_tx, raw_tx_receipt) = &proof.proof[proof.index as usize]; + let parse_result = bridge_node_runtime::exchange::EthTransaction::parse(raw_tx); if parse_result.is_err() { return Ok(false); } + // now let's check if transaction is successful + match sp_bridge_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 diff --git a/bridges/relays/ethereum/src/substrate_types.rs b/bridges/relays/ethereum/src/substrate_types.rs index 66e5aee67282b..7d761a627e66a 100644 --- a/bridges/relays/ethereum/src/substrate_types.rs +++ b/bridges/relays/ethereum/src/substrate_types.rs @@ -100,27 +100,29 @@ pub fn into_substrate_ethereum_header(header: &EthereumHeader) -> SubstrateEther pub fn into_substrate_ethereum_receipts( receipts: &Option>, ) -> Option> { - receipts.as_ref().map(|receipts| { - receipts + 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.gas_used.expect(ETHEREUM_RECEIPT_GAS_USED_PROOF), + log_bloom: receipt.logs_bloom.data().into(), + logs: receipt + .logs .iter() - .map(|receipt| SubstrateEthereumReceipt { - gas_used: receipt.gas_used.expect(ETHEREUM_RECEIPT_GAS_USED_PROOF), - 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, - }, + .map(|log_entry| SubstrateEthereumLogEntry { + address: log_entry.address, + topics: log_entry.topics.clone(), + data: log_entry.data.0.clone(), }) - .collect() - }) + .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, + }, + } }