@@ -578,17 +578,16 @@ impl Node {
578578 pipeline,
579579 producer : self . id ,
580580 } ) ;
581- let ibs = self . select_ibs_for_eb ( pipeline) ;
582- let ebs = self . select_ebs_for_eb ( pipeline) ;
583- let bytes = self . sim_config . sizes . eb ( ibs. len ( ) , ebs. len ( ) ) ;
584- let eb = EndorserBlock {
581+ let mut eb = EndorserBlock {
585582 slot,
586583 pipeline,
587584 producer : self . id ,
588- bytes,
589- ibs,
590- ebs,
585+ bytes : 0 ,
586+ ibs : vec ! [ ] ,
587+ ebs : vec ! [ ] ,
591588 } ;
589+ self . try_filling_eb ( & mut eb) ;
590+ eb. bytes = self . sim_config . sizes . eb ( eb. ibs . len ( ) , eb. ebs . len ( ) ) ;
592591 self . schedule_cpu_task ( CpuTaskType :: EBBlockGenerated ( eb) ) ;
593592 // A node should only generate at most 1 EB per slot
594593 return ;
@@ -1319,7 +1318,28 @@ impl Node {
13191318 Ok ( ( ) )
13201319 }
13211320
1322- fn select_ibs_for_eb ( & mut self , pipeline : u64 ) -> Vec < InputBlockId > {
1321+ fn try_filling_eb ( & mut self , eb : & mut EndorserBlock ) {
1322+ eb. ibs = self . select_ibs_from_pipeline ( eb. pipeline ) ;
1323+ eb. ebs = self . select_ebs_for_eb ( eb. pipeline ) ;
1324+ if !self . sim_config . late_ib_inclusion {
1325+ return ;
1326+ }
1327+ let Some ( expected_referenced_pipelines) = self . pipelines_referenced_by_ebs ( eb. pipeline )
1328+ else {
1329+ return ;
1330+ } ;
1331+
1332+ let pipelines_referenced_by_ebs: HashSet < u64 > =
1333+ eb. ebs . iter ( ) . map ( |eb| eb. pipeline ) . collect ( ) ;
1334+ for pipeline in expected_referenced_pipelines {
1335+ if !pipelines_referenced_by_ebs. contains ( & pipeline) {
1336+ let mut pipeline_ibs = self . select_ibs_from_pipeline ( pipeline) ;
1337+ eb. ibs . append ( & mut pipeline_ibs) ;
1338+ }
1339+ }
1340+ }
1341+
1342+ fn select_ibs_from_pipeline ( & self , pipeline : u64 ) -> Vec < InputBlockId > {
13231343 self . leios
13241344 . ibs_by_pipeline
13251345 . get ( & pipeline)
@@ -1389,14 +1409,14 @@ impl Node {
13891409
13901410 fn should_vote_for ( & self , eb : & EndorserBlock ) -> Result < ( ) , NoVoteReason > {
13911411 let mut ib_set = HashSet :: new ( ) ;
1412+ let mut pipelines_referenced_by_ibs = HashSet :: new ( ) ;
1413+
13921414 for ib in & eb. ibs {
13931415 if !matches ! ( self . leios. ibs. get( ib) , Some ( InputBlockState :: Received ( _) ) ) {
13941416 return Err ( NoVoteReason :: MissingIB ) ;
13951417 }
1396- if ib. pipeline != eb. pipeline {
1397- return Err ( NoVoteReason :: InvalidSlot ) ;
1398- }
13991418 ib_set. insert ( * ib) ;
1419+ pipelines_referenced_by_ibs. insert ( ib. pipeline ) ;
14001420 }
14011421
14021422 if let Some ( ibs) = self . leios . ibs_by_pipeline . get ( & eb. pipeline ) {
@@ -1409,31 +1429,40 @@ impl Node {
14091429
14101430 // If this EB is meant to reference other EBs, validate that it references whatever it needs
14111431 if let Some ( expected_referenced_pipelines) = self . pipelines_referenced_by_ebs ( eb. pipeline ) {
1412- let actual_referenced_pipelines : HashSet < u64 > =
1432+ let pipelines_referenced_by_ebs : HashSet < u64 > =
14131433 eb. ebs . iter ( ) . map ( |id| id. pipeline ) . collect ( ) ;
1434+
14141435 for expected_pipeline in expected_referenced_pipelines {
1415- let Some ( certified_at ) = self
1436+ let saw_certified_eb = self
14161437 . leios
14171438 . earliest_eb_cert_times_by_pipeline
14181439 . get ( & expected_pipeline)
1419- else {
1420- // We don't require an EB referenced from this pipeline if none of that pipeline's EBs have been certified yet,
1421- continue ;
1422- } ;
1423-
1424- let last_pipeline_slot = ( expected_pipeline + 1 ) * self . sim_config . stage_length - 1 ;
1425- let cutoff = Timestamp :: from_secs ( last_pipeline_slot)
1426- . checked_sub_duration ( self . sim_config . header_diffusion_time )
1427- . unwrap_or_default ( ) ;
1428- if certified_at > & cutoff {
1429- // Don't need an EB from this pipeline if it was certified so recently that it may not have propagated.
1430- continue ;
1431- }
1432-
1433- if !actual_referenced_pipelines. contains ( & expected_pipeline) {
1440+ . is_some_and ( |certified_at| {
1441+ let last_pipeline_slot =
1442+ ( expected_pipeline + 1 ) * self . sim_config . stage_length - 1 ;
1443+ let cutoff = Timestamp :: from_secs ( last_pipeline_slot)
1444+ . checked_sub_duration ( self . sim_config . header_diffusion_time )
1445+ . unwrap_or_default ( ) ;
1446+ certified_at <= & cutoff
1447+ } ) ;
1448+
1449+ if saw_certified_eb && !pipelines_referenced_by_ebs. contains ( & expected_pipeline) {
1450+ // We saw at least one certified EB for this pipeline, so we should have included one.
14341451 return Err ( NoVoteReason :: MissingEB ) ;
14351452 }
1453+ if pipelines_referenced_by_ebs. contains ( & expected_pipeline)
1454+ && pipelines_referenced_by_ibs. contains ( & expected_pipeline)
1455+ {
1456+ // No pipeline should be referenced by both an EB and any IBs
1457+ return Err ( NoVoteReason :: ExtraIB ) ;
1458+ }
14361459 }
1460+ } else if pipelines_referenced_by_ibs
1461+ . iter ( )
1462+ . any ( |pipeline| * pipeline != eb. pipeline )
1463+ {
1464+ // This EB should only reference IBs from its own pipeline
1465+ return Err ( NoVoteReason :: InvalidSlot ) ;
14371466 }
14381467
14391468 // If this EB _does_ reference other EBs, make sure we trust them
0 commit comments