@@ -48,6 +48,14 @@ pub enum Error {
4848 index : usize ,
4949 validator_count : usize ,
5050 } ,
51+ #[ error(
52+ "justified slot {slot} is outside the tracked range (finalized_boundary={finalized_slot}, tracked_length={tracked_length})"
53+ ) ]
54+ JustifiedSlotOutOfRange {
55+ slot : u64 ,
56+ finalized_slot : u64 ,
57+ tracked_length : usize ,
58+ } ,
5159}
5260
5361/// Transition the given pre-state to the block's post-state.
@@ -309,7 +317,7 @@ fn process_attestations(
309317 let source = attestation_data. source ;
310318 let target = attestation_data. target ;
311319
312- if !is_valid_vote ( state, attestation_data) {
320+ if !is_valid_vote ( state, attestation_data) ? {
313321 continue ;
314322 }
315323
@@ -392,7 +400,15 @@ fn process_attestations(
392400/// rejects zero-hash source or target roots)
393401/// 4. Target slot > source slot
394402/// 5. Target slot is justifiable after the finalized slot
395- fn is_valid_vote ( state : & State , data : & AttestationData ) -> bool {
403+ ///
404+ /// A failed check drops the vote and leaves the block valid, matching the
405+ /// spec's `continue` semantics. The exception is a source or target slot past
406+ /// the tracked justification window: that has no justification status to read
407+ /// at all, so it invalidates the whole block via `JustifiedSlotOutOfRange`
408+ /// (leanSpec #1023). After `process_block_header` the window covers up to
409+ /// `block.slot - 1`, so this is exactly a vote whose source or target slot is
410+ /// at or beyond the importing block's own slot.
411+ fn is_valid_vote ( state : & State , data : & AttestationData ) -> Result < bool , Error > {
396412 let source = data. source ;
397413 let target = data. target ;
398414
@@ -401,37 +417,36 @@ fn is_valid_vote(state: &State, data: &AttestationData) -> bool {
401417 & state. justified_slots ,
402418 state. latest_finalized . slot ,
403419 source. slot ,
404- ) {
405- // TODO: why doesn't this make the block invalid?
406- return false ;
420+ ) ? {
421+ return Ok ( false ) ;
407422 }
408423
409424 // Ignore votes for targets that have already reached consensus
410425 if justified_slots_ops:: is_slot_justified (
411426 & state. justified_slots ,
412427 state. latest_finalized . slot ,
413428 target. slot ,
414- ) {
415- return false ;
429+ ) ? {
430+ return Ok ( false ) ;
416431 }
417432
418433 // Ensure the vote refers to blocks that actually exist on our chain;
419434 // also rejects zero-hash source or target inline.
420435 if !attestation_data_matches_chain ( & state. historical_block_hashes , data) {
421- return false ;
436+ return Ok ( false ) ;
422437 }
423438
424439 // Ensure time flows forward
425440 if target. slot <= source. slot {
426- return false ;
441+ return Ok ( false ) ;
427442 }
428443
429444 // Ensure the target falls on a slot that can be justified after the finalized one.
430445 if !slot_is_justifiable_after ( target. slot , state. latest_finalized . slot ) {
431- return false ;
446+ return Ok ( false ) ;
432447 }
433448
434- true
449+ Ok ( true )
435450}
436451
437452/// Attempt to advance finalization from source to target.
0 commit comments