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