@@ -35,6 +35,17 @@ const JUSTIFICATION_LOOKBACK_SLOTS: u64 = 3;
3535/// See: https://github.com/lambdaclass/ethlambda/issues/259
3636const MAX_ATTESTATION_PROOF_BYTES : usize = 9 * 1024 * 1024 ;
3737
38+ /// Post-block checkpoints extracted from the state transition in `build_block`.
39+ ///
40+ /// When building a block, the state transition processes attestations that may
41+ /// advance justification/finalization. These checkpoints reflect the post-state
42+ /// values, which the proposer needs for its attestation (since the block hasn't
43+ /// been imported into the store yet).
44+ pub struct PostBlockCheckpoints {
45+ pub justified : Checkpoint ,
46+ pub finalized : Checkpoint ,
47+ }
48+
3849/// Accept new aggregated payloads, promoting them to known for fork choice.
3950fn accept_new_attestations ( store : & mut Store , log_tree : bool ) {
4051 store. promote_new_aggregated_payloads ( ) ;
@@ -633,6 +644,28 @@ fn on_block_core(
633644///
634645/// NOTE: this assumes that we have all the blocks from the head back to the latest finalized.
635646pub fn get_attestation_target ( store : & Store ) -> Checkpoint {
647+ get_attestation_target_with_checkpoints (
648+ store,
649+ store. latest_justified ( ) ,
650+ store. latest_finalized ( ) ,
651+ )
652+ }
653+
654+ /// Calculate target checkpoint using the provided justified and finalized checkpoints
655+ /// instead of reading them from the store.
656+ ///
657+ /// This is needed by the proposer, whose block hasn't been imported yet but whose
658+ /// state transition may have advanced justification/finalization.
659+ ///
660+ /// Note: the walk-back still starts from `store.head()` (the pre-import head), not
661+ /// the new block. This is correct because the new block is only 1 slot ahead with less than 2/3 of votes — the
662+ /// walk-back immediately reaches the same chain. The important fix is using the
663+ /// post-state justified/finalized for the justifiability check and clamping guard.
664+ pub fn get_attestation_target_with_checkpoints (
665+ store : & Store ,
666+ justified : Checkpoint ,
667+ finalized : Checkpoint ,
668+ ) -> Checkpoint {
636669 // Start from current head
637670 let mut target_block_root = store. head ( ) ;
638671 let mut target_header = store
@@ -659,7 +692,7 @@ pub fn get_attestation_target(store: &Store) -> Checkpoint {
659692 }
660693 }
661694
662- let finalized_slot = store . latest_finalized ( ) . slot ;
695+ let finalized_slot = finalized . slot ;
663696
664697 // Ensure target is in justifiable slot range
665698 //
@@ -673,7 +706,7 @@ pub fn get_attestation_target(store: &Store) -> Checkpoint {
673706 . get_block_header ( & target_block_root)
674707 . expect ( "parent block exists" ) ;
675708 }
676- // Guard: clamp target to latest_justified (not in the spec).
709+ // Guard: clamp target to justified (not in the spec).
677710 //
678711 // The spec's walk-back has no lower bound, so it can produce attestations
679712 // where target.slot < source.slot (source = latest_justified). These would
@@ -685,14 +718,13 @@ pub fn get_attestation_target(store: &Store) -> Checkpoint {
685718 // justified checkpoint.
686719 //
687720 // See https://github.com/blockblaz/zeam/blob/697c293879e922942965cdb1da3c6044187ae00e/pkgs/node/src/forkchoice.zig#L654-L659
688- let latest_justified = store. latest_justified ( ) ;
689- if target_header. slot < latest_justified. slot {
721+ if target_header. slot < justified. slot {
690722 warn ! (
691723 target_slot = target_header. slot,
692- justified_slot = latest_justified . slot,
724+ justified_slot = justified . slot,
693725 "Attestation target walked behind justified source, clamping to justified"
694726 ) ;
695- return latest_justified ;
727+ return justified ;
696728 }
697729
698730 Checkpoint {
@@ -769,7 +801,7 @@ pub fn produce_block_with_signatures(
769801 store : & mut Store ,
770802 slot : u64 ,
771803 validator_index : u64 ,
772- ) -> Result < ( Block , Vec < AggregatedSignatureProof > ) , StoreError > {
804+ ) -> Result < ( Block , Vec < AggregatedSignatureProof > , PostBlockCheckpoints ) , StoreError > {
773805 // Get parent block and state to build upon
774806 let head_root = get_proposal_head ( store, slot) ;
775807 let head_state = store
@@ -794,7 +826,7 @@ pub fn produce_block_with_signatures(
794826
795827 let known_block_roots = store. get_block_roots ( ) ;
796828
797- let ( block, signatures) = build_block (
829+ let ( block, signatures, post_checkpoints ) = build_block (
798830 & head_state,
799831 slot,
800832 validator_index,
@@ -803,7 +835,7 @@ pub fn produce_block_with_signatures(
803835 & aggregated_payloads,
804836 ) ?;
805837
806- Ok ( ( block, signatures) )
838+ Ok ( ( block, signatures, post_checkpoints ) )
807839}
808840
809841/// Errors that can occur during Store operations.
@@ -1102,7 +1134,7 @@ fn build_block(
11021134 parent_root : H256 ,
11031135 known_block_roots : & HashSet < H256 > ,
11041136 aggregated_payloads : & HashMap < H256 , ( AttestationData , Vec < AggregatedSignatureProof > ) > ,
1105- ) -> Result < ( Block , Vec < AggregatedSignatureProof > ) , StoreError > {
1137+ ) -> Result < ( Block , Vec < AggregatedSignatureProof > , PostBlockCheckpoints ) , StoreError > {
11061138 let mut aggregated_attestations: Vec < AggregatedAttestation > = Vec :: new ( ) ;
11071139 let mut aggregated_signatures: Vec < AggregatedSignatureProof > = Vec :: new ( ) ;
11081140 let mut accumulated_proof_bytes: usize = 0 ;
@@ -1206,7 +1238,12 @@ fn build_block(
12061238 process_block ( & mut post_state, & final_block) ?;
12071239 final_block. state_root = post_state. hash_tree_root ( ) ;
12081240
1209- Ok ( ( final_block, aggregated_signatures) )
1241+ let post_checkpoints = PostBlockCheckpoints {
1242+ justified : post_state. latest_justified ,
1243+ finalized : post_state. latest_finalized ,
1244+ } ;
1245+
1246+ Ok ( ( final_block, aggregated_signatures, post_checkpoints) )
12101247}
12111248
12121249/// Verify all signatures in a signed block.
@@ -1522,7 +1559,7 @@ mod tests {
15221559 }
15231560
15241561 // Build the block; this should succeed (the bug: no size guard)
1525- let ( block, signatures) = build_block (
1562+ let ( block, signatures, _post_checkpoints ) = build_block (
15261563 & head_state,
15271564 slot,
15281565 proposer_index,
0 commit comments