@@ -3563,6 +3563,266 @@ impl ApiTester {
3563
3563
self
3564
3564
}
3565
3565
3566
+ pub async fn test_blinded_block_production ( & self ) {
3567
+ let fork = self . chain . canonical_head . cached_head ( ) . head_fork ( ) ;
3568
+ let genesis_validators_root = self . chain . genesis_validators_root ;
3569
+
3570
+ for _ in 0 ..E :: slots_per_epoch ( ) * 3 {
3571
+ let slot = self . chain . slot ( ) . unwrap ( ) ;
3572
+ let epoch = self . chain . epoch ( ) . unwrap ( ) ;
3573
+
3574
+ let proposer_pubkey_bytes = self
3575
+ . client
3576
+ . get_validator_duties_proposer ( epoch)
3577
+ . await
3578
+ . unwrap ( )
3579
+ . data
3580
+ . into_iter ( )
3581
+ . find ( |duty| duty. slot == slot)
3582
+ . map ( |duty| duty. pubkey )
3583
+ . unwrap ( ) ;
3584
+ let proposer_pubkey = ( & proposer_pubkey_bytes) . try_into ( ) . unwrap ( ) ;
3585
+
3586
+ let sk = self
3587
+ . validator_keypairs ( )
3588
+ . iter ( )
3589
+ . find ( |kp| kp. pk == proposer_pubkey)
3590
+ . map ( |kp| kp. sk . clone ( ) )
3591
+ . unwrap ( ) ;
3592
+
3593
+ let randao_reveal = {
3594
+ let domain = self . chain . spec . get_domain (
3595
+ epoch,
3596
+ Domain :: Randao ,
3597
+ & fork,
3598
+ genesis_validators_root,
3599
+ ) ;
3600
+ let message = epoch. signing_root ( domain) ;
3601
+ sk. sign ( message) . into ( )
3602
+ } ;
3603
+
3604
+ let ( response, metadata) = self
3605
+ . client
3606
+ . get_validator_blocks_v3 :: < E > ( slot, & randao_reveal, None , None )
3607
+ . await
3608
+ . unwrap ( ) ;
3609
+
3610
+ match response. data {
3611
+ ProduceBlockV3Response :: Blinded ( blinded_block) => {
3612
+ assert ! ( metadata. execution_payload_blinded) ;
3613
+ assert_eq ! (
3614
+ metadata. consensus_version,
3615
+ blinded_block. to_ref( ) . fork_name( & self . chain. spec) . unwrap( )
3616
+ ) ;
3617
+ let signed_blinded_block =
3618
+ blinded_block. sign ( & sk, & fork, genesis_validators_root, & self . chain . spec ) ;
3619
+
3620
+ self . client
3621
+ . post_beacon_blinded_blocks ( & signed_blinded_block)
3622
+ . await
3623
+ . unwrap ( ) ;
3624
+
3625
+ let head_block = self . chain . head_beacon_block ( ) . clone_as_blinded ( ) ;
3626
+ assert_eq ! ( head_block, signed_blinded_block) ;
3627
+
3628
+ self . chain . slot_clock . set_slot ( slot. as_u64 ( ) + 1 ) ;
3629
+ }
3630
+ ProduceBlockV3Response :: Full ( block_contents) => {
3631
+ assert ! ( !metadata. execution_payload_blinded) ;
3632
+ assert_eq ! (
3633
+ metadata. consensus_version,
3634
+ block_contents
3635
+ . block( )
3636
+ . to_ref( )
3637
+ . fork_name( & self . chain. spec)
3638
+ . unwrap( )
3639
+ ) ;
3640
+ let signed_block_contents =
3641
+ block_contents. sign ( & sk, & fork, genesis_validators_root, & self . chain . spec ) ;
3642
+
3643
+ self . client
3644
+ . post_beacon_blocks ( & signed_block_contents)
3645
+ . await
3646
+ . unwrap ( ) ;
3647
+
3648
+ assert_eq ! (
3649
+ self . chain. head_beacon_block( ) ,
3650
+ * signed_block_contents. signed_block( )
3651
+ ) ;
3652
+
3653
+ self . chain . slot_clock . set_slot ( slot. as_u64 ( ) + 1 ) ;
3654
+ }
3655
+ }
3656
+ }
3657
+ }
3658
+
3659
+ pub async fn test_blinded_block_production_ssz ( & self ) {
3660
+ let fork = self . chain . canonical_head . cached_head ( ) . head_fork ( ) ;
3661
+ let genesis_validators_root = self . chain . genesis_validators_root ;
3662
+
3663
+ for _ in 0 ..E :: slots_per_epoch ( ) * 3 {
3664
+ let slot = self . chain . slot ( ) . unwrap ( ) ;
3665
+ let epoch = self . chain . epoch ( ) . unwrap ( ) ;
3666
+
3667
+ let proposer_pubkey_bytes = self
3668
+ . client
3669
+ . get_validator_duties_proposer ( epoch)
3670
+ . await
3671
+ . unwrap ( )
3672
+ . data
3673
+ . into_iter ( )
3674
+ . find ( |duty| duty. slot == slot)
3675
+ . map ( |duty| duty. pubkey )
3676
+ . unwrap ( ) ;
3677
+ let proposer_pubkey = ( & proposer_pubkey_bytes) . try_into ( ) . unwrap ( ) ;
3678
+
3679
+ let sk = self
3680
+ . validator_keypairs ( )
3681
+ . iter ( )
3682
+ . find ( |kp| kp. pk == proposer_pubkey)
3683
+ . map ( |kp| kp. sk . clone ( ) )
3684
+ . unwrap ( ) ;
3685
+
3686
+ let randao_reveal = {
3687
+ let domain = self . chain . spec . get_domain (
3688
+ epoch,
3689
+ Domain :: Randao ,
3690
+ & fork,
3691
+ genesis_validators_root,
3692
+ ) ;
3693
+ let message = epoch. signing_root ( domain) ;
3694
+ sk. sign ( message) . into ( )
3695
+ } ;
3696
+
3697
+ let block_contents_bytes = self
3698
+ . client
3699
+ . get_validator_blocks_ssz :: < E > ( slot, & randao_reveal, None )
3700
+ . await
3701
+ . unwrap ( )
3702
+ . expect ( "block bytes" ) ;
3703
+
3704
+ let block_contents =
3705
+ FullBlockContents :: < E > :: from_ssz_bytes ( & block_contents_bytes, & self . chain . spec )
3706
+ . expect ( "block contents bytes can be decoded" ) ;
3707
+
3708
+ let signed_block_contents =
3709
+ block_contents. sign ( & sk, & fork, genesis_validators_root, & self . chain . spec ) ;
3710
+
3711
+ self . client
3712
+ . post_beacon_blinded_blocks_ssz (
3713
+ & signed_block_contents. signed_block ( ) . clone_as_blinded ( ) ,
3714
+ )
3715
+ . await
3716
+ . unwrap ( ) ;
3717
+
3718
+ let head_block = self
3719
+ . client
3720
+ . get_beacon_blocks ( CoreBlockId :: Head )
3721
+ . await
3722
+ . unwrap ( )
3723
+ . unwrap ( )
3724
+ . into_data ( ) ;
3725
+
3726
+ let signed_block = signed_block_contents. signed_block ( ) ;
3727
+ assert_eq ! ( head_block, * * signed_block) ;
3728
+
3729
+ self . chain . slot_clock . set_slot ( slot. as_u64 ( ) + 1 ) ;
3730
+ }
3731
+ }
3732
+
3733
+ pub async fn test_blinded_block_production_no_verify_randao ( self ) -> Self {
3734
+ for _ in 0 ..E :: slots_per_epoch ( ) {
3735
+ let slot = self . chain . slot ( ) . unwrap ( ) ;
3736
+
3737
+ let ( response, _metadata) = self
3738
+ . client
3739
+ . get_validator_blocks_v3_modular :: < E > (
3740
+ slot,
3741
+ & Signature :: infinity ( ) . unwrap ( ) . into ( ) ,
3742
+ None ,
3743
+ SkipRandaoVerification :: Yes ,
3744
+ None ,
3745
+ )
3746
+ . await
3747
+ . unwrap ( ) ;
3748
+
3749
+ match response. data {
3750
+ ProduceBlockV3Response :: Blinded ( blinded_block) => {
3751
+ assert_eq ! ( blinded_block. slot( ) , slot) ;
3752
+ }
3753
+ ProduceBlockV3Response :: Full ( full_block) => {
3754
+ assert_eq ! ( full_block. block( ) . slot( ) , slot) ;
3755
+ }
3756
+ }
3757
+
3758
+ self . chain . slot_clock . set_slot ( slot. as_u64 ( ) + 1 ) ;
3759
+ }
3760
+
3761
+ self
3762
+ }
3763
+
3764
+ pub async fn test_blinded_block_production_verify_randao_invalid ( self ) -> Self {
3765
+ let fork = self . chain . canonical_head . cached_head ( ) . head_fork ( ) ;
3766
+ let genesis_validators_root = self . chain . genesis_validators_root ;
3767
+
3768
+ for _ in 0 ..E :: slots_per_epoch ( ) {
3769
+ let slot = self . chain . slot ( ) . unwrap ( ) ;
3770
+ let epoch = self . chain . epoch ( ) . unwrap ( ) ;
3771
+
3772
+ let proposer_pubkey_bytes = self
3773
+ . client
3774
+ . get_validator_duties_proposer ( epoch)
3775
+ . await
3776
+ . unwrap ( )
3777
+ . data
3778
+ . into_iter ( )
3779
+ . find ( |duty| duty. slot == slot)
3780
+ . map ( |duty| duty. pubkey )
3781
+ . unwrap ( ) ;
3782
+ let proposer_pubkey = ( & proposer_pubkey_bytes) . try_into ( ) . unwrap ( ) ;
3783
+
3784
+ let sk = self
3785
+ . validator_keypairs ( )
3786
+ . iter ( )
3787
+ . find ( |kp| kp. pk == proposer_pubkey)
3788
+ . map ( |kp| kp. sk . clone ( ) )
3789
+ . unwrap ( ) ;
3790
+
3791
+ let bad_randao_reveal = {
3792
+ let domain = self . chain . spec . get_domain (
3793
+ epoch,
3794
+ Domain :: Randao ,
3795
+ & fork,
3796
+ genesis_validators_root,
3797
+ ) ;
3798
+ let message = ( epoch + 1 ) . signing_root ( domain) ;
3799
+ sk. sign ( message) . into ( )
3800
+ } ;
3801
+
3802
+ // Check failure with full randao verification enabled.
3803
+ self . client
3804
+ . get_validator_blocks_v3 :: < E > ( slot, & bad_randao_reveal, None , None )
3805
+ . await
3806
+ . unwrap_err ( ) ;
3807
+
3808
+ // Check failure with `skip_randao_verification` (requires infinity sig).
3809
+ self . client
3810
+ . get_validator_blocks_v3_modular :: < E > (
3811
+ slot,
3812
+ & bad_randao_reveal,
3813
+ None ,
3814
+ SkipRandaoVerification :: Yes ,
3815
+ None ,
3816
+ )
3817
+ . await
3818
+ . unwrap_err ( ) ;
3819
+
3820
+ self . chain . slot_clock . set_slot ( slot. as_u64 ( ) + 1 ) ;
3821
+ }
3822
+
3823
+ self
3824
+ }
3825
+
3566
3826
pub async fn test_get_validator_attestation_data ( self ) -> Self {
3567
3827
let mut state = self . chain . head_beacon_state_cloned ( ) ;
3568
3828
let slot = state. slot ( ) ;
@@ -4256,7 +4516,7 @@ impl ApiTester {
4256
4516
ProduceBlockV3Response :: Blinded ( payload) => {
4257
4517
payload. body ( ) . execution_payload ( ) . unwrap ( ) . into ( )
4258
4518
}
4259
- ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a full payload" ) ,
4519
+ ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a blinded payload" ) ,
4260
4520
} ;
4261
4521
4262
4522
let expected_fee_recipient = Address :: from_low_u64_be ( proposer_index) ;
@@ -4429,7 +4689,7 @@ impl ApiTester {
4429
4689
ProduceBlockV3Response :: Blinded ( payload) => {
4430
4690
payload. body ( ) . execution_payload ( ) . unwrap ( ) . into ( )
4431
4691
}
4432
- ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a full payload" ) ,
4692
+ ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a blinded payload" ) ,
4433
4693
} ;
4434
4694
4435
4695
assert_eq ! ( payload. fee_recipient( ) , test_fee_recipient) ;
@@ -5021,7 +5281,7 @@ impl ApiTester {
5021
5281
ProduceBlockV3Response :: Blinded ( payload) => {
5022
5282
payload. body ( ) . execution_payload ( ) . unwrap ( ) . into ( )
5023
5283
}
5024
- ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a full payload" ) ,
5284
+ ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a blinded payload" ) ,
5025
5285
} ;
5026
5286
5027
5287
// This cache should not be populated because fallback should not have been used.
@@ -5210,7 +5470,7 @@ impl ApiTester {
5210
5470
ProduceBlockV3Response :: Blinded ( payload) => {
5211
5471
payload. body ( ) . execution_payload ( ) . unwrap ( ) . into ( )
5212
5472
}
5213
- ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a full payload" ) ,
5473
+ ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a blinded payload" ) ,
5214
5474
} ;
5215
5475
5216
5476
// This cache should not be populated because fallback should not have been used.
@@ -5625,7 +5885,7 @@ impl ApiTester {
5625
5885
ProduceBlockV3Response :: Blinded ( payload) => {
5626
5886
payload. body ( ) . execution_payload ( ) . unwrap ( ) . into ( )
5627
5887
}
5628
- ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a full payload" ) ,
5888
+ ProduceBlockV3Response :: Full ( _) => panic ! ( "Expecting a blinded payload" ) ,
5629
5889
} ;
5630
5890
5631
5891
// The builder's payload should've been chosen, so this cache should not be populated
@@ -6969,6 +7229,83 @@ async fn block_production_v3_ssz_with_skip_slots() {
6969
7229
. await ;
6970
7230
}
6971
7231
7232
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7233
+ async fn blinded_block_production_full_payload_premerge ( ) {
7234
+ ApiTester :: new ( ) . await . test_blinded_block_production ( ) . await ;
7235
+ }
7236
+
7237
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7238
+ async fn blinded_block_production_ssz_full_payload_premerge ( ) {
7239
+ ApiTester :: new ( )
7240
+ . await
7241
+ . test_blinded_block_production_ssz ( )
7242
+ . await ;
7243
+ }
7244
+
7245
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7246
+ async fn blinded_block_production_with_skip_slots_full_payload_premerge ( ) {
7247
+ ApiTester :: new ( )
7248
+ . await
7249
+ . skip_slots ( E :: slots_per_epoch ( ) * 2 )
7250
+ . test_blinded_block_production ( )
7251
+ . await ;
7252
+ }
7253
+
7254
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7255
+ async fn blinded_block_production_ssz_with_skip_slots_full_payload_premerge ( ) {
7256
+ ApiTester :: new ( )
7257
+ . await
7258
+ . skip_slots ( E :: slots_per_epoch ( ) * 2 )
7259
+ . test_blinded_block_production_ssz ( )
7260
+ . await ;
7261
+ }
7262
+
7263
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7264
+ async fn blinded_block_production_no_verify_randao_full_payload_premerge ( ) {
7265
+ ApiTester :: new ( )
7266
+ . await
7267
+ . test_blinded_block_production_no_verify_randao ( )
7268
+ . await ;
7269
+ }
7270
+
7271
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7272
+ async fn blinded_block_production_verify_randao_invalid_full_payload_premerge ( ) {
7273
+ ApiTester :: new ( )
7274
+ . await
7275
+ . test_blinded_block_production_verify_randao_invalid ( )
7276
+ . await ;
7277
+ }
7278
+
7279
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7280
+ async fn blinded_block_production_blinded_payload_premerge ( ) {
7281
+ ApiTester :: new ( ) . await . test_blinded_block_production ( ) . await ;
7282
+ }
7283
+
7284
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7285
+ async fn blinded_block_production_with_skip_slots_blinded_payload_premerge ( ) {
7286
+ ApiTester :: new ( )
7287
+ . await
7288
+ . skip_slots ( E :: slots_per_epoch ( ) * 2 )
7289
+ . test_blinded_block_production ( )
7290
+ . await ;
7291
+ }
7292
+
7293
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7294
+ async fn blinded_block_production_no_verify_randao_blinded_payload_premerge ( ) {
7295
+ ApiTester :: new ( )
7296
+ . await
7297
+ . test_blinded_block_production_no_verify_randao ( )
7298
+ . await ;
7299
+ }
7300
+
7301
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
7302
+ async fn blinded_block_production_verify_randao_invalid_blinded_payload_premerge ( ) {
7303
+ ApiTester :: new ( )
7304
+ . await
7305
+ . test_blinded_block_production_verify_randao_invalid ( )
7306
+ . await ;
7307
+ }
7308
+
6972
7309
#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
6973
7310
async fn get_validator_attestation_data ( ) {
6974
7311
ApiTester :: new ( )
0 commit comments