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::{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
718pub struct ConsensusHandler<C> {
719 epoch_store: Arc<AuthorityPerEpochStore>,
722 last_consensus_stats: ExecutionIndicesWithStatsV2,
726 checkpoint_service: Arc<C>,
727 cache_reader: Arc<dyn ObjectCacheRead>,
729 committee: ConsensusCommittee,
731 metrics: Arc<AuthorityMetrics>,
734 processed_cache: LruCache<SequencedConsensusTransactionKey, ()>,
736 throughput_calculator: Arc<ConsensusThroughputCalculator>,
738
739 additional_consensus_state: AdditionalConsensusState,
740
741 backpressure_subscriber: BackpressureSubscriber,
742
743 traffic_controller: Option<Arc<TrafficController>>,
744
745 congestion_logger: Option<Arc<Mutex<CongestionCommitLogger>>>,
746
747 consensus_gasless_counter: Arc<ConsensusGaslessCounter>,
748
749 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
750
751 checkpoint_queue: Mutex<CheckpointQueue>,
752}
753
754const PROCESSED_CACHE_CAP: usize = 1024 * 1024;
755
756fn assert_supported_protocol_config(protocol_config: &ProtocolConfig) {
757 assert!(
758 matches!(
759 protocol_config.per_object_congestion_control_mode(),
760 PerObjectCongestionControlMode::ExecutionTimeEstimate(_)
761 ),
762 "support for congestion control modes other than PerObjectCongestionControlMode::ExecutionTimeEstimate has been removed"
763 );
764 assert!(
765 protocol_config.split_checkpoints_in_consensus_handler(),
766 "support for splitting checkpoints outside of consensus handler has been removed"
767 );
768 assert!(protocol_config.ignore_execution_time_observations_after_certs_closed());
769 assert!(protocol_config.record_time_estimate_processed());
770 assert!(protocol_config.prepend_prologue_tx_in_consensus_commit_in_checkpoints());
771 assert!(protocol_config.consensus_checkpoint_signature_key_includes_digest());
772 assert!(protocol_config.authority_capabilities_v2());
773 assert!(protocol_config.cancel_for_failed_dkg_early());
774 assert!(protocol_config.record_consensus_determined_version_assignments_in_prologue_v2());
775 assert!(protocol_config.record_additional_state_digest_in_prologue());
776 assert!(protocol_config.additional_consensus_digest_indirect_state());
777 assert!(protocol_config.include_cancelled_randomness_txns_in_prologue());
778 assert!(protocol_config.fix_checkpoint_signature_mapping());
779 assert!(protocol_config.merge_randomness_into_checkpoint());
780 assert!(
781 protocol_config.timestamp_based_epoch_close(),
782 "support for non-timestamp-based epoch close has been removed"
783 );
784}
785
786impl<C> ConsensusHandler<C> {
787 pub(crate) fn new(
788 epoch_store: Arc<AuthorityPerEpochStore>,
789 checkpoint_service: Arc<C>,
790 settlement_scheduler: SettlementScheduler,
791 cache_reader: Arc<dyn ObjectCacheRead>,
792 committee: ConsensusCommittee,
793 metrics: Arc<AuthorityMetrics>,
794 throughput_calculator: Arc<ConsensusThroughputCalculator>,
795 backpressure_subscriber: BackpressureSubscriber,
796 traffic_controller: Option<Arc<TrafficController>>,
797 congestion_logger: Option<Arc<Mutex<CongestionCommitLogger>>>,
798 consensus_gasless_counter: Arc<ConsensusGaslessCounter>,
799 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
800 ) -> Self {
801 assert_supported_protocol_config(epoch_store.protocol_config());
802
803 let mut last_consensus_stats = epoch_store
805 .get_last_consensus_stats()
806 .expect("Should be able to read last consensus index");
807 if !last_consensus_stats.stats.is_initialized() {
809 last_consensus_stats.stats = ConsensusStats::new(committee.size());
810 last_consensus_stats.checkpoint_seq = epoch_store.previous_epoch_last_checkpoint();
811 }
812 let max_tx = epoch_store
813 .protocol_config()
814 .max_transactions_per_checkpoint() as usize;
815 let min_checkpoint_interval_ms = epoch_store
816 .protocol_config()
817 .min_checkpoint_interval_ms_as_option()
818 .unwrap_or_default();
819 let execution_scheduler_sender =
820 ExecutionSchedulerSender::start(settlement_scheduler, epoch_store.clone());
821 let commit_rate_estimate_window_size = epoch_store
822 .protocol_config()
823 .get_consensus_commit_rate_estimation_window_size();
824 let last_built_timestamp = last_consensus_stats.last_checkpoint_flush_timestamp;
825 let checkpoint_height = last_consensus_stats.height;
826 let next_checkpoint_seq = last_consensus_stats.checkpoint_seq + 1;
827 Self {
828 epoch_store,
829 last_consensus_stats,
830 checkpoint_service,
831 cache_reader,
832 committee,
833 metrics,
834 processed_cache: LruCache::new(
835 NonZeroUsize::new(randomize_cache_capacity_in_tests(PROCESSED_CACHE_CAP)).unwrap(),
836 ),
837 throughput_calculator,
838 additional_consensus_state: AdditionalConsensusState::new(
839 commit_rate_estimate_window_size,
840 ),
841 backpressure_subscriber,
842 traffic_controller,
843 congestion_logger,
844 consensus_gasless_counter,
845 transaction_deny_config_manager,
846 checkpoint_queue: Mutex::new(CheckpointQueue::new(
847 last_built_timestamp,
848 checkpoint_height,
849 next_checkpoint_seq,
850 max_tx,
851 min_checkpoint_interval_ms,
852 execution_scheduler_sender,
853 )),
854 }
855 }
856
857 pub(crate) fn last_processed_subdag_index(&self) -> u64 {
859 self.last_consensus_stats.index.sub_dag_index
860 }
861
862 pub(crate) fn new_for_testing(
863 epoch_store: Arc<AuthorityPerEpochStore>,
864 checkpoint_service: Arc<C>,
865 execution_scheduler_sender: ExecutionSchedulerSender,
866 cache_reader: Arc<dyn ObjectCacheRead>,
867 committee: ConsensusCommittee,
868 metrics: Arc<AuthorityMetrics>,
869 throughput_calculator: Arc<ConsensusThroughputCalculator>,
870 backpressure_subscriber: BackpressureSubscriber,
871 traffic_controller: Option<Arc<TrafficController>>,
872 transaction_deny_config_manager: Arc<TransactionDenyConfigManager>,
873 last_consensus_stats: ExecutionIndicesWithStatsV2,
874 ) -> Self {
875 assert_supported_protocol_config(epoch_store.protocol_config());
876
877 let commit_rate_estimate_window_size = epoch_store
878 .protocol_config()
879 .get_consensus_commit_rate_estimation_window_size();
880 let max_tx = epoch_store
881 .protocol_config()
882 .max_transactions_per_checkpoint() as usize;
883 let min_checkpoint_interval_ms = epoch_store
884 .protocol_config()
885 .min_checkpoint_interval_ms_as_option()
886 .unwrap_or_default();
887 let last_built_timestamp = last_consensus_stats.last_checkpoint_flush_timestamp;
888 let checkpoint_height = last_consensus_stats.height;
889 Self {
890 epoch_store,
891 last_consensus_stats,
892 checkpoint_service,
893 cache_reader,
894 committee,
895 metrics,
896 processed_cache: LruCache::new(
897 NonZeroUsize::new(randomize_cache_capacity_in_tests(PROCESSED_CACHE_CAP)).unwrap(),
898 ),
899 throughput_calculator,
900 additional_consensus_state: AdditionalConsensusState::new(
901 commit_rate_estimate_window_size,
902 ),
903 backpressure_subscriber,
904 traffic_controller,
905 congestion_logger: None,
906 consensus_gasless_counter: Arc::new(ConsensusGaslessCounter::default()),
907 transaction_deny_config_manager,
908 checkpoint_queue: Mutex::new(CheckpointQueue::new(
909 last_built_timestamp,
910 checkpoint_height,
911 0,
912 max_tx,
913 min_checkpoint_interval_ms,
914 execution_scheduler_sender,
915 )),
916 }
917 }
918}
919
920#[derive(Default)]
921struct CommitHandlerInput {
922 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
923 capability_notifications: Vec<AuthorityCapabilitiesV2>,
924 execution_time_observations: Vec<ExecutionTimeObservation>,
925 checkpoint_signature_messages: Vec<CheckpointSignatureMessage>,
926 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
927 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
928 end_of_publish_transactions: Vec<AuthorityName>,
929 new_jwks: Vec<(AuthorityName, JwkId, JWK)>,
930 transaction_deny_config_updates: Vec<(AuthorityName, SharedTransactionDenyConfig)>,
931}
932
933struct CommitHandlerState {
934 dkg_failed: bool,
935 randomness_round: Option<RandomnessRound>,
936 output: ConsensusCommitOutput,
937 indirect_state_observer: Option<IndirectStateObserver>,
938 initial_reconfig_state: ReconfigState,
939 occurrence_counts: HashMap<TransactionDigest, u32>,
941 contested_transaction_digests: HashMap<TransactionDigest, ConflictInfo>,
944}
945
946impl CommitHandlerState {
947 fn new(epoch_store: &AuthorityPerEpochStore, consensus_round: u64) -> Self {
948 Self {
949 output: ConsensusCommitOutput::new(consensus_round),
950 dkg_failed: false,
951 randomness_round: None,
952 indirect_state_observer: Some(IndirectStateObserver::new()),
953 initial_reconfig_state: epoch_store.get_reconfig_state_read_lock_guard().clone(),
954 occurrence_counts: HashMap::new(),
955 contested_transaction_digests: HashMap::new(),
956 }
957 }
958
959 fn get_notifications(&self) -> Vec<SequencedConsensusTransactionKey> {
960 self.output
961 .get_consensus_messages_processed()
962 .cloned()
963 .collect()
964 }
965
966 fn init_randomness<'a, 'epoch>(
967 &'a mut self,
968 epoch_store: &'epoch AuthorityPerEpochStore,
969 commit_info: &'a ConsensusCommitInfo,
970 ) -> Option<tokio::sync::MutexGuard<'epoch, RandomnessManager>> {
971 let mut randomness_manager = epoch_store.randomness_manager.get().map(|rm| {
972 rm.try_lock()
973 .expect("should only ever be called from the commit handler thread")
974 });
975
976 let mut dkg_failed = false;
977 let randomness_round = if epoch_store.randomness_state_enabled() {
978 let randomness_manager = randomness_manager
979 .as_mut()
980 .expect("randomness manager should exist if randomness is enabled");
981 match randomness_manager.dkg_status() {
982 DkgStatus::Pending => None,
983 DkgStatus::Failed => {
984 dkg_failed = true;
985 None
986 }
987 DkgStatus::Successful => {
988 if self.initial_reconfig_state.should_accept_tx() {
991 randomness_manager
992 .reserve_next_randomness(commit_info.timestamp, &mut self.output)
994 .expect("epoch ended")
995 } else {
996 None
997 }
998 }
999 }
1000 } else {
1001 None
1002 };
1003
1004 if randomness_round.is_some() {
1005 assert!(!dkg_failed); }
1007
1008 self.randomness_round = randomness_round;
1009 self.dkg_failed = dkg_failed;
1010
1011 randomness_manager
1012 }
1013}
1014
1015struct AbandonedDeferredTxns {
1018 count: usize,
1019 sample: Vec<(DeferralKey, TransactionDigest)>,
1021}
1022
1023impl<C: CheckpointServiceNotify + Send + Sync> ConsensusHandler<C> {
1024 fn handle_prior_consensus_commit(&mut self, consensus_commit: impl ConsensusCommitAPI) {
1028 assert!(
1029 self.epoch_store
1030 .protocol_config()
1031 .record_additional_state_digest_in_prologue()
1032 );
1033 let protocol_config = self.epoch_store.protocol_config();
1034 let epoch_start_time = self
1035 .epoch_store
1036 .epoch_start_config()
1037 .epoch_start_timestamp_ms();
1038
1039 self.additional_consensus_state.observe_commit(
1040 protocol_config,
1041 epoch_start_time,
1042 &consensus_commit,
1043 );
1044 }
1045
1046 #[cfg(test)]
1047 pub(crate) async fn handle_consensus_commit_for_test(
1048 &mut self,
1049 consensus_commit: impl ConsensusCommitAPI,
1050 ) {
1051 let transactions = consensus_commit.transactions();
1052 self.handle_consensus_commit(consensus_commit, transactions)
1053 .await;
1054 }
1055
1056 #[instrument(level = "debug", skip_all, fields(epoch = self.epoch_store.epoch(), round = consensus_commit.leader_round()))]
1057 pub(crate) async fn handle_consensus_commit(
1058 &mut self,
1059 consensus_commit: impl ConsensusCommitAPI,
1060 transactions: ParsedConsensusTransactions,
1061 ) {
1062 self.backpressure_subscriber.await_no_backpressure().await;
1067
1068 let epoch = self.epoch_store.epoch();
1069
1070 let _scope = monitored_scope("ConsensusCommitHandler::handle_consensus_commit");
1071
1072 let last_committed_round = self.last_consensus_stats.index.last_committed_round;
1073
1074 self.epoch_store
1075 .consensus_tx_status_cache
1076 .update_last_committed_leader_round(last_committed_round as u32);
1077 self.epoch_store
1078 .tx_reject_reason_cache
1079 .set_last_committed_leader_round(last_committed_round as u32);
1080
1081 let commit_info = self.additional_consensus_state.observe_commit(
1082 self.epoch_store.protocol_config(),
1083 self.epoch_store
1084 .epoch_start_config()
1085 .epoch_start_timestamp_ms(),
1086 &consensus_commit,
1087 );
1088 assert!(commit_info.round > last_committed_round);
1089
1090 let (timestamp, leader_author, commit_sub_dag_index) =
1091 self.gather_commit_metadata(&consensus_commit);
1092
1093 info!(
1094 %consensus_commit,
1095 "Received consensus output {}. Rejected transactions {}",
1096 consensus_commit.commit_ref(),
1097 consensus_commit.rejected_transactions_debug_string(),
1098 );
1099
1100 self.last_consensus_stats.index = ExecutionIndices {
1101 last_committed_round: commit_info.round,
1102 sub_dag_index: commit_sub_dag_index,
1103 transaction_index: 0_u64,
1104 };
1105
1106 self.metrics
1107 .consensus_committed_subdags
1108 .with_label_values(&[&leader_author.to_string()])
1109 .inc();
1110
1111 let mut state = CommitHandlerState::new(&self.epoch_store, commit_info.round);
1112
1113 let FilteredConsensusOutput {
1114 transactions,
1115 owned_object_locks,
1116 dropped_transaction_keys,
1117 contested_transaction_digests,
1118 } = self.filter_consensus_txns(
1119 state.initial_reconfig_state.clone(),
1120 &commit_info,
1121 transactions,
1122 );
1123 state.contested_transaction_digests = contested_transaction_digests;
1124 if !owned_object_locks.is_empty() {
1126 state.output.set_owned_object_locks(owned_object_locks);
1127 }
1128
1129 for key in dropped_transaction_keys {
1131 state.output.record_consensus_message_processed(
1132 SequencedConsensusTransactionKey::External(key),
1133 );
1134 }
1135 let transactions = self.deduplicate_consensus_txns(&mut state, &commit_info, transactions);
1136
1137 let mut randomness_manager = state.init_randomness(&self.epoch_store, &commit_info);
1138
1139 let CommitHandlerInput {
1140 user_transactions,
1141 capability_notifications,
1142 execution_time_observations,
1143 checkpoint_signature_messages,
1144 randomness_dkg_messages,
1145 randomness_dkg_confirmations,
1146 end_of_publish_transactions,
1147 new_jwks,
1148 transaction_deny_config_updates,
1149 } = self.build_commit_handler_input(transactions);
1150
1151 self.process_gasless_transactions(&commit_info, &user_transactions);
1152 self.process_jwks(&mut state, &commit_info, new_jwks);
1153 self.process_capability_notifications(capability_notifications);
1154 self.process_transaction_deny_config_updates(transaction_deny_config_updates);
1155 self.process_execution_time_observations(&mut state, execution_time_observations);
1156 self.process_checkpoint_signature_messages(checkpoint_signature_messages);
1157
1158 self.process_dkg_updates(
1159 &mut state,
1160 &commit_info,
1161 randomness_manager.as_deref_mut(),
1162 randomness_dkg_messages,
1163 randomness_dkg_confirmations,
1164 )
1165 .await;
1166
1167 let mut execution_time_estimator = self
1168 .epoch_store
1169 .execution_time_estimator
1170 .try_lock()
1171 .expect("should only ever be called from the commit handler thread");
1172
1173 let authenticator_state_update_transaction =
1174 self.create_authenticator_state_update(last_committed_round, &commit_info);
1175
1176 let (
1177 transactions_to_schedule,
1178 randomness_transactions_to_schedule,
1179 cancelled_txns,
1180 randomness_state_update_transaction,
1181 ) = self.collect_transactions_to_schedule(
1182 &mut state,
1183 &mut execution_time_estimator,
1184 &commit_info,
1185 user_transactions,
1186 );
1187
1188 let (should_accept_tx, lock, final_round, abandoned_deferred_txns) =
1189 self.handle_close_epoch(&mut state, &commit_info, end_of_publish_transactions);
1190
1191 let make_checkpoint = should_accept_tx || final_round;
1192 if !make_checkpoint {
1193 return;
1195 }
1196
1197 if final_round {
1200 self.record_end_of_epoch_execution_time_observations(&mut execution_time_estimator);
1201 }
1202
1203 let consensus_commit_prologue = (!commit_info.skip_consensus_commit_prologue_in_test)
1204 .then_some(Schedulable::ConsensusCommitPrologue(
1205 epoch,
1206 commit_info.round,
1207 commit_info.consensus_commit_ref.index,
1208 ));
1209
1210 let schedulables: Vec<_> = itertools::chain!(
1211 consensus_commit_prologue.into_iter(),
1212 authenticator_state_update_transaction
1213 .into_iter()
1214 .map(Schedulable::Transaction),
1215 transactions_to_schedule
1216 .into_iter()
1217 .map(Schedulable::Transaction),
1218 )
1219 .collect();
1220
1221 let randomness_schedulables: Vec<_> = randomness_state_update_transaction
1222 .into_iter()
1223 .chain(
1224 randomness_transactions_to_schedule
1225 .into_iter()
1226 .map(Schedulable::Transaction),
1227 )
1228 .collect();
1229
1230 let num_schedulables = schedulables.len();
1231 let checkpoint_height = self.create_pending_checkpoints(
1232 &mut state,
1233 &commit_info,
1234 schedulables,
1235 randomness_schedulables,
1236 &cancelled_txns,
1237 final_round,
1238 );
1239
1240 let notifications = state.get_notifications();
1241
1242 let mut stats_to_record = self.last_consensus_stats.clone();
1243 stats_to_record.height = checkpoint_height;
1244 {
1245 let queue = self.checkpoint_queue.lock().unwrap();
1246 stats_to_record.last_checkpoint_flush_timestamp = queue.last_built_timestamp();
1247 stats_to_record.checkpoint_seq = queue.checkpoint_seq();
1248 }
1249
1250 state.output.record_consensus_commit_stats(stats_to_record);
1251
1252 self.record_deferral_deletion(&mut state);
1253
1254 self.epoch_store
1255 .consensus_quarantine
1256 .write()
1257 .push_consensus_output(state.output, &self.epoch_store)
1258 .expect("push_consensus_output should not fail");
1259
1260 debug!(
1261 ?commit_info.round,
1262 "Notifying checkpoint service about new pending checkpoint(s)",
1263 );
1264 self.checkpoint_service
1265 .notify_checkpoint()
1266 .expect("failed to notify checkpoint service");
1267
1268 if let Some(randomness_round) = state.randomness_round {
1269 randomness_manager
1270 .as_ref()
1271 .expect("randomness manager should exist if randomness round is provided")
1272 .generate_randomness(epoch, randomness_round);
1273 }
1274
1275 self.epoch_store.process_notifications(notifications.iter());
1276
1277 self.log_final_round(lock, final_round);
1279
1280 if let Some(AbandonedDeferredTxns { count, sample }) = abandoned_deferred_txns {
1284 self.metrics
1285 .consensus_handler_dropped_transactions
1286 .with_label_values(&["epoch_close_deadline"])
1287 .inc_by(count as u64);
1288 debug_fatal!(
1289 "Epoch close deadline reached with unscheduled deferred transactions: count={}, sample={:?}",
1290 count,
1291 sample
1292 );
1293 }
1294
1295 self.throughput_calculator
1297 .add_transactions(timestamp, num_schedulables as u64);
1298
1299 fail_point_if!("correlated-crash-after-consensus-commit-boundary", || {
1300 let key = [commit_sub_dag_index, epoch];
1301 if sui_simulator::random::deterministic_probability_once(&key, 0.01) {
1302 sui_simulator::task::kill_current_node(None);
1303 }
1304 });
1305
1306 fail_point!("crash");
1307 }
1308
1309 fn handle_close_epoch(
1310 &self,
1311 state: &mut CommitHandlerState,
1312 commit_info: &ConsensusCommitInfo,
1313 end_of_publish_transactions: Vec<AuthorityName>,
1314 ) -> (
1315 bool,
1316 Option<RwLockWriteGuard<'_, ReconfigState>>,
1317 bool,
1318 Option<AbandonedDeferredTxns>,
1319 ) {
1320 let timestamp_triggered =
1321 commit_info.timestamp >= self.epoch_store.next_reconfiguration_timestamp_ms();
1322 let deadline_reached = self
1323 .epoch_store
1324 .protocol_config()
1325 .epoch_close_deadline_ms_as_option()
1326 .is_some_and(|deadline_ms| {
1327 commit_info.timestamp
1328 >= self
1329 .epoch_store
1330 .next_reconfiguration_timestamp_ms()
1331 .saturating_add(deadline_ms)
1332 });
1333 let collected_eop_quorum =
1334 self.process_end_of_publish_transactions(state, end_of_publish_transactions);
1335 if timestamp_triggered || collected_eop_quorum {
1336 let (lock, final_round, abandoned_deferred_txns) =
1337 self.advance_end_of_epoch_state_machine(state, deadline_reached);
1338 (
1339 lock.should_accept_tx(),
1340 Some(lock),
1341 final_round,
1342 abandoned_deferred_txns,
1343 )
1344 } else {
1345 (true, None, false, None)
1346 }
1347 }
1348
1349 fn record_end_of_epoch_execution_time_observations(
1350 &self,
1351 estimator: &mut ExecutionTimeEstimator,
1352 ) {
1353 self.epoch_store
1354 .end_of_epoch_execution_time_observations
1355 .set(estimator.take_observations())
1356 .expect("`stored_execution_time_observations` should only be set once at end of epoch");
1357 }
1358
1359 fn record_deferral_deletion(&self, state: &mut CommitHandlerState) {
1360 let mut deferred_transactions = self
1361 .epoch_store
1362 .consensus_output_cache
1363 .deferred_transactions
1364 .lock();
1365 for deleted_deferred_key in state.output.get_deleted_deferred_txn_keys() {
1366 deferred_transactions.remove(&deleted_deferred_key);
1367 }
1368 }
1369
1370 fn log_final_round(&self, lock: Option<RwLockWriteGuard<ReconfigState>>, final_round: bool) {
1371 if final_round {
1372 let epoch = self.epoch_store.epoch();
1373 info!(
1374 ?epoch,
1375 lock=?lock.as_ref(),
1376 final_round=?final_round,
1377 "Notified last checkpoint"
1378 );
1379 self.epoch_store.record_end_of_message_quorum_time_metric();
1380 }
1381 }
1382
1383 #[allow(clippy::type_complexity)]
1384 fn collect_transactions_to_schedule(
1385 &self,
1386 state: &mut CommitHandlerState,
1387 execution_time_estimator: &mut ExecutionTimeEstimator,
1388 commit_info: &ConsensusCommitInfo,
1389 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
1390 ) -> (
1391 Vec<VerifiedExecutableTransactionWithAliases>,
1392 Vec<VerifiedExecutableTransactionWithAliases>,
1393 BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1394 Option<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1395 ) {
1396 let _scope = monitored_scope("ConsensusCommitHandler::collect_transactions_to_schedule");
1397 let protocol_config = self.epoch_store.protocol_config();
1398 let epoch = self.epoch_store.epoch();
1399
1400 let (ordered_txns, ordered_randomness_txns, previously_deferred_tx_digests) =
1401 self.merge_and_reorder_transactions(state, commit_info, user_transactions);
1402
1403 let mut shared_object_congestion_tracker =
1404 self.init_congestion_tracker(commit_info, false, &ordered_txns);
1405 let mut shared_object_using_randomness_congestion_tracker =
1406 self.init_congestion_tracker(commit_info, true, &ordered_randomness_txns);
1407
1408 let randomness_state_update_transaction = state
1409 .randomness_round
1410 .map(|round| Schedulable::RandomnessStateUpdate(epoch, round));
1411 debug!(
1412 "Randomness state update transaction: {:?}",
1413 randomness_state_update_transaction
1414 .as_ref()
1415 .map(|t| t.key())
1416 );
1417
1418 let mut transactions_to_schedule = Vec::with_capacity(ordered_txns.len());
1419 let mut randomness_transactions_to_schedule =
1420 Vec::with_capacity(ordered_randomness_txns.len());
1421 let mut deferred_txns = BTreeMap::new();
1422 let mut cancelled_txns = BTreeMap::new();
1423
1424 for transaction in ordered_txns {
1425 self.handle_deferral_and_cancellation(
1426 state,
1427 &mut cancelled_txns,
1428 &mut deferred_txns,
1429 &mut transactions_to_schedule,
1430 protocol_config,
1431 commit_info,
1432 transaction,
1433 &mut shared_object_congestion_tracker,
1434 &previously_deferred_tx_digests,
1435 execution_time_estimator,
1436 );
1437 }
1438
1439 for transaction in ordered_randomness_txns {
1440 if state.dkg_failed {
1441 debug!(
1442 "Canceling randomness-using transaction {:?} because DKG failed",
1443 transaction.tx().digest(),
1444 );
1445 cancelled_txns.insert(
1446 *transaction.tx().digest(),
1447 CancelConsensusCertificateReason::DkgFailed,
1448 );
1449 randomness_transactions_to_schedule.push(transaction);
1450 continue;
1451 }
1452 self.handle_deferral_and_cancellation(
1453 state,
1454 &mut cancelled_txns,
1455 &mut deferred_txns,
1456 &mut randomness_transactions_to_schedule,
1457 protocol_config,
1458 commit_info,
1459 transaction,
1460 &mut shared_object_using_randomness_congestion_tracker,
1461 &previously_deferred_tx_digests,
1462 execution_time_estimator,
1463 );
1464 }
1465
1466 let mut total_deferred_txns = 0;
1467 {
1468 let mut deferred_transactions = self
1469 .epoch_store
1470 .consensus_output_cache
1471 .deferred_transactions
1472 .lock();
1473 for (key, txns) in deferred_txns.into_iter() {
1474 total_deferred_txns += txns.len();
1475 debug_assert!(
1490 !state
1491 .output
1492 .get_deleted_deferred_txn_keys()
1493 .any(|deleted| deleted == key),
1494 "deferral key {key:?} was reloaded by this commit and must not be re-inserted"
1495 );
1496 let txns = if protocol_config.merge_colliding_deferrals() {
1497 match deferred_transactions.remove(&key) {
1498 Some(mut merged) => {
1499 assert_reachable_gated!(
1500 "Merged colliding deferral entries instead of displacing finalized transactions.",
1501 |pc| pc.merge_colliding_deferrals()
1502 && (pc.defer_owned_object_double_spend()
1503 || pc.defer_unpaid_amplification())
1504 );
1505 debug_assert!(
1506 {
1507 let prev_digests: HashSet<_> =
1508 merged.iter().map(|t| *t.tx().digest()).collect();
1509 txns.iter().all(|t| !prev_digests.contains(t.tx().digest()))
1510 },
1511 "colliding deferral entries must not share transactions"
1512 );
1513 merged.extend(txns);
1516 merged
1517 }
1518 None => txns,
1519 }
1520 } else {
1521 if let Some(prev) = deferred_transactions.get(&key) {
1522 let new_digests: HashSet<_> =
1525 txns.iter().map(|t| *t.tx().digest()).collect();
1526 let displaced: Vec<_> = prev
1527 .iter()
1528 .map(|t| *t.tx().digest())
1529 .filter(|d| !new_digests.contains(d))
1530 .collect();
1531 if !displaced.is_empty() {
1532 debug_fatal!(
1533 "Deferral key collision displaced finalized transactions: key {:?}, displaced {:?}",
1534 key,
1535 displaced
1536 );
1537 }
1538 }
1539 txns
1540 };
1541 deferred_transactions.insert(key, txns.clone());
1542 state.output.defer_transactions(key, txns);
1543 }
1544 }
1545
1546 self.metrics
1547 .consensus_handler_deferred_transactions
1548 .inc_by(total_deferred_txns as u64);
1549 self.metrics
1550 .consensus_handler_cancelled_transactions
1551 .inc_by(cancelled_txns.len() as u64);
1552 self.metrics
1553 .consensus_handler_max_object_costs
1554 .with_label_values(&["regular_commit"])
1555 .set(shared_object_congestion_tracker.max_cost() as i64);
1556 self.metrics
1557 .consensus_handler_max_object_costs
1558 .with_label_values(&["randomness_commit"])
1559 .set(shared_object_using_randomness_congestion_tracker.max_cost() as i64);
1560
1561 let congestion_commit_data = shared_object_congestion_tracker.finish_commit(commit_info);
1562 let randomness_congestion_commit_data =
1563 shared_object_using_randomness_congestion_tracker.finish_commit(commit_info);
1564
1565 if let Some(logger) = &self.congestion_logger {
1566 let epoch = self.epoch_store.epoch();
1567 let mut logger = logger.lock().unwrap();
1568 logger.write_commit_log(epoch, commit_info, false, &congestion_commit_data);
1569 logger.write_commit_log(epoch, commit_info, true, &randomness_congestion_commit_data);
1570 }
1571
1572 if let Some(tx_object_debts) = self.epoch_store.tx_object_debts.get()
1573 && let Err(e) = tx_object_debts.try_send(
1574 congestion_commit_data
1575 .accumulated_debts
1576 .iter()
1577 .chain(randomness_congestion_commit_data.accumulated_debts.iter())
1578 .map(|(id, _)| *id)
1579 .collect(),
1580 )
1581 {
1582 info!("failed to send updated object debts to ExecutionTimeObserver: {e:?}");
1583 }
1584
1585 state
1586 .output
1587 .set_congestion_control_object_debts(congestion_commit_data.accumulated_debts);
1588 state.output.set_congestion_control_randomness_object_debts(
1589 randomness_congestion_commit_data.accumulated_debts,
1590 );
1591
1592 (
1593 transactions_to_schedule,
1594 randomness_transactions_to_schedule,
1595 cancelled_txns,
1596 randomness_state_update_transaction,
1597 )
1598 }
1599
1600 #[allow(clippy::type_complexity)]
1601 fn create_pending_checkpoints(
1602 &self,
1603 state: &mut CommitHandlerState,
1604 commit_info: &ConsensusCommitInfo,
1605 schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1606 randomness_schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1607 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1608 final_round: bool,
1609 ) -> CheckpointHeight {
1610 let _scope = monitored_scope("ConsensusCommitHandler::create_pending_checkpoints");
1611 let protocol_config = self.epoch_store.protocol_config();
1612 let epoch = self.epoch_store.epoch();
1613 let accumulators_enabled = self.epoch_store.accumulators_enabled();
1614 let max_transactions_per_checkpoint =
1615 protocol_config.max_transactions_per_checkpoint() as usize;
1616
1617 let should_write_random_checkpoint = state.randomness_round.is_some()
1618 || (state.dkg_failed && !randomness_schedulables.is_empty());
1619
1620 let mut checkpoint_queue = self.checkpoint_queue.lock().unwrap();
1621
1622 let build_chunks =
1623 |schedulables: Vec<Schedulable<VerifiedExecutableTransactionWithAliases>>,
1624 queue: &mut CheckpointQueue|
1625 -> Vec<Chunk<VerifiedExecutableTransactionWithAliases>> {
1626 schedulables
1627 .chunks(max_transactions_per_checkpoint)
1628 .map(|chunk| {
1629 let height = queue.next_height();
1630 let schedulables: Vec<_> = chunk.to_vec();
1631 let settlement = if accumulators_enabled {
1632 Some(Schedulable::AccumulatorSettlement(epoch, height))
1633 } else {
1634 None
1635 };
1636 Chunk {
1637 schedulables,
1638 settlement,
1639 height,
1640 }
1641 })
1642 .collect()
1643 };
1644
1645 let num_schedulables = schedulables.len();
1646 let chunked_schedulables = build_chunks(schedulables, &mut checkpoint_queue);
1647 if chunked_schedulables.len() > 1 {
1648 info!(
1649 "Splitting transactions into {} checkpoint chunks (num_schedulables={}, max_tx={})",
1650 chunked_schedulables.len(),
1651 num_schedulables,
1652 max_transactions_per_checkpoint
1653 );
1654 assert_reachable!("checkpoint split due to transaction limit");
1655 }
1656 let chunked_randomness_schedulables = if should_write_random_checkpoint {
1657 build_chunks(randomness_schedulables, &mut checkpoint_queue)
1658 } else {
1659 vec![]
1660 };
1661
1662 let schedulables_for_version_assignment =
1663 Chunk::all_schedulables_from(&chunked_schedulables);
1664 let randomness_schedulables_for_version_assignment =
1665 Chunk::all_schedulables_from(&chunked_randomness_schedulables);
1666
1667 let assigned_versions = self
1668 .epoch_store
1669 .process_consensus_transaction_shared_object_versions(
1670 self.cache_reader.as_ref(),
1671 schedulables_for_version_assignment,
1672 randomness_schedulables_for_version_assignment,
1673 cancelled_txns,
1674 &mut state.output,
1675 )
1676 .expect("failed to assign shared object versions");
1677
1678 let consensus_commit_prologue =
1679 self.add_consensus_commit_prologue_transaction(state, commit_info, &assigned_versions);
1680
1681 let mut chunked_schedulables = chunked_schedulables;
1682 let mut assigned_versions = assigned_versions;
1683 if let Some(consensus_commit_prologue) = consensus_commit_prologue {
1684 assert!(matches!(
1685 chunked_schedulables[0].schedulables[0],
1686 Schedulable::ConsensusCommitPrologue(..)
1687 ));
1688 assert!(matches!(
1689 assigned_versions.0[0].0,
1690 TransactionKey::ConsensusCommitPrologue(..)
1691 ));
1692 assigned_versions.0[0].0 =
1693 TransactionKey::Digest(*consensus_commit_prologue.tx().digest());
1694 chunked_schedulables[0].schedulables[0] =
1695 Schedulable::Transaction(consensus_commit_prologue);
1696 }
1697
1698 let assigned_versions = assigned_versions.into_map();
1699
1700 self.epoch_store.process_user_signatures(
1701 chunked_schedulables
1702 .iter()
1703 .flat_map(|c| c.all_schedulables())
1704 .chain(
1705 chunked_randomness_schedulables
1706 .iter()
1707 .flat_map(|c| c.all_schedulables()),
1708 ),
1709 );
1710
1711 let commit_height = chunked_randomness_schedulables
1712 .last()
1713 .or(chunked_schedulables.last())
1714 .map(|c| c.height)
1715 .expect("at least one checkpoint root must be created per commit");
1716
1717 let mut pending_checkpoints = Vec::new();
1718 for chunk in chunked_schedulables {
1719 pending_checkpoints.extend(checkpoint_queue.push_chunk(
1720 chunk.into(),
1721 &assigned_versions,
1722 commit_info.timestamp,
1723 commit_info.consensus_commit_ref,
1724 commit_info.rejected_transactions_digest,
1725 ));
1726 }
1727
1728 pending_checkpoints.extend(checkpoint_queue.flush(commit_info.timestamp, final_round));
1733
1734 if should_write_random_checkpoint {
1735 for chunk in chunked_randomness_schedulables {
1736 pending_checkpoints.extend(checkpoint_queue.push_chunk(
1737 chunk.into(),
1738 &assigned_versions,
1739 commit_info.timestamp,
1740 commit_info.consensus_commit_ref,
1741 commit_info.rejected_transactions_digest,
1742 ));
1743 }
1744 if final_round {
1745 pending_checkpoints.extend(checkpoint_queue.flush(commit_info.timestamp, true));
1746 }
1747 }
1748
1749 if final_round && let Some(last) = pending_checkpoints.last_mut() {
1750 last.details.last_of_epoch = true;
1751 }
1752
1753 let queue_drained = checkpoint_queue.is_empty();
1754 drop(checkpoint_queue);
1755
1756 for pending_checkpoint in pending_checkpoints {
1757 debug!(
1758 checkpoint_height = pending_checkpoint.details.checkpoint_height,
1759 roots_count = pending_checkpoint.num_roots(),
1760 "Writing pending checkpoint",
1761 );
1762 self.epoch_store
1763 .write_pending_checkpoint(&mut state.output, &pending_checkpoint)
1764 .expect("failed to write pending checkpoint");
1765 }
1766
1767 state.output.set_checkpoint_queue_drained(queue_drained);
1768
1769 commit_height
1770 }
1771
1772 fn add_consensus_commit_prologue_transaction<'a>(
1776 &'a self,
1777 state: &'a mut CommitHandlerState,
1778 commit_info: &'a ConsensusCommitInfo,
1779 assigned_versions: &AssignedTxAndVersions,
1780 ) -> Option<VerifiedExecutableTransactionWithAliases> {
1781 {
1782 if commit_info.skip_consensus_commit_prologue_in_test {
1783 return None;
1784 }
1785 }
1786
1787 let mut cancelled_txn_version_assignment = Vec::new();
1788
1789 for (txn_key, assigned_versions) in assigned_versions.0.iter() {
1790 let Some(d) = txn_key.as_digest() else {
1791 continue;
1792 };
1793
1794 if assigned_versions
1795 .shared_object_versions
1796 .iter()
1797 .any(|(_, version)| version.is_cancelled())
1798 {
1799 assert_reachable!("cancelled transactions");
1800 cancelled_txn_version_assignment
1801 .push((*d, assigned_versions.shared_object_versions.clone()));
1802 }
1803 }
1804
1805 fail_point_arg!(
1806 "additional_cancelled_txns_for_tests",
1807 |additional_cancelled_txns: Vec<(
1808 TransactionDigest,
1809 Vec<(ConsensusObjectSequenceKey, SequenceNumber)>
1810 )>| {
1811 cancelled_txn_version_assignment.extend(additional_cancelled_txns);
1812 }
1813 );
1814
1815 let transaction = commit_info.create_consensus_commit_prologue_transaction(
1816 self.epoch_store.epoch(),
1817 cancelled_txn_version_assignment,
1818 state.indirect_state_observer.take().unwrap(),
1819 );
1820 Some(VerifiedExecutableTransactionWithAliases::no_aliases(
1821 transaction,
1822 ))
1823 }
1824
1825 fn handle_deferral_and_cancellation(
1826 &self,
1827 state: &mut CommitHandlerState,
1828 cancelled_txns: &mut BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
1829 deferred_txns: &mut BTreeMap<DeferralKey, Vec<VerifiedExecutableTransactionWithAliases>>,
1830 scheduled_txns: &mut Vec<VerifiedExecutableTransactionWithAliases>,
1831 protocol_config: &ProtocolConfig,
1832 commit_info: &ConsensusCommitInfo,
1833 transaction: VerifiedExecutableTransactionWithAliases,
1834 shared_object_congestion_tracker: &mut SharedObjectCongestionTracker,
1835 previously_deferred_tx_digests: &HashMap<TransactionDigest, DeferralKey>,
1836 execution_time_estimator: &ExecutionTimeEstimator,
1837 ) {
1838 let tx_digest = *transaction.tx().digest();
1839
1840 if protocol_config.defer_unpaid_amplification()
1849 && !transaction
1850 .tx()
1851 .transaction_data()
1852 .expiration()
1853 .restricts_proposers(self.epoch_store.epoch())
1854 {
1855 let occurrence_count = state
1856 .occurrence_counts
1857 .get(&tx_digest)
1858 .copied()
1859 .unwrap_or(0);
1860
1861 let rgp = self.epoch_store.reference_gas_price();
1862 let gas_price = transaction.tx().transaction_data().gas_price();
1863 let allowed_count = (gas_price / rgp.max(1)) + 1;
1864
1865 if occurrence_count as u64 > allowed_count {
1866 self.metrics
1867 .consensus_handler_unpaid_amplification_deferrals
1868 .inc();
1869
1870 let deferred_from_round = previously_deferred_tx_digests
1871 .get(&tx_digest)
1872 .map(|k| k.deferred_from_round())
1873 .unwrap_or(commit_info.round);
1874
1875 let deferral_key = DeferralKey::new_for_consensus_round(
1876 commit_info.round + 1,
1877 deferred_from_round,
1878 );
1879
1880 if transaction_deferral_within_limit(
1881 &deferral_key,
1882 protocol_config.max_deferral_rounds_for_congestion_control(),
1883 ) {
1884 debug!(
1885 "Deferring transaction {:?} due to unpaid amplification (count={}, allowed={})",
1886 tx_digest, occurrence_count, allowed_count
1887 );
1888 deferred_txns
1889 .entry(deferral_key)
1890 .or_default()
1891 .push(transaction);
1892 return;
1893 }
1894 }
1895 }
1896
1897 if let Some(conflict_info) = state.contested_transaction_digests.get(&tx_digest) {
1901 self.metrics.consensus_handler_double_spend_deferrals.inc();
1902 self.metrics
1903 .consensus_handler_double_spend_conflict_count
1904 .with_label_values(&["gas_object"])
1905 .observe(conflict_info.gas_object_conflicts as f64);
1906 self.metrics
1907 .consensus_handler_double_spend_conflict_count
1908 .with_label_values(&["non_gas_object"])
1909 .observe(conflict_info.non_gas_object_conflicts as f64);
1910 self.metrics
1913 .consensus_handler_double_spend_conflicting_authority
1914 .with_label_values(&[
1915 self.authority_hostname(conflict_info.winner_author),
1916 "winner",
1917 ])
1918 .inc();
1919
1920 if protocol_config.defer_owned_object_double_spend() {
1921 let deferred_from_round = previously_deferred_tx_digests
1922 .get(&tx_digest)
1923 .map(|k| k.deferred_from_round())
1924 .unwrap_or(commit_info.round);
1925
1926 let deferral_key = DeferralKey::new_for_consensus_round(
1927 commit_info.round + 1,
1928 deferred_from_round,
1929 );
1930
1931 if transaction_deferral_within_limit(
1932 &deferral_key,
1933 protocol_config.max_deferral_rounds_for_congestion_control(),
1934 ) {
1935 debug!(
1936 "Deferring transaction {:?} due to owned object double-spend contention \
1937 (gas_conflicts={}, non_gas_conflicts={})",
1938 tx_digest,
1939 conflict_info.gas_object_conflicts,
1940 conflict_info.non_gas_object_conflicts,
1941 );
1942 assert_reachable_gated!(
1943 "Successfully deferred transaction attempting to double spend owned object.",
1944 |pc| pc.defer_owned_object_double_spend()
1945 );
1946 if transaction.tx().transaction_data().uses_randomness()
1947 && state.randomness_round.is_none()
1948 {
1949 assert_reachable_gated!(
1955 "Double-spend deferred a randomness-using transaction at a round without randomness.",
1956 |pc| pc.defer_owned_object_double_spend()
1957 );
1958 }
1959 deferred_txns
1960 .entry(deferral_key)
1961 .or_default()
1962 .push(transaction);
1963 return;
1964 }
1965 }
1966 }
1967
1968 let tx_cost = shared_object_congestion_tracker.get_tx_cost(
1969 execution_time_estimator,
1970 transaction.tx(),
1971 state.indirect_state_observer.as_mut().unwrap(),
1972 );
1973
1974 let deferral_info = self.epoch_store.should_defer(
1975 transaction.tx(),
1976 commit_info,
1977 state.dkg_failed,
1978 state.randomness_round.is_some(),
1979 previously_deferred_tx_digests,
1980 shared_object_congestion_tracker,
1981 );
1982
1983 if let Some((deferral_key, deferral_reason)) = deferral_info {
1984 debug!(
1985 "Deferring consensus certificate for transaction {:?} until {:?}",
1986 tx_digest, deferral_key
1987 );
1988
1989 match deferral_reason {
1990 DeferralReason::RandomnessNotReady => {
1991 deferred_txns
1992 .entry(deferral_key)
1993 .or_default()
1994 .push(transaction);
1995 }
1996 DeferralReason::SharedObjectCongestion(congested_objects) => {
1997 self.metrics.consensus_handler_congested_transactions.inc();
1998 if transaction_deferral_within_limit(
1999 &deferral_key,
2000 protocol_config.max_deferral_rounds_for_congestion_control(),
2001 ) {
2002 deferred_txns
2003 .entry(deferral_key)
2004 .or_default()
2005 .push(transaction);
2006 } else {
2007 assert_sometimes!(
2008 transaction.tx().data().transaction_data().uses_randomness(),
2009 "cancelled randomness-using transaction"
2010 );
2011 assert_sometimes!(
2012 !transaction.tx().data().transaction_data().uses_randomness(),
2013 "cancelled non-randomness-using transaction"
2014 );
2015
2016 debug!(
2018 "Cancelling consensus transaction {:?} with deferral key {:?} due to congestion on objects {:?}",
2019 tx_digest, deferral_key, congested_objects
2020 );
2021 cancelled_txns.insert(
2022 tx_digest,
2023 CancelConsensusCertificateReason::CongestionOnObjects(
2024 congested_objects,
2025 ),
2026 );
2027 scheduled_txns.push(transaction);
2028 }
2029 }
2030 }
2031 } else {
2032 shared_object_congestion_tracker.bump_object_execution_cost(tx_cost, transaction.tx());
2034 scheduled_txns.push(transaction);
2035 }
2036 }
2037
2038 fn merge_and_reorder_transactions(
2039 &self,
2040 state: &mut CommitHandlerState,
2041 commit_info: &ConsensusCommitInfo,
2042 user_transactions: Vec<VerifiedExecutableTransactionWithAliases>,
2043 ) -> (
2044 Vec<VerifiedExecutableTransactionWithAliases>,
2045 Vec<VerifiedExecutableTransactionWithAliases>,
2046 HashMap<TransactionDigest, DeferralKey>,
2047 ) {
2048 let protocol_config = self.epoch_store.protocol_config();
2049
2050 let (mut txns, mut randomness_txns, previously_deferred_tx_digests) =
2051 self.load_deferred_transactions(state, commit_info);
2052
2053 txns.reserve(user_transactions.len());
2054 randomness_txns.reserve(user_transactions.len());
2055
2056 let mut txns: Vec<_> = txns
2059 .into_iter()
2060 .filter_map(|tx| {
2061 if tx.tx().transaction_data().uses_randomness() {
2062 randomness_txns.push(tx);
2063 None
2064 } else {
2065 Some(tx)
2066 }
2067 })
2068 .collect();
2069
2070 for txn in user_transactions {
2071 if txn.tx().transaction_data().uses_randomness() {
2072 randomness_txns.push(txn);
2073 } else {
2074 txns.push(txn);
2075 }
2076 }
2077
2078 PostConsensusTxReorder::reorder(
2079 &mut txns,
2080 protocol_config.consensus_transaction_ordering(),
2081 );
2082 PostConsensusTxReorder::reorder(
2083 &mut randomness_txns,
2084 protocol_config.consensus_transaction_ordering(),
2085 );
2086
2087 (txns, randomness_txns, previously_deferred_tx_digests)
2088 }
2089
2090 fn load_deferred_transactions(
2091 &self,
2092 state: &mut CommitHandlerState,
2093 commit_info: &ConsensusCommitInfo,
2094 ) -> (
2095 Vec<VerifiedExecutableTransactionWithAliases>,
2096 Vec<VerifiedExecutableTransactionWithAliases>,
2097 HashMap<TransactionDigest, DeferralKey>,
2098 ) {
2099 let mut previously_deferred_tx_digests = HashMap::new();
2100
2101 let deferred_txs: Vec<_> = self
2102 .epoch_store
2103 .load_deferred_transactions_for_up_to_consensus_round_v2(
2104 &mut state.output,
2105 commit_info.round,
2106 )
2107 .expect("db error")
2108 .into_iter()
2109 .flat_map(|(key, txns)| txns.into_iter().map(move |tx| (key, tx)))
2110 .map(|(key, tx)| {
2111 previously_deferred_tx_digests.insert(*tx.tx().digest(), key);
2112 tx
2113 })
2114 .collect();
2115 trace!(
2116 "loading deferred transactions: {:?}",
2117 deferred_txs.iter().map(|tx| tx.tx().digest())
2118 );
2119
2120 let deferred_randomness_txs = if state.dkg_failed || state.randomness_round.is_some() {
2121 let txns: Vec<_> = self
2122 .epoch_store
2123 .load_deferred_transactions_for_randomness_v2(&mut state.output)
2124 .expect("db error")
2125 .into_iter()
2126 .flat_map(|(key, txns)| txns.into_iter().map(move |tx| (key, tx)))
2127 .map(|(key, tx)| {
2128 previously_deferred_tx_digests.insert(*tx.tx().digest(), key);
2129 tx
2130 })
2131 .collect();
2132 trace!(
2133 "loading deferred randomness transactions: {:?}",
2134 txns.iter().map(|tx| tx.tx().digest())
2135 );
2136 txns
2137 } else {
2138 vec![]
2139 };
2140
2141 (
2142 deferred_txs,
2143 deferred_randomness_txs,
2144 previously_deferred_tx_digests,
2145 )
2146 }
2147
2148 fn init_congestion_tracker(
2149 &self,
2150 commit_info: &ConsensusCommitInfo,
2151 for_randomness: bool,
2152 txns: &[VerifiedExecutableTransactionWithAliases],
2153 ) -> SharedObjectCongestionTracker {
2154 #[allow(unused_mut)]
2155 let mut ret = SharedObjectCongestionTracker::from_protocol_config(
2156 self.epoch_store
2157 .consensus_quarantine
2158 .read()
2159 .load_initial_object_debts(
2160 &self.epoch_store,
2161 commit_info.round,
2162 for_randomness,
2163 txns,
2164 )
2165 .expect("db error"),
2166 self.epoch_store.protocol_config(),
2167 for_randomness,
2168 self.congestion_logger.is_some(),
2169 );
2170
2171 fail_point_arg!(
2172 "initial_congestion_tracker",
2173 |tracker: SharedObjectCongestionTracker| {
2174 info!(
2175 "Initialize shared_object_congestion_tracker to {:?}",
2176 tracker
2177 );
2178 ret = tracker;
2179 }
2180 );
2181
2182 ret
2183 }
2184
2185 fn process_gasless_transactions(
2186 &self,
2187 commit_info: &ConsensusCommitInfo,
2188 user_transactions: &[VerifiedExecutableTransactionWithAliases],
2189 ) {
2190 let gasless_count = user_transactions
2191 .iter()
2192 .filter(|txn| txn.tx().transaction_data().is_gasless_transaction())
2193 .count() as u64;
2194 self.consensus_gasless_counter
2195 .record_commit(commit_info.timestamp, gasless_count);
2196 }
2197
2198 fn process_jwks(
2199 &self,
2200 state: &mut CommitHandlerState,
2201 commit_info: &ConsensusCommitInfo,
2202 new_jwks: Vec<(AuthorityName, JwkId, JWK)>,
2203 ) {
2204 for (authority_name, jwk_id, jwk) in new_jwks {
2205 self.epoch_store.record_jwk_vote(
2206 &mut state.output,
2207 commit_info.round,
2208 authority_name,
2209 &jwk_id,
2210 &jwk,
2211 );
2212 }
2213 }
2214
2215 fn process_capability_notifications(
2216 &self,
2217 capability_notifications: Vec<AuthorityCapabilitiesV2>,
2218 ) {
2219 for capabilities in capability_notifications {
2220 self.epoch_store
2221 .record_capabilities_v2(&capabilities)
2222 .expect("db error");
2223 }
2224 }
2225
2226 fn process_transaction_deny_config_updates(
2231 &self,
2232 updates: Vec<(AuthorityName, SharedTransactionDenyConfig)>,
2233 ) {
2234 for (author, update) in updates {
2235 self.transaction_deny_config_manager
2236 .apply_updates(author, vec![update]);
2237 }
2238 }
2239
2240 fn process_execution_time_observations(
2241 &self,
2242 state: &mut CommitHandlerState,
2243 execution_time_observations: Vec<ExecutionTimeObservation>,
2244 ) {
2245 let _scope = monitored_scope("ConsensusCommitHandler::process_execution_time_observations");
2246 let mut execution_time_estimator = self
2247 .epoch_store
2248 .execution_time_estimator
2249 .try_lock()
2250 .expect("should only ever be called from the commit handler thread");
2251
2252 for ExecutionTimeObservation {
2253 authority,
2254 generation,
2255 estimates,
2256 } in execution_time_observations
2257 {
2258 let authority_index = self
2259 .epoch_store
2260 .committee()
2261 .authority_index(&authority)
2262 .unwrap();
2263 execution_time_estimator.process_observations_from_consensus(
2264 authority_index,
2265 Some(generation),
2266 &estimates,
2267 );
2268 state
2269 .output
2270 .insert_execution_time_observation(authority_index, generation, estimates);
2271 }
2272 }
2273
2274 fn process_checkpoint_signature_messages(
2275 &self,
2276 checkpoint_signature_messages: Vec<CheckpointSignatureMessage>,
2277 ) {
2278 for checkpoint_signature_message in checkpoint_signature_messages {
2279 self.checkpoint_service
2280 .notify_checkpoint_signature(&checkpoint_signature_message)
2281 .expect("db error");
2282 }
2283 }
2284
2285 async fn process_dkg_updates(
2286 &self,
2287 state: &mut CommitHandlerState,
2288 commit_info: &ConsensusCommitInfo,
2289 randomness_manager: Option<&mut RandomnessManager>,
2290 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
2291 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
2292 ) {
2293 if !self.epoch_store.randomness_state_enabled() {
2294 let num_dkg_messages = randomness_dkg_messages.len();
2295 let num_dkg_confirmations = randomness_dkg_confirmations.len();
2296 if num_dkg_messages + num_dkg_confirmations > 0 {
2297 debug_fatal!(
2298 "received {} RandomnessDkgMessage and {} RandomnessDkgConfirmation messages when randomness is not enabled",
2299 num_dkg_messages,
2300 num_dkg_confirmations
2301 );
2302 }
2303 return;
2304 }
2305
2306 let randomness_manager =
2307 randomness_manager.expect("randomness manager should exist if randomness is enabled");
2308
2309 let randomness_dkg_updates =
2310 self.process_randomness_dkg_messages(randomness_manager, randomness_dkg_messages);
2311
2312 let randomness_dkg_confirmation_updates = self.process_randomness_dkg_confirmations(
2313 state,
2314 randomness_manager,
2315 randomness_dkg_confirmations,
2316 );
2317
2318 let always_advance_dkg_to_resolution = (self
2322 .epoch_store
2323 .protocol_config()
2324 .always_advance_dkg_to_resolution()
2325 || (self.epoch_store.get_chain() == Chain::Mainnet
2326 && self.epoch_store.epoch() >= 1143))
2327 && randomness_manager.dkg_status() == DkgStatus::Pending;
2328
2329 if randomness_dkg_updates
2330 || randomness_dkg_confirmation_updates
2331 || always_advance_dkg_to_resolution
2332 {
2333 randomness_manager
2334 .advance_dkg(&mut state.output, commit_info.round)
2335 .await
2336 .expect("epoch ended");
2337 }
2338 }
2339
2340 fn process_randomness_dkg_messages(
2341 &self,
2342 randomness_manager: &mut RandomnessManager,
2343 randomness_dkg_messages: Vec<(AuthorityName, Vec<u8>)>,
2344 ) -> bool {
2345 if randomness_dkg_messages.is_empty() {
2346 return false;
2347 }
2348
2349 let mut randomness_state_updated = false;
2350 for (authority, bytes) in randomness_dkg_messages {
2351 match bcs::from_bytes(&bytes) {
2352 Ok(message) => {
2353 randomness_manager
2354 .add_message(&authority, message)
2355 .expect("epoch ended");
2357 randomness_state_updated = true;
2358 }
2359
2360 Err(e) => {
2361 warn!(
2362 "Failed to deserialize RandomnessDkgMessage from {:?}: {e:?}",
2363 authority.concise(),
2364 );
2365 }
2366 }
2367 }
2368
2369 randomness_state_updated
2370 }
2371
2372 fn process_randomness_dkg_confirmations(
2373 &self,
2374 state: &mut CommitHandlerState,
2375 randomness_manager: &mut RandomnessManager,
2376 randomness_dkg_confirmations: Vec<(AuthorityName, Vec<u8>)>,
2377 ) -> bool {
2378 if randomness_dkg_confirmations.is_empty() {
2379 return false;
2380 }
2381
2382 let mut randomness_state_updated = false;
2383 for (authority, bytes) in randomness_dkg_confirmations {
2384 match bcs::from_bytes(&bytes) {
2385 Ok(message) => {
2386 randomness_manager
2387 .add_confirmation(&mut state.output, &authority, message)
2388 .expect("epoch ended");
2390 randomness_state_updated = true;
2391 }
2392 Err(e) => {
2393 warn!(
2394 "Failed to deserialize RandomnessDkgConfirmation from {:?}: {e:?}",
2395 authority.concise(),
2396 );
2397 }
2398 }
2399 }
2400
2401 randomness_state_updated
2402 }
2403
2404 fn process_end_of_publish_transactions(
2406 &self,
2407 state: &mut CommitHandlerState,
2408 end_of_publish_transactions: Vec<AuthorityName>,
2409 ) -> bool {
2410 let mut eop_aggregator = self.epoch_store.end_of_publish.try_lock().expect(
2411 "No contention on end_of_publish as it is only accessed from consensus handler",
2412 );
2413
2414 if eop_aggregator.has_quorum() {
2415 return true;
2416 }
2417
2418 if end_of_publish_transactions.is_empty() {
2419 return false;
2420 }
2421
2422 for authority in end_of_publish_transactions {
2423 info!("Received EndOfPublish from {:?}", authority.concise());
2424
2425 state.output.insert_end_of_publish(authority);
2428 if eop_aggregator
2429 .insert_generic(authority, ())
2430 .is_quorum_reached()
2431 {
2432 debug!(
2433 "Collected enough end_of_publish messages with last message from validator {:?}",
2434 authority.concise(),
2435 );
2436 return true;
2437 }
2438 }
2439
2440 false
2441 }
2442
2443 fn advance_end_of_epoch_state_machine(
2446 &self,
2447 state: &mut CommitHandlerState,
2448 deadline_reached: bool,
2449 ) -> (
2450 RwLockWriteGuard<'_, ReconfigState>,
2451 bool, Option<AbandonedDeferredTxns>,
2453 ) {
2454 let mut reconfig_state = self.epoch_store.get_reconfig_state_write_lock_guard();
2455 let start_state_is_reject_all_tx = reconfig_state.is_reject_all_tx();
2456
2457 if reconfig_state.should_accept_user_certs() {
2460 self.epoch_store.record_epoch_close_time_once();
2461 }
2462
2463 reconfig_state.close_all_certs();
2464
2465 let commit_has_deferred_txns = state.output.has_deferred_transactions();
2466 let previous_commits_have_deferred_txns = !self.epoch_store.deferred_transactions_empty();
2467 let has_deferred_txns = commit_has_deferred_txns || previous_commits_have_deferred_txns;
2468
2469 let should_close = !has_deferred_txns || deadline_reached;
2472 let final_round = should_close && !start_state_is_reject_all_tx;
2473
2474 let mut abandoned_deferred_txns = None;
2475 if final_round {
2476 info!("Transitioning to RejectAllTx");
2477 if has_deferred_txns {
2478 debug_assert!(deadline_reached);
2480 abandoned_deferred_txns = self.abandon_deferred_transactions(state);
2481 }
2482 reconfig_state.close_all_tx();
2483 } else if !should_close {
2484 debug!(
2485 "Blocking end of epoch on deferred transactions, from previous commits?={}, from this commit?={}",
2486 previous_commits_have_deferred_txns, commit_has_deferred_txns,
2487 );
2488 }
2489
2490 state.output.store_reconfig_state(reconfig_state.clone());
2491
2492 (reconfig_state, final_round, abandoned_deferred_txns)
2493 }
2494
2495 fn abandon_deferred_transactions(
2502 &self,
2503 state: &mut CommitHandlerState,
2504 ) -> Option<AbandonedDeferredTxns> {
2505 state.output.clear_deferred_transactions();
2506
2507 let already_deleted: BTreeSet<_> = state.output.get_deleted_deferred_txn_keys().collect();
2508 let mut count = 0;
2509 let mut sample = Vec::new();
2510 let mut abandoned_keys = Vec::new();
2511 {
2512 let deferred_transactions = self
2513 .epoch_store
2514 .consensus_output_cache
2515 .deferred_transactions
2516 .lock();
2517 for (key, txns) in deferred_transactions.iter() {
2518 if already_deleted.contains(key) {
2519 continue;
2521 }
2522 abandoned_keys.push(*key);
2523 count += txns.len();
2524 for tx in txns {
2525 if sample.len() < 10 {
2526 sample.push((*key, *tx.tx().digest()));
2527 }
2528 }
2529 }
2530 }
2531 if abandoned_keys.is_empty() {
2532 return None;
2533 }
2534 state
2535 .output
2536 .delete_loaded_deferred_transactions(&abandoned_keys);
2537 (count > 0).then_some(AbandonedDeferredTxns { count, sample })
2538 }
2539
2540 fn gather_commit_metadata(
2541 &self,
2542 consensus_commit: &impl ConsensusCommitAPI,
2543 ) -> (u64, AuthorityIndex, u64) {
2544 let timestamp = consensus_commit.commit_timestamp_ms();
2545 let leader_author = consensus_commit.leader_author_index();
2546 let commit_sub_dag_index = consensus_commit.commit_sub_dag_index();
2547
2548 let system_time_ms = SystemTime::now()
2549 .duration_since(UNIX_EPOCH)
2550 .unwrap()
2551 .as_millis() as i64;
2552
2553 let consensus_timestamp_bias_ms = system_time_ms - (timestamp as i64);
2554 let consensus_timestamp_bias_seconds = consensus_timestamp_bias_ms as f64 / 1000.0;
2555 self.metrics
2556 .consensus_timestamp_bias
2557 .observe(consensus_timestamp_bias_seconds);
2558
2559 let epoch_start = self
2560 .epoch_store
2561 .epoch_start_config()
2562 .epoch_start_timestamp_ms();
2563 let timestamp = if timestamp < epoch_start {
2564 error!(
2565 "Unexpected commit timestamp {timestamp} less then epoch start time {epoch_start}, author {leader_author}"
2566 );
2567 epoch_start
2568 } else {
2569 timestamp
2570 };
2571
2572 (timestamp, leader_author, commit_sub_dag_index)
2573 }
2574
2575 fn create_authenticator_state_update(
2576 &self,
2577 last_committed_round: u64,
2578 commit_info: &ConsensusCommitInfo,
2579 ) -> Option<VerifiedExecutableTransactionWithAliases> {
2580 let new_jwks = self
2588 .epoch_store
2589 .get_new_jwks(last_committed_round)
2590 .expect("Unrecoverable error in consensus handler");
2591
2592 if !new_jwks.is_empty() {
2593 let authenticator_state_update_transaction = authenticator_state_update_transaction(
2594 &self.epoch_store,
2595 commit_info.round,
2596 new_jwks,
2597 );
2598 debug!(
2599 "adding AuthenticatorStateUpdate({:?}) tx: {:?}",
2600 authenticator_state_update_transaction.digest(),
2601 authenticator_state_update_transaction,
2602 );
2603
2604 Some(VerifiedExecutableTransactionWithAliases::no_aliases(
2605 authenticator_state_update_transaction,
2606 ))
2607 } else {
2608 None
2609 }
2610 }
2611
2612 fn authority_hostname(&self, author: usize) -> &str {
2615 self.committee
2616 .to_authority_index(author)
2617 .map(|index| self.committee.authority(index).hostname.as_str())
2618 .unwrap_or("unknown")
2619 }
2620
2621 #[instrument(level = "trace", skip_all)]
2624 fn filter_consensus_txns(
2625 &mut self,
2626 initial_reconfig_state: ReconfigState,
2627 commit_info: &ConsensusCommitInfo,
2628 block_transactions: ParsedConsensusTransactions,
2629 ) -> FilteredConsensusOutput {
2630 let _scope = monitored_scope("ConsensusCommitHandler::filter_consensus_txns");
2631 let mut transactions = Vec::new();
2632 let mut owned_object_locks = HashMap::new();
2633 let mut dropped_transaction_keys = Vec::new();
2634 let mut status_updates: Vec<(ConsensusPosition, ConsensusTxStatus)> = Vec::new();
2638 let mut contested_transaction_digests: HashMap<TransactionDigest, ConflictInfo> =
2639 HashMap::new();
2640 let mut lock_holder_authors: HashMap<TransactionDigest, usize> = HashMap::new();
2644 let epoch = self.epoch_store.epoch();
2645 let mut num_finalized_user_transactions = vec![0; self.committee.size()];
2646 let mut num_rejected_user_transactions = vec![0; self.committee.size()];
2647 let mut num_dropped_user_transactions = vec![0; self.committee.size()];
2648
2649 let existing_locks = {
2655 let mut prefetch_refs: Vec<ObjectRef> = Vec::new();
2656 for (_block, parsed_transactions) in &block_transactions {
2657 for parsed in parsed_transactions {
2658 if let ConsensusTransactionKind::UserTransactionV2(tx_with_claims) =
2659 &parsed.transaction.kind
2660 && let Some(refs) = owned_object_refs_to_lock(tx_with_claims)
2661 {
2662 prefetch_refs.extend(refs);
2663 }
2664 }
2665 }
2666 prefetch_refs.sort();
2667 prefetch_refs.dedup();
2668 self.epoch_store
2671 .get_owned_object_locks_map(&prefetch_refs)
2672 .unwrap_or_default()
2673 };
2674
2675 for (block, parsed_transactions) in block_transactions {
2676 let author = block.author.value();
2677 let author_hostname = self.committee.authority(block.author).hostname.as_str();
2678 self.last_consensus_stats.stats.inc_num_messages(author);
2680
2681 status_updates.push((
2683 ConsensusPosition::ping(epoch, block),
2684 ConsensusTxStatus::Finalized,
2685 ));
2686
2687 for (tx_index, parsed) in parsed_transactions.into_iter().enumerate() {
2688 let position = ConsensusPosition {
2689 epoch,
2690 block,
2691 index: tx_index as TransactionIndex,
2692 };
2693
2694 if let Some(tx) = parsed.transaction.kind.as_user_transaction() {
2697 let digest = tx.digest();
2698 if let Some((spam_weight, submitter_client_addrs)) = self
2699 .epoch_store
2700 .submitted_transaction_cache
2701 .increment_submission_count(digest)
2702 {
2703 if let Some(ref traffic_controller) = self.traffic_controller {
2704 debug!(
2705 "Transaction {digest} exceeded submission limits, spam_weight: {spam_weight:?} applied to {} client addresses",
2706 submitter_client_addrs.len()
2707 );
2708
2709 for addr in submitter_client_addrs {
2711 traffic_controller.tally(
2712 TrafficTally::new(Some(addr), None, None, spam_weight.clone())
2713 .with_method(
2714 "consensus_submission_limit_exceeded".to_string(),
2715 ),
2716 );
2717 }
2718 } else {
2719 warn!(
2720 "Transaction {digest} exceeded submission limits, spam_weight: {spam_weight:?} for {} client addresses (traffic controller not configured)",
2721 submitter_client_addrs.len()
2722 );
2723 }
2724 }
2725 }
2726
2727 let kind = classify(&parsed.transaction);
2730 let outcome = if parsed.rejected {
2731 "rejected"
2732 } else {
2733 "accepted"
2734 };
2735 self.metrics
2736 .consensus_handler_processed
2737 .with_label_values(&[kind, outcome])
2738 .inc();
2739 self.metrics.observe_consensus_handler_transaction_size(
2740 kind,
2741 outcome,
2742 parsed.serialized_len,
2743 );
2744 if parsed.transaction.is_user_transaction() {
2748 self.metrics
2749 .consensus_handler_processed_user_transactions
2750 .with_label_values(&[outcome, author_hostname])
2751 .inc();
2752 }
2753
2754 if parsed.rejected {
2755 if parsed.transaction.is_user_transaction() {
2756 status_updates.push((position, ConsensusTxStatus::Rejected));
2757 num_rejected_user_transactions[author] += 1;
2758 }
2759 continue;
2761 }
2762
2763 if parsed.transaction.is_user_transaction() {
2764 self.last_consensus_stats
2765 .stats
2766 .inc_num_user_transactions(author);
2767 }
2768
2769 if !initial_reconfig_state.should_accept_consensus_certs() {
2770 match &parsed.transaction.kind {
2773 ConsensusTransactionKind::UserTransactionV2(_)
2774 | ConsensusTransactionKind::UserTransaction(_)
2776 | ConsensusTransactionKind::CertifiedTransaction(_)
2777 | ConsensusTransactionKind::CapabilityNotification(_)
2778 | ConsensusTransactionKind::CapabilityNotificationV2(_)
2779 | ConsensusTransactionKind::EndOfPublish(_)
2780 | ConsensusTransactionKind::ExecutionTimeObservation(_)
2782 | ConsensusTransactionKind::NewJWKFetched(_, _, _)
2783 | ConsensusTransactionKind::UpdateTransactionDenyConfig(_) => {
2784 if parsed.transaction.is_user_transaction() {
2791 status_updates.push((position, ConsensusTxStatus::Dropped));
2792 num_dropped_user_transactions[author] += 1;
2793 self.metrics
2794 .consensus_handler_dropped_transactions
2795 .with_label_values(&["end_of_epoch"])
2796 .inc();
2797 }
2798 debug!(
2799 "Ignoring consensus transaction {:?} because of end of epoch",
2800 parsed.transaction.key()
2801 );
2802 continue;
2803 }
2804
2805 ConsensusTransactionKind::CheckpointSignature(_)
2807 | ConsensusTransactionKind::CheckpointSignatureV2(_)
2808 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
2809 | ConsensusTransactionKind::RandomnessDkgMessage(_, _)
2810 | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _) => ()
2811 }
2812 }
2813
2814 if !initial_reconfig_state.should_accept_tx() {
2815 match &parsed.transaction.kind {
2816 ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
2817 | ConsensusTransactionKind::RandomnessDkgMessage(_, _) => continue,
2818 _ => {}
2819 }
2820 }
2821
2822 match &parsed.transaction.kind {
2824 ConsensusTransactionKind::CapabilityNotification(_)
2825 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
2826 | ConsensusTransactionKind::CheckpointSignature(_) => {
2827 debug_fatal!(
2828 "BUG: saw deprecated tx {:?}for commit round {}",
2829 parsed.transaction.key(),
2830 commit_info.round
2831 );
2832 continue;
2833 }
2834 _ => {}
2835 }
2836
2837 if parsed.transaction.is_user_transaction() {
2838 let author_name = self
2839 .epoch_store
2840 .committee()
2841 .authority_by_index(author as u32)
2842 .unwrap();
2843 if self
2844 .epoch_store
2845 .has_received_end_of_publish_from(author_name)
2846 {
2847 status_updates.push((position, ConsensusTxStatus::Dropped));
2854 num_dropped_user_transactions[author] += 1;
2855 self.metrics
2856 .consensus_handler_dropped_transactions
2857 .with_label_values(&["end_of_publish"])
2858 .inc();
2859 warn!(
2860 "Ignoring consensus transaction {:?} from authority {:?}, which already sent EndOfPublish message to consensus",
2861 author_name.concise(),
2862 parsed.transaction.key(),
2863 );
2864 continue;
2865 }
2866 }
2867
2868 if let ConsensusTransactionKind::UserTransactionV2(tx_with_claims) =
2874 &parsed.transaction.kind
2875 {
2876 let tx = tx_with_claims.tx();
2877 let Some(owned_object_refs) = owned_object_refs_to_lock(tx_with_claims) else {
2878 self.metrics
2880 .consensus_handler_dropped_transactions
2881 .with_label_values(&["invalid_input"])
2882 .inc();
2883 status_updates.push((position, ConsensusTxStatus::Dropped));
2884 num_dropped_user_transactions[author] += 1;
2885 if let Err(e) = tx.transaction_data().input_objects() {
2889 self.epoch_store
2890 .set_rejection_vote_reason(position, &e.into());
2891 }
2892 dropped_transaction_keys.push(parsed.transaction.key());
2893 debug_fatal!("Invalid input objects for transaction {}", tx.digest());
2894 continue;
2895 };
2896
2897 match self
2898 .epoch_store
2899 .try_acquire_owned_object_locks_post_consensus(
2900 &owned_object_refs,
2901 *tx.digest(),
2902 &owned_object_locks,
2903 &existing_locks,
2904 ) {
2905 Ok(new_locks) => {
2906 owned_object_locks.extend(new_locks.into_iter());
2907 lock_holder_authors.entry(*tx.digest()).or_insert(author);
2908 status_updates.push((position, ConsensusTxStatus::Finalized));
2910 num_finalized_user_transactions[author] += 1;
2911 }
2912 Err(e) => {
2913 let gas_object_ids: HashSet<ObjectID> = tx
2917 .transaction_data()
2918 .gas()
2919 .iter()
2920 .map(|obj_ref| obj_ref.0)
2921 .collect();
2922 let mut is_intra_commit_conflict = false;
2923 for obj_ref in &owned_object_refs {
2924 if let Some(holder_digest) = owned_object_locks.get(obj_ref) {
2925 is_intra_commit_conflict = true;
2926 let info = contested_transaction_digests
2927 .entry(*holder_digest)
2928 .or_default();
2929 info.winner_author = lock_holder_authors
2930 .get(holder_digest)
2931 .copied()
2932 .unwrap_or(author);
2933 if gas_object_ids.contains(&obj_ref.0) {
2934 info.gas_object_conflicts += 1;
2935 } else {
2936 info.non_gas_object_conflicts += 1;
2937 }
2938 }
2939 }
2940 if is_intra_commit_conflict {
2944 self.metrics
2945 .consensus_handler_double_spend_conflicting_authority
2946 .with_label_values(&[self.authority_hostname(author), "loser"])
2947 .inc();
2948 }
2949 debug!("Dropping transaction {}: {}", tx.digest(), e);
2950 self.metrics
2951 .consensus_handler_dropped_transactions
2952 .with_label_values(&["lock_conflict"])
2953 .inc();
2954 status_updates.push((position, ConsensusTxStatus::Dropped));
2955 num_dropped_user_transactions[author] += 1;
2956 self.epoch_store.set_rejection_vote_reason(position, &e);
2957 dropped_transaction_keys.push(parsed.transaction.key());
2958 continue;
2959 }
2960 }
2961 }
2962
2963 let transaction = SequencedConsensusTransactionKind::External(parsed.transaction);
2964 transactions.push((transaction, author as u32));
2965 }
2966 }
2967
2968 self.epoch_store.set_consensus_tx_statuses(status_updates);
2972
2973 for (i, authority) in self.committee.authorities() {
2974 let hostname = &authority.hostname;
2975 self.metrics
2976 .consensus_committed_messages
2977 .with_label_values(&[hostname])
2978 .set(self.last_consensus_stats.stats.get_num_messages(i.value()) as i64);
2979 self.metrics
2980 .consensus_committed_user_transactions
2981 .with_label_values(&[hostname])
2982 .set(
2983 self.last_consensus_stats
2984 .stats
2985 .get_num_user_transactions(i.value()) as i64,
2986 );
2987 self.metrics
2988 .consensus_finalized_user_transactions
2989 .with_label_values(&[hostname])
2990 .add(num_finalized_user_transactions[i.value()] as i64);
2991 self.metrics
2992 .consensus_rejected_user_transactions
2993 .with_label_values(&[hostname])
2994 .add(num_rejected_user_transactions[i.value()] as i64);
2995 self.metrics
2996 .consensus_dropped_user_transactions
2997 .with_label_values(&[hostname])
2998 .add(num_dropped_user_transactions[i.value()] as i64);
2999 }
3000
3001 FilteredConsensusOutput {
3002 transactions,
3003 owned_object_locks,
3004 dropped_transaction_keys,
3005 contested_transaction_digests,
3006 }
3007 }
3008
3009 fn deduplicate_consensus_txns(
3010 &mut self,
3011 state: &mut CommitHandlerState,
3012 commit_info: &ConsensusCommitInfo,
3013 transactions: Vec<(SequencedConsensusTransactionKind, u32)>,
3014 ) -> Vec<VerifiedSequencedConsensusTransaction> {
3015 let _scope = monitored_scope("ConsensusCommitHandler::deduplicate_consensus_txns");
3016 let mut all_transactions = Vec::new();
3017
3018 let mut occurrence_counts: HashMap<SequencedConsensusTransactionKey, u32> = HashMap::new();
3021 let mut first_commit_keys: HashSet<SequencedConsensusTransactionKey> = HashSet::new();
3023
3024 for (seq, (transaction, cert_origin)) in transactions.into_iter().enumerate() {
3025 let current_tx_index = ExecutionIndices {
3029 last_committed_round: commit_info.round,
3030 sub_dag_index: commit_info.consensus_commit_ref.index.into(),
3031 transaction_index: (seq + 1) as u64,
3032 };
3033
3034 self.last_consensus_stats.index = current_tx_index;
3035
3036 let certificate_author = *self
3037 .epoch_store
3038 .committee()
3039 .authority_by_index(cert_origin)
3040 .unwrap();
3041
3042 let sequenced_transaction = SequencedConsensusTransaction {
3043 certificate_author_index: cert_origin,
3044 certificate_author,
3045 consensus_index: current_tx_index,
3046 transaction,
3047 };
3048
3049 let Some(verified_transaction) = self
3050 .epoch_store
3051 .verify_consensus_transaction(sequenced_transaction)
3052 else {
3053 continue;
3054 };
3055
3056 let key = verified_transaction.0.key();
3057
3058 if let Some(tx_digest) = key.user_transaction_digest() {
3059 self.epoch_store
3060 .cache_recently_finalized_transaction(tx_digest);
3061 }
3062
3063 let count = occurrence_counts.entry(key.clone()).or_insert(0);
3066 *count += 1;
3067 let in_commit = *count > 1;
3068
3069 let in_cache = self.processed_cache.put(key.clone(), ()).is_some();
3070 if in_commit || in_cache {
3071 self.metrics.skipped_consensus_txns_cache_hit.inc();
3072 continue;
3073 }
3074 if self
3075 .epoch_store
3076 .is_consensus_message_processed(&key)
3077 .expect("db error")
3078 {
3079 self.metrics.skipped_consensus_txns.inc();
3080 continue;
3081 }
3082
3083 first_commit_keys.insert(key.clone());
3084
3085 state.output.record_consensus_message_processed(key);
3086
3087 all_transactions.push(verified_transaction);
3088 }
3089
3090 for key in first_commit_keys {
3091 if let Some(&count) = occurrence_counts.get(&key)
3092 && count > 1
3093 {
3094 self.metrics
3095 .consensus_handler_duplicate_tx_count
3096 .observe(count as f64);
3097 }
3098 }
3099
3100 assert!(
3102 state.occurrence_counts.is_empty(),
3103 "occurrence_counts should be empty before populating"
3104 );
3105 state.occurrence_counts.reserve(occurrence_counts.len());
3106 state.occurrence_counts.extend(
3107 occurrence_counts
3108 .into_iter()
3109 .filter_map(|(key, count)| key.user_transaction_digest().map(|d| (d, count))),
3110 );
3111
3112 all_transactions
3113 }
3114
3115 fn build_commit_handler_input(
3116 &self,
3117 transactions: Vec<VerifiedSequencedConsensusTransaction>,
3118 ) -> CommitHandlerInput {
3119 let _scope = monitored_scope("ConsensusCommitHandler::build_commit_handler_input");
3120 let epoch = self.epoch_store.epoch();
3121 let mut commit_handler_input = CommitHandlerInput::default();
3122
3123 for VerifiedSequencedConsensusTransaction(transaction) in transactions.into_iter() {
3124 match transaction.transaction {
3125 SequencedConsensusTransactionKind::External(consensus_transaction) => {
3126 match consensus_transaction.kind {
3127 ConsensusTransactionKind::UserTransactionV2(tx) => {
3129 let used_alias_versions = tx.aliases();
3131 let inner_tx = tx.into_tx();
3132 let tx = VerifiedTransaction::new_unchecked(inner_tx);
3134 let transaction =
3136 VerifiedExecutableTransaction::new_from_consensus(tx, epoch);
3137 if let Some(used_alias_versions) = used_alias_versions {
3138 commit_handler_input
3139 .user_transactions
3140 .push(WithAliases::new(transaction, used_alias_versions));
3141 } else {
3142 commit_handler_input.user_transactions.push(
3143 VerifiedExecutableTransactionWithAliases::no_aliases(
3144 transaction,
3145 ),
3146 );
3147 }
3148 }
3149
3150 ConsensusTransactionKind::EndOfPublish(authority_public_key_bytes) => {
3152 commit_handler_input
3153 .end_of_publish_transactions
3154 .push(authority_public_key_bytes);
3155 }
3156 ConsensusTransactionKind::NewJWKFetched(
3157 authority_public_key_bytes,
3158 jwk_id,
3159 jwk,
3160 ) => {
3161 commit_handler_input.new_jwks.push((
3162 authority_public_key_bytes,
3163 jwk_id,
3164 jwk,
3165 ));
3166 }
3167 ConsensusTransactionKind::RandomnessDkgMessage(
3168 authority_public_key_bytes,
3169 items,
3170 ) => {
3171 commit_handler_input
3172 .randomness_dkg_messages
3173 .push((authority_public_key_bytes, items));
3174 }
3175 ConsensusTransactionKind::RandomnessDkgConfirmation(
3176 authority_public_key_bytes,
3177 items,
3178 ) => {
3179 commit_handler_input
3180 .randomness_dkg_confirmations
3181 .push((authority_public_key_bytes, items));
3182 }
3183 ConsensusTransactionKind::CapabilityNotificationV2(
3184 authority_capabilities_v2,
3185 ) => {
3186 commit_handler_input
3187 .capability_notifications
3188 .push(authority_capabilities_v2);
3189 }
3190 ConsensusTransactionKind::ExecutionTimeObservation(
3191 execution_time_observation,
3192 ) => {
3193 commit_handler_input
3194 .execution_time_observations
3195 .push(execution_time_observation);
3196 }
3197 ConsensusTransactionKind::CheckpointSignatureV2(
3198 checkpoint_signature_message,
3199 ) => {
3200 commit_handler_input
3201 .checkpoint_signature_messages
3202 .push(*checkpoint_signature_message);
3203 }
3204 ConsensusTransactionKind::UpdateTransactionDenyConfig(msg) => {
3205 commit_handler_input
3206 .transaction_deny_config_updates
3207 .push((transaction.certificate_author, *msg));
3208 }
3209
3210 ConsensusTransactionKind::CheckpointSignature(_)
3213 | ConsensusTransactionKind::RandomnessStateUpdate(_, _)
3214 | ConsensusTransactionKind::CapabilityNotification(_)
3215 | ConsensusTransactionKind::CertifiedTransaction(_)
3216 | ConsensusTransactionKind::UserTransaction(_) => {
3217 unreachable!("filtered earlier")
3218 }
3219 }
3220 }
3221 SequencedConsensusTransactionKind::System(_verified_envelope) => unreachable!(),
3223 }
3224 }
3225
3226 commit_handler_input
3227 }
3228}
3229
3230pub(crate) type SchedulerMessage = (
3233 Vec<(Schedulable, AssignedVersions)>,
3234 Option<SettlementBatchInfo>,
3235);
3236
3237#[derive(Clone)]
3238pub(crate) struct ExecutionSchedulerSender {
3239 sender: monitored_mpsc::UnboundedSender<SchedulerMessage>,
3240}
3241
3242impl ExecutionSchedulerSender {
3243 fn start(
3244 settlement_scheduler: SettlementScheduler,
3245 epoch_store: Arc<AuthorityPerEpochStore>,
3246 ) -> Self {
3247 let (sender, recv) = monitored_mpsc::unbounded_channel("execution_scheduler_sender");
3248 spawn_monitored_task!(Self::run(recv, settlement_scheduler, epoch_store));
3249 Self { sender }
3250 }
3251
3252 pub(crate) fn new_for_testing(
3253 sender: monitored_mpsc::UnboundedSender<SchedulerMessage>,
3254 ) -> Self {
3255 Self { sender }
3256 }
3257
3258 fn send(
3259 &self,
3260 transactions: Vec<(Schedulable, AssignedVersions)>,
3261 settlement: Option<SettlementBatchInfo>,
3262 ) {
3263 let _ = self.sender.send((transactions, settlement));
3264 }
3265
3266 async fn run(
3267 mut recv: monitored_mpsc::UnboundedReceiver<SchedulerMessage>,
3268 settlement_scheduler: SettlementScheduler,
3269 epoch_store: Arc<AuthorityPerEpochStore>,
3270 ) {
3271 while let Some((transactions, settlement)) = recv.recv().await {
3272 let _guard = monitored_scope("ConsensusHandler::enqueue");
3273 let txns = transactions
3274 .into_iter()
3275 .map(|(txn, versions)| (txn, ExecutionEnv::new().with_assigned_versions(versions)))
3276 .collect();
3277 if let Some(settlement) = settlement {
3278 settlement_scheduler.enqueue_v2(txns, settlement, &epoch_store);
3279 } else {
3280 settlement_scheduler.enqueue(txns, &epoch_store);
3281 }
3282 }
3283 }
3284}
3285
3286const CONSENSUS_HANDLER_DESERIALIZE_CHANNEL_CAPACITY: usize = 2;
3290
3291type ParsedConsensusTransactions = Vec<(BlockRef, Vec<ParsedTransaction>)>;
3295
3296pub(crate) struct MysticetiConsensusHandler {
3298 tasks: JoinSet<()>,
3299}
3300
3301impl MysticetiConsensusHandler {
3302 pub(crate) fn new(
3303 last_processed_commit_at_startup: CommitIndex,
3304 mut consensus_handler: ConsensusHandler<CheckpointService>,
3305 mut commit_receiver: UnboundedReceiver<consensus_core::CommittedSubDag>,
3306 commit_consumer_monitor: Arc<CommitConsumerMonitor>,
3307 ) -> Self {
3308 debug!(
3309 last_processed_commit_at_startup,
3310 "Starting consensus replay"
3311 );
3312 let mut tasks = JoinSet::new();
3313
3314 let (parsed_sender, mut parsed_receiver) = monitored_mpsc::channel(
3320 "consensus_deserialized_commits",
3321 CONSENSUS_HANDLER_DESERIALIZE_CHANNEL_CAPACITY,
3322 );
3323 tasks.spawn(monitored_future!(async move {
3324 while let Some(consensus_commit) = commit_receiver.recv().await {
3325 let transactions: ParsedConsensusTransactions = {
3326 let _scope = monitored_scope("ConsensusCommitHandler::deserialize_worker");
3327 consensus_commit.transactions()
3328 };
3329 if parsed_sender
3333 .send((consensus_commit, transactions))
3334 .await
3335 .is_err()
3336 {
3337 break;
3338 }
3339 }
3340 }));
3341
3342 tasks.spawn(monitored_future!(async move {
3344 while let Some((consensus_commit, transactions)) = parsed_receiver.recv().await {
3346 let commit_index = consensus_commit.commit_ref.index;
3347 if commit_index <= last_processed_commit_at_startup {
3348 consensus_handler.handle_prior_consensus_commit(consensus_commit);
3349 } else {
3350 consensus_handler
3351 .handle_consensus_commit(consensus_commit, transactions)
3352 .await;
3353 }
3354 commit_consumer_monitor.set_highest_handled_commit(commit_index);
3355 }
3356 }));
3357 Self { tasks }
3358 }
3359
3360 pub(crate) async fn abort(&mut self) {
3361 self.tasks.shutdown().await;
3362 }
3363}
3364
3365fn authenticator_state_update_transaction(
3366 epoch_store: &AuthorityPerEpochStore,
3367 round: u64,
3368 mut new_active_jwks: Vec<ActiveJwk>,
3369) -> VerifiedExecutableTransaction {
3370 let epoch = epoch_store.epoch();
3371 new_active_jwks.sort();
3372
3373 info!("creating authenticator state update transaction");
3374 assert!(epoch_store.authenticator_state_enabled());
3375 let transaction = VerifiedTransaction::new_authenticator_state_update(
3376 epoch,
3377 round,
3378 new_active_jwks,
3379 epoch_store
3380 .epoch_start_config()
3381 .authenticator_obj_initial_shared_version()
3382 .expect("authenticator state obj must exist"),
3383 );
3384 VerifiedExecutableTransaction::new_system(transaction, epoch)
3385}
3386
3387fn owned_object_refs_to_lock(
3393 tx_with_claims: &PlainTransactionWithClaims,
3394) -> Option<Vec<ObjectRef>> {
3395 let immutable_object_ids: HashSet<ObjectID> =
3396 tx_with_claims.get_immutable_objects().into_iter().collect();
3397 let input_objects = tx_with_claims
3398 .tx()
3399 .transaction_data()
3400 .input_objects()
3401 .ok()?;
3402 Some(
3403 input_objects
3404 .iter()
3405 .filter_map(|obj| match obj {
3406 InputObjectKind::ImmOrOwnedMoveObject(obj_ref)
3407 if !immutable_object_ids.contains(&obj_ref.0) =>
3408 {
3409 Some(*obj_ref)
3410 }
3411 _ => None,
3412 })
3413 .collect(),
3414 )
3415}
3416
3417pub(crate) fn tx_type_label(transactions: &[ConsensusTransaction]) -> &'static str {
3419 match transactions {
3420 [transaction] => classify(transaction),
3421 _ => "soft_bundle",
3422 }
3423}
3424
3425pub(crate) fn classify(transaction: &ConsensusTransaction) -> &'static str {
3426 match &transaction.kind {
3427 ConsensusTransactionKind::CertifiedTransaction(_) => "_deprecated_certificate",
3429 ConsensusTransactionKind::CheckpointSignature(_) => "checkpoint_signature",
3430 ConsensusTransactionKind::CheckpointSignatureV2(_) => "checkpoint_signature",
3431 ConsensusTransactionKind::EndOfPublish(_) => "end_of_publish",
3432 ConsensusTransactionKind::CapabilityNotification(_) => "capability_notification",
3433 ConsensusTransactionKind::CapabilityNotificationV2(_) => "capability_notification_v2",
3434 ConsensusTransactionKind::NewJWKFetched(_, _, _) => "new_jwk_fetched",
3435 ConsensusTransactionKind::RandomnessStateUpdate(_, _) => "randomness_state_update",
3436 ConsensusTransactionKind::RandomnessDkgMessage(_, _) => "randomness_dkg_message",
3437 ConsensusTransactionKind::RandomnessDkgConfirmation(_, _) => "randomness_dkg_confirmation",
3438 ConsensusTransactionKind::UserTransaction(_) => "_deprecated_user_transaction",
3439 ConsensusTransactionKind::UserTransactionV2(tx) => {
3440 if tx.tx().is_consensus_tx() {
3441 "shared_user_transaction_v2"
3442 } else {
3443 "owned_user_transaction_v2"
3444 }
3445 }
3446 ConsensusTransactionKind::ExecutionTimeObservation(_) => "execution_time_observation",
3447 ConsensusTransactionKind::UpdateTransactionDenyConfig(_) => {
3448 "update_transaction_deny_config"
3449 }
3450 }
3451}
3452
3453#[derive(Debug, Clone, Serialize, Deserialize)]
3454pub struct SequencedConsensusTransaction {
3455 pub certificate_author_index: AuthorityIndex,
3456 pub certificate_author: AuthorityName,
3457 pub consensus_index: ExecutionIndices,
3458 pub transaction: SequencedConsensusTransactionKind,
3459}
3460
3461#[derive(Debug, Clone)]
3462#[allow(clippy::large_enum_variant)]
3463pub enum SequencedConsensusTransactionKind {
3464 External(ConsensusTransaction),
3465 System(VerifiedExecutableTransaction),
3466}
3467
3468impl Serialize for SequencedConsensusTransactionKind {
3469 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3470 let serializable = SerializableSequencedConsensusTransactionKind::from(self);
3471 serializable.serialize(serializer)
3472 }
3473}
3474
3475impl<'de> Deserialize<'de> for SequencedConsensusTransactionKind {
3476 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3477 let serializable =
3478 SerializableSequencedConsensusTransactionKind::deserialize(deserializer)?;
3479 Ok(serializable.into())
3480 }
3481}
3482
3483#[derive(Debug, Clone, Serialize, Deserialize)]
3487#[allow(clippy::large_enum_variant)]
3488enum SerializableSequencedConsensusTransactionKind {
3489 External(ConsensusTransaction),
3490 System(TrustedExecutableTransaction),
3491}
3492
3493impl From<&SequencedConsensusTransactionKind> for SerializableSequencedConsensusTransactionKind {
3494 fn from(kind: &SequencedConsensusTransactionKind) -> Self {
3495 match kind {
3496 SequencedConsensusTransactionKind::External(ext) => {
3497 SerializableSequencedConsensusTransactionKind::External(ext.clone())
3498 }
3499 SequencedConsensusTransactionKind::System(txn) => {
3500 SerializableSequencedConsensusTransactionKind::System(txn.clone().serializable())
3501 }
3502 }
3503 }
3504}
3505
3506impl From<SerializableSequencedConsensusTransactionKind> for SequencedConsensusTransactionKind {
3507 fn from(kind: SerializableSequencedConsensusTransactionKind) -> Self {
3508 match kind {
3509 SerializableSequencedConsensusTransactionKind::External(ext) => {
3510 SequencedConsensusTransactionKind::External(ext)
3511 }
3512 SerializableSequencedConsensusTransactionKind::System(txn) => {
3513 SequencedConsensusTransactionKind::System(txn.into())
3514 }
3515 }
3516 }
3517}
3518
3519#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
3520pub enum SequencedConsensusTransactionKey {
3521 External(ConsensusTransactionKey),
3522 System(TransactionDigest),
3523}
3524
3525impl SequencedConsensusTransactionKey {
3526 pub fn user_transaction_digest(&self) -> Option<TransactionDigest> {
3527 match self {
3528 SequencedConsensusTransactionKey::External(key) => match key {
3529 ConsensusTransactionKey::Certificate(digest) => Some(*digest),
3530 _ => None,
3531 },
3532 SequencedConsensusTransactionKey::System(_) => None,
3533 }
3534 }
3535}
3536
3537impl SequencedConsensusTransactionKind {
3538 pub fn key(&self) -> SequencedConsensusTransactionKey {
3539 match self {
3540 SequencedConsensusTransactionKind::External(ext) => {
3541 SequencedConsensusTransactionKey::External(ext.key())
3542 }
3543 SequencedConsensusTransactionKind::System(txn) => {
3544 SequencedConsensusTransactionKey::System(*txn.digest())
3545 }
3546 }
3547 }
3548
3549 pub fn get_tracking_id(&self) -> u64 {
3550 match self {
3551 SequencedConsensusTransactionKind::External(ext) => ext.get_tracking_id(),
3552 SequencedConsensusTransactionKind::System(_txn) => 0,
3553 }
3554 }
3555
3556 pub fn is_executable_transaction(&self) -> bool {
3557 match self {
3558 SequencedConsensusTransactionKind::External(ext) => ext.is_user_transaction(),
3559 SequencedConsensusTransactionKind::System(_) => true,
3560 }
3561 }
3562
3563 pub fn executable_transaction_digest(&self) -> Option<TransactionDigest> {
3564 match self {
3565 SequencedConsensusTransactionKind::External(ext) => match &ext.kind {
3566 ConsensusTransactionKind::UserTransactionV2(txn) => Some(*txn.tx().digest()),
3567 _ => None,
3568 },
3569 SequencedConsensusTransactionKind::System(txn) => Some(*txn.digest()),
3570 }
3571 }
3572
3573 pub fn is_end_of_publish(&self) -> bool {
3574 match self {
3575 SequencedConsensusTransactionKind::External(ext) => {
3576 matches!(ext.kind, ConsensusTransactionKind::EndOfPublish(..))
3577 }
3578 SequencedConsensusTransactionKind::System(_) => false,
3579 }
3580 }
3581}
3582
3583impl SequencedConsensusTransaction {
3584 pub fn sender_authority(&self) -> AuthorityName {
3585 self.certificate_author
3586 }
3587
3588 pub fn key(&self) -> SequencedConsensusTransactionKey {
3589 self.transaction.key()
3590 }
3591
3592 pub fn is_end_of_publish(&self) -> bool {
3593 if let SequencedConsensusTransactionKind::External(ref transaction) = self.transaction {
3594 matches!(transaction.kind, ConsensusTransactionKind::EndOfPublish(..))
3595 } else {
3596 false
3597 }
3598 }
3599
3600 pub fn try_take_execution_time_observation(&mut self) -> Option<ExecutionTimeObservation> {
3601 if let SequencedConsensusTransactionKind::External(ConsensusTransaction {
3602 kind: ConsensusTransactionKind::ExecutionTimeObservation(observation),
3603 ..
3604 }) = &mut self.transaction
3605 {
3606 Some(std::mem::take(observation))
3607 } else {
3608 None
3609 }
3610 }
3611
3612 pub fn is_system(&self) -> bool {
3613 matches!(
3614 self.transaction,
3615 SequencedConsensusTransactionKind::System(_)
3616 )
3617 }
3618
3619 pub fn is_user_tx_with_randomness(&self, randomness_state_enabled: bool) -> bool {
3620 if !randomness_state_enabled {
3621 return false;
3624 }
3625 match &self.transaction {
3626 SequencedConsensusTransactionKind::External(ConsensusTransaction {
3627 kind: ConsensusTransactionKind::UserTransactionV2(txn),
3628 ..
3629 }) => txn.tx().transaction_data().uses_randomness(),
3630 _ => false,
3631 }
3632 }
3633
3634 pub fn as_consensus_txn(&self) -> Option<&SenderSignedData> {
3635 match &self.transaction {
3636 SequencedConsensusTransactionKind::External(ConsensusTransaction {
3637 kind: ConsensusTransactionKind::UserTransactionV2(txn),
3638 ..
3639 }) if txn.tx().is_consensus_tx() => Some(txn.tx().data()),
3640 SequencedConsensusTransactionKind::System(txn) if txn.is_consensus_tx() => {
3641 Some(txn.data())
3642 }
3643 _ => None,
3644 }
3645 }
3646}
3647
3648#[derive(Debug, Clone, Serialize, Deserialize)]
3649pub struct VerifiedSequencedConsensusTransaction(pub SequencedConsensusTransaction);
3650
3651#[cfg(test)]
3652impl VerifiedSequencedConsensusTransaction {
3653 pub fn new_test(transaction: ConsensusTransaction) -> Self {
3654 Self(SequencedConsensusTransaction::new_test(transaction))
3655 }
3656}
3657
3658impl SequencedConsensusTransaction {
3659 pub fn new_test(transaction: ConsensusTransaction) -> Self {
3660 Self {
3661 certificate_author_index: 0,
3662 certificate_author: AuthorityName::ZERO,
3663 consensus_index: Default::default(),
3664 transaction: SequencedConsensusTransactionKind::External(transaction),
3665 }
3666 }
3667}
3668
3669#[derive(Serialize, Deserialize)]
3670pub(crate) struct CommitIntervalObserver {
3671 ring_buffer: VecDeque<u64>,
3672}
3673
3674impl CommitIntervalObserver {
3675 pub fn new(window_size: u32) -> Self {
3676 Self {
3677 ring_buffer: VecDeque::with_capacity(window_size as usize),
3678 }
3679 }
3680
3681 pub fn observe_commit_time(&mut self, consensus_commit: &impl ConsensusCommitAPI) {
3682 let commit_time = consensus_commit.commit_timestamp_ms();
3683 if self.ring_buffer.len() == self.ring_buffer.capacity() {
3684 self.ring_buffer.pop_front();
3685 }
3686 self.ring_buffer.push_back(commit_time);
3687 }
3688
3689 pub fn commit_interval_estimate(&self) -> Option<Duration> {
3690 if self.ring_buffer.len() <= 1 {
3691 None
3692 } else {
3693 let first = self.ring_buffer.front().unwrap();
3694 let last = self.ring_buffer.back().unwrap();
3695 let duration = last.saturating_sub(*first);
3696 let num_commits = self.ring_buffer.len() as u64;
3697 Some(Duration::from_millis(duration.div_ceil(num_commits)))
3698 }
3699 }
3700}
3701
3702#[cfg(test)]
3703mod tests {
3704 use consensus_core::{
3705 BlockAPI, CommitDigest, CommitRef, CommittedSubDag, TestBlock, Transaction, VerifiedBlock,
3706 };
3707 use futures::pin_mut;
3708 use prometheus::Registry;
3709 use sui_protocol_config::{ConsensusTransactionOrdering, ProtocolConfig};
3710 use sui_types::{
3711 base_types::ExecutionDigests,
3712 base_types::{AuthorityName, FullObjectRef, ObjectID, SuiAddress, random_object_ref},
3713 committee::Committee,
3714 crypto::deterministic_random_account_key,
3715 gas::GasCostSummary,
3716 message_envelope::Message,
3717 messages_checkpoint::{
3718 CheckpointContents, CheckpointSignatureMessage, CheckpointSummary,
3719 SignedCheckpointSummary,
3720 },
3721 messages_consensus::ConsensusTransaction,
3722 object::Object,
3723 transaction::{
3724 CertifiedTransaction, TransactionData, TransactionDataAPI, VerifiedCertificate,
3725 },
3726 };
3727
3728 use sui_types::SUI_RANDOMNESS_STATE_OBJECT_ID;
3729 use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
3730 use sui_types::transaction::{ObjectArg, SharedObjectMutability};
3731
3732 use super::*;
3733 use crate::{
3734 authority::{
3735 authority_per_epoch_store::ConsensusStatsAPI,
3736 consensus_tx_status_cache::NotifyReadConsensusTxStatusResult,
3737 test_authority_builder::TestAuthorityBuilder,
3738 },
3739 checkpoints::CheckpointServiceNoop,
3740 consensus_adapter::consensus_tests::test_user_transaction,
3741 consensus_test_utils::{TestConsensusCommit, setup_consensus_handler_for_testing},
3742 post_consensus_tx_reorder::PostConsensusTxReorder,
3743 };
3744
3745 fn epoch_close_deadline_config(deadline_ms: Option<u64>) -> ProtocolConfig {
3746 let mut protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
3747 if let Some(deadline_ms) = deadline_ms {
3748 protocol_config.set_epoch_close_deadline_ms_for_testing(deadline_ms);
3749 } else {
3750 protocol_config.disable_epoch_close_deadline_ms_for_testing();
3751 }
3752 protocol_config
3753 }
3754
3755 #[tokio::test]
3756 async fn test_consensus_handler_max_transaction_size() {
3757 let metrics = AuthorityMetrics::new(&Registry::new());
3758
3759 metrics.observe_consensus_handler_transaction_size("class_a", "accepted", 100);
3760 metrics.observe_consensus_handler_transaction_size("class_a", "rejected", 80);
3761 metrics.observe_consensus_handler_transaction_size("class_b", "accepted", 50);
3762
3763 assert_eq!(
3764 metrics
3765 .consensus_handler_max_transaction_size
3766 .with_label_values(&["class_a"])
3767 .get(),
3768 100
3769 );
3770 assert_eq!(
3771 metrics
3772 .consensus_handler_max_transaction_size
3773 .with_label_values(&["class_b"])
3774 .get(),
3775 50
3776 );
3777
3778 metrics.observe_consensus_handler_transaction_size("class_a", "rejected", 120);
3779
3780 assert_eq!(
3781 metrics
3782 .consensus_handler_max_transaction_size
3783 .with_label_values(&["class_a"])
3784 .get(),
3785 120
3786 );
3787 }
3788
3789 #[tokio::test(flavor = "current_thread")]
3790 async fn test_epoch_close_deadline_preserves_pre_deadline_blocking() {
3791 let state = TestAuthorityBuilder::new()
3792 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3793 .build()
3794 .await;
3795 let epoch_store = state.epoch_store_for_testing();
3796 epoch_store.insert_deferred_transactions_for_test(
3797 DeferralKey::new_for_consensus_round(u64::MAX, 1),
3798 vec![user_txn(1)],
3799 );
3800 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3801 let mut setup = setup_consensus_handler_for_testing(&state).await;
3802
3803 setup
3804 .consensus_handler
3805 .handle_consensus_commit_for_test(TestConsensusCommit::empty(1, scheduled_end + 99, 1))
3806 .await;
3807
3808 let reconfig_state = epoch_store.get_reconfig_state_read_lock_guard();
3809 assert!(reconfig_state.is_reject_all_certs());
3810 assert!(!reconfig_state.is_reject_all_tx());
3811 assert_eq!(
3812 epoch_store.get_all_deferred_transactions_for_test().len(),
3813 1
3814 );
3815 assert!(
3816 epoch_store
3817 .get_pending_checkpoints(None)
3818 .unwrap()
3819 .iter()
3820 .all(|(_, checkpoint)| !checkpoint.details.last_of_epoch)
3821 );
3822 assert_eq!(
3823 setup
3824 .consensus_handler
3825 .metrics
3826 .consensus_handler_dropped_transactions
3827 .with_label_values(&["epoch_close_deadline"])
3828 .get(),
3829 0
3830 );
3831 }
3832
3833 #[tokio::test(flavor = "current_thread")]
3834 async fn test_epoch_close_deadline_none_preserves_indefinite_blocking() {
3835 let state = TestAuthorityBuilder::new()
3836 .with_protocol_config(epoch_close_deadline_config(None))
3837 .build()
3838 .await;
3839 let epoch_store = state.epoch_store_for_testing();
3840 epoch_store.insert_deferred_transactions_for_test(
3841 DeferralKey::new_for_consensus_round(u64::MAX, 1),
3842 vec![user_txn(1)],
3843 );
3844 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3845 let mut setup = setup_consensus_handler_for_testing(&state).await;
3846
3847 setup
3848 .consensus_handler
3849 .handle_consensus_commit_for_test(TestConsensusCommit::empty(
3850 1,
3851 scheduled_end.saturating_add(1_000_000),
3852 1,
3853 ))
3854 .await;
3855
3856 let reconfig_state = epoch_store.get_reconfig_state_read_lock_guard();
3857 assert!(reconfig_state.is_reject_all_certs());
3858 assert!(!reconfig_state.is_reject_all_tx());
3859 }
3860
3861 #[tokio::test(flavor = "current_thread")]
3862 async fn test_epoch_close_deadline_is_inert_without_deferred_transactions() {
3863 let state = TestAuthorityBuilder::new()
3864 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3865 .build()
3866 .await;
3867 let epoch_store = state.epoch_store_for_testing();
3868 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
3869 let mut setup = setup_consensus_handler_for_testing(&state).await;
3870
3871 setup
3875 .consensus_handler
3876 .handle_consensus_commit_for_test(TestConsensusCommit::empty(1, scheduled_end + 100, 1))
3877 .await;
3878
3879 assert!(
3880 epoch_store
3881 .get_reconfig_state_read_lock_guard()
3882 .is_reject_all_tx()
3883 );
3884 let checkpoints = epoch_store.get_pending_checkpoints(None).unwrap();
3885 assert!(checkpoints.last().unwrap().1.details.last_of_epoch);
3886 assert_eq!(
3887 setup
3888 .consensus_handler
3889 .metrics
3890 .consensus_handler_dropped_transactions
3891 .with_label_values(&["epoch_close_deadline"])
3892 .get(),
3893 0
3894 );
3895 }
3896
3897 #[tokio::test(flavor = "current_thread")]
3898 async fn test_epoch_close_deadline_counts_abandoned_transactions_and_closes_first() {
3899 let state = TestAuthorityBuilder::new()
3900 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3901 .build()
3902 .await;
3903 let epoch_store = state.epoch_store_for_testing();
3904 let key = DeferralKey::new_for_consensus_round(u64::MAX, 1);
3905 epoch_store.insert_deferred_transactions_for_test(key, vec![user_txn(1), user_txn(2)]);
3906 let setup = setup_consensus_handler_for_testing(&state).await;
3907 let mut handler_state = CommitHandlerState::new(&epoch_store, 1);
3908
3909 let (reconfig_state, final_round, abandoned) = setup
3910 .consensus_handler
3911 .advance_end_of_epoch_state_machine(&mut handler_state, true);
3912
3913 assert!(reconfig_state.is_reject_all_tx());
3914 assert!(final_round);
3915 let abandoned = abandoned.expect("deferred transactions must be reported as abandoned");
3916 assert_eq!(abandoned.count, 2);
3917 assert_eq!(abandoned.sample.len(), 2);
3918 assert!(
3921 handler_state
3922 .output
3923 .get_deleted_deferred_txn_keys()
3924 .any(|deleted| deleted == key)
3925 );
3926 }
3927
3928 #[tokio::test(flavor = "current_thread")]
3929 async fn test_epoch_close_deadline_does_not_report_transactions_drained_in_commit() {
3930 let state = TestAuthorityBuilder::new()
3931 .with_protocol_config(epoch_close_deadline_config(Some(100)))
3932 .build()
3933 .await;
3934 let epoch_store = state.epoch_store_for_testing();
3935 let key = DeferralKey::new_for_consensus_round(1, 0);
3936 epoch_store.insert_deferred_transactions_for_test(key, vec![user_txn(1), user_txn(2)]);
3937 let setup = setup_consensus_handler_for_testing(&state).await;
3938 let mut handler_state = CommitHandlerState::new(&epoch_store, 1);
3939 handler_state
3940 .output
3941 .delete_loaded_deferred_transactions(&[key]);
3942
3943 let (reconfig_state, final_round, abandoned) = setup
3944 .consensus_handler
3945 .advance_end_of_epoch_state_machine(&mut handler_state, true);
3946
3947 assert!(reconfig_state.is_reject_all_tx());
3948 assert!(final_round);
3949 assert!(abandoned.is_none());
3950 }
3951
3952 #[cfg(debug_assertions)]
3956 #[tokio::test(flavor = "current_thread")]
3957 #[should_panic(
3958 expected = "Epoch close deadline reached with unscheduled deferred transactions"
3959 )]
3960 async fn test_epoch_close_deadline_timestamp_jump_abandons_fresh_deferral() {
3961 use sui_protocol_config::{ExecutionTimeEstimateParams, PerObjectCongestionControlMode};
3962
3963 let execution_time_params = ExecutionTimeEstimateParams {
3964 target_utilization: 1,
3965 allowed_txn_cost_overage_burst_limit_us: 0,
3966 max_estimate_us: u64::MAX,
3967 randomness_scalar: 100,
3968 stored_observations_num_included_checkpoints: 10,
3969 stored_observations_limit: u64::MAX,
3970 stake_weighted_median_threshold: 0,
3971 default_none_duration_for_new_keys: true,
3972 observations_chunk_size: None,
3973 };
3974 let mut protocol_config = epoch_close_deadline_config(Some(100));
3975 protocol_config.set_per_object_congestion_control_mode_for_testing(
3976 PerObjectCongestionControlMode::ExecutionTimeEstimate(execution_time_params),
3977 );
3978 protocol_config.set_max_deferral_rounds_for_congestion_control_for_testing(1_000);
3979
3980 let (sender, keypair) = deterministic_random_account_key();
3981 let gas_objects: Vec<_> = (0..4)
3982 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
3983 .collect();
3984 let shared_object = Object::shared_for_testing();
3985 let mut starting_objects = gas_objects.clone();
3986 starting_objects.push(shared_object.clone());
3987 let state = TestAuthorityBuilder::new()
3988 .with_starting_objects(&starting_objects)
3989 .with_protocol_config(protocol_config)
3990 .build()
3991 .await;
3992 let mut consensus_transactions = Vec::new();
3993 for gas_object in gas_objects {
3994 let transaction = test_user_transaction(
3995 &state,
3996 sender,
3997 &keypair,
3998 gas_object,
3999 vec![shared_object.clone()],
4000 )
4001 .await;
4002 consensus_transactions.push(ConsensusTransaction::new_user_transaction_v2_message(
4003 &state.name,
4004 transaction.into(),
4005 ));
4006 }
4007 let epoch_store = state.epoch_store_for_testing();
4008 let scheduled_end = epoch_store.next_reconfiguration_timestamp_ms();
4009 let mut setup = setup_consensus_handler_for_testing(&state).await;
4010
4011 setup
4012 .consensus_handler
4013 .handle_consensus_commit_for_test(TestConsensusCommit::new(
4014 consensus_transactions,
4015 1,
4016 scheduled_end + 100,
4017 1,
4018 ))
4019 .await;
4020 }
4021
4022 #[tokio::test(flavor = "current_thread", start_paused = true)]
4023 async fn test_consensus_commit_handler() {
4024 telemetry_subscribers::init_for_testing();
4025
4026 let (sender, keypair) = deterministic_random_account_key();
4029 let gas_objects: Vec<Object> = (0..12)
4031 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4032 .collect();
4033 let owned_objects: Vec<Object> = (0..4)
4035 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4036 .collect();
4037 let shared_objects: Vec<Object> = (0..6)
4039 .map(|_| Object::shared_for_testing())
4040 .collect::<Vec<_>>();
4041 let mut all_objects = gas_objects.clone();
4042 all_objects.extend(owned_objects.clone());
4043 all_objects.extend(shared_objects.clone());
4044
4045 let network_config =
4046 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
4047 .with_objects(all_objects.clone())
4048 .build();
4049
4050 let state = TestAuthorityBuilder::new()
4051 .with_network_config(&network_config, 0)
4052 .build()
4053 .await;
4054
4055 let epoch_store = state.epoch_store_for_testing().clone();
4056 let new_epoch_start_state = epoch_store.epoch_start_state();
4057 let consensus_committee = new_epoch_start_state.get_consensus_committee();
4058
4059 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
4060
4061 let throughput_calculator = ConsensusThroughputCalculator::new(None, metrics.clone());
4062
4063 let backpressure_manager = BackpressureManager::new_for_tests();
4064 let settlement_scheduler = SettlementScheduler::new(
4065 state.execution_scheduler().as_ref().clone(),
4066 state.get_transaction_cache_reader().clone(),
4067 state.metrics.clone(),
4068 );
4069 let mut consensus_handler = ConsensusHandler::new(
4070 epoch_store,
4071 Arc::new(CheckpointServiceNoop {}),
4072 settlement_scheduler,
4073 state.get_object_cache_reader().clone(),
4074 consensus_committee.clone(),
4075 metrics,
4076 Arc::new(throughput_calculator),
4077 backpressure_manager.subscribe(),
4078 state.traffic_controller.clone(),
4079 None,
4080 state.consensus_gasless_counter.clone(),
4081 state.transaction_deny_config_manager().clone(),
4082 );
4083
4084 let mut user_transactions = vec![];
4086 for (i, gas_object) in gas_objects[0..8].iter().enumerate() {
4087 let input_object = if i % 2 == 0 {
4088 owned_objects.get(i / 2).unwrap().clone()
4089 } else {
4090 shared_objects.get(i / 2).unwrap().clone()
4091 };
4092 let transaction = test_user_transaction(
4093 &state,
4094 sender,
4095 &keypair,
4096 gas_object.clone(),
4097 vec![input_object],
4098 )
4099 .await;
4100 user_transactions.push(transaction);
4101 }
4102
4103 for (i, gas_object) in gas_objects[8..12].iter().enumerate() {
4106 let shared_object = if i < 2 {
4107 shared_objects[4].clone()
4108 } else {
4109 shared_objects[5].clone()
4110 };
4111 let transaction = test_user_transaction(
4112 &state,
4113 sender,
4114 &keypair,
4115 gas_object.clone(),
4116 vec![shared_object],
4117 )
4118 .await;
4119 user_transactions.push(transaction);
4120 }
4121
4122 let mut blocks = Vec::new();
4124 for (i, consensus_transaction) in user_transactions
4125 .iter()
4126 .cloned()
4127 .map(|t| ConsensusTransaction::new_user_transaction_v2_message(&state.name, t.into()))
4128 .enumerate()
4129 {
4130 let transaction_bytes = bcs::to_bytes(&consensus_transaction).unwrap();
4131 let block = VerifiedBlock::new_for_test(
4132 TestBlock::new(100 + i as u32, (i % consensus_committee.size()) as u32)
4133 .set_transactions(vec![Transaction::new(transaction_bytes)])
4134 .build(),
4135 );
4136
4137 blocks.push(block);
4138 }
4139
4140 let leader_block = blocks[0].clone();
4142 let committed_sub_dag = CommittedSubDag::new(
4143 leader_block.reference(),
4144 blocks.clone(),
4145 leader_block.timestamp_ms(),
4146 CommitRef::new(10, CommitDigest::MIN),
4147 );
4148
4149 backpressure_manager.set_backpressure(true);
4151 backpressure_manager.update_highest_certified_checkpoint(1);
4153
4154 {
4156 let waiter =
4157 consensus_handler.handle_consensus_commit_for_test(committed_sub_dag.clone());
4158 pin_mut!(waiter);
4159
4160 tokio::time::timeout(std::time::Duration::from_secs(5), &mut waiter)
4162 .await
4163 .unwrap_err();
4164
4165 backpressure_manager.set_backpressure(false);
4167
4168 tokio::time::timeout(std::time::Duration::from_secs(100), waiter)
4170 .await
4171 .unwrap();
4172 }
4173
4174 let num_blocks = blocks.len();
4176 let num_transactions = user_transactions.len();
4177 let last_consensus_stats_1 = consensus_handler.last_consensus_stats.clone();
4178 assert_eq!(
4179 last_consensus_stats_1.index.transaction_index,
4180 num_transactions as u64
4181 );
4182 assert_eq!(last_consensus_stats_1.index.sub_dag_index, 10_u64);
4183 assert_eq!(last_consensus_stats_1.index.last_committed_round, 100_u64);
4184 assert_eq!(
4185 last_consensus_stats_1.stats.get_num_messages(0),
4186 num_blocks as u64
4187 );
4188 assert_eq!(
4189 last_consensus_stats_1.stats.get_num_user_transactions(0),
4190 num_transactions as u64
4191 );
4192
4193 for (i, t) in user_transactions.iter().enumerate() {
4195 let digest = t.tx().digest();
4196 if tokio::time::timeout(
4197 std::time::Duration::from_secs(10),
4198 state.notify_read_effects_for_testing("", *digest),
4199 )
4200 .await
4201 .is_ok()
4202 {
4203 } else {
4205 panic!("User transaction {} {} did not execute", i, digest);
4206 }
4207 }
4208
4209 state.execution_scheduler().check_empty_for_testing().await;
4211 }
4212
4213 #[tokio::test(flavor = "current_thread")]
4214 async fn test_dropped_owned_object_lock_conflict_is_marked_processed() {
4215 telemetry_subscribers::init_for_testing();
4216
4217 let (sender, keypair) = deterministic_random_account_key();
4218 let gas_objects: Vec<Object> = (0..2)
4219 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
4220 .collect();
4221 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4222 let mut all_objects = gas_objects.clone();
4223 all_objects.push(owned_object.clone());
4224
4225 let state = TestAuthorityBuilder::new()
4226 .with_starting_objects(&all_objects)
4227 .skip_genesis_owner_index()
4228 .build()
4229 .await;
4230 let epoch_store = state.epoch_store_for_testing();
4231 let owned_object_ref = state
4232 .get_object(&owned_object.id())
4233 .unwrap()
4234 .compute_object_reference();
4235
4236 let winner = test_user_transaction(
4237 &state,
4238 sender,
4239 &keypair,
4240 gas_objects[0].clone(),
4241 vec![owned_object.clone()],
4242 )
4243 .await;
4244 let loser = test_user_transaction(
4245 &state,
4246 sender,
4247 &keypair,
4248 gas_objects[1].clone(),
4249 vec![owned_object.clone()],
4250 )
4251 .await;
4252
4253 let winner_digest = *winner.tx().digest();
4254 let loser_digest = *loser.tx().digest();
4255 assert_ne!(winner_digest, loser_digest);
4256
4257 let winner_consensus_tx =
4258 ConsensusTransaction::new_user_transaction_v2_message(&state.name, winner.into());
4259 let loser_consensus_tx =
4260 ConsensusTransaction::new_user_transaction_v2_message(&state.name, loser.into());
4261 let winner_key = SequencedConsensusTransactionKey::External(winner_consensus_tx.key());
4262 let loser_key = SequencedConsensusTransactionKey::External(loser_consensus_tx.key());
4263
4264 let round = 100;
4265 let commit = TestConsensusCommit::new(
4266 vec![winner_consensus_tx, loser_consensus_tx],
4267 round as u64,
4268 1_000,
4269 10,
4270 );
4271 let mut setup = setup_consensus_handler_for_testing(&state).await;
4272 setup
4273 .consensus_handler
4274 .handle_consensus_commit_for_test(commit)
4275 .await;
4276 assert_eq!(
4277 setup
4278 .consensus_handler
4279 .metrics
4280 .consensus_handler_dropped_transactions
4281 .with_label_values(&["lock_conflict"])
4282 .get(),
4283 1
4284 );
4285
4286 let block = BlockRef {
4287 author: consensus_config::AuthorityIndex::ZERO,
4288 round,
4289 digest: Default::default(),
4290 };
4291 assert!(matches!(
4292 epoch_store
4293 .consensus_tx_status_cache
4294 .notify_read_transaction_status(ConsensusPosition {
4295 epoch: epoch_store.epoch(),
4296 block,
4297 index: 0,
4298 })
4299 .await,
4300 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Finalized)
4301 ));
4302 assert!(matches!(
4303 epoch_store
4304 .consensus_tx_status_cache
4305 .notify_read_transaction_status(ConsensusPosition {
4306 epoch: epoch_store.epoch(),
4307 block,
4308 index: 1,
4309 })
4310 .await,
4311 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4312 ));
4313
4314 let locks = epoch_store
4315 .get_owned_object_locks_map(&[owned_object_ref])
4316 .unwrap();
4317 assert_eq!(locks.get(&owned_object_ref), Some(&winner_digest));
4318 assert!(
4319 epoch_store
4320 .is_consensus_message_processed(&winner_key)
4321 .unwrap()
4322 );
4323 assert!(
4324 epoch_store
4325 .is_consensus_message_processed(&loser_key)
4326 .unwrap()
4327 );
4328 tokio::time::timeout(
4331 std::time::Duration::from_secs(5),
4332 epoch_store.consensus_messages_processed_notify(vec![loser_key]),
4333 )
4334 .await
4335 .expect("processed notification for dropped transaction should resolve")
4336 .unwrap();
4337 }
4338
4339 #[tokio::test(flavor = "current_thread")]
4340 async fn test_rejected_transaction_sets_status_and_is_not_marked_processed() {
4341 telemetry_subscribers::init_for_testing();
4342
4343 let (sender, keypair) = deterministic_random_account_key();
4344 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4345 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4346
4347 let state = TestAuthorityBuilder::new()
4348 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4349 .skip_genesis_owner_index()
4350 .build()
4351 .await;
4352 let epoch_store = state.epoch_store_for_testing();
4353
4354 let transaction =
4355 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4356 let consensus_tx =
4357 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4358 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4359
4360 let round = 100;
4361 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10)
4362 .with_rejected_indices([0]);
4363 let mut setup = setup_consensus_handler_for_testing(&state).await;
4364 setup
4365 .consensus_handler
4366 .handle_consensus_commit_for_test(commit)
4367 .await;
4368
4369 let block = BlockRef {
4371 author: consensus_config::AuthorityIndex::ZERO,
4372 round,
4373 digest: Default::default(),
4374 };
4375 assert!(matches!(
4376 epoch_store
4377 .consensus_tx_status_cache
4378 .notify_read_transaction_status(ConsensusPosition {
4379 epoch: epoch_store.epoch(),
4380 block,
4381 index: 0,
4382 })
4383 .await,
4384 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Rejected)
4385 ));
4386 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4390 }
4391
4392 #[tokio::test(flavor = "current_thread")]
4393 async fn test_user_transaction_ignored_at_epoch_close_sets_dropped_status() {
4394 telemetry_subscribers::init_for_testing();
4395
4396 let (sender, keypair) = deterministic_random_account_key();
4397 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4398 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4399
4400 let state = TestAuthorityBuilder::new()
4401 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4402 .skip_genesis_owner_index()
4403 .build()
4404 .await;
4405 let epoch_store = state.epoch_store_for_testing();
4406
4407 let transaction =
4408 test_user_transaction(&state, sender, &keypair, gas_object, vec![owned_object]).await;
4409 let consensus_tx =
4410 ConsensusTransaction::new_user_transaction_v2_message(&state.name, transaction.into());
4411 let key = SequencedConsensusTransactionKey::External(consensus_tx.key());
4412
4413 {
4415 let mut guard = epoch_store.get_reconfig_state_write_lock_guard();
4416 guard.close_all_certs();
4417 }
4418
4419 let round = 100;
4420 let commit = TestConsensusCommit::new(vec![consensus_tx], round as u64, 1_000, 10);
4421 let mut setup = setup_consensus_handler_for_testing(&state).await;
4422 setup
4423 .consensus_handler
4424 .handle_consensus_commit_for_test(commit)
4425 .await;
4426
4427 let block = BlockRef {
4430 author: consensus_config::AuthorityIndex::ZERO,
4431 round,
4432 digest: Default::default(),
4433 };
4434 let status = tokio::time::timeout(
4435 std::time::Duration::from_secs(5),
4436 epoch_store
4437 .consensus_tx_status_cache
4438 .notify_read_transaction_status(ConsensusPosition {
4439 epoch: epoch_store.epoch(),
4440 block,
4441 index: 0,
4442 }),
4443 )
4444 .await
4445 .expect("transaction ignored at epoch close should receive a terminal status");
4446 assert!(matches!(
4447 status,
4448 NotifyReadConsensusTxStatusResult::Status(ConsensusTxStatus::Dropped)
4449 ));
4450 assert!(!epoch_store.is_consensus_message_processed(&key).unwrap());
4451 assert_eq!(
4452 setup
4453 .consensus_handler
4454 .metrics
4455 .consensus_handler_dropped_transactions
4456 .with_label_values(&["end_of_epoch"])
4457 .get(),
4458 1
4459 );
4460 }
4461
4462 #[tokio::test(flavor = "current_thread")]
4463 async fn test_user_transaction_after_end_of_publish_sets_dropped_status() {
4464 telemetry_subscribers::init_for_testing();
4465
4466 let (sender, keypair) = deterministic_random_account_key();
4467 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4468 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
4469
4470 let state = TestAuthorityBuilder::new()
4471 .with_starting_objects(&[gas_object.clone(), owned_object.clone()])
4472 .skip_genesis_owner_index()
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}