@@ -3322,6 +3322,164 @@ pub mod tests {
33223322 assert ! ( res. unwrap( ) . blocks( ) [ 1 ] . op_code == BillOpCode :: Endorse ) ;
33233323 }
33243324
3325+ #[ tokio:: test]
3326+ async fn endorse_bitcredit_bill_multiple_back_and_forth ( ) {
3327+ let identity = get_baseline_identity ( ) ;
3328+ let mut bill = get_baseline_bill ( & bill_id_test ( ) ) ;
3329+ bill. payee = BillParticipant :: Ident ( bill_identified_participant_only_node_id (
3330+ identity. identity . node_id . clone ( ) ,
3331+ ) ) ;
3332+
3333+ let party2_keys = BcrKeys :: new ( ) ;
3334+ let party2_node_id = NodeId :: new ( party2_keys. pub_key ( ) , bitcoin:: Network :: Testnet ) ;
3335+ let party2_participant = bill_identified_participant_only_node_id ( party2_node_id. clone ( ) ) ;
3336+
3337+ let mut current_chain = get_genesis_chain ( Some ( bill. clone ( ) ) ) ;
3338+ let mut current_timestamp = 1731593928u64 ;
3339+
3340+ for i in 0 ..10 {
3341+ let is_even = i % 2 == 0 ;
3342+
3343+ let mut ctx = get_ctx ( ) ;
3344+ ctx. bill_store
3345+ . expect_save_bill_to_cache ( )
3346+ . returning ( |_, _, _| Ok ( ( ) ) )
3347+ . times ( 1 ) ;
3348+ ctx. bill_store . expect_exists ( ) . returning ( |_| Ok ( true ) ) ;
3349+
3350+ let chain_for_ctx = current_chain. clone ( ) ;
3351+ ctx. bill_blockchain_store
3352+ . expect_get_chain ( )
3353+ . returning ( move |_| Ok ( chain_for_ctx. clone ( ) ) ) ;
3354+
3355+ ctx. notification_service
3356+ . expect_send_bill_is_endorsed_event ( )
3357+ . returning ( |_| Ok ( ( ) ) ) ;
3358+
3359+ expect_populates_identity_block ( & mut ctx) ;
3360+
3361+ let service = get_service ( ctx) ;
3362+
3363+ // Determine endorser and endorsee based on iteration
3364+ let ( endorser_participant, endorser_keys, endorsee_participant) = if is_even {
3365+ // Even iterations: Party1 endorses to Party2
3366+ (
3367+ BillParticipant :: Ident (
3368+ BillIdentParticipant :: new ( identity. identity . clone ( ) ) . unwrap ( ) ,
3369+ ) ,
3370+ & identity. key_pair ,
3371+ BillParticipant :: Ident ( party2_participant. clone ( ) ) ,
3372+ )
3373+ } else {
3374+ // Odd iterations: Party2 endorses to Party1
3375+ (
3376+ BillParticipant :: Ident ( party2_participant. clone ( ) ) ,
3377+ & party2_keys,
3378+ BillParticipant :: Ident (
3379+ BillIdentParticipant :: new ( identity. identity . clone ( ) ) . unwrap ( ) ,
3380+ ) ,
3381+ )
3382+ } ;
3383+
3384+ // Execute the endorsement
3385+ let result = service
3386+ . execute_bill_action (
3387+ & bill_id_test ( ) ,
3388+ BillAction :: Endorse ( endorsee_participant) ,
3389+ & endorser_participant,
3390+ endorser_keys,
3391+ current_timestamp,
3392+ )
3393+ . await ;
3394+
3395+ assert ! ( result. is_ok( ) , "Endorsement {} failed" , i + 1 ) ;
3396+ current_chain = result. unwrap ( ) ;
3397+
3398+ // Verify the chain grows by one block each time
3399+ // The chain should have 1 genesis block plus (i+1) endorsement blocks after each iteration.
3400+ assert_eq ! ( current_chain. blocks( ) . len( ) , 1 + ( i + 1 ) ) ; // Genesis + (i+1) endorsements
3401+ assert_eq ! (
3402+ current_chain. blocks( ) . last( ) . unwrap( ) . op_code,
3403+ BillOpCode :: Endorse
3404+ ) ;
3405+
3406+ // Verify timestamp ordering: the new block's timestamp should be >= previous block's timestamp
3407+ let blocks = current_chain. blocks ( ) ;
3408+ let new_block_index = blocks. len ( ) - 1 ;
3409+ if new_block_index > 0 {
3410+ let prev_timestamp = blocks[ new_block_index - 1 ] . timestamp ;
3411+ let curr_timestamp = blocks[ new_block_index] . timestamp ;
3412+ assert ! (
3413+ curr_timestamp >= prev_timestamp,
3414+ "Block {} timestamp ({}) is before previous block {} timestamp ({})" ,
3415+ new_block_index,
3416+ curr_timestamp,
3417+ new_block_index - 1 ,
3418+ prev_timestamp
3419+ ) ;
3420+ }
3421+
3422+ current_timestamp += 1 ;
3423+ }
3424+
3425+ // Create a final context to verify the end state
3426+ let mut final_ctx = get_ctx ( ) ;
3427+ final_ctx. bill_store . expect_exists ( ) . returning ( |_| Ok ( true ) ) ;
3428+
3429+ let final_chain = current_chain. clone ( ) ;
3430+ final_ctx
3431+ . bill_blockchain_store
3432+ . expect_get_chain ( )
3433+ . returning ( move |_| Ok ( final_chain. clone ( ) ) ) ;
3434+
3435+ final_ctx
3436+ . notification_service
3437+ . expect_get_active_bill_notification ( )
3438+ . returning ( |_| None ) ;
3439+
3440+ let final_service = get_service ( final_ctx) ;
3441+
3442+ // After back-and-forth endorsements, the bill should be back with Party1
3443+ // (start with Party1 and do an even number of transfers)
3444+ let bill_detail = final_service
3445+ . get_detail (
3446+ & bill_id_test ( ) ,
3447+ & identity. identity ,
3448+ & identity. identity . node_id ,
3449+ current_timestamp,
3450+ )
3451+ . await ;
3452+ assert ! ( bill_detail. is_ok( ) ) ;
3453+
3454+ let bill_result = bill_detail. unwrap ( ) ;
3455+ assert_eq ! (
3456+ bill_result
3457+ . participants
3458+ . endorsee
3459+ . as_ref( )
3460+ . unwrap( )
3461+ . node_id( ) ,
3462+ identity. identity. node_id,
3463+ ) ;
3464+
3465+ // Verify the endorsement chain
3466+ let endorsements = final_service
3467+ . get_endorsements ( & bill_id_test ( ) , & identity. identity . node_id )
3468+ . await ;
3469+ assert ! ( endorsements. is_ok( ) ) ;
3470+
3471+ // Should have 10 endorsements total
3472+ assert_eq ! ( endorsements. as_ref( ) . unwrap( ) . len( ) , 10 ) ;
3473+
3474+ // The most recent endorsement should be back to party1 (identity)
3475+ assert_eq ! (
3476+ endorsements. as_ref( ) . unwrap( ) [ 0 ]
3477+ . pay_to_the_order_of
3478+ . node_id,
3479+ identity. identity. node_id
3480+ ) ;
3481+ }
3482+
33253483 #[ tokio:: test]
33263484 async fn endorse_bitcredit_bill_anon_baseline ( ) {
33273485 let mut ctx = get_ctx ( ) ;
0 commit comments