mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 08:41:02 +00:00
Batch transactions in complex relays (#1669)
* batch transactions in message relay: API prototype * get rid of Box<dyn BatchTransaction> and actually submit it * test batch transactions * message_lane_loop_works_with_batch_transactions * removed logger * BatchConfirmationTransaction + BatchDeliveryTransaction * more prototyping * fmt * continue with batch calls * impl BatchCallBuilder for () * BatchDeliveryTransaction impl * BundledBatchCallBuilder * proper impl of BundledBatchCallBuilder + use it in RialtoParachain -> Millau * impl prove_header in OnDemandHeadersRelay * impl OnDemandParachainsRelay::prove_header (needs extensive tests) * added a couple of TODOs * return Result<Option<BatchTx>> when asking for more headers * prove headers when reauire_* is called && return proper headers from required_header_id * split parachains::prove_header and test select_headers_to_prove * more traces and leave TODOs * use finality stream in SubstrateFinalitySource::prove_block_finality * prove parachain head at block, selected by headers relay * const ANCIENT_BLOCK_THRESHOLD * TODO -> proof * clippy and spelling * BatchCallBuilder::build_batch_call() returns Result * read first proof from two streams * FailedToFindFinalityProof -> FinalityProofNotFound * changed select_headers_to_prove to version from PR review
This commit is contained in:
committed by
Bastian Köcher
parent
a732a04ed4
commit
be27bd5e97
@@ -109,9 +109,28 @@ pub struct NoncesSubmitArtifacts<T> {
|
||||
pub tx_tracker: T,
|
||||
}
|
||||
|
||||
/// Batch transaction that already submit some headers and needs to be extended with
|
||||
/// messages/delivery proof before sending.
|
||||
#[async_trait]
|
||||
pub trait BatchTransaction<HeaderId, Proof, TransactionTracker, Error>: Send {
|
||||
/// Header that was required in the original call and which is bundled within this
|
||||
/// batch transaction.
|
||||
fn required_header_id(&self) -> HeaderId;
|
||||
|
||||
/// Append proof and send transaction to the connected node.
|
||||
async fn append_proof_and_send(self, proof: Proof) -> Result<TransactionTracker, Error>;
|
||||
}
|
||||
|
||||
/// Source client trait.
|
||||
#[async_trait]
|
||||
pub trait SourceClient<P: MessageLane>: RelayClient {
|
||||
/// Type of batch transaction that submits finality and message receiving proof.
|
||||
type BatchTransaction: BatchTransaction<
|
||||
TargetHeaderIdOf<P>,
|
||||
P::MessagesReceivingProof,
|
||||
Self::TransactionTracker,
|
||||
Self::Error,
|
||||
>;
|
||||
/// Transaction tracker to track submitted transactions.
|
||||
type TransactionTracker: TransactionTracker<HeaderId = SourceHeaderIdOf<P>>;
|
||||
|
||||
@@ -156,12 +175,31 @@ pub trait SourceClient<P: MessageLane>: RelayClient {
|
||||
) -> Result<Self::TransactionTracker, Self::Error>;
|
||||
|
||||
/// We need given finalized target header on source to continue synchronization.
|
||||
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<P>);
|
||||
///
|
||||
/// We assume that the absence of header `id` has already been checked by caller.
|
||||
///
|
||||
/// The client may return `Some(_)`, which means that nothing has happened yet and
|
||||
/// the caller must generate and append message receiving proof to the batch transaction
|
||||
/// to actually send it (along with required header) to the node.
|
||||
///
|
||||
/// If function has returned `None`, it means that the caller now must wait for the
|
||||
/// appearance of the target header `id` at the source client.
|
||||
async fn require_target_header_on_source(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P>,
|
||||
) -> Result<Option<Self::BatchTransaction>, Self::Error>;
|
||||
}
|
||||
|
||||
/// Target client trait.
|
||||
#[async_trait]
|
||||
pub trait TargetClient<P: MessageLane>: RelayClient {
|
||||
/// Type of batch transaction that submits finality and messages proof.
|
||||
type BatchTransaction: BatchTransaction<
|
||||
SourceHeaderIdOf<P>,
|
||||
P::MessagesProof,
|
||||
Self::TransactionTracker,
|
||||
Self::Error,
|
||||
>;
|
||||
/// Transaction tracker to track submitted transactions.
|
||||
type TransactionTracker: TransactionTracker<HeaderId = TargetHeaderIdOf<P>>;
|
||||
|
||||
@@ -201,7 +239,17 @@ pub trait TargetClient<P: MessageLane>: RelayClient {
|
||||
) -> Result<NoncesSubmitArtifacts<Self::TransactionTracker>, Self::Error>;
|
||||
|
||||
/// We need given finalized source header on target to continue synchronization.
|
||||
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<P>);
|
||||
///
|
||||
/// The client may return `Some(_)`, which means that nothing has happened yet and
|
||||
/// the caller must generate and append messages proof to the batch transaction
|
||||
/// to actually send it (along with required header) to the node.
|
||||
///
|
||||
/// If function has returned `None`, it means that the caller now must wait for the
|
||||
/// appearance of the source header `id` at the target client.
|
||||
async fn require_source_header_on_target(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<P>,
|
||||
) -> Result<Option<Self::BatchTransaction>, Self::Error>;
|
||||
}
|
||||
|
||||
/// State of the client.
|
||||
@@ -483,6 +531,61 @@ pub(crate) mod tests {
|
||||
type TargetHeaderHash = TestTargetHeaderHash;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TestMessagesBatchTransaction {
|
||||
data: Arc<Mutex<TestClientData>>,
|
||||
required_header_id: TestSourceHeaderId,
|
||||
tx_tracker: TestTransactionTracker,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BatchTransaction<TestSourceHeaderId, TestMessagesProof, TestTransactionTracker, TestError>
|
||||
for TestMessagesBatchTransaction
|
||||
{
|
||||
fn required_header_id(&self) -> TestSourceHeaderId {
|
||||
self.required_header_id
|
||||
}
|
||||
|
||||
async fn append_proof_and_send(
|
||||
self,
|
||||
proof: TestMessagesProof,
|
||||
) -> Result<TestTransactionTracker, TestError> {
|
||||
let mut data = self.data.lock();
|
||||
data.receive_messages(proof);
|
||||
Ok(self.tx_tracker)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TestConfirmationBatchTransaction {
|
||||
data: Arc<Mutex<TestClientData>>,
|
||||
required_header_id: TestTargetHeaderId,
|
||||
tx_tracker: TestTransactionTracker,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl
|
||||
BatchTransaction<
|
||||
TestTargetHeaderId,
|
||||
TestMessagesReceivingProof,
|
||||
TestTransactionTracker,
|
||||
TestError,
|
||||
> for TestConfirmationBatchTransaction
|
||||
{
|
||||
fn required_header_id(&self) -> TestTargetHeaderId {
|
||||
self.required_header_id
|
||||
}
|
||||
|
||||
async fn append_proof_and_send(
|
||||
self,
|
||||
proof: TestMessagesReceivingProof,
|
||||
) -> Result<TestTransactionTracker, TestError> {
|
||||
let mut data = self.data.lock();
|
||||
data.receive_messages_delivery_proof(proof);
|
||||
Ok(self.tx_tracker)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TestTransactionTracker(TrackedTransactionStatus<TestTargetHeaderId>);
|
||||
|
||||
@@ -517,8 +620,10 @@ pub(crate) mod tests {
|
||||
target_latest_confirmed_received_nonce: MessageNonce,
|
||||
target_tracked_transaction_status: TrackedTransactionStatus<TestTargetHeaderId>,
|
||||
submitted_messages_proofs: Vec<TestMessagesProof>,
|
||||
target_to_source_batch_transaction: Option<TestConfirmationBatchTransaction>,
|
||||
target_to_source_header_required: Option<TestTargetHeaderId>,
|
||||
target_to_source_header_requirements: Vec<TestTargetHeaderId>,
|
||||
source_to_target_batch_transaction: Option<TestMessagesBatchTransaction>,
|
||||
source_to_target_header_required: Option<TestSourceHeaderId>,
|
||||
source_to_target_header_requirements: Vec<TestSourceHeaderId>,
|
||||
}
|
||||
@@ -546,14 +651,38 @@ pub(crate) mod tests {
|
||||
Default::default(),
|
||||
)),
|
||||
submitted_messages_proofs: Vec::new(),
|
||||
target_to_source_batch_transaction: None,
|
||||
target_to_source_header_required: None,
|
||||
target_to_source_header_requirements: Vec::new(),
|
||||
source_to_target_batch_transaction: None,
|
||||
source_to_target_header_required: None,
|
||||
source_to_target_header_requirements: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestClientData {
|
||||
fn receive_messages(&mut self, proof: TestMessagesProof) {
|
||||
self.target_state.best_self =
|
||||
HeaderId(self.target_state.best_self.0 + 1, self.target_state.best_self.1 + 1);
|
||||
self.target_state.best_finalized_self = self.target_state.best_self;
|
||||
self.target_latest_received_nonce = *proof.0.end();
|
||||
if let Some(target_latest_confirmed_received_nonce) = proof.1 {
|
||||
self.target_latest_confirmed_received_nonce =
|
||||
target_latest_confirmed_received_nonce;
|
||||
}
|
||||
self.submitted_messages_proofs.push(proof);
|
||||
}
|
||||
|
||||
fn receive_messages_delivery_proof(&mut self, proof: TestMessagesReceivingProof) {
|
||||
self.source_state.best_self =
|
||||
HeaderId(self.source_state.best_self.0 + 1, self.source_state.best_self.1 + 1);
|
||||
self.source_state.best_finalized_self = self.source_state.best_self;
|
||||
self.submitted_messages_receiving_proofs.push(proof);
|
||||
self.source_latest_confirmed_received_nonce = proof;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestSourceClient {
|
||||
data: Arc<Mutex<TestClientData>>,
|
||||
@@ -588,6 +717,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[async_trait]
|
||||
impl SourceClient<TestMessageLane> for TestSourceClient {
|
||||
type BatchTransaction = TestConfirmationBatchTransaction;
|
||||
type TransactionTracker = TestTransactionTracker;
|
||||
|
||||
async fn state(&self) -> Result<SourceClientState<TestMessageLane>, TestError> {
|
||||
@@ -675,21 +805,25 @@ pub(crate) mod tests {
|
||||
) -> Result<Self::TransactionTracker, TestError> {
|
||||
let mut data = self.data.lock();
|
||||
(self.tick)(&mut data);
|
||||
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;
|
||||
data.submitted_messages_receiving_proofs.push(proof);
|
||||
data.source_latest_confirmed_received_nonce = proof;
|
||||
data.receive_messages_delivery_proof(proof);
|
||||
(self.post_tick)(&mut data);
|
||||
Ok(TestTransactionTracker(data.source_tracked_transaction_status))
|
||||
}
|
||||
|
||||
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<TestMessageLane>) {
|
||||
async fn require_target_header_on_source(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<TestMessageLane>,
|
||||
) -> Result<Option<Self::BatchTransaction>, Self::Error> {
|
||||
let mut data = self.data.lock();
|
||||
data.target_to_source_header_required = Some(id);
|
||||
data.target_to_source_header_requirements.push(id);
|
||||
(self.tick)(&mut data);
|
||||
(self.post_tick)(&mut data);
|
||||
|
||||
Ok(data.target_to_source_batch_transaction.take().map(|mut tx| {
|
||||
tx.required_header_id = id;
|
||||
tx
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,6 +861,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[async_trait]
|
||||
impl TargetClient<TestMessageLane> for TestTargetClient {
|
||||
type BatchTransaction = TestMessagesBatchTransaction;
|
||||
type TransactionTracker = TestTransactionTracker;
|
||||
|
||||
async fn state(&self) -> Result<TargetClientState<TestMessageLane>, TestError> {
|
||||
@@ -798,15 +933,7 @@ pub(crate) mod tests {
|
||||
if data.is_target_fails {
|
||||
return Err(TestError)
|
||||
}
|
||||
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;
|
||||
data.target_latest_received_nonce = *proof.0.end();
|
||||
if let Some(target_latest_confirmed_received_nonce) = proof.1 {
|
||||
data.target_latest_confirmed_received_nonce =
|
||||
target_latest_confirmed_received_nonce;
|
||||
}
|
||||
data.submitted_messages_proofs.push(proof);
|
||||
data.receive_messages(proof);
|
||||
(self.post_tick)(&mut data);
|
||||
Ok(NoncesSubmitArtifacts {
|
||||
nonces,
|
||||
@@ -814,17 +941,25 @@ pub(crate) mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<TestMessageLane>) {
|
||||
async fn require_source_header_on_target(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<TestMessageLane>,
|
||||
) -> Result<Option<Self::BatchTransaction>, Self::Error> {
|
||||
let mut data = self.data.lock();
|
||||
data.source_to_target_header_required = Some(id);
|
||||
data.source_to_target_header_requirements.push(id);
|
||||
(self.tick)(&mut data);
|
||||
(self.post_tick)(&mut data);
|
||||
|
||||
Ok(data.source_to_target_batch_transaction.take().map(|mut tx| {
|
||||
tx.required_header_id = id;
|
||||
tx
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn run_loop_test(
|
||||
data: TestClientData,
|
||||
data: Arc<Mutex<TestClientData>>,
|
||||
source_tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
|
||||
source_post_tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
|
||||
target_tick: Arc<dyn Fn(&mut TestClientData) + Send + Sync>,
|
||||
@@ -832,8 +967,6 @@ pub(crate) mod tests {
|
||||
exit_signal: impl Future<Output = ()> + 'static + Send,
|
||||
) -> TestClientData {
|
||||
async_std::task::block_on(async {
|
||||
let data = Arc::new(Mutex::new(data));
|
||||
|
||||
let source_client = TestSourceClient {
|
||||
data: data.clone(),
|
||||
tick: source_tick,
|
||||
@@ -876,7 +1009,7 @@ pub(crate) mod tests {
|
||||
// able to deliver messages.
|
||||
let (exit_sender, exit_receiver) = unbounded();
|
||||
let result = run_loop_test(
|
||||
TestClientData {
|
||||
Arc::new(Mutex::new(TestClientData {
|
||||
is_source_fails: true,
|
||||
source_state: ClientState {
|
||||
best_self: HeaderId(0, 0),
|
||||
@@ -893,7 +1026,7 @@ pub(crate) mod tests {
|
||||
},
|
||||
target_latest_received_nonce: 0,
|
||||
..Default::default()
|
||||
},
|
||||
})),
|
||||
Arc::new(|data: &mut TestClientData| {
|
||||
if data.is_source_reconnected {
|
||||
data.is_source_fails = false;
|
||||
@@ -929,7 +1062,7 @@ pub(crate) mod tests {
|
||||
let (source_exit_sender, exit_receiver) = unbounded();
|
||||
let target_exit_sender = source_exit_sender.clone();
|
||||
let result = run_loop_test(
|
||||
TestClientData {
|
||||
Arc::new(Mutex::new(TestClientData {
|
||||
source_state: ClientState {
|
||||
best_self: HeaderId(0, 0),
|
||||
best_finalized_self: HeaderId(0, 0),
|
||||
@@ -947,7 +1080,7 @@ pub(crate) mod tests {
|
||||
target_latest_received_nonce: 0,
|
||||
target_tracked_transaction_status: TrackedTransactionStatus::Lost,
|
||||
..Default::default()
|
||||
},
|
||||
})),
|
||||
Arc::new(move |data: &mut TestClientData| {
|
||||
if data.is_source_reconnected {
|
||||
data.source_tracked_transaction_status =
|
||||
@@ -980,7 +1113,7 @@ pub(crate) mod tests {
|
||||
// their corresponding nonce won't be udpated => reconnect will happen
|
||||
let (exit_sender, exit_receiver) = unbounded();
|
||||
let result = run_loop_test(
|
||||
TestClientData {
|
||||
Arc::new(Mutex::new(TestClientData {
|
||||
source_state: ClientState {
|
||||
best_self: HeaderId(0, 0),
|
||||
best_finalized_self: HeaderId(0, 0),
|
||||
@@ -996,7 +1129,7 @@ pub(crate) mod tests {
|
||||
},
|
||||
target_latest_received_nonce: 0,
|
||||
..Default::default()
|
||||
},
|
||||
})),
|
||||
Arc::new(move |data: &mut TestClientData| {
|
||||
// blocks are produced on every tick
|
||||
data.source_state.best_self =
|
||||
@@ -1054,7 +1187,7 @@ pub(crate) mod tests {
|
||||
fn message_lane_loop_works() {
|
||||
let (exit_sender, exit_receiver) = unbounded();
|
||||
let result = run_loop_test(
|
||||
TestClientData {
|
||||
Arc::new(Mutex::new(TestClientData {
|
||||
source_state: ClientState {
|
||||
best_self: HeaderId(10, 10),
|
||||
best_finalized_self: HeaderId(10, 10),
|
||||
@@ -1070,7 +1203,7 @@ pub(crate) mod tests {
|
||||
},
|
||||
target_latest_received_nonce: 0,
|
||||
..Default::default()
|
||||
},
|
||||
})),
|
||||
Arc::new(|data: &mut TestClientData| {
|
||||
// blocks are produced on every tick
|
||||
data.source_state.best_self =
|
||||
@@ -1133,4 +1266,74 @@ pub(crate) mod tests {
|
||||
assert!(!result.target_to_source_header_requirements.is_empty());
|
||||
assert!(!result.source_to_target_header_requirements.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_lane_loop_works_with_batch_transactions() {
|
||||
let (exit_sender, exit_receiver) = unbounded();
|
||||
let original_data = Arc::new(Mutex::new(TestClientData {
|
||||
source_state: ClientState {
|
||||
best_self: HeaderId(10, 10),
|
||||
best_finalized_self: HeaderId(10, 10),
|
||||
best_finalized_peer_at_best_self: HeaderId(0, 0),
|
||||
actual_best_finalized_peer_at_best_self: HeaderId(0, 0),
|
||||
},
|
||||
source_latest_generated_nonce: 10,
|
||||
target_state: ClientState {
|
||||
best_self: HeaderId(0, 0),
|
||||
best_finalized_self: HeaderId(0, 0),
|
||||
best_finalized_peer_at_best_self: HeaderId(0, 0),
|
||||
actual_best_finalized_peer_at_best_self: HeaderId(0, 0),
|
||||
},
|
||||
target_latest_received_nonce: 0,
|
||||
..Default::default()
|
||||
}));
|
||||
let target_original_data = original_data.clone();
|
||||
let source_original_data = original_data.clone();
|
||||
let result = run_loop_test(
|
||||
original_data,
|
||||
Arc::new(|_| {}),
|
||||
Arc::new(move |data: &mut TestClientData| {
|
||||
if let Some(target_to_source_header_required) =
|
||||
data.target_to_source_header_required.take()
|
||||
{
|
||||
data.target_to_source_batch_transaction =
|
||||
Some(TestConfirmationBatchTransaction {
|
||||
data: source_original_data.clone(),
|
||||
required_header_id: target_to_source_header_required,
|
||||
tx_tracker: TestTransactionTracker::default(),
|
||||
})
|
||||
}
|
||||
}),
|
||||
Arc::new(|_| {}),
|
||||
Arc::new(move |data: &mut TestClientData| {
|
||||
if let Some(source_to_target_header_required) =
|
||||
data.source_to_target_header_required.take()
|
||||
{
|
||||
data.source_to_target_batch_transaction = Some(TestMessagesBatchTransaction {
|
||||
data: target_original_data.clone(),
|
||||
required_header_id: source_to_target_header_required,
|
||||
tx_tracker: TestTransactionTracker::default(),
|
||||
})
|
||||
}
|
||||
|
||||
if data.source_latest_confirmed_received_nonce == 10 {
|
||||
exit_sender.unbounded_send(()).unwrap();
|
||||
}
|
||||
}),
|
||||
exit_receiver.into_future().map(|(_, _)| ()),
|
||||
);
|
||||
|
||||
// there are no strict restrictions on when reward confirmation should come
|
||||
// (because `max_unconfirmed_nonces_at_target` is `100` in tests and this confirmation
|
||||
// depends on the state of both clients)
|
||||
// => we do not check it here
|
||||
assert_eq!(result.submitted_messages_proofs[0].0, 1..=4);
|
||||
assert_eq!(result.submitted_messages_proofs[1].0, 5..=8);
|
||||
assert_eq!(result.submitted_messages_proofs[2].0, 9..=10);
|
||||
assert!(!result.submitted_messages_receiving_proofs.is_empty());
|
||||
|
||||
// check that we have at least once required new source->target or target->source headers
|
||||
assert!(!result.target_to_source_header_requirements.is_empty());
|
||||
assert!(!result.source_to_target_header_requirements.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user