From 59000c67dbbe1c25d6044fd07f0f7b76d1165cc7 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 14 Mar 2023 17:11:03 +0300 Subject: [PATCH] Fix invalid batch transaction (#1957) * fix invalid batch transaction * RaceState is now trait * clippy --- bridges/relays/lib-substrate-relay/src/lib.rs | 28 ++- .../lib-substrate-relay/src/messages_lane.rs | 13 +- .../relays/messages/src/message_lane_loop.rs | 13 +- .../messages/src/message_race_delivery.rs | 66 ++++--- .../relays/messages/src/message_race_loop.rs | 176 +++++++++++++----- .../messages/src/message_race_strategy.rs | 103 +++++----- 6 files changed, 275 insertions(+), 124 deletions(-) diff --git a/bridges/relays/lib-substrate-relay/src/lib.rs b/bridges/relays/lib-substrate-relay/src/lib.rs index 28c538b309a..37a4d602e59 100644 --- a/bridges/relays/lib-substrate-relay/src/lib.rs +++ b/bridges/relays/lib-substrate-relay/src/lib.rs @@ -91,18 +91,21 @@ impl TaggedAccount { } /// Batch call builder. -pub trait BatchCallBuilder: Send { +pub trait BatchCallBuilder: Clone + Send { /// Create batch call from given calls vector. fn build_batch_call(&self, _calls: Vec) -> Call; } /// Batch call builder constructor. -pub trait BatchCallBuilderConstructor { +pub trait BatchCallBuilderConstructor: Clone { + /// Call builder, used by this constructor. + type CallBuilder: BatchCallBuilder; /// Create a new instance of a batch call builder. - fn new_builder() -> Option>>; + fn new_builder() -> Option; } /// Batch call builder based on `pallet-utility`. +#[derive(Clone)] pub struct UtilityPalletBatchCallBuilder(PhantomData); impl BatchCallBuilder for UtilityPalletBatchCallBuilder @@ -118,14 +121,25 @@ impl BatchCallBuilderConstructor for UtilityPalletBatchCallBu where C: ChainWithUtilityPallet, { - fn new_builder() -> Option>> { - Some(Box::new(Self(Default::default()))) + type CallBuilder = Self; + + fn new_builder() -> Option { + Some(Self(Default::default())) } } -/// A `BatchCallBuilderConstructor` that always returns `None`. +// A `BatchCallBuilderConstructor` that always returns `None`. impl BatchCallBuilderConstructor for () { - fn new_builder() -> Option>> { + type CallBuilder = (); + fn new_builder() -> Option { None } } + +// Dummy `BatchCallBuilder` implementation that must never be used outside +// of the `impl BatchCallBuilderConstructor for ()` code. +impl BatchCallBuilder for () { + fn build_batch_call(&self, _calls: Vec) -> Call { + unreachable!("never called, because ()::new_builder() returns None; qed") + } +} diff --git a/bridges/relays/lib-substrate-relay/src/messages_lane.rs b/bridges/relays/lib-substrate-relay/src/messages_lane.rs index 0a7a3566d20..b86a2629b07 100644 --- a/bridges/relays/lib-substrate-relay/src/messages_lane.rs +++ b/bridges/relays/lib-substrate-relay/src/messages_lane.rs @@ -111,8 +111,9 @@ pub struct MessagesRelayParams { /// Batch transaction that brings headers + and messages delivery/receiving confirmations to the /// source node. +#[derive(Clone)] pub struct BatchProofTransaction>> { - builder: Box>>, + builder: B::CallBuilder, proved_header: HeaderIdOf, prove_calls: Vec>, @@ -120,6 +121,16 @@ pub struct BatchProofTransaction B>, } +impl>> std::fmt::Debug + for BatchProofTransaction +{ + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct("BatchProofTransaction") + .field("proved_header", &self.proved_header) + .finish() + } +} + impl>> BatchProofTransaction { diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs index 0533e51d5db..ba86f05ffd3 100644 --- a/bridges/relays/messages/src/message_lane_loop.rs +++ b/bridges/relays/messages/src/message_lane_loop.rs @@ -111,7 +111,7 @@ pub struct NoncesSubmitArtifacts { /// Batch transaction that already submit some headers and needs to be extended with /// messages/delivery proof before sending. -pub trait BatchTransaction: Send { +pub trait BatchTransaction: Debug + Send { /// Header that was required in the original call and which is bundled within this /// batch transaction. fn required_header_id(&self) -> HeaderId; @@ -121,7 +121,7 @@ pub trait BatchTransaction: Send { #[async_trait] pub trait SourceClient: RelayClient { /// Type of batch transaction that submits finality and message receiving proof. - type BatchTransaction: BatchTransaction>; + type BatchTransaction: BatchTransaction> + Clone; /// Transaction tracker to track submitted transactions. type TransactionTracker: TransactionTracker>; @@ -186,7 +186,7 @@ pub trait SourceClient: RelayClient { #[async_trait] pub trait TargetClient: RelayClient { /// Type of batch transaction that submits finality and messages proof. - type BatchTransaction: BatchTransaction>; + type BatchTransaction: BatchTransaction> + Clone; /// Transaction tracker to track submitted transactions. type TransactionTracker: TransactionTracker>; @@ -1212,6 +1212,9 @@ pub(crate) mod tests { original_data, Arc::new(|_| {}), Arc::new(move |data: &mut TestClientData| { + data.source_state.best_self = + HeaderId(data.source_state.best_self.0 + 1, data.source_state.best_self.1 + 1); + data.source_state.best_finalized_self = data.source_state.best_self; if let Some(target_to_source_header_required) = data.target_to_source_header_required.take() { @@ -1223,6 +1226,10 @@ pub(crate) mod tests { }), Arc::new(|_| {}), Arc::new(move |data: &mut TestClientData| { + data.target_state.best_self = + HeaderId(data.target_state.best_self.0 + 1, data.target_state.best_self.1 + 1); + data.target_state.best_finalized_self = data.target_state.best_self; + if let Some(source_to_target_header_required) = data.source_to_target_header_required.take() { diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs index b50e6c0841e..7a245858b32 100644 --- a/bridges/relays/messages/src/message_race_delivery.rs +++ b/bridges/relays/messages/src/message_race_delivery.rs @@ -322,13 +322,19 @@ where self.strategy.is_empty() } - fn required_source_header_at_target( + fn required_source_header_at_target, TargetHeaderIdOf

>>( &self, current_best: &SourceHeaderIdOf

, + race_state: RS, ) -> Option> { + // we have already submitted something - let's wait until it is mined + if race_state.nonces_submitted().is_some() { + return None + } + let has_nonces_to_deliver = !self.strategy.is_empty(); let header_required_for_messages_delivery = - self.strategy.required_source_header_at_target(current_best); + self.strategy.required_source_header_at_target(current_best, race_state); let header_required_for_reward_confirmations_delivery = self .latest_confirmed_nonces_at_source .back() @@ -381,10 +387,10 @@ where self.strategy.source_nonces_updated(at_block, nonces) } - fn best_target_nonces_updated( + fn best_target_nonces_updated, TargetHeaderIdOf

>>( &mut self, nonces: TargetClientNonces, - race_state: &mut RaceState, TargetHeaderIdOf

, P::MessagesProof>, + race_state: &mut RS, ) { // best target nonces must always be ge than finalized target nonces let latest_nonce = nonces.latest_nonce; @@ -396,13 +402,13 @@ where ) } - fn finalized_target_nonces_updated( + fn finalized_target_nonces_updated, TargetHeaderIdOf

>>( &mut self, nonces: TargetClientNonces, - race_state: &mut RaceState, TargetHeaderIdOf

, P::MessagesProof>, + race_state: &mut RS, ) { if let Some(ref best_finalized_source_header_id_at_best_target) = - race_state.best_finalized_source_header_id_at_best_target + race_state.best_finalized_source_header_id_at_best_target() { let oldest_header_number_to_keep = best_finalized_source_header_id_at_best_target.0; while self @@ -426,13 +432,13 @@ where ) } - async fn select_nonces_to_deliver( + async fn select_nonces_to_deliver, TargetHeaderIdOf

>>( &self, - race_state: RaceState, TargetHeaderIdOf

, P::MessagesProof>, + race_state: RS, ) -> Option<(RangeInclusive, Self::ProofParameters)> { let best_target_nonce = self.strategy.best_at_target()?; let best_finalized_source_header_id_at_best_target = - race_state.best_finalized_source_header_id_at_best_target.clone()?; + race_state.best_finalized_source_header_id_at_best_target()?; let latest_confirmed_nonce_at_source = self .latest_confirmed_nonces_at_source .iter() @@ -576,12 +582,16 @@ impl NoncesRange for MessageDetailsMap; + type TestRaceState = RaceStateImpl< + TestSourceHeaderId, + TestTargetHeaderId, + TestMessagesProof, + TestMessagesBatchTransaction, + >; type TestStrategy = MessageDeliveryStrategy; @@ -617,12 +632,13 @@ mod tests { } fn prepare_strategy() -> (TestRaceState, TestStrategy) { - let mut race_state = RaceState { + let mut race_state = RaceStateImpl { best_finalized_source_header_id_at_source: Some(header_id(1)), best_finalized_source_header_id_at_best_target: Some(header_id(1)), best_target_header_id: Some(header_id(1)), best_finalized_target_header_id: Some(header_id(1)), nonces_to_submit: None, + nonces_to_submit_batch: None, nonces_submitted: None, }; @@ -964,14 +980,17 @@ mod tests { ); // nothing needs to be delivered now and we don't need any new headers assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); - assert_eq!(strategy.required_source_header_at_target(&header_id(1)), None); + assert_eq!(strategy.required_source_header_at_target(&header_id(1), state.clone()), None); // now let's generate two more nonces [24; 25] at the soruce; strategy.source_nonces_updated(header_id(2), source_nonces(24..=25, 19, 0)); // // - so now we'll need to relay source block#2 to be able to accept messages [24; 25]. assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); - assert_eq!(strategy.required_source_header_at_target(&header_id(1)), Some(header_id(2))); + assert_eq!( + strategy.required_source_header_at_target(&header_id(1), state.clone()), + Some(header_id(2)) + ); // let's relay source block#2 state.best_finalized_source_header_id_at_source = Some(header_id(2)); @@ -982,7 +1001,7 @@ mod tests { // and ask strategy again => still nothing to deliver, because parallel confirmations // race need to be pushed further assert_eq!(strategy.select_nonces_to_deliver(state.clone()).await, None); - assert_eq!(strategy.required_source_header_at_target(&header_id(2)), None); + assert_eq!(strategy.required_source_header_at_target(&header_id(2), state.clone()), None); // let's confirm messages [20; 23] strategy.source_nonces_updated(header_id(2), source_nonces(24..=25, 23, 0)); @@ -990,10 +1009,10 @@ mod tests { // and ask strategy again => now we have everything required to deliver remaining // [24; 25] nonces and proof of [20; 23] confirmation assert_eq!( - strategy.select_nonces_to_deliver(state).await, + strategy.select_nonces_to_deliver(state.clone()).await, Some(((24..=25), proof_parameters(true, 2))), ); - assert_eq!(strategy.required_source_header_at_target(&header_id(2)), None); + assert_eq!(strategy.required_source_header_at_target(&header_id(2), state), None); } #[async_std::test] @@ -1025,6 +1044,7 @@ mod tests { #[test] #[allow(clippy::reversed_empty_ranges)] fn no_source_headers_required_at_target_if_lanes_are_empty() { + let (state, _) = prepare_strategy(); let mut strategy = TestStrategy { max_unrewarded_relayer_entries_at_target: 4, max_unconfirmed_nonces_at_target: 4, @@ -1053,7 +1073,7 @@ mod tests { strategy.latest_confirmed_nonces_at_source, VecDeque::from([(source_header_id, 0)]) ); - assert_eq!(strategy.required_source_header_at_target(&source_header_id), None); + assert_eq!(strategy.required_source_header_at_target(&source_header_id, state), None); } #[async_std::test] diff --git a/bridges/relays/messages/src/message_race_loop.rs b/bridges/relays/messages/src/message_race_loop.rs index 50f71ea050b..7e3f84dd5d1 100644 --- a/bridges/relays/messages/src/message_race_loop.rs +++ b/bridges/relays/messages/src/message_race_loop.rs @@ -25,7 +25,7 @@ use crate::message_lane_loop::{BatchTransaction, ClientState, NoncesSubmitArtifa use async_trait::async_trait; use bp_messages::MessageNonce; use futures::{ - future::FutureExt, + future::{FutureExt, TryFutureExt}, stream::{FusedStream, StreamExt}, }; use relay_utils::{ @@ -41,14 +41,14 @@ use std::{ /// One of races within lane. pub trait MessageRace { /// Header id of the race source. - type SourceHeaderId: Debug + Clone + PartialEq; + type SourceHeaderId: Debug + Clone + PartialEq + Send; /// Header id of the race source. - type TargetHeaderId: Debug + Clone + PartialEq; + type TargetHeaderId: Debug + Clone + PartialEq + Send; /// Message nonce used in the race. type MessageNonce: Debug + Clone; /// Proof that is generated and delivered in this race. - type Proof: Debug + Clone; + type Proof: Debug + Clone + Send; /// Name of the race source. fn source_name() -> String; @@ -128,7 +128,7 @@ pub trait TargetClient { /// Type of the additional data from the target client, used by the race. type TargetNoncesData: std::fmt::Debug; /// Type of batch transaction that submits finality and proof to the target node. - type BatchTransaction: BatchTransaction; + type BatchTransaction: BatchTransaction + Clone; /// Transaction tracker to track submitted transactions. type TransactionTracker: TransactionTracker; @@ -175,9 +175,10 @@ pub trait RaceStrategy: Debug { /// Should return true if nothing has to be synced. fn is_empty(&self) -> bool; /// Return id of source header that is required to be on target to continue synchronization. - fn required_source_header_at_target( + fn required_source_header_at_target>( &self, current_best: &SourceHeaderId, + race_state: RS, ) -> Option; /// Return the best nonce at source node. /// @@ -196,29 +197,53 @@ pub trait RaceStrategy: Debug { nonces: SourceClientNonces, ); /// Called when best nonces are updated at target node of the race. - fn best_target_nonces_updated( + fn best_target_nonces_updated>( &mut self, nonces: TargetClientNonces, - race_state: &mut RaceState, + race_state: &mut RS, ); /// Called when finalized nonces are updated at target node of the race. - fn finalized_target_nonces_updated( + fn finalized_target_nonces_updated>( &mut self, nonces: TargetClientNonces, - race_state: &mut RaceState, + race_state: &mut RS, ); /// Should return `Some(nonces)` if we need to deliver proof of `nonces` (and associated /// data) from source to target node. /// Additionally, parameters required to generate proof are returned. - async fn select_nonces_to_deliver( + async fn select_nonces_to_deliver>( &self, - race_state: RaceState, + race_state: RS, ) -> Option<(RangeInclusive, Self::ProofParameters)>; } /// State of the race. +pub trait RaceState: Send { + /// Best finalized source header id at the source client. + fn best_finalized_source_header_id_at_source(&self) -> Option; + /// Best finalized source header id at the best block on the target + /// client (at the `best_finalized_source_header_id_at_best_target`). + fn best_finalized_source_header_id_at_best_target(&self) -> Option; + /// The best header id at the target client. + fn best_target_header_id(&self) -> Option; + /// Best finalized header id at the target client. + fn best_finalized_target_header_id(&self) -> Option; + + /// Returns `true` if we have selected nonces to submit to the target node. + fn nonces_to_submit(&self) -> Option>; + /// Reset our nonces selection. + fn reset_nonces_to_submit(&mut self); + + /// Returns `true` if we have submitted some nonces to the target node and are + /// waiting for them to appear there. + fn nonces_submitted(&self) -> Option>; + /// Reset our nonces submission. + fn reset_nonces_submitted(&mut self); +} + +/// State of the race and prepared batch transaction (if available). #[derive(Debug, Clone)] -pub struct RaceState { +pub(crate) struct RaceStateImpl { /// Best finalized source header id at the source client. pub best_finalized_source_header_id_at_source: Option, /// Best finalized source header id at the best block on the target @@ -230,13 +255,67 @@ pub struct RaceState { pub best_finalized_target_header_id: Option, /// Range of nonces that we have selected to submit. pub nonces_to_submit: Option<(SourceHeaderId, RangeInclusive, Proof)>, + /// Batch transaction ready to include and deliver selected `nonces_to_submit` from the + /// `state`. + pub nonces_to_submit_batch: Option, /// Range of nonces that is currently submitted. pub nonces_submitted: Option>, } -impl RaceState { - /// Reset `nonces_submitted` to `None`. - fn reset_submitted(&mut self) { +impl Default + for RaceStateImpl +{ + fn default() -> Self { + RaceStateImpl { + best_finalized_source_header_id_at_source: None, + best_finalized_source_header_id_at_best_target: None, + best_target_header_id: None, + best_finalized_target_header_id: None, + nonces_to_submit: None, + nonces_to_submit_batch: None, + nonces_submitted: None, + } + } +} + +impl RaceState + for RaceStateImpl +where + SourceHeaderId: Clone + Send, + TargetHeaderId: Clone + Send, + Proof: Clone + Send, + BatchTx: Clone + Send, +{ + fn best_finalized_source_header_id_at_source(&self) -> Option { + self.best_finalized_source_header_id_at_source.clone() + } + + fn best_finalized_source_header_id_at_best_target(&self) -> Option { + self.best_finalized_source_header_id_at_best_target.clone() + } + + fn best_target_header_id(&self) -> Option { + self.best_target_header_id.clone() + } + + fn best_finalized_target_header_id(&self) -> Option { + self.best_finalized_target_header_id.clone() + } + + fn nonces_to_submit(&self) -> Option> { + self.nonces_to_submit.as_ref().map(|(_, nonces, _)| nonces.clone()) + } + + fn reset_nonces_to_submit(&mut self) { + self.nonces_to_submit = None; + self.nonces_to_submit_batch = None; + } + + fn nonces_submitted(&self) -> Option> { + self.nonces_submitted.clone() + } + + fn reset_nonces_submitted(&mut self) { self.nonces_submitted = None; } } @@ -257,7 +336,7 @@ pub async fn run, TC: TargetClient

>( >, ) -> Result<(), FailedClient> { let mut progress_context = Instant::now(); - let mut race_state = RaceState::default(); + let mut race_state = RaceStateImpl::default(); let mut source_retry_backoff = retry_backoff(); let mut source_client_is_online = true; @@ -302,7 +381,8 @@ pub async fn run, TC: TargetClient

>( != Some(&source_state.best_finalized_self); if is_source_state_updated { source_nonces_required = true; - race_state.best_finalized_source_header_id_at_source = Some(source_state.best_finalized_self); + race_state.best_finalized_source_header_id_at_source + = Some(source_state.best_finalized_self); } } }, @@ -353,7 +433,7 @@ pub async fn run, TC: TargetClient

>( source_required_header = race_state .best_finalized_source_header_id_at_best_target .as_ref() - .and_then(|best| strategy.required_source_header_at_target(best)); + .and_then(|best| strategy.required_source_header_at_target(best, race_state.clone())); }, nonces = target_best_nonces => { target_best_nonces_required = false; @@ -408,10 +488,13 @@ pub async fn run, TC: TargetClient

>( |maybe_batch_transaction: Option| { log::debug!( target: "bridge", - "Target {} client has been asked for more {} headers. Batch tx: {:?}", + "Target {} client has been asked for more {} headers. Batch tx: {}", P::target_name(), P::source_name(), - maybe_batch_transaction.is_some(), + maybe_batch_transaction + .as_ref() + .map(|bt| format!("yes ({:?})", bt.required_header_id())) + .unwrap_or_else(|| "no".into()), ); target_batch_transaction = maybe_batch_transaction; @@ -425,7 +508,7 @@ pub async fn run, TC: TargetClient

>( source_client_is_online = process_future_result( proof, &mut source_retry_backoff, - |(at_block, nonces_range, proof)| { + |(at_block, nonces_range, proof, batch_transaction)| { log::debug!( target: "bridge", "Received proof for nonces in range {:?} from {}", @@ -434,6 +517,7 @@ pub async fn run, TC: TargetClient

>( ); race_state.nonces_to_submit = Some((at_block, nonces_range, proof)); + race_state.nonces_to_submit_batch = batch_transaction; }, &mut source_go_offline_future, async_std::task::sleep, @@ -452,8 +536,7 @@ pub async fn run, TC: TargetClient

>( P::target_name(), ); - target_batch_transaction = None; - race_state.nonces_to_submit = None; + race_state.reset_nonces_to_submit(); race_state.nonces_submitted = Some(artifacts.nonces); target_tx_tracker.set(artifacts.tx_tracker.wait().fuse()); }, @@ -490,7 +573,7 @@ pub async fn run, TC: TargetClient

>( e, ); - race_state.reset_submitted(); + race_state.reset_nonces_submitted(); }); }, (TrackedTransactionStatus::Lost, _) => { @@ -503,7 +586,7 @@ pub async fn run, TC: TargetClient

>( strategy, ); - race_state.reset_submitted(); + race_state.reset_nonces_submitted(); }, _ => (), } @@ -524,6 +607,12 @@ pub async fn run, TC: TargetClient

>( source_client_is_online = false; // if we've started to submit batch transaction, let's prioritize it + // + // we're using `take` here, because we don't need batch transaction (i.e. some + // underlying finality proof) anymore for our future calls - we were unable to + // use it for our current state, so why would we need to keep an obsolete proof + // for the future? + let target_batch_transaction = target_batch_transaction.take(); let expected_race_state = if let Some(ref target_batch_transaction) = target_batch_transaction { // when selecting nonces for the batch transaction, we assume that the required @@ -551,7 +640,12 @@ pub async fn run, TC: TargetClient

>( ); source_generate_proof.set( - race_source.generate_proof(at_block, nonces_range, proof_parameters).fuse(), + race_source + .generate_proof(at_block, nonces_range, proof_parameters) + .and_then(|(at_source_block, nonces, proof)| async { + Ok((at_source_block, nonces, proof, target_batch_transaction)) + }) + .fuse(), ); } else if source_nonces_required && best_at_source.is_some() { log::debug!(target: "bridge", "Asking {} about message nonces", P::source_name()); @@ -582,7 +676,7 @@ pub async fn run, TC: TargetClient

>( "Going to submit proof of messages in range {:?} to {} node{}", nonces_range, P::target_name(), - target_batch_transaction.as_ref().map(|tx| format!( + race_state.nonces_to_submit_batch.as_ref().map(|tx| format!( ". This transaction is batched with sending the proof for header {:?}.", tx.required_header_id()) ).unwrap_or_default(), @@ -591,7 +685,7 @@ pub async fn run, TC: TargetClient

>( target_submit_proof.set( race_target .submit_proof( - target_batch_transaction.take(), + race_state.nonces_to_submit_batch.clone(), at_block.clone(), nonces_range.clone(), proof.clone(), @@ -628,21 +722,6 @@ pub async fn run, TC: TargetClient

>( } } -impl Default - for RaceState -{ - fn default() -> Self { - RaceState { - best_finalized_source_header_id_at_source: None, - best_finalized_source_header_id_at_best_target: None, - best_target_header_id: None, - best_finalized_target_header_id: None, - nonces_to_submit: None, - nonces_submitted: None, - } - } -} - /// Print race progress. fn print_race_progress(prev_time: Instant, strategy: &S) -> Instant where @@ -670,7 +749,7 @@ where } async fn select_nonces_to_deliver( - race_state: RaceState, + race_state: impl RaceState, strategy: &Strategy, ) -> Option<(SourceHeaderId, RangeInclusive, Strategy::ProofParameters)> where @@ -678,7 +757,7 @@ where Strategy: RaceStrategy, { let best_finalized_source_header_id_at_best_target = - race_state.best_finalized_source_header_id_at_best_target.clone()?; + race_state.best_finalized_source_header_id_at_best_target()?; strategy .select_nonces_to_deliver(race_state) .await @@ -701,7 +780,7 @@ mod tests { // target node only knows about source' BEST_AT_TARGET block // source node has BEST_AT_SOURCE > BEST_AT_TARGET block - let mut race_state = RaceState::<_, _, ()> { + let mut race_state = RaceStateImpl::<_, _, (), ()> { best_finalized_source_header_id_at_source: Some(HeaderId( BEST_AT_SOURCE, BEST_AT_SOURCE, @@ -713,11 +792,12 @@ mod tests { best_target_header_id: Some(HeaderId(0, 0)), best_finalized_target_header_id: Some(HeaderId(0, 0)), nonces_to_submit: None, + nonces_to_submit_batch: None, nonces_submitted: None, }; // we have some nonces to deliver and they're generated at GENERATED_AT < BEST_AT_SOURCE - let mut strategy = BasicStrategy::new(); + let mut strategy = BasicStrategy::<_, _, _, _, _, ()>::new(); strategy.source_nonces_updated( HeaderId(GENERATED_AT, GENERATED_AT), SourceClientNonces { new_nonces: 0..=10, confirmed_nonce: None }, diff --git a/bridges/relays/messages/src/message_race_strategy.rs b/bridges/relays/messages/src/message_race_strategy.rs index 479ffe51329..e6016448c95 100644 --- a/bridges/relays/messages/src/message_race_strategy.rs +++ b/bridges/relays/messages/src/message_race_strategy.rs @@ -106,24 +106,25 @@ impl< /// at source blocks that are known to be finalized at the target node. /// /// Returns `None` if no entries may be delivered. - pub fn available_source_queue_indices( - &self, - race_state: RaceState< + pub fn available_source_queue_indices< + RS: RaceState< HeaderId, HeaderId, - Proof, >, + >( + &self, + race_state: RS, ) -> Option> { // if we do not know best nonce at target node, we can't select anything let best_target_nonce = self.best_target_nonce?; // if we have already selected nonces that we want to submit, do nothing - if race_state.nonces_to_submit.is_some() { + if race_state.nonces_to_submit().is_some() { return None } // if we already submitted some nonces, do nothing - if race_state.nonces_submitted.is_some() { + if race_state.nonces_submitted().is_some() { return None } @@ -143,7 +144,7 @@ impl< // // => let's first select range of entries inside deque that are already finalized at // the target client and pass this range to the selector - let best_header_at_target = race_state.best_finalized_source_header_id_at_best_target?; + let best_header_at_target = race_state.best_finalized_source_header_id_at_best_target()?; let end_index = self .source_queue .iter() @@ -204,9 +205,15 @@ impl< self.source_queue.is_empty() } - fn required_source_header_at_target( + fn required_source_header_at_target< + RS: RaceState< + HeaderId, + HeaderId, + >, + >( &self, current_best: &HeaderId, + _race_state: RS, ) -> Option> { self.source_queue .back() @@ -247,46 +254,46 @@ impl< ) } - fn best_target_nonces_updated( - &mut self, - nonces: TargetClientNonces<()>, - race_state: &mut RaceState< + fn best_target_nonces_updated< + RS: RaceState< HeaderId, HeaderId, - Proof, >, + >( + &mut self, + nonces: TargetClientNonces<()>, + race_state: &mut RS, ) { let nonce = nonces.latest_nonce; let need_to_select_new_nonces = race_state - .nonces_to_submit - .as_ref() - .map(|(_, nonces, _)| *nonces.end() <= nonce) + .nonces_to_submit() + .map(|nonces| *nonces.end() <= nonce) .unwrap_or(false); if need_to_select_new_nonces { - race_state.nonces_to_submit = None; + race_state.reset_nonces_to_submit(); } let need_new_nonces_to_submit = race_state - .nonces_submitted - .as_ref() + .nonces_submitted() .map(|nonces| *nonces.end() <= nonce) .unwrap_or(false); if need_new_nonces_to_submit { - race_state.nonces_submitted = None; + race_state.reset_nonces_submitted(); } self.best_target_nonce = Some(nonce); } - fn finalized_target_nonces_updated( - &mut self, - nonces: TargetClientNonces<()>, - _race_state: &mut RaceState< + fn finalized_target_nonces_updated< + RS: RaceState< HeaderId, HeaderId, - Proof, >, + >( + &mut self, + nonces: TargetClientNonces<()>, + _race_state: &mut RS, ) { self.remove_le_nonces_from_source_queue(nonces.latest_nonce); self.best_target_nonce = Some(std::cmp::max( @@ -295,13 +302,14 @@ impl< )); } - async fn select_nonces_to_deliver( - &self, - race_state: RaceState< + async fn select_nonces_to_deliver< + RS: RaceState< HeaderId, HeaderId, - Proof, >, + >( + &self, + race_state: RS, ) -> Option<(RangeInclusive, Self::ProofParameters)> { let available_indices = self.available_source_queue_indices(race_state)?; let range_begin = std::cmp::max( @@ -317,15 +325,23 @@ impl< mod tests { use super::*; use crate::{ - message_lane::MessageLane, + message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, message_lane_loop::tests::{ header_id, TestMessageLane, TestMessagesProof, TestSourceHeaderHash, TestSourceHeaderNumber, }, + message_race_loop::RaceStateImpl, }; type SourceNoncesRange = RangeInclusive; + type TestRaceStateImpl = RaceStateImpl< + SourceHeaderIdOf, + TargetHeaderIdOf, + TestMessagesProof, + (), + >; + type BasicStrategy

= super::BasicStrategy<

::SourceHeaderNumber,

::SourceHeaderHash, @@ -357,7 +373,7 @@ mod tests { assert_eq!(strategy.best_at_source(), None); strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); assert_eq!(strategy.best_at_source(), None); - strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default()); + strategy.best_target_nonces_updated(target_nonces(10), &mut TestRaceStateImpl::default()); assert_eq!(strategy.source_queue, vec![(header_id(1), 1..=5)]); assert_eq!(strategy.best_at_source(), Some(10)); } @@ -365,7 +381,7 @@ mod tests { #[test] fn source_nonce_is_never_lower_than_known_target_nonce() { let mut strategy = BasicStrategy::::new(); - strategy.best_target_nonces_updated(target_nonces(10), &mut Default::default()); + strategy.best_target_nonces_updated(target_nonces(10), &mut TestRaceStateImpl::default()); strategy.source_nonces_updated(header_id(1), source_nonces(1..=5)); assert_eq!(strategy.source_queue, vec![]); } @@ -386,15 +402,17 @@ mod tests { strategy.source_nonces_updated(header_id(2), source_nonces(6..=10)); strategy.source_nonces_updated(header_id(3), source_nonces(11..=15)); strategy.source_nonces_updated(header_id(4), source_nonces(16..=20)); - strategy.finalized_target_nonces_updated(target_nonces(15), &mut Default::default()); + strategy + .finalized_target_nonces_updated(target_nonces(15), &mut TestRaceStateImpl::default()); assert_eq!(strategy.source_queue, vec![(header_id(4), 16..=20)]); - strategy.finalized_target_nonces_updated(target_nonces(17), &mut Default::default()); + strategy + .finalized_target_nonces_updated(target_nonces(17), &mut TestRaceStateImpl::default()); assert_eq!(strategy.source_queue, vec![(header_id(4), 18..=20)]); } #[test] fn selected_nonces_are_dropped_on_target_nonce_update() { - let mut state = RaceState::default(); + let mut state = TestRaceStateImpl::default(); let mut strategy = BasicStrategy::::new(); state.nonces_to_submit = Some((header_id(1), 5..=10, (5..=10, None))); strategy.best_target_nonces_updated(target_nonces(7), &mut state); @@ -405,7 +423,7 @@ mod tests { #[test] fn submitted_nonces_are_dropped_on_target_nonce_update() { - let mut state = RaceState::default(); + let mut state = TestRaceStateImpl::default(); let mut strategy = BasicStrategy::::new(); state.nonces_submitted = Some(5..=10); strategy.best_target_nonces_updated(target_nonces(7), &mut state); @@ -416,7 +434,7 @@ mod tests { #[async_std::test] async fn nothing_is_selected_if_something_is_already_selected() { - let mut state = RaceState::default(); + let mut state = TestRaceStateImpl::default(); let mut strategy = BasicStrategy::::new(); state.nonces_to_submit = Some((header_id(1), 1..=10, (1..=10, None))); strategy.best_target_nonces_updated(target_nonces(0), &mut state); @@ -426,7 +444,7 @@ mod tests { #[async_std::test] async fn nothing_is_selected_if_something_is_already_submitted() { - let mut state = RaceState::default(); + let mut state = TestRaceStateImpl::default(); let mut strategy = BasicStrategy::::new(); state.nonces_submitted = Some(1..=10); strategy.best_target_nonces_updated(target_nonces(0), &mut state); @@ -436,7 +454,7 @@ mod tests { #[async_std::test] async fn select_nonces_to_deliver_works() { - let mut state = RaceState::<_, _, TestMessagesProof>::default(); + let mut state = TestRaceStateImpl::default(); let mut strategy = BasicStrategy::::new(); strategy.best_target_nonces_updated(target_nonces(0), &mut state); strategy.source_nonces_updated(header_id(1), source_nonces(1..=1)); @@ -457,7 +475,7 @@ mod tests { #[test] fn available_source_queue_indices_works() { - let mut state = RaceState::<_, _, TestMessagesProof>::default(); + let mut state = TestRaceStateImpl::default(); let mut strategy = BasicStrategy::::new(); strategy.best_target_nonces_updated(target_nonces(0), &mut state); strategy.source_nonces_updated(header_id(1), source_nonces(1..=3)); @@ -482,7 +500,7 @@ mod tests { #[test] fn remove_le_nonces_from_source_queue_works() { - let mut state = RaceState::<_, _, TestMessagesProof>::default(); + let mut state = TestRaceStateImpl::default(); let mut strategy = BasicStrategy::::new(); strategy.best_target_nonces_updated(target_nonces(0), &mut state); strategy.source_nonces_updated(header_id(1), source_nonces(1..=3)); @@ -518,12 +536,13 @@ mod tests { let target_header_1 = header_id(1); // we start in perfec sync state - all headers are synced and finalized on both ends - let mut state = RaceState::<_, _, TestMessagesProof> { + let mut state = TestRaceStateImpl { best_finalized_source_header_id_at_source: Some(source_header_1), best_finalized_source_header_id_at_best_target: Some(source_header_1), best_target_header_id: Some(target_header_1), best_finalized_target_header_id: Some(target_header_1), nonces_to_submit: None, + nonces_to_submit_batch: None, nonces_submitted: None, };