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