@@ -1954,47 +1954,44 @@ impl Blockstore {
19541954 None
19551955 }
19561956
1957- fn maybe_insert_parent_meta (
1958- & self ,
1959- current_shred : & Shred ,
1960- entry : & mut ParentMetaWorkingSetEntry ,
1961- ) {
1957+ fn maybe_parse_block_header ( & self , current_shred : & Shred ) -> Option < ParentMeta > {
1958+ // Block headers only occur at index 0
1959+ if current_shred. index ( ) != 0 {
1960+ return None ;
1961+ }
1962+
19621963 // The contents of BlockHeaderV1 entirely fit into a single shred. So, let's just parse the
19631964 // first shred.
19641965 let shred_bytes = current_shred. payload ( ) ;
19651966 let Ok ( payload) = shred:: wire:: get_data ( shred_bytes) else {
1966- return ;
1967+ return None ;
19671968 } ;
19681969
19691970 // Check whether this is a BlockMarker
19701971 if !BlockComponent :: infer_is_block_marker ( payload) . unwrap_or ( false ) {
1971- return ;
1972+ return None ;
19721973 }
19731974
19741975 // Try to parse BlockHeader from the payload
1975- let Some ( ( parent_slot, parent_block_id) ) =
1976- BlockComponent :: parse_block_header_from_data_payload ( payload)
1977- else {
1978- return ;
1979- } ;
1976+ let ( parent_slot, parent_block_id) =
1977+ BlockComponent :: parse_block_header_from_data_payload ( payload) ?;
19801978
19811979 let parent_meta = ParentMeta {
19821980 parent_slot,
19831981 parent_block_id,
19841982 replay_fec_set_index : 0 ,
19851983 } ;
19861984
1987- entry . parent_meta = Some ( WorkingEntry :: Dirty ( parent_meta) ) ;
1985+ Some ( parent_meta)
19881986 }
19891987
1990- fn maybe_insert_update_parent (
1988+ fn maybe_parse_update_parent (
19911989 & self ,
19921990 current_shred : & Shred ,
19931991 slot : Slot ,
19941992 location : BlockLocation ,
19951993 just_inserted_shreds : & HashMap < ( BlockLocation , ShredId ) , Cow < ' _ , Shred > > ,
1996- entry : & mut ParentMetaWorkingSetEntry ,
1997- ) -> bool {
1994+ ) -> Option < ParentMeta > {
19981995 let current_index = current_shred. index ( ) ;
19991996 let fec_set_index = current_shred. fec_set_index ( ) ;
20001997 let data_complete = current_shred. data_complete ( ) ;
@@ -2032,41 +2029,30 @@ impl Blockstore {
20322029
20332030 ( shred_bytes, fec_set_index)
20342031 } else {
2035- return false ;
2032+ return None ;
20362033 } ;
20372034
20382035 // Process the shred bytes if we have them
2039- let Some ( shred_bytes) = shred_bytes else {
2040- return false ;
2041- } ;
2042-
2043- let Ok ( payload) = shred:: wire:: get_data ( & shred_bytes) else {
2044- return false ;
2045- } ;
2036+ let shred_bytes = shred_bytes?;
2037+ let payload = shred:: wire:: get_data ( & shred_bytes) . ok ( ) ?;
20462038
20472039 // Check whether this is a BlockMarker
20482040 if !BlockComponent :: infer_is_block_marker ( payload) . unwrap_or ( false ) {
2049- return false ;
2041+ return None ;
20502042 }
20512043
20522044 // Try to parse UpdateParent from the payload
2053- let Some ( ( new_parent_slot, new_parent_block_id) ) =
2054- BlockComponent :: parse_update_parent_from_data_payload ( payload)
2055- else {
2056- return false ;
2057- } ;
2045+ let ( new_parent_slot, new_parent_block_id) =
2046+ BlockComponent :: parse_update_parent_from_data_payload ( payload) ?;
20582047
20592048 // First time seeing this UpdateParent
2060- let parent_meta = ParentMeta {
2049+ let update_parent_meta = ParentMeta {
20612050 parent_slot : new_parent_slot,
20622051 parent_block_id : new_parent_block_id,
20632052 replay_fec_set_index : target_fec_set_index,
20642053 } ;
20652054
2066- // Store the UpdateParent metadata
2067- entry. parent_meta = Some ( WorkingEntry :: Dirty ( parent_meta) ) ;
2068-
2069- true
2055+ Some ( update_parent_meta)
20702056 }
20712057
20722058 /// Create an entry to the specified `write_batch` that performs shred
@@ -2250,6 +2236,75 @@ impl Blockstore {
22502236 Ok ( ( ) )
22512237 }
22522238
2239+ /// Validates compatibility between two ParentMetas.
2240+ ///
2241+ /// This function determines which ParentMeta is which (UpdateParent vs BlockHeader) and
2242+ /// validates that they are compatible. Works irrespective of parameter order.
2243+ ///
2244+ /// Returns:
2245+ /// - `Ok(true)` if validation passed and the new ParentMeta should be written
2246+ /// - `Ok(false)` if the new ParentMeta should NOT be written (same type and match, or UpdateParent takes precedence)
2247+ /// - `Err` if validation fails
2248+ fn should_write_parent_meta ( slot : Slot , new : & ParentMeta , prev : & ParentMeta ) -> Result < bool > {
2249+ // Determine which is the UpdateParent and which is the BlockHeader
2250+ let ( update_parent_meta, block_header_meta, should_write) = match (
2251+ new. populated_from_block_header ( ) ,
2252+ prev. populated_from_block_header ( ) ,
2253+ ) {
2254+ // New is BlockHeader, prev is UpdateParent
2255+ // UpdateParent takes precedence, so don't overwrite
2256+ ( true , false ) => ( prev, new, false ) ,
2257+ // New is UpdateParent, prev is BlockHeader
2258+ // Validate and allow UpdateParent to replace BlockHeader
2259+ ( false , true ) => ( new, prev, true ) ,
2260+ // Both are the same type - ensure they match
2261+ _ => match new == prev {
2262+ true => return Ok ( false ) ,
2263+ false => return Err ( BlockstoreError :: BlockComponentMismatch ( slot) ) ,
2264+ } ,
2265+ } ;
2266+
2267+ // Validate that the UpdateParent is compatible with the BlockHeader
2268+ if update_parent_meta. block ( ) == block_header_meta. block ( ) {
2269+ return Err ( BlockstoreError :: UpdateParentMatchesBlockHeader ( slot) ) ;
2270+ }
2271+
2272+ if update_parent_meta. parent_slot > block_header_meta. parent_slot {
2273+ return Err ( BlockstoreError :: UpdateParentSlotGreaterThanBlockHeader (
2274+ slot,
2275+ ) ) ;
2276+ }
2277+
2278+ Ok ( should_write)
2279+ }
2280+
2281+ /// Fetches or retrieves the existing ParentMeta for a given slot and location.
2282+ ///
2283+ /// This method first checks the working set, and if not found there, fetches from blockstore.
2284+ /// Returns the existing ParentMeta if it exists, or None otherwise.
2285+ fn get_or_fetch_parent_meta (
2286+ & self ,
2287+ slot : Slot ,
2288+ location : BlockLocation ,
2289+ entry : & mut ParentMetaWorkingSetEntry ,
2290+ ) -> Result < Option < ParentMeta > > {
2291+ // First check working set
2292+ if let Some ( parent_meta) = entry. parent_meta . as_ref ( ) {
2293+ return Ok ( Some ( * parent_meta. as_ref ( ) ) ) ;
2294+ }
2295+
2296+ // Then fetch from blockstore if needed
2297+ if !entry. fetched_from_blockstore {
2298+ entry. fetched_from_blockstore = true ;
2299+ if let Some ( parent_meta) = self . parent_meta_cf . get ( ( slot, location) ) ? {
2300+ entry. parent_meta = Some ( WorkingEntry :: Clean ( parent_meta) ) ;
2301+ return Ok ( Some ( parent_meta) ) ;
2302+ }
2303+ }
2304+
2305+ Ok ( None )
2306+ }
2307+
22532308 fn maybe_update_parent_meta (
22542309 & self ,
22552310 shred : & Shred ,
@@ -2261,42 +2316,41 @@ impl Blockstore {
22612316 let key = ( location, slot) ;
22622317 let entry = parent_meta_working_set. entry ( key) . or_default ( ) ;
22632318
2264- // If the working set ParentMeta exists and has an UpdateParent, then return early
2265- if let Some ( parent_meta) = entry. parent_meta . as_ref ( ) {
2266- let populated_from_update_parent = match parent_meta {
2267- WorkingEntry :: Dirty ( parent_meta) | WorkingEntry :: Clean ( parent_meta) => {
2268- parent_meta. populated_from_update_parent ( )
2269- }
2270- } ;
2271-
2272- if populated_from_update_parent {
2273- return Ok ( ( ) ) ;
2319+ // Fetch or retrieve existing ParentMeta (from working set or blockstore)
2320+ let previous_parent_meta = self . get_or_fetch_parent_meta ( slot, location, entry) ?;
2321+
2322+ // Try to parse new ParentMeta from the current shred
2323+ // - If shred.index() == 0, try to parse as a block header
2324+ // - Otherwise, try to parse as an UpdateParent (only if we don't already have one,
2325+ // since UpdateParent takes precedence and parsing is expensive)
2326+ let new_parent_meta = match ( shred. index ( ) , & previous_parent_meta) {
2327+ // Always try to parse block header at index 0
2328+ ( 0 , _) => self . maybe_parse_block_header ( shred) ,
2329+ // Try to parse UpdateParent only if we don't have one already
2330+ ( _, Some ( pm) ) if pm. populated_from_block_header ( ) => {
2331+ self . maybe_parse_update_parent ( shred, slot, location, just_inserted_shreds)
22742332 }
2275- }
2276- // If we haven't hit blockstore for this key yet...
2277- else if !entry. fetched_from_blockstore {
2278- entry. fetched_from_blockstore = true ;
2279-
2280- if let Some ( parent_meta) = self . parent_meta_cf . get ( ( slot, location) ) ? {
2281- entry. parent_meta = Some ( WorkingEntry :: Clean ( parent_meta) ) ;
2282-
2283- if parent_meta. populated_from_update_parent ( ) {
2284- return Ok ( ( ) ) ;
2285- }
2333+ ( _, None ) => {
2334+ self . maybe_parse_update_parent ( shred, slot, location, just_inserted_shreds)
22862335 }
2287- }
2336+ // previous_parent_meta is UpdateParent; don't parse another one
2337+ _ => None ,
2338+ } ;
22882339
2289- // Run through insertion logic for update_parent. If we succeed at inserting an
2290- // UpdateParent, then return.
2291- if self . maybe_insert_update_parent ( shred, slot, location, just_inserted_shreds, entry) {
2340+ let Some ( new_parent_meta) = new_parent_meta else {
22922341 return Ok ( ( ) ) ;
2293- }
2342+ } ;
22942343
2295- // If there isn't any parent_meta, then attempt to detect a block header.
2296- if entry. parent_meta . is_none ( ) && shred. index ( ) == 0 {
2297- self . maybe_insert_parent_meta ( shred, entry) ;
2344+ // Validate new ParentMeta against previous (if both exist)
2345+ if let Some ( prev) = & previous_parent_meta {
2346+ if !Self :: should_write_parent_meta ( slot, & new_parent_meta, prev) ? {
2347+ return Ok ( ( ) ) ;
2348+ }
22982349 }
22992350
2351+ // Update entry with new ParentMeta
2352+ entry. parent_meta = Some ( WorkingEntry :: Dirty ( new_parent_meta) ) ;
2353+
23002354 Ok ( ( ) )
23012355 }
23022356
0 commit comments