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::{CommitConsumerMonitor, CommitIndex, CommitRef};
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,
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, ()>,
740 throughput_calculator: Arc<ConsensusThroughputCalculator>,
742
743 additional_consensus_state: AdditionalConsensusState,
744
745 backpressure_subscriber: BackpressureSubscriber,
746
747 traffic_controller: Option<Arc<TrafficController>>,
748
749 congestion_logger: Option<Arc<Mutex<CongestionCommitLogger>>>,
750
751 consensus_gasless_counter: Arc<ConsensusGaslessCounter>,
752
753 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
754
755 checkpoint_queue: Mutex<CheckpointQueue>,
756}
757
758const PROCESSED_CACHE_CAP: usize = 1024 * 1024;
759
760fn assert_supported_protocol_config(protocol_config: &ProtocolConfig) {
761 assert!(
762 matches!(
763 protocol_config.per_object_congestion_control_mode(),
764 PerObjectCongestionControlMode::ExecutionTimeEstimate(_)
765 ),
766 "support for congestion control modes other than PerObjectCongestionControlMode::ExecutionTimeEstimate has been removed"
767 );
768 assert!(
769 protocol_config.split_checkpoints_in_consensus_handler(),
770 "support for splitting checkpoints outside of consensus handler has been removed"
771 );
772 assert!(protocol_config.ignore_execution_time_observations_after_certs_closed());
773 assert!(protocol_config.record_time_estimate_processed());
774 assert!(protocol_config.prepend_prologue_tx_in_consensus_commit_in_checkpoints());
775 assert!(protocol_config.consensus_checkpoint_signature_key_includes_digest());
776 assert!(protocol_config.authority_capabilities_v2());
777 assert!(protocol_config.cancel_for_failed_dkg_early());
778 assert!(protocol_config.record_consensus_determined_version_assignments_in_prologue_v2());
779 assert!(protocol_config.record_additional_state_digest_in_prologue());
780 assert!(protocol_config.additional_consensus_digest_indirect_state());
781 assert!(protocol_config.include_cancelled_randomness_txns_in_prologue());
782 assert!(protocol_config.fix_checkpoint_signature_mapping());
783 assert!(protocol_config.merge_randomness_into_checkpoint());
784 assert!(
785 protocol_config.timestamp_based_epoch_close(),
786 "support for non-timestamp-based epoch close has been removed"
787 );
788}
789
790impl<C> ConsensusHandler<C> {
791 pub(crate) fn new(
792 epoch_store: Arc<AuthorityPerEpochStore>,
793 checkpoint_service: Arc<C>,
794 settlement_scheduler: SettlementScheduler,
795 cache_reader: Arc<dyn ObjectCacheRead>,
796 committee: ConsensusCommittee,
797 metrics: Arc<AuthorityMetrics>,
798 throughput_calculator: Arc<ConsensusThroughputCalculator>,
799 backpressure_subscriber: BackpressureSubscriber,
800 traffic_controller: Option<Arc<TrafficController>>,
801 congestion_logger: Option<Arc<Mutex<CongestionCommitLogger>>>,
802 consensus_gasless_counter: Arc<ConsensusGaslessCounter>,
803 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
804 ) -> Self {
805 assert_supported_protocol_config(epoch_store.protocol_config());
806
807 let mut last_consensus_stats = epoch_store
809 .get_last_consensus_stats()
810 .expect("Should be able to read last consensus index");
811 if !last_consensus_stats.stats.is_initialized() {
813 last_consensus_stats.stats = ConsensusStats::new(committee.size());
814 last_consensus_stats.checkpoint_seq = epoch_store.previous_epoch_last_checkpoint();
815 }
816 let max_tx = epoch_store
817 .protocol_config()
818 .max_transactions_per_checkpoint() as usize;
819 let min_checkpoint_interval_ms = epoch_store
820 .protocol_config()
821 .min_checkpoint_interval_ms_as_option()
822 .unwrap_or_default();
823 let execution_scheduler_sender =
824 ExecutionSchedulerSender::start(settlement_scheduler, epoch_store.clone());
825 let commit_rate_estimate_window_size = epoch_store
826 .protocol_config()
827 .get_consensus_commit_rate_estimation_window_size();
828 let last_built_timestamp = last_consensus_stats.last_checkpoint_flush_timestamp;
829 let checkpoint_height = last_consensus_stats.height;
830 let next_checkpoint_seq = last_consensus_stats.checkpoint_seq + 1;
831 Self {
832 epoch_store,
833 last_consensus_stats,
834 checkpoint_service,
835 cache_reader,
836 committee,
837 metrics,
838 processed_cache: LruCache::new(
839 NonZeroUsize::new(randomize_cache_capacity_in_tests(PROCESSED_CACHE_CAP)).unwrap(),
840 ),
841 throughput_calculator,
842 additional_consensus_state: AdditionalConsensusState::new(
843 commit_rate_estimate_window_size,
844 ),
845 backpressure_subscriber,
846 traffic_controller,
847 congestion_logger,
848 consensus_gasless_counter,
849 transaction_deny_config_manager,
850 checkpoint_queue: Mutex::new(CheckpointQueue::new(
851 last_built_timestamp,
852 checkpoint_height,
853 next_checkpoint_seq,
854 max_tx,
855 min_checkpoint_interval_ms,
856 execution_scheduler_sender,
857 )),
858 }
859 }
860
861 pub(crate) fn last_processed_subdag_index(&self) -> u64 {
863 self.last_consensus_stats.index.sub_dag_index
864 }
865
866 pub(crate) fn new_for_testing(
867 epoch_store: Arc<AuthorityPerEpochStore>,
868 checkpoint_service: Arc<C>,
869 execution_scheduler_sender: ExecutionSchedulerSender,
870 cache_reader: Arc<dyn ObjectCacheRead>,
871 committee: ConsensusCommittee,
872 metrics: Arc<AuthorityMetrics>,
873 throughput_calculator: Arc<ConsensusThroughputCalculator>,
874 backpressure_subscriber: BackpressureSubscriber,
875 traffic_controller: Option<Arc<TrafficController>>,
876 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
877 last_consensus_stats: ExecutionIndicesWithStatsV2,
878 ) -> Self {
879 assert_supported_protocol_config(epoch_store.protocol_config());
880
881 let commit_rate_estimate_window_size = epoch_store
882 .protocol_config()
883 .get_consensus_commit_rate_estimation_window_size();
884 let max_tx = epoch_store
885 .protocol_config()
886 .max_transactions_per_checkpoint() as usize;
887 let min_checkpoint_interval_ms = epoch_store
888 .protocol_config()
889 .min_checkpoint_interval_ms_as_option()
890 .unwrap_or_default();
891 let last_built_timestamp = last_consensus_stats.last_checkpoint_flush_timestamp;
892 let checkpoint_height = last_consensus_stats.height;
893 Self {
894 epoch_store,
895 last_consensus_stats,
896 checkpoint_service,
897 cache_reader,
898 committee,
899 metrics,
900 processed_cache: LruCache::new(
901 NonZeroUsize::new(randomize_cache_capacity_in_tests(PROCESSED_CACHE_CAP)).unwrap(),
902 ),
903 throughput_calculator,
904 additional_consensus_state: AdditionalConsensusState::new(
905 commit_rate_estimate_window_size,
906 ),
907 backpressure_subscriber,
908 traffic_controller,
909 congestion_logger: None,
910 consensus_gasless_counter: Arc::new(ConsensusGaslessCounter::default()),
911 transaction_deny_config_manager,
912 checkpoint_queue: Mutex::new(CheckpointQueue::new(
913 last_built_timestamp,
914 checkpoint_height,
915 0,
916 max_tx,
917 min_checkpoint_interval_ms,
918 execution_scheduler_sender,
919 )),
920 }
921 }
922}
923
924#[derive(Default)]
925struct CommitHandlerInput {
926 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
927 capability_notifications: Vec<AuthorityCapabilitiesV2>,
928 execution_time_observations: Vec<ExecutionTimeObservation>,
929 checkpoint_signature_messages: Vec<CheckpointSignatureMessage>,
930 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
931 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
932 end_of_publish_transactions: Vec<AuthorityName>,
933 new_jwks: Vec<(AuthorityName, JwkId, JWK)>,
934 transaction_deny_config_updates: Vec<(AuthorityName, SharedTransactionDenyConfig)>,
935}
936
937struct CommitHandlerState {
938 dkg_failed: bool,
939 randomness_round: Option<RandomnessRound>,
940 output: ConsensusCommitOutput,
941 indirect_state_observer: Option<IndirectStateObserver>,
942 initial_reconfig_state: ReconfigState,
943 occurrence_counts: HashMap<TransactionDigest, u32>,
945 contested_transaction_digests: HashMap<TransactionDigest, ConflictInfo>,
948}
949
950impl CommitHandlerState {
951 fn new(epoch_store: &AuthorityPerEpochStore, consensus_round: u64) -> Self {
952 Self {
953 output: ConsensusCommitOutput::new(consensus_round),
954 dkg_failed: false,
955 randomness_round: None,
956 indirect_state_observer: Some(IndirectStateObserver::new()),
957 initial_reconfig_state: epoch_store.get_reconfig_state_read_lock_guard().clone(),
958 occurrence_counts: HashMap::new(),
959 contested_transaction_digests: HashMap::new(),
960 }
961 }
962
963 fn get_notifications(&self) -> Vec<SequencedConsensusTransactionKey> {
964 self.output
965 .get_consensus_messages_processed()
966 .cloned()
967 .collect()
968 }
969
970 fn init_randomness<'a, 'epoch>(
971 &'a mut self,
972 epoch_store: &'epoch AuthorityPerEpochStore,
973 commit_info: &'a ConsensusCommitInfo,
974 ) -> Option<tokio::sync::MutexGuard<'epoch, RandomnessManager>> {
975 let mut randomness_manager = epoch_store.randomness_manager.get().map(|rm| {
976 rm.try_lock()
977 .expect("should only ever be called from the commit handler thread")
978 });
979
980 let mut dkg_failed = false;
981 let randomness_round = if epoch_store.randomness_state_enabled() {
982 let randomness_manager = randomness_manager
983 .as_mut()
984 .expect("randomness manager should exist if randomness is enabled");
985 match randomness_manager.dkg_status_for_commit_round(commit_info.round) {
986 DkgStatus::Pending => None,
987 DkgStatus::TimedOut => {
988 dkg_failed = true;
989 None
990 }
991 DkgStatus::Successful => {
992 if self.initial_reconfig_state.should_accept_tx() {
995 randomness_manager
996 .reserve_next_randomness(commit_info.timestamp, &mut self.output)
998 .expect("epoch ended")
999 } else {
1000 None
1001 }
1002 }
1003 }
1004 } else {
1005 None
1006 };
1007
1008 if randomness_round.is_some() {
1009 assert!(!dkg_failed); }
1011
1012 self.randomness_round = randomness_round;
1013 self.dkg_failed = dkg_failed;
1014
1015 randomness_manager
1016 }
1017}
1018
1019struct AbandonedDeferredTxns {
1022 count: usize,
1023 sample: Vec<(DeferralKey, TransactionDigest)>,
1025}
1026
1027impl<C: CheckpointServiceNotify + Send + Sync> ConsensusHandler<C> {
1028 fn handle_prior_consensus_commit(&mut self, consensus_commit: impl ConsensusCommitAPI) {
1032 assert!(
1033 self.epoch_store
1034 .protocol_config()
1035 .record_additional_state_digest_in_prologue()
1036 );
1037 let protocol_config = self.epoch_store.protocol_config();
1038 let epoch_start_time = self
1039 .epoch_store
1040 .epoch_start_config()
1041 .epoch_start_timestamp_ms();
1042
1043 self.additional_consensus_state.observe_commit(
1044 protocol_config,
1045 epoch_start_time,
1046 &consensus_commit,
1047 );
1048 }
1049
1050 #[cfg(test)]
1051 pub(crate) async fn handle_consensus_commit_for_test(
1052 &mut self,
1053 consensus_commit: impl ConsensusCommitAPI,
1054 ) {
1055 let transactions = consensus_commit.transactions();
1056 self.handle_consensus_commit(consensus_commit, transactions)
1057 .await
1058 .expect("runtime shutdown during test commit");
1059 }
1060
1061 #[instrument(level = "debug", skip_all, fields(epoch = self.epoch_store.epoch(), round = consensus_commit.leader_round()))]
1064 pub(crate) async fn handle_consensus_commit(
1065 &mut self,
1066 consensus_commit: impl ConsensusCommitAPI,
1067 transactions: ParsedConsensusTransactions,
1068 ) -> Result<(), RuntimeShutdown> {
1069 self.backpressure_subscriber.await_no_backpressure().await;
1074
1075 let epoch = self.epoch_store.epoch();
1076
1077 let _scope = monitored_scope("ConsensusCommitHandler::handle_consensus_commit");
1078
1079 let last_committed_round = self.last_consensus_stats.index.last_committed_round;
1080
1081 self.epoch_store
1082 .consensus_tx_status_cache
1083 .update_last_committed_leader_round(last_committed_round as u32);
1084 self.epoch_store
1085 .tx_reject_reason_cache
1086 .set_last_committed_leader_round(last_committed_round as u32);
1087
1088 let commit_info = self.additional_consensus_state.observe_commit(
1089 self.epoch_store.protocol_config(),
1090 self.epoch_store
1091 .epoch_start_config()
1092 .epoch_start_timestamp_ms(),
1093 &consensus_commit,
1094 );
1095 assert!(commit_info.round > last_committed_round);
1096
1097 let (timestamp, leader_author, commit_sub_dag_index) =
1098 self.gather_commit_metadata(&consensus_commit);
1099
1100 info!(
1101 %consensus_commit,
1102 "Received consensus output {}. Rejected transactions {}",
1103 consensus_commit.commit_ref(),
1104 consensus_commit.rejected_transactions_debug_string(),
1105 );
1106
1107 self.last_consensus_stats.index = ExecutionIndices {
1108 last_committed_round: commit_info.round,
1109 sub_dag_index: commit_sub_dag_index,
1110 transaction_index: 0_u64,
1111 };
1112
1113 self.metrics
1114 .consensus_committed_subdags
1115 .with_label_values(&[&leader_author.to_string()])
1116 .inc();
1117
1118 let mut state = CommitHandlerState::new(&self.epoch_store, commit_info.round);
1119
1120 let FilteredConsensusOutput {
1121 transactions,
1122 owned_object_locks,
1123 dropped_transaction_keys,
1124 contested_transaction_digests,
1125 } = self.filter_consensus_txns(
1126 state.initial_reconfig_state.clone(),
1127 &commit_info,
1128 transactions,
1129 );
1130 state.contested_transaction_digests = contested_transaction_digests;
1131 if !owned_object_locks.is_empty() {
1133 state.output.set_owned_object_locks(owned_object_locks);
1134 }
1135
1136 for key in dropped_transaction_keys {
1138 state.output.record_consensus_message_processed(
1139 SequencedConsensusTransactionKey::External(key),
1140 );
1141 }
1142 let transactions = self.deduplicate_consensus_txns(&mut state, &commit_info, transactions);
1143
1144 let mut randomness_manager = state.init_randomness(&self.epoch_store, &commit_info);
1145
1146 let CommitHandlerInput {
1147 user_transactions,
1148 capability_notifications,
1149 execution_time_observations,
1150 checkpoint_signature_messages,
1151 randomness_dkg_messages,
1152 randomness_dkg_confirmations,
1153 end_of_publish_transactions,
1154 new_jwks,
1155 transaction_deny_config_updates,
1156 } = self.build_commit_handler_input(transactions);
1157
1158 self.process_gasless_transactions(&commit_info, &user_transactions);
1159 self.process_jwks(&mut state, &commit_info, new_jwks);
1160 self.process_capability_notifications(capability_notifications);
1161 self.process_transaction_deny_config_updates(transaction_deny_config_updates);
1162 self.process_execution_time_observations(&mut state, execution_time_observations);
1163 self.process_checkpoint_signature_messages(checkpoint_signature_messages);
1164
1165 self.process_dkg_updates(
1166 &mut state,
1167 &commit_info,
1168 randomness_manager.as_deref_mut(),
1169 randomness_dkg_messages,
1170 randomness_dkg_confirmations,
1171 )
1172 .await?;
1173
1174 let mut execution_time_estimator = self
1175 .epoch_store
1176 .execution_time_estimator
1177 .try_lock()
1178 .expect("should only ever be called from the commit handler thread");
1179
1180 let authenticator_state_update_transaction =
1181 self.create_authenticator_state_update(last_committed_round, &commit_info);
1182
1183 let (
1184 transactions_to_schedule,
1185 randomness_transactions_to_schedule,
1186 cancelled_txns,
1187 randomness_state_update_transaction,
1188 ) = self.collect_transactions_to_schedule(
1189 &mut state,
1190 &mut execution_time_estimator,
1191 &commit_info,
1192 user_transactions,
1193 );
1194
1195 let (should_accept_tx, lock, final_round, abandoned_deferred_txns) =
1196 self.handle_close_epoch(&mut state, &commit_info, end_of_publish_transactions);
1197
1198 let make_checkpoint = should_accept_tx || final_round;
1199 if !make_checkpoint {
1200 return Ok(());
1202 }
1203
1204 if final_round {
1207 self.record_end_of_epoch_execution_time_observations(&mut execution_time_estimator);
1208 }
1209
1210 let consensus_commit_prologue = (!commit_info.skip_consensus_commit_prologue_in_test)
1211 .then_some(Schedulable::ConsensusCommitPrologue(
1212 epoch,
1213 commit_info.round,
1214 commit_info.consensus_commit_ref.index,
1215 ));
1216
1217 let schedulables: Vec<_> = itertools::chain!(
1218 consensus_commit_prologue.into_iter(),
1219 authenticator_state_update_transaction
1220 .into_iter()
1221 .map(Schedulable::Transaction),
1222 transactions_to_schedule
1223 .into_iter()
1224 .map(Schedulable::Transaction),
1225 )
1226 .collect();
1227
1228 let randomness_schedulables: Vec<_> = randomness_state_update_transaction
1229 .into_iter()
1230 .chain(
1231 randomness_transactions_to_schedule
1232 .into_iter()
1233 .map(Schedulable::Transaction),
1234 )
1235 .collect();
1236
1237 let num_schedulables = schedulables.len();
1238 let checkpoint_height = self.create_pending_checkpoints(
1239 &mut state,
1240 &commit_info,
1241 schedulables,
1242 randomness_schedulables,
1243 &cancelled_txns,
1244 final_round,
1245 );
1246
1247 let notifications = state.get_notifications();
1248
1249 let mut stats_to_record = self.last_consensus_stats.clone();
1250 stats_to_record.height = checkpoint_height;
1251 {
1252 let queue = self.checkpoint_queue.lock().unwrap();
1253 stats_to_record.last_checkpoint_flush_timestamp = queue.last_built_timestamp();
1254 stats_to_record.checkpoint_seq = queue.checkpoint_seq();
1255 }
1256
1257 state.output.record_consensus_commit_stats(stats_to_record);
1258
1259 self.record_deferral_deletion(&mut state);
1260
1261 self.epoch_store
1262 .consensus_quarantine
1263 .write()
1264 .push_consensus_output(state.output, &self.epoch_store)
1265 .expect("push_consensus_output should not fail");
1266
1267 debug!(
1268 ?commit_info.round,
1269 "Notifying checkpoint service about new pending checkpoint(s)",
1270 );
1271 self.checkpoint_service
1272 .notify_checkpoint()
1273 .expect("failed to notify checkpoint service");
1274
1275 if let Some(randomness_round) = state.randomness_round {
1276 randomness_manager
1277 .as_ref()
1278 .expect("randomness manager should exist if randomness round is provided")
1279 .generate_randomness(epoch, randomness_round);
1280 }
1281
1282 self.epoch_store.process_notifications(notifications.iter());
1283
1284 self.log_final_round(lock, final_round);
1286
1287 if let Some(AbandonedDeferredTxns { count, sample }) = abandoned_deferred_txns {
1291 self.metrics
1292 .consensus_handler_dropped_transactions
1293 .with_label_values(&["epoch_close_deadline"])
1294 .inc_by(count as u64);
1295 debug_fatal!(
1296 "Epoch close deadline reached with unscheduled deferred transactions: count={}, sample={:?}",
1297 count,
1298 sample
1299 );
1300 }
1301
1302 self.throughput_calculator
1304 .add_transactions(timestamp, num_schedulables as u64);
1305
1306 fail_point_if!("correlated-crash-after-consensus-commit-boundary", || {
1307 let key = [commit_sub_dag_index, epoch];
1308 if sui_simulator::random::deterministic_probability_once(&key, 0.01) {
1309 sui_simulator::task::kill_current_node(None);
1310 }
1311 });
1312
1313 fail_point!("crash");
1314 Ok(())
1315 }
1316
1317 fn handle_close_epoch(
1318 &self,
1319 state: &mut CommitHandlerState,
1320 commit_info: &ConsensusCommitInfo,
1321 end_of_publish_transactions: Vec<AuthorityName>,
1322 ) -> (
1323 bool,
1324 Option<RwLockWriteGuard<'_, ReconfigState>>,
1325 bool,
1326 Option<AbandonedDeferredTxns>,
1327 ) {
1328 let timestamp_triggered =
1329 commit_info.timestamp >= self.epoch_store.next_reconfiguration_timestamp_ms();
1330 let deadline_reached = self
1331 .epoch_store
1332 .protocol_config()
1333 .epoch_close_deadline_ms_as_option()
1334 .is_some_and(|deadline_ms| {
1335 commit_info.timestamp
1336 >= self
1337 .epoch_store
1338 .next_reconfiguration_timestamp_ms()
1339 .saturating_add(deadline_ms)
1340 });
1341 let collected_eop_quorum =
1342 self.process_end_of_publish_transactions(state, end_of_publish_transactions);
1343 if timestamp_triggered || collected_eop_quorum {
1344 let (lock, final_round, abandoned_deferred_txns) =
1345 self.advance_end_of_epoch_state_machine(state, deadline_reached);
1346 (
1347 lock.should_accept_tx(),
1348 Some(lock),
1349 final_round,
1350 abandoned_deferred_txns,
1351 )
1352 } else {
1353 (true, None, false, None)
1354 }
1355 }
1356
1357 fn record_end_of_epoch_execution_time_observations(
1358 &self,
1359 estimator: &mut ExecutionTimeEstimator,
1360 ) {
1361 self.epoch_store
1362 .end_of_epoch_execution_time_observations
1363 .set(estimator.take_observations())
1364 .expect("`stored_execution_time_observations` should only be set once at end of epoch");
1365 }
1366
1367 fn record_deferral_deletion(&self, state: &mut CommitHandlerState) {
1368 let mut deferred_transactions = self
1369 .epoch_store
1370 .consensus_output_cache
1371 .deferred_transactions
1372 .lock();
1373 for deleted_deferred_key in state.output.get_deleted_deferred_txn_keys() {
1374 deferred_transactions.remove(&deleted_deferred_key);
1375 }
1376 }
1377
1378 fn log_final_round(&self, lock: Option<RwLockWriteGuard<ReconfigState>>, final_round: bool) {
1379 if final_round {
1380 let epoch = self.epoch_store.epoch();
1381 info!(
1382 ?epoch,
1383 lock=?lock.as_ref(),
1384 final_round=?final_round,
1385 "Notified last checkpoint"
1386 );
1387 self.epoch_store.record_end_of_message_quorum_time_metric();
1388 }
1389 }
1390
1391 #[allow(clippy::type_complexity)]
1392 fn collect_transactions_to_schedule(
1393 &self,
1394 state: &mut CommitHandlerState,
1395 execution_time_estimator: &mut ExecutionTimeEstimator,
1396 commit_info: &ConsensusCommitInfo,
1397 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
1398 ) -> (
1399 Vec<VerifiedExecutableTransactionWithAliases>,
1400 Vec<VerifiedExecutableTransactionWithAliases>,
1401 BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1402 Option<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1403 ) {
1404 let _scope = monitored_scope("ConsensusCommitHandler::collect_transactions_to_schedule");
1405 let protocol_config = self.epoch_store.protocol_config();
1406 let epoch = self.epoch_store.epoch();
1407
1408 let (ordered_txns, ordered_randomness_txns, previously_deferred_tx_digests) =
1409 self.merge_and_reorder_transactions(state, commit_info, user_transactions);
1410
1411 let mut shared_object_congestion_tracker =
1412 self.init_congestion_tracker(commit_info, false, &ordered_txns);
1413 let mut shared_object_using_randomness_congestion_tracker =
1414 self.init_congestion_tracker(commit_info, true, &ordered_randomness_txns);
1415
1416 let randomness_state_update_transaction = state
1417 .randomness_round
1418 .map(|round| Schedulable::RandomnessStateUpdate(epoch, round));
1419 debug!(
1420 "Randomness state update transaction: {:?}",
1421 randomness_state_update_transaction
1422 .as_ref()
1423 .map(|t| t.key())
1424 );
1425
1426 let mut transactions_to_schedule = Vec::with_capacity(ordered_txns.len());
1427 let mut randomness_transactions_to_schedule =
1428 Vec::with_capacity(ordered_randomness_txns.len());
1429 let mut deferred_txns = BTreeMap::new();
1430 let mut cancelled_txns = BTreeMap::new();
1431
1432 for transaction in ordered_txns {
1433 self.handle_deferral_and_cancellation(
1434 state,
1435 &mut cancelled_txns,
1436 &mut deferred_txns,
1437 &mut transactions_to_schedule,
1438 protocol_config,
1439 commit_info,
1440 transaction,
1441 &mut shared_object_congestion_tracker,
1442 &previously_deferred_tx_digests,
1443 execution_time_estimator,
1444 );
1445 }
1446
1447 for transaction in ordered_randomness_txns {
1448 if state.dkg_failed {
1449 debug!(
1450 "Canceling randomness-using transaction {:?} because DKG failed",
1451 transaction.tx().digest(),
1452 );
1453 cancelled_txns.insert(
1454 *transaction.tx().digest(),
1455 CancelConsensusCertificateReason::DkgFailed,
1456 );
1457 randomness_transactions_to_schedule.push(transaction);
1458 continue;
1459 }
1460 self.handle_deferral_and_cancellation(
1461 state,
1462 &mut cancelled_txns,
1463 &mut deferred_txns,
1464 &mut randomness_transactions_to_schedule,
1465 protocol_config,
1466 commit_info,
1467 transaction,
1468 &mut shared_object_using_randomness_congestion_tracker,
1469 &previously_deferred_tx_digests,
1470 execution_time_estimator,
1471 );
1472 }
1473
1474 let mut total_deferred_txns = 0;
1475 {
1476 let mut deferred_transactions = self
1477 .epoch_store
1478 .consensus_output_cache
1479 .deferred_transactions
1480 .lock();
1481 for (key, txns) in deferred_txns.into_iter() {
1482 total_deferred_txns += txns.len();
1483 debug_assert!(
1498 !state
1499 .output
1500 .get_deleted_deferred_txn_keys()
1501 .any(|deleted| deleted == key),
1502 "deferral key {key:?} was reloaded by this commit and must not be re-inserted"
1503 );
1504 let txns = if protocol_config.merge_colliding_deferrals() {
1505 match deferred_transactions.remove(&key) {
1506 Some(mut merged) => {
1507 assert_reachable_gated!(
1508 "Merged colliding deferral entries instead of displacing finalized transactions.",
1509 |pc| pc.merge_colliding_deferrals()
1510 && (pc.defer_owned_object_double_spend()
1511 || pc.defer_unpaid_amplification())
1512 );
1513 debug_assert!(
1514 {
1515 let prev_digests: HashSet<_> =
1516 merged.iter().map(|t| *t.tx().digest()).collect();
1517 txns.iter().all(|t| !prev_digests.contains(t.tx().digest()))
1518 },
1519 "colliding deferral entries must not share transactions"
1520 );
1521 merged.extend(txns);
1524 merged
1525 }
1526 None => txns,
1527 }
1528 } else {
1529 if let Some(prev) = deferred_transactions.get(&key) {
1530 let new_digests: HashSet<_> =
1533 txns.iter().map(|t| *t.tx().digest()).collect();
1534 let displaced: Vec<_> = prev
1535 .iter()
1536 .map(|t| *t.tx().digest())
1537 .filter(|d| !new_digests.contains(d))
1538 .collect();
1539 if !displaced.is_empty() {
1540 debug_fatal!(
1541 "Deferral key collision displaced finalized transactions: key {:?}, displaced {:?}",
1542 key,
1543 displaced
1544 );
1545 }
1546 }
1547 txns
1548 };
1549 deferred_transactions.insert(key, txns.clone());
1550 state.output.defer_transactions(key, txns);
1551 }
1552 }
1553
1554 self.metrics
1555 .consensus_handler_deferred_transactions
1556 .inc_by(total_deferred_txns as u64);
1557 self.metrics
1558 .consensus_handler_cancelled_transactions
1559 .inc_by(cancelled_txns.len() as u64);
1560 self.metrics
1561 .consensus_handler_max_object_costs
1562 .with_label_values(&["regular_commit"])
1563 .set(shared_object_congestion_tracker.max_cost() as i64);
1564 self.metrics
1565 .consensus_handler_max_object_costs
1566 .with_label_values(&["randomness_commit"])
1567 .set(shared_object_using_randomness_congestion_tracker.max_cost() as i64);
1568
1569 let congestion_commit_data = shared_object_congestion_tracker.finish_commit(commit_info);
1570 let randomness_congestion_commit_data =
1571 shared_object_using_randomness_congestion_tracker.finish_commit(commit_info);
1572
1573 if let Some(logger) = &self.congestion_logger {
1574 let epoch = self.epoch_store.epoch();
1575 let mut logger = logger.lock().unwrap();
1576 logger.write_commit_log(epoch, commit_info, false, &congestion_commit_data);
1577 logger.write_commit_log(epoch, commit_info, true, &randomness_congestion_commit_data);
1578 }
1579
1580 if let Some(tx_object_debts) = self.epoch_store.tx_object_debts.get()
1581 && let Err(e) = tx_object_debts.try_send(
1582 congestion_commit_data
1583 .accumulated_debts
1584 .iter()
1585 .chain(randomness_congestion_commit_data.accumulated_debts.iter())
1586 .map(|(id, _)| *id)
1587 .collect(),
1588 )
1589 {
1590 info!("failed to send updated object debts to ExecutionTimeObserver: {e:?}");
1591 }
1592
1593 state
1594 .output
1595 .set_congestion_control_object_debts(congestion_commit_data.accumulated_debts);
1596 state.output.set_congestion_control_randomness_object_debts(
1597 randomness_congestion_commit_data.accumulated_debts,
1598 );
1599
1600 (
1601 transactions_to_schedule,
1602 randomness_transactions_to_schedule,
1603 cancelled_txns,
1604 randomness_state_update_transaction,
1605 )
1606 }
1607
1608 #[allow(clippy::type_complexity)]
1609 fn create_pending_checkpoints(
1610 &self,
1611 state: &mut CommitHandlerState,
1612 commit_info: &ConsensusCommitInfo,
1613 schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1614 randomness_schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1615 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1616 final_round: bool,
1617 ) -> CheckpointHeight {
1618 let _scope = monitored_scope("ConsensusCommitHandler::create_pending_checkpoints");
1619 let protocol_config = self.epoch_store.protocol_config();
1620 let epoch = self.epoch_store.epoch();
1621 let accumulators_enabled = self.epoch_store.accumulators_enabled();
1622 let max_transactions_per_checkpoint =
1623 protocol_config.max_transactions_per_checkpoint() as usize;
1624
1625 let should_write_random_checkpoint = state.randomness_round.is_some()
1626 || (state.dkg_failed && !randomness_schedulables.is_empty());
1627
1628 let mut checkpoint_queue = self.checkpoint_queue.lock().unwrap();
1629
1630 let build_chunks =
1631 |schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1632 queue: &mut CheckpointQueue|
1633 -> Vec<Chunk<VerifiedExecutableTransactionWithAliases>> {
1634 schedulables
1635 .chunks(max_transactions_per_checkpoint)
1636 .map(|chunk| {
1637 let height = queue.next_height();
1638 let schedulables: Vec<_> = chunk.to_vec();
1639 let settlement = if accumulators_enabled {
1640 Some(Schedulable::AccumulatorSettlement(epoch, height))
1641 } else {
1642 None
1643 };
1644 Chunk {
1645 schedulables,
1646 settlement,
1647 height,
1648 }
1649 })
1650 .collect()
1651 };
1652
1653 let num_schedulables = schedulables.len();
1654 let chunked_schedulables = build_chunks(schedulables, &mut checkpoint_queue);
1655 if chunked_schedulables.len() > 1 {
1656 info!(
1657 "Splitting transactions into {} checkpoint chunks (num_schedulables={}, max_tx={})",
1658 chunked_schedulables.len(),
1659 num_schedulables,
1660 max_transactions_per_checkpoint
1661 );
1662 assert_reachable!("checkpoint split due to transaction limit");
1663 }
1664 let chunked_randomness_schedulables = if should_write_random_checkpoint {
1665 build_chunks(randomness_schedulables, &mut checkpoint_queue)
1666 } else {
1667 vec![]
1668 };
1669
1670 let schedulables_for_version_assignment =
1671 Chunk::all_schedulables_from(&chunked_schedulables);
1672 let randomness_schedulables_for_version_assignment =
1673 Chunk::all_schedulables_from(&chunked_randomness_schedulables);
1674
1675 let assigned_versions = self
1676 .epoch_store
1677 .process_consensus_transaction_shared_object_versions(
1678 self.cache_reader.as_ref(),
1679 schedulables_for_version_assignment,
1680 randomness_schedulables_for_version_assignment,
1681 cancelled_txns,
1682 &mut state.output,
1683 )
1684 .expect("failed to assign shared object versions");
1685
1686 let consensus_commit_prologue =
1687 self.add_consensus_commit_prologue_transaction(state, commit_info, &assigned_versions);
1688
1689 let mut chunked_schedulables = chunked_schedulables;
1690 let mut assigned_versions = assigned_versions;
1691 if let Some(consensus_commit_prologue) = consensus_commit_prologue {
1692 assert!(matches!(
1693 chunked_schedulables[0].schedulables[0],
1694 Schedulable::ConsensusCommitPrologue(..)
1695 ));
1696 assert!(matches!(
1697 assigned_versions.0[0].0,
1698 TransactionKey::ConsensusCommitPrologue(..)
1699 ));
1700 assigned_versions.0[0].0 =
1701 TransactionKey::Digest(*consensus_commit_prologue.tx().digest());
1702 chunked_schedulables[0].schedulables[0] =
1703 Schedulable::Transaction(consensus_commit_prologue);
1704 }
1705
1706 let assigned_versions = assigned_versions.into_map();
1707
1708 self.epoch_store.process_user_signatures(
1709 chunked_schedulables
1710 .iter()
1711 .flat_map(|c| c.all_schedulables())
1712 .chain(
1713 chunked_randomness_schedulables
1714 .iter()
1715 .flat_map(|c| c.all_schedulables()),
1716 ),
1717 );
1718
1719 let commit_height = chunked_randomness_schedulables
1720 .last()
1721 .or(chunked_schedulables.last())
1722 .map(|c| c.height)
1723 .expect("at least one checkpoint root must be created per commit");
1724
1725 let mut pending_checkpoints = Vec::new();
1726 for chunk in chunked_schedulables {
1727 pending_checkpoints.extend(checkpoint_queue.push_chunk(
1728 chunk.into(),
1729 &assigned_versions,
1730 commit_info.timestamp,
1731 commit_info.consensus_commit_ref,
1732 commit_info.rejected_transactions_digest,
1733 ));
1734 }
1735
1736 pending_checkpoints.extend(checkpoint_queue.flush(commit_info.timestamp, final_round));
1741
1742 if should_write_random_checkpoint {
1743 for chunk in chunked_randomness_schedulables {
1744 pending_checkpoints.extend(checkpoint_queue.push_chunk(
1745 chunk.into(),
1746 &assigned_versions,
1747 commit_info.timestamp,
1748 commit_info.consensus_commit_ref,
1749 commit_info.rejected_transactions_digest,
1750 ));
1751 }
1752 if final_round {
1753 pending_checkpoints.extend(checkpoint_queue.flush(commit_info.timestamp, true));
1754 }
1755 }
1756
1757 if final_round && let Some(last) = pending_checkpoints.last_mut() {
1758 last.details.last_of_epoch = true;
1759 }
1760
1761 let queue_drained = checkpoint_queue.is_empty();
1762 drop(checkpoint_queue);
1763
1764 for pending_checkpoint in pending_checkpoints {
1765 debug!(
1766 checkpoint_height = pending_checkpoint.details.checkpoint_height,
1767 roots_count = pending_checkpoint.num_roots(),
1768 "Writing pending checkpoint",
1769 );
1770 self.epoch_store
1771 .write_pending_checkpoint(&mut state.output, &pending_checkpoint);
1772 }
1773
1774 state.output.set_checkpoint_queue_drained(queue_drained);
1775
1776 commit_height
1777 }
1778
1779 fn add_consensus_commit_prologue_transaction<'a>(
1783 &'a self,
1784 state: &'a mut CommitHandlerState,
1785 commit_info: &'a ConsensusCommitInfo,
1786 assigned_versions: &AssignedTxAndVersions,
1787 ) -> Option<VerifiedExecutableTransactionWithAliases> {
1788 {
1789 if commit_info.skip_consensus_commit_prologue_in_test {
1790 return None;
1791 }
1792 }
1793
1794 let mut cancelled_txn_version_assignment = Vec::new();
1795
1796 for (txn_key, assigned_versions) in assigned_versions.0.iter() {
1797 let Some(d) = txn_key.as_digest() else {
1798 continue;
1799 };
1800
1801 if assigned_versions
1802 .shared_object_versions
1803 .iter()
1804 .any(|(_, version)| version.is_cancelled())
1805 {
1806 assert_reachable!("cancelled transactions");
1807 cancelled_txn_version_assignment
1808 .push((*d, assigned_versions.shared_object_versions.clone()));
1809 }
1810 }
1811
1812 fail_point_arg!(
1813 "additional_cancelled_txns_for_tests",
1814 |additional_cancelled_txns: Vec<(
1815 TransactionDigest,
1816 Vec<(ConsensusObjectSequenceKey, SequenceNumber)>
1817 )>| {
1818 cancelled_txn_version_assignment.extend(additional_cancelled_txns);
1819 }
1820 );
1821
1822 let transaction = commit_info.create_consensus_commit_prologue_transaction(
1823 self.epoch_store.epoch(),
1824 cancelled_txn_version_assignment,
1825 state.indirect_state_observer.take().unwrap(),
1826 );
1827 Some(VerifiedExecutableTransactionWithAliases::no_aliases(
1828 transaction,
1829 ))
1830 }
1831
1832 fn handle_deferral_and_cancellation(
1833 &self,
1834 state: &mut CommitHandlerState,
1835 cancelled_txns: &mut BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1836 deferred_txns: &mut BTreeMap<DeferralKey, Vec<VerifiedExecutableTransactionWithAliases>>,
1837 scheduled_txns: &mut Vec<VerifiedExecutableTransactionWithAliases>,
1838 protocol_config: &ProtocolConfig,
1839 commit_info: &ConsensusCommitInfo,
1840 transaction: VerifiedExecutableTransactionWithAliases,
1841 shared_object_congestion_tracker: &mut SharedObjectCongestionTracker,
1842 previously_deferred_tx_digests: &HashMap<TransactionDigest, DeferralKey>,
1843 execution_time_estimator: &ExecutionTimeEstimator,
1844 ) {
1845 let tx_digest = *transaction.tx().digest();
1846
1847 if protocol_config.defer_unpaid_amplification()
1856 && !transaction
1857 .tx()
1858 .transaction_data()
1859 .expiration()
1860 .restricts_proposers(self.epoch_store.epoch())
1861 {
1862 let occurrence_count = state
1863 .occurrence_counts
1864 .get(&tx_digest)
1865 .copied()
1866 .unwrap_or(0);
1867
1868 let rgp = self.epoch_store.reference_gas_price();
1869 let gas_price = transaction.tx().transaction_data().gas_price();
1870 let allowed_count = (gas_price / rgp.max(1)) + 1;
1871
1872 if occurrence_count as u64 > allowed_count {
1873 self.metrics
1874 .consensus_handler_unpaid_amplification_deferrals
1875 .inc();
1876
1877 let deferred_from_round = previously_deferred_tx_digests
1878 .get(&tx_digest)
1879 .map(|k| k.deferred_from_round())
1880 .unwrap_or(commit_info.round);
1881
1882 let deferral_key = DeferralKey::new_for_consensus_round(
1883 commit_info.round + 1,
1884 deferred_from_round,
1885 );
1886
1887 if transaction_deferral_within_limit(
1888 &deferral_key,
1889 protocol_config.max_deferral_rounds_for_congestion_control(),
1890 ) {
1891 debug!(
1892 "Deferring transaction {:?} due to unpaid amplification (count={}, allowed={})",
1893 tx_digest, occurrence_count, allowed_count
1894 );
1895 deferred_txns
1896 .entry(deferral_key)
1897 .or_default()
1898 .push(transaction);
1899 return;
1900 }
1901 }
1902 }
1903
1904 if let Some(conflict_info) = state.contested_transaction_digests.get(&tx_digest) {
1908 self.metrics.consensus_handler_double_spend_deferrals.inc();
1909 self.metrics
1910 .consensus_handler_double_spend_conflict_count
1911 .with_label_values(&["gas_object"])
1912 .observe(conflict_info.gas_object_conflicts as f64);
1913 self.metrics
1914 .consensus_handler_double_spend_conflict_count
1915 .with_label_values(&["non_gas_object"])
1916 .observe(conflict_info.non_gas_object_conflicts as f64);
1917 self.metrics
1920 .consensus_handler_double_spend_conflicting_authority
1921 .with_label_values(&[
1922 self.authority_hostname(conflict_info.winner_author),
1923 "winner",
1924 ])
1925 .inc();
1926
1927 if protocol_config.defer_owned_object_double_spend() {
1928 let deferred_from_round = previously_deferred_tx_digests
1929 .get(&tx_digest)
1930 .map(|k| k.deferred_from_round())
1931 .unwrap_or(commit_info.round);
1932
1933 let deferral_key = DeferralKey::new_for_consensus_round(
1934 commit_info.round + 1,
1935 deferred_from_round,
1936 );
1937
1938 if transaction_deferral_within_limit(
1939 &deferral_key,
1940 protocol_config.max_deferral_rounds_for_congestion_control(),
1941 ) {
1942 debug!(
1943 "Deferring transaction {:?} due to owned object double-spend contention \
1944 (gas_conflicts={}, non_gas_conflicts={})",
1945 tx_digest,
1946 conflict_info.gas_object_conflicts,
1947 conflict_info.non_gas_object_conflicts,
1948 );
1949 assert_reachable_gated!(
1950 "Successfully deferred transaction attempting to double spend owned object.",
1951 |pc| pc.defer_owned_object_double_spend()
1952 );
1953 if transaction.tx().transaction_data().uses_randomness()
1954 && state.randomness_round.is_none()
1955 {
1956 assert_reachable_gated!(
1962 "Double-spend deferred a randomness-using transaction at a round without randomness.",
1963 |pc| pc.defer_owned_object_double_spend()
1964 );
1965 }
1966 deferred_txns
1967 .entry(deferral_key)
1968 .or_default()
1969 .push(transaction);
1970 return;
1971 }
1972 }
1973 }
1974
1975 let tx_cost = shared_object_congestion_tracker.get_tx_cost(
1976 execution_time_estimator,
1977 transaction.tx(),
1978 state.indirect_state_observer.as_mut().unwrap(),
1979 );
1980
1981 let deferral_info = self.epoch_store.should_defer(
1982 transaction.tx(),
1983 commit_info,
1984 state.dkg_failed,
1985 state.randomness_round.is_some(),
1986 previously_deferred_tx_digests,
1987 shared_object_congestion_tracker,
1988 );
1989
1990 if let Some((deferral_key, deferral_reason)) = deferral_info {
1991 debug!(
1992 "Deferring consensus certificate for transaction {:?} until {:?}",
1993 tx_digest, deferral_key
1994 );
1995
1996 match deferral_reason {
1997 DeferralReason::RandomnessNotReady => {
1998 deferred_txns
1999 .entry(deferral_key)
2000 .or_default()
2001 .push(transaction);
2002 }
2003 DeferralReason::SharedObjectCongestion(congested_objects) => {
2004 self.metrics.consensus_handler_congested_transactions.inc();
2005 if transaction_deferral_within_limit(
2006 &deferral_key,
2007 protocol_config.max_deferral_rounds_for_congestion_control(),
2008 ) {
2009 deferred_txns
2010 .entry(deferral_key)
2011 .or_default()
2012 .push(transaction);
2013 } else {
2014 assert_sometimes!(
2015 transaction.tx().data().transaction_data().uses_randomness(),
2016 "cancelled randomness-using transaction"
2017 );
2018 assert_sometimes!(
2019 !transaction.tx().data().transaction_data().uses_randomness(),
2020 "cancelled non-randomness-using transaction"
2021 );
2022
2023 debug!(
2025 "Cancelling consensus transaction {:?} with deferral key {:?} due to congestion on objects {:?}",
2026 tx_digest, deferral_key, congested_objects
2027 );
2028 cancelled_txns.insert(
2029 tx_digest,
2030 CancelConsensusCertificateReason::CongestionOnObjects(
2031 congested_objects,
2032 ),
2033 );
2034 scheduled_txns.push(transaction);
2035 }
2036 }
2037 }
2038 } else {
2039 shared_object_congestion_tracker.bump_object_execution_cost(tx_cost, transaction.tx());
2041 scheduled_txns.push(transaction);
2042 }
2043 }
2044
2045 fn merge_and_reorder_transactions(
2046 &self,
2047 state: &mut CommitHandlerState,
2048 commit_info: &ConsensusCommitInfo,
2049 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
2050 ) -> (
2051 Vec<VerifiedExecutableTransactionWithAliases>,
2052 Vec<VerifiedExecutableTransactionWithAliases>,
2053 HashMap<TransactionDigest, DeferralKey>,
2054 ) {
2055 let protocol_config = self.epoch_store.protocol_config();
2056
2057 let (mut txns, mut randomness_txns, previously_deferred_tx_digests) =
2058 self.load_deferred_transactions(state, commit_info);
2059
2060 txns.reserve(user_transactions.len());
2061 randomness_txns.reserve(user_transactions.len());
2062
2063 let mut txns: Vec<_> = txns
2066 .into_iter()
2067 .filter_map(|tx| {
2068 if tx.tx().transaction_data().uses_randomness() {
2069 randomness_txns.push(tx);
2070 None
2071 } else {
2072 Some(tx)
2073 }
2074 })
2075 .collect();
2076
2077 for txn in user_transactions {
2078 if txn.tx().transaction_data().uses_randomness() {
2079 randomness_txns.push(txn);
2080 } else {
2081 txns.push(txn);
2082 }
2083 }
2084
2085 PostConsensusTxReorder::reorder(
2086 &mut txns,
2087 protocol_config.consensus_transaction_ordering(),
2088 );
2089 PostConsensusTxReorder::reorder(
2090 &mut randomness_txns,
2091 protocol_config.consensus_transaction_ordering(),
2092 );
2093
2094 (txns, randomness_txns, previously_deferred_tx_digests)
2095 }
2096
2097 fn load_deferred_transactions(
2098 &self,
2099 state: &mut CommitHandlerState,
2100 commit_info: &ConsensusCommitInfo,
2101 ) -> (
2102 Vec<VerifiedExecutableTransactionWithAliases>,
2103 Vec<VerifiedExecutableTransactionWithAliases>,
2104 HashMap<TransactionDigest, DeferralKey>,
2105 ) {
2106 let mut previously_deferred_tx_digests = HashMap::new();
2107
2108 let deferred_txs: Vec<_> = self
2109 .epoch_store
2110 .load_deferred_transactions_for_up_to_consensus_round_v2(
2111 &mut state.output,
2112 commit_info.round,
2113 )
2114 .into_iter()
2115 .flat_map(|(key, txns)| txns.into_iter().map(move |tx| (key, tx)))
2116 .map(|(key, tx)| {
2117 previously_deferred_tx_digests.insert(*tx.tx().digest(), key);
2118 tx
2119 })
2120 .collect();
2121 trace!(
2122 "loading deferred transactions: {:?}",
2123 deferred_txs.iter().map(|tx| tx.tx().digest())
2124 );
2125
2126 let deferred_randomness_txs = if state.dkg_failed || state.randomness_round.is_some() {
2127 let txns: Vec<_> = self
2128 .epoch_store
2129 .load_deferred_transactions_for_randomness_v2(&mut state.output)
2130 .into_iter()
2131 .flat_map(|(key, txns)| txns.into_iter().map(move |tx| (key, tx)))
2132 .map(|(key, tx)| {
2133 previously_deferred_tx_digests.insert(*tx.tx().digest(), key);
2134 tx
2135 })
2136 .collect();
2137 trace!(
2138 "loading deferred randomness transactions: {:?}",
2139 txns.iter().map(|tx| tx.tx().digest())
2140 );
2141 txns
2142 } else {
2143 vec![]
2144 };
2145
2146 (
2147 deferred_txs,
2148 deferred_randomness_txs,
2149 previously_deferred_tx_digests,
2150 )
2151 }
2152
2153 fn init_congestion_tracker(
2154 &self,
2155 commit_info: &ConsensusCommitInfo,
2156 for_randomness: bool,
2157 txns: &[VerifiedExecutableTransactionWithAliases],
2158 ) -> SharedObjectCongestionTracker {
2159 #[allow(unused_mut)]
2160 let mut ret = SharedObjectCongestionTracker::from_protocol_config(
2161 self.epoch_store
2162 .consensus_quarantine
2163 .read()
2164 .load_initial_object_debts(
2165 &self.epoch_store,
2166 commit_info.round,
2167 for_randomness,
2168 txns,
2169 ),
2170 self.epoch_store.protocol_config(),
2171 for_randomness,
2172 self.congestion_logger.is_some(),
2173 );
2174
2175 fail_point_arg!(
2176 "initial_congestion_tracker",
2177 |tracker: SharedObjectCongestionTracker| {
2178 info!(
2179 "Initialize shared_object_congestion_tracker to {:?}",
2180 tracker
2181 );
2182 ret = tracker;
2183 }
2184 );
2185
2186 ret
2187 }
2188
2189 fn process_gasless_transactions(
2190 &self,
2191 commit_info: &ConsensusCommitInfo,
2192 user_transactions: &[VerifiedExecutableTransactionWithAliases],
2193 ) {
2194 let gasless_count = user_transactions
2195 .iter()
2196 .filter(|txn| txn.tx().transaction_data().is_gasless_transaction())
2197 .count() as u64;
2198 self.consensus_gasless_counter
2199 .record_commit(commit_info.timestamp, gasless_count);
2200 }
2201
2202 fn process_jwks(
2203 &self,
2204 state: &mut CommitHandlerState,
2205 commit_info: &ConsensusCommitInfo,
2206 new_jwks: Vec<(AuthorityName, JwkId, JWK)>,
2207 ) {
2208 for (authority_name, jwk_id, jwk) in new_jwks {
2209 self.epoch_store.record_jwk_vote(
2210 &mut state.output,
2211 commit_info.round,
2212 authority_name,
2213 &jwk_id,
2214 &jwk,
2215 );
2216 }
2217 }
2218
2219 fn process_capability_notifications(
2220 &self,
2221 capability_notifications: Vec<AuthorityCapabilitiesV2>,
2222 ) {
2223 for capabilities in capability_notifications {
2224 self.epoch_store
2225 .record_capabilities_v2(&capabilities)
2226 .expect("db error");
2227 }
2228 }
2229
2230 fn process_transaction_deny_config_updates(
2235 &self,
2236 updates: Vec<(AuthorityName, SharedTransactionDenyConfig)>,
2237 ) {
2238 for (author, update) in updates {
2239 self.transaction_deny_config_manager
2240 .apply_updates(author, vec![update]);
2241 }
2242 }
2243
2244 fn process_execution_time_observations(
2245 &self,
2246 state: &mut CommitHandlerState,
2247 execution_time_observations: Vec<ExecutionTimeObservation>,
2248 ) {
2249 let _scope = monitored_scope("ConsensusCommitHandler::process_execution_time_observations");
2250 let mut execution_time_estimator = self
2251 .epoch_store
2252 .execution_time_estimator
2253 .try_lock()
2254 .expect("should only ever be called from the commit handler thread");
2255
2256 for ExecutionTimeObservation {
2257 authority,
2258 generation,
2259 estimates,
2260 } in execution_time_observations
2261 {
2262 let authority_index = self
2263 .epoch_store
2264 .committee()
2265 .authority_index(&authority)
2266 .unwrap();
2267 execution_time_estimator.process_observations_from_consensus(
2268 authority_index,
2269 Some(generation),
2270 &estimates,
2271 );
2272 state
2273 .output
2274 .insert_execution_time_observation(authority_index, generation, estimates);
2275 }
2276 }
2277
2278 fn process_checkpoint_signature_messages(
2279 &self,
2280 checkpoint_signature_messages: Vec<CheckpointSignatureMessage>,
2281 ) {
2282 for checkpoint_signature_message in checkpoint_signature_messages {
2283 self.checkpoint_service
2284 .notify_checkpoint_signature(&checkpoint_signature_message)
2285 .expect("db error");
2286 }
2287 }
2288
2289 async fn process_dkg_updates(
2290 &self,
2291 state: &mut CommitHandlerState,
2292 commit_info: &ConsensusCommitInfo,
2293 randomness_manager: Option<&mut RandomnessManager>,
2294 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
2295 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
2296 ) -> Result<(), RuntimeShutdown> {
2297 if !self.epoch_store.randomness_state_enabled() {
2298 let num_dkg_messages = randomness_dkg_messages.len();
2299 let num_dkg_confirmations = randomness_dkg_confirmations.len();
2300 if num_dkg_messages + num_dkg_confirmations > 0 {
2301 debug_fatal!(
2302 "received {} RandomnessDkgMessage and {} RandomnessDkgConfirmation messages when randomness is not enabled",
2303 num_dkg_messages,
2304 num_dkg_confirmations
2305 );
2306 }
2307 return Ok(());
2308 }
2309
2310 let randomness_manager =
2311 randomness_manager.expect("randomness manager should exist if randomness is enabled");
2312
2313 let randomness_dkg_updates =
2314 self.process_randomness_dkg_messages(randomness_manager, randomness_dkg_messages);
2315
2316 let randomness_dkg_confirmation_updates = self.process_randomness_dkg_confirmations(
2317 state,
2318 randomness_manager,
2319 randomness_dkg_confirmations,
2320 );
2321
2322 let protocol_config = self.epoch_store.protocol_config();
2323 let always_advance_dkg_to_resolution = !protocol_config
2326 .allow_dkg_completion_after_timeout()
2327 && (protocol_config.always_advance_dkg_to_resolution()
2328 || (self.epoch_store.get_chain() == Chain::Mainnet
2329 && self.epoch_store.epoch() >= 1143))
2330 && randomness_manager.dkg_status() == DkgStatus::Pending;
2331
2332 if randomness_dkg_updates
2333 || randomness_dkg_confirmation_updates
2334 || always_advance_dkg_to_resolution
2335 {
2336 match randomness_manager
2337 .advance_dkg(&mut state.output, commit_info.round)
2338 .await
2339 {
2340 Ok(()) => {}
2341 Err(AdvanceDkgError::RuntimeShutdown) => return Err(RuntimeShutdown),
2342 Err(AdvanceDkgError::Sui(e)) => panic!("failed to advance randomness DKG: {e:?}"),
2343 }
2344 }
2345 Ok(())
2346 }
2347
2348 fn process_randomness_dkg_messages(
2349 &self,
2350 randomness_manager: &mut RandomnessManager,
2351 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
2352 ) -> bool {
2353 if randomness_dkg_messages.is_empty() {
2354 return false;
2355 }
2356
2357 let mut randomness_state_updated = false;
2358 for (authority, bytes) in randomness_dkg_messages {
2359 match bcs::from_bytes(&bytes) {
2360 Ok(message) => {
2361 randomness_manager
2362 .add_message(&authority, message)
2363 .expect("epoch ended");
2365 randomness_state_updated = true;
2366 }
2367
2368 Err(e) => {
2369 warn!(
2370 "Failed to deserialize RandomnessDkgMessage from {:?}: {e:?}",
2371 authority.concise(),
2372 );
2373 }
2374 }
2375 }
2376
2377 randomness_state_updated
2378 }
2379
2380 fn process_randomness_dkg_confirmations(
2381 &self,
2382 state: &mut CommitHandlerState,
2383 randomness_manager: &mut RandomnessManager,
2384 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
2385 ) -> bool {
2386 if randomness_dkg_confirmations.is_empty() {
2387 return false;
2388 }
2389
2390 let mut randomness_state_updated = false;
2391 for (authority, bytes) in randomness_dkg_confirmations {
2392 match bcs::from_bytes(&bytes) {
2393 Ok(message) => {
2394 randomness_manager
2395 .add_confirmation(&mut state.output, &authority, message)
2396 .expect("epoch ended");
2398 randomness_state_updated = true;
2399 }
2400 Err(e) => {
2401 warn!(
2402 "Failed to deserialize RandomnessDkgConfirmation from {:?}: {e:?}",
2403 authority.concise(),
2404 );
2405 }
2406 }
2407 }
2408
2409 randomness_state_updated
2410 }
2411
2412 fn process_end_of_publish_transactions(
2414 &self,
2415 state: &mut CommitHandlerState,
2416 end_of_publish_transactions: Vec<AuthorityName>,
2417 ) -> bool {
2418 let mut eop_aggregator = self.epoch_store.end_of_publish.try_lock().expect(
2419 "No contention on end_of_publish as it is only accessed from consensus handler",
2420 );
2421
2422 if eop_aggregator.has_quorum() {
2423 return true;
2424 }
2425
2426 if end_of_publish_transactions.is_empty() {
2427 return false;
2428 }
2429
2430 for authority in end_of_publish_transactions {
2431 info!("Received EndOfPublish from {:?}", authority.concise());
2432
2433 state.output.insert_end_of_publish(authority);
2436 if eop_aggregator
2437 .insert_generic(authority, ())
2438 .is_quorum_reached()
2439 {
2440 debug!(
2441 "Collected enough end_of_publish messages with last message from validator {:?}",
2442 authority.concise(),
2443 );
2444 return true;
2445 }
2446 }
2447
2448 false
2449 }
2450
2451 fn advance_end_of_epoch_state_machine(
2454 &self,
2455 state: &mut CommitHandlerState,
2456 deadline_reached: bool,
2457 ) -> (
2458 RwLockWriteGuard<'_, ReconfigState>,
2459 bool, Option<AbandonedDeferredTxns>,
2461 ) {
2462 let mut reconfig_state = self.epoch_store.get_reconfig_state_write_lock_guard();
2463 let start_state_is_reject_all_tx = reconfig_state.is_reject_all_tx();
2464
2465 if reconfig_state.should_accept_user_certs() {
2468 self.epoch_store.record_epoch_close_time_once();
2469 }
2470
2471 reconfig_state.close_all_certs();
2472
2473 let commit_has_deferred_txns = state.output.has_deferred_transactions();
2474 let previous_commits_have_deferred_txns = !self.epoch_store.deferred_transactions_empty();
2475 let has_deferred_txns = commit_has_deferred_txns || previous_commits_have_deferred_txns;
2476
2477 let should_close = !has_deferred_txns || deadline_reached;
2480 let final_round = should_close && !start_state_is_reject_all_tx;
2481
2482 let mut abandoned_deferred_txns = None;
2483 if final_round {
2484 info!("Transitioning to RejectAllTx");
2485 if has_deferred_txns {
2486 debug_assert!(deadline_reached);
2488 abandoned_deferred_txns = self.abandon_deferred_transactions(state);
2489 }
2490 reconfig_state.close_all_tx();
2491 } else if !should_close {
2492 debug!(
2493 "Blocking end of epoch on deferred transactions, from previous commits?={}, from this commit?={}",
2494 previous_commits_have_deferred_txns, commit_has_deferred_txns,
2495 );
2496 }
2497
2498 state.output.store_reconfig_state(reconfig_state.clone());
2499
2500 (reconfig_state, final_round, abandoned_deferred_txns)
2501 }
2502
2503 fn abandon_deferred_transactions(
2510 &self,
2511 state: &mut CommitHandlerState,
2512 ) -> Option<AbandonedDeferredTxns> {
2513 state.output.clear_deferred_transactions();
2514
2515 let already_deleted: BTreeSet<_> = state.output.get_deleted_deferred_txn_keys().collect();
2516 let mut count = 0;
2517 let mut sample = Vec::new();
2518 let mut abandoned_keys = Vec::new();
2519 {
2520 let deferred_transactions = self
2521 .epoch_store
2522 .consensus_output_cache
2523 .deferred_transactions
2524 .lock();
2525 for (key, txns) in deferred_transactions.iter() {
2526 if already_deleted.contains(key) {
2527 continue;
2529 }
2530 abandoned_keys.push(*key);
2531 count += txns.len();
2532 for tx in txns {
2533 if sample.len() < 10 {
2534 sample.push((*key, *tx.tx().digest()));
2535 }
2536 }
2537 }
2538 }
2539 if abandoned_keys.is_empty() {
2540 return None;
2541 }
2542 state
2543 .output
2544 .delete_loaded_deferred_transactions(&abandoned_keys);
2545 (count > 0).then_some(AbandonedDeferredTxns { count, sample })
2546 }
2547
2548 fn gather_commit_metadata(
2549 &self,
2550 consensus_commit: &impl ConsensusCommitAPI,
2551 ) -> (u64, AuthorityIndex, u64) {
2552 let timestamp = consensus_commit.commit_timestamp_ms();
2553 let leader_author = consensus_commit.leader_author_index();
2554 let commit_sub_dag_index = consensus_commit.commit_sub_dag_index();
2555
2556 let system_time_ms = SystemTime::now()
2557 .duration_since(UNIX_EPOCH)
2558 .unwrap()
2559 .as_millis() as i64;
2560
2561 let consensus_timestamp_bias_ms = system_time_ms - (timestamp as i64);
2562 let consensus_timestamp_bias_seconds = consensus_timestamp_bias_ms as f64 / 1000.0;
2563 self.metrics
2564 .consensus_timestamp_bias
2565 .observe(consensus_timestamp_bias_seconds);
2566
2567 let epoch_start = self
2568 .epoch_store
2569 .epoch_start_config()
2570 .epoch_start_timestamp_ms();
2571 let timestamp = if timestamp < epoch_start {
2572 error!(
2573 "Unexpected commit timestamp {timestamp} less then epoch start time {epoch_start}, author {leader_author}"
2574 );
2575 epoch_start
2576 } else {
2577 timestamp
2578 };
2579
2580 (timestamp, leader_author, commit_sub_dag_index)
2581 }
2582
2583 fn create_authenticator_state_update(
2584 &self,
2585 last_committed_round: u64,
2586 commit_info: &ConsensusCommitInfo,
2587 ) -> Option<VerifiedExecutableTransactionWithAliases> {
2588 let new_jwks = self
2596 .epoch_store
2597 .get_new_jwks(last_committed_round)
2598 .expect("Unrecoverable error in consensus handler");
2599
2600 if !new_jwks.is_empty() {
2601 let authenticator_state_update_transaction = authenticator_state_update_transaction(
2602 &self.epoch_store,
2603 commit_info.round,
2604 new_jwks,
2605 );
2606 debug!(
2607 "adding AuthenticatorStateUpdate({:?}) tx: {:?}",
2608 authenticator_state_update_transaction.digest(),
2609 authenticator_state_update_transaction,
2610 );
2611
2612 Some(VerifiedExecutableTransactionWithAliases::no_aliases(
2613 authenticator_state_update_transaction,
2614 ))
2615 } else {
2616 None
2617 }
2618 }
2619
2620 fn authority_hostname(&self, author: usize) -> &str {
2623 self.committee
2624 .to_authority_index(author)
2625 .map(|index| self.committee.authority(index).hostname.as_str())
2626 .unwrap_or("unknown")
2627 }
2628
2629 #[instrument(level = "trace", skip_all)]
2632 fn filter_consensus_txns(
2633 &mut self,
2634 initial_reconfig_state: ReconfigState,
2635 commit_info: &ConsensusCommitInfo,
2636 block_transactions: ParsedConsensusTransactions,
2637 ) -> FilteredConsensusOutput {
2638 let _scope = monitored_scope("ConsensusCommitHandler::filter_consensus_txns");
2639 let mut transactions = Vec::new();
2640 let mut owned_object_locks = HashMap::new();
2641 let mut dropped_transaction_keys = Vec::new();
2642 let mut status_updates: Vec<(ConsensusPosition, ConsensusTxStatus)> = Vec::new();
2646 let mut contested_transaction_digests: HashMap<TransactionDigest, ConflictInfo> =
2647 HashMap::new();
2648 let mut lock_holder_authors: HashMap<TransactionDigest, usize> = HashMap::new();
2652 let epoch = self.epoch_store.epoch();
2653 let mut num_finalized_user_transactions = vec![0; self.committee.size()];
2654 let mut num_rejected_user_transactions = vec![0; self.committee.size()];
2655 let mut num_dropped_user_transactions = vec![0; self.committee.size()];
2656
2657 let existing_locks = {
2663 let mut prefetch_refs: Vec<ObjectRef> = Vec::new();
2664 for (_block, parsed_transactions) in &block_transactions {
2665 for parsed in parsed_transactions {
2666 if let ConsensusTransactionKind::UserTransactionV2(tx_with_claims) =
2667 &parsed.transaction.kind
2668 && let Some(refs) = owned_object_refs_to_lock(tx_with_claims)
2669 {
2670 prefetch_refs.extend(refs);
2671 }
2672 }
2673 }
2674 prefetch_refs.sort();
2675 prefetch_refs.dedup();
2676 self.epoch_store.get_owned_object_locks_map(&prefetch_refs)
2677 };
2678
2679 for (block, parsed_transactions) in block_transactions {
2680 let author = block.author.value();
2681 let author_hostname = self.committee.authority(block.author).hostname.as_str();
2682 self.last_consensus_stats.stats.inc_num_messages(author);
2684
2685 status_updates.push((
2687 ConsensusPosition::ping(epoch, block),
2688 ConsensusTxStatus::Finalized,
2689 ));
2690
2691 for (tx_index, parsed) in parsed_transactions.into_iter().enumerate() {
2692 let position = ConsensusPosition {
2693 epoch,
2694 block,
2695 index: tx_index as TransactionIndex,
2696 };
2697
2698 if let Some(tx) = parsed.transaction.kind.as_user_transaction() {
2701 let digest = tx.digest();
2702 if let Some((spam_weight, submitter_client_addrs)) = self
2703 .epoch_store
2704 .submitted_transaction_cache
2705 .increment_submission_count(digest)
2706 {
2707 if let Some(ref traffic_controller) = self.traffic_controller {
2708 debug!(
2709 "Transaction {digest} exceeded submission limits, spam_weight: {spam_weight:?} applied to {} client addresses",
2710 submitter_client_addrs.len()
2711 );
2712
2713 for addr in submitter_client_addrs {
2715 traffic_controller.tally(
2716 TrafficTally::new(Some(addr), None, None, spam_weight.clone())
2717 .with_method(
2718 "consensus_submission_limit_exceeded".to_string(),
2719 ),
2720 );
2721 }
2722 } else {
2723 warn!(
2724 "Transaction {digest} exceeded submission limits, spam_weight: {spam_weight:?} for {} client addresses (traffic controller not configured)",
2725 submitter_client_addrs.len()
2726 );
2727 }
2728 }
2729 }
2730
2731 let kind = classify(&parsed.transaction);
2734 let outcome = if parsed.rejected {
2735 "rejected"
2736 } else {
2737 "accepted"
2738 };
2739 self.metrics
2740 .consensus_handler_processed
2741 .with_label_values(&[kind, outcome])
2742 .inc();
2743 self.metrics.observe_consensus_handler_transaction_size(
2744 kind,
2745 outcome,
2746 parsed.serialized_len,
2747 );
2748 if parsed.transaction.is_user_transaction() {
2752 self.metrics
2753 .consensus_handler_processed_user_transactions
2754 .with_label_values(&[outcome, author_hostname])
2755 .inc();
2756 }
2757
2758 if parsed.rejected {
2759 if parsed.transaction.is_user_transaction() {
2760 status_updates.push((position, ConsensusTxStatus::Rejected));
2761 num_rejected_user_transactions[author] += 1;
2762 }
2763 continue;
2765 }
2766
2767 if parsed.transaction.is_user_transaction() {
2768 self.last_consensus_stats
2769 .stats
2770 .inc_num_user_transactions(author);
2771 }
2772
2773 if !initial_reconfig_state.should_accept_consensus_certs() {
2774 match &parsed.transaction.kind {
2777 ConsensusTransactionKind::UserTransactionV2(_)
2778 | ConsensusTransactionKind::UserTransaction(_)
2780 | ConsensusTransactionKind::CertifiedTransaction(_)
2781 | ConsensusTransactionKind::CapabilityNotification(_)
2782 | ConsensusTransactionKind::CapabilityNotificationV2(_)
2783 | ConsensusTransactionKind::EndOfPublish(_)
2784 | ConsensusTransactionKind::ExecutionTimeObservation(_)
2786 | ConsensusTransactionKind::NewJWKFetched(_, _, _)
2787 | ConsensusTransactionKind::UpdateTransactionDenyConfig(_) => {
2788 if parsed.transaction.is_user_transaction() {
2795 status_updates.push((position, ConsensusTxStatus::Dropped));
2796 num_dropped_user_transactions[author] += 1;
2797 self.metrics
2798 .consensus_handler_dropped_transactions
2799 .with_label_values(&["end_of_epoch"])
2800 .inc();
2801 }
2802 debug!(
2803 "Ignoring consensus transaction {:?} because of end of epoch",
2804 parsed.transaction.key()
2805 );
2806 continue;
2807 }
2808
2809 ConsensusTransactionKind::CheckpointSignature(_)
2811 | ConsensusTransactionKind::CheckpointSignatureV2(_)
2812 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
2813 | ConsensusTransactionKind::RandomnessDkgMessage(_, _)
2814 | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _) => ()
2815 }
2816 }
2817
2818 if !initial_reconfig_state.should_accept_tx() {
2819 match &parsed.transaction.kind {
2820 ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
2821 | ConsensusTransactionKind::RandomnessDkgMessage(_, _) => continue,
2822 _ => {}
2823 }
2824 }
2825
2826 match &parsed.transaction.kind {
2828 ConsensusTransactionKind::CapabilityNotification(_)
2829 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
2830 | ConsensusTransactionKind::CheckpointSignature(_) => {
2831 debug_fatal!(
2832 "BUG: saw deprecated tx {:?}for commit round {}",
2833 parsed.transaction.key(),
2834 commit_info.round
2835 );
2836 continue;
2837 }
2838 _ => {}
2839 }
2840
2841 if parsed.transaction.is_user_transaction() {
2842 let author_name = self
2843 .epoch_store
2844 .committee()
2845 .authority_by_index(author as u32)
2846 .unwrap();
2847 if self
2848 .epoch_store
2849 .has_received_end_of_publish_from(author_name)
2850 {
2851 status_updates.push((position, ConsensusTxStatus::Dropped));
2858 num_dropped_user_transactions[author] += 1;
2859 self.metrics
2860 .consensus_handler_dropped_transactions
2861 .with_label_values(&["end_of_publish"])
2862 .inc();
2863 warn!(
2864 "Ignoring consensus transaction {:?} from authority {:?}, which already sent EndOfPublish message to consensus",
2865 author_name.concise(),
2866 parsed.transaction.key(),
2867 );
2868 continue;
2869 }
2870 }
2871
2872 if let ConsensusTransactionKind::UserTransactionV2(tx_with_claims) =
2878 &parsed.transaction.kind
2879 {
2880 let tx = tx_with_claims.tx();
2881 let Some(owned_object_refs) = owned_object_refs_to_lock(tx_with_claims) else {
2882 self.metrics
2884 .consensus_handler_dropped_transactions
2885 .with_label_values(&["invalid_input"])
2886 .inc();
2887 status_updates.push((position, ConsensusTxStatus::Dropped));
2888 num_dropped_user_transactions[author] += 1;
2889 if let Err(e) = tx.transaction_data().input_objects() {
2893 self.epoch_store
2894 .set_rejection_vote_reason(position, &e.into());
2895 }
2896 dropped_transaction_keys.push(parsed.transaction.key());
2897 debug_fatal!("Invalid input objects for transaction {}", tx.digest());
2898 continue;
2899 };
2900
2901 match self
2902 .epoch_store
2903 .try_acquire_owned_object_locks_post_consensus(
2904 &owned_object_refs,
2905 *tx.digest(),
2906 &owned_object_locks,
2907 &existing_locks,
2908 ) {
2909 Ok(new_locks) => {
2910 owned_object_locks.extend(new_locks.into_iter());
2911 lock_holder_authors.entry(*tx.digest()).or_insert(author);
2912 status_updates.push((position, ConsensusTxStatus::Finalized));
2914 num_finalized_user_transactions[author] += 1;
2915 }
2916 Err(e) => {
2917 let gas_object_ids: HashSet<ObjectID> = tx
2921 .transaction_data()
2922 .gas()
2923 .iter()
2924 .map(|obj_ref| obj_ref.0)
2925 .collect();
2926 let mut is_intra_commit_conflict = false;
2927 for obj_ref in &owned_object_refs {
2928 if let Some(holder_digest) = owned_object_locks.get(obj_ref) {
2929 is_intra_commit_conflict = true;
2930 let info = contested_transaction_digests
2931 .entry(*holder_digest)
2932 .or_default();
2933 info.winner_author = lock_holder_authors
2934 .get(holder_digest)
2935 .copied()
2936 .unwrap_or(author);
2937 if gas_object_ids.contains(&obj_ref.0) {
2938 info.gas_object_conflicts += 1;
2939 } else {
2940 info.non_gas_object_conflicts += 1;
2941 }
2942 }
2943 }
2944 if is_intra_commit_conflict {
2948 self.metrics
2949 .consensus_handler_double_spend_conflicting_authority
2950 .with_label_values(&[self.authority_hostname(author), "loser"])
2951 .inc();
2952 }
2953 debug!("Dropping transaction {}: {}", tx.digest(), e);
2954 self.metrics
2955 .consensus_handler_dropped_transactions
2956 .with_label_values(&["lock_conflict"])
2957 .inc();
2958 status_updates.push((position, ConsensusTxStatus::Dropped));
2959 num_dropped_user_transactions[author] += 1;
2960 self.epoch_store.set_rejection_vote_reason(position, &e);
2961 dropped_transaction_keys.push(parsed.transaction.key());
2962 continue;
2963 }
2964 }
2965 }
2966
2967 let transaction = SequencedConsensusTransactionKind::External(parsed.transaction);
2968 transactions.push((transaction, author as u32));
2969 }
2970 }
2971
2972 self.epoch_store.set_consensus_tx_statuses(status_updates);
2976
2977 for (i, authority) in self.committee.authorities() {
2978 let hostname = &authority.hostname;
2979 self.metrics
2980 .consensus_committed_messages
2981 .with_label_values(&[hostname])
2982 .set(self.last_consensus_stats.stats.get_num_messages(i.value()) as i64);
2983 self.metrics
2984 .consensus_committed_user_transactions
2985 .with_label_values(&[hostname])
2986 .set(
2987 self.last_consensus_stats
2988 .stats
2989 .get_num_user_transactions(i.value()) as i64,
2990 );
2991 self.metrics
2992 .consensus_finalized_user_transactions
2993 .with_label_values(&[hostname])
2994 .add(num_finalized_user_transactions[i.value()] as i64);
2995 self.metrics
2996 .consensus_rejected_user_transactions
2997 .with_label_values(&[hostname])
2998 .add(num_rejected_user_transactions[i.value()] as i64);
2999 self.metrics
3000 .consensus_dropped_user_transactions
3001 .with_label_values(&[hostname])
3002 .add(num_dropped_user_transactions[i.value()] as i64);
3003 }
3004
3005 FilteredConsensusOutput {
3006 transactions,
3007 owned_object_locks,
3008 dropped_transaction_keys,
3009 contested_transaction_digests,
3010 }
3011 }
3012
3013 fn deduplicate_consensus_txns(
3014 &mut self,
3015 state: &mut CommitHandlerState,
3016 commit_info: &ConsensusCommitInfo,
3017 transactions: Vec<(SequencedConsensusTransactionKind, u32)>,
3018 ) -> Vec<VerifiedSequencedConsensusTransaction> {
3019 let _scope = monitored_scope("ConsensusCommitHandler::deduplicate_consensus_txns");
3020 let mut all_transactions = Vec::new();
3021
3022 let mut occurrence_counts: HashMap<SequencedConsensusTransactionKey, u32> = HashMap::new();
3025 let mut first_commit_keys: HashSet<SequencedConsensusTransactionKey> = HashSet::new();
3027
3028 for (seq, (transaction, cert_origin)) in transactions.into_iter().enumerate() {
3029 let current_tx_index = ExecutionIndices {
3033 last_committed_round: commit_info.round,
3034 sub_dag_index: commit_info.consensus_commit_ref.index.into(),
3035 transaction_index: (seq + 1) as u64,
3036 };
3037
3038 self.last_consensus_stats.index = current_tx_index;
3039
3040 let certificate_author = *self
3041 .epoch_store
3042 .committee()
3043 .authority_by_index(cert_origin)
3044 .unwrap();
3045
3046 let sequenced_transaction = SequencedConsensusTransaction {
3047 certificate_author_index: cert_origin,
3048 certificate_author,
3049 consensus_index: current_tx_index,
3050 transaction,
3051 };
3052
3053 let Some(verified_transaction) = self
3054 .epoch_store
3055 .verify_consensus_transaction(sequenced_transaction)
3056 else {
3057 continue;
3058 };
3059
3060 let key = verified_transaction.0.key();
3061
3062 if let Some(tx_digest) = key.user_transaction_digest() {
3063 self.epoch_store
3064 .cache_recently_finalized_transaction(tx_digest);
3065 }
3066
3067 let count = occurrence_counts.entry(key.clone()).or_insert(0);
3070 *count += 1;
3071 let in_commit = *count > 1;
3072
3073 let in_cache = self.processed_cache.put(key.clone(), ()).is_some();
3074 if in_commit || in_cache {
3075 self.metrics.skipped_consensus_txns_cache_hit.inc();
3076 continue;
3077 }
3078 if self
3079 .epoch_store
3080 .is_consensus_message_processed(&key)
3081 .expect("db error")
3082 {
3083 self.metrics.skipped_consensus_txns.inc();
3084 continue;
3085 }
3086
3087 first_commit_keys.insert(key.clone());
3088
3089 state.output.record_consensus_message_processed(key);
3090
3091 all_transactions.push(verified_transaction);
3092 }
3093
3094 for key in first_commit_keys {
3095 if let Some(&count) = occurrence_counts.get(&key)
3096 && count > 1
3097 {
3098 self.metrics
3099 .consensus_handler_duplicate_tx_count
3100 .observe(count as f64);
3101 }
3102 }
3103
3104 assert!(
3106 state.occurrence_counts.is_empty(),
3107 "occurrence_counts should be empty before populating"
3108 );
3109 state.occurrence_counts.reserve(occurrence_counts.len());
3110 state.occurrence_counts.extend(
3111 occurrence_counts
3112 .into_iter()
3113 .filter_map(|(key, count)| key.user_transaction_digest().map(|d| (d, count))),
3114 );
3115
3116 all_transactions
3117 }
3118
3119 fn build_commit_handler_input(
3120 &self,
3121 transactions: Vec<VerifiedSequencedConsensusTransaction>,
3122 ) -> CommitHandlerInput {
3123 let _scope = monitored_scope("ConsensusCommitHandler::build_commit_handler_input");
3124 let epoch = self.epoch_store.epoch();
3125 let mut commit_handler_input = CommitHandlerInput::default();
3126
3127 for VerifiedSequencedConsensusTransaction(transaction) in transactions.into_iter() {
3128 match transaction.transaction {
3129 SequencedConsensusTransactionKind::External(consensus_transaction) => {
3130 match consensus_transaction.kind {
3131 ConsensusTransactionKind::UserTransactionV2(tx) => {
3133 let used_alias_versions = tx.aliases();
3135 let inner_tx = tx.into_tx();
3136 let tx = VerifiedTransaction::new_unchecked(inner_tx);
3138 let transaction =
3140 VerifiedExecutableTransaction::new_from_consensus(tx, epoch);
3141 if let Some(used_alias_versions) = used_alias_versions {
3142 commit_handler_input
3143 .user_transactions
3144 .push(WithAliases::new(transaction, used_alias_versions));
3145 } else {
3146 commit_handler_input.user_transactions.push(
3147 VerifiedExecutableTransactionWithAliases::no_aliases(
3148 transaction,
3149 ),
3150 );
3151 }
3152 }
3153
3154 ConsensusTransactionKind::EndOfPublish(authority_public_key_bytes) => {
3156 commit_handler_input
3157 .end_of_publish_transactions
3158 .push(authority_public_key_bytes);
3159 }
3160 ConsensusTransactionKind::NewJWKFetched(
3161 authority_public_key_bytes,
3162 jwk_id,
3163 jwk,
3164 ) => {
3165 commit_handler_input.new_jwks.push((
3166 authority_public_key_bytes,
3167 jwk_id,
3168 jwk,
3169 ));
3170 }
3171 ConsensusTransactionKind::RandomnessDkgMessage(
3172 authority_public_key_bytes,
3173 items,
3174 ) => {
3175 commit_handler_input
3176 .randomness_dkg_messages
3177 .push((authority_public_key_bytes, items));
3178 }
3179 ConsensusTransactionKind::RandomnessDkgConfirmation(
3180 authority_public_key_bytes,
3181 items,
3182 ) => {
3183 commit_handler_input
3184 .randomness_dkg_confirmations
3185 .push((authority_public_key_bytes, items));
3186 }
3187 ConsensusTransactionKind::CapabilityNotificationV2(
3188 authority_capabilities_v2,
3189 ) => {
3190 commit_handler_input
3191 .capability_notifications
3192 .push(authority_capabilities_v2);
3193 }
3194 ConsensusTransactionKind::ExecutionTimeObservation(
3195 execution_time_observation,
3196 ) => {
3197 commit_handler_input
3198 .execution_time_observations
3199 .push(execution_time_observation);
3200 }
3201 ConsensusTransactionKind::CheckpointSignatureV2(
3202 checkpoint_signature_message,
3203 ) => {
3204 commit_handler_input
3205 .checkpoint_signature_messages
3206 .push(*checkpoint_signature_message);
3207 }
3208 ConsensusTransactionKind::UpdateTransactionDenyConfig(msg) => {
3209 commit_handler_input
3210 .transaction_deny_config_updates
3211 .push((transaction.certificate_author, *msg));
3212 }
3213
3214 ConsensusTransactionKind::CheckpointSignature(_)
3217 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
3218 | ConsensusTransactionKind::CapabilityNotification(_)
3219 | ConsensusTransactionKind::CertifiedTransaction(_)
3220 | ConsensusTransactionKind::UserTransaction(_) => {
3221 unreachable!("filtered earlier")
3222 }
3223 }
3224 }
3225 SequencedConsensusTransactionKind::System(_verified_envelope) => unreachable!(),
3227 }
3228 }
3229
3230 commit_handler_input
3231 }
3232}
3233
3234pub(crate) type SchedulerMessage = (
3237 Vec<(Schedulable, AssignedVersions)>,
3238 Option<SettlementBatchInfo>,
3239);
3240
3241#[derive(Clone)]
3242pub(crate) struct ExecutionSchedulerSender {
3243 sender: monitored_mpsc::UnboundedSender<SchedulerMessage>,
3244}
3245
3246impl ExecutionSchedulerSender {
3247 fn start(
3248 settlement_scheduler: SettlementScheduler,
3249 epoch_store: Arc<AuthorityPerEpochStore>,
3250 ) -> Self {
3251 let (sender, recv) = monitored_mpsc::unbounded_channel("execution_scheduler_sender");
3252 spawn_monitored_task!(Self::run(recv, settlement_scheduler, epoch_store));
3253 Self { sender }
3254 }
3255
3256 pub(crate) fn new_for_testing(
3257 sender: monitored_mpsc::UnboundedSender<SchedulerMessage>,
3258 ) -> Self {
3259 Self { sender }
3260 }
3261
3262 fn send(
3263 &self,
3264 transactions: Vec<(Schedulable, AssignedVersions)>,
3265 settlement: Option<SettlementBatchInfo>,
3266 ) {
3267 let _ = self.sender.send((transactions, settlement));
3268 }
3269
3270 async fn run(
3271 mut recv: monitored_mpsc::UnboundedReceiver<SchedulerMessage>,
3272 settlement_scheduler: SettlementScheduler,
3273 epoch_store: Arc<AuthorityPerEpochStore>,
3274 ) {
3275 while let Some((transactions, settlement)) = recv.recv().await {
3276 let _guard = monitored_scope("ConsensusHandler::enqueue");
3277 let txns = transactions
3278 .into_iter()
3279 .map(|(txn, versions)| (txn, ExecutionEnv::new().with_assigned_versions(versions)))
3280 .collect();
3281 if let Some(settlement) = settlement {
3282 settlement_scheduler.enqueue_v2(txns, settlement, &epoch_store);
3283 } else {
3284 settlement_scheduler.enqueue(txns, &epoch_store);
3285 }
3286 }
3287 }
3288}
3289
3290const CONSENSUS_HANDLER_DESERIALIZE_CHANNEL_CAPACITY: usize = 2;
3294
3295type ParsedConsensusTransactions = Vec<(BlockRef, Vec<ParsedTransaction>)>;
3299
3300pub(crate) struct MysticetiConsensusHandler {
3302 tasks: JoinSet<()>,
3303}
3304
3305impl MysticetiConsensusHandler {
3306 pub(crate) fn new(
3307 last_processed_commit_at_startup: CommitIndex,
3308 mut consensus_handler: ConsensusHandler<CheckpointService>,
3309 mut commit_receiver: UnboundedReceiver<consensus_core::CommittedSubDag>,
3310 commit_consumer_monitor: Arc<CommitConsumerMonitor>,
3311 ) -> Self {
3312 debug!(
3313 last_processed_commit_at_startup,
3314 "Starting consensus replay"
3315 );
3316 let mut tasks = JoinSet::new();
3317
3318 let (parsed_sender, mut parsed_receiver) = monitored_mpsc::channel(
3324 "consensus_deserialized_commits",
3325 CONSENSUS_HANDLER_DESERIALIZE_CHANNEL_CAPACITY,
3326 );
3327 tasks.spawn(monitored_future!(async move {
3328 while let Some(consensus_commit) = commit_receiver.recv().await {
3329 let transactions: ParsedConsensusTransactions = {
3330 let _scope = monitored_scope("ConsensusCommitHandler::deserialize_worker");
3331 consensus_commit.transactions()
3332 };
3333 if parsed_sender
3337 .send((consensus_commit, transactions))
3338 .await
3339 .is_err()
3340 {
3341 break;
3342 }
3343 }
3344 }));
3345
3346 tasks.spawn(monitored_future!(async move {
3348 while let Some((consensus_commit, transactions)) = parsed_receiver.recv().await {
3350 let commit_index = consensus_commit.commit_ref.index;
3351 if commit_index <= last_processed_commit_at_startup {
3352 consensus_handler.handle_prior_consensus_commit(consensus_commit);
3353 } else if let Err(e) = consensus_handler
3354 .handle_consensus_commit(consensus_commit, transactions)
3355 .await
3356 {
3357 info!(
3358 "Consensus commit {commit_index} was not handled ({e:?}); it will be replayed after restart"
3359 );
3360 break;
3361 }
3362 commit_consumer_monitor.set_highest_handled_commit(commit_index);
3363 }
3364 }));
3365 Self { tasks }
3366 }
3367
3368 pub(crate) async fn abort(&mut self) {
3369 self.tasks.shutdown().await;
3370 }
3371}
3372
3373fn authenticator_state_update_transaction(
3374 epoch_store: &AuthorityPerEpochStore,
3375 round: u64,
3376 mut new_active_jwks: Vec<ActiveJwk>,
3377) -> VerifiedExecutableTransaction {
3378 let epoch = epoch_store.epoch();
3379 new_active_jwks.sort();
3380
3381 info!("creating authenticator state update transaction");
3382 assert!(epoch_store.authenticator_state_enabled());
3383 let transaction = VerifiedTransaction::new_authenticator_state_update(
3384 epoch,
3385 round,
3386 new_active_jwks,
3387 epoch_store
3388 .epoch_start_config()
3389 .authenticator_obj_initial_shared_version()
3390 .expect("authenticator state obj must exist"),
3391 );
3392 VerifiedExecutableTransaction::new_system(transaction, epoch)
3393}
3394
3395fn owned_object_refs_to_lock(
3401 tx_with_claims: &PlainTransactionWithClaims,
3402) -> Option<Vec<ObjectRef>> {
3403 let immutable_object_ids: HashSet<ObjectID> =
3404 tx_with_claims.get_immutable_objects().into_iter().collect();
3405 let input_objects = tx_with_claims
3406 .tx()
3407 .transaction_data()
3408 .input_objects()
3409 .ok()?;
3410 Some(
3411 input_objects
3412 .iter()
3413 .filter_map(|obj| match obj {
3414 InputObjectKind::ImmOrOwnedMoveObject(obj_ref)
3415 if !immutable_object_ids.contains(&obj_ref.0) =>
3416 {
3417 Some(*obj_ref)
3418 }
3419 _ => None,
3420 })
3421 .collect(),
3422 )
3423}
3424
3425pub(crate) fn tx_type_label(transactions: &[ConsensusTransaction]) -> &'static str {
3427 match transactions {
3428 [transaction] => classify(transaction),
3429 _ => "soft_bundle",
3430 }
3431}
3432
3433pub(crate) fn classify(transaction: &ConsensusTransaction) -> &'static str {
3434 match &transaction.kind {
3435 ConsensusTransactionKind::CertifiedTransaction(_) => "_deprecated_certificate",
3437 ConsensusTransactionKind::CheckpointSignature(_) => "checkpoint_signature",
3438 ConsensusTransactionKind::CheckpointSignatureV2(_) => "checkpoint_signature",
3439 ConsensusTransactionKind::EndOfPublish(_) => "end_of_publish",
3440 ConsensusTransactionKind::CapabilityNotification(_) => "capability_notification",
3441 ConsensusTransactionKind::CapabilityNotificationV2(_) => "capability_notification_v2",
3442 ConsensusTransactionKind::NewJWKFetched(_, _, _) => "new_jwk_fetched",
3443 ConsensusTransactionKind::RandomnessStateUpdate(_, _) => "randomness_state_update",
3444 ConsensusTransactionKind::RandomnessDkgMessage(_, _) => "randomness_dkg_message",
3445 ConsensusTransactionKind::RandomnessDkgConfirmation(_, _) => "randomness_dkg_confirmation",
3446 ConsensusTransactionKind::UserTransaction(_) => "_deprecated_user_transaction",
3447 ConsensusTransactionKind::UserTransactionV2(tx) => {
3448 if tx.tx().is_consensus_tx() {
3449 "shared_user_transaction_v2"
3450 } else {
3451 "owned_user_transaction_v2"
3452 }
3453 }
3454 ConsensusTransactionKind::ExecutionTimeObservation(_) => "execution_time_observation",
3455 ConsensusTransactionKind::UpdateTransactionDenyConfig(_) => {
3456 "update_transaction_deny_config"
3457 }
3458 }
3459}
3460
3461#[derive(Debug, Clone, Serialize, Deserialize)]
3462pub struct SequencedConsensusTransaction {
3463 pub certificate_author_index: AuthorityIndex,
3464 pub certificate_author: AuthorityName,
3465 pub consensus_index: ExecutionIndices,
3466 pub transaction: SequencedConsensusTransactionKind,
3467}
3468
3469#[derive(Debug, Clone)]
3470#[allow(clippy::large_enum_variant)]
3471pub enum SequencedConsensusTransactionKind {
3472 External(ConsensusTransaction),
3473 System(VerifiedExecutableTransaction),
3474}
3475
3476impl Serialize for SequencedConsensusTransactionKind {
3477 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3478 let serializable = SerializableSequencedConsensusTransactionKind::from(self);
3479 serializable.serialize(serializer)
3480 }
3481}
3482
3483impl<'de> Deserialize<'de> for SequencedConsensusTransactionKind {
3484 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3485 let serializable =
3486 SerializableSequencedConsensusTransactionKind::deserialize(deserializer)?;
3487 Ok(serializable.into())
3488 }
3489}
3490
3491#[derive(Debug, Clone, Serialize, Deserialize)]
3495#[allow(clippy::large_enum_variant)]
3496enum SerializableSequencedConsensusTransactionKind {
3497 External(ConsensusTransaction),
3498 System(TrustedExecutableTransaction),
3499}
3500
3501impl From<&SequencedConsensusTransactionKind> for SerializableSequencedConsensusTransactionKind {
3502 fn from(kind: &SequencedConsensusTransactionKind) -> Self {
3503 match kind {
3504 SequencedConsensusTransactionKind::External(ext) => {
3505 SerializableSequencedConsensusTransactionKind::External(ext.clone())
3506 }
3507 SequencedConsensusTransactionKind::System(txn) => {
3508 SerializableSequencedConsensusTransactionKind::System(txn.clone().serializable())
3509 }
3510 }
3511 }
3512}
3513
3514impl From<SerializableSequencedConsensusTransactionKind> for SequencedConsensusTransactionKind {
3515 fn from(kind: SerializableSequencedConsensusTransactionKind) -> Self {
3516 match kind {
3517 SerializableSequencedConsensusTransactionKind::External(ext) => {
3518 SequencedConsensusTransactionKind::External(ext)
3519 }
3520 SerializableSequencedConsensusTransactionKind::System(txn) => {
3521 SequencedConsensusTransactionKind::System(txn.into())
3522 }
3523 }
3524 }
3525}
3526
3527#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
3528pub enum SequencedConsensusTransactionKey {
3529 External(ConsensusTransactionKey),
3530 System(TransactionDigest),
3531}
3532
3533impl SequencedConsensusTransactionKey {
3534 pub fn user_transaction_digest(&self) -> Option<TransactionDigest> {
3535 match self {
3536 SequencedConsensusTransactionKey::External(key) => match key {
3537 ConsensusTransactionKey::Certificate(digest) => Some(*digest),
3538 _ => None,
3539 },
3540 SequencedConsensusTransactionKey::System(_) => None,
3541 }
3542 }
3543}
3544
3545impl SequencedConsensusTransactionKind {
3546 pub fn key(&self) -> SequencedConsensusTransactionKey {
3547 match self {
3548 SequencedConsensusTransactionKind::External(ext) => {
3549 SequencedConsensusTransactionKey::External(ext.key())
3550 }
3551 SequencedConsensusTransactionKind::System(txn) => {
3552 SequencedConsensusTransactionKey::System(*txn.digest())
3553 }
3554 }
3555 }
3556
3557 pub fn get_tracking_id(&self) -> u64 {
3558 match self {
3559 SequencedConsensusTransactionKind::External(ext) => ext.get_tracking_id(),
3560 SequencedConsensusTransactionKind::System(_txn) => 0,
3561 }
3562 }
3563
3564 pub fn is_executable_transaction(&self) -> bool {
3565 match self {
3566 SequencedConsensusTransactionKind::External(ext) => ext.is_user_transaction(),
3567 SequencedConsensusTransactionKind::System(_) => true,
3568 }
3569 }
3570
3571 pub fn executable_transaction_digest(&self) -> Option<TransactionDigest> {
3572 match self {
3573 SequencedConsensusTransactionKind::External(ext) => match &ext.kind {
3574 ConsensusTransactionKind::UserTransactionV2(txn) => Some(*txn.tx().digest()),
3575 _ => None,
3576 },
3577 SequencedConsensusTransactionKind::System(txn) => Some(*txn.digest()),
3578 }
3579 }
3580
3581 pub fn is_end_of_publish(&self) -> bool {
3582 match self {
3583 SequencedConsensusTransactionKind::External(ext) => {
3584 matches!(ext.kind, ConsensusTransactionKind::EndOfPublish(..))
3585 }
3586 SequencedConsensusTransactionKind::System(_) => false,
3587 }
3588 }
3589}
3590
3591impl SequencedConsensusTransaction {
3592 pub fn sender_authority(&self) -> AuthorityName {
3593 self.certificate_author
3594 }
3595
3596 pub fn key(&self) -> SequencedConsensusTransactionKey {
3597 self.transaction.key()
3598 }
3599
3600 pub fn is_end_of_publish(&self) -> bool {
3601 if let SequencedConsensusTransactionKind::External(ref transaction) = self.transaction {
3602 matches!(transaction.kind, ConsensusTransactionKind::EndOfPublish(..))
3603 } else {
3604 false
3605 }
3606 }
3607
3608 pub fn try_take_execution_time_observation(&mut self) -> Option<ExecutionTimeObservation> {
3609 if let SequencedConsensusTransactionKind::External(ConsensusTransaction {
3610 kind: ConsensusTransactionKind::ExecutionTimeObservation(observation),
3611 ..
3612 }) = &mut self.transaction
3613 {
3614 Some(std::mem::take(observation))
3615 } else {
3616 None
3617 }
3618 }
3619
3620 pub fn is_system(&self) -> bool {
3621 matches!(
3622 self.transaction,
3623 SequencedConsensusTransactionKind::System(_)
3624 )
3625 }
3626
3627 pub fn is_user_tx_with_randomness(&self, randomness_state_enabled: bool) -> bool {
3628 if !randomness_state_enabled {
3629 return false;
3632 }
3633 match &self.transaction {
3634 SequencedConsensusTransactionKind::External(ConsensusTransaction {
3635 kind: ConsensusTransactionKind::UserTransactionV2(txn),
3636 ..
3637 }) => txn.tx().transaction_data().uses_randomness(),
3638 _ => false,
3639 }
3640 }
3641
3642 pub fn as_consensus_txn(&self) -> Option<&SenderSignedData> {
3643 match &self.transaction {
3644 SequencedConsensusTransactionKind::External(ConsensusTransaction {
3645 kind: ConsensusTransactionKind::UserTransactionV2(txn),
3646 ..
3647 }) if txn.tx().is_consensus_tx() => Some(txn.tx().data()),
3648 SequencedConsensusTransactionKind::System(txn) if txn.is_consensus_tx() => {
3649 Some(txn.data())
3650 }
3651 _ => None,
3652 }
3653 }
3654}
3655
3656#[derive(Debug, Clone, Serialize, Deserialize)]
3657pub struct VerifiedSequencedConsensusTransaction(pub SequencedConsensusTransaction);
3658
3659#[cfg(test)]
3660impl VerifiedSequencedConsensusTransaction {
3661 pub fn new_test(transaction: ConsensusTransaction) -> Self {
3662 Self(SequencedConsensusTransaction::new_test(transaction))
3663 }
3664}
3665
3666impl SequencedConsensusTransaction {
3667 pub fn new_test(transaction: ConsensusTransaction) -> Self {
3668 Self {
3669 certificate_author_index: 0,
3670 certificate_author: AuthorityName::ZERO,
3671 consensus_index: Default::default(),
3672 transaction: SequencedConsensusTransactionKind::External(transaction),
3673 }
3674 }
3675}
3676
3677#[derive(Serialize, Deserialize)]
3678pub(crate) struct CommitIntervalObserver {
3679 ring_buffer: VecDeque<u64>,
3680}
3681
3682impl CommitIntervalObserver {
3683 pub fn new(window_size: u32) -> Self {
3684 Self {
3685 ring_buffer: VecDeque::with_capacity(window_size as usize),
3686 }
3687 }
3688
3689 pub fn observe_commit_time(&mut self, consensus_commit: &impl ConsensusCommitAPI) {
3690 let commit_time = consensus_commit.commit_timestamp_ms();
3691 if self.ring_buffer.len() == self.ring_buffer.capacity() {
3692 self.ring_buffer.pop_front();
3693 }
3694 self.ring_buffer.push_back(commit_time);
3695 }
3696
3697 pub fn commit_interval_estimate(&self) -> Option<Duration> {
3698 if self.ring_buffer.len() <= 1 {
3699 None
3700 } else {
3701 let first = self.ring_buffer.front().unwrap();
3702 let last = self.ring_buffer.back().unwrap();
3703 let duration = last.saturating_sub(*first);
3704 let num_commits = self.ring_buffer.len() as u64;
3705 Some(Duration::from_millis(duration.div_ceil(num_commits)))
3706 }
3707 }
3708}
3709
3710#[cfg(test)]
3711mod tests {
3712 use consensus_core::{
3713 BlockAPI, CommitDigest, CommitRef, CommittedSubDag, TestBlock, Transaction, VerifiedBlock,
3714 };
3715 use futures::pin_mut;
3716 use prometheus::Registry;
3717 use sui_protocol_config::{ConsensusTransactionOrdering, ProtocolConfig};
3718 use sui_types::{
3719 base_types::ExecutionDigests,
3720 base_types::{AuthorityName, FullObjectRef, ObjectID, SuiAddress, random_object_ref},
3721 committee::Committee,
3722 crypto::deterministic_random_account_key,
3723 gas::GasCostSummary,
3724 message_envelope::Message,
3725 messages_checkpoint::{
3726 CheckpointContents, CheckpointSignatureMessage, CheckpointSummary,
3727 SignedCheckpointSummary,
3728 },
3729 messages_consensus::ConsensusTransaction,
3730 object::Object,
3731 transaction::{
3732 CertifiedTransaction, TransactionData, TransactionDataAPI, VerifiedCertificate,
3733 },
3734 };
3735
3736 use sui_types::SUI_RANDOMNESS_STATE_OBJECT_ID;
3737 use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
3738 use sui_types::transaction::{ObjectArg, SharedObjectMutability};
3739
3740 use super::*;
3741 use crate::{
3742 authority::{
3743 authority_per_epoch_store::ConsensusStatsAPI,
3744 consensus_tx_status_cache::NotifyReadConsensusTxStatusResult,
3745 test_authority_builder::TestAuthorityBuilder,
3746 },
3747 checkpoints::CheckpointServiceNoop,
3748 consensus_adapter::consensus_tests::test_user_transaction,
3749 consensus_test_utils::{TestConsensusCommit, setup_consensus_handler_for_testing},
3750 post_consensus_tx_reorder::PostConsensusTxReorder,
3751 };
3752
3753 fn epoch_close_deadline_config(deadline_ms: Option<u64>) -> ProtocolConfig {
3754 let mut protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
3755 if let Some(deadline_ms) = deadline_ms {
3756 protocol_config.set_epoch_close_deadline_ms_for_testing(deadline_ms);
3757 } else {
3758 protocol_config.disable_epoch_close_deadline_ms_for_testing();
3759 }
3760 protocol_config
3761 }
3762
3763 #[tokio::test]
3764 async fn test_consensus_handler_max_transaction_size() {
3765 let metrics = AuthorityMetrics::new(&Registry::new());
3766
3767 metrics.observe_consensus_handler_transaction_size("class_a", "accepted", 100);
3768 metrics.observe_consensus_handler_transaction_size("class_a", "rejected", 80);
3769 metrics.observe_consensus_handler_transaction_size("class_b", "accepted", 50);
3770
3771 assert_eq!(
3772 metrics
3773 .consensus_handler_max_transaction_size
3774 .with_label_values(&["class_a"])
3775 .get(),
3776 100
3777 );
3778 assert_eq!(
3779 metrics
3780 .consensus_handler_max_transaction_size
3781 .with_label_values(&["class_b"])
3782 .get(),
3783 50
3784 );
3785
3786 metrics.observe_consensus_handler_transaction_size("class_a", "rejected", 120);
3787
3788 assert_eq!(
3789 metrics
3790 .consensus_handler_max_transaction_size
3791 .with_label_values(&["class_a"])
3792 .get(),
3793 120
3794 );
3795 }
3796
3797 #[tokio::test(flavor = "current_thread")]
3798 async fn test_epoch_close_deadline_preserves_pre_deadline_blocking() {
3799 let state = TestAuthorityBuilder::new()
3800 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3801 .build()
3802 .await;
3803 let epoch_store = state.epoch_store_for_testing();
3804 epoch_store.insert_deferred_transactions_for_test(
3805 DeferralKey::new_for_consensus_round(u64::MAX, 1),
3806 vec![user_txn(1)],
3807 );
3808 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3809 let mut setup = setup_consensus_handler_for_testing(&state).await;
3810
3811 setup
3812 .consensus_handler
3813 .handle_consensus_commit_for_test(TestConsensusCommit::empty(1, scheduled_end + 99, 1))
3814 .await;
3815
3816 let reconfig_state = epoch_store.get_reconfig_state_read_lock_guard();
3817 assert!(reconfig_state.is_reject_all_certs());
3818 assert!(!reconfig_state.is_reject_all_tx());
3819 assert_eq!(
3820 epoch_store.get_all_deferred_transactions_for_test().len(),
3821 1
3822 );
3823 assert!(
3824 epoch_store
3825 .get_pending_checkpoints(None)
3826 .iter()
3827 .all(|(_, checkpoint)| !checkpoint.details.last_of_epoch)
3828 );
3829 assert_eq!(
3830 setup
3831 .consensus_handler
3832 .metrics
3833 .consensus_handler_dropped_transactions
3834 .with_label_values(&["epoch_close_deadline"])
3835 .get(),
3836 0
3837 );
3838 }
3839
3840 #[tokio::test(flavor = "current_thread")]
3841 async fn test_epoch_close_deadline_none_preserves_indefinite_blocking() {
3842 let state = TestAuthorityBuilder::new()
3843 .with_protocol_config(epoch_close_deadline_config(None))
3844 .build()
3845 .await;
3846 let epoch_store = state.epoch_store_for_testing();
3847 epoch_store.insert_deferred_transactions_for_test(
3848 DeferralKey::new_for_consensus_round(u64::MAX, 1),
3849 vec![user_txn(1)],
3850 );
3851 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3852 let mut setup = setup_consensus_handler_for_testing(&state).await;
3853
3854 setup
3855 .consensus_handler
3856 .handle_consensus_commit_for_test(TestConsensusCommit::empty(
3857 1,
3858 scheduled_end.saturating_add(1_000_000),
3859 1,
3860 ))
3861 .await;
3862
3863 let reconfig_state = epoch_store.get_reconfig_state_read_lock_guard();
3864 assert!(reconfig_state.is_reject_all_certs());
3865 assert!(!reconfig_state.is_reject_all_tx());
3866 }
3867
3868 #[tokio::test(flavor = "current_thread")]
3869 async fn test_epoch_close_deadline_is_inert_without_deferred_transactions() {
3870 let state = TestAuthorityBuilder::new()
3871 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3872 .build()
3873 .await;
3874 let epoch_store = state.epoch_store_for_testing();
3875 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3876 let mut setup = setup_consensus_handler_for_testing(&state).await;
3877
3878 setup
3882 .consensus_handler
3883 .handle_consensus_commit_for_test(TestConsensusCommit::empty(1, scheduled_end + 100, 1))
3884 .await;
3885
3886 assert!(
3887 epoch_store
3888 .get_reconfig_state_read_lock_guard()
3889 .is_reject_all_tx()
3890 );
3891 let checkpoints = epoch_store.get_pending_checkpoints(None);
3892 assert!(checkpoints.last().unwrap().1.details.last_of_epoch);
3893 assert_eq!(
3894 setup
3895 .consensus_handler
3896 .metrics
3897 .consensus_handler_dropped_transactions
3898 .with_label_values(&["epoch_close_deadline"])
3899 .get(),
3900 0
3901 );
3902 }
3903
3904 #[tokio::test(flavor = "current_thread")]
3905 async fn test_epoch_close_deadline_counts_abandoned_transactions_and_closes_first() {
3906 let state = TestAuthorityBuilder::new()
3907 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3908 .build()
3909 .await;
3910 let epoch_store = state.epoch_store_for_testing();
3911 let key = DeferralKey::new_for_consensus_round(u64::MAX, 1);
3912 epoch_store.insert_deferred_transactions_for_test(key, vec![user_txn(1), user_txn(2)]);
3913 let setup = setup_consensus_handler_for_testing(&state).await;
3914 let mut handler_state = CommitHandlerState::new(&epoch_store, 1);
3915
3916 let (reconfig_state, final_round, abandoned) = setup
3917 .consensus_handler
3918 .advance_end_of_epoch_state_machine(&mut handler_state, true);
3919
3920 assert!(reconfig_state.is_reject_all_tx());
3921 assert!(final_round);
3922 let abandoned = abandoned.expect("deferred transactions must be reported as abandoned");
3923 assert_eq!(abandoned.count, 2);
3924 assert_eq!(abandoned.sample.len(), 2);
3925 assert!(
3928 handler_state
3929 .output
3930 .get_deleted_deferred_txn_keys()
3931 .any(|deleted| deleted == key)
3932 );
3933 }
3934
3935 #[tokio::test(flavor = "current_thread")]
3936 async fn test_epoch_close_deadline_does_not_report_transactions_drained_in_commit() {
3937 let state = TestAuthorityBuilder::new()
3938 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3939 .build()
3940 .await;
3941 let epoch_store = state.epoch_store_for_testing();
3942 let key = DeferralKey::new_for_consensus_round(1, 0);
3943 epoch_store.insert_deferred_transactions_for_test(key, vec![user_txn(1), user_txn(2)]);
3944 let setup = setup_consensus_handler_for_testing(&state).await;
3945 let mut handler_state = CommitHandlerState::new(&epoch_store, 1);
3946 handler_state
3947 .output
3948 .delete_loaded_deferred_transactions(&[key]);
3949
3950 let (reconfig_state, final_round, abandoned) = setup
3951 .consensus_handler
3952 .advance_end_of_epoch_state_machine(&mut handler_state, true);
3953
3954 assert!(reconfig_state.is_reject_all_tx());
3955 assert!(final_round);
3956 assert!(abandoned.is_none());
3957 }
3958
3959 #[cfg(debug_assertions)]
3963 #[tokio::test(flavor = "current_thread")]
3964 #[should_panic(
3965 expected = "Epoch close deadline reached with unscheduled deferred transactions"
3966 )]
3967 async fn test_epoch_close_deadline_timestamp_jump_abandons_fresh_deferral() {
3968 use sui_protocol_config::{ExecutionTimeEstimateParams, PerObjectCongestionControlMode};
3969
3970 let execution_time_params = ExecutionTimeEstimateParams {
3971 target_utilization: 1,
3972 allowed_txn_cost_overage_burst_limit_us: 0,
3973 max_estimate_us: u64::MAX,
3974 randomness_scalar: 100,
3975 stored_observations_num_included_checkpoints: 10,
3976 stored_observations_limit: u64::MAX,
3977 stake_weighted_median_threshold: 0,
3978 default_none_duration_for_new_keys: true,
3979 observations_chunk_size: None,
3980 };
3981 let mut protocol_config = epoch_close_deadline_config(Some(100));
3982 protocol_config.set_per_object_congestion_control_mode_for_testing(
3983 PerObjectCongestionControlMode::ExecutionTimeEstimate(execution_time_params),
3984 );
3985 protocol_config.set_max_deferral_rounds_for_congestion_control_for_testing(1_000);
3986
3987 let (sender, keypair) = deterministic_random_account_key();
3988 let gas_objects: Vec<_> = (0..4)
3989 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
3990 .collect();
3991 let shared_object = Object::shared_for_testing();
3992 let mut starting_objects = gas_objects.clone();
3993 starting_objects.push(shared_object.clone());
3994 let state = TestAuthorityBuilder::new()
3995 .with_starting_objects(&starting_objects)
3996 .with_protocol_config(protocol_config)
3997 .build()
3998 .await;
3999 let mut consensus_transactions = Vec::new();
4000 for gas_object in gas_objects {
4001 let transaction = test_user_transaction(
4002 &state,
4003 sender,
4004 &keypair,
4005 gas_object,
4006 vec![shared_object.clone()],
4007 )
4008 .await;
4009 consensus_transactions.push(ConsensusTransaction::new_user_transaction_v2_message(
4010 &state.name,
4011 transaction.into(),
4012 ));
4013 }
4014 let epoch_store = state.epoch_store_for_testing();
4015 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
4016 let mut setup = setup_consensus_handler_for_testing(&state).await;
4017
4018 setup
4019 .consensus_handler
4020 .handle_consensus_commit_for_test(TestConsensusCommit::new(
4021 consensus_transactions,
4022 1,
4023 scheduled_end + 100,
4024 1,
4025 ))
4026 .await;
4027 }
4028
4029 #[tokio::test(flavor = "current_thread", start_paused = true)]
4030 async fn test_consensus_commit_handler() {
4031 telemetry_subscribers::init_for_testing();
4032
4033 let (sender, keypair) = deterministic_random_account_key();
4036 let gas_objects: Vec<Object> = (0..12)
4038 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4039 .collect();
4040 let owned_objects: Vec<Object> = (0..4)
4042 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4043 .collect();
4044 let shared_objects: Vec<Object> = (0..6)
4046 .map(|_| Object::shared_for_testing())
4047 .collect::<Vec<_>>();
4048 let mut all_objects = gas_objects.clone();
4049 all_objects.extend(owned_objects.clone());
4050 all_objects.extend(shared_objects.clone());
4051
4052 let network_config =
4053 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
4054 .with_objects(all_objects.clone())
4055 .build();
4056
4057 let state = TestAuthorityBuilder::new()
4058 .with_network_config(&network_config, 0)
4059 .build()
4060 .await;
4061
4062 let epoch_store = state.epoch_store_for_testing().clone();
4063 let new_epoch_start_state = epoch_store.epoch_start_state();
4064 let consensus_committee = new_epoch_start_state.get_consensus_committee();
4065
4066 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4067
4068 let throughput_calculator = ConsensusThroughputCalculator::new(None, metrics.clone());
4069
4070 let backpressure_manager = BackpressureManager::new_for_tests();
4071 let settlement_scheduler = SettlementScheduler::new(
4072 state.execution_scheduler().as_ref().clone(),
4073 state.get_transaction_cache_reader().clone(),
4074 state.metrics.clone(),
4075 );
4076 let mut consensus_handler = ConsensusHandler::new(
4077 epoch_store,
4078 Arc::new(CheckpointServiceNoop {}),
4079 settlement_scheduler,
4080 state.get_object_cache_reader().clone(),
4081 consensus_committee.clone(),
4082 metrics,
4083 Arc::new(throughput_calculator),
4084 backpressure_manager.subscribe(),
4085 state.traffic_controller.clone(),
4086 None,
4087 state.consensus_gasless_counter.clone(),
4088 state.transaction_deny_config_manager().clone(),
4089 );
4090
4091 let mut user_transactions = vec![];
4093 for (i, gas_object) in gas_objects[0..8].iter().enumerate() {
4094 let input_object = if i % 2 == 0 {
4095 owned_objects.get(i / 2).unwrap().clone()
4096 } else {
4097 shared_objects.get(i / 2).unwrap().clone()
4098 };
4099 let transaction = test_user_transaction(
4100 &state,
4101 sender,
4102 &keypair,
4103 gas_object.clone(),
4104 vec![input_object],
4105 )
4106 .await;
4107 user_transactions.push(transaction);
4108 }
4109
4110 for (i, gas_object) in gas_objects[8..12].iter().enumerate() {
4113 let shared_object = if i < 2 {
4114 shared_objects[4].clone()
4115 } else {
4116 shared_objects[5].clone()
4117 };
4118 let transaction = test_user_transaction(
4119 &state,
4120 sender,
4121 &keypair,
4122 gas_object.clone(),
4123 vec![shared_object],
4124 )
4125 .await;
4126 user_transactions.push(transaction);
4127 }
4128
4129 let mut blocks = Vec::new();
4131 for (i, consensus_transaction) in user_transactions
4132 .iter()
4133 .cloned()
4134 .map(|t| ConsensusTransaction::new_user_transaction_v2_message(&state.name, t.into()))
4135 .enumerate()
4136 {
4137 let transaction_bytes = bcs::to_bytes(&consensus_transaction).unwrap();
4138 let block = VerifiedBlock::new_for_test(
4139 TestBlock::new(100 + i as u32, (i % consensus_committee.size()) as u32)
4140 .set_transactions(vec![Transaction::new(transaction_bytes)])
4141 .build(),
4142 );
4143
4144 blocks.push(block);
4145 }
4146
4147 let leader_block = blocks[0].clone();
4149 let committed_sub_dag = CommittedSubDag::new(
4150 leader_block.reference(),
4151 blocks.clone(),
4152 leader_block.timestamp_ms(),
4153 CommitRef::new(10, CommitDigest::MIN),
4154 );
4155
4156 backpressure_manager.set_backpressure(true);
4158 backpressure_manager.update_highest_certified_checkpoint(1);
4160
4161 {
4163 let waiter =
4164 consensus_handler.handle_consensus_commit_for_test(committed_sub_dag.clone());
4165 pin_mut!(waiter);
4166
4167 tokio::time::timeout(std::time::Duration::from_secs(5), &mut waiter)
4169 .await
4170 .unwrap_err();
4171
4172 backpressure_manager.set_backpressure(false);
4174
4175 tokio::time::timeout(std::time::Duration::from_secs(100), waiter)
4177 .await
4178 .unwrap();
4179 }
4180
4181 let num_blocks = blocks.len();
4183 let num_transactions = user_transactions.len();
4184 let last_consensus_stats_1 = consensus_handler.last_consensus_stats.clone();
4185 assert_eq!(
4186 last_consensus_stats_1.index.transaction_index,
4187 num_transactions as u64
4188 );
4189 assert_eq!(last_consensus_stats_1.index.sub_dag_index, 10_u64);
4190 assert_eq!(last_consensus_stats_1.index.last_committed_round, 100_u64);
4191 assert_eq!(
4192 last_consensus_stats_1.stats.get_num_messages(0),
4193 num_blocks as u64
4194 );
4195 assert_eq!(
4196 last_consensus_stats_1.stats.get_num_user_transactions(0),
4197 num_transactions as u64
4198 );
4199
4200 for (i, t) in user_transactions.iter().enumerate() {
4202 let digest = t.tx().digest();
4203 if tokio::time::timeout(
4204 std::time::Duration::from_secs(10),
4205 state.notify_read_effects_for_testing("", *digest),
4206 )
4207 .await
4208 .is_ok()
4209 {
4210 } else {
4212 panic!("User transaction {} {} did not execute", i, digest);
4213 }
4214 }
4215
4216 state.execution_scheduler().check_empty_for_testing().await;
4218 }
4219
4220 #[tokio::test(flavor = "current_thread")]
4221 async fn test_dropped_owned_object_lock_conflict_is_marked_processed() {
4222 telemetry_subscribers::init_for_testing();
4223
4224 let (sender, keypair) = deterministic_random_account_key();
4225 let gas_objects: Vec<Object> = (0..2)
4226 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4227 .collect();
4228 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4229 let mut all_objects = gas_objects.clone();
4230 all_objects.push(owned_object.clone());
4231
4232 let state = TestAuthorityBuilder::new()
4233 .with_starting_objects(&all_objects)
4234 .build()
4235 .await;
4236 let epoch_store = state.epoch_store_for_testing();
4237 let owned_object_ref = state
4238 .get_object(&owned_object.id())
4239 .unwrap()
4240 .compute_object_reference();
4241
4242 let winner = test_user_transaction(
4243 &state,
4244 sender,
4245 &keypair,
4246 gas_objects[0].clone(),
4247 vec![owned_object.clone()],
4248 )
4249 .await;
4250 let loser = test_user_transaction(
4251 &state,
4252 sender,
4253 &keypair,
4254 gas_objects[1].clone(),
4255 vec![owned_object.clone()],
4256 )
4257 .await;
4258
4259 let winner_digest = *winner.tx().digest();
4260 let loser_digest = *loser.tx().digest();
4261 assert_ne!(winner_digest, loser_digest);
4262
4263 let winner_consensus_tx =
4264 ConsensusTransaction::new_user_transaction_v2_message(&state.name, winner.into());
4265 let loser_consensus_tx =
4266 ConsensusTransaction::new_user_transaction_v2_message(&state.name, loser.into());
4267 let winner_key = SequencedConsensusTransactionKey::External(winner_consensus_tx.key());
4268 let loser_key = SequencedConsensusTransactionKey::External(loser_consensus_tx.key());
4269
4270 let round = 100;
4271 let commit = TestConsensusCommit::new(
4272 vec![winner_consensus_tx, loser_consensus_tx],
4273 round as u64,
4274 1_000,
4275 10,
4276 );
4277 let mut setup = setup_consensus_handler_for_testing(&state).await;
4278 setup
4279 .consensus_handler
4280 .handle_consensus_commit_for_test(commit)
4281 .await;
4282 assert_eq!(
4283 setup
4284 .consensus_handler
4285 .metrics
4286 .consensus_handler_dropped_transactions
4287 .with_label_values(&["lock_conflict"])
4288 .get(),
4289 1
4290 );
4291
4292 let block = BlockRef {
4293 author: consensus_config::AuthorityIndex::ZERO,
4294 round,
4295 digest: Default::default(),
4296 };
4297 assert!(matches!(
4298 epoch_store
4299 .consensus_tx_status_cache
4300 .notify_read_transaction_status(ConsensusPosition {
4301 epoch: epoch_store.epoch(),
4302 block,
4303 index: 0,
4304 })
4305 .await,
4306 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Finalized)
4307 ));
4308 assert!(matches!(
4309 epoch_store
4310 .consensus_tx_status_cache
4311 .notify_read_transaction_status(ConsensusPosition {
4312 epoch: epoch_store.epoch(),
4313 block,
4314 index: 1,
4315 })
4316 .await,
4317 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4318 ));
4319
4320 let locks = epoch_store.get_owned_object_locks_map(&[owned_object_ref]);
4321 assert_eq!(locks.get(&owned_object_ref), Some(&winner_digest));
4322 assert!(
4323 epoch_store
4324 .is_consensus_message_processed(&winner_key)
4325 .unwrap()
4326 );
4327 assert!(
4328 epoch_store
4329 .is_consensus_message_processed(&loser_key)
4330 .unwrap()
4331 );
4332 tokio::time::timeout(
4335 std::time::Duration::from_secs(5),
4336 epoch_store.consensus_messages_processed_notify(vec![loser_key]),
4337 )
4338 .await
4339 .expect("processed notification for dropped transaction should resolve");
4340 }
4341
4342 #[tokio::test(flavor = "current_thread")]
4343 async fn test_rejected_transaction_sets_status_and_is_not_marked_processed() {
4344 telemetry_subscribers::init_for_testing();
4345
4346 let (sender, keypair) = deterministic_random_account_key();
4347 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4348 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4349
4350 let state = TestAuthorityBuilder::new()
4351 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4352 .build()
4353 .await;
4354 let epoch_store = state.epoch_store_for_testing();
4355
4356 let transaction =
4357 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4358 let consensus_tx =
4359 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4360 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4361
4362 let round = 100;
4363 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10)
4364 .with_rejected_indices([0]);
4365 let mut setup = setup_consensus_handler_for_testing(&state).await;
4366 setup
4367 .consensus_handler
4368 .handle_consensus_commit_for_test(commit)
4369 .await;
4370
4371 let block = BlockRef {
4373 author: consensus_config::AuthorityIndex::ZERO,
4374 round,
4375 digest: Default::default(),
4376 };
4377 assert!(matches!(
4378 epoch_store
4379 .consensus_tx_status_cache
4380 .notify_read_transaction_status(ConsensusPosition {
4381 epoch: epoch_store.epoch(),
4382 block,
4383 index: 0,
4384 })
4385 .await,
4386 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Rejected)
4387 ));
4388 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4392 }
4393
4394 #[tokio::test(flavor = "current_thread")]
4395 async fn test_user_transaction_ignored_at_epoch_close_sets_dropped_status() {
4396 telemetry_subscribers::init_for_testing();
4397
4398 let (sender, keypair) = deterministic_random_account_key();
4399 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4400 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4401
4402 let state = TestAuthorityBuilder::new()
4403 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4404 .build()
4405 .await;
4406 let epoch_store = state.epoch_store_for_testing();
4407
4408 let transaction =
4409 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4410 let consensus_tx =
4411 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4412 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4413
4414 {
4416 let mut guard = epoch_store.get_reconfig_state_write_lock_guard();
4417 guard.close_all_certs();
4418 }
4419
4420 let round = 100;
4421 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10);
4422 let mut setup = setup_consensus_handler_for_testing(&state).await;
4423 setup
4424 .consensus_handler
4425 .handle_consensus_commit_for_test(commit)
4426 .await;
4427
4428 let block = BlockRef {
4431 author: consensus_config::AuthorityIndex::ZERO,
4432 round,
4433 digest: Default::default(),
4434 };
4435 let status = tokio::time::timeout(
4436 std::time::Duration::from_secs(5),
4437 epoch_store
4438 .consensus_tx_status_cache
4439 .notify_read_transaction_status(ConsensusPosition {
4440 epoch: epoch_store.epoch(),
4441 block,
4442 index: 0,
4443 }),
4444 )
4445 .await
4446 .expect("transaction ignored at epoch close should receive a terminal status");
4447 assert!(matches!(
4448 status,
4449 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4450 ));
4451 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4452 assert_eq!(
4453 setup
4454 .consensus_handler
4455 .metrics
4456 .consensus_handler_dropped_transactions
4457 .with_label_values(&["end_of_epoch"])
4458 .get(),
4459 1
4460 );
4461 }
4462
4463 #[tokio::test(flavor = "current_thread")]
4464 async fn test_user_transaction_after_end_of_publish_sets_dropped_status() {
4465 telemetry_subscribers::init_for_testing();
4466
4467 let (sender, keypair) = deterministic_random_account_key();
4468 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4469 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4470
4471 let state = TestAuthorityBuilder::new()
4472 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4473 .build()
4474 .await;
4475 let epoch_store = state.epoch_store_for_testing();
4476
4477 let transaction =
4478 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4479 let consensus_tx =
4480 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4481 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4482
4483 epoch_store
4487 .end_of_publish
4488 .try_lock()
4489 .unwrap()
4490 .insert_generic(state.name, ());
4491
4492 let round = 100;
4493 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10);
4494 let mut setup = setup_consensus_handler_for_testing(&state).await;
4495 setup
4496 .consensus_handler
4497 .handle_consensus_commit_for_test(commit)
4498 .await;
4499
4500 let block = BlockRef {
4501 author: consensus_config::AuthorityIndex::ZERO,
4502 round,
4503 digest: Default::default(),
4504 };
4505 let status = tokio::time::timeout(
4506 std::time::Duration::from_secs(5),
4507 epoch_store
4508 .consensus_tx_status_cache
4509 .notify_read_transaction_status(ConsensusPosition {
4510 epoch: epoch_store.epoch(),
4511 block,
4512 index: 0,
4513 }),
4514 )
4515 .await
4516 .expect("transaction ignored after EndOfPublish should receive a terminal status");
4517 assert!(matches!(
4518 status,
4519 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4520 ));
4521 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4522 assert_eq!(
4523 setup
4524 .consensus_handler
4525 .metrics
4526 .consensus_handler_dropped_transactions
4527 .with_label_values(&["end_of_publish"])
4528 .get(),
4529 1
4530 );
4531 }
4532
4533 fn to_short_strings(txs: Vec<VerifiedExecutableTransactionWithAliases>) -> Vec<String> {
4534 txs.into_iter()
4535 .map(|tx| format!("transaction({})", tx.tx().transaction_data().gas_price()))
4536 .collect()
4537 }
4538
4539 #[test]
4540 fn test_order_by_gas_price() {
4541 let mut v = vec![user_txn(42), user_txn(100)];
4542 PostConsensusTxReorder::reorder(&mut v, ConsensusTransactionOrdering::ByGasPrice);
4543 assert_eq!(
4544 to_short_strings(v),
4545 vec![
4546 "transaction(100)".to_string(),
4547 "transaction(42)".to_string(),
4548 ]
4549 );
4550
4551 let mut v = vec![
4552 user_txn(1200),
4553 user_txn(12),
4554 user_txn(1000),
4555 user_txn(42),
4556 user_txn(100),
4557 user_txn(1000),
4558 ];
4559 PostConsensusTxReorder::reorder(&mut v, ConsensusTransactionOrdering::ByGasPrice);
4560 assert_eq!(
4561 to_short_strings(v),
4562 vec![
4563 "transaction(1200)".to_string(),
4564 "transaction(1000)".to_string(),
4565 "transaction(1000)".to_string(),
4566 "transaction(100)".to_string(),
4567 "transaction(42)".to_string(),
4568 "transaction(12)".to_string(),
4569 ]
4570 );
4571 }
4572
4573 #[tokio::test(flavor = "current_thread")]
4574 async fn test_checkpoint_signature_dedup() {
4575 telemetry_subscribers::init_for_testing();
4576
4577 let network_config =
4578 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir().build();
4579 let state = TestAuthorityBuilder::new()
4580 .with_network_config(&network_config, 0)
4581 .build()
4582 .await;
4583
4584 let epoch_store = state.epoch_store_for_testing().clone();
4585 let consensus_committee = epoch_store.epoch_start_state().get_consensus_committee();
4586
4587 let make_signed = || {
4588 let epoch = epoch_store.epoch();
4589 let contents =
4590 CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
4591 let summary = CheckpointSummary::new(
4592 &ProtocolConfig::get_for_max_version_UNSAFE(),
4593 epoch,
4594 42, 10, &contents,
4597 None, GasCostSummary::default(),
4599 None, 0, Vec::new(), Vec::new(), );
4604 SignedCheckpointSummary::new(epoch, summary, &*state.secret, state.name)
4605 };
4606
4607 let v2_s1 = make_signed();
4609 let v2_s1_clone = v2_s1.clone();
4610 let v2_digest_a = v2_s1.data().digest();
4611 let v2_a =
4612 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4613 summary: v2_s1,
4614 });
4615
4616 let v2_s2 = make_signed();
4617 let v2_digest_b = v2_s2.data().digest();
4618 let v2_b =
4619 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4620 summary: v2_s2,
4621 });
4622
4623 assert_ne!(v2_digest_a, v2_digest_b);
4624
4625 assert_eq!(v2_s1_clone.data().digest(), v2_digest_a);
4627 let v2_dup =
4628 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4629 summary: v2_s1_clone,
4630 });
4631
4632 let to_tx = |ct: &ConsensusTransaction| Transaction::new(bcs::to_bytes(ct).unwrap());
4633 let block = VerifiedBlock::new_for_test(
4634 TestBlock::new(100, 0)
4635 .set_transactions(vec![to_tx(&v2_a), to_tx(&v2_b), to_tx(&v2_dup)])
4636 .build(),
4637 );
4638 let commit = CommittedSubDag::new(
4639 block.reference(),
4640 vec![block.clone()],
4641 block.timestamp_ms(),
4642 CommitRef::new(10, CommitDigest::MIN),
4643 );
4644
4645 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4646 let throughput = ConsensusThroughputCalculator::new(None, metrics.clone());
4647 let backpressure = BackpressureManager::new_for_tests();
4648 let settlement_scheduler = SettlementScheduler::new(
4649 state.execution_scheduler().as_ref().clone(),
4650 state.get_transaction_cache_reader().clone(),
4651 state.metrics.clone(),
4652 );
4653 let mut handler = ConsensusHandler::new(
4654 epoch_store.clone(),
4655 Arc::new(CheckpointServiceNoop {}),
4656 settlement_scheduler,
4657 state.get_object_cache_reader().clone(),
4658 consensus_committee.clone(),
4659 metrics,
4660 Arc::new(throughput),
4661 backpressure.subscribe(),
4662 state.traffic_controller.clone(),
4663 None,
4664 state.consensus_gasless_counter.clone(),
4665 state.transaction_deny_config_manager().clone(),
4666 );
4667
4668 handler.handle_consensus_commit_for_test(commit).await;
4669
4670 use crate::consensus_handler::SequencedConsensusTransactionKey as SK;
4671 use sui_types::messages_consensus::ConsensusTransactionKey as CK;
4672
4673 let v2_key_a = SK::External(CK::CheckpointSignatureV2(state.name, 42, v2_digest_a));
4675 let v2_key_b = SK::External(CK::CheckpointSignatureV2(state.name, 42, v2_digest_b));
4676 assert!(
4677 epoch_store
4678 .is_consensus_message_processed(&v2_key_a)
4679 .unwrap()
4680 );
4681 assert!(
4682 epoch_store
4683 .is_consensus_message_processed(&v2_key_b)
4684 .unwrap()
4685 );
4686 }
4687
4688 #[tokio::test(flavor = "current_thread")]
4689 async fn test_verify_consensus_transaction_filters_mismatched_authorities() {
4690 telemetry_subscribers::init_for_testing();
4691
4692 let network_config =
4693 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir().build();
4694 let state = TestAuthorityBuilder::new()
4695 .with_network_config(&network_config, 0)
4696 .build()
4697 .await;
4698
4699 let epoch_store = state.epoch_store_for_testing().clone();
4700 let consensus_committee = epoch_store.epoch_start_state().get_consensus_committee();
4701
4702 use fastcrypto::traits::KeyPair;
4704 let (_, wrong_keypair) = sui_types::crypto::get_authority_key_pair();
4705 let wrong_authority: AuthorityName = wrong_keypair.public().into();
4706
4707 let mismatched_eop = ConsensusTransaction::new_end_of_publish(wrong_authority);
4709
4710 let valid_eop = ConsensusTransaction::new_end_of_publish(state.name);
4712
4713 let epoch = epoch_store.epoch();
4715 let contents =
4716 CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
4717 let summary = CheckpointSummary::new(
4718 &ProtocolConfig::get_for_max_version_UNSAFE(),
4719 epoch,
4720 42, 10, &contents,
4723 None, GasCostSummary::default(),
4725 None, 0, Vec::new(), Vec::new(), );
4730
4731 let mismatched_checkpoint_signed =
4733 SignedCheckpointSummary::new(epoch, summary.clone(), &wrong_keypair, wrong_authority);
4734 let mismatched_checkpoint_digest = mismatched_checkpoint_signed.data().digest();
4735 let mismatched_checkpoint =
4736 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4737 summary: mismatched_checkpoint_signed,
4738 });
4739
4740 let valid_checkpoint_signed =
4742 SignedCheckpointSummary::new(epoch, summary, &*state.secret, state.name);
4743 let valid_checkpoint_digest = valid_checkpoint_signed.data().digest();
4744 let valid_checkpoint =
4745 ConsensusTransaction::new_checkpoint_signature_message_v2(CheckpointSignatureMessage {
4746 summary: valid_checkpoint_signed,
4747 });
4748
4749 let to_tx = |ct: &ConsensusTransaction| Transaction::new(bcs::to_bytes(ct).unwrap());
4750
4751 let block = VerifiedBlock::new_for_test(
4753 TestBlock::new(100, 0)
4754 .set_transactions(vec![
4755 to_tx(&mismatched_eop),
4756 to_tx(&valid_eop),
4757 to_tx(&mismatched_checkpoint),
4758 to_tx(&valid_checkpoint),
4759 ])
4760 .build(),
4761 );
4762 let commit = CommittedSubDag::new(
4763 block.reference(),
4764 vec![block.clone()],
4765 block.timestamp_ms(),
4766 CommitRef::new(10, CommitDigest::MIN),
4767 );
4768
4769 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4770 let throughput = ConsensusThroughputCalculator::new(None, metrics.clone());
4771 let backpressure = BackpressureManager::new_for_tests();
4772 let settlement_scheduler = SettlementScheduler::new(
4773 state.execution_scheduler().as_ref().clone(),
4774 state.get_transaction_cache_reader().clone(),
4775 state.metrics.clone(),
4776 );
4777 let mut handler = ConsensusHandler::new(
4778 epoch_store.clone(),
4779 Arc::new(CheckpointServiceNoop {}),
4780 settlement_scheduler,
4781 state.get_object_cache_reader().clone(),
4782 consensus_committee.clone(),
4783 metrics,
4784 Arc::new(throughput),
4785 backpressure.subscribe(),
4786 state.traffic_controller.clone(),
4787 None,
4788 state.consensus_gasless_counter.clone(),
4789 state.transaction_deny_config_manager().clone(),
4790 );
4791
4792 handler.handle_consensus_commit_for_test(commit).await;
4793
4794 use crate::consensus_handler::SequencedConsensusTransactionKey as SK;
4795 use sui_types::messages_consensus::ConsensusTransactionKey as CK;
4796
4797 let valid_eop_key = SK::External(CK::EndOfPublish(state.name));
4799 assert!(
4800 epoch_store
4801 .is_consensus_message_processed(&valid_eop_key)
4802 .unwrap(),
4803 "Valid EndOfPublish should have been processed"
4804 );
4805
4806 let valid_checkpoint_key = SK::External(CK::CheckpointSignatureV2(
4807 state.name,
4808 42,
4809 valid_checkpoint_digest,
4810 ));
4811 assert!(
4812 epoch_store
4813 .is_consensus_message_processed(&valid_checkpoint_key)
4814 .unwrap(),
4815 "Valid CheckpointSignature should have been processed"
4816 );
4817
4818 let mismatched_eop_key = SK::External(CK::EndOfPublish(wrong_authority));
4820 assert!(
4821 !epoch_store
4822 .is_consensus_message_processed(&mismatched_eop_key)
4823 .unwrap(),
4824 "Mismatched EndOfPublish should NOT have been processed (filtered by verify_consensus_transaction)"
4825 );
4826
4827 let mismatched_checkpoint_key = SK::External(CK::CheckpointSignatureV2(
4828 wrong_authority,
4829 42,
4830 mismatched_checkpoint_digest,
4831 ));
4832 assert!(
4833 !epoch_store
4834 .is_consensus_message_processed(&mismatched_checkpoint_key)
4835 .unwrap(),
4836 "Mismatched CheckpointSignature should NOT have been processed (filtered by verify_consensus_transaction)"
4837 );
4838 }
4839
4840 #[tokio::test(flavor = "current_thread")]
4846 async fn test_deny_config_updates_applied_at_commit() {
4847 telemetry_subscribers::init_for_testing();
4848
4849 let _guard = ProtocolConfig::apply_overrides_for_testing(|_, mut c| {
4850 c.set_share_transaction_deny_config_in_consensus_for_testing(true);
4851 c
4852 });
4853
4854 let network_config =
4856 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
4857 .committee_size(std::num::NonZeroUsize::new(1).unwrap())
4858 .build();
4859 let state = TestAuthorityBuilder::new()
4860 .with_network_config(&network_config, 0)
4861 .build()
4862 .await;
4863 let epoch_store = state.epoch_store_for_testing().clone();
4864 let consensus_committee = epoch_store.epoch_start_state().get_consensus_committee();
4865 let manager = state.transaction_deny_config_manager().clone();
4866
4867 let now_ms = crate::authority::AuthorityState::unixtime_now_ms();
4868 let make_update = |authority, generation| {
4869 ConsensusTransaction::new_update_transaction_deny_config(
4870 SharedTransactionDenyConfig::V1(
4871 sui_types::messages_consensus::SharedTransactionDenyConfigV1 {
4872 authority,
4873 generation,
4874 rules: Some(sui_types::transaction_deny_rules::TransactionDenyRules {
4875 package_publish_disabled: true,
4876 ..Default::default()
4877 }),
4878 },
4879 ),
4880 )
4881 };
4882
4883 let spoofed = make_update(AuthorityName::ZERO, now_ms + 1);
4884 let sane = make_update(state.name, now_ms);
4885
4886 let to_tx = |ct: &ConsensusTransaction| Transaction::new(bcs::to_bytes(ct).unwrap());
4887 let block = VerifiedBlock::new_for_test(
4888 TestBlock::new(100, 0)
4889 .set_transactions(vec![to_tx(&spoofed), to_tx(&sane)])
4890 .build(),
4891 );
4892 let commit = CommittedSubDag::new(
4893 block.reference(),
4894 vec![block.clone()],
4895 block.timestamp_ms(),
4896 CommitRef::new(10, CommitDigest::MIN),
4897 );
4898
4899 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4900 let throughput = ConsensusThroughputCalculator::new(None, metrics.clone());
4901 let backpressure = BackpressureManager::new_for_tests();
4902 let settlement_scheduler = SettlementScheduler::new(
4903 state.execution_scheduler().as_ref().clone(),
4904 state.get_transaction_cache_reader().clone(),
4905 state.metrics.clone(),
4906 );
4907 let mut handler = ConsensusHandler::new(
4908 epoch_store.clone(),
4909 Arc::new(CheckpointServiceNoop {}),
4910 settlement_scheduler,
4911 state.get_object_cache_reader().clone(),
4912 consensus_committee.clone(),
4913 metrics,
4914 Arc::new(throughput),
4915 backpressure.subscribe(),
4916 state.traffic_controller.clone(),
4917 None,
4918 state.consensus_gasless_counter.clone(),
4919 state.transaction_deny_config_manager().clone(),
4920 );
4921
4922 handler.handle_consensus_commit_for_test(commit).await;
4923
4924 let snapshot = manager.peer_configs_snapshot();
4925 assert_eq!(
4926 snapshot.get(&state.name).map(|msg| msg.generation()),
4927 Some(now_ms),
4928 "committed update from the block author should be applied at commit time"
4929 );
4930 assert!(
4931 !snapshot.contains_key(&AuthorityName::ZERO),
4932 "spoofed authority claim should be dropped"
4933 );
4934 }
4935
4936 fn randomness_user_txn() -> VerifiedExecutableTransactionWithAliases {
4939 let (committee, keypairs) = Committee::new_simple_test_committee();
4940 let (sender, sender_keypair) = deterministic_random_account_key();
4941 let mut builder = ProgrammableTransactionBuilder::new();
4942 builder
4943 .obj(ObjectArg::SharedObject {
4944 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
4945 initial_shared_version: 1.into(),
4946 mutability: SharedObjectMutability::Immutable,
4947 })
4948 .unwrap();
4949 let tx = sui_types::transaction::Transaction::from_data_and_signer(
4950 TransactionData::new_programmable(
4951 sender,
4952 vec![random_object_ref()],
4953 builder.finish(),
4954 1_000_000,
4955 1_000,
4956 ),
4957 vec![&sender_keypair],
4958 );
4959 let tx = VerifiedExecutableTransaction::new_from_certificate(
4960 VerifiedCertificate::new_unchecked(
4961 CertifiedTransaction::new_from_keypairs_for_testing(
4962 tx.into_data(),
4963 &keypairs,
4964 &committee,
4965 ),
4966 ),
4967 );
4968 VerifiedExecutableTransactionWithAliases::no_aliases(tx)
4969 }
4970
4971 fn seed_deferral_key_collision(
4978 epoch_store: &AuthorityPerEpochStore,
4979 ) -> (TransactionDigest, TransactionDigest) {
4980 let parked = randomness_user_txn();
4981 let redeferred = randomness_user_txn();
4982 let parked_digest = *parked.tx().digest();
4983 let redeferred_digest = *redeferred.tx().digest();
4984 epoch_store.insert_deferred_transactions_for_test(
4987 DeferralKey::new_for_randomness(1),
4988 vec![parked],
4989 );
4990 epoch_store.insert_deferred_transactions_for_test(
4993 DeferralKey::new_for_consensus_round(2, 1),
4994 vec![redeferred],
4995 );
4996 (parked_digest, redeferred_digest)
4997 }
4998
4999 #[tokio::test(flavor = "current_thread")]
5002 async fn test_deferral_key_collision_merges_entries() {
5003 let state = TestAuthorityBuilder::new().build().await;
5004 let epoch_store = state.epoch_store_for_testing();
5005 let (parked_digest, redeferred_digest) = seed_deferral_key_collision(&epoch_store);
5006
5007 let mid_epoch = epoch_store
5008 .next_reconfiguration_timestamp_ms()
5009 .saturating_sub(10_000);
5010 let mut setup = setup_consensus_handler_for_testing(&state).await;
5011 setup
5012 .consensus_handler
5013 .handle_consensus_commit_for_test(TestConsensusCommit::empty(2, mid_epoch, 1))
5014 .await;
5015
5016 let deferred = epoch_store.get_all_deferred_transactions_for_test();
5017 assert_eq!(deferred.len(), 1);
5018 let (key, txns) = &deferred[0];
5019 assert_eq!(*key, DeferralKey::new_for_randomness(1));
5020 let digests: Vec<_> = txns.iter().map(|t| *t.tx().digest()).collect();
5021 assert_eq!(digests, vec![parked_digest, redeferred_digest]);
5023 }
5024
5025 #[tokio::test(flavor = "current_thread")]
5029 #[should_panic(expected = "Deferral key collision displaced finalized transactions")]
5030 async fn test_deferral_key_collision_displaces_randomness_deferrals() {
5031 let mut protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
5032 protocol_config.set_merge_colliding_deferrals_for_testing(false);
5033 let state = TestAuthorityBuilder::new()
5034 .with_protocol_config(protocol_config)
5035 .build()
5036 .await;
5037 let epoch_store = state.epoch_store_for_testing();
5038 seed_deferral_key_collision(&epoch_store);
5039
5040 let mid_epoch = epoch_store
5041 .next_reconfiguration_timestamp_ms()
5042 .saturating_sub(10_000);
5043 let mut setup = setup_consensus_handler_for_testing(&state).await;
5044 setup
5045 .consensus_handler
5046 .handle_consensus_commit_for_test(TestConsensusCommit::empty(2, mid_epoch, 1))
5047 .await;
5048 }
5049
5050 fn user_txn(gas_price: u64) -> VerifiedExecutableTransactionWithAliases {
5051 let (committee, keypairs) = Committee::new_simple_test_committee();
5052 let (sender, sender_keypair) = deterministic_random_account_key();
5053 let tx = sui_types::transaction::Transaction::from_data_and_signer(
5054 TransactionData::new_transfer(
5055 SuiAddress::default(),
5056 FullObjectRef::from_fastpath_ref(random_object_ref()),
5057 sender,
5058 random_object_ref(),
5059 1000 * gas_price,
5060 gas_price,
5061 ),
5062 vec![&sender_keypair],
5063 );
5064 let tx = VerifiedExecutableTransaction::new_from_certificate(
5065 VerifiedCertificate::new_unchecked(
5066 CertifiedTransaction::new_from_keypairs_for_testing(
5067 tx.into_data(),
5068 &keypairs,
5069 &committee,
5070 ),
5071 ),
5072 );
5073 VerifiedExecutableTransactionWithAliases::no_aliases(tx)
5074 }
5075
5076 mod checkpoint_queue_tests {
5077 use super::*;
5078 use consensus_core::CommitRef;
5079 use sui_types::digests::Digest;
5080
5081 fn make_chunk(tx_count: usize, height: u64) -> Chunk {
5082 Chunk {
5083 schedulables: (0..tx_count)
5084 .map(|_| Schedulable::Transaction(user_txn(1000).into_tx()))
5085 .collect(),
5086 settlement: None,
5087 height,
5088 }
5089 }
5090
5091 fn make_commit_ref(index: u32) -> CommitRef {
5092 CommitRef {
5093 index,
5094 digest: CommitDigest::MIN,
5095 }
5096 }
5097
5098 fn default_versions() -> HashMap<TransactionKey, AssignedVersions> {
5099 HashMap::new()
5100 }
5101
5102 #[test]
5103 fn test_flush_all_checkpoint_roots() {
5104 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 0);
5105 let versions = default_versions();
5106
5107 queue.push_chunk(
5108 make_chunk(5, 1),
5109 &versions,
5110 1000,
5111 make_commit_ref(1),
5112 Digest::default(),
5113 );
5114 queue.push_chunk(
5115 make_chunk(3, 2),
5116 &versions,
5117 1000,
5118 make_commit_ref(1),
5119 Digest::default(),
5120 );
5121
5122 let pending = queue.flush(1000, true);
5123
5124 assert!(pending.is_some());
5125 assert!(queue.pending_roots.is_empty());
5126 }
5127
5128 #[test]
5129 fn test_flush_respects_min_checkpoint_interval() {
5130 let min_interval = 200;
5131 let mut queue = CheckpointQueue::new_for_testing(1000, 0, 0, 1000, min_interval);
5132 let versions = default_versions();
5133
5134 queue.push_chunk(
5135 make_chunk(5, 1),
5136 &versions,
5137 1000,
5138 make_commit_ref(1),
5139 Digest::default(),
5140 );
5141
5142 let pending = queue.flush(1000 + min_interval - 1, false);
5143 assert!(pending.is_none());
5144 assert_eq!(queue.pending_roots.len(), 1);
5145
5146 let pending = queue.flush(1000 + min_interval, false);
5147 assert!(pending.is_some());
5148 assert!(queue.pending_roots.is_empty());
5149 }
5150
5151 #[test]
5152 fn test_push_chunk_flushes_when_exceeds_max() {
5153 let max_tx = 10;
5154 let mut queue = CheckpointQueue::new_for_testing(1000, 0, 0, max_tx, 0);
5155 let versions = default_versions();
5156
5157 queue.push_chunk(
5158 make_chunk(max_tx / 2 + 1, 1),
5159 &versions,
5160 1000,
5161 make_commit_ref(1),
5162 Digest::default(),
5163 );
5164
5165 let flushed = queue.push_chunk(
5166 make_chunk(max_tx / 2 + 1, 2),
5167 &versions,
5168 1000,
5169 make_commit_ref(2),
5170 Digest::default(),
5171 );
5172
5173 assert_eq!(flushed.len(), 1);
5174 assert_eq!(queue.pending_roots.len(), 1);
5175 }
5176
5177 #[test]
5178 fn test_multiple_chunks_merged_into_one_checkpoint() {
5179 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 200);
5180 let versions = default_versions();
5181
5182 queue.push_chunk(
5183 make_chunk(10, 1),
5184 &versions,
5185 1000,
5186 make_commit_ref(1),
5187 Digest::default(),
5188 );
5189 queue.push_chunk(
5190 make_chunk(10, 2),
5191 &versions,
5192 1000,
5193 make_commit_ref(2),
5194 Digest::default(),
5195 );
5196 queue.push_chunk(
5197 make_chunk(10, 3),
5198 &versions,
5199 1000,
5200 make_commit_ref(3),
5201 Digest::default(),
5202 );
5203
5204 let pending = queue.flush(1000, true).unwrap();
5205
5206 assert_eq!(pending.roots.len(), 3);
5207 }
5208
5209 #[test]
5210 fn test_push_chunk_handles_overflow() {
5211 let max_tx = 10;
5212 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, max_tx, 0);
5213 let versions = default_versions();
5214
5215 let flushed1 = queue.push_chunk(
5216 make_chunk(max_tx / 2, 1),
5217 &versions,
5218 1000,
5219 make_commit_ref(1),
5220 Digest::default(),
5221 );
5222 assert!(flushed1.is_empty());
5223
5224 let flushed2 = queue.push_chunk(
5225 make_chunk(max_tx / 2, 2),
5226 &versions,
5227 1000,
5228 make_commit_ref(2),
5229 Digest::default(),
5230 );
5231 assert!(flushed2.is_empty());
5232
5233 let flushed3 = queue.push_chunk(
5234 make_chunk(max_tx / 2, 3),
5235 &versions,
5236 1000,
5237 make_commit_ref(3),
5238 Digest::default(),
5239 );
5240 assert_eq!(flushed3.len(), 1);
5241
5242 let pending = queue.flush(1000, true);
5243
5244 for p in pending.iter().chain(flushed3.iter()) {
5245 let tx_count: usize = p.roots.iter().map(|r| r.tx_roots.len()).sum();
5246 assert!(tx_count <= max_tx);
5247 }
5248 }
5249
5250 #[test]
5251 fn test_checkpoint_uses_last_chunk_height() {
5252 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 0);
5253 let versions = default_versions();
5254
5255 queue.push_chunk(
5256 make_chunk(10, 100),
5257 &versions,
5258 1000,
5259 make_commit_ref(1),
5260 Digest::default(),
5261 );
5262 queue.push_chunk(
5263 make_chunk(10, 200),
5264 &versions,
5265 1000,
5266 make_commit_ref(2),
5267 Digest::default(),
5268 );
5269
5270 let pending = queue.flush(1000, true).unwrap();
5271
5272 assert_eq!(pending.details.checkpoint_height, 200);
5273 }
5274
5275 #[test]
5276 fn test_last_built_timestamp_updated_on_flush() {
5277 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, 1000, 0);
5278 let versions = default_versions();
5279
5280 queue.push_chunk(
5281 make_chunk(10, 1),
5282 &versions,
5283 5000,
5284 make_commit_ref(1),
5285 Digest::default(),
5286 );
5287
5288 assert_eq!(queue.last_built_timestamp, 0);
5289
5290 let _ = queue.flush(5000, true);
5291
5292 assert_eq!(queue.last_built_timestamp, 5000);
5293 }
5294
5295 #[test]
5296 fn test_settlement_info_sent_through_channel() {
5297 let mut queue = CheckpointQueue::new_for_testing(0, 0, 5, 1000, 0);
5298 let versions = default_versions();
5299
5300 let chunk1 = Chunk {
5301 schedulables: vec![
5302 Schedulable::ConsensusCommitPrologue(0, 1, 0),
5303 Schedulable::ConsensusCommitPrologue(0, 2, 0),
5304 Schedulable::ConsensusCommitPrologue(0, 3, 0),
5305 ],
5306 settlement: Some(Schedulable::AccumulatorSettlement(1, 1)),
5307 height: 1,
5308 };
5309
5310 let chunk2 = Chunk {
5311 schedulables: vec![
5312 Schedulable::ConsensusCommitPrologue(0, 4, 0),
5313 Schedulable::ConsensusCommitPrologue(0, 5, 0),
5314 ],
5315 settlement: Some(Schedulable::AccumulatorSettlement(1, 2)),
5316 height: 2,
5317 };
5318
5319 queue.push_chunk(
5320 chunk1,
5321 &versions,
5322 1000,
5323 make_commit_ref(1),
5324 Digest::default(),
5325 );
5326 queue.push_chunk(
5327 chunk2,
5328 &versions,
5329 1000,
5330 make_commit_ref(1),
5331 Digest::default(),
5332 );
5333 }
5334
5335 #[test]
5336 fn test_settlement_checkpoint_seq_correct_after_flush() {
5337 let max_tx = 10;
5338 let initial_seq = 5;
5339 let (sender, mut receiver) = monitored_mpsc::unbounded_channel("test_settlement_seq");
5340 let mut queue =
5341 CheckpointQueue::new_for_testing_with_sender(0, 0, initial_seq, max_tx, 0, sender);
5342 let versions = default_versions();
5343
5344 let chunk1 = Chunk {
5346 schedulables: (0..max_tx / 2 + 1)
5347 .map(|_| Schedulable::Transaction(user_txn(1000).into_tx()))
5348 .collect(),
5349 settlement: Some(Schedulable::AccumulatorSettlement(1, 1)),
5350 height: 1,
5351 };
5352 queue.push_chunk(
5353 chunk1,
5354 &versions,
5355 1000,
5356 make_commit_ref(1),
5357 Digest::default(),
5358 );
5359
5360 let msg1 = receiver.try_recv().unwrap();
5362 let settlement1 = msg1.1.unwrap();
5363 assert_eq!(settlement1.checkpoint_seq, initial_seq);
5364
5365 let chunk2 = Chunk {
5367 schedulables: (0..max_tx / 2 + 1)
5368 .map(|_| Schedulable::Transaction(user_txn(1000).into_tx()))
5369 .collect(),
5370 settlement: Some(Schedulable::AccumulatorSettlement(1, 2)),
5371 height: 2,
5372 };
5373 let flushed = queue.push_chunk(
5374 chunk2,
5375 &versions,
5376 1000,
5377 make_commit_ref(2),
5378 Digest::default(),
5379 );
5380 assert_eq!(flushed.len(), 1);
5381 assert_eq!(flushed[0].details.checkpoint_seq, initial_seq);
5382
5383 let msg2 = receiver.try_recv().unwrap();
5386 let settlement2 = msg2.1.unwrap();
5387 assert_eq!(settlement2.checkpoint_seq, initial_seq + 1);
5388
5389 let pending = queue.flush_forced().unwrap();
5392 assert_eq!(pending.details.checkpoint_seq, settlement2.checkpoint_seq);
5393 }
5394
5395 #[test]
5396 fn test_checkpoint_seq_increments_on_flush() {
5397 let mut queue = CheckpointQueue::new_for_testing(0, 0, 10, 1000, 0);
5398 let versions = default_versions();
5399
5400 queue.push_chunk(
5401 make_chunk(5, 1),
5402 &versions,
5403 1000,
5404 make_commit_ref(1),
5405 Digest::default(),
5406 );
5407
5408 let pending = queue.flush(1000, true).unwrap();
5409
5410 assert_eq!(pending.details.checkpoint_seq, 10);
5411 assert_eq!(queue.current_checkpoint_seq, 11);
5412 }
5413
5414 #[test]
5415 fn test_multiple_chunks_with_overflow() {
5416 let max_tx = 10;
5417 let mut queue = CheckpointQueue::new_for_testing(0, 0, 0, max_tx, 0);
5418 let versions = default_versions();
5419
5420 let flushed1 = queue.push_chunk(
5421 make_chunk(max_tx / 2 + 1, 1),
5422 &versions,
5423 1000,
5424 make_commit_ref(1),
5425 Digest::default(),
5426 );
5427 let flushed2 = queue.push_chunk(
5428 make_chunk(max_tx / 2 + 1, 2),
5429 &versions,
5430 1000,
5431 make_commit_ref(1),
5432 Digest::default(),
5433 );
5434 let flushed3 = queue.push_chunk(
5435 make_chunk(max_tx / 2 + 1, 3),
5436 &versions,
5437 1000,
5438 make_commit_ref(1),
5439 Digest::default(),
5440 );
5441
5442 let all_flushed: Vec<_> = flushed1
5443 .into_iter()
5444 .chain(flushed2)
5445 .chain(flushed3)
5446 .collect();
5447 assert_eq!(all_flushed.len(), 2);
5448 assert_eq!(queue.pending_roots.len(), 1);
5449
5450 for p in &all_flushed {
5451 let tx_count: usize = p.roots.iter().map(|r| r.tx_roots.len()).sum();
5452 assert!(tx_count <= max_tx);
5453 }
5454 }
5455 }
5456}