1use std::{collections::HashSet, sync::Arc};
5
6use consensus_core::{TransactionVerifier, ValidationError};
7use consensus_types::block::{BlockRef, TransactionIndex};
8use fastcrypto_tbls::dkg_v1;
9use mysten_metrics::monitored_scope;
10use prometheus::{
11 IntCounter, IntCounterVec, Registry, register_int_counter_vec_with_registry,
12 register_int_counter_with_registry,
13};
14use sui_macros::fail_point_arg;
15#[cfg(msim)]
16use sui_types::base_types::AuthorityName;
17use sui_types::{
18 base_types::{ObjectID, ObjectRef},
19 error::{SuiError, SuiErrorKind, SuiResult, UserInputError},
20 messages_consensus::{ConsensusPosition, ConsensusTransaction, ConsensusTransactionKind},
21 transaction::{
22 InputObjectKind, PlainTransactionWithClaims, TransactionDataAPI, TransactionWithClaims,
23 },
24};
25use tap::TapFallible;
26use tracing::{debug, info, instrument, warn};
27
28use crate::{
29 authority::{AuthorityState, authority_per_epoch_store::AuthorityPerEpochStore},
30 checkpoints::CheckpointServiceNotify,
31 consensus_adapter::{ConsensusOverloadChecker, NoopConsensusOverloadChecker},
32};
33
34#[derive(Clone)]
37pub struct SuiTxValidator {
38 authority_state: Arc<AuthorityState>,
39 epoch_store: Arc<AuthorityPerEpochStore>,
40 consensus_overload_checker: Arc<dyn ConsensusOverloadChecker>,
41 checkpoint_service: Arc<dyn CheckpointServiceNotify + Send + Sync>,
42 metrics: Arc<SuiTxValidatorMetrics>,
43}
44
45impl SuiTxValidator {
46 pub fn new(
47 authority_state: Arc<AuthorityState>,
48 epoch_store: Arc<AuthorityPerEpochStore>,
49 checkpoint_service: Arc<dyn CheckpointServiceNotify + Send + Sync>,
50 metrics: Arc<SuiTxValidatorMetrics>,
51 ) -> Self {
52 info!(
53 "SuiTxValidator constructed for epoch {}",
54 epoch_store.epoch()
55 );
56 let consensus_overload_checker = Arc::new(NoopConsensusOverloadChecker {});
58 Self {
59 authority_state,
60 epoch_store,
61 consensus_overload_checker,
62 checkpoint_service,
63 metrics,
64 }
65 }
66
67 fn validate_transactions(&self, txs: &[ConsensusTransactionKind]) -> Result<(), SuiError> {
68 let epoch_store = self.epoch_store.clone();
69 let mut cert_batch = Vec::new();
70 let mut ckpt_messages = Vec::new();
71 let mut ckpt_batch = Vec::new();
72 for tx in txs.iter() {
73 match tx {
74 ConsensusTransactionKind::CertifiedTransaction(certificate) => {
75 if epoch_store.protocol_config().disable_preconsensus_locking() {
76 return Err(SuiErrorKind::UnexpectedMessage(
77 "CertifiedTransaction cannot be used when preconsensus locking is disabled".to_string(),
78 )
79 .into());
80 }
81 cert_batch.push(certificate.as_ref());
82 }
83 ConsensusTransactionKind::CheckpointSignature(signature) => {
84 ckpt_messages.push(signature.as_ref());
85 ckpt_batch.push(&signature.summary);
86 }
87 ConsensusTransactionKind::CheckpointSignatureV2(signature) => {
88 if !epoch_store
89 .protocol_config()
90 .consensus_checkpoint_signature_key_includes_digest()
91 {
92 return Err(SuiErrorKind::UnexpectedMessage(
93 "ConsensusTransactionKind::CheckpointSignatureV2 is unsupported"
94 .to_string(),
95 )
96 .into());
97 }
98 ckpt_messages.push(signature.as_ref());
99 ckpt_batch.push(&signature.summary);
100 }
101 ConsensusTransactionKind::RandomnessDkgMessage(_, bytes) => {
102 if bytes.len() > dkg_v1::DKG_MESSAGES_MAX_SIZE {
103 warn!("batch verification error: DKG Message too large");
104 return Err(SuiErrorKind::InvalidDkgMessageSize.into());
105 }
106 }
107 ConsensusTransactionKind::RandomnessDkgConfirmation(_, bytes) => {
108 if bytes.len() > dkg_v1::DKG_MESSAGES_MAX_SIZE {
109 warn!("batch verification error: DKG Confirmation too large");
110 return Err(SuiErrorKind::InvalidDkgMessageSize.into());
111 }
112 }
113
114 ConsensusTransactionKind::CapabilityNotification(_) => {}
115
116 ConsensusTransactionKind::EndOfPublish(_)
117 | ConsensusTransactionKind::NewJWKFetched(_, _, _)
118 | ConsensusTransactionKind::CapabilityNotificationV2(_)
119 | ConsensusTransactionKind::RandomnessStateUpdate(_, _) => {}
120
121 ConsensusTransactionKind::UserTransaction(_) => {
122 if epoch_store.protocol_config().address_aliases()
123 || epoch_store.protocol_config().disable_preconsensus_locking()
124 {
125 return Err(SuiErrorKind::UnexpectedMessage(
126 "ConsensusTransactionKind::UserTransaction cannot be used when address aliases is enabled or preconsensus locking is disabled".to_string(),
127 )
128 .into());
129 }
130 }
131
132 ConsensusTransactionKind::UserTransactionV2(tx) => {
133 if !(epoch_store.protocol_config().address_aliases()
134 || epoch_store.protocol_config().disable_preconsensus_locking())
135 {
136 return Err(SuiErrorKind::UnexpectedMessage(
137 "ConsensusTransactionKind::UserTransactionV2 must be used when either address aliases is enabled or preconsensus locking is disabled".to_string(),
138 )
139 .into());
140 }
141 if epoch_store.protocol_config().address_aliases() && tx.aliases().is_none() {
142 return Err(SuiErrorKind::UnexpectedMessage(
143 "ConsensusTransactionKind::UserTransactionV2 must contain an aliases claim".to_string(),
144 )
145 .into());
146 }
147 }
149
150 ConsensusTransactionKind::ExecutionTimeObservation(obs) => {
151 if obs.estimates.len()
153 > epoch_store
154 .protocol_config()
155 .max_programmable_tx_commands()
156 .try_into()
157 .unwrap()
158 {
159 return Err(SuiErrorKind::UnexpectedMessage(format!(
160 "ExecutionTimeObservation contains too many estimates: {}",
161 obs.estimates.len()
162 ))
163 .into());
164 }
165 }
166 }
167 }
168
169 let cert_count = cert_batch.len();
171 let ckpt_count = ckpt_batch.len();
172
173 epoch_store
174 .signature_verifier
175 .verify_certs_and_checkpoints(cert_batch, ckpt_batch)
176 .tap_err(|e| warn!("batch verification error: {}", e))?;
177
178 for ckpt in ckpt_messages {
180 self.checkpoint_service
181 .notify_checkpoint_signature(&epoch_store, ckpt)?;
182 }
183
184 self.metrics
185 .certificate_signatures_verified
186 .inc_by(cert_count as u64);
187 self.metrics
188 .checkpoint_signatures_verified
189 .inc_by(ckpt_count as u64);
190 Ok(())
191 }
192
193 #[instrument(level = "debug", skip_all, fields(block_ref))]
194 fn vote_transactions(
195 &self,
196 block_ref: &BlockRef,
197 txs: Vec<ConsensusTransactionKind>,
198 ) -> Vec<TransactionIndex> {
199 let epoch_store = self.epoch_store.clone();
200 if !epoch_store.protocol_config().mysticeti_fastpath() {
201 return vec![];
202 }
203
204 let mut reject_txn_votes = Vec::new();
205 for (i, tx) in txs.into_iter().enumerate() {
206 let tx: PlainTransactionWithClaims = match tx {
207 ConsensusTransactionKind::UserTransaction(tx) => {
208 TransactionWithClaims::no_aliases(*tx)
209 }
210 ConsensusTransactionKind::UserTransactionV2(tx) => *tx,
211 _ => continue,
212 };
213
214 let tx_digest = *tx.tx().digest();
215 if let Err(error) = self.vote_transaction(&epoch_store, tx) {
216 debug!(?tx_digest, "Voting to reject transaction: {error}");
217 self.metrics
218 .transaction_reject_votes
219 .with_label_values(&[error.to_variant_name()])
220 .inc();
221 reject_txn_votes.push(i as TransactionIndex);
222 epoch_store.set_rejection_vote_reason(
224 ConsensusPosition {
225 epoch: epoch_store.epoch(),
226 block: *block_ref,
227 index: i as TransactionIndex,
228 },
229 &error,
230 );
231 } else {
232 debug!(?tx_digest, "Voting to accept transaction");
233 }
234 }
235
236 reject_txn_votes
237 }
238
239 #[instrument(level = "debug", skip_all, err(level = "debug"), fields(tx_digest = ?tx.tx().digest()))]
240 fn vote_transaction(
241 &self,
242 epoch_store: &Arc<AuthorityPerEpochStore>,
243 tx: PlainTransactionWithClaims,
244 ) -> SuiResult<()> {
245 let aliases = tx.aliases();
247 let claimed_immutable_ids = tx.get_immutable_objects();
248 let inner_tx = tx.into_tx();
249
250 inner_tx.validity_check(&epoch_store.tx_validity_check_context())?;
253
254 self.authority_state.check_system_overload(
255 &*self.consensus_overload_checker,
256 inner_tx.data(),
257 self.authority_state.check_system_overload_at_signing(),
258 )?;
259
260 #[allow(unused_mut)]
261 let mut fail_point_always_report_aliases_changed = false;
262 fail_point_arg!(
263 "consensus-validator-always-report-aliases-changed",
264 |for_validators: Vec<AuthorityName>| {
265 if for_validators.contains(&self.authority_state.name) {
266 fail_point_always_report_aliases_changed = true;
268 }
269 }
270 );
271
272 let verified_tx = epoch_store.verify_transaction_with_current_aliases(inner_tx)?;
273
274 if epoch_store.protocol_config().address_aliases()
276 && (*verified_tx.aliases() != aliases.unwrap()
277 || fail_point_always_report_aliases_changed)
278 {
279 return Err(SuiErrorKind::AliasesChanged.into());
280 }
281
282 let inner_tx = verified_tx.into_tx();
283 self.authority_state
284 .handle_vote_transaction(epoch_store, inner_tx.clone())?;
285
286 if epoch_store.protocol_config().disable_preconsensus_locking()
287 && !claimed_immutable_ids.is_empty()
288 {
289 let owned_object_refs: HashSet<ObjectRef> = inner_tx
290 .data()
291 .transaction_data()
292 .input_objects()?
293 .iter()
294 .filter_map(|obj| match obj {
295 InputObjectKind::ImmOrOwnedMoveObject(obj_ref) => Some(*obj_ref),
296 _ => None,
297 })
298 .collect();
299 self.verify_immutable_object_claims(&claimed_immutable_ids, owned_object_refs)?;
300 }
301
302 Ok(())
303 }
304
305 fn verify_immutable_object_claims(
309 &self,
310 claimed_ids: &[ObjectID],
311 owned_object_refs: HashSet<ObjectRef>,
312 ) -> SuiResult<()> {
313 if claimed_ids.is_empty() {
314 return Ok(());
315 }
316
317 let objects = self
318 .authority_state
319 .get_object_cache_reader()
320 .get_objects(claimed_ids);
321
322 for (obj, id) in objects.into_iter().zip(claimed_ids.iter()) {
323 match obj {
324 Some(o) => {
325 let object_ref = o.compute_object_reference();
327 if !owned_object_refs.contains(&o.compute_object_reference()) {
328 return Err(SuiErrorKind::ImmutableObjectClaimNotFoundInInput {
329 object_id: *id,
330 }
331 .into());
332 }
333 if !o.is_immutable() {
334 return Err(SuiErrorKind::InvalidImmutableObjectClaim {
335 claimed_object_id: *id,
336 found_object_ref: object_ref,
337 }
338 .into());
339 }
340 }
341 None => {
342 return Err(SuiErrorKind::UserInputError {
345 error: UserInputError::ObjectNotFound {
346 object_id: *id,
347 version: None,
348 },
349 }
350 .into());
351 }
352 }
353 }
354
355 Ok(())
356 }
357}
358
359fn tx_kind_from_bytes(tx: &[u8]) -> Result<ConsensusTransactionKind, ValidationError> {
360 bcs::from_bytes::<ConsensusTransaction>(tx)
361 .map_err(|e| {
362 ValidationError::InvalidTransaction(format!(
363 "Failed to parse transaction bytes: {:?}",
364 e
365 ))
366 })
367 .map(|tx| tx.kind)
368}
369
370impl TransactionVerifier for SuiTxValidator {
371 fn verify_batch(&self, batch: &[&[u8]]) -> Result<(), ValidationError> {
372 let _scope = monitored_scope("ValidateBatch");
373
374 let txs: Vec<_> = batch
375 .iter()
376 .map(|tx| tx_kind_from_bytes(tx))
377 .collect::<Result<Vec<_>, _>>()?;
378
379 self.validate_transactions(&txs)
380 .map_err(|e| ValidationError::InvalidTransaction(e.to_string()))
381 }
382
383 fn verify_and_vote_batch(
384 &self,
385 block_ref: &BlockRef,
386 batch: &[&[u8]],
387 ) -> Result<Vec<TransactionIndex>, ValidationError> {
388 let _scope = monitored_scope("VerifyAndVoteBatch");
389
390 let txs: Vec<_> = batch
391 .iter()
392 .map(|tx| tx_kind_from_bytes(tx))
393 .collect::<Result<Vec<_>, _>>()?;
394
395 self.validate_transactions(&txs)
396 .map_err(|e| ValidationError::InvalidTransaction(e.to_string()))?;
397
398 Ok(self.vote_transactions(block_ref, txs))
399 }
400}
401
402pub struct SuiTxValidatorMetrics {
403 certificate_signatures_verified: IntCounter,
404 checkpoint_signatures_verified: IntCounter,
405 transaction_reject_votes: IntCounterVec,
406}
407
408impl SuiTxValidatorMetrics {
409 pub fn new(registry: &Registry) -> Arc<Self> {
410 Arc::new(Self {
411 certificate_signatures_verified: register_int_counter_with_registry!(
412 "tx_validator_certificate_signatures_verified",
413 "Number of certificates verified in consensus batch verifier",
414 registry
415 )
416 .unwrap(),
417 checkpoint_signatures_verified: register_int_counter_with_registry!(
418 "tx_validator_checkpoint_signatures_verified",
419 "Number of checkpoint verified in consensus batch verifier",
420 registry
421 )
422 .unwrap(),
423 transaction_reject_votes: register_int_counter_vec_with_registry!(
424 "tx_validator_transaction_reject_votes",
425 "Number of reject transaction votes per reason",
426 &["reason"],
427 registry
428 )
429 .unwrap(),
430 })
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use std::num::NonZeroUsize;
437 use std::sync::Arc;
438
439 use consensus_core::TransactionVerifier as _;
440 use consensus_types::block::BlockRef;
441 use fastcrypto::traits::KeyPair;
442 use sui_config::transaction_deny_config::TransactionDenyConfigBuilder;
443 use sui_macros::sim_test;
444 use sui_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
445 use sui_types::crypto::deterministic_random_account_key;
446 use sui_types::error::{SuiErrorKind, UserInputError};
447 use sui_types::executable_transaction::VerifiedExecutableTransaction;
448 use sui_types::messages_checkpoint::{
449 CheckpointContents, CheckpointSignatureMessage, CheckpointSummary, SignedCheckpointSummary,
450 };
451 use sui_types::messages_consensus::ConsensusPosition;
452 use sui_types::{
453 base_types::{ExecutionDigests, ObjectID},
454 crypto::Ed25519SuiSignature,
455 effects::TransactionEffectsAPI as _,
456 messages_consensus::ConsensusTransaction,
457 object::Object,
458 signature::GenericSignature,
459 transaction::{PlainTransactionWithClaims, Transaction},
460 };
461
462 use crate::authority::ExecutionEnv;
463 use crate::{
464 authority::test_authority_builder::TestAuthorityBuilder,
465 checkpoints::CheckpointServiceNoop,
466 consensus_adapter::consensus_tests::{
467 test_gas_objects, test_user_transaction, test_user_transactions,
468 },
469 consensus_validator::{SuiTxValidator, SuiTxValidatorMetrics},
470 };
471
472 #[sim_test]
473 async fn accept_valid_transaction() {
474 let mut objects = test_gas_objects();
476 let shared_object = Object::shared_for_testing();
477 objects.push(shared_object.clone());
478
479 let network_config =
480 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
481 .with_objects(objects.clone())
482 .build();
483
484 let state = TestAuthorityBuilder::new()
485 .with_network_config(&network_config, 0)
486 .build()
487 .await;
488 let name1 = state.name;
489 let transactions = test_user_transactions(&state, shared_object).await;
490
491 let first_transaction = transactions[0].clone();
492 let first_transaction_bytes: Vec<u8> =
493 bcs::to_bytes(&ConsensusTransaction::new_user_transaction_v2_message(
494 &name1,
495 first_transaction.into(),
496 ))
497 .unwrap();
498
499 let metrics = SuiTxValidatorMetrics::new(&Default::default());
500 let validator = SuiTxValidator::new(
501 state.clone(),
502 state.epoch_store_for_testing().clone(),
503 Arc::new(CheckpointServiceNoop {}),
504 metrics,
505 );
506 let res = validator.verify_batch(&[&first_transaction_bytes]);
507 assert!(res.is_ok(), "{res:?}");
508
509 let transaction_bytes: Vec<_> = transactions
510 .clone()
511 .into_iter()
512 .map(|tx| {
513 bcs::to_bytes(&ConsensusTransaction::new_user_transaction_v2_message(
514 &name1,
515 tx.into(),
516 ))
517 .unwrap()
518 })
519 .collect();
520
521 let batch: Vec<_> = transaction_bytes.iter().map(|t| t.as_slice()).collect();
522 let res_batch = validator.verify_batch(&batch);
523 assert!(res_batch.is_ok(), "{res_batch:?}");
524
525 let bogus_transaction_bytes: Vec<_> = transactions
526 .into_iter()
527 .map(|tx| {
528 let aliases = tx.aliases().clone();
530 let mut signed_tx: Transaction = tx.into_tx().into();
531 signed_tx.tx_signatures_mut_for_testing()[0] =
532 GenericSignature::Signature(sui_types::crypto::Signature::Ed25519SuiSignature(
533 Ed25519SuiSignature::default(),
534 ));
535 let tx_with_claims = PlainTransactionWithClaims::from_aliases(signed_tx, aliases);
536 bcs::to_bytes(&ConsensusTransaction::new_user_transaction_v2_message(
537 &name1,
538 tx_with_claims,
539 ))
540 .unwrap()
541 })
542 .collect();
543
544 let batch: Vec<_> = bogus_transaction_bytes
545 .iter()
546 .map(|t| t.as_slice())
547 .collect();
548 let res_batch = validator.verify_and_vote_batch(&BlockRef::MIN, &batch);
551 assert!(res_batch.is_ok());
552 let rejections = res_batch.unwrap();
554 assert_eq!(
555 rejections.len(),
556 batch.len(),
557 "All bogus transactions should be rejected"
558 );
559 }
560
561 #[tokio::test]
562 async fn test_verify_and_vote_batch() {
563 let (sender, keypair) = deterministic_random_account_key();
565
566 let gas_objects: Vec<Object> = (0..8)
568 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
569 .collect();
570
571 let owned_objects: Vec<Object> = (0..2)
573 .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender))
574 .collect();
575 let denied_object = owned_objects[1].clone();
576
577 let mut objects = gas_objects.clone();
578 objects.extend(owned_objects.clone());
579
580 let network_config =
581 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
582 .committee_size(NonZeroUsize::new(1).unwrap())
583 .with_objects(objects.clone())
584 .build();
585
586 let transaction_deny_config = TransactionDenyConfigBuilder::new()
588 .add_denied_object(denied_object.id())
589 .build();
590 let state = TestAuthorityBuilder::new()
591 .with_network_config(&network_config, 0)
592 .with_transaction_deny_config(transaction_deny_config)
593 .build()
594 .await;
595
596 let valid_transaction = test_user_transaction(
600 &state,
601 sender,
602 &keypair,
603 gas_objects[0].clone(),
604 vec![owned_objects[0].clone()],
605 )
606 .await;
607
608 let invalid_transaction = test_user_transaction(
610 &state,
611 sender,
612 &keypair,
613 gas_objects[1].clone(),
614 vec![denied_object.clone()],
615 )
616 .await;
617
618 let transactions = vec![valid_transaction, invalid_transaction];
620 let serialized_transactions: Vec<_> = transactions
621 .into_iter()
622 .map(|t| {
623 bcs::to_bytes(&ConsensusTransaction::new_user_transaction_v2_message(
624 &state.name,
625 t.into(),
626 ))
627 .unwrap()
628 })
629 .collect();
630 let batch: Vec<_> = serialized_transactions
631 .iter()
632 .map(|t| t.as_slice())
633 .collect();
634
635 let validator = SuiTxValidator::new(
636 state.clone(),
637 state.epoch_store_for_testing().clone(),
638 Arc::new(CheckpointServiceNoop {}),
639 SuiTxValidatorMetrics::new(&Default::default()),
640 );
641
642 let rejected_transactions = validator
644 .verify_and_vote_batch(&BlockRef::MAX, &batch)
645 .unwrap();
646
647 assert_eq!(rejected_transactions, vec![1]);
650
651 let epoch_store = state.load_epoch_store_one_call_per_task();
654 let reason = epoch_store
655 .get_rejection_vote_reason(ConsensusPosition {
656 epoch: state.load_epoch_store_one_call_per_task().epoch(),
657 block: BlockRef::MAX,
658 index: 1,
659 })
660 .expect("Rejection vote reason should be set");
661
662 assert_eq!(
663 reason,
664 SuiErrorKind::UserInputError {
665 error: UserInputError::TransactionDenied {
666 error: format!(
667 "Access to input object {:?} is temporarily disabled",
668 denied_object.id()
669 )
670 }
671 }
672 );
673 }
674
675 #[sim_test]
676 async fn reject_checkpoint_signature_v2_when_flag_disabled() {
677 let network_config =
679 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir().build();
680
681 let disabled_cfg =
682 ProtocolConfig::get_for_version(ProtocolVersion::new(92), Chain::Unknown);
683 let state = TestAuthorityBuilder::new()
684 .with_network_config(&network_config, 0)
685 .with_protocol_config(disabled_cfg)
686 .build()
687 .await;
688
689 let epoch_store = state.load_epoch_store_one_call_per_task();
690
691 let checkpoint_summary = CheckpointSummary::new(
693 &ProtocolConfig::get_for_max_version_UNSAFE(),
694 epoch_store.epoch(),
695 0,
696 0,
697 &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]),
698 None,
699 Default::default(),
700 None,
701 0,
702 Vec::new(),
703 Vec::new(),
704 );
705
706 let keypair = network_config.validator_configs()[0].protocol_key_pair();
707 let authority = keypair.public().into();
708 let signed = SignedCheckpointSummary::new(
709 epoch_store.epoch(),
710 checkpoint_summary,
711 keypair,
712 authority,
713 );
714 let message = CheckpointSignatureMessage { summary: signed };
715
716 let tx = ConsensusTransaction::new_checkpoint_signature_message_v2(message);
717 let bytes = bcs::to_bytes(&tx).unwrap();
718
719 let validator = SuiTxValidator::new(
720 state.clone(),
721 state.epoch_store_for_testing().clone(),
722 Arc::new(CheckpointServiceNoop {}),
723 SuiTxValidatorMetrics::new(&Default::default()),
724 );
725
726 let res = validator.verify_batch(&[&bytes]);
727 assert!(res.is_err());
728 }
729
730 #[sim_test]
731 async fn accept_checkpoint_signature_v2_when_flag_enabled() {
732 let network_config =
734 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir().build();
735
736 let enabled_cfg = ProtocolConfig::get_for_version(ProtocolVersion::new(93), Chain::Unknown);
737 let state = TestAuthorityBuilder::new()
738 .with_network_config(&network_config, 0)
739 .with_protocol_config(enabled_cfg)
740 .build()
741 .await;
742
743 let epoch_store = state.load_epoch_store_one_call_per_task();
744
745 let checkpoint_summary = CheckpointSummary::new(
747 &ProtocolConfig::get_for_max_version_UNSAFE(),
748 epoch_store.epoch(),
749 0,
750 0,
751 &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]),
752 None,
753 Default::default(),
754 None,
755 0,
756 Vec::new(),
757 Vec::new(),
758 );
759
760 let keypair = network_config.validator_configs()[0].protocol_key_pair();
761 let authority = keypair.public().into();
762 let signed = SignedCheckpointSummary::new(
763 epoch_store.epoch(),
764 checkpoint_summary,
765 keypair,
766 authority,
767 );
768 let message = CheckpointSignatureMessage { summary: signed };
769
770 let tx = ConsensusTransaction::new_checkpoint_signature_message_v2(message);
771 let bytes = bcs::to_bytes(&tx).unwrap();
772
773 let validator = SuiTxValidator::new(
774 state.clone(),
775 state.epoch_store_for_testing().clone(),
776 Arc::new(CheckpointServiceNoop {}),
777 SuiTxValidatorMetrics::new(&Default::default()),
778 );
779
780 let res = validator.verify_batch(&[&bytes]);
781 assert!(res.is_ok(), "{res:?}");
782 }
783
784 #[sim_test]
785 async fn accept_already_executed_transaction() {
786 let _guard = ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
790 config.set_disable_preconsensus_locking_for_testing(false);
791 config
792 });
793
794 let (sender, keypair) = deterministic_random_account_key();
795
796 let gas_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
797 let owned_object = Object::with_id_owner_for_testing(ObjectID::random(), sender);
798
799 let network_config =
800 sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir()
801 .committee_size(NonZeroUsize::new(1).unwrap())
802 .with_objects(vec![gas_object.clone(), owned_object.clone()])
803 .build();
804
805 let state = TestAuthorityBuilder::new()
806 .with_network_config(&network_config, 0)
807 .build()
808 .await;
809
810 let epoch_store = state.load_epoch_store_one_call_per_task();
811
812 let transaction = test_user_transaction(
814 &state,
815 sender,
816 &keypair,
817 gas_object.clone(),
818 vec![owned_object.clone()],
819 )
820 .await
821 .into_tx();
822 let tx_digest = *transaction.digest();
823 let cert = VerifiedExecutableTransaction::new_from_consensus(transaction.clone(), 0);
824 let (executed_effects, _) = state
825 .try_execute_immediately(&cert, ExecutionEnv::new(), &state.epoch_store_for_testing())
826 .await
827 .unwrap();
828
829 let read_effects = state
831 .get_transaction_cache_reader()
832 .get_executed_effects(&tx_digest)
833 .expect("Transaction should be executed");
834 assert_eq!(read_effects, executed_effects);
835 assert_eq!(read_effects.executed_epoch(), epoch_store.epoch());
836
837 let serialized_tx = bcs::to_bytes(&ConsensusTransaction::new_user_transaction_message(
839 &state.name,
840 transaction.into_inner().clone(),
841 ))
842 .unwrap();
843 let validator = SuiTxValidator::new(
844 state.clone(),
845 state.epoch_store_for_testing().clone(),
846 Arc::new(CheckpointServiceNoop {}),
847 SuiTxValidatorMetrics::new(&Default::default()),
848 );
849 let rejected_transactions = validator
850 .verify_and_vote_batch(&BlockRef::MAX, &[&serialized_tx])
851 .expect("Verify and vote should succeed");
852
853 assert!(rejected_transactions.is_empty());
855 }
856}