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
This commit is contained in:
Svyatoslav Nikolsky
2020-07-28 13:57:25 +03:00
committed by Bastian Köcher
parent 70135cb797
commit 156ec9862f
10 changed files with 305 additions and 77 deletions
+39 -19
View File
@@ -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<RawTransaction>,
/// 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<LockFundsTransaction<Self::Id, Self::Recipient, Self::Amount>> {
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<T: pallet_bridge_eth_poa::Trait<I>, 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<T: pallet_bridge_eth_poa::Trait<I>,
let mut storage = BridgeStorage::<T, I>::new();
let header = HeaderBuilder::with_parent_number_on_runtime::<T, I>(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<T: pallet_bridge_eth_poa::Trait<I>,
pub(crate) fn prepare_ethereum_transaction(
recipient: &crate::AccountId,
editor: impl Fn(&mut sp_bridge_eth_poa::UnsignedTransaction),
) -> Vec<u8> {
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),
);
}
+1 -1
View File
@@ -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())
}
}
+2 -2
View File
@@ -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::<Vec<_>>();
let block_hash = crate::exchange::prepare_environment_for_claim::<Runtime, Kovan>(&transactions);
+1 -1
View File
@@ -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())
}
}
+111 -11
View File
@@ -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<T: Trait<I>, I: Instance> Module<T, I> {
}
/// 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::<T, I>::new(), block, tx_index, proof)
}
}
@@ -886,7 +890,7 @@ pub fn verify_transaction_finalized<S: Storage>(
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<S: Storage>(
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<u8> {
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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::new();
assert_eq!(
verify_transaction_finalized(
&storage,
example_header_with_failed_receipt().compute_hash(),
0,
&[(example_tx(), example_tx_receipt(false))],
),
false,
);
+1 -1
View File
@@ -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
}
+102 -9
View File
@@ -48,6 +48,9 @@ impl_fixed_hash_serde!(H520, 65);
/// Raw (RLP-encoded) ethereum transaction.
pub type RawTransaction = Vec<u8>;
/// Raw (RLP-encoded) ethereum transaction receipt.
pub type RawTransactionReceipt = Vec<u8>;
/// 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<Item = &'a RawTransactionReceipt>) -> 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<Item = &'a RawTransaction>) -> 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<Self, DecoderError> {
pub fn decode_rlp(raw_tx: &[u8]) -> Result<Self, DecoderError> {
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<bool, DecoderError> {
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<Transaction, DecoderError> {
pub fn transaction_decode_rlp(raw_tx: &[u8]) -> Result<Transaction, DecoderError> {
// 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),);
}
}
@@ -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,
@@ -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<EthereumToSubstrateExchange> 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<EthereumToSubstrateExchange> for SubstrateTransactionsTarget {
async fn filter_transaction_proof(&self, proof: &EthereumTransactionInclusionProof) -> Result<bool, Self::Error> {
// 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
+25 -23
View File
@@ -100,27 +100,29 @@ pub fn into_substrate_ethereum_header(header: &EthereumHeader) -> SubstrateEther
pub fn into_substrate_ethereum_receipts(
receipts: &Option<Vec<EthereumReceipt>>,
) -> Option<Vec<SubstrateEthereumReceipt>> {
receipts.as_ref().map(|receipts| {
receipts
.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,
},
})
.collect()
})
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(|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,
},
}
}