1use std::collections::HashSet;
7use std::sync::Arc;
8
9use consensus_core::BlockStatus;
10use consensus_types::block::BlockRef;
11use itertools::Itertools;
12use parking_lot::Mutex;
13use prometheus::Registry;
14use sui_types::digests::{Digest, TransactionDigest};
15use sui_types::error::SuiResult;
16use sui_types::executable_transaction::VerifiedExecutableTransaction;
17use sui_types::messages_consensus::{
18 AuthorityIndex, ConsensusPosition, ConsensusTransaction, ConsensusTransactionKind,
19};
20use sui_types::sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait;
21use sui_types::transaction::VerifiedTransaction;
22
23use crate::authority::authority_per_epoch_store::{
24 AuthorityPerEpochStore, ExecutionIndicesWithStatsV2,
25};
26use crate::authority::backpressure::BackpressureManager;
27use crate::authority::shared_object_version_manager::Schedulable;
28use crate::authority::{AuthorityMetrics, AuthorityState, ExecutionEnv};
29use crate::consensus_adapter::{
30 BlockStatusReceiver, ConsensusAdapter, ConsensusAdapterMetrics, ConsensusClient,
31};
32use crate::consensus_handler::{
33 ConsensusHandler, ExecutionSchedulerSender, SequencedConsensusTransaction,
34 SequencedConsensusTransactionKind,
35};
36use crate::consensus_throughput_calculator::ConsensusThroughputCalculator;
37use crate::consensus_types::consensus_output_api::{ConsensusCommitAPI, ParsedTransaction};
38use crate::mock_consensus::with_block_status;
39
40pub(crate) type CapturedTransactions = Arc<Mutex<Vec<crate::consensus_handler::SchedulerMessage>>>;
41
42pub struct TestConsensusCommit {
43 pub transactions: Vec<ConsensusTransaction>,
44 pub round: u64,
45 pub timestamp_ms: u64,
46 pub sub_dag_index: u64,
47 rejected_indices: HashSet<usize>,
49 transaction_authors: Option<Vec<consensus_config::AuthorityIndex>>,
50}
51
52impl TestConsensusCommit {
53 pub fn new(
54 transactions: Vec<ConsensusTransaction>,
55 round: u64,
56 timestamp_ms: u64,
57 sub_dag_index: u64,
58 ) -> Self {
59 Self {
60 transactions,
61 round,
62 timestamp_ms,
63 sub_dag_index,
64 rejected_indices: HashSet::new(),
65 transaction_authors: None,
66 }
67 }
68
69 pub fn empty(round: u64, timestamp_ms: u64, sub_dag_index: u64) -> Self {
70 Self::new(vec![], round, timestamp_ms, sub_dag_index)
71 }
72
73 pub fn with_rejected_indices(mut self, indices: impl IntoIterator<Item = usize>) -> Self {
74 self.rejected_indices = indices.into_iter().collect();
75 self
76 }
77
78 pub fn with_transaction_authors(mut self, authors: impl IntoIterator<Item = u32>) -> Self {
79 self.transaction_authors = Some(
80 authors
81 .into_iter()
82 .map(consensus_config::AuthorityIndex::new_for_test)
83 .collect(),
84 );
85 self
86 }
87}
88
89impl std::fmt::Display for TestConsensusCommit {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 write!(
92 f,
93 "TestConsensusCommitAPI(round={}, timestamp_ms={}, sub_dag_index={})",
94 self.round, self.timestamp_ms, self.sub_dag_index
95 )
96 }
97}
98
99impl ConsensusCommitAPI for TestConsensusCommit {
100 fn commit_ref(&self) -> consensus_core::CommitRef {
101 consensus_core::CommitRef::default()
102 }
103
104 fn leader_round(&self) -> u64 {
105 self.round
106 }
107
108 fn leader_author_index(&self) -> AuthorityIndex {
109 0
110 }
111
112 fn commit_timestamp_ms(&self) -> u64 {
113 self.timestamp_ms
114 }
115
116 fn commit_sub_dag_index(&self) -> u64 {
117 self.sub_dag_index
118 }
119
120 fn transactions(&self) -> Vec<(BlockRef, Vec<ParsedTransaction>)> {
121 if let Some(authors) = &self.transaction_authors {
122 return self
123 .transactions
124 .iter()
125 .zip_eq(authors)
126 .enumerate()
127 .map(|(i, (tx, author))| {
128 let block_ref = BlockRef {
129 author: *author,
130 round: self.round as u32,
131 digest: Default::default(),
132 };
133 let parsed_tx = ParsedTransaction {
134 transaction: tx.clone(),
135 rejected: self.rejected_indices.contains(&i),
136 serialized_len: 0,
137 };
138 (block_ref, vec![parsed_tx])
139 })
140 .collect();
141 }
142
143 let block_ref = BlockRef {
144 author: consensus_config::AuthorityIndex::ZERO,
145 round: self.round as u32,
146 digest: Default::default(),
147 };
148
149 let parsed_txs: Vec<ParsedTransaction> = self
150 .transactions
151 .iter()
152 .enumerate()
153 .map(|(i, tx)| ParsedTransaction {
154 transaction: tx.clone(),
155 rejected: self.rejected_indices.contains(&i),
156 serialized_len: 0,
157 })
158 .collect();
159
160 vec![(block_ref, parsed_txs)]
161 }
162
163 fn rejected_transactions_digest(&self) -> Digest {
164 Digest::default()
165 }
166
167 fn rejected_transactions_debug_string(&self) -> String {
168 "no rejected transactions from TestConsensusCommit".to_string()
169 }
170}
171
172pub struct TestConsensusHandlerSetup<C> {
173 pub consensus_handler: ConsensusHandler<C>,
174 pub captured_transactions: CapturedTransactions,
175 pub metrics: Arc<AuthorityMetrics>,
176}
177
178pub fn make_consensus_adapter_with_client_for_test(
181 state: &Arc<AuthorityState>,
182 client: Arc<dyn ConsensusClient>,
183 max_pending_local_submissions: usize,
184) -> Arc<ConsensusAdapter> {
185 Arc::new(ConsensusAdapter::new(
186 client,
187 state.checkpoint_store.clone(),
188 state.name,
189 100_000,
190 max_pending_local_submissions,
191 ConsensusAdapterMetrics::new_test(),
192 Arc::new(tokio::sync::Notify::new()),
193 ))
194}
195
196pub fn make_consensus_adapter_for_test(
197 state: Arc<AuthorityState>,
198 process_via_checkpoint: HashSet<TransactionDigest>,
199 execute: bool,
200 mock_block_status_receivers: Vec<BlockStatusReceiver>,
201) -> Arc<ConsensusAdapter> {
202 make_consensus_adapter_for_test_with_submit_limit(
203 state,
204 process_via_checkpoint,
205 execute,
206 mock_block_status_receivers,
207 100_000,
208 )
209}
210
211pub fn make_consensus_adapter_for_test_with_submit_limit(
212 state: Arc<AuthorityState>,
213 process_via_checkpoint: HashSet<TransactionDigest>,
214 execute: bool,
215 mock_block_status_receivers: Vec<BlockStatusReceiver>,
216 max_pending_local_submissions: usize,
217) -> Arc<ConsensusAdapter> {
218 #[derive(Clone)]
219 struct SubmitDirectly {
220 state: Arc<AuthorityState>,
221 process_via_checkpoint: HashSet<TransactionDigest>,
222 execute: bool,
223 mock_block_status_receivers: Arc<Mutex<Vec<BlockStatusReceiver>>>,
224 }
225
226 #[async_trait::async_trait]
227 impl ConsensusClient for SubmitDirectly {
228 async fn submit(
229 &self,
230 transactions: &[ConsensusTransaction],
231 epoch_store: &Arc<AuthorityPerEpochStore>,
232 ) -> SuiResult<(Vec<ConsensusPosition>, BlockStatusReceiver)> {
233 if transactions.is_empty() {
235 return Ok((
236 vec![ConsensusPosition::ping(epoch_store.epoch(), BlockRef::MIN)],
237 with_block_status(BlockStatus::Sequenced(BlockRef::MIN)),
238 ));
239 }
240
241 let num_transactions = transactions.len();
242 let mut executed_via_checkpoint = 0;
243
244 for txn in transactions {
246 if let ConsensusTransactionKind::UserTransactionV2(tx) = &txn.kind {
247 let transaction_digest = tx.tx().digest();
248 if self.process_via_checkpoint.contains(transaction_digest) {
249 epoch_store
250 .insert_finalized_transactions(vec![*transaction_digest].as_slice(), 10)
251 .expect("Should not fail");
252 executed_via_checkpoint += 1;
253 }
254 }
255 }
256
257 let sequenced_transactions: Vec<SequencedConsensusTransaction> = transactions
258 .iter()
259 .map(|txn| SequencedConsensusTransaction::new_test(txn.clone()))
260 .collect();
261
262 let keys = sequenced_transactions
263 .iter()
264 .map(|tx| tx.key())
265 .collect::<Vec<_>>();
266
267 if self.execute {
269 for tx in sequenced_transactions {
270 if let Some(transaction_digest) = tx.transaction.executable_transaction_digest()
271 {
272 if self.process_via_checkpoint.contains(&transaction_digest) {
274 continue;
275 }
276
277 let executable_tx = match &tx.transaction {
279 SequencedConsensusTransactionKind::External(ext) => match &ext.kind {
280 ConsensusTransactionKind::UserTransactionV2(tx) => {
281 Some(VerifiedExecutableTransaction::new_from_consensus(
282 VerifiedTransaction::new_unchecked(tx.tx().clone()),
283 0,
284 ))
285 }
286 _ => None,
287 },
288 SequencedConsensusTransactionKind::System(sys_tx) => {
289 Some(sys_tx.clone())
290 }
291 };
292
293 if let Some(exec_tx) = executable_tx {
294 let versions = epoch_store.assign_shared_object_versions_for_tests(
295 self.state.get_object_cache_reader().as_ref(),
296 std::slice::from_ref(&exec_tx),
297 )?;
298
299 let assigned_version = versions
300 .into_map()
301 .into_iter()
302 .next()
303 .map(|(_, v)| v)
304 .unwrap_or_default();
305
306 self.state.execution_scheduler().enqueue(
307 vec![(
308 Schedulable::Transaction(exec_tx),
309 ExecutionEnv::new().with_assigned_versions(assigned_version),
310 )],
311 epoch_store,
312 );
313 }
314 }
315 }
316 }
317
318 epoch_store.process_notifications(keys.iter());
319
320 assert_eq!(
321 executed_via_checkpoint,
322 self.process_via_checkpoint.len(),
323 "Some transactions were not executed via checkpoint"
324 );
325
326 assert!(
327 !self.mock_block_status_receivers.lock().is_empty(),
328 "No mock submit responses left"
329 );
330
331 let mut consensus_positions = Vec::new();
332 for index in 0..num_transactions {
333 consensus_positions.push(ConsensusPosition {
334 epoch: epoch_store.epoch(),
335 index: index as u16,
336 block: BlockRef::MIN,
337 });
338 }
339
340 Ok((
341 consensus_positions,
342 self.mock_block_status_receivers.lock().remove(0),
343 ))
344 }
345 }
346 let client = Arc::new(SubmitDirectly {
348 state: state.clone(),
349 process_via_checkpoint,
350 execute,
351 mock_block_status_receivers: Arc::new(Mutex::new(mock_block_status_receivers)),
352 });
353 make_consensus_adapter_with_client_for_test(&state, client, max_pending_local_submissions)
354}
355
356pub async fn setup_consensus_handler_for_testing_with_checkpoint_service<C>(
358 authority: &Arc<AuthorityState>,
359 checkpoint_service: Arc<C>,
360) -> TestConsensusHandlerSetup<C>
361where
362 C: Send + Sync + 'static,
363{
364 let epoch_store = authority.epoch_store_for_testing();
365 let consensus_committee = epoch_store.epoch_start_state().get_consensus_committee();
366 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
367 let throughput_calculator = ConsensusThroughputCalculator::new(None, metrics.clone());
368 let backpressure_manager = BackpressureManager::new_for_tests();
369 let last_consensus_stats = ExecutionIndicesWithStatsV2 {
370 stats: crate::authority::authority_per_epoch_store::ConsensusStats::new(
371 consensus_committee.size(),
372 ),
373 ..Default::default()
374 };
375
376 let captured_transactions: CapturedTransactions = Arc::new(Mutex::new(Vec::new()));
377 let captured_tx_clone = captured_transactions.clone();
378
379 let (tx_sender, mut receiver) =
380 mysten_metrics::monitored_mpsc::unbounded_channel("test_execution_scheduler");
381
382 tokio::spawn(async move {
383 while let Some(item) = receiver.recv().await {
384 captured_tx_clone.lock().push(item);
385 }
386 });
387
388 let execution_scheduler_sender = ExecutionSchedulerSender::new_for_testing(tx_sender);
389
390 let consensus_handler = ConsensusHandler::new_for_testing(
391 epoch_store.clone(),
392 checkpoint_service,
393 execution_scheduler_sender,
394 authority.get_object_cache_reader().clone(),
395 consensus_committee,
396 metrics.clone(),
397 Arc::new(throughput_calculator),
398 backpressure_manager.subscribe(),
399 authority.traffic_controller.clone(),
400 authority.transaction_deny_config_manager().clone(),
401 last_consensus_stats,
402 );
403
404 TestConsensusHandlerSetup {
405 consensus_handler,
406 captured_transactions,
407 metrics,
408 }
409}
410
411#[cfg(test)]
413pub async fn setup_consensus_handler_for_testing(
414 authority: &Arc<AuthorityState>,
415) -> TestConsensusHandlerSetup<crate::checkpoints::CheckpointServiceNoop> {
416 setup_consensus_handler_for_testing_with_checkpoint_service(
417 authority,
418 Arc::new(crate::checkpoints::CheckpointServiceNoop {}),
419 )
420 .await
421}