1use std::{
5 collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
6 hash::Hash,
7 num::NonZeroUsize,
8 sync::{Arc, Mutex},
9 time::{Duration, SystemTime, UNIX_EPOCH},
10};
11
12use consensus_config::Committee as ConsensusCommittee;
13use consensus_core::{CommitIndex, CommitRef, ConsumerMonitor};
14use consensus_types::block::BlockRef;
15use consensus_types::block::TransactionIndex;
16use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
17use lru::LruCache;
18use mysten_common::{
19 assert_reachable, assert_sometimes, debug_fatal, random_util::randomize_cache_capacity_in_tests,
20};
21use mysten_metrics::{
22 monitored_future,
23 monitored_mpsc::{self, UnboundedReceiver},
24 monitored_scope, spawn_monitored_task,
25};
26use parking_lot::RwLockWriteGuard;
27use serde::{Deserialize, Serialize};
28use sui_config::node::CongestionLogConfig;
29use sui_macros::{fail_point, fail_point_arg, fail_point_if};
30use sui_protocol_config::{
31 Chain, PerObjectCongestionControlMode, ProtocolConfig, assert_reachable_gated,
32};
33use sui_types::{
34 authenticator_state::ActiveJwk,
35 base_types::{
36 AuthorityName, ConciseableName, ConsensusObjectSequenceKey, ObjectID, ObjectRef,
37 SequenceNumber, TransactionDigest,
38 },
39 crypto::RandomnessRound,
40 digests::{AdditionalConsensusStateDigest, ConsensusCommitDigest, Digest},
41 executable_transaction::{
42 TrustedExecutableTransaction, VerifiedExecutableTransaction,
43 VerifiedExecutableTransactionWithAliases,
44 },
45 messages_checkpoint::{
46 CheckpointSequenceNumber, CheckpointSignatureMessage, CheckpointTimestamp,
47 },
48 messages_consensus::{
49 AuthorityCapabilitiesV2, AuthorityIndex, ConsensusDeterminedVersionAssignments,
50 ConsensusPosition, ConsensusTransaction, ConsensusTransactionKey, ConsensusTransactionKind,
51 ExecutionTimeObservation, SharedTransactionDenyConfig,
52 },
53 sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait,
54 transaction::{
55 InputObjectKind, PlainTransactionWithClaims, SenderSignedData, TransactionDataAPI,
56 TransactionKey, VerifiedTransaction, WithAliases, paid_proposer_count,
57 },
58};
59use tokio::task::JoinSet;
60use tracing::{debug, error, info, instrument, trace, warn};
61
62use crate::{
63 authority::{
64 AuthorityMetrics, AuthorityState, ExecutionEnv,
65 authority_per_epoch_store::{
66 AuthorityPerEpochStore, CancelConsensusCertificateReason, ConsensusStats,
67 ConsensusStatsAPI, ExecutionIndices, ExecutionIndicesWithStatsV2,
68 consensus_quarantine::ConsensusCommitOutput,
69 },
70 backpressure::{BackpressureManager, BackpressureSubscriber},
71 congestion_log::CongestionCommitLogger,
72 consensus_tx_status_cache::ConsensusTxStatus,
73 execution_time_estimator::ExecutionTimeEstimator,
74 shared_object_congestion_tracker::SharedObjectCongestionTracker,
75 shared_object_version_manager::{AssignedTxAndVersions, AssignedVersions, Schedulable},
76 transaction_deferral::{DeferralKey, DeferralReason, transaction_deferral_within_limit},
77 },
78 checkpoints::{
79 CheckpointHeight, CheckpointRoots, CheckpointService, CheckpointServiceNotify,
80 PendingCheckpoint, PendingCheckpointInfo,
81 },
82 consensus_throughput_calculator::ConsensusThroughputCalculator,
83 consensus_types::consensus_output_api::{ConsensusCommitAPI, ParsedTransaction},
84 epoch::{
85 randomness::{AdvanceDkgError, DkgStatus, RandomnessManager},
86 reconfiguration::ReconfigState,
87 },
88 execution_cache::ObjectCacheRead,
89 execution_scheduler::{SettlementBatchInfo, SettlementScheduler},
90 gasless_rate_limiter::ConsensusGaslessCounter,
91 post_consensus_tx_reorder::PostConsensusTxReorder,
92 traffic_controller::{TrafficController, policies::TrafficTally},
93 transaction_deny_config_manager::TransactionDenyConfigManager,
94};
95
96#[derive(Default)]
98struct ConflictInfo {
99 gas_object_conflicts: u64,
101 non_gas_object_conflicts: u64,
103 winner_author: usize,
105}
106
107struct FilteredConsensusOutput {
110 transactions: Vec<(SequencedConsensusTransactionKind, u32)>,
111 owned_object_locks: HashMap<ObjectRef, TransactionDigest>,
112 dropped_transaction_keys: Vec<ConsensusTransactionKey>,
113 contested_transaction_digests: HashMap<TransactionDigest, ConflictInfo>,
116}
117
118pub struct ConsensusHandlerInitializer {
119 state: Arc<AuthorityState>,
120 checkpoint_service: Arc<CheckpointService>,
121 epoch_store: Arc<AuthorityPerEpochStore>,
122 throughput_calculator: Arc<ConsensusThroughputCalculator>,
123 backpressure_manager: Arc<BackpressureManager>,
124 congestion_logger: Option<Arc<Mutex<CongestionCommitLogger>>>,
125 consensus_gasless_counter: Arc<ConsensusGaslessCounter>,
126}
127
128impl ConsensusHandlerInitializer {
129 pub fn new(
130 state: Arc<AuthorityState>,
131 checkpoint_service: Arc<CheckpointService>,
132 epoch_store: Arc<AuthorityPerEpochStore>,
133 throughput_calculator: Arc<ConsensusThroughputCalculator>,
134 backpressure_manager: Arc<BackpressureManager>,
135 congestion_log_config: Option<CongestionLogConfig>,
136 ) -> Self {
137 let congestion_logger =
138 congestion_log_config.and_then(|config| match CongestionCommitLogger::new(&config) {
139 Ok(logger) => Some(Arc::new(Mutex::new(logger))),
140 Err(e) => {
141 debug_fatal!("Failed to create congestion logger: {e}");
142 None
143 }
144 });
145 let consensus_gasless_counter = state.consensus_gasless_counter.clone();
146 Self {
147 state,
148 checkpoint_service,
149 epoch_store,
150 throughput_calculator,
151 backpressure_manager,
152 congestion_logger,
153 consensus_gasless_counter,
154 }
155 }
156
157 #[cfg(test)]
158 pub(crate) fn new_for_testing(
159 state: Arc<AuthorityState>,
160 checkpoint_service: Arc<CheckpointService>,
161 ) -> Self {
162 let backpressure_manager = BackpressureManager::new_for_tests();
163 let consensus_gasless_counter = state.consensus_gasless_counter.clone();
164 Self {
165 state: state.clone(),
166 checkpoint_service,
167 epoch_store: state.epoch_store_for_testing().clone(),
168 throughput_calculator: Arc::new(ConsensusThroughputCalculator::new(
169 None,
170 state.metrics.clone(),
171 )),
172 backpressure_manager,
173 congestion_logger: None,
174 consensus_gasless_counter,
175 }
176 }
177
178 pub(crate) fn new_consensus_handler(&self) -> ConsensusHandler<CheckpointService> {
179 let new_epoch_start_state = self.epoch_store.epoch_start_state();
180 let consensus_committee = new_epoch_start_state.get_consensus_committee();
181
182 let settlement_scheduler = SettlementScheduler::new(
183 self.state.execution_scheduler().as_ref().clone(),
184 self.state.get_transaction_cache_reader().clone(),
185 self.state.metrics.clone(),
186 );
187 ConsensusHandler::new(
188 self.epoch_store.clone(),
189 self.checkpoint_service.clone(),
190 settlement_scheduler,
191 self.state.get_object_cache_reader().clone(),
192 consensus_committee,
193 self.state.metrics.clone(),
194 self.throughput_calculator.clone(),
195 self.backpressure_manager.subscribe(),
196 self.state.traffic_controller.clone(),
197 self.congestion_logger.clone(),
198 self.consensus_gasless_counter.clone(),
199 self.state.transaction_deny_config_manager().clone(),
200 )
201 }
202}
203
204mod additional_consensus_state {
205 use std::marker::PhantomData;
206
207 use consensus_core::CommitRef;
208 use fastcrypto::hash::HashFunction as _;
209 use sui_types::{crypto::DefaultHash, digests::Digest};
210
211 use super::*;
212 #[derive(Serialize, Deserialize)]
221 pub(super) struct AdditionalConsensusState {
222 commit_interval_observer: CommitIntervalObserver,
223 }
224
225 impl AdditionalConsensusState {
226 pub fn new(additional_consensus_state_window_size: u32) -> Self {
227 Self {
228 commit_interval_observer: CommitIntervalObserver::new(
229 additional_consensus_state_window_size,
230 ),
231 }
232 }
233
234 pub(crate) fn observe_commit(
236 &mut self,
237 protocol_config: &ProtocolConfig,
238 epoch_start_time: u64,
239 consensus_commit: &impl ConsensusCommitAPI,
240 ) -> ConsensusCommitInfo {
241 self.commit_interval_observer
242 .observe_commit_time(consensus_commit);
243
244 let estimated_commit_period = self
245 .commit_interval_observer
246 .commit_interval_estimate()
247 .unwrap_or(Duration::from_millis(
248 protocol_config.min_checkpoint_interval_ms(),
249 ));
250
251 info!("estimated commit rate: {:?}", estimated_commit_period);
252
253 self.commit_info_impl(
254 epoch_start_time,
255 consensus_commit,
256 Some(estimated_commit_period),
257 )
258 }
259
260 fn commit_info_impl(
261 &self,
262 epoch_start_time: u64,
263 consensus_commit: &impl ConsensusCommitAPI,
264 estimated_commit_period: Option<Duration>,
265 ) -> ConsensusCommitInfo {
266 let leader_author = consensus_commit.leader_author_index();
267 let timestamp = consensus_commit.commit_timestamp_ms();
268
269 let timestamp = if timestamp < epoch_start_time {
270 error!(
271 "Unexpected commit timestamp {timestamp} less then epoch start time {epoch_start_time}, author {leader_author:?}"
272 );
273 epoch_start_time
274 } else {
275 timestamp
276 };
277
278 ConsensusCommitInfo {
279 _phantom: PhantomData,
280 round: consensus_commit.leader_round(),
281 timestamp,
282 leader_author,
283 consensus_commit_ref: consensus_commit.commit_ref(),
284 rejected_transactions_digest: consensus_commit.rejected_transactions_digest(),
285 additional_state_digest: Some(self.digest()),
286 estimated_commit_period,
287 skip_consensus_commit_prologue_in_test: false,
288 }
289 }
290
291 fn digest(&self) -> AdditionalConsensusStateDigest {
293 let mut hash = DefaultHash::new();
294 bcs::serialize_into(&mut hash, self).unwrap();
295 AdditionalConsensusStateDigest::new(hash.finalize().into())
296 }
297 }
298
299 pub struct ConsensusCommitInfo {
300 _phantom: PhantomData<()>,
302
303 pub round: u64,
304 pub timestamp: u64,
305 pub leader_author: AuthorityIndex,
306 pub consensus_commit_ref: CommitRef,
307 pub rejected_transactions_digest: Digest,
308
309 additional_state_digest: Option<AdditionalConsensusStateDigest>,
310 estimated_commit_period: Option<Duration>,
311
312 pub skip_consensus_commit_prologue_in_test: bool,
313 }
314
315 impl ConsensusCommitInfo {
316 pub fn new_for_test(
317 commit_round: u64,
318 commit_timestamp: u64,
319 estimated_commit_period: Option<Duration>,
320 skip_consensus_commit_prologue_in_test: bool,
321 ) -> Self {
322 Self {
323 _phantom: PhantomData,
324 round: commit_round,
325 timestamp: commit_timestamp,
326 leader_author: 0,
327 consensus_commit_ref: CommitRef::default(),
328 rejected_transactions_digest: Digest::default(),
329 additional_state_digest: Some(AdditionalConsensusStateDigest::ZERO),
330 estimated_commit_period,
331 skip_consensus_commit_prologue_in_test,
332 }
333 }
334
335 pub fn new_for_congestion_test(
336 commit_round: u64,
337 commit_timestamp: u64,
338 estimated_commit_period: Duration,
339 ) -> Self {
340 Self::new_for_test(
341 commit_round,
342 commit_timestamp,
343 Some(estimated_commit_period),
344 true,
345 )
346 }
347
348 pub fn additional_state_digest(&self) -> AdditionalConsensusStateDigest {
349 self.additional_state_digest
351 .expect("additional_state_digest is not available")
352 }
353
354 pub fn estimated_commit_period(&self) -> Duration {
355 self.estimated_commit_period
357 .expect("estimated commit period is not available")
358 }
359
360 fn consensus_commit_digest(&self) -> ConsensusCommitDigest {
361 ConsensusCommitDigest::new(self.consensus_commit_ref.digest.into_inner())
362 }
363
364 fn consensus_commit_prologue_v4_transaction(
365 &self,
366 epoch: u64,
367 consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
368 additional_state_digest: AdditionalConsensusStateDigest,
369 ) -> VerifiedExecutableTransaction {
370 let transaction = VerifiedTransaction::new_consensus_commit_prologue_v4(
371 epoch,
372 self.round,
373 self.timestamp,
374 self.consensus_commit_digest(),
375 consensus_determined_version_assignments,
376 additional_state_digest,
377 );
378 VerifiedExecutableTransaction::new_system(transaction, epoch)
379 }
380
381 pub fn create_consensus_commit_prologue_transaction(
382 &self,
383 epoch: u64,
384 cancelled_txn_version_assignment: Vec<(
385 TransactionDigest,
386 Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
387 )>,
388 indirect_state_observer: IndirectStateObserver,
389 ) -> VerifiedExecutableTransaction {
390 let version_assignments =
391 ConsensusDeterminedVersionAssignments::CancelledTransactionsV2(
392 cancelled_txn_version_assignment,
393 );
394 let additional_state_digest =
395 indirect_state_observer.fold_with(self.additional_state_digest());
396
397 self.consensus_commit_prologue_v4_transaction(
398 epoch,
399 version_assignments,
400 additional_state_digest,
401 )
402 }
403 }
404
405 #[derive(Default)]
406 pub struct IndirectStateObserver {
407 hash: DefaultHash,
408 }
409
410 impl IndirectStateObserver {
411 pub fn new() -> Self {
412 Self::default()
413 }
414
415 pub fn observe_indirect_state<T: Serialize>(&mut self, state: &T) {
416 bcs::serialize_into(&mut self.hash, state).unwrap();
417 }
418
419 pub fn fold_with(
420 self,
421 d1: AdditionalConsensusStateDigest,
422 ) -> AdditionalConsensusStateDigest {
423 let hash = self.hash.finalize();
424 let d2 = AdditionalConsensusStateDigest::new(hash.into());
425
426 let mut hasher = DefaultHash::new();
427 bcs::serialize_into(&mut hasher, &d1).unwrap();
428 bcs::serialize_into(&mut hasher, &d2).unwrap();
429 AdditionalConsensusStateDigest::new(hasher.finalize().into())
430 }
431 }
432
433 #[test]
434 fn test_additional_consensus_state() {
435 use crate::consensus_test_utils::TestConsensusCommit;
436
437 fn observe(state: &mut AdditionalConsensusState, round: u64, timestamp: u64) {
438 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
439 state.observe_commit(
440 &protocol_config,
441 100,
442 &TestConsensusCommit::empty(round, timestamp, 0),
443 );
444 }
445
446 let mut s1 = AdditionalConsensusState::new(3);
447 observe(&mut s1, 1, 1000);
448 observe(&mut s1, 2, 2000);
449 observe(&mut s1, 3, 3000);
450 observe(&mut s1, 4, 4000);
451
452 let mut s2 = AdditionalConsensusState::new(3);
453 observe(&mut s2, 2, 2000);
456 observe(&mut s2, 3, 3000);
457 observe(&mut s2, 4, 4000);
458
459 assert_eq!(s1.digest(), s2.digest());
460
461 observe(&mut s1, 5, 5000);
462 observe(&mut s2, 5, 5000);
463
464 assert_eq!(s1.digest(), s2.digest());
465 }
466}
467use additional_consensus_state::AdditionalConsensusState;
468pub(crate) use additional_consensus_state::{ConsensusCommitInfo, IndirectStateObserver};
469
470struct QueuedCheckpointRoots {
471 roots: CheckpointRoots,
472 timestamp: CheckpointTimestamp,
473 consensus_commit_ref: CommitRef,
474 rejected_transactions_digest: Digest,
475}
476
477struct Chunk<
478 T: crate::authority::shared_object_version_manager::AsTx = VerifiedExecutableTransaction,
479> {
480 schedulables: Vec<Schedulable<T>>,
481 settlement: Option<Schedulable<T>>,
482 height: CheckpointHeight,
483}
484
485impl<T: crate::authority::shared_object_version_manager::AsTx + Clone> Chunk<T> {
486 fn all_schedulables(&self) -> impl Iterator<Item = &Schedulable<T>> + Clone {
487 self.schedulables.iter().chain(self.settlement.iter())
488 }
489
490 fn all_schedulables_from(chunks: &[Self]) -> impl Iterator<Item = &Schedulable<T>> + Clone {
491 chunks.iter().flat_map(|c| c.all_schedulables())
492 }
493
494 fn to_checkpoint_roots(&self) -> CheckpointRoots {
495 let tx_roots: Vec<_> = self.schedulables.iter().map(|s| s.key()).collect();
496 let settlement_root = self.settlement.as_ref().map(|s| s.key());
497 CheckpointRoots {
498 tx_roots,
499 settlement_root,
500 height: self.height,
501 }
502 }
503}
504
505impl From<Chunk<VerifiedExecutableTransactionWithAliases>> for Chunk {
506 fn from(chunk: Chunk<VerifiedExecutableTransactionWithAliases>) -> Self {
507 Chunk {
508 schedulables: chunk.schedulables.into_iter().map(|s| s.into()).collect(),
509 settlement: chunk.settlement.map(|s| s.into()),
510 height: chunk.height,
511 }
512 }
513}
514
515pub(crate) struct CheckpointQueue {
523 last_built_timestamp: CheckpointTimestamp,
524 pending_roots: VecDeque<QueuedCheckpointRoots>,
525 height: u64,
526 pending_tx_count: usize,
527 current_checkpoint_seq: CheckpointSequenceNumber,
528 max_tx: usize,
529 min_checkpoint_interval_ms: u64,
530 execution_scheduler_sender: ExecutionSchedulerSender,
531}
532
533impl CheckpointQueue {
534 pub(crate) fn new(
535 last_built_timestamp: CheckpointTimestamp,
536 checkpoint_height: u64,
537 next_checkpoint_seq: CheckpointSequenceNumber,
538 max_tx: usize,
539 min_checkpoint_interval_ms: u64,
540 execution_scheduler_sender: ExecutionSchedulerSender,
541 ) -> Self {
542 Self {
543 last_built_timestamp,
544 pending_roots: VecDeque::new(),
545 height: checkpoint_height,
546 pending_tx_count: 0,
547 current_checkpoint_seq: next_checkpoint_seq,
548 max_tx,
549 min_checkpoint_interval_ms,
550 execution_scheduler_sender,
551 }
552 }
553
554 #[cfg(test)]
555 fn new_for_testing(
556 last_built_timestamp: CheckpointTimestamp,
557 checkpoint_height: u64,
558 next_checkpoint_seq: CheckpointSequenceNumber,
559 max_tx: usize,
560 min_checkpoint_interval_ms: u64,
561 ) -> Self {
562 let (sender, _receiver) = monitored_mpsc::unbounded_channel("test_checkpoint_queue_sender");
563 Self {
564 last_built_timestamp,
565 pending_roots: VecDeque::new(),
566 height: checkpoint_height,
567 pending_tx_count: 0,
568 current_checkpoint_seq: next_checkpoint_seq,
569 max_tx,
570 min_checkpoint_interval_ms,
571 execution_scheduler_sender: ExecutionSchedulerSender::new_for_testing(sender),
572 }
573 }
574
575 #[cfg(test)]
576 fn new_for_testing_with_sender(
577 last_built_timestamp: CheckpointTimestamp,
578 checkpoint_height: u64,
579 next_checkpoint_seq: CheckpointSequenceNumber,
580 max_tx: usize,
581 min_checkpoint_interval_ms: u64,
582 sender: monitored_mpsc::UnboundedSender<SchedulerMessage>,
583 ) -> Self {
584 Self {
585 last_built_timestamp,
586 pending_roots: VecDeque::new(),
587 height: checkpoint_height,
588 pending_tx_count: 0,
589 current_checkpoint_seq: next_checkpoint_seq,
590 max_tx,
591 min_checkpoint_interval_ms,
592 execution_scheduler_sender: ExecutionSchedulerSender::new_for_testing(sender),
593 }
594 }
595
596 pub(crate) fn last_built_timestamp(&self) -> CheckpointTimestamp {
597 self.last_built_timestamp
598 }
599
600 pub(crate) fn is_empty(&self) -> bool {
601 self.pending_roots.is_empty()
602 }
603
604 fn next_height(&mut self) -> u64 {
605 self.height += 1;
606 self.height
607 }
608
609 fn push_chunk(
610 &mut self,
611 chunk: Chunk,
612 assigned_versions: &HashMap<TransactionKey, AssignedVersions>,
613 timestamp: CheckpointTimestamp,
614 consensus_commit_ref: CommitRef,
615 rejected_transactions_digest: Digest,
616 ) -> Vec<PendingCheckpoint> {
617 let max_tx = self.max_tx;
618 let user_tx_count = chunk.schedulables.len();
619
620 let roots = chunk.to_checkpoint_roots();
621
622 let schedulables: Vec<_> = chunk
623 .schedulables
624 .into_iter()
625 .map(|s| {
626 let versions = assigned_versions
627 .get(&s.key())
628 .cloned()
629 .unwrap_or_else(AssignedVersions::empty);
630 (s, versions)
631 })
632 .collect();
633
634 let mut flushed_checkpoints = Vec::new();
635
636 if self.pending_tx_count > 0
637 && self.pending_tx_count + user_tx_count > max_tx
638 && let Some(checkpoint) = self.flush_forced()
639 {
640 flushed_checkpoints.push(checkpoint);
641 }
642
643 let settlement_info = chunk.settlement.as_ref().map(|s| {
644 let settlement_key = s.key();
645 let tx_keys: Vec<_> = schedulables.iter().map(|(s, _)| s.key()).collect();
646 SettlementBatchInfo {
647 settlement_key,
648 tx_keys,
649 checkpoint_height: chunk.height,
650 checkpoint_seq: self.current_checkpoint_seq,
651 assigned_versions: assigned_versions
652 .get(&settlement_key)
653 .cloned()
654 .unwrap_or_else(AssignedVersions::empty),
655 }
656 });
657
658 self.execution_scheduler_sender
659 .send(schedulables, settlement_info);
660
661 self.pending_tx_count += user_tx_count;
662 self.pending_roots.push_back(QueuedCheckpointRoots {
663 roots,
664 timestamp,
665 consensus_commit_ref,
666 rejected_transactions_digest,
667 });
668
669 flushed_checkpoints
670 }
671
672 pub(crate) fn flush(
673 &mut self,
674 current_timestamp: CheckpointTimestamp,
675 force: bool,
676 ) -> Option<PendingCheckpoint> {
677 if !force && current_timestamp < self.last_built_timestamp + self.min_checkpoint_interval_ms
678 {
679 return None;
680 }
681 self.flush_forced()
682 }
683
684 fn flush_forced(&mut self) -> Option<PendingCheckpoint> {
685 if self.pending_roots.is_empty() {
686 return None;
687 }
688
689 let to_flush: Vec<_> = self.pending_roots.drain(..).collect();
690 let last_root = to_flush.last().unwrap();
691
692 let checkpoint = PendingCheckpoint {
693 roots: to_flush.iter().map(|q| q.roots.clone()).collect(),
694 details: PendingCheckpointInfo {
695 timestamp_ms: last_root.timestamp,
696 last_of_epoch: false,
697 checkpoint_height: last_root.roots.height,
698 consensus_commit_ref: last_root.consensus_commit_ref,
699 rejected_transactions_digest: last_root.rejected_transactions_digest,
700 checkpoint_seq: self.current_checkpoint_seq,
701 },
702 };
703
704 self.last_built_timestamp = last_root.timestamp;
705 self.pending_tx_count = 0;
706 self.current_checkpoint_seq += 1;
707
708 Some(checkpoint)
709 }
710
711 pub(crate) fn checkpoint_seq(&self) -> CheckpointSequenceNumber {
712 self.current_checkpoint_seq
713 .checked_sub(1)
714 .expect("checkpoint_seq called before any checkpoint was assigned")
715 }
716}
717
718#[derive(Debug)]
720pub(crate) struct RuntimeShutdown;
721
722pub struct ConsensusHandler<C> {
723 epoch_store: Arc<AuthorityPerEpochStore>,
726 last_consensus_stats: ExecutionIndicesWithStatsV2,
730 checkpoint_service: Arc<C>,
731 cache_reader: Arc<dyn ObjectCacheRead>,
733 committee: ConsensusCommittee,
735 metrics: Arc<AuthorityMetrics>,
738 processed_cache: LruCache<SequencedConsensusTransactionKey, u64>,
743 throughput_calculator: Arc<ConsensusThroughputCalculator>,
745
746 additional_consensus_state: AdditionalConsensusState,
747
748 backpressure_subscriber: BackpressureSubscriber,
749
750 traffic_controller: Option<Arc<TrafficController>>,
751
752 congestion_logger: Option<Arc<Mutex<CongestionCommitLogger>>>,
753
754 consensus_gasless_counter: Arc<ConsensusGaslessCounter>,
755
756 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
757
758 checkpoint_queue: Mutex<CheckpointQueue>,
759}
760
761const PROCESSED_CACHE_CAP: usize = 1024 * 1024;
762
763fn assert_supported_protocol_config(protocol_config: &ProtocolConfig) {
764 assert!(
765 matches!(
766 protocol_config.per_object_congestion_control_mode(),
767 PerObjectCongestionControlMode::ExecutionTimeEstimate(_)
768 ),
769 "support for congestion control modes other than PerObjectCongestionControlMode::ExecutionTimeEstimate has been removed"
770 );
771 assert!(
772 protocol_config.split_checkpoints_in_consensus_handler(),
773 "support for splitting checkpoints outside of consensus handler has been removed"
774 );
775 assert!(protocol_config.ignore_execution_time_observations_after_certs_closed());
776 assert!(protocol_config.record_time_estimate_processed());
777 assert!(protocol_config.prepend_prologue_tx_in_consensus_commit_in_checkpoints());
778 assert!(protocol_config.consensus_checkpoint_signature_key_includes_digest());
779 assert!(protocol_config.authority_capabilities_v2());
780 assert!(protocol_config.cancel_for_failed_dkg_early());
781 assert!(protocol_config.record_consensus_determined_version_assignments_in_prologue_v2());
782 assert!(protocol_config.record_additional_state_digest_in_prologue());
783 assert!(protocol_config.additional_consensus_digest_indirect_state());
784 assert!(protocol_config.include_cancelled_randomness_txns_in_prologue());
785 assert!(protocol_config.fix_checkpoint_signature_mapping());
786 assert!(protocol_config.merge_randomness_into_checkpoint());
787 assert!(
788 protocol_config.timestamp_based_epoch_close(),
789 "support for non-timestamp-based epoch close has been removed"
790 );
791}
792
793impl<C> ConsensusHandler<C> {
794 pub(crate) fn new(
795 epoch_store: Arc<AuthorityPerEpochStore>,
796 checkpoint_service: Arc<C>,
797 settlement_scheduler: SettlementScheduler,
798 cache_reader: Arc<dyn ObjectCacheRead>,
799 committee: ConsensusCommittee,
800 metrics: Arc<AuthorityMetrics>,
801 throughput_calculator: Arc<ConsensusThroughputCalculator>,
802 backpressure_subscriber: BackpressureSubscriber,
803 traffic_controller: Option<Arc<TrafficController>>,
804 congestion_logger: Option<Arc<Mutex<CongestionCommitLogger>>>,
805 consensus_gasless_counter: Arc<ConsensusGaslessCounter>,
806 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
807 ) -> Self {
808 assert_supported_protocol_config(epoch_store.protocol_config());
809
810 let mut last_consensus_stats = epoch_store
812 .get_last_consensus_stats()
813 .expect("Should be able to read last consensus index");
814 if !last_consensus_stats.stats.is_initialized() {
816 last_consensus_stats.stats = ConsensusStats::new(committee.size());
817 last_consensus_stats.checkpoint_seq = epoch_store.previous_epoch_last_checkpoint();
818 }
819 let max_tx = epoch_store
820 .protocol_config()
821 .max_transactions_per_checkpoint() as usize;
822 let min_checkpoint_interval_ms = epoch_store
823 .protocol_config()
824 .min_checkpoint_interval_ms_as_option()
825 .unwrap_or_default();
826 let execution_scheduler_sender =
827 ExecutionSchedulerSender::start(settlement_scheduler, epoch_store.clone());
828 let commit_rate_estimate_window_size = epoch_store
829 .protocol_config()
830 .get_consensus_commit_rate_estimation_window_size();
831 let last_built_timestamp = last_consensus_stats.last_checkpoint_flush_timestamp;
832 let checkpoint_height = last_consensus_stats.height;
833 let next_checkpoint_seq = last_consensus_stats.checkpoint_seq + 1;
834 Self {
835 epoch_store,
836 last_consensus_stats,
837 checkpoint_service,
838 cache_reader,
839 committee,
840 metrics,
841 processed_cache: LruCache::new(
842 NonZeroUsize::new(randomize_cache_capacity_in_tests(PROCESSED_CACHE_CAP)).unwrap(),
843 ),
844 throughput_calculator,
845 additional_consensus_state: AdditionalConsensusState::new(
846 commit_rate_estimate_window_size,
847 ),
848 backpressure_subscriber,
849 traffic_controller,
850 congestion_logger,
851 consensus_gasless_counter,
852 transaction_deny_config_manager,
853 checkpoint_queue: Mutex::new(CheckpointQueue::new(
854 last_built_timestamp,
855 checkpoint_height,
856 next_checkpoint_seq,
857 max_tx,
858 min_checkpoint_interval_ms,
859 execution_scheduler_sender,
860 )),
861 }
862 }
863
864 pub(crate) fn last_processed_subdag_index(&self) -> u64 {
866 self.last_consensus_stats.index.sub_dag_index
867 }
868
869 pub(crate) fn new_for_testing(
870 epoch_store: Arc<AuthorityPerEpochStore>,
871 checkpoint_service: Arc<C>,
872 execution_scheduler_sender: ExecutionSchedulerSender,
873 cache_reader: Arc<dyn ObjectCacheRead>,
874 committee: ConsensusCommittee,
875 metrics: Arc<AuthorityMetrics>,
876 throughput_calculator: Arc<ConsensusThroughputCalculator>,
877 backpressure_subscriber: BackpressureSubscriber,
878 traffic_controller: Option<Arc<TrafficController>>,
879 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
880 last_consensus_stats: ExecutionIndicesWithStatsV2,
881 ) -> Self {
882 assert_supported_protocol_config(epoch_store.protocol_config());
883
884 let commit_rate_estimate_window_size = epoch_store
885 .protocol_config()
886 .get_consensus_commit_rate_estimation_window_size();
887 let max_tx = epoch_store
888 .protocol_config()
889 .max_transactions_per_checkpoint() as usize;
890 let min_checkpoint_interval_ms = epoch_store
891 .protocol_config()
892 .min_checkpoint_interval_ms_as_option()
893 .unwrap_or_default();
894 let last_built_timestamp = last_consensus_stats.last_checkpoint_flush_timestamp;
895 let checkpoint_height = last_consensus_stats.height;
896 Self {
897 epoch_store,
898 last_consensus_stats,
899 checkpoint_service,
900 cache_reader,
901 committee,
902 metrics,
903 processed_cache: LruCache::new(
904 NonZeroUsize::new(randomize_cache_capacity_in_tests(PROCESSED_CACHE_CAP)).unwrap(),
905 ),
906 throughput_calculator,
907 additional_consensus_state: AdditionalConsensusState::new(
908 commit_rate_estimate_window_size,
909 ),
910 backpressure_subscriber,
911 traffic_controller,
912 congestion_logger: None,
913 consensus_gasless_counter: Arc::new(ConsensusGaslessCounter::default()),
914 transaction_deny_config_manager,
915 checkpoint_queue: Mutex::new(CheckpointQueue::new(
916 last_built_timestamp,
917 checkpoint_height,
918 0,
919 max_tx,
920 min_checkpoint_interval_ms,
921 execution_scheduler_sender,
922 )),
923 }
924 }
925}
926
927#[derive(Default)]
928struct CommitHandlerInput {
929 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
930 capability_notifications: Vec<AuthorityCapabilitiesV2>,
931 execution_time_observations: Vec<ExecutionTimeObservation>,
932 checkpoint_signature_messages: Vec<CheckpointSignatureMessage>,
933 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
934 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
935 end_of_publish_transactions: Vec<AuthorityName>,
936 new_jwks: Vec<(AuthorityName, JwkId, JWK)>,
937 transaction_deny_config_updates: Vec<(AuthorityName, SharedTransactionDenyConfig)>,
938}
939
940#[derive(Default)]
943struct StaggeringCommitCounts {
944 excess_copies: u64,
948 unique_user_txns: u64,
950 paid_duplicate_copies: u64,
952 restricted_duplicate_copies: u64,
954}
955
956impl StaggeringCommitCounts {
957 fn by_kind(&self) -> [(&'static str, u64); 4] {
959 [
960 ("unique", self.unique_user_txns),
961 ("excess", self.excess_copies),
962 ("paid_duplicate", self.paid_duplicate_copies),
963 ("restricted_duplicate", self.restricted_duplicate_copies),
964 ]
965 }
966}
967
968struct CommitHandlerState {
969 dkg_failed: bool,
970 randomness_round: Option<RandomnessRound>,
971 output: ConsensusCommitOutput,
972 indirect_state_observer: Option<IndirectStateObserver>,
973 initial_reconfig_state: ReconfigState,
974 occurrence_counts: HashMap<TransactionDigest, u32>,
977 staggering_counts: StaggeringCommitCounts,
978 contested_transaction_digests: HashMap<TransactionDigest, ConflictInfo>,
981}
982
983impl CommitHandlerState {
984 fn new(epoch_store: &AuthorityPerEpochStore, consensus_round: u64) -> Self {
985 Self {
986 output: ConsensusCommitOutput::new(consensus_round),
987 dkg_failed: false,
988 randomness_round: None,
989 indirect_state_observer: Some(IndirectStateObserver::new()),
990 initial_reconfig_state: epoch_store.get_reconfig_state_read_lock_guard().clone(),
991 occurrence_counts: HashMap::new(),
992 staggering_counts: StaggeringCommitCounts::default(),
993 contested_transaction_digests: HashMap::new(),
994 }
995 }
996
997 fn get_notifications(&self) -> Vec<SequencedConsensusTransactionKey> {
998 self.output
999 .get_consensus_messages_processed()
1000 .cloned()
1001 .collect()
1002 }
1003
1004 fn init_randomness<'a, 'epoch>(
1005 &'a mut self,
1006 epoch_store: &'epoch AuthorityPerEpochStore,
1007 commit_info: &'a ConsensusCommitInfo,
1008 ) -> Option<tokio::sync::MutexGuard<'epoch, RandomnessManager>> {
1009 let mut randomness_manager = epoch_store.randomness_manager.get().map(|rm| {
1010 rm.try_lock()
1011 .expect("should only ever be called from the commit handler thread")
1012 });
1013
1014 let mut dkg_failed = false;
1015 let randomness_round = if epoch_store.randomness_state_enabled() {
1016 let randomness_manager = randomness_manager
1017 .as_mut()
1018 .expect("randomness manager should exist if randomness is enabled");
1019 match randomness_manager.dkg_status_for_commit_round(commit_info.round) {
1020 DkgStatus::Pending => None,
1021 DkgStatus::TimedOut => {
1022 dkg_failed = true;
1023 None
1024 }
1025 DkgStatus::Successful => {
1026 if self.initial_reconfig_state.should_accept_tx() {
1029 randomness_manager
1030 .reserve_next_randomness(commit_info.timestamp, &mut self.output)
1032 .expect("epoch ended")
1033 } else {
1034 None
1035 }
1036 }
1037 }
1038 } else {
1039 None
1040 };
1041
1042 if randomness_round.is_some() {
1043 assert!(!dkg_failed); }
1045
1046 self.randomness_round = randomness_round;
1047 self.dkg_failed = dkg_failed;
1048
1049 randomness_manager
1050 }
1051}
1052
1053struct AbandonedDeferredTxns {
1056 count: usize,
1057 sample: Vec<(DeferralKey, TransactionDigest)>,
1059}
1060
1061impl<C: CheckpointServiceNotify + Send + Sync> ConsensusHandler<C> {
1062 fn handle_prior_consensus_commit(&mut self, consensus_commit: impl ConsensusCommitAPI) {
1066 assert!(
1067 self.epoch_store
1068 .protocol_config()
1069 .record_additional_state_digest_in_prologue()
1070 );
1071 let protocol_config = self.epoch_store.protocol_config();
1072 let epoch_start_time = self
1073 .epoch_store
1074 .epoch_start_config()
1075 .epoch_start_timestamp_ms();
1076
1077 self.additional_consensus_state.observe_commit(
1078 protocol_config,
1079 epoch_start_time,
1080 &consensus_commit,
1081 );
1082 }
1083
1084 #[cfg(test)]
1085 pub(crate) async fn handle_consensus_commit_for_test(
1086 &mut self,
1087 consensus_commit: impl ConsensusCommitAPI,
1088 ) {
1089 let transactions = consensus_commit.transactions();
1090 self.handle_consensus_commit(consensus_commit, transactions)
1091 .await
1092 .expect("runtime shutdown during test commit");
1093 }
1094
1095 #[instrument(level = "debug", skip_all, fields(epoch = self.epoch_store.epoch(), round = consensus_commit.leader_round()))]
1098 pub(crate) async fn handle_consensus_commit(
1099 &mut self,
1100 consensus_commit: impl ConsensusCommitAPI,
1101 transactions: ParsedConsensusTransactions,
1102 ) -> Result<(), RuntimeShutdown> {
1103 self.backpressure_subscriber.await_no_backpressure().await;
1108
1109 let epoch = self.epoch_store.epoch();
1110
1111 let _scope = monitored_scope("ConsensusCommitHandler::handle_consensus_commit");
1112
1113 let last_committed_round = self.last_consensus_stats.index.last_committed_round;
1114
1115 self.epoch_store
1116 .consensus_tx_status_cache
1117 .update_last_committed_leader_round(last_committed_round as u32);
1118 self.epoch_store
1119 .tx_reject_reason_cache
1120 .set_last_committed_leader_round(last_committed_round as u32);
1121
1122 let commit_info = self.additional_consensus_state.observe_commit(
1123 self.epoch_store.protocol_config(),
1124 self.epoch_store
1125 .epoch_start_config()
1126 .epoch_start_timestamp_ms(),
1127 &consensus_commit,
1128 );
1129 assert!(commit_info.round > last_committed_round);
1130
1131 let (timestamp, leader_author, commit_sub_dag_index) =
1132 self.gather_commit_metadata(&consensus_commit);
1133
1134 info!(
1135 %consensus_commit,
1136 "Received consensus output {}. Rejected transactions {}",
1137 consensus_commit.commit_ref(),
1138 consensus_commit.rejected_transactions_debug_string(),
1139 );
1140
1141 self.last_consensus_stats.index = ExecutionIndices {
1142 last_committed_round: commit_info.round,
1143 sub_dag_index: commit_sub_dag_index,
1144 transaction_index: 0_u64,
1145 };
1146
1147 self.metrics
1148 .consensus_committed_subdags
1149 .with_label_values(&[&leader_author.to_string()])
1150 .inc();
1151
1152 let mut state = CommitHandlerState::new(&self.epoch_store, commit_info.round);
1153
1154 let FilteredConsensusOutput {
1155 transactions,
1156 owned_object_locks,
1157 dropped_transaction_keys,
1158 contested_transaction_digests,
1159 } = self.filter_consensus_txns(
1160 state.initial_reconfig_state.clone(),
1161 &commit_info,
1162 transactions,
1163 );
1164 state.contested_transaction_digests = contested_transaction_digests;
1165 if !owned_object_locks.is_empty() {
1167 state.output.set_owned_object_locks(owned_object_locks);
1168 }
1169
1170 for key in dropped_transaction_keys {
1172 state.output.record_consensus_message_processed(
1173 SequencedConsensusTransactionKey::External(key),
1174 );
1175 }
1176 let transactions = self.deduplicate_consensus_txns(&mut state, &commit_info, transactions);
1177
1178 let mut randomness_manager = state.init_randomness(&self.epoch_store, &commit_info);
1179
1180 let CommitHandlerInput {
1181 user_transactions,
1182 capability_notifications,
1183 execution_time_observations,
1184 checkpoint_signature_messages,
1185 randomness_dkg_messages,
1186 randomness_dkg_confirmations,
1187 end_of_publish_transactions,
1188 new_jwks,
1189 transaction_deny_config_updates,
1190 } = self.build_commit_handler_input(transactions);
1191
1192 self.process_gasless_transactions(&commit_info, &user_transactions);
1193 self.process_jwks(&mut state, &commit_info, new_jwks);
1194 self.process_capability_notifications(capability_notifications);
1195 self.process_transaction_deny_config_updates(transaction_deny_config_updates);
1196 self.process_execution_time_observations(&mut state, execution_time_observations);
1197 self.process_checkpoint_signature_messages(checkpoint_signature_messages);
1198
1199 self.process_dkg_updates(
1200 &mut state,
1201 &commit_info,
1202 randomness_manager.as_deref_mut(),
1203 randomness_dkg_messages,
1204 randomness_dkg_confirmations,
1205 )
1206 .await?;
1207
1208 let mut execution_time_estimator = self
1209 .epoch_store
1210 .execution_time_estimator
1211 .try_lock()
1212 .expect("should only ever be called from the commit handler thread");
1213
1214 let authenticator_state_update_transaction =
1215 self.create_authenticator_state_update(last_committed_round, &commit_info);
1216
1217 let (
1218 transactions_to_schedule,
1219 randomness_transactions_to_schedule,
1220 cancelled_txns,
1221 randomness_state_update_transaction,
1222 ) = self.collect_transactions_to_schedule(
1223 &mut state,
1224 &mut execution_time_estimator,
1225 &commit_info,
1226 user_transactions,
1227 );
1228
1229 let (should_accept_tx, lock, final_round, abandoned_deferred_txns) =
1230 self.handle_close_epoch(&mut state, &commit_info, end_of_publish_transactions);
1231
1232 let make_checkpoint = should_accept_tx || final_round;
1233 if !make_checkpoint {
1234 return Ok(());
1236 }
1237
1238 if final_round {
1241 self.record_end_of_epoch_execution_time_observations(&mut execution_time_estimator);
1242 }
1243
1244 let consensus_commit_prologue = (!commit_info.skip_consensus_commit_prologue_in_test)
1245 .then_some(Schedulable::ConsensusCommitPrologue(
1246 epoch,
1247 commit_info.round,
1248 commit_info.consensus_commit_ref.index,
1249 ));
1250
1251 let schedulables: Vec<_> = itertools::chain!(
1252 consensus_commit_prologue.into_iter(),
1253 authenticator_state_update_transaction
1254 .into_iter()
1255 .map(Schedulable::Transaction),
1256 transactions_to_schedule
1257 .into_iter()
1258 .map(Schedulable::Transaction),
1259 )
1260 .collect();
1261
1262 let randomness_schedulables: Vec<_> = randomness_state_update_transaction
1263 .into_iter()
1264 .chain(
1265 randomness_transactions_to_schedule
1266 .into_iter()
1267 .map(Schedulable::Transaction),
1268 )
1269 .collect();
1270
1271 let num_schedulables = schedulables.len();
1272 let checkpoint_height = self.create_pending_checkpoints(
1273 &mut state,
1274 &commit_info,
1275 schedulables,
1276 randomness_schedulables,
1277 &cancelled_txns,
1278 final_round,
1279 );
1280
1281 let notifications = state.get_notifications();
1282
1283 let mut stats_to_record = self.last_consensus_stats.clone();
1284 stats_to_record.height = checkpoint_height;
1285 {
1286 let queue = self.checkpoint_queue.lock().unwrap();
1287 stats_to_record.last_checkpoint_flush_timestamp = queue.last_built_timestamp();
1288 stats_to_record.checkpoint_seq = queue.checkpoint_seq();
1289 }
1290
1291 state.output.record_consensus_commit_stats(stats_to_record);
1292
1293 self.record_deferral_deletion(&mut state);
1294
1295 self.epoch_store
1296 .consensus_quarantine
1297 .write()
1298 .push_consensus_output(state.output, &self.epoch_store)
1299 .expect("push_consensus_output should not fail");
1300
1301 debug!(
1302 ?commit_info.round,
1303 "Notifying checkpoint service about new pending checkpoint(s)",
1304 );
1305 self.checkpoint_service
1306 .notify_checkpoint()
1307 .expect("failed to notify checkpoint service");
1308
1309 if let Some(randomness_round) = state.randomness_round {
1310 randomness_manager
1311 .as_ref()
1312 .expect("randomness manager should exist if randomness round is provided")
1313 .generate_randomness(epoch, randomness_round);
1314 }
1315
1316 self.epoch_store.process_notifications(notifications.iter());
1317
1318 self.log_final_round(lock, final_round);
1320
1321 if let Some(AbandonedDeferredTxns { count, sample }) = abandoned_deferred_txns {
1325 self.metrics
1326 .consensus_handler_dropped_transactions
1327 .with_label_values(&["epoch_close_deadline"])
1328 .inc_by(count as u64);
1329 debug_fatal!(
1330 "Epoch close deadline reached with unscheduled deferred transactions: count={}, sample={:?}",
1331 count,
1332 sample
1333 );
1334 }
1335
1336 self.throughput_calculator
1338 .add_transactions(timestamp, num_schedulables as u64);
1339
1340 fail_point_if!("correlated-crash-after-consensus-commit-boundary", || {
1341 let key = [commit_sub_dag_index, epoch];
1342 if sui_simulator::random::deterministic_probability_once(&key, 0.01) {
1343 sui_simulator::task::kill_current_node(None);
1344 }
1345 });
1346
1347 fail_point!("crash");
1348 Ok(())
1349 }
1350
1351 fn handle_close_epoch(
1352 &self,
1353 state: &mut CommitHandlerState,
1354 commit_info: &ConsensusCommitInfo,
1355 end_of_publish_transactions: Vec<AuthorityName>,
1356 ) -> (
1357 bool,
1358 Option<RwLockWriteGuard<'_, ReconfigState>>,
1359 bool,
1360 Option<AbandonedDeferredTxns>,
1361 ) {
1362 let timestamp_triggered =
1363 commit_info.timestamp >= self.epoch_store.next_reconfiguration_timestamp_ms();
1364 let deadline_reached = self
1365 .epoch_store
1366 .protocol_config()
1367 .epoch_close_deadline_ms_as_option()
1368 .is_some_and(|deadline_ms| {
1369 commit_info.timestamp
1370 >= self
1371 .epoch_store
1372 .next_reconfiguration_timestamp_ms()
1373 .saturating_add(deadline_ms)
1374 });
1375 let collected_eop_quorum =
1376 self.process_end_of_publish_transactions(state, end_of_publish_transactions);
1377 if timestamp_triggered || collected_eop_quorum {
1378 let (lock, final_round, abandoned_deferred_txns) =
1379 self.advance_end_of_epoch_state_machine(state, deadline_reached);
1380 (
1381 lock.should_accept_tx(),
1382 Some(lock),
1383 final_round,
1384 abandoned_deferred_txns,
1385 )
1386 } else {
1387 (true, None, false, None)
1388 }
1389 }
1390
1391 fn record_end_of_epoch_execution_time_observations(
1392 &self,
1393 estimator: &mut ExecutionTimeEstimator,
1394 ) {
1395 self.epoch_store
1396 .end_of_epoch_execution_time_observations
1397 .set(estimator.take_observations())
1398 .expect("`stored_execution_time_observations` should only be set once at end of epoch");
1399 }
1400
1401 fn record_deferral_deletion(&self, state: &mut CommitHandlerState) {
1402 let mut deferred_transactions = self
1403 .epoch_store
1404 .consensus_output_cache
1405 .deferred_transactions
1406 .lock();
1407 for deleted_deferred_key in state.output.get_deleted_deferred_txn_keys() {
1408 deferred_transactions.remove(&deleted_deferred_key);
1409 }
1410 }
1411
1412 fn log_final_round(&self, lock: Option<RwLockWriteGuard<ReconfigState>>, final_round: bool) {
1413 if final_round {
1414 let epoch = self.epoch_store.epoch();
1415 info!(
1416 ?epoch,
1417 lock=?lock.as_ref(),
1418 final_round=?final_round,
1419 "Notified last checkpoint"
1420 );
1421 self.epoch_store.record_end_of_message_quorum_time_metric();
1422 }
1423 }
1424
1425 #[allow(clippy::type_complexity)]
1426 fn collect_transactions_to_schedule(
1427 &self,
1428 state: &mut CommitHandlerState,
1429 execution_time_estimator: &mut ExecutionTimeEstimator,
1430 commit_info: &ConsensusCommitInfo,
1431 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
1432 ) -> (
1433 Vec<VerifiedExecutableTransactionWithAliases>,
1434 Vec<VerifiedExecutableTransactionWithAliases>,
1435 BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1436 Option<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1437 ) {
1438 let _scope = monitored_scope("ConsensusCommitHandler::collect_transactions_to_schedule");
1439 let protocol_config = self.epoch_store.protocol_config();
1440 let epoch = self.epoch_store.epoch();
1441
1442 let (ordered_txns, ordered_randomness_txns, previously_deferred_tx_digests) =
1443 self.merge_and_reorder_transactions(state, commit_info, user_transactions);
1444
1445 let mut shared_object_congestion_tracker =
1446 self.init_congestion_tracker(commit_info, false, &ordered_txns);
1447 let mut shared_object_using_randomness_congestion_tracker =
1448 self.init_congestion_tracker(commit_info, true, &ordered_randomness_txns);
1449
1450 let randomness_state_update_transaction = state
1451 .randomness_round
1452 .map(|round| Schedulable::RandomnessStateUpdate(epoch, round));
1453 debug!(
1454 "Randomness state update transaction: {:?}",
1455 randomness_state_update_transaction
1456 .as_ref()
1457 .map(|t| t.key())
1458 );
1459
1460 let mut transactions_to_schedule = Vec::with_capacity(ordered_txns.len());
1461 let mut randomness_transactions_to_schedule =
1462 Vec::with_capacity(ordered_randomness_txns.len());
1463 let mut deferred_txns = BTreeMap::new();
1464 let mut cancelled_txns = BTreeMap::new();
1465
1466 self.record_duplication_signal(state);
1467
1468 for transaction in ordered_txns {
1469 self.handle_deferral_and_cancellation(
1470 state,
1471 &mut cancelled_txns,
1472 &mut deferred_txns,
1473 &mut transactions_to_schedule,
1474 protocol_config,
1475 commit_info,
1476 transaction,
1477 &mut shared_object_congestion_tracker,
1478 &previously_deferred_tx_digests,
1479 execution_time_estimator,
1480 );
1481 }
1482
1483 for transaction in ordered_randomness_txns {
1484 if state.dkg_failed {
1485 debug!(
1486 "Canceling randomness-using transaction {:?} because DKG failed",
1487 transaction.tx().digest(),
1488 );
1489 cancelled_txns.insert(
1490 *transaction.tx().digest(),
1491 CancelConsensusCertificateReason::DkgFailed,
1492 );
1493 randomness_transactions_to_schedule.push(transaction);
1494 continue;
1495 }
1496 self.handle_deferral_and_cancellation(
1497 state,
1498 &mut cancelled_txns,
1499 &mut deferred_txns,
1500 &mut randomness_transactions_to_schedule,
1501 protocol_config,
1502 commit_info,
1503 transaction,
1504 &mut shared_object_using_randomness_congestion_tracker,
1505 &previously_deferred_tx_digests,
1506 execution_time_estimator,
1507 );
1508 }
1509
1510 let mut total_deferred_txns = 0;
1511 {
1512 let mut deferred_transactions = self
1513 .epoch_store
1514 .consensus_output_cache
1515 .deferred_transactions
1516 .lock();
1517 for (key, txns) in deferred_txns.into_iter() {
1518 total_deferred_txns += txns.len();
1519 debug_assert!(
1534 !state
1535 .output
1536 .get_deleted_deferred_txn_keys()
1537 .any(|deleted| deleted == key),
1538 "deferral key {key:?} was reloaded by this commit and must not be re-inserted"
1539 );
1540 let txns = if protocol_config.merge_colliding_deferrals() {
1541 match deferred_transactions.remove(&key) {
1542 Some(mut merged) => {
1543 assert_reachable_gated!(
1544 "Merged colliding deferral entries instead of displacing finalized transactions.",
1545 |pc| pc.merge_colliding_deferrals()
1546 && (pc.defer_owned_object_double_spend()
1547 || pc.defer_unpaid_amplification())
1548 );
1549 debug_assert!(
1550 {
1551 let prev_digests: HashSet<_> =
1552 merged.iter().map(|t| *t.tx().digest()).collect();
1553 txns.iter().all(|t| !prev_digests.contains(t.tx().digest()))
1554 },
1555 "colliding deferral entries must not share transactions"
1556 );
1557 merged.extend(txns);
1560 merged
1561 }
1562 None => txns,
1563 }
1564 } else {
1565 if let Some(prev) = deferred_transactions.get(&key) {
1566 let new_digests: HashSet<_> =
1569 txns.iter().map(|t| *t.tx().digest()).collect();
1570 let displaced: Vec<_> = prev
1571 .iter()
1572 .map(|t| *t.tx().digest())
1573 .filter(|d| !new_digests.contains(d))
1574 .collect();
1575 if !displaced.is_empty() {
1576 debug_fatal!(
1577 "Deferral key collision displaced finalized transactions: key {:?}, displaced {:?}",
1578 key,
1579 displaced
1580 );
1581 }
1582 }
1583 txns
1584 };
1585 deferred_transactions.insert(key, txns.clone());
1586 state.output.defer_transactions(key, txns);
1587 }
1588 }
1589
1590 self.metrics
1591 .consensus_handler_deferred_transactions
1592 .inc_by(total_deferred_txns as u64);
1593 self.metrics
1594 .consensus_handler_cancelled_transactions
1595 .inc_by(cancelled_txns.len() as u64);
1596 self.metrics
1597 .consensus_handler_max_object_costs
1598 .with_label_values(&["regular_commit"])
1599 .set(shared_object_congestion_tracker.max_cost() as i64);
1600 self.metrics
1601 .consensus_handler_max_object_costs
1602 .with_label_values(&["randomness_commit"])
1603 .set(shared_object_using_randomness_congestion_tracker.max_cost() as i64);
1604
1605 let congestion_commit_data = shared_object_congestion_tracker.finish_commit(commit_info);
1606 let randomness_congestion_commit_data =
1607 shared_object_using_randomness_congestion_tracker.finish_commit(commit_info);
1608
1609 if let Some(logger) = &self.congestion_logger {
1610 let epoch = self.epoch_store.epoch();
1611 let mut logger = logger.lock().unwrap();
1612 logger.write_commit_log(epoch, commit_info, false, &congestion_commit_data);
1613 logger.write_commit_log(epoch, commit_info, true, &randomness_congestion_commit_data);
1614 }
1615
1616 if let Some(tx_object_debts) = self.epoch_store.tx_object_debts.get()
1617 && let Err(e) = tx_object_debts.try_send(
1618 congestion_commit_data
1619 .accumulated_debts
1620 .iter()
1621 .chain(randomness_congestion_commit_data.accumulated_debts.iter())
1622 .map(|(id, _)| *id)
1623 .collect(),
1624 )
1625 {
1626 info!("failed to send updated object debts to ExecutionTimeObserver: {e:?}");
1627 }
1628
1629 state
1630 .output
1631 .set_congestion_control_object_debts(congestion_commit_data.accumulated_debts);
1632 state.output.set_congestion_control_randomness_object_debts(
1633 randomness_congestion_commit_data.accumulated_debts,
1634 );
1635
1636 (
1637 transactions_to_schedule,
1638 randomness_transactions_to_schedule,
1639 cancelled_txns,
1640 randomness_state_update_transaction,
1641 )
1642 }
1643
1644 #[allow(clippy::type_complexity)]
1645 fn create_pending_checkpoints(
1646 &self,
1647 state: &mut CommitHandlerState,
1648 commit_info: &ConsensusCommitInfo,
1649 schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1650 randomness_schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1651 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1652 final_round: bool,
1653 ) -> CheckpointHeight {
1654 let _scope = monitored_scope("ConsensusCommitHandler::create_pending_checkpoints");
1655 let protocol_config = self.epoch_store.protocol_config();
1656 let epoch = self.epoch_store.epoch();
1657 let accumulators_enabled = self.epoch_store.accumulators_enabled();
1658 let max_transactions_per_checkpoint =
1659 protocol_config.max_transactions_per_checkpoint() as usize;
1660
1661 let should_write_random_checkpoint = state.randomness_round.is_some()
1662 || (state.dkg_failed && !randomness_schedulables.is_empty());
1663
1664 let mut checkpoint_queue = self.checkpoint_queue.lock().unwrap();
1665
1666 let build_chunks =
1667 |schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1668 queue: &mut CheckpointQueue|
1669 -> Vec<Chunk<VerifiedExecutableTransactionWithAliases>> {
1670 schedulables
1671 .chunks(max_transactions_per_checkpoint)
1672 .map(|chunk| {
1673 let height = queue.next_height();
1674 let schedulables: Vec<_> = chunk.to_vec();
1675 let settlement = if accumulators_enabled {
1676 Some(Schedulable::AccumulatorSettlement(epoch, height))
1677 } else {
1678 None
1679 };
1680 Chunk {
1681 schedulables,
1682 settlement,
1683 height,
1684 }
1685 })
1686 .collect()
1687 };
1688
1689 let num_schedulables = schedulables.len();
1690 let chunked_schedulables = build_chunks(schedulables, &mut checkpoint_queue);
1691 if chunked_schedulables.len() > 1 {
1692 info!(
1693 "Splitting transactions into {} checkpoint chunks (num_schedulables={}, max_tx={})",
1694 chunked_schedulables.len(),
1695 num_schedulables,
1696 max_transactions_per_checkpoint
1697 );
1698 assert_reachable!("checkpoint split due to transaction limit");
1699 }
1700 let chunked_randomness_schedulables = if should_write_random_checkpoint {
1701 build_chunks(randomness_schedulables, &mut checkpoint_queue)
1702 } else {
1703 vec![]
1704 };
1705
1706 let schedulables_for_version_assignment =
1707 Chunk::all_schedulables_from(&chunked_schedulables);
1708 let randomness_schedulables_for_version_assignment =
1709 Chunk::all_schedulables_from(&chunked_randomness_schedulables);
1710
1711 let assigned_versions = self
1712 .epoch_store
1713 .process_consensus_transaction_shared_object_versions(
1714 self.cache_reader.as_ref(),
1715 schedulables_for_version_assignment,
1716 randomness_schedulables_for_version_assignment,
1717 cancelled_txns,
1718 &mut state.output,
1719 )
1720 .expect("failed to assign shared object versions");
1721
1722 let consensus_commit_prologue =
1723 self.add_consensus_commit_prologue_transaction(state, commit_info, &assigned_versions);
1724
1725 let mut chunked_schedulables = chunked_schedulables;
1726 let mut assigned_versions = assigned_versions;
1727 if let Some(consensus_commit_prologue) = consensus_commit_prologue {
1728 assert!(matches!(
1729 chunked_schedulables[0].schedulables[0],
1730 Schedulable::ConsensusCommitPrologue(..)
1731 ));
1732 assert!(matches!(
1733 assigned_versions.0[0].0,
1734 TransactionKey::ConsensusCommitPrologue(..)
1735 ));
1736 assigned_versions.0[0].0 =
1737 TransactionKey::Digest(*consensus_commit_prologue.tx().digest());
1738 chunked_schedulables[0].schedulables[0] =
1739 Schedulable::Transaction(consensus_commit_prologue);
1740 }
1741
1742 let assigned_versions = assigned_versions.into_map();
1743
1744 self.epoch_store.process_user_signatures(
1745 chunked_schedulables
1746 .iter()
1747 .flat_map(|c| c.all_schedulables())
1748 .chain(
1749 chunked_randomness_schedulables
1750 .iter()
1751 .flat_map(|c| c.all_schedulables()),
1752 ),
1753 );
1754
1755 let commit_height = chunked_randomness_schedulables
1756 .last()
1757 .or(chunked_schedulables.last())
1758 .map(|c| c.height)
1759 .expect("at least one checkpoint root must be created per commit");
1760
1761 let mut pending_checkpoints = Vec::new();
1762 for chunk in chunked_schedulables {
1763 pending_checkpoints.extend(checkpoint_queue.push_chunk(
1764 chunk.into(),
1765 &assigned_versions,
1766 commit_info.timestamp,
1767 commit_info.consensus_commit_ref,
1768 commit_info.rejected_transactions_digest,
1769 ));
1770 }
1771
1772 pending_checkpoints.extend(checkpoint_queue.flush(commit_info.timestamp, final_round));
1777
1778 if should_write_random_checkpoint {
1779 for chunk in chunked_randomness_schedulables {
1780 pending_checkpoints.extend(checkpoint_queue.push_chunk(
1781 chunk.into(),
1782 &assigned_versions,
1783 commit_info.timestamp,
1784 commit_info.consensus_commit_ref,
1785 commit_info.rejected_transactions_digest,
1786 ));
1787 }
1788 if final_round {
1789 pending_checkpoints.extend(checkpoint_queue.flush(commit_info.timestamp, true));
1790 }
1791 }
1792
1793 if final_round && let Some(last) = pending_checkpoints.last_mut() {
1794 last.details.last_of_epoch = true;
1795 }
1796
1797 let queue_drained = checkpoint_queue.is_empty();
1798 drop(checkpoint_queue);
1799
1800 for pending_checkpoint in pending_checkpoints {
1801 debug!(
1802 checkpoint_height = pending_checkpoint.details.checkpoint_height,
1803 roots_count = pending_checkpoint.num_roots(),
1804 "Writing pending checkpoint",
1805 );
1806 self.epoch_store
1807 .write_pending_checkpoint(&mut state.output, &pending_checkpoint);
1808 }
1809
1810 state.output.set_checkpoint_queue_drained(queue_drained);
1811
1812 commit_height
1813 }
1814
1815 fn add_consensus_commit_prologue_transaction<'a>(
1819 &'a self,
1820 state: &'a mut CommitHandlerState,
1821 commit_info: &'a ConsensusCommitInfo,
1822 assigned_versions: &AssignedTxAndVersions,
1823 ) -> Option<VerifiedExecutableTransactionWithAliases> {
1824 {
1825 if commit_info.skip_consensus_commit_prologue_in_test {
1826 return None;
1827 }
1828 }
1829
1830 let mut cancelled_txn_version_assignment = Vec::new();
1831
1832 for (txn_key, assigned_versions) in assigned_versions.0.iter() {
1833 let Some(d) = txn_key.as_digest() else {
1834 continue;
1835 };
1836
1837 if assigned_versions
1838 .shared_object_versions
1839 .iter()
1840 .any(|(_, version)| version.is_cancelled())
1841 {
1842 assert_reachable!("cancelled transactions");
1843 cancelled_txn_version_assignment
1844 .push((*d, assigned_versions.shared_object_versions.clone()));
1845 }
1846 }
1847
1848 fail_point_arg!(
1849 "additional_cancelled_txns_for_tests",
1850 |additional_cancelled_txns: Vec<(
1851 TransactionDigest,
1852 Vec<(ConsensusObjectSequenceKey, SequenceNumber)>
1853 )>| {
1854 cancelled_txn_version_assignment.extend(additional_cancelled_txns);
1855 }
1856 );
1857
1858 let transaction = commit_info.create_consensus_commit_prologue_transaction(
1859 self.epoch_store.epoch(),
1860 cancelled_txn_version_assignment,
1861 state.indirect_state_observer.take().unwrap(),
1862 );
1863 Some(VerifiedExecutableTransactionWithAliases::no_aliases(
1864 transaction,
1865 ))
1866 }
1867
1868 fn record_duplication_signal(&self, state: &CommitHandlerState) {
1881 self.metrics
1882 .staggered_submission_excess_copies
1883 .observe(state.staggering_counts.excess_copies as f64);
1884 for (kind, copies) in state.staggering_counts.by_kind() {
1886 self.metrics
1887 .staggered_submission_commit_copies
1888 .with_label_values(&[kind])
1889 .inc_by(copies);
1890 }
1891 let staggered = self.epoch_store.staggered_submission();
1892 let outcome = staggered.record_commit(
1893 state.staggering_counts.excess_copies,
1894 state.staggering_counts.unique_user_txns,
1895 );
1896 let transition = outcome.transition;
1897 self.metrics
1898 .staggered_submission_duplication_ratio
1899 .set(outcome.duplication_ratio);
1900 self.metrics
1902 .staggered_submission_signal_band
1903 .set(staggered.signal_band() as i64);
1904 self.metrics
1905 .staggered_submission_active
1906 .set(staggered.is_active() as i64);
1907 if let Some(band) = transition {
1908 self.metrics
1909 .staggered_submission_signal_transitions
1910 .with_label_values(&[crate::staggered_submission::signal_band_label(band)])
1911 .inc();
1912 self.metrics
1913 .staggered_submission_band_duration_commits
1914 .with_label_values(&[crate::staggered_submission::signal_band_label(
1915 outcome.previous_band,
1916 )])
1917 .observe(outcome.commits_in_previous_band as f64);
1918 }
1919 }
1920
1921 fn handle_deferral_and_cancellation(
1922 &self,
1923 state: &mut CommitHandlerState,
1924 cancelled_txns: &mut BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1925 deferred_txns: &mut BTreeMap<DeferralKey, Vec<VerifiedExecutableTransactionWithAliases>>,
1926 scheduled_txns: &mut Vec<VerifiedExecutableTransactionWithAliases>,
1927 protocol_config: &ProtocolConfig,
1928 commit_info: &ConsensusCommitInfo,
1929 transaction: VerifiedExecutableTransactionWithAliases,
1930 shared_object_congestion_tracker: &mut SharedObjectCongestionTracker,
1931 previously_deferred_tx_digests: &HashMap<TransactionDigest, DeferralKey>,
1932 execution_time_estimator: &ExecutionTimeEstimator,
1933 ) {
1934 let tx_digest = *transaction.tx().digest();
1935
1936 let restricts_proposers = transaction
1945 .tx()
1946 .transaction_data()
1947 .expiration()
1948 .restricts_proposers(self.epoch_store.epoch());
1949 if restricts_proposers {
1950 assert_reachable!("committed a proposer-restricted transaction");
1951 }
1952 if protocol_config.defer_unpaid_amplification() && !restricts_proposers {
1953 let occurrence_count = state
1954 .occurrence_counts
1955 .get(&tx_digest)
1956 .copied()
1957 .unwrap_or(0);
1958
1959 let allowed_count = paid_proposer_count(
1960 transaction.tx().transaction_data().gas_price(),
1961 self.epoch_store.reference_gas_price(),
1962 );
1963
1964 if occurrence_count as u64 > allowed_count {
1965 self.metrics
1966 .consensus_handler_unpaid_amplification_deferrals
1967 .inc();
1968
1969 let deferred_from_round = previously_deferred_tx_digests
1970 .get(&tx_digest)
1971 .map(|k| k.deferred_from_round())
1972 .unwrap_or(commit_info.round);
1973
1974 let deferral_key = DeferralKey::new_for_consensus_round(
1975 commit_info.round + 1,
1976 deferred_from_round,
1977 );
1978
1979 if transaction_deferral_within_limit(
1980 &deferral_key,
1981 protocol_config.max_deferral_rounds_for_congestion_control(),
1982 ) {
1983 debug!(
1984 "Deferring transaction {:?} due to unpaid amplification (count={}, allowed={})",
1985 tx_digest, occurrence_count, allowed_count
1986 );
1987 deferred_txns
1988 .entry(deferral_key)
1989 .or_default()
1990 .push(transaction);
1991 return;
1992 }
1993 }
1994 }
1995
1996 if let Some(conflict_info) = state.contested_transaction_digests.get(&tx_digest) {
2000 self.metrics.consensus_handler_double_spend_deferrals.inc();
2001 self.metrics
2002 .consensus_handler_double_spend_conflict_count
2003 .with_label_values(&["gas_object"])
2004 .observe(conflict_info.gas_object_conflicts as f64);
2005 self.metrics
2006 .consensus_handler_double_spend_conflict_count
2007 .with_label_values(&["non_gas_object"])
2008 .observe(conflict_info.non_gas_object_conflicts as f64);
2009 self.metrics
2012 .consensus_handler_double_spend_conflicting_authority
2013 .with_label_values(&[
2014 self.authority_hostname(conflict_info.winner_author),
2015 "winner",
2016 ])
2017 .inc();
2018
2019 if protocol_config.defer_owned_object_double_spend() {
2020 let deferred_from_round = previously_deferred_tx_digests
2021 .get(&tx_digest)
2022 .map(|k| k.deferred_from_round())
2023 .unwrap_or(commit_info.round);
2024
2025 let deferral_key = DeferralKey::new_for_consensus_round(
2026 commit_info.round + 1,
2027 deferred_from_round,
2028 );
2029
2030 if transaction_deferral_within_limit(
2031 &deferral_key,
2032 protocol_config.max_deferral_rounds_for_congestion_control(),
2033 ) {
2034 debug!(
2035 "Deferring transaction {:?} due to owned object double-spend contention \
2036 (gas_conflicts={}, non_gas_conflicts={})",
2037 tx_digest,
2038 conflict_info.gas_object_conflicts,
2039 conflict_info.non_gas_object_conflicts,
2040 );
2041 assert_reachable_gated!(
2042 "Successfully deferred transaction attempting to double spend owned object.",
2043 |pc| pc.defer_owned_object_double_spend()
2044 );
2045 if transaction.tx().transaction_data().uses_randomness()
2046 && state.randomness_round.is_none()
2047 {
2048 assert_reachable_gated!(
2054 "Double-spend deferred a randomness-using transaction at a round without randomness.",
2055 |pc| pc.defer_owned_object_double_spend()
2056 );
2057 }
2058 deferred_txns
2059 .entry(deferral_key)
2060 .or_default()
2061 .push(transaction);
2062 return;
2063 }
2064 }
2065 }
2066
2067 let tx_cost = shared_object_congestion_tracker.get_tx_cost(
2068 execution_time_estimator,
2069 transaction.tx(),
2070 state.indirect_state_observer.as_mut().unwrap(),
2071 );
2072
2073 let deferral_info = self.epoch_store.should_defer(
2074 transaction.tx(),
2075 commit_info,
2076 state.dkg_failed,
2077 state.randomness_round.is_some(),
2078 previously_deferred_tx_digests,
2079 shared_object_congestion_tracker,
2080 );
2081
2082 if let Some((deferral_key, deferral_reason)) = deferral_info {
2083 debug!(
2084 "Deferring consensus certificate for transaction {:?} until {:?}",
2085 tx_digest, deferral_key
2086 );
2087
2088 match deferral_reason {
2089 DeferralReason::RandomnessNotReady => {
2090 deferred_txns
2091 .entry(deferral_key)
2092 .or_default()
2093 .push(transaction);
2094 }
2095 DeferralReason::SharedObjectCongestion(congested_objects) => {
2096 self.metrics.consensus_handler_congested_transactions.inc();
2097 if transaction_deferral_within_limit(
2098 &deferral_key,
2099 protocol_config.max_deferral_rounds_for_congestion_control(),
2100 ) {
2101 deferred_txns
2102 .entry(deferral_key)
2103 .or_default()
2104 .push(transaction);
2105 } else {
2106 assert_sometimes!(
2107 transaction.tx().data().transaction_data().uses_randomness(),
2108 "cancelled randomness-using transaction"
2109 );
2110 assert_sometimes!(
2111 !transaction.tx().data().transaction_data().uses_randomness(),
2112 "cancelled non-randomness-using transaction"
2113 );
2114
2115 debug!(
2117 "Cancelling consensus transaction {:?} with deferral key {:?} due to congestion on objects {:?}",
2118 tx_digest, deferral_key, congested_objects
2119 );
2120 cancelled_txns.insert(
2121 tx_digest,
2122 CancelConsensusCertificateReason::CongestionOnObjects(
2123 congested_objects,
2124 ),
2125 );
2126 scheduled_txns.push(transaction);
2127 }
2128 }
2129 }
2130 } else {
2131 shared_object_congestion_tracker.bump_object_execution_cost(tx_cost, transaction.tx());
2133 scheduled_txns.push(transaction);
2134 }
2135 }
2136
2137 fn merge_and_reorder_transactions(
2138 &self,
2139 state: &mut CommitHandlerState,
2140 commit_info: &ConsensusCommitInfo,
2141 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
2142 ) -> (
2143 Vec<VerifiedExecutableTransactionWithAliases>,
2144 Vec<VerifiedExecutableTransactionWithAliases>,
2145 HashMap<TransactionDigest, DeferralKey>,
2146 ) {
2147 let protocol_config = self.epoch_store.protocol_config();
2148
2149 let (mut txns, mut randomness_txns, previously_deferred_tx_digests) =
2150 self.load_deferred_transactions(state, commit_info);
2151
2152 txns.reserve(user_transactions.len());
2153 randomness_txns.reserve(user_transactions.len());
2154
2155 let mut txns: Vec<_> = txns
2158 .into_iter()
2159 .filter_map(|tx| {
2160 if tx.tx().transaction_data().uses_randomness() {
2161 randomness_txns.push(tx);
2162 None
2163 } else {
2164 Some(tx)
2165 }
2166 })
2167 .collect();
2168
2169 for txn in user_transactions {
2170 if txn.tx().transaction_data().uses_randomness() {
2171 randomness_txns.push(txn);
2172 } else {
2173 txns.push(txn);
2174 }
2175 }
2176
2177 PostConsensusTxReorder::reorder(
2178 &mut txns,
2179 protocol_config.consensus_transaction_ordering(),
2180 );
2181 PostConsensusTxReorder::reorder(
2182 &mut randomness_txns,
2183 protocol_config.consensus_transaction_ordering(),
2184 );
2185
2186 (txns, randomness_txns, previously_deferred_tx_digests)
2187 }
2188
2189 fn load_deferred_transactions(
2190 &self,
2191 state: &mut CommitHandlerState,
2192 commit_info: &ConsensusCommitInfo,
2193 ) -> (
2194 Vec<VerifiedExecutableTransactionWithAliases>,
2195 Vec<VerifiedExecutableTransactionWithAliases>,
2196 HashMap<TransactionDigest, DeferralKey>,
2197 ) {
2198 let mut previously_deferred_tx_digests = HashMap::new();
2199
2200 let deferred_txs: Vec<_> = self
2201 .epoch_store
2202 .load_deferred_transactions_for_up_to_consensus_round_v2(
2203 &mut state.output,
2204 commit_info.round,
2205 )
2206 .into_iter()
2207 .flat_map(|(key, txns)| txns.into_iter().map(move |tx| (key, tx)))
2208 .map(|(key, tx)| {
2209 previously_deferred_tx_digests.insert(*tx.tx().digest(), key);
2210 tx
2211 })
2212 .collect();
2213 trace!(
2214 "loading deferred transactions: {:?}",
2215 deferred_txs.iter().map(|tx| tx.tx().digest())
2216 );
2217
2218 let deferred_randomness_txs = if state.dkg_failed || state.randomness_round.is_some() {
2219 let txns: Vec<_> = self
2220 .epoch_store
2221 .load_deferred_transactions_for_randomness_v2(&mut state.output)
2222 .into_iter()
2223 .flat_map(|(key, txns)| txns.into_iter().map(move |tx| (key, tx)))
2224 .map(|(key, tx)| {
2225 previously_deferred_tx_digests.insert(*tx.tx().digest(), key);
2226 tx
2227 })
2228 .collect();
2229 trace!(
2230 "loading deferred randomness transactions: {:?}",
2231 txns.iter().map(|tx| tx.tx().digest())
2232 );
2233 txns
2234 } else {
2235 vec![]
2236 };
2237
2238 (
2239 deferred_txs,
2240 deferred_randomness_txs,
2241 previously_deferred_tx_digests,
2242 )
2243 }
2244
2245 fn init_congestion_tracker(
2246 &self,
2247 commit_info: &ConsensusCommitInfo,
2248 for_randomness: bool,
2249 txns: &[VerifiedExecutableTransactionWithAliases],
2250 ) -> SharedObjectCongestionTracker {
2251 #[allow(unused_mut)]
2252 let mut ret = SharedObjectCongestionTracker::from_protocol_config(
2253 self.epoch_store
2254 .consensus_quarantine
2255 .read()
2256 .load_initial_object_debts(
2257 &self.epoch_store,
2258 commit_info.round,
2259 for_randomness,
2260 txns,
2261 ),
2262 self.epoch_store.protocol_config(),
2263 for_randomness,
2264 self.congestion_logger.is_some(),
2265 );
2266
2267 fail_point_arg!(
2268 "initial_congestion_tracker",
2269 |tracker: SharedObjectCongestionTracker| {
2270 info!(
2271 "Initialize shared_object_congestion_tracker to {:?}",
2272 tracker
2273 );
2274 ret = tracker;
2275 }
2276 );
2277
2278 ret
2279 }
2280
2281 fn process_gasless_transactions(
2282 &self,
2283 commit_info: &ConsensusCommitInfo,
2284 user_transactions: &[VerifiedExecutableTransactionWithAliases],
2285 ) {
2286 let gasless_count = user_transactions
2287 .iter()
2288 .filter(|txn| txn.tx().transaction_data().is_gasless_transaction())
2289 .count() as u64;
2290 self.consensus_gasless_counter
2291 .record_commit(commit_info.timestamp, gasless_count);
2292 }
2293
2294 fn process_jwks(
2295 &self,
2296 state: &mut CommitHandlerState,
2297 commit_info: &ConsensusCommitInfo,
2298 new_jwks: Vec<(AuthorityName, JwkId, JWK)>,
2299 ) {
2300 for (authority_name, jwk_id, jwk) in new_jwks {
2301 self.epoch_store.record_jwk_vote(
2302 &mut state.output,
2303 commit_info.round,
2304 authority_name,
2305 &jwk_id,
2306 &jwk,
2307 );
2308 }
2309 }
2310
2311 fn process_capability_notifications(
2312 &self,
2313 capability_notifications: Vec<AuthorityCapabilitiesV2>,
2314 ) {
2315 for capabilities in capability_notifications {
2316 self.epoch_store
2317 .record_capabilities_v2(&capabilities)
2318 .expect("db error");
2319 }
2320 }
2321
2322 fn process_transaction_deny_config_updates(
2327 &self,
2328 updates: Vec<(AuthorityName, SharedTransactionDenyConfig)>,
2329 ) {
2330 for (author, update) in updates {
2331 self.transaction_deny_config_manager
2332 .apply_updates(author, vec![update]);
2333 }
2334 }
2335
2336 fn process_execution_time_observations(
2337 &self,
2338 state: &mut CommitHandlerState,
2339 execution_time_observations: Vec<ExecutionTimeObservation>,
2340 ) {
2341 let _scope = monitored_scope("ConsensusCommitHandler::process_execution_time_observations");
2342 let mut execution_time_estimator = self
2343 .epoch_store
2344 .execution_time_estimator
2345 .try_lock()
2346 .expect("should only ever be called from the commit handler thread");
2347
2348 for ExecutionTimeObservation {
2349 authority,
2350 generation,
2351 estimates,
2352 } in execution_time_observations
2353 {
2354 let authority_index = self
2355 .epoch_store
2356 .committee()
2357 .authority_index(&authority)
2358 .unwrap();
2359 execution_time_estimator.process_observations_from_consensus(
2360 authority_index,
2361 Some(generation),
2362 &estimates,
2363 );
2364 state
2365 .output
2366 .insert_execution_time_observation(authority_index, generation, estimates);
2367 }
2368 }
2369
2370 fn process_checkpoint_signature_messages(
2371 &self,
2372 checkpoint_signature_messages: Vec<CheckpointSignatureMessage>,
2373 ) {
2374 for checkpoint_signature_message in checkpoint_signature_messages {
2375 self.checkpoint_service
2376 .notify_checkpoint_signature(&checkpoint_signature_message)
2377 .expect("db error");
2378 }
2379 }
2380
2381 async fn process_dkg_updates(
2382 &self,
2383 state: &mut CommitHandlerState,
2384 commit_info: &ConsensusCommitInfo,
2385 randomness_manager: Option<&mut RandomnessManager>,
2386 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
2387 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
2388 ) -> Result<(), RuntimeShutdown> {
2389 if !self.epoch_store.randomness_state_enabled() {
2390 let num_dkg_messages = randomness_dkg_messages.len();
2391 let num_dkg_confirmations = randomness_dkg_confirmations.len();
2392 if num_dkg_messages + num_dkg_confirmations > 0 {
2393 debug_fatal!(
2394 "received {} RandomnessDkgMessage and {} RandomnessDkgConfirmation messages when randomness is not enabled",
2395 num_dkg_messages,
2396 num_dkg_confirmations
2397 );
2398 }
2399 return Ok(());
2400 }
2401
2402 let randomness_manager =
2403 randomness_manager.expect("randomness manager should exist if randomness is enabled");
2404
2405 let randomness_dkg_updates =
2406 self.process_randomness_dkg_messages(randomness_manager, randomness_dkg_messages);
2407
2408 let randomness_dkg_confirmation_updates = self.process_randomness_dkg_confirmations(
2409 state,
2410 randomness_manager,
2411 randomness_dkg_confirmations,
2412 );
2413
2414 let protocol_config = self.epoch_store.protocol_config();
2415 let always_advance_dkg_to_resolution = !protocol_config
2418 .allow_dkg_completion_after_timeout()
2419 && (protocol_config.always_advance_dkg_to_resolution()
2420 || (self.epoch_store.get_chain() == Chain::Mainnet
2421 && self.epoch_store.epoch() >= 1143))
2422 && randomness_manager.dkg_status() == DkgStatus::Pending;
2423
2424 if randomness_dkg_updates
2425 || randomness_dkg_confirmation_updates
2426 || always_advance_dkg_to_resolution
2427 {
2428 match randomness_manager
2429 .advance_dkg(&mut state.output, commit_info.round)
2430 .await
2431 {
2432 Ok(()) => {}
2433 Err(AdvanceDkgError::RuntimeShutdown) => return Err(RuntimeShutdown),
2434 Err(AdvanceDkgError::Sui(e)) => panic!("failed to advance randomness DKG: {e:?}"),
2435 }
2436 }
2437 Ok(())
2438 }
2439
2440 fn process_randomness_dkg_messages(
2441 &self,
2442 randomness_manager: &mut RandomnessManager,
2443 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
2444 ) -> bool {
2445 if randomness_dkg_messages.is_empty() {
2446 return false;
2447 }
2448
2449 let mut randomness_state_updated = false;
2450 for (authority, bytes) in randomness_dkg_messages {
2451 match bcs::from_bytes(&bytes) {
2452 Ok(message) => {
2453 randomness_manager
2454 .add_message(&authority, message)
2455 .expect("epoch ended");
2457 randomness_state_updated = true;
2458 }
2459
2460 Err(e) => {
2461 warn!(
2462 "Failed to deserialize RandomnessDkgMessage from {:?}: {e:?}",
2463 authority.concise(),
2464 );
2465 }
2466 }
2467 }
2468
2469 randomness_state_updated
2470 }
2471
2472 fn process_randomness_dkg_confirmations(
2473 &self,
2474 state: &mut CommitHandlerState,
2475 randomness_manager: &mut RandomnessManager,
2476 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
2477 ) -> bool {
2478 if randomness_dkg_confirmations.is_empty() {
2479 return false;
2480 }
2481
2482 let mut randomness_state_updated = false;
2483 for (authority, bytes) in randomness_dkg_confirmations {
2484 match bcs::from_bytes(&bytes) {
2485 Ok(message) => {
2486 randomness_manager
2487 .add_confirmation(&mut state.output, &authority, message)
2488 .expect("epoch ended");
2490 randomness_state_updated = true;
2491 }
2492 Err(e) => {
2493 warn!(
2494 "Failed to deserialize RandomnessDkgConfirmation from {:?}: {e:?}",
2495 authority.concise(),
2496 );
2497 }
2498 }
2499 }
2500
2501 randomness_state_updated
2502 }
2503
2504 fn process_end_of_publish_transactions(
2506 &self,
2507 state: &mut CommitHandlerState,
2508 end_of_publish_transactions: Vec<AuthorityName>,
2509 ) -> bool {
2510 let mut eop_aggregator = self.epoch_store.end_of_publish.try_lock().expect(
2511 "No contention on end_of_publish as it is only accessed from consensus handler",
2512 );
2513
2514 if eop_aggregator.has_quorum() {
2515 return true;
2516 }
2517
2518 if end_of_publish_transactions.is_empty() {
2519 return false;
2520 }
2521
2522 for authority in end_of_publish_transactions {
2523 info!("Received EndOfPublish from {:?}", authority.concise());
2524
2525 state.output.insert_end_of_publish(authority);
2528 if eop_aggregator
2529 .insert_generic(authority, ())
2530 .is_quorum_reached()
2531 {
2532 debug!(
2533 "Collected enough end_of_publish messages with last message from validator {:?}",
2534 authority.concise(),
2535 );
2536 return true;
2537 }
2538 }
2539
2540 false
2541 }
2542
2543 fn advance_end_of_epoch_state_machine(
2546 &self,
2547 state: &mut CommitHandlerState,
2548 deadline_reached: bool,
2549 ) -> (
2550 RwLockWriteGuard<'_, ReconfigState>,
2551 bool, Option<AbandonedDeferredTxns>,
2553 ) {
2554 let mut reconfig_state = self.epoch_store.get_reconfig_state_write_lock_guard();
2555 let start_state_is_reject_all_tx = reconfig_state.is_reject_all_tx();
2556
2557 if reconfig_state.should_accept_user_certs() {
2560 self.epoch_store.record_epoch_close_time_once();
2561 }
2562
2563 reconfig_state.close_all_certs();
2564
2565 let commit_has_deferred_txns = state.output.has_deferred_transactions();
2566 let previous_commits_have_deferred_txns = !self.epoch_store.deferred_transactions_empty();
2567 let has_deferred_txns = commit_has_deferred_txns || previous_commits_have_deferred_txns;
2568
2569 let should_close = !has_deferred_txns || deadline_reached;
2572 let final_round = should_close && !start_state_is_reject_all_tx;
2573
2574 let mut abandoned_deferred_txns = None;
2575 if final_round {
2576 info!("Transitioning to RejectAllTx");
2577 if has_deferred_txns {
2578 debug_assert!(deadline_reached);
2580 abandoned_deferred_txns = self.abandon_deferred_transactions(state);
2581 }
2582 reconfig_state.close_all_tx();
2583 } else if !should_close {
2584 debug!(
2585 "Blocking end of epoch on deferred transactions, from previous commits?={}, from this commit?={}",
2586 previous_commits_have_deferred_txns, commit_has_deferred_txns,
2587 );
2588 }
2589
2590 state.output.store_reconfig_state(reconfig_state.clone());
2591
2592 (reconfig_state, final_round, abandoned_deferred_txns)
2593 }
2594
2595 fn abandon_deferred_transactions(
2602 &self,
2603 state: &mut CommitHandlerState,
2604 ) -> Option<AbandonedDeferredTxns> {
2605 state.output.clear_deferred_transactions();
2606
2607 let already_deleted: BTreeSet<_> = state.output.get_deleted_deferred_txn_keys().collect();
2608 let mut count = 0;
2609 let mut sample = Vec::new();
2610 let mut abandoned_keys = Vec::new();
2611 {
2612 let deferred_transactions = self
2613 .epoch_store
2614 .consensus_output_cache
2615 .deferred_transactions
2616 .lock();
2617 for (key, txns) in deferred_transactions.iter() {
2618 if already_deleted.contains(key) {
2619 continue;
2621 }
2622 abandoned_keys.push(*key);
2623 count += txns.len();
2624 for tx in txns {
2625 if sample.len() < 10 {
2626 sample.push((*key, *tx.tx().digest()));
2627 }
2628 }
2629 }
2630 }
2631 if abandoned_keys.is_empty() {
2632 return None;
2633 }
2634 state
2635 .output
2636 .delete_loaded_deferred_transactions(&abandoned_keys);
2637 (count > 0).then_some(AbandonedDeferredTxns { count, sample })
2638 }
2639
2640 fn gather_commit_metadata(
2641 &self,
2642 consensus_commit: &impl ConsensusCommitAPI,
2643 ) -> (u64, AuthorityIndex, u64) {
2644 let timestamp = consensus_commit.commit_timestamp_ms();
2645 let leader_author = consensus_commit.leader_author_index();
2646 let commit_sub_dag_index = consensus_commit.commit_sub_dag_index();
2647
2648 let system_time_ms = SystemTime::now()
2649 .duration_since(UNIX_EPOCH)
2650 .unwrap()
2651 .as_millis() as i64;
2652
2653 let consensus_timestamp_bias_ms = system_time_ms - (timestamp as i64);
2654 let consensus_timestamp_bias_seconds = consensus_timestamp_bias_ms as f64 / 1000.0;
2655 self.metrics
2656 .consensus_timestamp_bias
2657 .observe(consensus_timestamp_bias_seconds);
2658
2659 let epoch_start = self
2660 .epoch_store
2661 .epoch_start_config()
2662 .epoch_start_timestamp_ms();
2663 let timestamp = if timestamp < epoch_start {
2664 error!(
2665 "Unexpected commit timestamp {timestamp} less then epoch start time {epoch_start}, author {leader_author}"
2666 );
2667 epoch_start
2668 } else {
2669 timestamp
2670 };
2671
2672 (timestamp, leader_author, commit_sub_dag_index)
2673 }
2674
2675 fn create_authenticator_state_update(
2676 &self,
2677 last_committed_round: u64,
2678 commit_info: &ConsensusCommitInfo,
2679 ) -> Option<VerifiedExecutableTransactionWithAliases> {
2680 let new_jwks = self
2688 .epoch_store
2689 .get_new_jwks(last_committed_round)
2690 .expect("Unrecoverable error in consensus handler");
2691
2692 if !new_jwks.is_empty() {
2693 let authenticator_state_update_transaction = authenticator_state_update_transaction(
2694 &self.epoch_store,
2695 commit_info.round,
2696 new_jwks,
2697 );
2698 debug!(
2699 "adding AuthenticatorStateUpdate({:?}) tx: {:?}",
2700 authenticator_state_update_transaction.digest(),
2701 authenticator_state_update_transaction,
2702 );
2703
2704 Some(VerifiedExecutableTransactionWithAliases::no_aliases(
2705 authenticator_state_update_transaction,
2706 ))
2707 } else {
2708 None
2709 }
2710 }
2711
2712 fn authority_hostname(&self, author: usize) -> &str {
2715 self.committee
2716 .to_authority_index(author)
2717 .map(|index| self.committee.authority(index).hostname.as_str())
2718 .unwrap_or("unknown")
2719 }
2720
2721 #[instrument(level = "trace", skip_all)]
2724 fn filter_consensus_txns(
2725 &mut self,
2726 initial_reconfig_state: ReconfigState,
2727 commit_info: &ConsensusCommitInfo,
2728 block_transactions: ParsedConsensusTransactions,
2729 ) -> FilteredConsensusOutput {
2730 let _scope = monitored_scope("ConsensusCommitHandler::filter_consensus_txns");
2731 let mut transactions = Vec::new();
2732 let mut owned_object_locks = HashMap::new();
2733 let mut dropped_transaction_keys = Vec::new();
2734 let mut status_updates: Vec<(ConsensusPosition, ConsensusTxStatus)> = Vec::new();
2738 let mut contested_transaction_digests: HashMap<TransactionDigest, ConflictInfo> =
2739 HashMap::new();
2740 let mut lock_holder_authors: HashMap<TransactionDigest, usize> = HashMap::new();
2744 let epoch = self.epoch_store.epoch();
2745 let mut num_finalized_user_transactions = vec![0; self.committee.size()];
2746 let mut num_rejected_user_transactions = vec![0; self.committee.size()];
2747 let mut num_dropped_user_transactions = vec![0; self.committee.size()];
2748
2749 let existing_locks = {
2755 let mut prefetch_refs: Vec<ObjectRef> = Vec::new();
2756 for (_block, parsed_transactions) in &block_transactions {
2757 for parsed in parsed_transactions {
2758 if let ConsensusTransactionKind::UserTransactionV2(tx_with_claims) =
2759 &parsed.transaction.kind
2760 && let Some(refs) = owned_object_refs_to_lock(tx_with_claims)
2761 {
2762 prefetch_refs.extend(refs);
2763 }
2764 }
2765 }
2766 prefetch_refs.sort();
2767 prefetch_refs.dedup();
2768 self.epoch_store.get_owned_object_locks_map(&prefetch_refs)
2769 };
2770
2771 for (block, parsed_transactions) in block_transactions {
2772 let author = block.author.value();
2773 let author_hostname = self.committee.authority(block.author).hostname.as_str();
2774 self.last_consensus_stats.stats.inc_num_messages(author);
2776
2777 status_updates.push((
2779 ConsensusPosition::ping(epoch, block),
2780 ConsensusTxStatus::Finalized,
2781 ));
2782
2783 for (tx_index, parsed) in parsed_transactions.into_iter().enumerate() {
2784 let position = ConsensusPosition {
2785 epoch,
2786 block,
2787 index: tx_index as TransactionIndex,
2788 };
2789
2790 if let Some(tx) = parsed.transaction.kind.as_user_transaction() {
2793 let digest = tx.digest();
2794 if let Some((spam_weight, submitter_client_addrs)) = self
2795 .epoch_store
2796 .submitted_transaction_cache
2797 .increment_submission_count(digest)
2798 {
2799 if let Some(ref traffic_controller) = self.traffic_controller {
2800 debug!(
2801 "Transaction {digest} exceeded submission limits, spam_weight: {spam_weight:?} applied to {} client addresses",
2802 submitter_client_addrs.len()
2803 );
2804
2805 for addr in submitter_client_addrs {
2807 traffic_controller.tally(
2808 TrafficTally::new(Some(addr), None, None, spam_weight.clone())
2809 .with_method(
2810 "consensus_submission_limit_exceeded".to_string(),
2811 ),
2812 );
2813 }
2814 } else {
2815 warn!(
2816 "Transaction {digest} exceeded submission limits, spam_weight: {spam_weight:?} for {} client addresses (traffic controller not configured)",
2817 submitter_client_addrs.len()
2818 );
2819 }
2820 }
2821 }
2822
2823 let kind = classify(&parsed.transaction);
2826 let outcome = if parsed.rejected {
2827 "rejected"
2828 } else {
2829 "accepted"
2830 };
2831 self.metrics
2832 .consensus_handler_processed
2833 .with_label_values(&[kind, outcome])
2834 .inc();
2835 self.metrics.observe_consensus_handler_transaction_size(
2836 kind,
2837 outcome,
2838 parsed.serialized_len,
2839 );
2840 if parsed.transaction.is_user_transaction() {
2844 self.metrics
2845 .consensus_handler_processed_user_transactions
2846 .with_label_values(&[outcome, author_hostname])
2847 .inc();
2848 }
2849
2850 if parsed.rejected {
2851 if parsed.transaction.is_user_transaction() {
2852 status_updates.push((position, ConsensusTxStatus::Rejected));
2853 num_rejected_user_transactions[author] += 1;
2854 }
2855 continue;
2857 }
2858
2859 if parsed.transaction.is_user_transaction() {
2860 self.last_consensus_stats
2861 .stats
2862 .inc_num_user_transactions(author);
2863 }
2864
2865 if !initial_reconfig_state.should_accept_consensus_certs() {
2866 match &parsed.transaction.kind {
2869 ConsensusTransactionKind::UserTransactionV2(_)
2870 | ConsensusTransactionKind::UserTransaction(_)
2872 | ConsensusTransactionKind::CertifiedTransaction(_)
2873 | ConsensusTransactionKind::CapabilityNotification(_)
2874 | ConsensusTransactionKind::CapabilityNotificationV2(_)
2875 | ConsensusTransactionKind::EndOfPublish(_)
2876 | ConsensusTransactionKind::ExecutionTimeObservation(_)
2878 | ConsensusTransactionKind::NewJWKFetched(_, _, _)
2879 | ConsensusTransactionKind::UpdateTransactionDenyConfig(_) => {
2880 if parsed.transaction.is_user_transaction() {
2887 status_updates.push((position, ConsensusTxStatus::Dropped));
2888 num_dropped_user_transactions[author] += 1;
2889 self.metrics
2890 .consensus_handler_dropped_transactions
2891 .with_label_values(&["end_of_epoch"])
2892 .inc();
2893 }
2894 debug!(
2895 "Ignoring consensus transaction {:?} because of end of epoch",
2896 parsed.transaction.key()
2897 );
2898 continue;
2899 }
2900
2901 ConsensusTransactionKind::CheckpointSignature(_)
2903 | ConsensusTransactionKind::CheckpointSignatureV2(_)
2904 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
2905 | ConsensusTransactionKind::RandomnessDkgMessage(_, _)
2906 | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _) => ()
2907 }
2908 }
2909
2910 if !initial_reconfig_state.should_accept_tx() {
2911 match &parsed.transaction.kind {
2912 ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
2913 | ConsensusTransactionKind::RandomnessDkgMessage(_, _) => continue,
2914 _ => {}
2915 }
2916 }
2917
2918 match &parsed.transaction.kind {
2920 ConsensusTransactionKind::CapabilityNotification(_)
2921 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
2922 | ConsensusTransactionKind::CheckpointSignature(_) => {
2923 debug_fatal!(
2924 "BUG: saw deprecated tx {:?}for commit round {}",
2925 parsed.transaction.key(),
2926 commit_info.round
2927 );
2928 continue;
2929 }
2930 _ => {}
2931 }
2932
2933 if parsed.transaction.is_user_transaction() {
2934 let author_name = self
2935 .epoch_store
2936 .committee()
2937 .authority_by_index(author as u32)
2938 .unwrap();
2939 if self
2940 .epoch_store
2941 .has_received_end_of_publish_from(author_name)
2942 {
2943 status_updates.push((position, ConsensusTxStatus::Dropped));
2950 num_dropped_user_transactions[author] += 1;
2951 self.metrics
2952 .consensus_handler_dropped_transactions
2953 .with_label_values(&["end_of_publish"])
2954 .inc();
2955 warn!(
2956 "Ignoring consensus transaction {:?} from authority {:?}, which already sent EndOfPublish message to consensus",
2957 author_name.concise(),
2958 parsed.transaction.key(),
2959 );
2960 continue;
2961 }
2962 }
2963
2964 if let ConsensusTransactionKind::UserTransactionV2(tx_with_claims) =
2970 &parsed.transaction.kind
2971 {
2972 let tx = tx_with_claims.tx();
2973 let Some(owned_object_refs) = owned_object_refs_to_lock(tx_with_claims) else {
2974 self.metrics
2976 .consensus_handler_dropped_transactions
2977 .with_label_values(&["invalid_input"])
2978 .inc();
2979 status_updates.push((position, ConsensusTxStatus::Dropped));
2980 num_dropped_user_transactions[author] += 1;
2981 if let Err(e) = tx.transaction_data().input_objects() {
2985 self.epoch_store
2986 .set_rejection_vote_reason(position, &e.into());
2987 }
2988 dropped_transaction_keys.push(parsed.transaction.key());
2989 debug_fatal!("Invalid input objects for transaction {}", tx.digest());
2990 continue;
2991 };
2992
2993 match self
2994 .epoch_store
2995 .try_acquire_owned_object_locks_post_consensus(
2996 &owned_object_refs,
2997 *tx.digest(),
2998 &owned_object_locks,
2999 &existing_locks,
3000 ) {
3001 Ok(new_locks) => {
3002 owned_object_locks.extend(new_locks.into_iter());
3003 lock_holder_authors.entry(*tx.digest()).or_insert(author);
3004 status_updates.push((position, ConsensusTxStatus::Finalized));
3006 num_finalized_user_transactions[author] += 1;
3007 }
3008 Err(e) => {
3009 let gas_object_ids: HashSet<ObjectID> = tx
3013 .transaction_data()
3014 .gas()
3015 .iter()
3016 .map(|obj_ref| obj_ref.0)
3017 .collect();
3018 let mut is_intra_commit_conflict = false;
3019 for obj_ref in &owned_object_refs {
3020 if let Some(holder_digest) = owned_object_locks.get(obj_ref) {
3021 is_intra_commit_conflict = true;
3022 let info = contested_transaction_digests
3023 .entry(*holder_digest)
3024 .or_default();
3025 info.winner_author = lock_holder_authors
3026 .get(holder_digest)
3027 .copied()
3028 .unwrap_or(author);
3029 if gas_object_ids.contains(&obj_ref.0) {
3030 info.gas_object_conflicts += 1;
3031 } else {
3032 info.non_gas_object_conflicts += 1;
3033 }
3034 }
3035 }
3036 if is_intra_commit_conflict {
3040 self.metrics
3041 .consensus_handler_double_spend_conflicting_authority
3042 .with_label_values(&[self.authority_hostname(author), "loser"])
3043 .inc();
3044 }
3045 debug!("Dropping transaction {}: {}", tx.digest(), e);
3046 self.metrics
3047 .consensus_handler_dropped_transactions
3048 .with_label_values(&["lock_conflict"])
3049 .inc();
3050 status_updates.push((position, ConsensusTxStatus::Dropped));
3051 num_dropped_user_transactions[author] += 1;
3052 self.epoch_store.set_rejection_vote_reason(position, &e);
3053 dropped_transaction_keys.push(parsed.transaction.key());
3054 continue;
3055 }
3056 }
3057 }
3058
3059 let transaction = SequencedConsensusTransactionKind::External(parsed.transaction);
3060 transactions.push((transaction, author as u32));
3061 }
3062 }
3063
3064 self.epoch_store.set_consensus_tx_statuses(status_updates);
3068
3069 for (i, authority) in self.committee.authorities() {
3070 let hostname = &authority.hostname;
3071 self.metrics
3072 .consensus_committed_messages
3073 .with_label_values(&[hostname])
3074 .set(self.last_consensus_stats.stats.get_num_messages(i.value()) as i64);
3075 self.metrics
3076 .consensus_committed_user_transactions
3077 .with_label_values(&[hostname])
3078 .set(
3079 self.last_consensus_stats
3080 .stats
3081 .get_num_user_transactions(i.value()) as i64,
3082 );
3083 self.metrics
3084 .consensus_finalized_user_transactions
3085 .with_label_values(&[hostname])
3086 .add(num_finalized_user_transactions[i.value()] as i64);
3087 self.metrics
3088 .consensus_rejected_user_transactions
3089 .with_label_values(&[hostname])
3090 .add(num_rejected_user_transactions[i.value()] as i64);
3091 self.metrics
3092 .consensus_dropped_user_transactions
3093 .with_label_values(&[hostname])
3094 .add(num_dropped_user_transactions[i.value()] as i64);
3095 }
3096
3097 FilteredConsensusOutput {
3098 transactions,
3099 owned_object_locks,
3100 dropped_transaction_keys,
3101 contested_transaction_digests,
3102 }
3103 }
3104
3105 fn deduplicate_consensus_txns(
3106 &mut self,
3107 state: &mut CommitHandlerState,
3108 commit_info: &ConsensusCommitInfo,
3109 transactions: Vec<(SequencedConsensusTransactionKind, u32)>,
3110 ) -> Vec<VerifiedSequencedConsensusTransaction> {
3111 let _scope = monitored_scope("ConsensusCommitHandler::deduplicate_consensus_txns");
3112 let epoch = self.epoch_store.epoch();
3113 let rgp = self.epoch_store.reference_gas_price().max(1);
3114 let mut all_transactions = Vec::new();
3115
3116 let mut occurrence_counts: HashMap<SequencedConsensusTransactionKey, u32> = HashMap::new();
3119 let mut first_commit_keys: HashSet<SequencedConsensusTransactionKey> = HashSet::new();
3121
3122 for (seq, (transaction, cert_origin)) in transactions.into_iter().enumerate() {
3123 let current_tx_index = ExecutionIndices {
3127 last_committed_round: commit_info.round,
3128 sub_dag_index: commit_info.consensus_commit_ref.index.into(),
3129 transaction_index: (seq + 1) as u64,
3130 };
3131
3132 self.last_consensus_stats.index = current_tx_index;
3133
3134 let certificate_author = *self
3135 .epoch_store
3136 .committee()
3137 .authority_by_index(cert_origin)
3138 .unwrap();
3139
3140 let sequenced_transaction = SequencedConsensusTransaction {
3141 certificate_author_index: cert_origin,
3142 certificate_author,
3143 consensus_index: current_tx_index,
3144 transaction,
3145 };
3146
3147 let Some(verified_transaction) = self
3148 .epoch_store
3149 .verify_consensus_transaction(sequenced_transaction)
3150 else {
3151 continue;
3152 };
3153
3154 let key = verified_transaction.0.key();
3155
3156 if let Some(tx_digest) = key.user_transaction_digest() {
3157 self.epoch_store
3158 .cache_recently_finalized_transaction(tx_digest);
3159 }
3160
3161 let count = occurrence_counts.entry(key.clone()).or_insert(0);
3164 *count += 1;
3165 let in_commit = *count > 1;
3166
3167 let (total_occurrences, in_cache) = match self.processed_cache.get_mut(&key) {
3168 Some(total) => {
3169 *total = total.saturating_add(1);
3170 (*total, true)
3171 }
3172 None => {
3173 let total = u64::from(*count);
3176 self.processed_cache.put(key.clone(), total);
3177 (total, false)
3178 }
3179 };
3180 let user_transaction = match &verified_transaction.0.transaction {
3181 SequencedConsensusTransactionKind::External(transaction) => {
3182 transaction.kind.as_user_transaction()
3183 }
3184 SequencedConsensusTransactionKind::System(_) => None,
3185 };
3186 if let Some(transaction) = user_transaction {
3187 let tx_data = transaction.data().transaction_data();
3188 if !tx_data.expiration().restricts_proposers(epoch) {
3189 let allowance = paid_proposer_count(tx_data.gas_price(), rgp);
3190 if total_occurrences > allowance {
3191 state.staggering_counts.excess_copies += 1;
3192 } else if total_occurrences > 1 {
3193 state.staggering_counts.paid_duplicate_copies += 1;
3194 }
3195 } else if total_occurrences > 1 {
3196 state.staggering_counts.restricted_duplicate_copies += 1;
3197 }
3198 }
3199 if in_commit || in_cache {
3200 self.metrics.skipped_consensus_txns_cache_hit.inc();
3201 continue;
3202 }
3203 if self
3204 .epoch_store
3205 .is_consensus_message_processed(&key)
3206 .expect("db error")
3207 {
3208 self.metrics.skipped_consensus_txns.inc();
3209 continue;
3210 }
3211
3212 state.staggering_counts.unique_user_txns += u64::from(user_transaction.is_some());
3213 first_commit_keys.insert(key.clone());
3214
3215 state.output.record_consensus_message_processed(key);
3216
3217 all_transactions.push(verified_transaction);
3218 }
3219
3220 for key in first_commit_keys {
3221 if let Some(&count) = occurrence_counts.get(&key)
3222 && count > 1
3223 {
3224 self.metrics
3225 .consensus_handler_duplicate_tx_count
3226 .observe(count as f64);
3227 }
3228 }
3229
3230 assert!(
3232 state.occurrence_counts.is_empty(),
3233 "occurrence_counts should be empty before populating"
3234 );
3235 state.occurrence_counts.reserve(occurrence_counts.len());
3236 state.occurrence_counts.extend(
3237 occurrence_counts
3238 .into_iter()
3239 .filter_map(|(key, count)| key.user_transaction_digest().map(|d| (d, count))),
3240 );
3241
3242 all_transactions
3243 }
3244
3245 fn build_commit_handler_input(
3246 &self,
3247 transactions: Vec<VerifiedSequencedConsensusTransaction>,
3248 ) -> CommitHandlerInput {
3249 let _scope = monitored_scope("ConsensusCommitHandler::build_commit_handler_input");
3250 let epoch = self.epoch_store.epoch();
3251 let mut commit_handler_input = CommitHandlerInput::default();
3252
3253 for VerifiedSequencedConsensusTransaction(transaction) in transactions.into_iter() {
3254 match transaction.transaction {
3255 SequencedConsensusTransactionKind::External(consensus_transaction) => {
3256 match consensus_transaction.kind {
3257 ConsensusTransactionKind::UserTransactionV2(tx) => {
3259 let used_alias_versions = tx.aliases();
3261 let inner_tx = tx.into_tx();
3262 let tx = VerifiedTransaction::new_unchecked(inner_tx);
3264 let transaction =
3266 VerifiedExecutableTransaction::new_from_consensus(tx, epoch);
3267 if let Some(used_alias_versions) = used_alias_versions {
3268 commit_handler_input
3269 .user_transactions
3270 .push(WithAliases::new(transaction, used_alias_versions));
3271 } else {
3272 commit_handler_input.user_transactions.push(
3273 VerifiedExecutableTransactionWithAliases::no_aliases(
3274 transaction,
3275 ),
3276 );
3277 }
3278 }
3279
3280 ConsensusTransactionKind::EndOfPublish(authority_public_key_bytes) => {
3282 commit_handler_input
3283 .end_of_publish_transactions
3284 .push(authority_public_key_bytes);
3285 }
3286 ConsensusTransactionKind::NewJWKFetched(
3287 authority_public_key_bytes,
3288 jwk_id,
3289 jwk,
3290 ) => {
3291 commit_handler_input.new_jwks.push((
3292 authority_public_key_bytes,
3293 jwk_id,
3294 jwk,
3295 ));
3296 }
3297 ConsensusTransactionKind::RandomnessDkgMessage(
3298 authority_public_key_bytes,
3299 items,
3300 ) => {
3301 commit_handler_input
3302 .randomness_dkg_messages
3303 .push((authority_public_key_bytes, items));
3304 }
3305 ConsensusTransactionKind::RandomnessDkgConfirmation(
3306 authority_public_key_bytes,
3307 items,
3308 ) => {
3309 commit_handler_input
3310 .randomness_dkg_confirmations
3311 .push((authority_public_key_bytes, items));
3312 }
3313 ConsensusTransactionKind::CapabilityNotificationV2(
3314 authority_capabilities_v2,
3315 ) => {
3316 commit_handler_input
3317 .capability_notifications
3318 .push(authority_capabilities_v2);
3319 }
3320 ConsensusTransactionKind::ExecutionTimeObservation(
3321 execution_time_observation,
3322 ) => {
3323 commit_handler_input
3324 .execution_time_observations
3325 .push(execution_time_observation);
3326 }
3327 ConsensusTransactionKind::CheckpointSignatureV2(
3328 checkpoint_signature_message,
3329 ) => {
3330 commit_handler_input
3331 .checkpoint_signature_messages
3332 .push(*checkpoint_signature_message);
3333 }
3334 ConsensusTransactionKind::UpdateTransactionDenyConfig(msg) => {
3335 commit_handler_input
3336 .transaction_deny_config_updates
3337 .push((transaction.certificate_author, *msg));
3338 }
3339
3340 ConsensusTransactionKind::CheckpointSignature(_)
3343 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
3344 | ConsensusTransactionKind::CapabilityNotification(_)
3345 | ConsensusTransactionKind::CertifiedTransaction(_)
3346 | ConsensusTransactionKind::UserTransaction(_) => {
3347 unreachable!("filtered earlier")
3348 }
3349 }
3350 }
3351 SequencedConsensusTransactionKind::System(_verified_envelope) => unreachable!(),
3353 }
3354 }
3355
3356 commit_handler_input
3357 }
3358}
3359
3360pub(crate) type SchedulerMessage = (
3363 Vec<(Schedulable, AssignedVersions)>,
3364 Option<SettlementBatchInfo>,
3365);
3366
3367#[derive(Clone)]
3368pub(crate) struct ExecutionSchedulerSender {
3369 sender: monitored_mpsc::UnboundedSender<SchedulerMessage>,
3370}
3371
3372impl ExecutionSchedulerSender {
3373 fn start(
3374 settlement_scheduler: SettlementScheduler,
3375 epoch_store: Arc<AuthorityPerEpochStore>,
3376 ) -> Self {
3377 let (sender, recv) = monitored_mpsc::unbounded_channel("execution_scheduler_sender");
3378 spawn_monitored_task!(Self::run(recv, settlement_scheduler, epoch_store));
3379 Self { sender }
3380 }
3381
3382 pub(crate) fn new_for_testing(
3383 sender: monitored_mpsc::UnboundedSender<SchedulerMessage>,
3384 ) -> Self {
3385 Self { sender }
3386 }
3387
3388 fn send(
3389 &self,
3390 transactions: Vec<(Schedulable, AssignedVersions)>,
3391 settlement: Option<SettlementBatchInfo>,
3392 ) {
3393 let _ = self.sender.send((transactions, settlement));
3394 }
3395
3396 async fn run(
3397 mut recv: monitored_mpsc::UnboundedReceiver<SchedulerMessage>,
3398 settlement_scheduler: SettlementScheduler,
3399 epoch_store: Arc<AuthorityPerEpochStore>,
3400 ) {
3401 while let Some((transactions, settlement)) = recv.recv().await {
3402 let _guard = monitored_scope("ConsensusHandler::enqueue");
3403 let txns = transactions
3404 .into_iter()
3405 .map(|(txn, versions)| (txn, ExecutionEnv::new().with_assigned_versions(versions)))
3406 .collect();
3407 if let Some(settlement) = settlement {
3408 settlement_scheduler.enqueue_v2(txns, settlement, &epoch_store);
3409 } else {
3410 settlement_scheduler.enqueue(txns, &epoch_store);
3411 }
3412 }
3413 }
3414}
3415
3416const CONSENSUS_HANDLER_DESERIALIZE_CHANNEL_CAPACITY: usize = 2;
3420
3421type ParsedConsensusTransactions = Vec<(BlockRef, Vec<ParsedTransaction>)>;
3425
3426pub(crate) struct MysticetiConsensusHandler {
3428 tasks: JoinSet<()>,
3429}
3430
3431impl MysticetiConsensusHandler {
3432 pub(crate) fn new(
3433 last_processed_commit_at_startup: CommitIndex,
3434 mut consensus_handler: ConsensusHandler<CheckpointService>,
3435 mut commit_receiver: UnboundedReceiver<consensus_core::CommittedSubDag>,
3436 consumer_monitor: Arc<ConsumerMonitor>,
3437 ) -> Self {
3438 debug!(
3439 last_processed_commit_at_startup,
3440 "Starting consensus replay"
3441 );
3442 let mut tasks = JoinSet::new();
3443
3444 let (parsed_sender, mut parsed_receiver) = monitored_mpsc::channel(
3450 "consensus_deserialized_commits",
3451 CONSENSUS_HANDLER_DESERIALIZE_CHANNEL_CAPACITY,
3452 );
3453 tasks.spawn(monitored_future!(async move {
3454 while let Some(consensus_commit) = commit_receiver.recv().await {
3455 let transactions: ParsedConsensusTransactions = {
3456 let _scope = monitored_scope("ConsensusCommitHandler::deserialize_worker");
3457 consensus_commit.transactions()
3458 };
3459 if parsed_sender
3463 .send((consensus_commit, transactions))
3464 .await
3465 .is_err()
3466 {
3467 break;
3468 }
3469 }
3470 }));
3471
3472 tasks.spawn(monitored_future!(async move {
3474 while let Some((consensus_commit, transactions)) = parsed_receiver.recv().await {
3476 let commit_index = consensus_commit.commit_ref.index;
3477 if commit_index <= last_processed_commit_at_startup {
3478 consensus_handler.handle_prior_consensus_commit(consensus_commit);
3479 } else if let Err(e) = consensus_handler
3480 .handle_consensus_commit(consensus_commit, transactions)
3481 .await
3482 {
3483 info!(
3484 "Consensus commit {commit_index} was not handled ({e:?}); it will be replayed after restart"
3485 );
3486 break;
3487 }
3488 consumer_monitor.set_highest_handled_commit(commit_index);
3489 }
3490 }));
3491 Self { tasks }
3492 }
3493
3494 pub(crate) async fn abort(&mut self) {
3495 self.tasks.shutdown().await;
3496 }
3497}
3498
3499fn authenticator_state_update_transaction(
3500 epoch_store: &AuthorityPerEpochStore,
3501 round: u64,
3502 mut new_active_jwks: Vec<ActiveJwk>,
3503) -> VerifiedExecutableTransaction {
3504 let epoch = epoch_store.epoch();
3505 new_active_jwks.sort();
3506
3507 info!("creating authenticator state update transaction");
3508 assert!(epoch_store.authenticator_state_enabled());
3509 let transaction = VerifiedTransaction::new_authenticator_state_update(
3510 epoch,
3511 round,
3512 new_active_jwks,
3513 epoch_store
3514 .epoch_start_config()
3515 .authenticator_obj_initial_shared_version()
3516 .expect("authenticator state obj must exist"),
3517 );
3518 VerifiedExecutableTransaction::new_system(transaction, epoch)
3519}
3520
3521fn owned_object_refs_to_lock(
3527 tx_with_claims: &PlainTransactionWithClaims,
3528) -> Option<Vec<ObjectRef>> {
3529 let immutable_object_ids: HashSet<ObjectID> =
3530 tx_with_claims.get_immutable_objects().into_iter().collect();
3531 let input_objects = tx_with_claims
3532 .tx()
3533 .transaction_data()
3534 .input_objects()
3535 .ok()?;
3536 Some(
3537 input_objects
3538 .iter()
3539 .filter_map(|obj| match obj {
3540 InputObjectKind::ImmOrOwnedMoveObject(obj_ref)
3541 if !immutable_object_ids.contains(&obj_ref.0) =>
3542 {
3543 Some(*obj_ref)
3544 }
3545 _ => None,
3546 })
3547 .collect(),
3548 )
3549}
3550
3551pub(crate) fn tx_type_label(transactions: &[ConsensusTransaction]) -> &'static str {
3553 match transactions {
3554 [transaction] => classify(transaction),
3555 _ => "soft_bundle",
3556 }
3557}
3558
3559pub(crate) fn classify(transaction: &ConsensusTransaction) -> &'static str {
3560 match &transaction.kind {
3561 ConsensusTransactionKind::CertifiedTransaction(_) => "_deprecated_certificate",
3563 ConsensusTransactionKind::CheckpointSignature(_) => "checkpoint_signature",
3564 ConsensusTransactionKind::CheckpointSignatureV2(_) => "checkpoint_signature",
3565 ConsensusTransactionKind::EndOfPublish(_) => "end_of_publish",
3566 ConsensusTransactionKind::CapabilityNotification(_) => "capability_notification",
3567 ConsensusTransactionKind::CapabilityNotificationV2(_) => "capability_notification_v2",
3568 ConsensusTransactionKind::NewJWKFetched(_, _, _) => "new_jwk_fetched",
3569 ConsensusTransactionKind::RandomnessStateUpdate(_, _) => "randomness_state_update",
3570 ConsensusTransactionKind::RandomnessDkgMessage(_, _) => "randomness_dkg_message",
3571 ConsensusTransactionKind::RandomnessDkgConfirmation(_, _) => "randomness_dkg_confirmation",
3572 ConsensusTransactionKind::UserTransaction(_) => "_deprecated_user_transaction",
3573 ConsensusTransactionKind::UserTransactionV2(tx) => {
3574 if tx.tx().is_consensus_tx() {
3575 "shared_user_transaction_v2"
3576 } else {
3577 "owned_user_transaction_v2"
3578 }
3579 }
3580 ConsensusTransactionKind::ExecutionTimeObservation(_) => "execution_time_observation",
3581 ConsensusTransactionKind::UpdateTransactionDenyConfig(_) => {
3582 "update_transaction_deny_config"
3583 }
3584 }
3585}
3586
3587#[derive(Debug, Clone, Serialize, Deserialize)]
3588pub struct SequencedConsensusTransaction {
3589 pub certificate_author_index: AuthorityIndex,
3590 pub certificate_author: AuthorityName,
3591 pub consensus_index: ExecutionIndices,
3592 pub transaction: SequencedConsensusTransactionKind,
3593}
3594
3595#[derive(Debug, Clone)]
3596#[allow(clippy::large_enum_variant)]
3597pub enum SequencedConsensusTransactionKind {
3598 External(ConsensusTransaction),
3599 System(VerifiedExecutableTransaction),
3600}
3601
3602impl Serialize for SequencedConsensusTransactionKind {
3603 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3604 let serializable = SerializableSequencedConsensusTransactionKind::from(self);
3605 serializable.serialize(serializer)
3606 }
3607}
3608
3609impl<'de> Deserialize<'de> for SequencedConsensusTransactionKind {
3610 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3611 let serializable =
3612 SerializableSequencedConsensusTransactionKind::deserialize(deserializer)?;
3613 Ok(serializable.into())
3614 }
3615}
3616
3617#[derive(Debug, Clone, Serialize, Deserialize)]
3621#[allow(clippy::large_enum_variant)]
3622enum SerializableSequencedConsensusTransactionKind {
3623 External(ConsensusTransaction),
3624 System(TrustedExecutableTransaction),
3625}
3626
3627impl From<&SequencedConsensusTransactionKind> for SerializableSequencedConsensusTransactionKind {
3628 fn from(kind: &SequencedConsensusTransactionKind) -> Self {
3629 match kind {
3630 SequencedConsensusTransactionKind::External(ext) => {
3631 SerializableSequencedConsensusTransactionKind::External(ext.clone())
3632 }
3633 SequencedConsensusTransactionKind::System(txn) => {
3634 SerializableSequencedConsensusTransactionKind::System(txn.clone().serializable())
3635 }
3636 }
3637 }
3638}
3639
3640impl From<SerializableSequencedConsensusTransactionKind> for SequencedConsensusTransactionKind {
3641 fn from(kind: SerializableSequencedConsensusTransactionKind) -> Self {
3642 match kind {
3643 SerializableSequencedConsensusTransactionKind::External(ext) => {
3644 SequencedConsensusTransactionKind::External(ext)
3645 }
3646 SerializableSequencedConsensusTransactionKind::System(txn) => {
3647 SequencedConsensusTransactionKind::System(txn.into())
3648 }
3649 }
3650 }
3651}
3652
3653#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
3654pub enum SequencedConsensusTransactionKey {
3655 External(ConsensusTransactionKey),
3656 System(TransactionDigest),
3657}
3658
3659impl SequencedConsensusTransactionKey {
3660 pub fn user_transaction_digest(&self) -> Option<TransactionDigest> {
3661 match self {
3662 SequencedConsensusTransactionKey::External(key) => match key {
3663 ConsensusTransactionKey::Certificate(digest) => Some(*digest),
3664 _ => None,
3665 },
3666 SequencedConsensusTransactionKey::System(_) => None,
3667 }
3668 }
3669}
3670
3671impl SequencedConsensusTransactionKind {
3672 pub fn key(&self) -> SequencedConsensusTransactionKey {
3673 match self {
3674 SequencedConsensusTransactionKind::External(ext) => {
3675 SequencedConsensusTransactionKey::External(ext.key())
3676 }
3677 SequencedConsensusTransactionKind::System(txn) => {
3678 SequencedConsensusTransactionKey::System(*txn.digest())
3679 }
3680 }
3681 }
3682
3683 pub fn get_tracking_id(&self) -> u64 {
3684 match self {
3685 SequencedConsensusTransactionKind::External(ext) => ext.get_tracking_id(),
3686 SequencedConsensusTransactionKind::System(_txn) => 0,
3687 }
3688 }
3689
3690 pub fn is_executable_transaction(&self) -> bool {
3691 match self {
3692 SequencedConsensusTransactionKind::External(ext) => ext.is_user_transaction(),
3693 SequencedConsensusTransactionKind::System(_) => true,
3694 }
3695 }
3696
3697 pub fn executable_transaction_digest(&self) -> Option<TransactionDigest> {
3698 match self {
3699 SequencedConsensusTransactionKind::External(ext) => match &ext.kind {
3700 ConsensusTransactionKind::UserTransactionV2(txn) => Some(*txn.tx().digest()),
3701 _ => None,
3702 },
3703 SequencedConsensusTransactionKind::System(txn) => Some(*txn.digest()),
3704 }
3705 }
3706
3707 pub fn is_end_of_publish(&self) -> bool {
3708 match self {
3709 SequencedConsensusTransactionKind::External(ext) => {
3710 matches!(ext.kind, ConsensusTransactionKind::EndOfPublish(..))
3711 }
3712 SequencedConsensusTransactionKind::System(_) => false,
3713 }
3714 }
3715}
3716
3717impl SequencedConsensusTransaction {
3718 pub fn sender_authority(&self) -> AuthorityName {
3719 self.certificate_author
3720 }
3721
3722 pub fn key(&self) -> SequencedConsensusTransactionKey {
3723 self.transaction.key()
3724 }
3725
3726 pub fn is_end_of_publish(&self) -> bool {
3727 if let SequencedConsensusTransactionKind::External(ref transaction) = self.transaction {
3728 matches!(transaction.kind, ConsensusTransactionKind::EndOfPublish(..))
3729 } else {
3730 false
3731 }
3732 }
3733
3734 pub fn try_take_execution_time_observation(&mut self) -> Option<ExecutionTimeObservation> {
3735 if let SequencedConsensusTransactionKind::External(ConsensusTransaction {
3736 kind: ConsensusTransactionKind::ExecutionTimeObservation(observation),
3737 ..
3738 }) = &mut self.transaction
3739 {
3740 Some(std::mem::take(observation))
3741 } else {
3742 None
3743 }
3744 }
3745
3746 pub fn is_system(&self) -> bool {
3747 matches!(
3748 self.transaction,
3749 SequencedConsensusTransactionKind::System(_)
3750 )
3751 }
3752
3753 pub fn is_user_tx_with_randomness(&self, randomness_state_enabled: bool) -> bool {
3754 if !randomness_state_enabled {
3755 return false;
3758 }
3759 match &self.transaction {
3760 SequencedConsensusTransactionKind::External(ConsensusTransaction {
3761 kind: ConsensusTransactionKind::UserTransactionV2(txn),
3762 ..
3763 }) => txn.tx().transaction_data().uses_randomness(),
3764 _ => false,
3765 }
3766 }
3767
3768 pub fn as_consensus_txn(&self) -> Option<&SenderSignedData> {
3769 match &self.transaction {
3770 SequencedConsensusTransactionKind::External(ConsensusTransaction {
3771 kind: ConsensusTransactionKind::UserTransactionV2(txn),
3772 ..
3773 }) if txn.tx().is_consensus_tx() => Some(txn.tx().data()),
3774 SequencedConsensusTransactionKind::System(txn) if txn.is_consensus_tx() => {
3775 Some(txn.data())
3776 }
3777 _ => None,
3778 }
3779 }
3780}
3781
3782#[derive(Debug, Clone, Serialize, Deserialize)]
3783pub struct VerifiedSequencedConsensusTransaction(pub SequencedConsensusTransaction);
3784
3785#[cfg(test)]
3786impl VerifiedSequencedConsensusTransaction {
3787 pub fn new_test(transaction: ConsensusTransaction) -> Self {
3788 Self(SequencedConsensusTransaction::new_test(transaction))
3789 }
3790}
3791
3792impl SequencedConsensusTransaction {
3793 pub fn new_test(transaction: ConsensusTransaction) -> Self {
3794 Self {
3795 certificate_author_index: 0,
3796 certificate_author: AuthorityName::ZERO,
3797 consensus_index: Default::default(),
3798 transaction: SequencedConsensusTransactionKind::External(transaction),
3799 }
3800 }
3801}
3802
3803#[derive(Serialize, Deserialize)]
3804pub(crate) struct CommitIntervalObserver {
3805 ring_buffer: VecDeque<u64>,
3806}
3807
3808impl CommitIntervalObserver {
3809 pub fn new(window_size: u32) -> Self {
3810 Self {
3811 ring_buffer: VecDeque::with_capacity(window_size as usize),
3812 }
3813 }
3814
3815 pub fn observe_commit_time(&mut self, consensus_commit: &impl ConsensusCommitAPI) {
3816 let commit_time = consensus_commit.commit_timestamp_ms();
3817 if self.ring_buffer.len() == self.ring_buffer.capacity() {
3818 self.ring_buffer.pop_front();
3819 }
3820 self.ring_buffer.push_back(commit_time);
3821 }
3822
3823 pub fn commit_interval_estimate(&self) -> Option<Duration> {
3824 if self.ring_buffer.len() <= 1 {
3825 None
3826 } else {
3827 let first = self.ring_buffer.front().unwrap();
3828 let last = self.ring_buffer.back().unwrap();
3829 let duration = last.saturating_sub(*first);
3830 let num_commits = self.ring_buffer.len() as u64;
3831 Some(Duration::from_millis(duration.div_ceil(num_commits)))
3832 }
3833 }
3834}
3835
3836#[cfg(test)]
3837#[path = "unit_tests/staggered_submission_signal_tests.rs"]
3838mod staggering_signal_tests;
3839
3840#[cfg(test)]
3841mod tests {
3842 use consensus_core::{
3843 BlockAPI, CommitDigest, CommitRef, CommittedSubDag, TestBlock, Transaction, VerifiedBlock,
3844 };
3845 use futures::pin_mut;
3846 use prometheus::Registry;
3847 use sui_macros::sim_test;
3848 use sui_protocol_config::{ConsensusTransactionOrdering, ProtocolConfig};
3849 use sui_types::{
3850 base_types::ExecutionDigests,
3851 base_types::{AuthorityName, FullObjectRef, ObjectID, SuiAddress, random_object_ref},
3852 committee::Committee,
3853 crypto::deterministic_random_account_key,
3854 gas::GasCostSummary,
3855 message_envelope::Message,
3856 messages_checkpoint::{
3857 CheckpointContents, CheckpointSignatureMessage, CheckpointSummary,
3858 SignedCheckpointSummary,
3859 },
3860 messages_consensus::ConsensusTransaction,
3861 object::Object,
3862 transaction::{
3863 CertifiedTransaction, TransactionData, TransactionDataAPI, VerifiedCertificate,
3864 },
3865 };
3866
3867 use sui_types::SUI_RANDOMNESS_STATE_OBJECT_ID;
3868 use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
3869 use sui_types::transaction::{ObjectArg, SharedObjectMutability};
3870
3871 use super::*;
3872 use crate::{
3873 authority::{
3874 authority_per_epoch_store::ConsensusStatsAPI,
3875 consensus_tx_status_cache::NotifyReadConsensusTxStatusResult,
3876 test_authority_builder::TestAuthorityBuilder,
3877 },
3878 checkpoints::CheckpointServiceNoop,
3879 consensus_adapter::consensus_tests::test_user_transaction,
3880 consensus_test_utils::{TestConsensusCommit, setup_consensus_handler_for_testing},
3881 post_consensus_tx_reorder::PostConsensusTxReorder,
3882 };
3883
3884 fn epoch_close_deadline_config(deadline_ms: Option<u64>) -> ProtocolConfig {
3885 let mut protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
3886 if let Some(deadline_ms) = deadline_ms {
3887 protocol_config.set_epoch_close_deadline_ms_for_testing(deadline_ms);
3888 } else {
3889 protocol_config.disable_epoch_close_deadline_ms_for_testing();
3890 }
3891 protocol_config
3892 }
3893
3894 #[tokio::test]
3895 async fn test_consensus_handler_max_transaction_size() {
3896 let metrics = AuthorityMetrics::new(&Registry::new());
3897
3898 metrics.observe_consensus_handler_transaction_size("class_a", "accepted", 100);
3899 metrics.observe_consensus_handler_transaction_size("class_a", "rejected", 80);
3900 metrics.observe_consensus_handler_transaction_size("class_b", "accepted", 50);
3901
3902 assert_eq!(
3903 metrics
3904 .consensus_handler_max_transaction_size
3905 .with_label_values(&["class_a"])
3906 .get(),
3907 100
3908 );
3909 assert_eq!(
3910 metrics
3911 .consensus_handler_max_transaction_size
3912 .with_label_values(&["class_b"])
3913 .get(),
3914 50
3915 );
3916
3917 metrics.observe_consensus_handler_transaction_size("class_a", "rejected", 120);
3918
3919 assert_eq!(
3920 metrics
3921 .consensus_handler_max_transaction_size
3922 .with_label_values(&["class_a"])
3923 .get(),
3924 120
3925 );
3926 }
3927
3928 #[sim_test]
3929 async fn test_epoch_close_deadline_preserves_pre_deadline_blocking() {
3930 let state = TestAuthorityBuilder::new()
3931 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3932 .build()
3933 .await;
3934 let epoch_store = state.epoch_store_for_testing();
3935 epoch_store.insert_deferred_transactions_for_test(
3936 DeferralKey::new_for_consensus_round(u64::MAX, 1),
3937 vec![user_txn(1)],
3938 );
3939 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3940 let mut setup = setup_consensus_handler_for_testing(&state).await;
3941
3942 setup
3943 .consensus_handler
3944 .handle_consensus_commit_for_test(TestConsensusCommit::empty(1, scheduled_end + 99, 1))
3945 .await;
3946
3947 let reconfig_state = epoch_store.get_reconfig_state_read_lock_guard();
3948 assert!(reconfig_state.is_reject_all_certs());
3949 assert!(!reconfig_state.is_reject_all_tx());
3950 assert_eq!(
3951 epoch_store.get_all_deferred_transactions_for_test().len(),
3952 1
3953 );
3954 assert!(
3955 epoch_store
3956 .get_pending_checkpoints(None)
3957 .iter()
3958 .all(|(_, checkpoint)| !checkpoint.details.last_of_epoch)
3959 );
3960 assert_eq!(
3961 setup
3962 .consensus_handler
3963 .metrics
3964 .consensus_handler_dropped_transactions
3965 .with_label_values(&["epoch_close_deadline"])
3966 .get(),
3967 0
3968 );
3969 }
3970
3971 #[sim_test]
3972 async fn test_epoch_close_deadline_none_preserves_indefinite_blocking() {
3973 let state = TestAuthorityBuilder::new()
3974 .with_protocol_config(epoch_close_deadline_config(None))
3975 .build()
3976 .await;
3977 let epoch_store = state.epoch_store_for_testing();
3978 epoch_store.insert_deferred_transactions_for_test(
3979 DeferralKey::new_for_consensus_round(u64::MAX, 1),
3980 vec![user_txn(1)],
3981 );
3982 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3983 let mut setup = setup_consensus_handler_for_testing(&state).await;
3984
3985 setup
3986 .consensus_handler
3987 .handle_consensus_commit_for_test(TestConsensusCommit::empty(
3988 1,
3989 scheduled_end.saturating_add(1_000_000),
3990 1,
3991 ))
3992 .await;
3993
3994 let reconfig_state = epoch_store.get_reconfig_state_read_lock_guard();
3995 assert!(reconfig_state.is_reject_all_certs());
3996 assert!(!reconfig_state.is_reject_all_tx());
3997 }
3998
3999 #[sim_test]
4000 async fn test_epoch_close_deadline_is_inert_without_deferred_transactions() {
4001 let state = TestAuthorityBuilder::new()
4002 .with_protocol_config(epoch_close_deadline_config(Some(100)))
4003 .build()
4004 .await;
4005 let epoch_store = state.epoch_store_for_testing();
4006 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
4007 let mut setup = setup_consensus_handler_for_testing(&state).await;
4008
4009 setup
4013 .consensus_handler
4014 .handle_consensus_commit_for_test(TestConsensusCommit::empty(1, scheduled_end + 100, 1))
4015 .await;
4016
4017 assert!(
4018 epoch_store
4019 .get_reconfig_state_read_lock_guard()
4020 .is_reject_all_tx()
4021 );
4022 let checkpoints = epoch_store.get_pending_checkpoints(None);
4023 assert!(checkpoints.last().unwrap().1.details.last_of_epoch);
4024 assert_eq!(
4025 setup
4026 .consensus_handler
4027 .metrics
4028 .consensus_handler_dropped_transactions
4029 .with_label_values(&["epoch_close_deadline"])
4030 .get(),
4031 0
4032 );
4033 }
4034
4035 #[sim_test]
4036 async fn test_epoch_close_deadline_counts_abandoned_transactions_and_closes_first() {
4037 let state = TestAuthorityBuilder::new()
4038 .with_protocol_config(epoch_close_deadline_config(Some(100)))
4039 .build()
4040 .await;
4041 let epoch_store = state.epoch_store_for_testing();
4042 let key = DeferralKey::new_for_consensus_round(u64::MAX, 1);
4043 epoch_store.insert_deferred_transactions_for_test(key, vec![user_txn(1), user_txn(2)]);
4044 let setup = setup_consensus_handler_for_testing(&state).await;
4045 let mut handler_state = CommitHandlerState::new(&epoch_store, 1);
4046
4047 let (reconfig_state, final_round, abandoned) = setup
4048 .consensus_handler
4049 .advance_end_of_epoch_state_machine(&mut handler_state, true);
4050
4051 assert!(reconfig_state.is_reject_all_tx());
4052 assert!(final_round);
4053 let abandoned = abandoned.expect("deferred transactions must be reported as abandoned");
4054 assert_eq!(abandoned.count, 2);
4055 assert_eq!(abandoned.sample.len(), 2);
4056 assert!(
4059 handler_state
4060 .output
4061 .get_deleted_deferred_txn_keys()
4062 .any(|deleted| deleted == key)
4063 );
4064 }
4065
4066 #[sim_test]
4067 async fn test_epoch_close_deadline_does_not_report_transactions_drained_in_commit() {
4068 let state = TestAuthorityBuilder::new()
4069 .with_protocol_config(epoch_close_deadline_config(Some(100)))
4070 .build()
4071 .await;
4072 let epoch_store = state.epoch_store_for_testing();
4073 let key = DeferralKey::new_for_consensus_round(1, 0);
4074 epoch_store.insert_deferred_transactions_for_test(key, vec![user_txn(1), user_txn(2)]);
4075 let setup = setup_consensus_handler_for_testing(&state).await;
4076 let mut handler_state = CommitHandlerState::new(&epoch_store, 1);
4077 handler_state
4078 .output
4079 .delete_loaded_deferred_transactions(&[key]);
4080
4081 let (reconfig_state, final_round, abandoned) = setup
4082 .consensus_handler
4083 .advance_end_of_epoch_state_machine(&mut handler_state, true);
4084
4085 assert!(reconfig_state.is_reject_all_tx());
4086 assert!(final_round);
4087 assert!(abandoned.is_none());
4088 }
4089
4090 #[cfg(debug_assertions)]
4094 #[tokio::test(flavor = "current_thread")]
4095 #[should_panic(
4096 expected = "Epoch close deadline reached with unscheduled deferred transactions"
4097 )]
4098 async fn test_epoch_close_deadline_timestamp_jump_abandons_fresh_deferral() {
4099 use sui_protocol_config::{ExecutionTimeEstimateParams, PerObjectCongestionControlMode};
4100
4101 let execution_time_params = ExecutionTimeEstimateParams {
4102 target_utilization: 1,
4103 allowed_txn_cost_overage_burst_limit_us: 0,
4104 max_estimate_us: u64::MAX,
4105 randomness_scalar: 100,
4106 stored_observations_num_included_checkpoints: 10,
4107 stored_observations_limit: u64::MAX,
4108 stake_weighted_median_threshold: 0,
4109 default_none_duration_for_new_keys: true,
4110 observations_chunk_size: None,
4111 };
4112 let mut protocol_config = epoch_close_deadline_config(Some(100));
4113 protocol_config.set_per_object_congestion_control_mode_for_testing(
4114 PerObjectCongestionControlMode::ExecutionTimeEstimate(execution_time_params),
4115 );
4116 protocol_config.set_max_deferral_rounds_for_congestion_control_for_testing(1_000);
4117
4118 let (sender, keypair) = deterministic_random_account_key();
4119 let gas_objects: Vec<_> = (0..4)
4120 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4121 .collect();
4122 let shared_object = Object::shared_for_testing();
4123 let mut starting_objects = gas_objects.clone();
4124 starting_objects.push(shared_object.clone());
4125 let state = TestAuthorityBuilder::new()
4126 .with_starting_objects(&starting_objects)
4127 .with_protocol_config(protocol_config)
4128 .build()
4129 .await;
4130 let mut consensus_transactions = Vec::new();
4131 for gas_object in gas_objects {
4132 let transaction = test_user_transaction(
4133 &state,
4134 sender,
4135 &keypair,
4136 gas_object,
4137 vec![shared_object.clone()],
4138 )
4139 .await;
4140 consensus_transactions.push(ConsensusTransaction::new_user_transaction_v2_message(
4141 &state.name,
4142 transaction.into(),
4143 ));
4144 }
4145 let epoch_store = state.epoch_store_for_testing();
4146 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
4147 let mut setup = setup_consensus_handler_for_testing(&state).await;
4148
4149 setup
4150 .consensus_handler
4151 .handle_consensus_commit_for_test(TestConsensusCommit::new(
4152 consensus_transactions,
4153 1,
4154 scheduled_end + 100,
4155 1,
4156 ))
4157 .await;
4158 }
4159
4160 #[sim_test]
4161 async fn test_consensus_commit_handler() {
4162 telemetry_subscribers::init_for_testing();
4163
4164 let (sender, keypair) = deterministic_random_account_key();
4167 let gas_objects: Vec<Object> = (0..12)
4169 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4170 .collect();
4171 let owned_objects: Vec<Object> = (0..4)
4173 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4174 .collect();
4175 let shared_objects: Vec<Object> = (0..6)
4177 .map(|_| Object::shared_for_testing())
4178 .collect::<Vec<_>>();
4179 let mut all_objects = gas_objects.clone();
4180 all_objects.extend(owned_objects.clone());
4181 all_objects.extend(shared_objects.clone());
4182
4183 let network_config =
4184 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
4185 .with_objects(all_objects.clone())
4186 .build();
4187
4188 let state = TestAuthorityBuilder::new()
4189 .with_network_config(&network_config, 0)
4190 .build()
4191 .await;
4192
4193 let epoch_store = state.epoch_store_for_testing().clone();
4194 let new_epoch_start_state = epoch_store.epoch_start_state();
4195 let consensus_committee = new_epoch_start_state.get_consensus_committee();
4196
4197 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4198
4199 let throughput_calculator = ConsensusThroughputCalculator::new(None, metrics.clone());
4200
4201 let backpressure_manager = BackpressureManager::new_for_tests();
4202 let settlement_scheduler = SettlementScheduler::new(
4203 state.execution_scheduler().as_ref().clone(),
4204 state.get_transaction_cache_reader().clone(),
4205 state.metrics.clone(),
4206 );
4207 let mut consensus_handler = ConsensusHandler::new(
4208 epoch_store,
4209 Arc::new(CheckpointServiceNoop {}),
4210 settlement_scheduler,
4211 state.get_object_cache_reader().clone(),
4212 consensus_committee.clone(),
4213 metrics,
4214 Arc::new(throughput_calculator),
4215 backpressure_manager.subscribe(),
4216 state.traffic_controller.clone(),
4217 None,
4218 state.consensus_gasless_counter.clone(),
4219 state.transaction_deny_config_manager().clone(),
4220 );
4221
4222 let mut user_transactions = vec![];
4224 for (i, gas_object) in gas_objects[0..8].iter().enumerate() {
4225 let input_object = if i % 2 == 0 {
4226 owned_objects.get(i / 2).unwrap().clone()
4227 } else {
4228 shared_objects.get(i / 2).unwrap().clone()
4229 };
4230 let transaction = test_user_transaction(
4231 &state,
4232 sender,
4233 &keypair,
4234 gas_object.clone(),
4235 vec![input_object],
4236 )
4237 .await;
4238 user_transactions.push(transaction);
4239 }
4240
4241 for (i, gas_object) in gas_objects[8..12].iter().enumerate() {
4244 let shared_object = if i < 2 {
4245 shared_objects[4].clone()
4246 } else {
4247 shared_objects[5].clone()
4248 };
4249 let transaction = test_user_transaction(
4250 &state,
4251 sender,
4252 &keypair,
4253 gas_object.clone(),
4254 vec![shared_object],
4255 )
4256 .await;
4257 user_transactions.push(transaction);
4258 }
4259
4260 let mut blocks = Vec::new();
4262 for (i, consensus_transaction) in user_transactions
4263 .iter()
4264 .cloned()
4265 .map(|t| ConsensusTransaction::new_user_transaction_v2_message(&state.name, t.into()))
4266 .enumerate()
4267 {
4268 let transaction_bytes = bcs::to_bytes(&consensus_transaction).unwrap();
4269 let block = VerifiedBlock::new_for_test(
4270 TestBlock::new(100 + i as u32, (i % consensus_committee.size()) as u32)
4271 .set_transactions(vec![Transaction::new(transaction_bytes)])
4272 .build(),
4273 );
4274
4275 blocks.push(block);
4276 }
4277
4278 let leader_block = blocks[0].clone();
4280 let committed_sub_dag = CommittedSubDag::new(
4281 leader_block.reference(),
4282 blocks.clone(),
4283 leader_block.timestamp_ms(),
4284 CommitRef::new(10, CommitDigest::MIN),
4285 );
4286
4287 backpressure_manager.set_backpressure(true);
4289 backpressure_manager.update_highest_certified_checkpoint(1);
4291
4292 {
4294 let waiter =
4295 consensus_handler.handle_consensus_commit_for_test(committed_sub_dag.clone());
4296 pin_mut!(waiter);
4297
4298 tokio::time::timeout(std::time::Duration::from_secs(5), &mut waiter)
4300 .await
4301 .unwrap_err();
4302
4303 backpressure_manager.set_backpressure(false);
4305
4306 tokio::time::timeout(std::time::Duration::from_secs(100), waiter)
4308 .await
4309 .unwrap();
4310 }
4311
4312 let num_blocks = blocks.len();
4314 let num_transactions = user_transactions.len();
4315 let last_consensus_stats_1 = consensus_handler.last_consensus_stats.clone();
4316 assert_eq!(
4317 last_consensus_stats_1.index.transaction_index,
4318 num_transactions as u64
4319 );
4320 assert_eq!(last_consensus_stats_1.index.sub_dag_index, 10_u64);
4321 assert_eq!(last_consensus_stats_1.index.last_committed_round, 100_u64);
4322 assert_eq!(
4323 last_consensus_stats_1.stats.get_num_messages(0),
4324 num_blocks as u64
4325 );
4326 assert_eq!(
4327 last_consensus_stats_1.stats.get_num_user_transactions(0),
4328 num_transactions as u64
4329 );
4330
4331 for (i, t) in user_transactions.iter().enumerate() {
4333 let digest = t.tx().digest();
4334 if tokio::time::timeout(
4335 std::time::Duration::from_secs(10),
4336 state.notify_read_effects_for_testing("", *digest),
4337 )
4338 .await
4339 .is_ok()
4340 {
4341 } else {
4343 panic!("User transaction {} {} did not execute", i, digest);
4344 }
4345 }
4346
4347 state.execution_scheduler().check_empty_for_testing().await;
4349 }
4350
4351 #[sim_test]
4352 async fn test_dropped_owned_object_lock_conflict_is_marked_processed() {
4353 telemetry_subscribers::init_for_testing();
4354
4355 let (sender, keypair) = deterministic_random_account_key();
4356 let gas_objects: Vec<Object> = (0..2)
4357 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4358 .collect();
4359 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4360 let mut all_objects = gas_objects.clone();
4361 all_objects.push(owned_object.clone());
4362
4363 let state = TestAuthorityBuilder::new()
4364 .with_starting_objects(&all_objects)
4365 .build()
4366 .await;
4367 let epoch_store = state.epoch_store_for_testing();
4368 let owned_object_ref = state
4369 .get_object(&owned_object.id())
4370 .unwrap()
4371 .compute_object_reference();
4372
4373 let winner = test_user_transaction(
4374 &state,
4375 sender,
4376 &keypair,
4377 gas_objects[0].clone(),
4378 vec![owned_object.clone()],
4379 )
4380 .await;
4381 let loser = test_user_transaction(
4382 &state,
4383 sender,
4384 &keypair,
4385 gas_objects[1].clone(),
4386 vec![owned_object.clone()],
4387 )
4388 .await;
4389
4390 let winner_digest = *winner.tx().digest();
4391 let loser_digest = *loser.tx().digest();
4392 assert_ne!(winner_digest, loser_digest);
4393
4394 let winner_consensus_tx =
4395 ConsensusTransaction::new_user_transaction_v2_message(&state.name, winner.into());
4396 let loser_consensus_tx =
4397 ConsensusTransaction::new_user_transaction_v2_message(&state.name, loser.into());
4398 let winner_key = SequencedConsensusTransactionKey::External(winner_consensus_tx.key());
4399 let loser_key = SequencedConsensusTransactionKey::External(loser_consensus_tx.key());
4400
4401 let round = 100;
4402 let commit = TestConsensusCommit::new(
4403 vec![winner_consensus_tx, loser_consensus_tx],
4404 round as u64,
4405 1_000,
4406 10,
4407 );
4408 let mut setup = setup_consensus_handler_for_testing(&state).await;
4409 setup
4410 .consensus_handler
4411 .handle_consensus_commit_for_test(commit)
4412 .await;
4413 assert_eq!(
4414 setup
4415 .consensus_handler
4416 .metrics
4417 .consensus_handler_dropped_transactions
4418 .with_label_values(&["lock_conflict"])
4419 .get(),
4420 1
4421 );
4422
4423 let block = BlockRef {
4424 author: consensus_config::AuthorityIndex::ZERO,
4425 round,
4426 digest: Default::default(),
4427 };
4428 assert!(matches!(
4429 epoch_store
4430 .consensus_tx_status_cache
4431 .notify_read_transaction_status(ConsensusPosition {
4432 epoch: epoch_store.epoch(),
4433 block,
4434 index: 0,
4435 })
4436 .await,
4437 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Finalized)
4438 ));
4439 assert!(matches!(
4440 epoch_store
4441 .consensus_tx_status_cache
4442 .notify_read_transaction_status(ConsensusPosition {
4443 epoch: epoch_store.epoch(),
4444 block,
4445 index: 1,
4446 })
4447 .await,
4448 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4449 ));
4450
4451 let locks = epoch_store.get_owned_object_locks_map(&[owned_object_ref]);
4452 assert_eq!(locks.get(&owned_object_ref), Some(&winner_digest));
4453 assert!(
4454 epoch_store
4455 .is_consensus_message_processed(&winner_key)
4456 .unwrap()
4457 );
4458 assert!(
4459 epoch_store
4460 .is_consensus_message_processed(&loser_key)
4461 .unwrap()
4462 );
4463 tokio::time::timeout(
4466 std::time::Duration::from_secs(5),
4467 epoch_store.consensus_messages_processed_notify(vec![loser_key]),
4468 )
4469 .await
4470 .expect("processed notification for dropped transaction should resolve");
4471 }
4472
4473 #[sim_test]
4474 async fn test_rejected_transaction_sets_status_and_is_not_marked_processed() {
4475 telemetry_subscribers::init_for_testing();
4476
4477 let (sender, keypair) = deterministic_random_account_key();
4478 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4479 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4480
4481 let state = TestAuthorityBuilder::new()
4482 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4483 .build()
4484 .await;
4485 let epoch_store = state.epoch_store_for_testing();
4486
4487 let transaction =
4488 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4489 let consensus_tx =
4490 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4491 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4492
4493 let round = 100;
4494 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10)
4495 .with_rejected_indices([0]);
4496 let mut setup = setup_consensus_handler_for_testing(&state).await;
4497 setup
4498 .consensus_handler
4499 .handle_consensus_commit_for_test(commit)
4500 .await;
4501
4502 let block = BlockRef {
4504 author: consensus_config::AuthorityIndex::ZERO,
4505 round,
4506 digest: Default::default(),
4507 };
4508 assert!(matches!(
4509 epoch_store
4510 .consensus_tx_status_cache
4511 .notify_read_transaction_status(ConsensusPosition {
4512 epoch: epoch_store.epoch(),
4513 block,
4514 index: 0,
4515 })
4516 .await,
4517 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Rejected)
4518 ));
4519 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4523 }
4524
4525 #[sim_test]
4526 async fn test_user_transaction_ignored_at_epoch_close_sets_dropped_status() {
4527 telemetry_subscribers::init_for_testing();
4528
4529 let (sender, keypair) = deterministic_random_account_key();
4530 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4531 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4532
4533 let state = TestAuthorityBuilder::new()
4534 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4535 .build()
4536 .await;
4537 let epoch_store = state.epoch_store_for_testing();
4538
4539 let transaction =
4540 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4541 let consensus_tx =
4542 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4543 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4544
4545 {
4547 let mut guard = epoch_store.get_reconfig_state_write_lock_guard();
4548 guard.close_all_certs();
4549 }
4550
4551 let round = 100;
4552 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10);
4553 let mut setup = setup_consensus_handler_for_testing(&state).await;
4554 setup
4555 .consensus_handler
4556 .handle_consensus_commit_for_test(commit)
4557 .await;
4558
4559 let block = BlockRef {
4562 author: consensus_config::AuthorityIndex::ZERO,
4563 round,
4564 digest: Default::default(),
4565 };
4566 let status = tokio::time::timeout(
4567 std::time::Duration::from_secs(5),
4568 epoch_store
4569 .consensus_tx_status_cache
4570 .notify_read_transaction_status(ConsensusPosition {
4571 epoch: epoch_store.epoch(),
4572 block,
4573 index: 0,
4574 }),
4575 )
4576 .await
4577 .expect("transaction ignored at epoch close should receive a terminal status");
4578 assert!(matches!(
4579 status,
4580 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4581 ));
4582 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4583 assert_eq!(
4584 setup
4585 .consensus_handler
4586 .metrics
4587 .consensus_handler_dropped_transactions
4588 .with_label_values(&["end_of_epoch"])
4589 .get(),
4590 1
4591 );
4592 }
4593
4594 #[sim_test]
4595 async fn test_user_transaction_after_end_of_publish_sets_dropped_status() {
4596 telemetry_subscribers::init_for_testing();
4597
4598 let (sender, keypair) = deterministic_random_account_key();
4599 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4600 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4601
4602 let state = TestAuthorityBuilder::new()
4603 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4604 .build()
4605 .await;
4606 let epoch_store = state.epoch_store_for_testing();
4607
4608 let transaction =
4609 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4610 let consensus_tx =
4611 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4612 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4613
4614 epoch_store
4618 .end_of_publish
4619 .try_lock()
4620 .unwrap()
4621 .insert_generic(state.name, ());
4622
4623 let round = 100;
4624 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10);
4625 let mut setup = setup_consensus_handler_for_testing(&state).await;
4626 setup
4627 .consensus_handler
4628 .handle_consensus_commit_for_test(commit)
4629 .await;
4630
4631 let block = BlockRef {
4632 author: consensus_config::AuthorityIndex::ZERO,
4633 round,
4634 digest: Default::default(),
4635 };
4636 let status = tokio::time::timeout(
4637 std::time::Duration::from_secs(5),
4638 epoch_store
4639 .consensus_tx_status_cache
4640 .notify_read_transaction_status(ConsensusPosition {
4641 epoch: epoch_store.epoch(),
4642 block,
4643 index: 0,
4644 }),
4645 )
4646 .await
4647 .expect("transaction ignored after EndOfPublish should receive a terminal status");
4648 assert!(matches!(
4649 status,
4650 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4651 ));
4652 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4653 assert_eq!(
4654 setup
4655 .consensus_handler
4656 .metrics
4657 .consensus_handler_dropped_transactions
4658 .with_label_values(&["end_of_publish"])
4659 .get(),
4660 1
4661 );
4662 }
4663
4664 fn to_short_strings(txs: Vec<VerifiedExecutableTransactionWithAliases>) -> Vec<String> {
4665 txs.into_iter()
4666 .map(|tx| format!("transaction({})", tx.tx().transaction_data().gas_price()))
4667 .collect()
4668 }
4669
4670 #[test]
4671 fn test_order_by_gas_price() {
4672 let mut v = vec![user_txn(42), user_txn(100)];
4673 PostConsensusTxReorder::reorder(&mut v, ConsensusTransactionOrdering::ByGasPrice);
4674 assert_eq!(
4675 to_short_strings(v),
4676 vec![
4677 "transaction(100)".to_string(),
4678 "transaction(42)".to_string(),
4679 ]
4680 );
4681
4682 let mut v = vec![
4683 user_txn(1200),
4684 user_txn(12),
4685 user_txn(1000),
4686 user_txn(42),
4687 user_txn(100),
4688 user_txn(1000),
4689 ];
4690 PostConsensusTxReorder::reorder(&mut v, ConsensusTransactionOrdering::ByGasPrice);
4691 assert_eq!(
4692 to_short_strings(v),
4693 vec![
4694 "transaction(1200)".to_string(),
4695 "transaction(1000)".to_string(),
4696 "transaction(1000)".to_string(),
4697 "transaction(100)".to_string(),
4698 "transaction(42)".to_string(),
4699 "transaction(12)".to_string(),
4700 ]
4701 );
4702 }
4703
4704 #[sim_test]
4705 async fn test_checkpoint_signature_dedup() {
4706 telemetry_subscribers::init_for_testing();
4707
4708 let network_config =
4709 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir().build();
4710 let state = TestAuthorityBuilder::new()
4711 .with_network_config(&network_config, 0)
4712 .build()
4713 .await;
4714
4715 let epoch_store = state.epoch_store_for_testing().clone();
4716 let consensus_committee = epoch_store.epoch_start_state().get_consensus_committee();
4717
4718 let make_signed = || {
4719 let epoch = epoch_store.epoch();
4720 let contents =
4721 CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
4722 let summary = CheckpointSummary::new(
4723 &ProtocolConfig::get_for_max_version_UNSAFE(),
4724 epoch,
4725 42, 10, &contents,
4728 None, GasCostSummary::default(),
4730 None, 0, Vec::new(), Vec::new(), );
4735 SignedCheckpointSummary::new(epoch, summary, &*state.secret, state.name)
4736 };
4737
4738 let v2_s1 = make_signed();
4740 let v2_s1_clone = v2_s1.clone();
4741 let v2_digest_a = v2_s1.data().digest();
4742 let v2_a =
4743 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4744 summary: v2_s1,
4745 });
4746
4747 let v2_s2 = make_signed();
4748 let v2_digest_b = v2_s2.data().digest();
4749 let v2_b =
4750 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4751 summary: v2_s2,
4752 });
4753
4754 assert_ne!(v2_digest_a, v2_digest_b);
4755
4756 assert_eq!(v2_s1_clone.data().digest(), v2_digest_a);
4758 let v2_dup =
4759 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4760 summary: v2_s1_clone,
4761 });
4762
4763 let to_tx = |ct: &ConsensusTransaction| Transaction::new(bcs::to_bytes(ct).unwrap());
4764 let block = VerifiedBlock::new_for_test(
4765 TestBlock::new(100, 0)
4766 .set_transactions(vec![to_tx(&v2_a), to_tx(&v2_b), to_tx(&v2_dup)])
4767 .build(),
4768 );
4769 let commit = CommittedSubDag::new(
4770 block.reference(),
4771 vec![block.clone()],
4772 block.timestamp_ms(),
4773 CommitRef::new(10, CommitDigest::MIN),
4774 );
4775
4776 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4777 let throughput = ConsensusThroughputCalculator::new(None, metrics.clone());
4778 let backpressure = BackpressureManager::new_for_tests();
4779 let settlement_scheduler = SettlementScheduler::new(
4780 state.execution_scheduler().as_ref().clone(),
4781 state.get_transaction_cache_reader().clone(),
4782 state.metrics.clone(),
4783 );
4784 let mut handler = ConsensusHandler::new(
4785 epoch_store.clone(),
4786 Arc::new(CheckpointServiceNoop {}),
4787 settlement_scheduler,
4788 state.get_object_cache_reader().clone(),
4789 consensus_committee.clone(),
4790 metrics,
4791 Arc::new(throughput),
4792 backpressure.subscribe(),
4793 state.traffic_controller.clone(),
4794 None,
4795 state.consensus_gasless_counter.clone(),
4796 state.transaction_deny_config_manager().clone(),
4797 );
4798
4799 handler.handle_consensus_commit_for_test(commit).await;
4800
4801 use crate::consensus_handler::SequencedConsensusTransactionKey as SK;
4802 use sui_types::messages_consensus::ConsensusTransactionKey as CK;
4803
4804 let v2_key_a = SK::External(CK::CheckpointSignatureV2(state.name, 42, v2_digest_a));
4806 let v2_key_b = SK::External(CK::CheckpointSignatureV2(state.name, 42, v2_digest_b));
4807 assert!(
4808 epoch_store
4809 .is_consensus_message_processed(&v2_key_a)
4810 .unwrap()
4811 );
4812 assert!(
4813 epoch_store
4814 .is_consensus_message_processed(&v2_key_b)
4815 .unwrap()
4816 );
4817 }
4818
4819 #[sim_test]
4820 async fn test_verify_consensus_transaction_filters_mismatched_authorities() {
4821 telemetry_subscribers::init_for_testing();
4822
4823 let network_config =
4824 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir().build();
4825 let state = TestAuthorityBuilder::new()
4826 .with_network_config(&network_config, 0)
4827 .build()
4828 .await;
4829
4830 let epoch_store = state.epoch_store_for_testing().clone();
4831 let consensus_committee = epoch_store.epoch_start_state().get_consensus_committee();
4832
4833 use fastcrypto::traits::KeyPair;
4835 let (_, wrong_keypair) = sui_types::crypto::get_authority_key_pair();
4836 let wrong_authority: AuthorityName = wrong_keypair.public().into();
4837
4838 let mismatched_eop = ConsensusTransaction::new_end_of_publish(wrong_authority);
4840
4841 let valid_eop = ConsensusTransaction::new_end_of_publish(state.name);
4843
4844 let epoch = epoch_store.epoch();
4846 let contents =
4847 CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
4848 let summary = CheckpointSummary::new(
4849 &ProtocolConfig::get_for_max_version_UNSAFE(),
4850 epoch,
4851 42, 10, &contents,
4854 None, GasCostSummary::default(),
4856 None, 0, Vec::new(), Vec::new(), );
4861
4862 let mismatched_checkpoint_signed =
4864 SignedCheckpointSummary::new(epoch, summary.clone(), &wrong_keypair, wrong_authority);
4865 let mismatched_checkpoint_digest = mismatched_checkpoint_signed.data().digest();
4866 let mismatched_checkpoint =
4867 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4868 summary: mismatched_checkpoint_signed,
4869 });
4870
4871 let valid_checkpoint_signed =
4873 SignedCheckpointSummary::new(epoch, summary, &*state.secret, state.name);
4874 let valid_checkpoint_digest = valid_checkpoint_signed.data().digest();
4875 let valid_checkpoint =
4876 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4877 summary: valid_checkpoint_signed,
4878 });
4879
4880 let to_tx = |ct: &ConsensusTransaction| Transaction::new(bcs::to_bytes(ct).unwrap());
4881
4882 let block = VerifiedBlock::new_for_test(
4884 TestBlock::new(100, 0)
4885 .set_transactions(vec![
4886 to_tx(&mismatched_eop),
4887 to_tx(&valid_eop),
4888 to_tx(&mismatched_checkpoint),
4889 to_tx(&valid_checkpoint),
4890 ])
4891 .build(),
4892 );
4893 let commit = CommittedSubDag::new(
4894 block.reference(),
4895 vec![block.clone()],
4896 block.timestamp_ms(),
4897 CommitRef::new(10, CommitDigest::MIN),
4898 );
4899
4900 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4901 let throughput = ConsensusThroughputCalculator::new(None, metrics.clone());
4902 let backpressure = BackpressureManager::new_for_tests();
4903 let settlement_scheduler = SettlementScheduler::new(
4904 state.execution_scheduler().as_ref().clone(),
4905 state.get_transaction_cache_reader().clone(),
4906 state.metrics.clone(),
4907 );
4908 let mut handler = ConsensusHandler::new(
4909 epoch_store.clone(),
4910 Arc::new(CheckpointServiceNoop {}),
4911 settlement_scheduler,
4912 state.get_object_cache_reader().clone(),
4913 consensus_committee.clone(),
4914 metrics,
4915 Arc::new(throughput),
4916 backpressure.subscribe(),
4917 state.traffic_controller.clone(),
4918 None,
4919 state.consensus_gasless_counter.clone(),
4920 state.transaction_deny_config_manager().clone(),
4921 );
4922
4923 handler.handle_consensus_commit_for_test(commit).await;
4924
4925 use crate::consensus_handler::SequencedConsensusTransactionKey as SK;
4926 use sui_types::messages_consensus::ConsensusTransactionKey as CK;
4927
4928 let valid_eop_key = SK::External(CK::EndOfPublish(state.name));
4930 assert!(
4931 epoch_store
4932 .is_consensus_message_processed(&valid_eop_key)
4933 .unwrap(),
4934 "Valid EndOfPublish should have been processed"
4935 );
4936
4937 let valid_checkpoint_key = SK::External(CK::CheckpointSignatureV2(
4938 state.name,
4939 42,
4940 valid_checkpoint_digest,
4941 ));
4942 assert!(
4943 epoch_store
4944 .is_consensus_message_processed(&valid_checkpoint_key)
4945 .unwrap(),
4946 "Valid CheckpointSignature should have been processed"
4947 );
4948
4949 let mismatched_eop_key = SK::External(CK::EndOfPublish(wrong_authority));
4951 assert!(
4952 !epoch_store
4953 .is_consensus_message_processed(&mismatched_eop_key)
4954 .unwrap(),
4955 "Mismatched EndOfPublish should NOT have been processed (filtered by verify_consensus_transaction)"
4956 );
4957
4958 let mismatched_checkpoint_key = SK::External(CK::CheckpointSignatureV2(
4959 wrong_authority,
4960 42,
4961 mismatched_checkpoint_digest,
4962 ));
4963 assert!(
4964 !epoch_store
4965 .is_consensus_message_processed(&mismatched_checkpoint_key)
4966 .unwrap(),
4967 "Mismatched CheckpointSignature should NOT have been processed (filtered by verify_consensus_transaction)"
4968 );
4969 }
4970
4971 #[sim_test]
4977 async fn test_deny_config_updates_applied_at_commit() {
4978 telemetry_subscribers::init_for_testing();
4979
4980 let _guard = ProtocolConfig::apply_overrides_for_testing(|_, mut c| {
4981 c.set_share_transaction_deny_config_in_consensus_for_testing(true);
4982 c
4983 });
4984
4985 let network_config =
4987 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
4988 .committee_size(std::num::NonZeroUsize::new(1).unwrap())
4989 .build();
4990 let state = TestAuthorityBuilder::new()
4991 .with_network_config(&network_config, 0)
4992 .build()
4993 .await;
4994 let epoch_store = state.epoch_store_for_testing().clone();
4995 let consensus_committee = epoch_store.epoch_start_state().get_consensus_committee();
4996 let manager = state.transaction_deny_config_manager().clone();
4997
4998 let now_ms = crate::authority::AuthorityState::unixtime_now_ms();
4999 let make_update = |authority, generation| {
5000 ConsensusTransaction::new_update_transaction_deny_config(
5001 SharedTransactionDenyConfig::V1(
5002 sui_types::messages_consensus::SharedTransactionDenyConfigV1 {
5003 authority,
5004 generation,
5005 rules: Some(sui_types::transaction_deny_rules::TransactionDenyRules {
5006 package_publish_disabled: true,
5007 ..Default::default()
5008 }),
5009 },
5010 ),
5011 )
5012 };
5013
5014 let spoofed = make_update(AuthorityName::ZERO, now_ms + 1);
5015 let sane = make_update(state.name, now_ms);
5016
5017 let to_tx = |ct: &ConsensusTransaction| Transaction::new(bcs::to_bytes(ct).unwrap());
5018 let block = VerifiedBlock::new_for_test(
5019 TestBlock::new(100, 0)
5020 .set_transactions(vec![to_tx(&spoofed), to_tx(&sane)])
5021 .build(),
5022 );
5023 let commit = CommittedSubDag::new(
5024 block.reference(),
5025 vec![block.clone()],
5026 block.timestamp_ms(),
5027 CommitRef::new(10, CommitDigest::MIN),
5028 );
5029
5030 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
5031 let throughput = ConsensusThroughputCalculator::new(None, metrics.clone());
5032 let backpressure = BackpressureManager::new_for_tests();
5033 let settlement_scheduler = SettlementScheduler::new(
5034 state.execution_scheduler().as_ref().clone(),
5035 state.get_transaction_cache_reader().clone(),
5036 state.metrics.clone(),
5037 );
5038 let mut handler = ConsensusHandler::new(
5039 epoch_store.clone(),
5040 Arc::new(CheckpointServiceNoop {}),
5041 settlement_scheduler,
5042 state.get_object_cache_reader().clone(),
5043 consensus_committee.clone(),
5044 metrics,
5045 Arc::new(throughput),
5046 backpressure.subscribe(),
5047 state.traffic_controller.clone(),
5048 None,
5049 state.consensus_gasless_counter.clone(),
5050 state.transaction_deny_config_manager().clone(),
5051 );
5052
5053 handler.handle_consensus_commit_for_test(commit).await;
5054
5055 let snapshot = manager.peer_configs_snapshot();
5056 assert_eq!(
5057 snapshot.get(&state.name).map(|msg| msg.generation()),
5058 Some(now_ms),
5059 "committed update from the block author should be applied at commit time"
5060 );
5061 assert!(
5062 !snapshot.contains_key(&AuthorityName::ZERO),
5063 "spoofed authority claim should be dropped"
5064 );
5065 }
5066
5067 fn randomness_user_txn() -> VerifiedExecutableTransactionWithAliases {
5070 let (committee, keypairs) = Committee::new_simple_test_committee();
5071 let (sender, sender_keypair) = deterministic_random_account_key();
5072 let mut builder = ProgrammableTransactionBuilder::new();
5073 builder
5074 .obj(ObjectArg::SharedObject {
5075 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
5076 initial_shared_version: 1.into(),
5077 mutability: SharedObjectMutability::Immutable,
5078 })
5079 .unwrap();
5080 let tx = sui_types::transaction::Transaction::from_data_and_signer(
5081 TransactionData::new_programmable(
5082 sender,
5083 vec![random_object_ref()],
5084 builder.finish(),
5085 1_000_000,
5086 1_000,
5087 ),
5088 vec![&sender_keypair],
5089 );
5090 let tx = VerifiedExecutableTransaction::new_from_certificate(
5091 VerifiedCertificate::new_unchecked(
5092 CertifiedTransaction::new_from_keypairs_for_testing(
5093 tx.into_data(),
5094 &keypairs,
5095 &committee,
5096 ),
5097 ),
5098 );
5099 VerifiedExecutableTransactionWithAliases::no_aliases(tx)
5100 }
5101
5102 fn seed_deferral_key_collision(
5109 epoch_store: &AuthorityPerEpochStore,
5110 ) -> (TransactionDigest, TransactionDigest) {
5111 let parked = randomness_user_txn();
5112 let redeferred = randomness_user_txn();
5113 let parked_digest = *parked.tx().digest();
5114 let redeferred_digest = *redeferred.tx().digest();
5115 epoch_store.insert_deferred_transactions_for_test(
5118 DeferralKey::new_for_randomness(1),
5119 vec![parked],
5120 );
5121 epoch_store.insert_deferred_transactions_for_test(
5124 DeferralKey::new_for_consensus_round(2, 1),
5125 vec![redeferred],
5126 );
5127 (parked_digest, redeferred_digest)
5128 }
5129
5130 #[sim_test]
5133 async fn test_deferral_key_collision_merges_entries() {
5134 let state = TestAuthorityBuilder::new().build().await;
5135 let epoch_store = state.epoch_store_for_testing();
5136 let (parked_digest, redeferred_digest) = seed_deferral_key_collision(&epoch_store);
5137
5138 let mid_epoch = epoch_store
5139 .next_reconfiguration_timestamp_ms()
5140 .saturating_sub(10_000);
5141 let mut setup = setup_consensus_handler_for_testing(&state).await;
5142 setup
5143 .consensus_handler
5144 .handle_consensus_commit_for_test(TestConsensusCommit::empty(2, mid_epoch, 1))
5145 .await;
5146
5147 let deferred = epoch_store.get_all_deferred_transactions_for_test();
5148 assert_eq!(deferred.len(), 1);
5149 let (key, txns) = &deferred[0];
5150 assert_eq!(*key, DeferralKey::new_for_randomness(1));
5151 let digests: Vec<_> = txns.iter().map(|t| *t.tx().digest()).collect();
5152 assert_eq!(digests, vec![parked_digest, redeferred_digest]);
5154 }
5155
5156 #[tokio::test(flavor = "current_thread")]
5160 #[should_panic(expected = "Deferral key collision displaced finalized transactions")]
5161 async fn test_deferral_key_collision_displaces_randomness_deferrals() {
5162 let mut protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
5163 protocol_config.set_merge_colliding_deferrals_for_testing(false);
5164 let state = TestAuthorityBuilder::new()
5165 .with_protocol_config(protocol_config)
5166 .build()
5167 .await;
5168 let epoch_store = state.epoch_store_for_testing();
5169 seed_deferral_key_collision(&epoch_store);
5170
5171 let mid_epoch = epoch_store
5172 .next_reconfiguration_timestamp_ms()
5173 .saturating_sub(10_000);
5174 let mut setup = setup_consensus_handler_for_testing(&state).await;
5175 setup
5176 .consensus_handler
5177 .handle_consensus_commit_for_test(TestConsensusCommit::empty(2, mid_epoch, 1))
5178 .await;
5179 }
5180
5181 fn user_txn(gas_price: u64) -> VerifiedExecutableTransactionWithAliases {
5182 let (committee, keypairs) = Committee::new_simple_test_committee();
5183 let (sender, sender_keypair) = deterministic_random_account_key();
5184 let tx = sui_types::transaction::Transaction::from_data_and_signer(
5185 TransactionData::new_transfer(
5186 SuiAddress::default(),
5187 FullObjectRef::from_fastpath_ref(random_object_ref()),
5188 sender,
5189 random_object_ref(),
5190 1000 * gas_price,
5191 gas_price,
5192 ),
5193 vec![&sender_keypair],
5194 );
5195 let tx = VerifiedExecutableTransaction::new_from_certificate(
5196 VerifiedCertificate::new_unchecked(
5197 CertifiedTransaction::new_from_keypairs_for_testing(
5198 tx.into_data(),
5199 &keypairs,
5200 &committee,
5201 ),
5202 ),
5203 );
5204 VerifiedExecutableTransactionWithAliases::no_aliases(tx)
5205 }
5206
5207 mod checkpoint_queue_tests {
5208 use super::*;
5209 use consensus_core::CommitRef;
5210 use sui_types::digests::Digest;
5211
5212 fn make_chunk(tx_count: usize, height: u64) -> Chunk {
5213 Chunk {
5214 schedulables: (0..tx_count)
5215 .map(|_| Schedulable::Transaction(user_txn(1000).into_tx()))
5216 .collect(),
5217 settlement: None,
5218 height,
5219 }
5220 }
5221
5222 fn make_commit_ref(index: u32) -> CommitRef {
5223 CommitRef {
5224 index,
5225 digest: CommitDigest::MIN,
5226 }
5227 }
5228
5229 fn default_versions() -> HashMap<TransactionKey, AssignedVersions> {
5230 HashMap::new()
5231 }
5232
5233 #[test]
5234 fn test_flush_all_checkpoint_roots() {
5235 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 0);
5236 let versions = default_versions();
5237
5238 queue.push_chunk(
5239 make_chunk(5, 1),
5240 &versions,
5241 1000,
5242 make_commit_ref(1),
5243 Digest::default(),
5244 );
5245 queue.push_chunk(
5246 make_chunk(3, 2),
5247 &versions,
5248 1000,
5249 make_commit_ref(1),
5250 Digest::default(),
5251 );
5252
5253 let pending = queue.flush(1000, true);
5254
5255 assert!(pending.is_some());
5256 assert!(queue.pending_roots.is_empty());
5257 }
5258
5259 #[test]
5260 fn test_flush_respects_min_checkpoint_interval() {
5261 let min_interval = 200;
5262 let mut queue = CheckpointQueue::new_for_testing(1000, 0, 0, 1000, min_interval);
5263 let versions = default_versions();
5264
5265 queue.push_chunk(
5266 make_chunk(5, 1),
5267 &versions,
5268 1000,
5269 make_commit_ref(1),
5270 Digest::default(),
5271 );
5272
5273 let pending = queue.flush(1000 + min_interval - 1, false);
5274 assert!(pending.is_none());
5275 assert_eq!(queue.pending_roots.len(), 1);
5276
5277 let pending = queue.flush(1000 + min_interval, false);
5278 assert!(pending.is_some());
5279 assert!(queue.pending_roots.is_empty());
5280 }
5281
5282 #[test]
5283 fn test_push_chunk_flushes_when_exceeds_max() {
5284 let max_tx = 10;
5285 let mut queue = CheckpointQueue::new_for_testing(1000, 0, 0, max_tx, 0);
5286 let versions = default_versions();
5287
5288 queue.push_chunk(
5289 make_chunk(max_tx / 2 + 1, 1),
5290 &versions,
5291 1000,
5292 make_commit_ref(1),
5293 Digest::default(),
5294 );
5295
5296 let flushed = queue.push_chunk(
5297 make_chunk(max_tx / 2 + 1, 2),
5298 &versions,
5299 1000,
5300 make_commit_ref(2),
5301 Digest::default(),
5302 );
5303
5304 assert_eq!(flushed.len(), 1);
5305 assert_eq!(queue.pending_roots.len(), 1);
5306 }
5307
5308 #[test]
5309 fn test_multiple_chunks_merged_into_one_checkpoint() {
5310 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 200);
5311 let versions = default_versions();
5312
5313 queue.push_chunk(
5314 make_chunk(10, 1),
5315 &versions,
5316 1000,
5317 make_commit_ref(1),
5318 Digest::default(),
5319 );
5320 queue.push_chunk(
5321 make_chunk(10, 2),
5322 &versions,
5323 1000,
5324 make_commit_ref(2),
5325 Digest::default(),
5326 );
5327 queue.push_chunk(
5328 make_chunk(10, 3),
5329 &versions,
5330 1000,
5331 make_commit_ref(3),
5332 Digest::default(),
5333 );
5334
5335 let pending = queue.flush(1000, true).unwrap();
5336
5337 assert_eq!(pending.roots.len(), 3);
5338 }
5339
5340 #[test]
5341 fn test_push_chunk_handles_overflow() {
5342 let max_tx = 10;
5343 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, max_tx, 0);
5344 let versions = default_versions();
5345
5346 let flushed1 = queue.push_chunk(
5347 make_chunk(max_tx / 2, 1),
5348 &versions,
5349 1000,
5350 make_commit_ref(1),
5351 Digest::default(),
5352 );
5353 assert!(flushed1.is_empty());
5354
5355 let flushed2 = queue.push_chunk(
5356 make_chunk(max_tx / 2, 2),
5357 &versions,
5358 1000,
5359 make_commit_ref(2),
5360 Digest::default(),
5361 );
5362 assert!(flushed2.is_empty());
5363
5364 let flushed3 = queue.push_chunk(
5365 make_chunk(max_tx / 2, 3),
5366 &versions,
5367 1000,
5368 make_commit_ref(3),
5369 Digest::default(),
5370 );
5371 assert_eq!(flushed3.len(), 1);
5372
5373 let pending = queue.flush(1000, true);
5374
5375 for p in pending.iter().chain(flushed3.iter()) {
5376 let tx_count: usize = p.roots.iter().map(|r| r.tx_roots.len()).sum();
5377 assert!(tx_count <= max_tx);
5378 }
5379 }
5380
5381 #[test]
5382 fn test_checkpoint_uses_last_chunk_height() {
5383 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 0);
5384 let versions = default_versions();
5385
5386 queue.push_chunk(
5387 make_chunk(10, 100),
5388 &versions,
5389 1000,
5390 make_commit_ref(1),
5391 Digest::default(),
5392 );
5393 queue.push_chunk(
5394 make_chunk(10, 200),
5395 &versions,
5396 1000,
5397 make_commit_ref(2),
5398 Digest::default(),
5399 );
5400
5401 let pending = queue.flush(1000, true).unwrap();
5402
5403 assert_eq!(pending.details.checkpoint_height, 200);
5404 }
5405
5406 #[test]
5407 fn test_last_built_timestamp_updated_on_flush() {
5408 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 0);
5409 let versions = default_versions();
5410
5411 queue.push_chunk(
5412 make_chunk(10, 1),
5413 &versions,
5414 5000,
5415 make_commit_ref(1),
5416 Digest::default(),
5417 );
5418
5419 assert_eq!(queue.last_built_timestamp, 0);
5420
5421 let _ = queue.flush(5000, true);
5422
5423 assert_eq!(queue.last_built_timestamp, 5000);
5424 }
5425
5426 #[test]
5427 fn test_settlement_info_sent_through_channel() {
5428 let mut queue = CheckpointQueue::new_for_testing(0, 0, 5, 1000, 0);
5429 let versions = default_versions();
5430
5431 let chunk1 = Chunk {
5432 schedulables: vec![
5433 Schedulable::ConsensusCommitPrologue(0, 1, 0),
5434 Schedulable::ConsensusCommitPrologue(0, 2, 0),
5435 Schedulable::ConsensusCommitPrologue(0, 3, 0),
5436 ],
5437 settlement: Some(Schedulable::AccumulatorSettlement(1, 1)),
5438 height: 1,
5439 };
5440
5441 let chunk2 = Chunk {
5442 schedulables: vec![
5443 Schedulable::ConsensusCommitPrologue(0, 4, 0),
5444 Schedulable::ConsensusCommitPrologue(0, 5, 0),
5445 ],
5446 settlement: Some(Schedulable::AccumulatorSettlement(1, 2)),
5447 height: 2,
5448 };
5449
5450 queue.push_chunk(
5451 chunk1,
5452 &versions,
5453 1000,
5454 make_commit_ref(1),
5455 Digest::default(),
5456 );
5457 queue.push_chunk(
5458 chunk2,
5459 &versions,
5460 1000,
5461 make_commit_ref(1),
5462 Digest::default(),
5463 );
5464 }
5465
5466 #[test]
5467 fn test_settlement_checkpoint_seq_correct_after_flush() {
5468 let max_tx = 10;
5469 let initial_seq = 5;
5470 let (sender, mut receiver) = monitored_mpsc::unbounded_channel("test_settlement_seq");
5471 let mut queue =
5472 CheckpointQueue::new_for_testing_with_sender(0, 0, initial_seq, max_tx, 0, sender);
5473 let versions = default_versions();
5474
5475 let chunk1 = Chunk {
5477 schedulables: (0..max_tx / 2 + 1)
5478 .map(|_| Schedulable::Transaction(user_txn(1000).into_tx()))
5479 .collect(),
5480 settlement: Some(Schedulable::AccumulatorSettlement(1, 1)),
5481 height: 1,
5482 };
5483 queue.push_chunk(
5484 chunk1,
5485 &versions,
5486 1000,
5487 make_commit_ref(1),
5488 Digest::default(),
5489 );
5490
5491 let msg1 = receiver.try_recv().unwrap();
5493 let settlement1 = msg1.1.unwrap();
5494 assert_eq!(settlement1.checkpoint_seq, initial_seq);
5495
5496 let chunk2 = Chunk {
5498 schedulables: (0..max_tx / 2 + 1)
5499 .map(|_| Schedulable::Transaction(user_txn(1000).into_tx()))
5500 .collect(),
5501 settlement: Some(Schedulable::AccumulatorSettlement(1, 2)),
5502 height: 2,
5503 };
5504 let flushed = queue.push_chunk(
5505 chunk2,
5506 &versions,
5507 1000,
5508 make_commit_ref(2),
5509 Digest::default(),
5510 );
5511 assert_eq!(flushed.len(), 1);
5512 assert_eq!(flushed[0].details.checkpoint_seq, initial_seq);
5513
5514 let msg2 = receiver.try_recv().unwrap();
5517 let settlement2 = msg2.1.unwrap();
5518 assert_eq!(settlement2.checkpoint_seq, initial_seq + 1);
5519
5520 let pending = queue.flush_forced().unwrap();
5523 assert_eq!(pending.details.checkpoint_seq, settlement2.checkpoint_seq);
5524 }
5525
5526 #[test]
5527 fn test_checkpoint_seq_increments_on_flush() {
5528 let mut queue = CheckpointQueue::new_for_testing(0, 0, 10, 1000, 0);
5529 let versions = default_versions();
5530
5531 queue.push_chunk(
5532 make_chunk(5, 1),
5533 &versions,
5534 1000,
5535 make_commit_ref(1),
5536 Digest::default(),
5537 );
5538
5539 let pending = queue.flush(1000, true).unwrap();
5540
5541 assert_eq!(pending.details.checkpoint_seq, 10);
5542 assert_eq!(queue.current_checkpoint_seq, 11);
5543 }
5544
5545 #[test]
5546 fn test_multiple_chunks_with_overflow() {
5547 let max_tx = 10;
5548 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, max_tx, 0);
5549 let versions = default_versions();
5550
5551 let flushed1 = queue.push_chunk(
5552 make_chunk(max_tx / 2 + 1, 1),
5553 &versions,
5554 1000,
5555 make_commit_ref(1),
5556 Digest::default(),
5557 );
5558 let flushed2 = queue.push_chunk(
5559 make_chunk(max_tx / 2 + 1, 2),
5560 &versions,
5561 1000,
5562 make_commit_ref(1),
5563 Digest::default(),
5564 );
5565 let flushed3 = queue.push_chunk(
5566 make_chunk(max_tx / 2 + 1, 3),
5567 &versions,
5568 1000,
5569 make_commit_ref(1),
5570 Digest::default(),
5571 );
5572
5573 let all_flushed: Vec<_> = flushed1
5574 .into_iter()
5575 .chain(flushed2)
5576 .chain(flushed3)
5577 .collect();
5578 assert_eq!(all_flushed.len(), 2);
5579 assert_eq!(queue.pending_roots.len(), 1);
5580
5581 for p in &all_flushed {
5582 let tx_count: usize = p.roots.iter().map(|r| r.tx_roots.len()).sum();
5583 assert!(tx_count <= max_tx);
5584 }
5585 }
5586 }
5587}