@@ -312,18 +312,21 @@ mod tests {
312312 use sui_protocol_config:: { Chain , ProtocolConfig , ProtocolVersion } ;
313313 use sui_types:: crypto:: deterministic_random_account_key;
314314 use sui_types:: error:: { SuiErrorKind , UserInputError } ;
315+ use sui_types:: executable_transaction:: VerifiedExecutableTransaction ;
315316 use sui_types:: messages_checkpoint:: {
316317 CheckpointContents , CheckpointSignatureMessage , CheckpointSummary , SignedCheckpointSummary ,
317318 } ;
318319 use sui_types:: messages_consensus:: ConsensusPosition ;
319320 use sui_types:: {
320321 base_types:: { ExecutionDigests , ObjectID } ,
321322 crypto:: Ed25519SuiSignature ,
323+ effects:: TransactionEffectsAPI as _,
322324 messages_consensus:: ConsensusTransaction ,
323325 object:: Object ,
324326 signature:: GenericSignature ,
325327 } ;
326328
329+ use crate :: authority:: ExecutionEnv ;
327330 use crate :: {
328331 authority:: test_authority_builder:: TestAuthorityBuilder ,
329332 checkpoints:: CheckpointServiceNoop ,
@@ -624,4 +627,68 @@ mod tests {
624627 let res = validator. verify_batch ( & [ & bytes] ) ;
625628 assert ! ( res. is_ok( ) , "{res:?}" ) ;
626629 }
630+
631+ #[ sim_test]
632+ async fn accept_already_executed_transaction ( ) {
633+ let ( sender, keypair) = deterministic_random_account_key ( ) ;
634+
635+ let gas_object = Object :: with_id_owner_for_testing ( ObjectID :: random ( ) , sender) ;
636+ let owned_object = Object :: with_id_owner_for_testing ( ObjectID :: random ( ) , sender) ;
637+
638+ let network_config =
639+ sui_swarm_config:: network_config_builder:: ConfigBuilder :: new_with_temp_dir ( )
640+ . committee_size ( NonZeroUsize :: new ( 1 ) . unwrap ( ) )
641+ . with_objects ( vec ! [ gas_object. clone( ) , owned_object. clone( ) ] )
642+ . build ( ) ;
643+
644+ let state = TestAuthorityBuilder :: new ( )
645+ . with_network_config ( & network_config, 0 )
646+ . build ( )
647+ . await ;
648+
649+ let epoch_store = state. load_epoch_store_one_call_per_task ( ) ;
650+
651+ // Create a transaction and execute it.
652+ let transaction = test_user_transaction (
653+ & state,
654+ sender,
655+ & keypair,
656+ gas_object. clone ( ) ,
657+ vec ! [ owned_object. clone( ) ] ,
658+ )
659+ . await ;
660+ let tx_digest = * transaction. digest ( ) ;
661+ let cert = VerifiedExecutableTransaction :: new_from_quorum_execution ( transaction. clone ( ) , 0 ) ;
662+ let ( executed_effects, _) = state
663+ . try_execute_immediately ( & cert, ExecutionEnv :: new ( ) , & state. epoch_store_for_testing ( ) )
664+ . await
665+ . unwrap ( ) ;
666+
667+ // Verify the transaction is executed.
668+ let read_effects = state
669+ . get_transaction_cache_reader ( )
670+ . get_executed_effects ( & tx_digest)
671+ . expect ( "Transaction should be executed" ) ;
672+ assert_eq ! ( read_effects, executed_effects) ;
673+ assert_eq ! ( read_effects. executed_epoch( ) , epoch_store. epoch( ) ) ;
674+
675+ // Now try to vote on the already executed transaction
676+ let serialized_tx = bcs:: to_bytes ( & ConsensusTransaction :: new_user_transaction_message (
677+ & state. name ,
678+ transaction. into_inner ( ) . clone ( ) ,
679+ ) )
680+ . unwrap ( ) ;
681+ let validator = SuiTxValidator :: new (
682+ state. clone ( ) ,
683+ Arc :: new ( NoopConsensusOverloadChecker { } ) ,
684+ Arc :: new ( CheckpointServiceNoop { } ) ,
685+ SuiTxValidatorMetrics :: new ( & Default :: default ( ) ) ,
686+ ) ;
687+ let rejected_transactions = validator
688+ . verify_and_vote_batch ( & BlockRef :: MAX , & [ & serialized_tx] )
689+ . expect ( "Verify and vote should succeed" ) ;
690+
691+ // The executed transaction should NOT be rejected.
692+ assert ! ( rejected_transactions. is_empty( ) ) ;
693+ }
627694}
0 commit comments