@@ -702,9 +702,9 @@ pub(crate) async fn collect_follower_settlement_signatures<P>(
702702
703703/// Import live/backfilled blocks in canonical order on a follower.
704704///
705- /// Live blocks are only imported from the leader when the sender equals
706- /// `schedule.leader_for(the block's embedded anchor)`. We do this because if there are accidentally
707- /// two leaders (split brain) for a block, we need to decide to import the correct one .
705+ /// Live full blocks are only imported when the sender equals the leader for their imported Tempo
706+ /// header. Checkpoint-only blocks have no designated leader and may be imported from any authorized
707+ /// P2P peer. The full- block check resolves accidental split brain using the finalized schedule .
708708///
709709/// Backfilled blocks carry no producer claim and are judged by
710710/// parent/anchor/execution/conflict validation alone. The loop exits when `stop` fires.
@@ -1161,22 +1161,21 @@ where
11611161 let headers = tempo_import. headers ( ) ;
11621162 validate_l1_checkpoint_range ( headers, local. number , local. hash , block_number) ?;
11631163 let anchor = headers. last ( ) . expect ( "validated nonempty range" ) . num_hash ( ) ;
1164- let production_anchor = tempo_import. production_anchor ( ) ;
1164+ let leader_anchor = tempo_import. leader_anchor ( ) ;
11651165
1166- // Anchor-aware fence for live blocks: a full block has one imported header, while a
1167- // checkpoint-only block may cross leadership boundaries and is assigned to the leader of its
1168- // first imported header. This gives every live block one producer without making a later
1169- // transition inside a checkpoint range invalidate the block.
1166+ // Anchor-aware fence for live full blocks. Checkpoint-only blocks are leader-neutral and may
1167+ // cross leadership boundaries; leader authority resumes at the final full block and is checked
1168+ // against that block's single imported Tempo header.
11701169 // Backfilled blocks carry no producer claim and are judged by parent/anchor/execution/conflict
11711170 // validation only.
11721171 //
11731172 // Check once before waiting to reject an already-known invalid sender without blocking the
1174- // import loop, then again after observing every imported header. The first header itself may
1175- // finalize a leadership transition that changes its assigned producer.
1176- validate_live_block_sender (
1173+ // import loop, then again after observing every imported header. The full block's imported
1174+ // header may itself finalize a leadership transition that changes its assigned producer.
1175+ validate_live_import_sender (
11771176 schedule,
11781177 peer_block. live_sender . as_ref ( ) ,
1179- production_anchor ,
1178+ leader_anchor ,
11801179 block_number,
11811180 ) ?;
11821181 let mut observed_headers = Vec :: with_capacity ( headers. len ( ) ) ;
@@ -1207,10 +1206,10 @@ where
12071206 } ;
12081207 observed_headers. push ( observed) ;
12091208 }
1210- validate_live_block_sender (
1209+ validate_live_import_sender (
12111210 schedule,
12121211 peer_block. live_sender . as_ref ( ) ,
1213- production_anchor ,
1212+ leader_anchor ,
12141213 block_number,
12151214 ) ?;
12161215
@@ -1277,7 +1276,7 @@ where
12771276 }
12781277 schedule. record_applied_anchor ( anchor. number ) ;
12791278
1280- info ! ( target: "zone::p2p" , block_number, ?hash, "Imported canonical leader block" ) ;
1279+ info ! ( target: "zone::p2p" , block_number, ?hash, "Imported canonical peer block" ) ;
12811280 Ok ( PeerBlockImportOutcome :: Imported )
12821281}
12831282
@@ -1305,6 +1304,18 @@ fn validate_live_block_sender(
13051304 }
13061305}
13071306
1307+ fn validate_live_import_sender (
1308+ schedule : & LeadershipSchedule ,
1309+ live_sender : Option < & P2pPeerId > ,
1310+ leader_anchor : Option < u64 > ,
1311+ block_number : u64 ,
1312+ ) -> eyre:: Result < ( ) > {
1313+ let Some ( anchor_number) = leader_anchor else {
1314+ return Ok ( ( ) ) ;
1315+ } ;
1316+ validate_live_block_sender ( schedule, live_sender, anchor_number, block_number)
1317+ }
1318+
13081319fn reconcile_canonical_import (
13091320 deposit_queue : & DepositQueue ,
13101321 tempo_import : & DecodedTempoImport ,
@@ -1511,15 +1522,15 @@ impl DecodedTempoImport {
15111522 }
15121523 }
15131524
1514- /// Tempo anchor whose leader is the unique live producer for this Zone block.
1525+ /// Tempo anchor whose leader must produce this Zone block.
15151526 ///
1516- /// A checkpoint -only block may cross later leadership boundaries, so its first imported
1517- /// header selects the producer. A full block contains one header and follows the same rule .
1518- fn production_anchor ( & self ) -> u64 {
1519- self . headers ( )
1520- . first ( )
1521- . expect ( "decoded Tempo imports are validated as nonempty" )
1522- . number ( )
1527+ /// Checkpoint -only blocks have no designated leader. A full block imports exactly one Tempo
1528+ /// header, whose effective leader supplies its production authority .
1529+ fn leader_anchor ( & self ) -> Option < u64 > {
1530+ match self {
1531+ Self :: Full { header , .. } => Some ( header . number ( ) ) ,
1532+ Self :: CheckpointOnly { .. } => None ,
1533+ }
15231534 }
15241535}
15251536
@@ -1993,7 +2004,7 @@ mod tests {
19932004 }
19942005
19952006 #[ test]
1996- fn checkpoint_live_producer_is_leader_of_first_imported_anchor ( ) {
2007+ fn checkpoint_live_producer_is_not_leader_restricted ( ) {
19972008 use commonware_cryptography:: { Signer as _, ed25519:: PrivateKey } ;
19982009 use reth_primitives_traits:: SealedHeader ;
19992010 use tempo_primitives:: TempoHeader ;
@@ -2018,14 +2029,29 @@ mod tests {
20182029 . to_vec ( ) ;
20192030 let tempo_import = super :: DecodedTempoImport :: CheckpointOnly { headers } ;
20202031
2021- let production_anchor = tempo_import. production_anchor ( ) ;
2022- assert_eq ! ( production_anchor, 90 ) ;
2023- validate_live_block_sender ( & schedule, Some ( & outgoing) , production_anchor, 7 ) . unwrap ( ) ;
2024- let error = validate_live_block_sender ( & schedule, Some ( & incoming) , production_anchor, 7 )
2025- . expect_err (
2026- "the later epoch leader must not produce a range beginning in the old epoch" ,
2027- ) ;
2028- assert ! ( error. to_string( ) . contains( & outgoing. to_string( ) ) ) ;
2032+ let leader_anchor = tempo_import. leader_anchor ( ) ;
2033+ assert_eq ! ( leader_anchor, None ) ;
2034+ super :: validate_live_import_sender ( & schedule, Some ( & outgoing) , leader_anchor, 7 ) . unwrap ( ) ;
2035+ super :: validate_live_import_sender ( & schedule, Some ( & incoming) , leader_anchor, 7 ) . unwrap ( ) ;
2036+
2037+ let full_import = super :: DecodedTempoImport :: Full {
2038+ header : Box :: new ( SealedHeader :: seal_slow ( TempoHeader {
2039+ inner : alloy_consensus:: Header {
2040+ number : 110 ,
2041+ ..Default :: default ( )
2042+ } ,
2043+ ..Default :: default ( )
2044+ } ) ) ,
2045+ deposits : Vec :: new ( ) ,
2046+ enabled_tokens : Vec :: new ( ) ,
2047+ } ;
2048+ let leader_anchor = full_import. leader_anchor ( ) ;
2049+ assert_eq ! ( leader_anchor, Some ( 110 ) ) ;
2050+ super :: validate_live_import_sender ( & schedule, Some ( & incoming) , leader_anchor, 8 ) . unwrap ( ) ;
2051+ let error =
2052+ super :: validate_live_import_sender ( & schedule, Some ( & outgoing) , leader_anchor, 8 )
2053+ . expect_err ( "the final full block must be produced by its effective leader" ) ;
2054+ assert ! ( error. to_string( ) . contains( & incoming. to_string( ) ) ) ;
20292055 }
20302056
20312057 #[ tokio:: test]
0 commit comments