Skip to main content

consensus_core/
transaction.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3use std::{collections::BTreeMap, sync::Arc};
4
5use consensus_config::Epoch;
6use consensus_types::block::{
7    BlockRef, NUM_RESERVED_TRANSACTION_INDICES, PING_TRANSACTION_INDEX, Round, TransactionIndex,
8};
9use mysten_common::debug_fatal;
10use mysten_metrics::monitored_mpsc::{Receiver, Sender, channel};
11use parking_lot::Mutex;
12use tap::TapFallible;
13use thiserror::Error;
14use tokio::sync::oneshot;
15use tracing::{error, warn};
16
17use crate::{block::Transaction, context::Context};
18
19/// The maximum number of transactions pending to the queue to be pulled for block proposal
20const MAX_PENDING_TRANSACTIONS: usize = 2_000;
21
22/// Priority of a submission to consensus. Consensus is agnostic to transaction type; the
23/// submitter decides the priority. `High` submissions use a dedicated, reserved lane and
24/// are pulled ahead of `Normal` ones for block proposal.
25#[derive(Debug, Clone, Copy, Eq, PartialEq)]
26pub enum Priority {
27    Normal,
28    High,
29}
30
31/// Reserved capacity for the local validator's own high-priority submissions, which are
32/// low-volume and pulled ahead of normal-priority ones for block proposal.
33const MAX_PENDING_PRIORITY_TRANSACTIONS: usize = 128;
34
35/// The guard acts as an acknowledgment mechanism for the inclusion of the transactions to a block.
36/// When its last transaction is included to a block then `included_in_block_ack` will be signalled.
37/// If the guard is dropped without getting acknowledged that means the transactions have not been
38/// included to a block and the consensus is shutting down.
39pub(crate) struct TransactionsGuard {
40    // Holds a list of transactions to be included in the block.
41    // A TransactionsGuard may be partially consumed by `TransactionConsumer`, in which case, this holds the remaining transactions.
42    transactions: Vec<Transaction>,
43
44    // When the transactions are included in a block, this will be signalled with
45    // the following information
46    included_in_block_ack: oneshot::Sender<(
47        // The block reference in which the transactions have been included
48        BlockRef,
49        // The indices of the transactions that have been included in the block
50        Vec<TransactionIndex>,
51        // A receiver to notify the submitter about the block status
52        oneshot::Receiver<BlockStatus>,
53    )>,
54}
55
56/// The TransactionConsumer is responsible for fetching the next transactions to be included for the block proposals.
57/// The transactions are submitted to a channel which is shared between the TransactionConsumer and the TransactionClient
58/// and are pulled every time the `next` method is called.
59pub(crate) struct TransactionConsumer {
60    tx_receiver: Receiver<TransactionsGuard>,
61    // Reserved lane for the local validator's own high-priority submissions, drained
62    // ahead of `tx_receiver` so they are never delayed behind a normal-priority backlog.
63    priority_tx_receiver: Receiver<TransactionsGuard>,
64    max_transactions_in_block_bytes: u64,
65    max_num_transactions_in_block: u64,
66    pending_transactions: Option<TransactionsGuard>,
67    block_status_subscribers: Arc<Mutex<BTreeMap<BlockRef, Vec<oneshot::Sender<BlockStatus>>>>>,
68}
69
70#[derive(Debug, Clone, Eq, PartialEq)]
71pub enum BlockStatus {
72    /// The block has been sequenced as part of a committed sub dag. That means that any transaction that has been included in the block
73    /// has been committed as well.
74    Sequenced(BlockRef),
75    /// The block has been garbage collected and will never be committed. Any transactions that have been included in the block should also
76    /// be considered as impossible to be committed as part of this block and might need to be retried
77    GarbageCollected(BlockRef),
78}
79
80#[derive(Debug, Clone, Eq, PartialEq)]
81pub enum LimitReached {
82    // The maximum number of transactions have been included
83    MaxNumOfTransactions,
84    // The maximum number of bytes have been included
85    MaxBytes,
86    // All available transactions have been included
87    AllTransactionsIncluded,
88}
89
90impl TransactionConsumer {
91    pub(crate) fn new(
92        tx_receiver: Receiver<TransactionsGuard>,
93        priority_tx_receiver: Receiver<TransactionsGuard>,
94        context: Arc<Context>,
95    ) -> Self {
96        // max_num_transactions_in_block - 1 is the max possible transaction index in a block.
97        // TransactionIndex::MAX is reserved for the ping transaction.
98        // Indexes down to TransactionIndex::MAX - 8 are also reserved for future use.
99        // This check makes sure they do not overlap.
100        assert!(
101            context
102                .protocol_config
103                .max_num_transactions_in_block()
104                .saturating_sub(1)
105                < TransactionIndex::MAX.saturating_sub(NUM_RESERVED_TRANSACTION_INDICES) as u64,
106            "Unsupported max_num_transactions_in_block: {}",
107            context.protocol_config.max_num_transactions_in_block()
108        );
109
110        Self {
111            tx_receiver,
112            priority_tx_receiver,
113            max_transactions_in_block_bytes: context
114                .protocol_config
115                .max_transactions_in_block_bytes(),
116            max_num_transactions_in_block: context.protocol_config.max_num_transactions_in_block(),
117            pending_transactions: None,
118            block_status_subscribers: Arc::new(Mutex::new(BTreeMap::new())),
119        }
120    }
121
122    // Attempts to fetch the next transactions that have been submitted for sequence. Respects the `max_transactions_in_block_bytes`
123    // and `max_num_transactions_in_block` parameters specified via protocol config.
124    // This returns one or more transactions to be included in the block and a callback to acknowledge the inclusion of those transactions.
125    // Also returns a `LimitReached` enum to indicate which limit type has been reached.
126    pub(crate) fn next(
127        &mut self,
128    ) -> (
129        Vec<Transaction>,
130        Box<dyn FnOnce(BlockRef) + Send>,
131        LimitReached,
132    ) {
133        let mut transactions = Vec::new();
134        let mut acks = Vec::new();
135        let mut total_bytes = 0;
136        let mut limit_reached = LimitReached::AllTransactionsIncluded;
137
138        // Handle one batch of incoming transactions from TransactionGuard.
139        // The method will return `None` if all the transactions can be included in the block. Otherwise none of the transactions will be
140        // included in the block and the method will return the TransactionGuard.
141        let mut handle_txs = |t: TransactionsGuard| -> Option<TransactionsGuard> {
142            // If no transactions are submitted, it means that the transaction guard represents a ping transaction.
143            // In this case, we need to push the `PING_TRANSACTION_INDEX` to the indices vector.
144            let transactions_num = t.transactions.len() as u64;
145            if transactions_num == 0 {
146                acks.push((t.included_in_block_ack, vec![PING_TRANSACTION_INDEX]));
147                return None;
148            }
149
150            // Check if the total bytes of the transactions exceed the max transactions in block bytes.
151            let transactions_bytes =
152                t.transactions.iter().map(|t| t.data().len()).sum::<usize>() as u64;
153            if total_bytes + transactions_bytes > self.max_transactions_in_block_bytes {
154                limit_reached = LimitReached::MaxBytes;
155                return Some(t);
156            }
157            if transactions.len() as u64 + transactions_num > self.max_num_transactions_in_block {
158                limit_reached = LimitReached::MaxNumOfTransactions;
159                return Some(t);
160            }
161
162            total_bytes += transactions_bytes;
163
164            // Calculate indices for this batch
165            let start_idx = transactions.len() as TransactionIndex;
166            let indices: Vec<TransactionIndex> =
167                (start_idx..start_idx + t.transactions.len() as TransactionIndex).collect();
168
169            // The transactions can be consumed, register its ack and transaction
170            // indices to be sent with the ack.
171            acks.push((t.included_in_block_ack, indices));
172            transactions.extend(t.transactions);
173            None
174        };
175
176        if let Some(t) = self.pending_transactions.take()
177            && let Some(pending_transactions) = handle_txs(t)
178        {
179            debug_fatal!(
180                "Previously pending transaction(s) should fit into an empty block! Dropping: {:?}",
181                pending_transactions.transactions
182            );
183        }
184
185        // Pull until we reach the block limit (which may already be reached above).
186        // The reserved priority lane is drained first, so this validator's own
187        // high-priority submissions get block space ahead of a normal-priority backlog.
188        for receiver in [&mut self.priority_tx_receiver, &mut self.tx_receiver] {
189            while self.pending_transactions.is_none() {
190                if let Ok(t) = receiver.try_recv() {
191                    self.pending_transactions = handle_txs(t);
192                } else {
193                    break;
194                }
195            }
196        }
197
198        let block_status_subscribers = self.block_status_subscribers.clone();
199        (
200            transactions,
201            Box::new(move |block_ref: BlockRef| {
202                let mut block_status_subscribers = block_status_subscribers.lock();
203
204                for (ack, tx_indices) in acks {
205                    let (status_tx, status_rx) = oneshot::channel();
206
207                    block_status_subscribers
208                        .entry(block_ref)
209                        .or_default()
210                        .push(status_tx);
211
212                    let _ = ack.send((block_ref, tx_indices, status_rx));
213                }
214            }),
215            limit_reached,
216        )
217    }
218
219    /// Notifies all the transaction submitters who are waiting to receive an update on the status of the block.
220    /// The `committed_blocks` are the blocks that have been committed and the `gc_round` is the round up to which the blocks have been garbage collected.
221    /// First we'll notify for all the committed blocks, and then for all the blocks that have been garbage collected.
222    pub(crate) fn notify_own_blocks_status(
223        &self,
224        committed_blocks: Vec<BlockRef>,
225        gc_round: Round,
226    ) {
227        // Notify for all the committed blocks first
228        let mut block_status_subscribers = self.block_status_subscribers.lock();
229        for block_ref in committed_blocks {
230            if let Some(subscribers) = block_status_subscribers.remove(&block_ref) {
231                subscribers.into_iter().for_each(|s| {
232                    let _ = s.send(BlockStatus::Sequenced(block_ref));
233                });
234            }
235        }
236
237        // Now notify everyone <= gc_round that their block has been garbage collected and clean up the entries
238        while let Some((block_ref, subscribers)) = block_status_subscribers.pop_first() {
239            if block_ref.round <= gc_round {
240                subscribers.into_iter().for_each(|s| {
241                    let _ = s.send(BlockStatus::GarbageCollected(block_ref));
242                });
243            } else {
244                block_status_subscribers.insert(block_ref, subscribers);
245                break;
246            }
247        }
248    }
249
250    #[cfg(test)]
251    pub(crate) fn subscribe_for_block_status_testing(
252        &self,
253        block_ref: BlockRef,
254    ) -> oneshot::Receiver<BlockStatus> {
255        let (tx, rx) = oneshot::channel();
256        let mut block_status_subscribers = self.block_status_subscribers.lock();
257        block_status_subscribers
258            .entry(block_ref)
259            .or_default()
260            .push(tx);
261        rx
262    }
263
264    #[cfg(test)]
265    fn is_empty(&mut self) -> bool {
266        if self.pending_transactions.is_some() {
267            return false;
268        }
269        for receiver in [&mut self.priority_tx_receiver, &mut self.tx_receiver] {
270            if let Ok(t) = receiver.try_recv() {
271                self.pending_transactions = Some(t);
272                return false;
273            }
274        }
275        true
276    }
277}
278
279#[derive(Clone)]
280pub struct TransactionClient {
281    context: Arc<Context>,
282    sender: Sender<TransactionsGuard>,
283    // Reserved channel for the local validator's own high-priority submissions.
284    priority_sender: Sender<TransactionsGuard>,
285    max_transaction_size: u64,
286    max_transactions_in_block_bytes: u64,
287    max_transactions_in_block_count: u64,
288}
289
290#[derive(Debug, Error)]
291pub enum ClientError {
292    #[error("Failed to submit transaction, consensus is shutting down: {0}")]
293    ConsensusShuttingDown(String),
294
295    #[error("Transaction size ({0}B) is over limit ({1}B)")]
296    OversizedTransaction(u64, u64),
297
298    #[error("Transaction bundle size ({0}B) is over limit ({1}B)")]
299    OversizedTransactionBundleBytes(u64, u64),
300
301    #[error("Transaction bundle count ({0}) is over limit ({1})")]
302    OversizedTransactionBundleCount(u64, u64),
303}
304
305impl TransactionClient {
306    /// Returns the client and the receivers for normal and priority transactions, in that order.
307    pub(crate) fn new(
308        context: Arc<Context>,
309    ) -> (
310        Self,
311        Receiver<TransactionsGuard>,
312        Receiver<TransactionsGuard>,
313    ) {
314        Self::new_with_max_pending_transactions(
315            context,
316            MAX_PENDING_TRANSACTIONS,
317            MAX_PENDING_PRIORITY_TRANSACTIONS,
318        )
319    }
320
321    /// Returns the client and the receivers for normal and priority transactions, in that order.
322    fn new_with_max_pending_transactions(
323        context: Arc<Context>,
324        max_pending_transactions: usize,
325        max_pending_priority_transactions: usize,
326    ) -> (
327        Self,
328        Receiver<TransactionsGuard>,
329        Receiver<TransactionsGuard>,
330    ) {
331        let (sender, receiver) = channel("consensus_input", max_pending_transactions);
332        let (priority_sender, priority_receiver) = channel(
333            "consensus_input_priority",
334            max_pending_priority_transactions,
335        );
336        (
337            Self {
338                sender,
339                priority_sender,
340                max_transaction_size: context.protocol_config.max_transaction_size_bytes(),
341
342                max_transactions_in_block_bytes: context
343                    .protocol_config
344                    .max_transactions_in_block_bytes(),
345                max_transactions_in_block_count: context
346                    .protocol_config
347                    .max_num_transactions_in_block(),
348                context: context.clone(),
349            },
350            receiver,
351            priority_receiver,
352        )
353    }
354
355    /// Returns the current epoch of this client.
356    pub fn epoch(&self) -> Epoch {
357        self.context.committee.epoch()
358    }
359
360    /// Submits a list of transactions to be sequenced. The method returns when all the transactions have been successfully included
361    /// to next proposed blocks.
362    ///
363    /// If `transactions` is empty, then this will be interpreted as a "ping" signal from the client in order to get information about the next
364    /// block and simulate a transaction inclusion to the next block. In this an empty vector of the transaction index will be returned as response
365    /// and the block status receiver.
366    pub async fn submit(
367        &self,
368        transactions: Vec<Vec<u8>>,
369        priority: Priority,
370    ) -> Result<
371        (
372            BlockRef,
373            Vec<TransactionIndex>,
374            oneshot::Receiver<BlockStatus>,
375        ),
376        ClientError,
377    > {
378        let included_in_block = self.submit_no_wait(transactions, priority).await?;
379        included_in_block
380            .await
381            .tap_err(|e| warn!("Transaction acknowledge failed with {:?}", e))
382            .map_err(|e| ClientError::ConsensusShuttingDown(e.to_string()))
383    }
384
385    /// Submits a list of transactions to be sequenced.
386    /// If any transaction's length exceeds `max_transaction_size`, no transaction will be submitted.
387    /// That shouldn't be the common case as sizes should be aligned between consensus and client. The method returns
388    /// a receiver to wait on until the transactions has been included in the next block to get proposed. The consumer should
389    /// wait on it to consider as inclusion acknowledgement. If the receiver errors then consensus is shutting down and transaction
390    /// has not been included to any block.
391    /// If multiple transactions are submitted, the method will attempt to bundle them together in a single block. If the total size of
392    /// the transactions exceeds `max_transactions_in_block_bytes`, no transaction will be submitted and an error will be returned instead.
393    /// Similar if transactions exceed `max_transactions_in_block_count` an error will be returned.
394    pub(crate) async fn submit_no_wait(
395        &self,
396        transactions: Vec<Vec<u8>>,
397        priority: Priority,
398    ) -> Result<
399        oneshot::Receiver<(
400            BlockRef,
401            Vec<TransactionIndex>,
402            oneshot::Receiver<BlockStatus>,
403        )>,
404        ClientError,
405    > {
406        let (included_in_block_ack_send, included_in_block_ack_receive) = oneshot::channel();
407
408        let mut bundle_size = 0;
409
410        if transactions.len() as u64 > self.max_transactions_in_block_count {
411            return Err(ClientError::OversizedTransactionBundleCount(
412                transactions.len() as u64,
413                self.max_transactions_in_block_count,
414            ));
415        }
416
417        for transaction in &transactions {
418            if transaction.len() as u64 > self.max_transaction_size {
419                return Err(ClientError::OversizedTransaction(
420                    transaction.len() as u64,
421                    self.max_transaction_size,
422                ));
423            }
424            bundle_size += transaction.len() as u64;
425
426            if bundle_size > self.max_transactions_in_block_bytes {
427                return Err(ClientError::OversizedTransactionBundleBytes(
428                    bundle_size,
429                    self.max_transactions_in_block_bytes,
430                ));
431            }
432        }
433
434        let t = TransactionsGuard {
435            transactions: transactions.into_iter().map(Transaction::new).collect(),
436            included_in_block_ack: included_in_block_ack_send,
437        };
438        let sender = match priority {
439            Priority::High => &self.priority_sender,
440            Priority::Normal => &self.sender,
441        };
442        // A full reserved priority lane means a high-priority submission has to wait —
443        // silently reintroducing the buffering the lane exists to avoid. Record it.
444        if priority == Priority::High && sender.capacity() == 0 {
445            self.context
446                .metrics
447                .node_metrics
448                .priority_submission_backpressure
449                .inc();
450        }
451        sender
452            .send(t)
453            .await
454            .tap_err(|e| error!("Submit transactions failed with {:?}", e))
455            .map_err(|e| ClientError::ConsensusShuttingDown(e.to_string()))?;
456        Ok(included_in_block_ack_receive)
457    }
458}
459
460/// `TransactionPool` supplies transactions for block proposals, as an alternative to
461/// submitting transactions through `TransactionClient`. Like `TransactionVerifier`, the
462/// implementation can be provided by Sui and passed into `ConsensusAuthority::start()`.
463pub trait TransactionPool: Send + Sync + 'static {
464    /// Called by the proposer while building a block. Takes transactions to include, up to
465    /// `max_count` transactions and `max_bytes` total serialized bytes. Returns the
466    /// transactions in block order, an ack callback the proposer invokes with the created
467    /// block's reference after the block is durably created, and which limit stopped the
468    /// take. Dropping the callback without invoking it means the transactions have not been
469    /// included in a block, and the implementation may make them available to take again.
470    fn take(
471        &self,
472        max_count: usize,
473        max_bytes: usize,
474    ) -> (
475        Vec<Transaction>,
476        Box<dyn FnOnce(BlockRef) + Send>,
477        LimitReached,
478    );
479
480    /// Called from the commit path with this authority's own committed block refs and the
481    /// current GC round. Own blocks at rounds <= `gc_round` that are not committed are GC'ed
482    /// and will never commit.
483    fn notify_committed(&self, own_committed_blocks: Vec<BlockRef>, gc_round: Round);
484}
485
486/// Adapts the channel-based `TransactionConsumer` to the `TransactionPool` interface, for
487/// transactions submitted through `TransactionClient`.
488pub(crate) struct TransactionConsumerPool {
489    consumer: Mutex<TransactionConsumer>,
490}
491
492impl TransactionConsumerPool {
493    pub(crate) fn new(consumer: TransactionConsumer) -> Self {
494        Self {
495            consumer: Mutex::new(consumer),
496        }
497    }
498
499    #[cfg(test)]
500    pub(crate) fn subscribe_for_block_status_testing(
501        &self,
502        block_ref: BlockRef,
503    ) -> oneshot::Receiver<BlockStatus> {
504        self.consumer
505            .lock()
506            .subscribe_for_block_status_testing(block_ref)
507    }
508}
509
510impl TransactionPool for TransactionConsumerPool {
511    // TransactionConsumer enforces the same max limits internally via protocol config.
512    fn take(
513        &self,
514        _max_count: usize,
515        _max_bytes: usize,
516    ) -> (
517        Vec<Transaction>,
518        Box<dyn FnOnce(BlockRef) + Send>,
519        LimitReached,
520    ) {
521        self.consumer.lock().next()
522    }
523
524    fn notify_committed(&self, own_committed_blocks: Vec<BlockRef>, gc_round: Round) {
525        self.consumer
526            .lock()
527            .notify_own_blocks_status(own_committed_blocks, gc_round);
528    }
529}
530
531/// `TransactionVerifier` implementation is supplied by Sui to validate transactions in a block,
532/// before acceptance of the block.
533pub trait TransactionVerifier: Send + Sync + 'static {
534    /// Determines if this batch of transactions is valid.
535    /// Fails if any one of the transactions is invalid.
536    fn verify_batch(&self, batch: &[&[u8]]) -> Result<(), ValidationError>;
537
538    /// Returns indices of transactions to reject, or a transaction validation error.
539    /// Currently only uncertified user transactions can be voted to reject, which are created
540    /// by Mysticeti fastpath client.
541    /// Honest validators may disagree on voting for uncertified user transactions.
542    /// The other types of transactions are implicitly voted to be accepted if they pass validation.
543    ///
544    /// Honest validators should produce the same validation outcome on the same batch of
545    /// transactions. So if a batch from a peer fails validation, the peer is equivocating.
546    fn verify_and_vote_batch(
547        &self,
548        block_ref: &BlockRef,
549        batch: &[&[u8]],
550    ) -> Result<Vec<TransactionIndex>, ValidationError>;
551}
552
553#[derive(Debug, Error)]
554pub enum ValidationError {
555    #[error("Invalid transaction: {0}")]
556    InvalidTransaction(String),
557}
558
559/// `NoopTransactionVerifier` accepts all transactions.
560#[cfg(any(test, msim))]
561pub struct NoopTransactionVerifier;
562
563#[cfg(any(test, msim))]
564impl TransactionVerifier for NoopTransactionVerifier {
565    fn verify_batch(&self, _batch: &[&[u8]]) -> Result<(), ValidationError> {
566        Ok(())
567    }
568
569    fn verify_and_vote_batch(
570        &self,
571        _block_ref: &BlockRef,
572        _batch: &[&[u8]],
573    ) -> Result<Vec<TransactionIndex>, ValidationError> {
574        Ok(vec![])
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use std::{sync::Arc, time::Duration};
581
582    use consensus_config::AuthorityIndex;
583    use consensus_types::block::{
584        BlockDigest, BlockRef, NUM_RESERVED_TRANSACTION_INDICES, PING_TRANSACTION_INDEX,
585        TransactionIndex,
586    };
587    use futures::{StreamExt, stream::FuturesUnordered};
588    use tokio::time::timeout;
589
590    use crate::transaction::NoopTransactionVerifier;
591    use crate::{
592        block_verifier::SignedBlockVerifier,
593        context::Context,
594        transaction::{
595            BlockStatus, LimitReached, MAX_PENDING_PRIORITY_TRANSACTIONS, Priority,
596            TransactionClient, TransactionConsumer,
597        },
598    };
599
600    #[tokio::test(flavor = "current_thread", start_paused = true)]
601    async fn basic_submit_and_consume() {
602        let (mut context, _) = Context::new_for_test(4);
603        context
604            .protocol_config
605            .set_max_transaction_size_bytes_for_testing(2_000); // 2KB
606        context
607            .protocol_config
608            .set_max_transactions_in_block_bytes_for_testing(2_000);
609        let context = Arc::new(context);
610        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
611        let mut consumer =
612            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
613
614        // submit asynchronously the transactions and keep the waiters
615        let mut included_in_block_waiters = FuturesUnordered::new();
616        for i in 0..3 {
617            let transaction =
618                bcs::to_bytes(&format!("transaction {i}")).expect("Serialization should not fail.");
619            let w = client
620                .submit_no_wait(vec![transaction], Priority::Normal)
621                .await
622                .expect("Shouldn't submit successfully transaction");
623            included_in_block_waiters.push(w);
624        }
625
626        // now pull the transactions from the consumer
627        let (transactions, ack_transactions, _limit_reached) = consumer.next();
628        assert_eq!(transactions.len(), 3);
629
630        for (i, t) in transactions.iter().enumerate() {
631            let t: String = bcs::from_bytes(t.data()).unwrap();
632            assert_eq!(format!("transaction {i}").to_string(), t);
633        }
634
635        assert!(
636            timeout(Duration::from_secs(1), included_in_block_waiters.next())
637                .await
638                .is_err(),
639            "We should expect to timeout as none of the transactions have been acknowledged yet"
640        );
641
642        // Now acknowledge the inclusion of transactions
643        ack_transactions(BlockRef::MIN);
644
645        // Now make sure that all the waiters have returned
646        while let Some(result) = included_in_block_waiters.next().await {
647            assert!(result.is_ok());
648        }
649
650        // try to pull again transactions, result should be empty
651        assert!(consumer.is_empty());
652    }
653
654    #[tokio::test(flavor = "current_thread", start_paused = true)]
655    async fn high_priority_transactions_included_first() {
656        let (mut context, _) = Context::new_for_test(4);
657        context
658            .protocol_config
659            .set_max_transaction_size_bytes_for_testing(2_000);
660        context
661            .protocol_config
662            .set_max_transactions_in_block_bytes_for_testing(2_000);
663        let context = Arc::new(context);
664        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
665        let mut consumer =
666            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
667
668        // Submit normal transactions first, then a high-priority one.
669        for i in 0..3 {
670            let t = bcs::to_bytes(&format!("normal {i}")).unwrap();
671            client
672                .submit_no_wait(vec![t], Priority::Normal)
673                .await
674                .unwrap();
675        }
676        let high = bcs::to_bytes(&"high".to_string()).unwrap();
677        client
678            .submit_no_wait(vec![high], Priority::High)
679            .await
680            .unwrap();
681
682        // The high-priority transaction is pulled ahead of the normal backlog.
683        let (transactions, _ack, _limit) = consumer.next();
684        let decoded: Vec<String> = transactions
685            .iter()
686            .map(|t| bcs::from_bytes(t.data()).unwrap())
687            .collect();
688        assert_eq!(decoded, vec!["high", "normal 0", "normal 1", "normal 2"]);
689    }
690
691    #[tokio::test(flavor = "current_thread", start_paused = true)]
692    async fn high_priority_bypasses_full_normal_lane() {
693        let (mut context, _) = Context::new_for_test(4);
694        context
695            .protocol_config
696            .set_max_transaction_size_bytes_for_testing(2_000);
697        context
698            .protocol_config
699            .set_max_transactions_in_block_bytes_for_testing(2_000);
700        let context = Arc::new(context);
701        // Each lane holds a single entry. The receivers are held (never drained) so
702        // the lanes stay full once an entry is buffered.
703        let (client, _tx_receiver, _priority_tx_receiver) =
704            TransactionClient::new_with_max_pending_transactions(context.clone(), 1, 1);
705
706        // Fill the normal lane.
707        let n0 = bcs::to_bytes(&"n0".to_string()).unwrap();
708        client
709            .submit_no_wait(vec![n0], Priority::Normal)
710            .await
711            .unwrap();
712
713        // A further normal submission blocks on the full normal lane.
714        let n1 = bcs::to_bytes(&"n1".to_string()).unwrap();
715        assert!(
716            timeout(
717                Duration::from_millis(100),
718                client.submit_no_wait(vec![n1], Priority::Normal)
719            )
720            .await
721            .is_err(),
722            "normal lane is full, so this submission must block"
723        );
724
725        // A high-priority submission uses the reserved lane and is not blocked.
726        let h = bcs::to_bytes(&"h".to_string()).unwrap();
727        timeout(
728            Duration::from_millis(100),
729            client.submit_no_wait(vec![h], Priority::High),
730        )
731        .await
732        .expect("high-priority submission must not block on a full normal lane")
733        .expect("high-priority submission should succeed");
734    }
735
736    #[tokio::test(flavor = "current_thread", start_paused = true)]
737    async fn priority_overflow_held_and_drained_before_normal() {
738        let (mut context, _) = Context::new_for_test(4);
739        context
740            .protocol_config
741            .set_max_transaction_size_bytes_for_testing(2_000);
742        context
743            .protocol_config
744            .set_max_transactions_in_block_bytes_for_testing(2_000);
745        // Only two transactions fit per block.
746        context
747            .protocol_config
748            .set_max_num_transactions_in_block_for_testing(2);
749        let context = Arc::new(context);
750        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
751        let mut consumer =
752            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
753
754        for i in 0..3 {
755            let t = bcs::to_bytes(&format!("p{i}")).unwrap();
756            client
757                .submit_no_wait(vec![t], Priority::High)
758                .await
759                .unwrap();
760        }
761        let n = bcs::to_bytes(&"n0".to_string()).unwrap();
762        client
763            .submit_no_wait(vec![n], Priority::Normal)
764            .await
765            .unwrap();
766
767        // Block 1: only the first two priority txns fit; the rest is held.
768        let (txs, ack, limit) = consumer.next();
769        let got: Vec<String> = txs
770            .iter()
771            .map(|t| bcs::from_bytes(t.data()).unwrap())
772            .collect();
773        assert_eq!(got, vec!["p0", "p1"]);
774        assert_eq!(limit, LimitReached::MaxNumOfTransactions);
775        ack(BlockRef::MIN);
776
777        // Block 2: the held priority txn is drained ahead of the normal one.
778        let (txs, ack, _) = consumer.next();
779        let got: Vec<String> = txs
780            .iter()
781            .map(|t| bcs::from_bytes(t.data()).unwrap())
782            .collect();
783        assert_eq!(got, vec!["p2", "n0"]);
784        ack(BlockRef::MIN);
785        assert!(consumer.is_empty());
786    }
787
788    #[tokio::test(flavor = "current_thread", start_paused = true)]
789    async fn priority_overflow_by_bytes_held_and_re_offered() {
790        let (mut context, _) = Context::new_for_test(4);
791        context
792            .protocol_config
793            .set_max_transaction_size_bytes_for_testing(2_000);
794        // Each payload below is 11 bytes; only one fits in a 15-byte block.
795        context
796            .protocol_config
797            .set_max_transactions_in_block_bytes_for_testing(15);
798        let context = Arc::new(context);
799        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
800        let mut consumer =
801            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
802
803        for p in ["AAAAAAAAAA", "BBBBBBBBBB"] {
804            let t = bcs::to_bytes(&p.to_string()).unwrap();
805            assert_eq!(t.len(), 11);
806            client
807                .submit_no_wait(vec![t], Priority::High)
808                .await
809                .unwrap();
810        }
811
812        // Block 1: only the first priority txn fits (byte limit); the second is held.
813        let (txs, ack, limit) = consumer.next();
814        let got: Vec<String> = txs
815            .iter()
816            .map(|t| bcs::from_bytes(t.data()).unwrap())
817            .collect();
818        assert_eq!(got, vec!["AAAAAAAAAA"]);
819        assert_eq!(limit, LimitReached::MaxBytes);
820        ack(BlockRef::MIN);
821
822        // Block 2: the held priority txn is re-offered first.
823        let (txs, ack, _) = consumer.next();
824        let got: Vec<String> = txs
825            .iter()
826            .map(|t| bcs::from_bytes(t.data()).unwrap())
827            .collect();
828        assert_eq!(got, vec!["BBBBBBBBBB"]);
829        ack(BlockRef::MIN);
830        assert!(consumer.is_empty());
831    }
832
833    #[tokio::test(flavor = "current_thread", start_paused = true)]
834    async fn interleaved_submissions_ordered_priority_first_then_fifo() {
835        let (mut context, _) = Context::new_for_test(4);
836        context
837            .protocol_config
838            .set_max_transaction_size_bytes_for_testing(2_000);
839        context
840            .protocol_config
841            .set_max_transactions_in_block_bytes_for_testing(2_000);
842        let context = Arc::new(context);
843        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
844        let mut consumer =
845            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
846
847        // Interleave the two lanes.
848        for (payload, priority) in [
849            ("n0", Priority::Normal),
850            ("p0", Priority::High),
851            ("n1", Priority::Normal),
852            ("p1", Priority::High),
853        ] {
854            let t = bcs::to_bytes(&payload.to_string()).unwrap();
855            client.submit_no_wait(vec![t], priority).await.unwrap();
856        }
857
858        // All priority txns first (in submission order), then all normal txns.
859        let (txs, _ack, _) = consumer.next();
860        let got: Vec<String> = txs
861            .iter()
862            .map(|t| bcs::from_bytes(t.data()).unwrap())
863            .collect();
864        assert_eq!(got, vec!["p0", "p1", "n0", "n1"]);
865    }
866
867    #[tokio::test(flavor = "current_thread", start_paused = true)]
868    async fn block_status_update() {
869        let (mut context, _) = Context::new_for_test(4);
870        context
871            .protocol_config
872            .set_max_transaction_size_bytes_for_testing(2_000); // 2KB
873        context
874            .protocol_config
875            .set_max_transactions_in_block_bytes_for_testing(2_000);
876        context.protocol_config.set_gc_depth_for_testing(10);
877        let context = Arc::new(context);
878        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
879        let mut consumer =
880            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
881
882        // submit the transactions and include 2 of each on a new block
883        let mut included_in_block_waiters = FuturesUnordered::new();
884        for i in 1..=10 {
885            let transaction =
886                bcs::to_bytes(&format!("transaction {i}")).expect("Serialization should not fail.");
887            let w = client
888                .submit_no_wait(vec![transaction], Priority::Normal)
889                .await
890                .expect("Shouldn't submit successfully transaction");
891            included_in_block_waiters.push(w);
892
893            // Every 2 transactions simulate the creation of a new block and acknowledge the inclusion of the transactions
894            if i % 2 == 0 {
895                let (transactions, ack_transactions, _limit_reached) = consumer.next();
896                assert_eq!(transactions.len(), 2);
897                ack_transactions(BlockRef::new(
898                    i,
899                    AuthorityIndex::new_for_test(0),
900                    BlockDigest::MIN,
901                ));
902            }
903        }
904
905        let mut transaction_count = 0;
906        // Now iterate over all the waiters. Everyone should have been acknowledged.
907        let mut block_status_waiters = Vec::new();
908        while let Some(result) = included_in_block_waiters.next().await {
909            let (block_ref, tx_indices, block_status_waiter) =
910                result.expect("Block inclusion waiter shouldn't fail");
911            // tx is submitted one at a time so tx acks should only return one tx index
912            assert_eq!(tx_indices.len(), 1);
913            // The first transaction in the block should have index 0, the second one 1, etc.
914            // because we submit 2 transactions per block, the index should be 0 then 1 and then
915            // reset back to 0 for the next block.
916            assert_eq!(tx_indices[0], transaction_count % 2);
917            transaction_count += 1;
918
919            block_status_waiters.push((block_ref, block_status_waiter));
920        }
921
922        // Now acknowledge the commit of the blocks 6, 8, 10 and set gc_round = 5, which should trigger the garbage collection of blocks 1..=5
923        let gc_round = 5;
924        consumer.notify_own_blocks_status(
925            vec![
926                BlockRef::new(6, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
927                BlockRef::new(8, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
928                BlockRef::new(10, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
929            ],
930            gc_round,
931        );
932
933        // Now iterate over all the block status waiters. Everyone should have been notified.
934        for (block_ref, waiter) in block_status_waiters {
935            let block_status = waiter.await.expect("Block status waiter shouldn't fail");
936
937            if block_ref.round <= gc_round {
938                assert!(matches!(block_status, BlockStatus::GarbageCollected(_)))
939            } else {
940                assert!(matches!(block_status, BlockStatus::Sequenced(_)));
941            }
942        }
943
944        // Ensure internal structure is clear
945        assert!(consumer.block_status_subscribers.lock().is_empty());
946    }
947
948    #[tokio::test]
949    async fn submit_over_max_fetch_size_and_consume() {
950        let (mut context, _) = Context::new_for_test(4);
951        context
952            .protocol_config
953            .set_max_transaction_size_bytes_for_testing(100);
954        context
955            .protocol_config
956            .set_max_transactions_in_block_bytes_for_testing(100);
957        let context = Arc::new(context);
958        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
959        let mut consumer =
960            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
961
962        // submit some transactions
963        for i in 0..10 {
964            let transaction =
965                bcs::to_bytes(&format!("transaction {i}")).expect("Serialization should not fail.");
966            let _w = client
967                .submit_no_wait(vec![transaction], Priority::Normal)
968                .await
969                .expect("Shouldn't submit successfully transaction");
970        }
971
972        // now pull the transactions from the consumer
973        let mut all_transactions = Vec::new();
974        let (transactions, _ack_transactions, _limit_reached) = consumer.next();
975        assert_eq!(transactions.len(), 7);
976
977        // ensure their total size is less than `max_bytes_to_fetch`
978        let total_size: u64 = transactions.iter().map(|t| t.data().len() as u64).sum();
979        assert!(
980            total_size <= context.protocol_config.max_transactions_in_block_bytes(),
981            "Should have fetched transactions up to {}",
982            context.protocol_config.max_transactions_in_block_bytes()
983        );
984        all_transactions.extend(transactions);
985
986        // try to pull again transactions, next should be provided
987        let (transactions, _ack_transactions, _limit_reached) = consumer.next();
988        assert_eq!(transactions.len(), 3);
989
990        // ensure their total size is less than `max_bytes_to_fetch`
991        let total_size: u64 = transactions.iter().map(|t| t.data().len() as u64).sum();
992        assert!(
993            total_size <= context.protocol_config.max_transactions_in_block_bytes(),
994            "Should have fetched transactions up to {}",
995            context.protocol_config.max_transactions_in_block_bytes()
996        );
997        all_transactions.extend(transactions);
998
999        // try to pull again transactions, result should be empty
1000        assert!(consumer.is_empty());
1001
1002        for (i, t) in all_transactions.iter().enumerate() {
1003            let t: String = bcs::from_bytes(t.data()).unwrap();
1004            assert_eq!(format!("transaction {i}").to_string(), t);
1005        }
1006    }
1007
1008    #[tokio::test]
1009    async fn submit_large_batch_and_ack() {
1010        let (mut context, _) = Context::new_for_test(4);
1011        context
1012            .protocol_config
1013            .set_max_transaction_size_bytes_for_testing(15);
1014        context
1015            .protocol_config
1016            .set_max_transactions_in_block_bytes_for_testing(200);
1017        let context = Arc::new(context);
1018        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
1019        let mut consumer =
1020            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
1021        let mut all_receivers = Vec::new();
1022        // submit a few transactions individually.
1023        for i in 0..10 {
1024            let transaction =
1025                bcs::to_bytes(&format!("transaction {i}")).expect("Serialization should not fail.");
1026            let w = client
1027                .submit_no_wait(vec![transaction], Priority::Normal)
1028                .await
1029                .expect("Should submit successfully transaction");
1030            all_receivers.push(w);
1031        }
1032
1033        // construct an acceptable batch and submit, it should be accepted
1034        {
1035            let transactions: Vec<_> = (10..15)
1036                .map(|i| {
1037                    bcs::to_bytes(&format!("transaction {i}"))
1038                        .expect("Serialization should not fail.")
1039                })
1040                .collect();
1041            let w = client
1042                .submit_no_wait(transactions, Priority::Normal)
1043                .await
1044                .expect("Should submit successfully transaction");
1045            all_receivers.push(w);
1046        }
1047
1048        // submit another individual transaction.
1049        {
1050            let i = 15;
1051            let transaction =
1052                bcs::to_bytes(&format!("transaction {i}")).expect("Serialization should not fail.");
1053            let w = client
1054                .submit_no_wait(vec![transaction], Priority::Normal)
1055                .await
1056                .expect("Shouldn't submit successfully transaction");
1057            all_receivers.push(w);
1058        }
1059
1060        // construct a over-size-limit batch and submit, it should not be accepted
1061        {
1062            let transactions: Vec<_> = (16..32)
1063                .map(|i| {
1064                    bcs::to_bytes(&format!("transaction {i}"))
1065                        .expect("Serialization should not fail.")
1066                })
1067                .collect();
1068            let result = client
1069                .submit_no_wait(transactions, Priority::Normal)
1070                .await
1071                .unwrap_err();
1072            assert_eq!(
1073                result.to_string(),
1074                "Transaction bundle size (210B) is over limit (200B)"
1075            );
1076        }
1077
1078        // now pull the transactions from the consumer.
1079        // we expect all transactions are fetched in order, not missing any, and not exceeding the size limit.
1080        let mut all_acks: Vec<Box<dyn FnOnce(BlockRef)>> = Vec::new();
1081        let mut batch_index = 0;
1082        while !consumer.is_empty() {
1083            let (transactions, ack_transactions, _limit_reached) = consumer.next();
1084
1085            assert!(
1086                transactions.len() as u64
1087                    <= context.protocol_config.max_num_transactions_in_block(),
1088                "Should have fetched transactions up to {}",
1089                context.protocol_config.max_num_transactions_in_block()
1090            );
1091
1092            let total_size: u64 = transactions.iter().map(|t| t.data().len() as u64).sum();
1093            assert!(
1094                total_size <= context.protocol_config.max_transactions_in_block_bytes(),
1095                "Should have fetched transactions up to {}",
1096                context.protocol_config.max_transactions_in_block_bytes()
1097            );
1098
1099            // first batch should contain all transactions from 0..10. The softbundle it is to big to fit as well, so it's parked.
1100            if batch_index == 0 {
1101                assert_eq!(transactions.len(), 10);
1102                for (i, transaction) in transactions.iter().enumerate() {
1103                    let t: String = bcs::from_bytes(transaction.data()).unwrap();
1104                    assert_eq!(format!("transaction {}", i).to_string(), t);
1105                }
1106            // second batch will contain the soft bundle and the additional last transaction.
1107            } else if batch_index == 1 {
1108                assert_eq!(transactions.len(), 6);
1109                for (i, transaction) in transactions.iter().enumerate() {
1110                    let t: String = bcs::from_bytes(transaction.data()).unwrap();
1111                    assert_eq!(format!("transaction {}", i + 10).to_string(), t);
1112                }
1113            } else {
1114                panic!("Unexpected batch index");
1115            }
1116
1117            batch_index += 1;
1118
1119            all_acks.push(ack_transactions);
1120        }
1121
1122        // now acknowledge the inclusion of all transactions.
1123        for ack in all_acks {
1124            ack(BlockRef::MIN);
1125        }
1126
1127        // expect all receivers to be resolved.
1128        for w in all_receivers {
1129            let r = w.await;
1130            assert!(r.is_ok());
1131        }
1132    }
1133
1134    #[tokio::test]
1135    async fn test_submit_over_max_block_size_and_validate_block_size() {
1136        // submit transactions individually so we make sure that we have reached the block size limit of 10
1137        {
1138            let (mut context, _) = Context::new_for_test(4);
1139            context
1140                .protocol_config
1141                .set_max_transaction_size_bytes_for_testing(100);
1142            context
1143                .protocol_config
1144                .set_max_num_transactions_in_block_for_testing(10);
1145            context
1146                .protocol_config
1147                .set_max_transactions_in_block_bytes_for_testing(300);
1148            let context = Arc::new(context);
1149            let (client, tx_receiver, priority_tx_receiver) =
1150                TransactionClient::new(context.clone());
1151            let mut consumer =
1152                TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
1153            let mut all_receivers = Vec::new();
1154
1155            // create enough transactions
1156            let max_num_transactions_in_block =
1157                context.protocol_config.max_num_transactions_in_block();
1158            for i in 0..2 * max_num_transactions_in_block {
1159                let transaction = bcs::to_bytes(&format!("transaction {i}"))
1160                    .expect("Serialization should not fail.");
1161                let w = client
1162                    .submit_no_wait(vec![transaction], Priority::Normal)
1163                    .await
1164                    .expect("Should submit successfully transaction");
1165                all_receivers.push(w);
1166            }
1167
1168            // Fetch the next transactions to be included in a block
1169            let (transactions, _ack_transactions, limit) = consumer.next();
1170            assert_eq!(limit, LimitReached::MaxNumOfTransactions);
1171            assert_eq!(transactions.len() as u64, max_num_transactions_in_block);
1172
1173            // Now create a block and verify that transactions are within the size limits
1174            let block_verifier =
1175                SignedBlockVerifier::new(context.clone(), Arc::new(NoopTransactionVerifier {}));
1176
1177            let batch: Vec<_> = transactions.iter().map(|t| t.data()).collect();
1178            assert!(
1179                block_verifier.check_transactions(&batch).is_ok(),
1180                "Number of transactions limit verification failed"
1181            );
1182        }
1183
1184        // submit transactions individually so we make sure that we have reached the block size bytes 300
1185        {
1186            let (mut context, _) = Context::new_for_test(4);
1187            context
1188                .protocol_config
1189                .set_max_transaction_size_bytes_for_testing(100);
1190            context
1191                .protocol_config
1192                .set_max_num_transactions_in_block_for_testing(1_000);
1193            context
1194                .protocol_config
1195                .set_max_transactions_in_block_bytes_for_testing(300);
1196            let context = Arc::new(context);
1197            let (client, tx_receiver, priority_tx_receiver) =
1198                TransactionClient::new(context.clone());
1199            let mut consumer =
1200                TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
1201            let mut all_receivers = Vec::new();
1202
1203            let max_transactions_in_block_bytes =
1204                context.protocol_config.max_transactions_in_block_bytes();
1205            let mut total_size = 0;
1206            loop {
1207                let transaction = bcs::to_bytes(&"transaction".to_string())
1208                    .expect("Serialization should not fail.");
1209                total_size += transaction.len() as u64;
1210                let w = client
1211                    .submit_no_wait(vec![transaction], Priority::Normal)
1212                    .await
1213                    .expect("Should submit successfully transaction");
1214                all_receivers.push(w);
1215
1216                // create enough transactions to reach the block size limit
1217                if total_size >= 2 * max_transactions_in_block_bytes {
1218                    break;
1219                }
1220            }
1221
1222            // Fetch the next transactions to be included in a block
1223            let (transactions, _ack_transactions, limit) = consumer.next();
1224            let batch: Vec<_> = transactions.iter().map(|t| t.data()).collect();
1225            let size = batch.iter().map(|t| t.len() as u64).sum::<u64>();
1226
1227            assert_eq!(limit, LimitReached::MaxBytes);
1228            assert!(
1229                batch.len() < context.protocol_config.max_num_transactions_in_block() as usize,
1230                "Should have submitted less than the max number of transactions in a block"
1231            );
1232            assert!(size <= max_transactions_in_block_bytes);
1233
1234            // Now create a block and verify that transactions are within the size limits
1235            let block_verifier =
1236                SignedBlockVerifier::new(context.clone(), Arc::new(NoopTransactionVerifier {}));
1237
1238            assert!(
1239                block_verifier.check_transactions(&batch).is_ok(),
1240                "Total size of transactions limit verification failed"
1241            );
1242        }
1243    }
1244
1245    // This is the case where the client submits a "ping" signal to the consensus to get information about the next block and simulate a transaction inclusion to the next block.
1246    #[tokio::test]
1247    async fn submit_with_no_transactions() {
1248        let (mut context, _) = Context::new_for_test(4);
1249        context
1250            .protocol_config
1251            .set_max_transaction_size_bytes_for_testing(15);
1252        context
1253            .protocol_config
1254            .set_max_transactions_in_block_bytes_for_testing(200);
1255        let context = Arc::new(context);
1256        let (client, tx_receiver, priority_tx_receiver) = TransactionClient::new(context.clone());
1257        let mut consumer =
1258            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
1259
1260        let w_no_transactions = client
1261            .submit_no_wait(vec![], Priority::Normal)
1262            .await
1263            .expect("Should submit successfully empty array of transactions");
1264
1265        let transaction =
1266            bcs::to_bytes(&"transaction".to_string()).expect("Serialization should not fail.");
1267        let w_with_transactions = client
1268            .submit_no_wait(vec![transaction], Priority::Normal)
1269            .await
1270            .expect("Should submit successfully transaction");
1271
1272        let (transactions, ack_transactions, _limit_reached) = consumer.next();
1273        assert_eq!(transactions.len(), 1);
1274
1275        // Acknowledge the inclusion of the transactions
1276        ack_transactions(BlockRef::MIN);
1277
1278        {
1279            let r = w_no_transactions.await;
1280            let (block_ref, indices, _status) = r.unwrap();
1281            assert_eq!(block_ref, BlockRef::MIN);
1282            assert_eq!(indices, vec![PING_TRANSACTION_INDEX]);
1283        }
1284
1285        {
1286            let r = w_with_transactions.await;
1287            let (block_ref, indices, _status) = r.unwrap();
1288            assert_eq!(block_ref, BlockRef::MIN);
1289            assert_eq!(indices, vec![0]);
1290        }
1291    }
1292
1293    #[tokio::test]
1294    async fn ping_transaction_index_never_reached() {
1295        // Set the max number of transactions in a block to the max value of u16.
1296        static MAX_NUM_TRANSACTIONS_IN_BLOCK: u64 =
1297            (TransactionIndex::MAX - NUM_RESERVED_TRANSACTION_INDICES) as u64;
1298
1299        // Ensure that enough space is allocated in the channel for the pending transactions, so we don't end up consuming the transactions in chunks.
1300        static MAX_PENDING_TRANSACTIONS: usize = 2 * MAX_NUM_TRANSACTIONS_IN_BLOCK as usize;
1301
1302        let (mut context, _) = Context::new_for_test(4);
1303        context
1304            .protocol_config
1305            .set_max_transaction_size_bytes_for_testing(200_000);
1306        context
1307            .protocol_config
1308            .set_max_transactions_in_block_bytes_for_testing(1_000_000);
1309        context
1310            .protocol_config
1311            .set_max_num_transactions_in_block_for_testing(MAX_NUM_TRANSACTIONS_IN_BLOCK);
1312        let context = Arc::new(context);
1313        let (client, tx_receiver, priority_tx_receiver) =
1314            TransactionClient::new_with_max_pending_transactions(
1315                context.clone(),
1316                MAX_PENDING_TRANSACTIONS,
1317                MAX_PENDING_PRIORITY_TRANSACTIONS,
1318            );
1319        let mut consumer =
1320            TransactionConsumer::new(tx_receiver, priority_tx_receiver, context.clone());
1321
1322        // Add 10 more transactions than the max number of transactions in a block.
1323        for i in 0..MAX_NUM_TRANSACTIONS_IN_BLOCK + 10 {
1324            println!("Submitting transaction {i}");
1325            let transaction =
1326                bcs::to_bytes(&format!("t {i}")).expect("Serialization should not fail.");
1327            let _w = client
1328                .submit_no_wait(vec![transaction], Priority::Normal)
1329                .await
1330                .expect("Shouldn't submit successfully transaction");
1331        }
1332
1333        // now pull the transactions from the consumer
1334        let (transactions, _ack_transactions, _limit_reached) = consumer.next();
1335        assert_eq!(transactions.len() as u64, MAX_NUM_TRANSACTIONS_IN_BLOCK);
1336
1337        let t: String = bcs::from_bytes(transactions.last().unwrap().data()).unwrap();
1338        assert_eq!(
1339            t,
1340            format!(
1341                "t {}",
1342                PING_TRANSACTION_INDEX - NUM_RESERVED_TRANSACTION_INDICES - 1
1343            )
1344        );
1345    }
1346}