@@ -7,7 +7,7 @@ use crate::attestation::AggregatedAttestation;
77use crate :: car:: Car ;
88use alloy_primitives:: Address ;
99use cipherbft_crypto:: BlsPublicKey ;
10- use cipherbft_types:: { Hash , ValidatorId } ;
10+ use cipherbft_types:: { Hash , ValidatorId , VALIDATOR_ID_SIZE } ;
1111use serde:: { Deserialize , Serialize } ;
1212use std:: collections:: HashMap ;
1313
@@ -95,8 +95,8 @@ impl Cut {
9595 // Number of Cars
9696 data. extend_from_slice ( & ( self . cars . len ( ) as u32 ) . to_be_bytes ( ) ) ;
9797
98- // Cars in deterministic order (ValidatorId ascending)
99- for ( _, car) in self . ordered_cars ( ) {
98+ // Cars in deterministic order (ValidatorId ascending for hash stability )
99+ for ( _, car) in self . ordered_cars ( None ) {
100100 // Include Car hash and position
101101 data. extend_from_slice ( car. hash ( ) . as_bytes ( ) ) ;
102102 data. extend_from_slice ( & car. position . to_be_bytes ( ) ) ;
@@ -120,19 +120,44 @@ impl Cut {
120120 self . attestations . insert ( car_hash, attestation) ;
121121 }
122122
123- /// Iterate Cars in deterministic order (ValidatorId ascending)
123+ /// Iterate Cars in deterministic order.
124124 ///
125- /// This ensures all validators process transactions in the same order
126- /// for deterministic deduplication.
127- pub fn ordered_cars ( & self ) -> impl Iterator < Item = ( & ValidatorId , & Car ) > {
125+ /// # Arguments
126+ /// * `parent_hash` - If `Some`, uses XOR-based fair ordering for execution.
127+ /// If `None`, uses ValidatorId ascending order for hashing.
128+ ///
129+ /// # Fair Ordering
130+ /// When `parent_hash` is provided, the sort key is:
131+ /// `validator_id[0..20] XOR parent_hash[0..20]`
132+ ///
133+ /// This ensures:
134+ /// - Deterministic ordering (all validators compute same order)
135+ /// - Unpredictable ordering (depends on previous block)
136+ /// - Fair rotation (no validator consistently first)
137+ pub fn ordered_cars (
138+ & self ,
139+ parent_hash : Option < & Hash > ,
140+ ) -> impl Iterator < Item = ( & ValidatorId , & Car ) > {
128141 let mut entries: Vec < _ > = self . cars . iter ( ) . collect ( ) ;
129- entries. sort_by_key ( |( vid, _) | * vid) ;
130- entries. into_iter ( )
131- }
132142
133- /// Get Cars as ordered Vec (for serialization)
134- pub fn ordered_cars_vec ( & self ) -> Vec < ( & ValidatorId , & Car ) > {
135- self . ordered_cars ( ) . collect ( )
143+ match parent_hash {
144+ Some ( hash) => {
145+ let hash_bytes = hash. as_bytes ( ) ;
146+ entries. sort_by_key ( |( vid, _) | {
147+ let vid_bytes = vid. as_bytes ( ) ;
148+ let mut sort_key = [ 0u8 ; VALIDATOR_ID_SIZE ] ;
149+ for i in 0 ..VALIDATOR_ID_SIZE {
150+ sort_key[ i] = vid_bytes[ i] ^ hash_bytes[ i] ;
151+ }
152+ sort_key
153+ } ) ;
154+ }
155+ None => {
156+ entries. sort_by_key ( |( vid, _) | * vid) ;
157+ }
158+ }
159+
160+ entries. into_iter ( )
136161 }
137162
138163 /// Total transaction count across all Cars
@@ -274,7 +299,7 @@ impl Cut {
274299 proposer_address : self . proposer_address ,
275300 } ;
276301
277- let car_parts = self . ordered_cars ( ) . filter_map ( move |( _, car) | {
302+ let car_parts = self . ordered_cars ( None ) . filter_map ( move |( _, car) | {
278303 let car_hash = car. hash ( ) ;
279304 self . attestations
280305 . get ( & car_hash)
@@ -577,8 +602,8 @@ mod tests {
577602 cut. cars . insert ( car2. proposer , car2. clone ( ) ) ;
578603 cut. cars . insert ( car3. proposer , car3. clone ( ) ) ;
579604
580- // ordered_cars should return in ValidatorId order
581- let ordered: Vec < _ > = cut. ordered_cars ( ) . collect ( ) ;
605+ // ordered_cars(None) should return in ValidatorId order
606+ let ordered: Vec < _ > = cut. ordered_cars ( None ) . collect ( ) ;
582607 assert_eq ! ( ordered. len( ) , 3 ) ;
583608
584609 // Verify ordering
@@ -1006,4 +1031,135 @@ mod tests {
10061031 }
10071032 panic ! ( "cut was not assembled" ) ;
10081033 }
1034+
1035+ // ============ Fair Ordering Tests ============
1036+
1037+ #[ test]
1038+ fn test_ordered_cars_fair_differs_from_id_ordering ( ) {
1039+ // Create validators with varied byte patterns to ensure XOR produces different order
1040+ // v1: starts with 0x10, v2: starts with 0x20, v3: starts with 0x30
1041+ let mut v1_bytes = [ 0u8 ; VALIDATOR_ID_SIZE ] ;
1042+ let mut v2_bytes = [ 0u8 ; VALIDATOR_ID_SIZE ] ;
1043+ let mut v3_bytes = [ 0u8 ; VALIDATOR_ID_SIZE ] ;
1044+
1045+ v1_bytes[ 0 ] = 0x10 ;
1046+ v1_bytes[ 1 ] = 0xaa ;
1047+ v2_bytes[ 0 ] = 0x20 ;
1048+ v2_bytes[ 1 ] = 0xbb ;
1049+ v3_bytes[ 0 ] = 0x30 ;
1050+ v3_bytes[ 1 ] = 0xcc ;
1051+
1052+ let v1 = ValidatorId :: from_bytes ( v1_bytes) ;
1053+ let v2 = ValidatorId :: from_bytes ( v2_bytes) ;
1054+ let v3 = ValidatorId :: from_bytes ( v3_bytes) ;
1055+
1056+ let mut cut = Cut :: new ( 1 ) ;
1057+ cut. cars . insert ( v1, Car :: new ( v1, 0 , vec ! [ ] , None ) ) ;
1058+ cut. cars . insert ( v2, Car :: new ( v2, 0 , vec ! [ ] , None ) ) ;
1059+ cut. cars . insert ( v3, Car :: new ( v3, 0 , vec ! [ ] , None ) ) ;
1060+
1061+ // With None, should be ValidatorId order: v1 < v2 < v3
1062+ let ordered_none: Vec < _ > = cut. ordered_cars ( None ) . map ( |( vid, _) | * vid) . collect ( ) ;
1063+ assert_eq ! ( ordered_none, vec![ v1, v2, v3] ) ;
1064+
1065+ // Use a hash that will shuffle the order: 0x25 XOR 0x10=0x35, 0x25 XOR 0x20=0x05, 0x25 XOR 0x30=0x15
1066+ // Expected order after XOR: v2 (0x05) < v3 (0x15) < v1 (0x35)
1067+ let mut hash_bytes = [ 0u8 ; 32 ] ;
1068+ hash_bytes[ 0 ] = 0x25 ;
1069+ hash_bytes[ 1 ] = 0x00 ;
1070+ let parent_hash = Hash :: from_bytes ( hash_bytes) ;
1071+
1072+ let ordered_fair: Vec < _ > = cut
1073+ . ordered_cars ( Some ( & parent_hash) )
1074+ . map ( |( vid, _) | * vid)
1075+ . collect ( ) ;
1076+
1077+ // v2 XOR 0x25 = 0x05, v3 XOR 0x25 = 0x15, v1 XOR 0x25 = 0x35
1078+ // So order should be: v2, v3, v1
1079+ assert_eq ! (
1080+ ordered_fair,
1081+ vec![ v2, v3, v1] ,
1082+ "XOR should reorder validators"
1083+ ) ;
1084+ }
1085+
1086+ #[ test]
1087+ fn test_ordered_cars_fair_is_deterministic ( ) {
1088+ let v1 = ValidatorId :: from_bytes ( [ 0x10 ; VALIDATOR_ID_SIZE ] ) ;
1089+ let v2 = ValidatorId :: from_bytes ( [ 0x20 ; VALIDATOR_ID_SIZE ] ) ;
1090+
1091+ let mut cut = Cut :: new ( 1 ) ;
1092+ cut. cars . insert ( v1, Car :: new ( v1, 0 , vec ! [ ] , None ) ) ;
1093+ cut. cars . insert ( v2, Car :: new ( v2, 0 , vec ! [ ] , None ) ) ;
1094+
1095+ let parent_hash = Hash :: compute ( b"deterministic_test" ) ;
1096+
1097+ // Same inputs should produce same order
1098+ let order1: Vec < _ > = cut
1099+ . ordered_cars ( Some ( & parent_hash) )
1100+ . map ( |( vid, _) | * vid)
1101+ . collect ( ) ;
1102+ let order2: Vec < _ > = cut
1103+ . ordered_cars ( Some ( & parent_hash) )
1104+ . map ( |( vid, _) | * vid)
1105+ . collect ( ) ;
1106+
1107+ assert_eq ! ( order1, order2, "Same parent_hash must produce same order" ) ;
1108+ }
1109+
1110+ #[ test]
1111+ fn test_ordered_cars_fair_rotation ( ) {
1112+ // Different parent hashes should produce different orderings
1113+ let v1 = ValidatorId :: from_bytes ( [ 0xaa ; VALIDATOR_ID_SIZE ] ) ;
1114+ let v2 = ValidatorId :: from_bytes ( [ 0xbb ; VALIDATOR_ID_SIZE ] ) ;
1115+ let v3 = ValidatorId :: from_bytes ( [ 0xcc ; VALIDATOR_ID_SIZE ] ) ;
1116+
1117+ let mut cut = Cut :: new ( 1 ) ;
1118+ cut. cars . insert ( v1, Car :: new ( v1, 0 , vec ! [ ] , None ) ) ;
1119+ cut. cars . insert ( v2, Car :: new ( v2, 0 , vec ! [ ] , None ) ) ;
1120+ cut. cars . insert ( v3, Car :: new ( v3, 0 , vec ! [ ] , None ) ) ;
1121+
1122+ let hash1 = Hash :: compute ( b"block_1" ) ;
1123+ let hash2 = Hash :: compute ( b"block_2" ) ;
1124+ let hash3 = Hash :: compute ( b"block_3" ) ;
1125+
1126+ let order1: Vec < _ > = cut
1127+ . ordered_cars ( Some ( & hash1) )
1128+ . map ( |( vid, _) | * vid)
1129+ . collect ( ) ;
1130+ let order2: Vec < _ > = cut
1131+ . ordered_cars ( Some ( & hash2) )
1132+ . map ( |( vid, _) | * vid)
1133+ . collect ( ) ;
1134+ let order3: Vec < _ > = cut
1135+ . ordered_cars ( Some ( & hash3) )
1136+ . map ( |( vid, _) | * vid)
1137+ . collect ( ) ;
1138+
1139+ // At least one ordering should differ (very high probability)
1140+ let all_same = order1 == order2 && order2 == order3;
1141+ assert ! (
1142+ !all_same,
1143+ "Different parent hashes should produce different orderings"
1144+ ) ;
1145+ }
1146+
1147+ #[ test]
1148+ fn test_ordered_cars_none_preserves_backward_compat ( ) {
1149+ // Regression test: ordered_cars(None) must match original behavior
1150+ let v_low = ValidatorId :: from_bytes ( [ 0x00 ; VALIDATOR_ID_SIZE ] ) ;
1151+ let v_mid = ValidatorId :: from_bytes ( [ 0x80 ; VALIDATOR_ID_SIZE ] ) ;
1152+ let v_high = ValidatorId :: from_bytes ( [ 0xff ; VALIDATOR_ID_SIZE ] ) ;
1153+
1154+ let mut cut = Cut :: new ( 1 ) ;
1155+ // Insert in random order
1156+ cut. cars . insert ( v_high, Car :: new ( v_high, 0 , vec ! [ ] , None ) ) ;
1157+ cut. cars . insert ( v_low, Car :: new ( v_low, 0 , vec ! [ ] , None ) ) ;
1158+ cut. cars . insert ( v_mid, Car :: new ( v_mid, 0 , vec ! [ ] , None ) ) ;
1159+
1160+ let ordered: Vec < _ > = cut. ordered_cars ( None ) . map ( |( vid, _) | * vid) . collect ( ) ;
1161+
1162+ // Must be ascending ValidatorId order
1163+ assert_eq ! ( ordered, vec![ v_low, v_mid, v_high] ) ;
1164+ }
10091165}
0 commit comments