Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 63b51d9

Browse files
authored
Read extrinsic dispatch result for mined transaction (#1582)
* read extrinsic dispatch result for mined transaction * commit for the history * Revert "commit for the history" This reverts commit 99341b04750639db296172cc1432bd70e458ef4b. * Revert "read extrinsic dispatch result for mined transaction" This reverts commit 662b776cbf992be9f1637e52f023b782e8c441d1. * check for successfult transaction in finality relay * check for successful transaction in parachains relay * TrackedTransactionStatus ->TrackedTransactionStatus<HeaderId> * check for successful transaction in messages relay * fix compilation * message_lane_loop_is_able_to_recover_from_unsuccessful_transaction * fixed too-complex-type clippy error * aaand compilation
1 parent 8b56d7c commit 63b51d9

File tree

12 files changed

+462
-114
lines changed

12 files changed

+462
-114
lines changed

relays/client-substrate/src/client.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,8 @@ impl<C: Chain> Client<C> {
467467
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Result<UnsignedTransaction<C>>
468468
+ Send
469469
+ 'static,
470-
) -> Result<TransactionTracker<C>> {
470+
) -> Result<TransactionTracker<C, Self>> {
471+
let self_clone = self.clone();
471472
let _guard = self.submit_signed_extrinsic_lock.lock().await;
472473
let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
473474
let best_header = self.best_header().await?;
@@ -494,6 +495,7 @@ impl<C: Chain> Client<C> {
494495
})?;
495496
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
496497
let tracker = TransactionTracker::new(
498+
self_clone,
497499
stall_timeout,
498500
tx_hash,
499501
Subscription(Mutex::new(receiver)),

relays/client-substrate/src/transaction_tracker.rs

+101-23
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,28 @@
1616

1717
//! Helper for tracking transaction invalidation events.
1818
19-
use crate::{Chain, HashOf, Subscription, TransactionStatusOf};
19+
use crate::{Chain, Client, Error, HashOf, HeaderIdOf, Subscription, TransactionStatusOf};
2020

2121
use async_trait::async_trait;
2222
use futures::{future::Either, Future, FutureExt, Stream, StreamExt};
23-
use relay_utils::TrackedTransactionStatus;
23+
use relay_utils::{HeaderId, TrackedTransactionStatus};
24+
use sp_runtime::traits::Header as _;
2425
use std::time::Duration;
2526

27+
/// Transaction tracker environment.
28+
#[async_trait]
29+
pub trait Environment<C: Chain>: Send + Sync {
30+
/// Returns header id by its hash.
31+
async fn header_id_by_hash(&self, hash: HashOf<C>) -> Result<HeaderIdOf<C>, Error>;
32+
}
33+
34+
#[async_trait]
35+
impl<C: Chain> Environment<C> for Client<C> {
36+
async fn header_id_by_hash(&self, hash: HashOf<C>) -> Result<HeaderIdOf<C>, Error> {
37+
self.header_by_hash(hash).await.map(|h| HeaderId(*h.number(), hash))
38+
}
39+
}
40+
2641
/// Substrate transaction tracker implementation.
2742
///
2843
/// Substrate node provides RPC API to submit and watch for transaction events. This way
@@ -43,20 +58,22 @@ use std::time::Duration;
4358
/// it is lost.
4459
///
4560
/// This struct implements third option as it seems to be the most optimal.
46-
pub struct TransactionTracker<C: Chain> {
61+
pub struct TransactionTracker<C: Chain, E> {
62+
environment: E,
4763
transaction_hash: HashOf<C>,
4864
stall_timeout: Duration,
4965
subscription: Subscription<TransactionStatusOf<C>>,
5066
}
5167

52-
impl<C: Chain> TransactionTracker<C> {
68+
impl<C: Chain, E: Environment<C>> TransactionTracker<C, E> {
5369
/// Create transaction tracker.
5470
pub fn new(
71+
environment: E,
5572
stall_timeout: Duration,
5673
transaction_hash: HashOf<C>,
5774
subscription: Subscription<TransactionStatusOf<C>>,
5875
) -> Self {
59-
Self { stall_timeout, transaction_hash, subscription }
76+
Self { environment, stall_timeout, transaction_hash, subscription }
6077
}
6178

6279
/// Wait for final transaction status and return it along with last known internal invalidation
@@ -65,10 +82,11 @@ impl<C: Chain> TransactionTracker<C> {
6582
self,
6683
wait_for_stall_timeout: impl Future<Output = ()>,
6784
wait_for_stall_timeout_rest: impl Future<Output = ()>,
68-
) -> (TrackedTransactionStatus, Option<InvalidationStatus>) {
85+
) -> (TrackedTransactionStatus<HeaderIdOf<C>>, Option<InvalidationStatus<HeaderIdOf<C>>>) {
6986
// sometimes we want to wait for the rest of the stall timeout even if
7087
// `wait_for_invalidation` has been "select"ed first => it is shared
71-
let wait_for_invalidation = watch_transaction_status::<C, _>(
88+
let wait_for_invalidation = watch_transaction_status::<_, C, _>(
89+
self.environment,
7290
self.transaction_hash,
7391
self.subscription.into_stream(),
7492
);
@@ -86,8 +104,8 @@ impl<C: Chain> TransactionTracker<C> {
86104
(TrackedTransactionStatus::Lost, None)
87105
},
88106
Either::Right((invalidation_status, _)) => match invalidation_status {
89-
InvalidationStatus::Finalized =>
90-
(TrackedTransactionStatus::Finalized, Some(invalidation_status)),
107+
InvalidationStatus::Finalized(at_block) =>
108+
(TrackedTransactionStatus::Finalized(at_block), Some(invalidation_status)),
91109
InvalidationStatus::Invalid =>
92110
(TrackedTransactionStatus::Lost, Some(invalidation_status)),
93111
InvalidationStatus::Lost => {
@@ -111,8 +129,10 @@ impl<C: Chain> TransactionTracker<C> {
111129
}
112130

113131
#[async_trait]
114-
impl<C: Chain> relay_utils::TransactionTracker for TransactionTracker<C> {
115-
async fn wait(self) -> TrackedTransactionStatus {
132+
impl<C: Chain, E: Environment<C>> relay_utils::TransactionTracker for TransactionTracker<C, E> {
133+
type HeaderId = HeaderIdOf<C>;
134+
135+
async fn wait(self) -> TrackedTransactionStatus<HeaderIdOf<C>> {
116136
let wait_for_stall_timeout = async_std::task::sleep(self.stall_timeout).shared();
117137
let wait_for_stall_timeout_rest = wait_for_stall_timeout.clone();
118138
self.do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest).await.0
@@ -125,20 +145,25 @@ impl<C: Chain> relay_utils::TransactionTracker for TransactionTracker<C> {
125145
/// ignored - relay loops are detecting the mining/finalization using their own
126146
/// techniques. That's why we're using `InvalidationStatus` here.
127147
#[derive(Debug, PartialEq)]
128-
enum InvalidationStatus {
129-
/// Transaction has been included into block and finalized.
130-
Finalized,
148+
enum InvalidationStatus<BlockId> {
149+
/// Transaction has been included into block and finalized at given block.
150+
Finalized(BlockId),
131151
/// Transaction has been invalidated.
132152
Invalid,
133153
/// We have lost track of transaction status.
134154
Lost,
135155
}
136156

137157
/// Watch for transaction status until transaction is finalized or we lose track of its status.
138-
async fn watch_transaction_status<C: Chain, S: Stream<Item = TransactionStatusOf<C>>>(
158+
async fn watch_transaction_status<
159+
E: Environment<C>,
160+
C: Chain,
161+
S: Stream<Item = TransactionStatusOf<C>>,
162+
>(
163+
environment: E,
139164
transaction_hash: HashOf<C>,
140165
subscription: S,
141-
) -> InvalidationStatus {
166+
) -> InvalidationStatus<HeaderIdOf<C>> {
142167
futures::pin_mut!(subscription);
143168

144169
loop {
@@ -153,7 +178,23 @@ async fn watch_transaction_status<C: Chain, S: Stream<Item = TransactionStatusOf
153178
transaction_hash,
154179
block_hash,
155180
);
156-
return InvalidationStatus::Finalized
181+
182+
let header_id = match environment.header_id_by_hash(block_hash).await {
183+
Ok(header_id) => header_id,
184+
Err(e) => {
185+
log::error!(
186+
target: "bridge",
187+
"Failed to read header {:?} when watching for {} transaction {:?}: {:?}",
188+
block_hash,
189+
C::NAME,
190+
transaction_hash,
191+
e,
192+
);
193+
// that's the best option we have here
194+
return InvalidationStatus::Lost
195+
},
196+
};
197+
return InvalidationStatus::Finalized(header_id)
157198
},
158199
Some(TransactionStatusOf::<C>::Invalid) => {
159200
// if node says that the transaction is invalid, there are still chances that
@@ -247,11 +288,27 @@ mod tests {
247288
use futures::{FutureExt, SinkExt};
248289
use sc_transaction_pool_api::TransactionStatus;
249290

291+
struct TestEnvironment(Result<HeaderIdOf<TestChain>, Error>);
292+
293+
#[async_trait]
294+
impl Environment<TestChain> for TestEnvironment {
295+
async fn header_id_by_hash(
296+
&self,
297+
_hash: HashOf<TestChain>,
298+
) -> Result<HeaderIdOf<TestChain>, Error> {
299+
self.0.as_ref().map_err(|_| Error::UninitializedBridgePallet).cloned()
300+
}
301+
}
302+
250303
async fn on_transaction_status(
251304
status: TransactionStatus<HashOf<TestChain>, HashOf<TestChain>>,
252-
) -> Option<(TrackedTransactionStatus, InvalidationStatus)> {
305+
) -> Option<(
306+
TrackedTransactionStatus<HeaderIdOf<TestChain>>,
307+
InvalidationStatus<HeaderIdOf<TestChain>>,
308+
)> {
253309
let (mut sender, receiver) = futures::channel::mpsc::channel(1);
254-
let tx_tracker = TransactionTracker::<TestChain>::new(
310+
let tx_tracker = TransactionTracker::<TestChain, TestEnvironment>::new(
311+
TestEnvironment(Ok(HeaderId(0, Default::default()))),
255312
Duration::from_secs(0),
256313
Default::default(),
257314
Subscription(async_std::sync::Mutex::new(receiver)),
@@ -270,7 +327,23 @@ mod tests {
270327
async fn returns_finalized_on_finalized() {
271328
assert_eq!(
272329
on_transaction_status(TransactionStatus::Finalized(Default::default())).await,
273-
Some((TrackedTransactionStatus::Finalized, InvalidationStatus::Finalized)),
330+
Some((
331+
TrackedTransactionStatus::Finalized(Default::default()),
332+
InvalidationStatus::Finalized(Default::default())
333+
)),
334+
);
335+
}
336+
337+
#[async_std::test]
338+
async fn returns_lost_on_finalized_and_environment_error() {
339+
assert_eq!(
340+
watch_transaction_status::<_, TestChain, _>(
341+
TestEnvironment(Err(Error::UninitializedBridgePallet)),
342+
Default::default(),
343+
futures::stream::iter([TransactionStatus::Finalized(Default::default())])
344+
)
345+
.now_or_never(),
346+
Some(InvalidationStatus::Lost),
274347
);
275348
}
276349

@@ -343,16 +416,21 @@ mod tests {
343416
#[async_std::test]
344417
async fn lost_on_subscription_error() {
345418
assert_eq!(
346-
watch_transaction_status::<TestChain, _>(Default::default(), futures::stream::iter([]))
347-
.now_or_never(),
419+
watch_transaction_status::<_, TestChain, _>(
420+
TestEnvironment(Ok(HeaderId(0, Default::default()))),
421+
Default::default(),
422+
futures::stream::iter([])
423+
)
424+
.now_or_never(),
348425
Some(InvalidationStatus::Lost),
349426
);
350427
}
351428

352429
#[async_std::test]
353430
async fn lost_on_timeout_when_waiting_for_invalidation_status() {
354431
let (_sender, receiver) = futures::channel::mpsc::channel(1);
355-
let tx_tracker = TransactionTracker::<TestChain>::new(
432+
let tx_tracker = TransactionTracker::<TestChain, TestEnvironment>::new(
433+
TestEnvironment(Ok(HeaderId(0, Default::default()))),
356434
Duration::from_secs(0),
357435
Default::default(),
358436
Subscription(async_std::sync::Mutex::new(receiver)),

relays/finality/src/finality_loop.rs

+49-9
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,55 @@ pub(crate) async fn run_until_connection_lost<P: FinalitySyncPipeline>(
290290
// wait till exit signal, or new source block
291291
select! {
292292
transaction_status = last_transaction_tracker => {
293-
if transaction_status == TrackedTransactionStatus::Lost {
294-
log::error!(
295-
target: "bridge",
296-
"Finality synchronization from {} to {} has stalled. Going to restart",
297-
P::SOURCE_NAME,
298-
P::TARGET_NAME,
299-
);
300-
301-
return Err(FailedClient::Both);
293+
match transaction_status {
294+
TrackedTransactionStatus::Finalized(_) => {
295+
// transaction has been finalized, but it may have been finalized in the "failed" state. So
296+
// let's check if the block number has been actually updated. If it is not, then we are stalled.
297+
//
298+
// please also note that we're restarting the loop if we have failed to read required data
299+
// from the target client - that's the best we can do here to avoid actual stall.
300+
target_client
301+
.best_finalized_source_block_id()
302+
.await
303+
.map_err(|e| format!("failed to read best block from target node: {:?}", e))
304+
.and_then(|best_id_at_target| {
305+
let last_submitted_header_number = last_submitted_header_number
306+
.expect("always Some when last_transaction_tracker is set;\
307+
last_transaction_tracker is set;\
308+
qed");
309+
if last_submitted_header_number > best_id_at_target.0 {
310+
Err(format!(
311+
"best block at target after tx is {:?} and we've submitted {:?}",
312+
best_id_at_target,
313+
last_submitted_header_number,
314+
))
315+
} else {
316+
Ok(())
317+
}
318+
})
319+
.map_err(|e| {
320+
log::error!(
321+
target: "bridge",
322+
"Failed Finality synchronization from {} to {} has stalled. Transaction failed: {}. \
323+
Going to restart",
324+
P::SOURCE_NAME,
325+
P::TARGET_NAME,
326+
e,
327+
);
328+
329+
FailedClient::Both
330+
})?;
331+
},
332+
TrackedTransactionStatus::Lost => {
333+
log::error!(
334+
target: "bridge",
335+
"Finality synchronization from {} to {} has stalled. Going to restart",
336+
P::SOURCE_NAME,
337+
P::TARGET_NAME,
338+
);
339+
340+
return Err(FailedClient::Both);
341+
},
302342
}
303343
},
304344
_ = async_std::task::sleep(next_tick).fuse() => {},

relays/finality/src/finality_loop_tests.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,19 @@ type TestNumber = u64;
4848
type TestHash = u64;
4949

5050
#[derive(Clone, Debug)]
51-
struct TestTransactionTracker(TrackedTransactionStatus);
51+
struct TestTransactionTracker(TrackedTransactionStatus<HeaderId<TestHash, TestNumber>>);
5252

5353
impl Default for TestTransactionTracker {
5454
fn default() -> TestTransactionTracker {
55-
TestTransactionTracker(TrackedTransactionStatus::Finalized)
55+
TestTransactionTracker(TrackedTransactionStatus::Finalized(Default::default()))
5656
}
5757
}
5858

5959
#[async_trait]
6060
impl TransactionTracker for TestTransactionTracker {
61-
async fn wait(self) -> TrackedTransactionStatus {
61+
type HeaderId = HeaderId<TestHash, TestNumber>;
62+
63+
async fn wait(self) -> TrackedTransactionStatus<HeaderId<TestHash, TestNumber>> {
6264
self.0
6365
}
6466
}
@@ -224,7 +226,9 @@ fn prepare_test_clients(
224226

225227
target_best_block_id: HeaderId(5, 5),
226228
target_headers: vec![],
227-
target_transaction_tracker: TestTransactionTracker(TrackedTransactionStatus::Finalized),
229+
target_transaction_tracker: TestTransactionTracker(TrackedTransactionStatus::Finalized(
230+
Default::default(),
231+
)),
228232
}));
229233
(
230234
TestSourceClient {
@@ -581,3 +585,13 @@ fn stalls_when_transaction_tracker_returns_error() {
581585

582586
assert_eq!(result, Err(FailedClient::Both));
583587
}
588+
589+
#[test]
590+
fn stalls_when_transaction_tracker_returns_finalized_but_transaction_fails() {
591+
let (_, result) = run_sync_loop(|data| {
592+
data.target_best_block_id = HeaderId(5, 5);
593+
data.target_best_block_id.0 == 16
594+
});
595+
596+
assert_eq!(result, Err(FailedClient::Both));
597+
}

relays/lib-substrate-relay/src/finality/target.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ where
8989
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TransactionSignScheme> as Pair>::Public>,
9090
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
9191
{
92-
type TransactionTracker = TransactionTracker<P::TargetChain>;
92+
type TransactionTracker = TransactionTracker<P::TargetChain, Client<P::TargetChain>>;
9393

9494
async fn best_finalized_source_block_id(&self) -> Result<HeaderIdOf<P::SourceChain>, Error> {
9595
// we can't continue to relay finality if target node is out of sync, because

relays/lib-substrate-relay/src/messages_source.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ where
144144
From<<AccountKeyPairOf<P::SourceTransactionSignScheme> as Pair>::Public>,
145145
P::SourceTransactionSignScheme: TransactionSignScheme<Chain = P::SourceChain>,
146146
{
147-
type TransactionTracker = TransactionTracker<P::SourceChain>;
147+
type TransactionTracker = TransactionTracker<P::SourceChain, Client<P::SourceChain>>;
148148

149149
async fn state(&self) -> Result<SourceClientState<MessageLaneAdapter<P>>, SubstrateError> {
150150
// we can't continue to deliver confirmations if source node is out of sync, because

relays/lib-substrate-relay/src/messages_target.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ where
145145
P::TargetTransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
146146
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
147147
{
148-
type TransactionTracker = TransactionTracker<P::TargetChain>;
148+
type TransactionTracker = TransactionTracker<P::TargetChain, Client<P::TargetChain>>;
149149

150150
async fn state(&self) -> Result<TargetClientState<MessageLaneAdapter<P>>, SubstrateError> {
151151
// we can't continue to deliver confirmations if source node is out of sync, because

relays/lib-substrate-relay/src/parachains/target.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ where
8686
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
8787
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TransactionSignScheme> as Pair>::Public>,
8888
{
89-
type TransactionTracker = TransactionTracker<P::TargetChain>;
89+
type TransactionTracker = TransactionTracker<P::TargetChain, Client<P::TargetChain>>;
9090

9191
async fn best_block(&self) -> Result<HeaderIdOf<P::TargetChain>, Self::Error> {
9292
let best_header = self.client.best_header().await?;

0 commit comments

Comments
 (0)