@@ -3322,6 +3322,164 @@ pub mod tests {
3322
3322
assert ! ( res. unwrap( ) . blocks( ) [ 1 ] . op_code == BillOpCode :: Endorse ) ;
3323
3323
}
3324
3324
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
+
3325
3483
#[ tokio:: test]
3326
3484
async fn endorse_bitcredit_bill_anon_baseline ( ) {
3327
3485
let mut ctx = get_ctx ( ) ;
0 commit comments