@@ -4226,3 +4226,109 @@ where
42264226
42274227 Ok ( ( ) )
42284228}
4229+ #[ test_case( MemoryStorageBuilder :: default ( ) ; "memory" ) ]
4230+ #[ cfg_attr( feature = "rocksdb" , test_case( RocksDbStorageBuilder :: new( ) . await ; "rocks_db" ) ) ]
4231+ #[ cfg_attr( feature = "dynamodb" , test_case( DynamoDbStorageBuilder :: default ( ) ; "dynamo_db" ) ) ]
4232+ #[ cfg_attr( feature = "scylladb" , test_case( ScyllaDbStorageBuilder :: default ( ) ; "scylla_db" ) ) ]
4233+ #[ test_log:: test( tokio:: test) ]
4234+ async fn test_stage_block_with_message_earlier_than_cursor < B > (
4235+ mut storage_builder : B ,
4236+ ) -> anyhow:: Result < ( ) >
4237+ where
4238+ B : StorageBuilder ,
4239+ {
4240+ let mut signer = InMemorySigner :: new ( None ) ;
4241+ let receiver_public_key = signer. generate_new ( ) ;
4242+ let owner = receiver_public_key. into ( ) ;
4243+ let mut env = TestEnvironment :: new ( storage_builder. build ( ) . await ?, false , false ) . await ;
4244+ let chain_1_desc = env. add_root_chain ( 1 , owner, Amount :: from_tokens ( 10 ) ) . await ;
4245+ let chain_2_desc = env. add_root_chain ( 2 , owner, Amount :: ZERO ) . await ;
4246+ let chain_1 = chain_1_desc. id ( ) ;
4247+ let chain_2 = chain_2_desc. id ( ) ;
4248+
4249+ // Simulate a certificate sending two messages from chain_1 to chain_2.
4250+ let sender_hash = CryptoHash :: test_hash ( "sender block" ) ;
4251+
4252+ // Process the second message bundle on chain_2. This advances next_cursor_to_remove
4253+ // to height=0, index=1.
4254+ let block_proposal = make_first_block ( chain_2)
4255+ . with_incoming_bundle ( IncomingBundle {
4256+ origin : chain_1,
4257+ bundle : MessageBundle {
4258+ certificate_hash : sender_hash,
4259+ height : BlockHeight :: ZERO ,
4260+ timestamp : Timestamp :: from ( 0 ) ,
4261+ transaction_index : 1 ,
4262+ messages : vec ! [ system_credit_message( Amount :: from_tokens( 2 ) )
4263+ . to_posted( 0 , MessageKind :: Tracked ) ] ,
4264+ } ,
4265+ action : MessageAction :: Accept ,
4266+ } )
4267+ . into_first_proposal ( owner, & signer)
4268+ . await
4269+ . unwrap ( ) ;
4270+
4271+ let certificate_chain_2 = env. make_certificate ( ConfirmedBlock :: new (
4272+ BlockExecutionOutcome {
4273+ messages : vec ! [ Vec :: new( ) ] ,
4274+ previous_message_blocks : BTreeMap :: new ( ) ,
4275+ previous_event_blocks : BTreeMap :: new ( ) ,
4276+ events : vec ! [ Vec :: new( ) ] ,
4277+ blobs : vec ! [ Vec :: new( ) ] ,
4278+ state_hash : SystemExecutionState {
4279+ balance : Amount :: from_tokens ( 2 ) ,
4280+ ..env. system_execution_state ( & chain_2_desc. id ( ) )
4281+ }
4282+ . into_hash ( )
4283+ . await ,
4284+ oracle_responses : vec ! [ Vec :: new( ) ] ,
4285+ operation_results : vec ! [ ] ,
4286+ }
4287+ . with ( block_proposal. content . block ) ,
4288+ ) ) ;
4289+
4290+ env. worker ( )
4291+ . handle_confirmed_certificate ( certificate_chain_2. clone ( ) , None )
4292+ . await ?;
4293+
4294+ // Now try to stage a block with the earlier message (transaction_index: 0).
4295+ // This should fail with IncorrectMessageOrder because next_cursor_to_remove
4296+ // is now at index 1, but we're trying to process index 0.
4297+ let bad_proposed_block = make_child_block ( & certificate_chain_2. into_value ( ) )
4298+ . with_incoming_bundle ( IncomingBundle {
4299+ origin : chain_1,
4300+ bundle : MessageBundle {
4301+ certificate_hash : sender_hash,
4302+ height : BlockHeight :: ZERO ,
4303+ timestamp : Timestamp :: from ( 0 ) ,
4304+ transaction_index : 0 ,
4305+ messages : vec ! [
4306+ system_credit_message( Amount :: ONE ) . to_posted( 0 , MessageKind :: Tracked )
4307+ ] ,
4308+ } ,
4309+ action : MessageAction :: Accept ,
4310+ } ) ;
4311+
4312+ // Test stage_block_execution directly - this should fail with IncorrectMessageOrder.
4313+ assert_matches ! (
4314+ env. worker( )
4315+ . stage_block_execution( bad_proposed_block. clone( ) , None , vec![ ] )
4316+ . await ,
4317+ Err ( WorkerError :: ChainError ( chain_error) )
4318+ if matches!( * chain_error, ChainError :: IncorrectMessageOrder { .. } )
4319+ ) ;
4320+
4321+ // Also test handle_block_proposal for completeness.
4322+ let bad_proposal = bad_proposed_block
4323+ . into_first_proposal ( owner, & signer)
4324+ . await
4325+ . unwrap ( ) ;
4326+
4327+ assert_matches ! (
4328+ env. worker( ) . handle_block_proposal( bad_proposal) . await ,
4329+ Err ( WorkerError :: ChainError ( chain_error) )
4330+ if matches!( * chain_error, ChainError :: IncorrectMessageOrder { .. } )
4331+ ) ;
4332+
4333+ Ok ( ( ) )
4334+ }
0 commit comments