mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-10 02:57:58 +00:00
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:
committed by
Bastian Köcher
parent
70135cb797
commit
156ec9862f
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user