From 84d86cf41388861bbd53b9314bbdd2a7bc8bae9a Mon Sep 17 00:00:00 2001 From: Lukasz Rubaszewski <117115317+lrubasze@users.noreply.github.com> Date: Wed, 6 May 2026 11:08:53 +0000 Subject: [PATCH 1/6] runtime_service: emit Block+Finalized for warp-synced block on reset --- lib/src/chain/async_tree.rs | 9 ++ light-base/src/runtime_service.rs | 222 ++++++++++++++++++++++-------- 2 files changed, 177 insertions(+), 54 deletions(-) diff --git a/lib/src/chain/async_tree.rs b/lib/src/chain/async_tree.rs index e45e6ef757..0c21bbbcff 100644 --- a/lib/src/chain/async_tree.rs +++ b/lib/src/chain/async_tree.rs @@ -374,6 +374,15 @@ where self.input_best_block_index } + /// Returns the user data of the current "input" finalized block. + /// + /// Returns `None` if the input finalized block is the output finalized block (i.e. the + /// async_tree's output has caught up to the input). + pub fn input_finalized_user_data(&self) -> Option<&TBl> { + self.input_finalized_index + .map(|idx| &self.non_finalized_blocks.get(idx).unwrap().user_data) + } + /// Returns the list of all non-finalized blocks that have been inserted, both input and /// output. /// diff --git a/light-base/src/runtime_service.rs b/light-base/src/runtime_service.rs index eb1a0b8b63..8f20146de5 100644 --- a/light-base/src/runtime_service.rs +++ b/light-base/src/runtime_service.rs @@ -1355,10 +1355,10 @@ async fn run_background( // TODO: DRY below if let Some(finalized_block_runtime) = subscription.finalized_block_runtime { - let finalized_block_hash = header::hash_from_scale_encoded_header( + let warp_synced_hash = header::hash_from_scale_encoded_header( &subscription.finalized_block_scale_encoded_header, ); - let finalized_block_height = header::decode( + let warp_synced_height = header::decode( &subscription.finalized_block_scale_encoded_header, background.sync_service.block_number_bytes(), ) @@ -1412,64 +1412,178 @@ async fn run_background( } } - background.tree = Tree::FinalizedBlockRuntimeKnown { - all_blocks_subscriptions: hashbrown::HashMap::with_capacity_and_hasher( - 32, - Default::default(), - ), // TODO: capacity? - pinned_blocks: BTreeMap::new(), - finalized_block: Block { - hash: finalized_block_hash, - height: finalized_block_height, - scale_encoded_header: subscription - .finalized_block_scale_encoded_header, + // Pre-warp finalized block: the finalized block from before the warp + // sync. Used as the tree's initial finalized state so that the + // warp-synced block can be inserted as a non-finalized child and then + // finalized, producing the canonical `Notification::Block` then + // `Notification::Finalized` sequence to subscribers. The pre-warp + // finalized block is pruned the moment warp-synced is finalized. + let pre_warp_finalized = match &background.tree { + Tree::FinalizedBlockRuntimeKnown { + finalized_block, .. + } => Block { + hash: finalized_block.hash, + height: finalized_block.height, + scale_encoded_header: finalized_block.scale_encoded_header.clone(), }, - tree: { - let mut tree = - async_tree::AsyncTree::<_, Block, _>::new(async_tree::Config { - finalized_async_user_data: runtime, - retry_after_failed: Duration::from_secs(4), // TODO: hardcoded - blocks_capacity: 32, - }); + Tree::FinalizedBlockRuntimeUnknown { tree } => tree + .input_finalized_user_data() + .map(|b| Block { + hash: b.hash, + height: b.height, + scale_encoded_header: b.scale_encoded_header.clone(), + }) + .unwrap_or_else(|| Block { + hash: warp_synced_hash, + height: warp_synced_height, + scale_encoded_header: subscription + .finalized_block_scale_encoded_header + .clone(), + }), + }; + // If for any reason the pre-warp finalized would equal the warp-synced + // block, fall back to the legacy single-block initialization (no + // synthetic Block/Finalized sequence). This shouldn't happen in practice. + let use_pre_warp_finalized = pre_warp_finalized.hash != warp_synced_hash; + + // Placeholder runtime for the pre-warp finalized block. Its runtime + // is never queried for runtime calls because the block is pruned as + // soon as warp-synced is finalized; calls against it fail loudly. + let pre_warp_finalized_runtime = if use_pre_warp_finalized { + Arc::new(Runtime { + runtime: Err(RuntimeError::CodeNotFound), + runtime_code: None, + heap_pages: None, + code_merkle_value: None, + closest_ancestor_excluding: None, + }) + } else { + runtime.clone() + }; - for block in subscription.non_finalized_blocks_ancestry_order { - let parent_index = if block.parent_hash == finalized_block_hash - { - None - } else { - Some( - tree.input_output_iter_unordered() - .find(|b| b.user_data.hash == block.parent_hash) - .unwrap() - .id, + let warp_synced_block = Block { + hash: warp_synced_hash, + height: warp_synced_height, + scale_encoded_header: subscription.finalized_block_scale_encoded_header, + }; + + let (new_tree, finalized_block) = if use_pre_warp_finalized { + let mut tree = + async_tree::AsyncTree::<_, Block, _>::new(async_tree::Config { + finalized_async_user_data: pre_warp_finalized_runtime, + retry_after_failed: Duration::from_secs(4), // TODO: hardcoded + blocks_capacity: 32, + }); + + // Insert the warp-synced block as a non-finalized child of the + // pre-warp finalized block (parent=None) and pre-complete its + // runtime download so that the natural tree-advance loop will emit + // `OutputUpdate::Block` followed by `OutputUpdate::Finalized` + // to subscribers without waiting for any actual download. + let warp_synced_idx = tree.input_insert_block( + warp_synced_block.clone(), + None, + false, + true, + ); + let async_op = + match tree.next_necessary_async_op(&background.platform.now()) { + async_tree::NextNecessaryAsyncOp::Ready(op) => op, + async_tree::NextNecessaryAsyncOp::NotReady { .. } => { + unreachable!( + "warp-synced block just inserted with pending async op" ) - }; + } + }; + debug_assert_eq!(async_op.block_index, warp_synced_idx); + tree.async_op_finished(async_op.id, runtime); + tree.input_finalize(warp_synced_idx); + + for block in subscription.non_finalized_blocks_ancestry_order { + let parent_index = tree + .input_output_iter_unordered() + .find(|b| b.user_data.hash == block.parent_hash) + .map(|b| b.id); + let same_runtime_as_parent = same_runtime_as_parent( + &block.scale_encoded_header, + background.sync_service.block_number_bytes(), + ); + let _ = tree.input_insert_block( + Block { + hash: header::hash_from_scale_encoded_header( + &block.scale_encoded_header, + ), + height: header::decode( + &block.scale_encoded_header, + background.sync_service.block_number_bytes(), + ) + .unwrap() + .number, + scale_encoded_header: block.scale_encoded_header, + }, + parent_index, + same_runtime_as_parent, + block.is_new_best, + ); + } - let same_runtime_as_parent = same_runtime_as_parent( - &block.scale_encoded_header, - background.sync_service.block_number_bytes(), - ); - let _ = tree.input_insert_block( - Block { - hash: header::hash_from_scale_encoded_header( - &block.scale_encoded_header, - ), - height: header::decode( - &block.scale_encoded_header, - background.sync_service.block_number_bytes(), - ) + (tree, pre_warp_finalized) + } else { + // Legacy path: warp-synced block is the tree's initial finalized. + // No synthetic Block/Finalized notifications are produced. + let mut tree = + async_tree::AsyncTree::<_, Block, _>::new(async_tree::Config { + finalized_async_user_data: runtime, + retry_after_failed: Duration::from_secs(4), + blocks_capacity: 32, + }); + + for block in subscription.non_finalized_blocks_ancestry_order { + let parent_index = if block.parent_hash == warp_synced_hash { + None + } else { + Some( + tree.input_output_iter_unordered() + .find(|b| b.user_data.hash == block.parent_hash) .unwrap() - .number, // TODO: consider feeding the information from the sync service? - scale_encoded_header: block.scale_encoded_header, - }, - parent_index, - same_runtime_as_parent, - block.is_new_best, - ); - } + .id, + ) + }; - tree - }, + let same_runtime_as_parent = same_runtime_as_parent( + &block.scale_encoded_header, + background.sync_service.block_number_bytes(), + ); + let _ = tree.input_insert_block( + Block { + hash: header::hash_from_scale_encoded_header( + &block.scale_encoded_header, + ), + height: header::decode( + &block.scale_encoded_header, + background.sync_service.block_number_bytes(), + ) + .unwrap() + .number, + scale_encoded_header: block.scale_encoded_header, + }, + parent_index, + same_runtime_as_parent, + block.is_new_best, + ); + } + + (tree, warp_synced_block) + }; + + background.tree = Tree::FinalizedBlockRuntimeKnown { + all_blocks_subscriptions: hashbrown::HashMap::with_capacity_and_hasher( + 32, + Default::default(), + ), // TODO: capacity? + pinned_blocks: BTreeMap::new(), + finalized_block, + tree: new_tree, }; } else { background.tree = Tree::FinalizedBlockRuntimeUnknown { From 3828468b42012fa481c3cfe50816d3d8c286905d Mon Sep 17 00:00:00 2001 From: Lukasz Rubaszewski <117115317+lrubasze@users.noreply.github.com> Date: Thu, 7 May 2026 08:51:47 +0000 Subject: [PATCH 2/6] runtime_service: simplify warp-sync tree initialization --- light-base/src/runtime_service.rs | 159 ++++++++++++------------------ 1 file changed, 64 insertions(+), 95 deletions(-) diff --git a/light-base/src/runtime_service.rs b/light-base/src/runtime_service.rs index 8f20146de5..ac70d318c3 100644 --- a/light-base/src/runtime_service.rs +++ b/light-base/src/runtime_service.rs @@ -1467,114 +1467,83 @@ async fn run_background( scale_encoded_header: subscription.finalized_block_scale_encoded_header, }; - let (new_tree, finalized_block) = if use_pre_warp_finalized { - let mut tree = - async_tree::AsyncTree::<_, Block, _>::new(async_tree::Config { - finalized_async_user_data: pre_warp_finalized_runtime, - retry_after_failed: Duration::from_secs(4), // TODO: hardcoded - blocks_capacity: 32, - }); + let (finalized_async_user_data, finalized_block) = if use_pre_warp_finalized + { + (pre_warp_finalized_runtime, pre_warp_finalized) + } else { + (runtime.clone(), warp_synced_block.clone()) + }; + let mut new_tree = + async_tree::AsyncTree::<_, Block, _>::new(async_tree::Config { + finalized_async_user_data, + retry_after_failed: Duration::from_secs(4), // TODO: hardcoded + blocks_capacity: 32, + }); + + if use_pre_warp_finalized { // Insert the warp-synced block as a non-finalized child of the // pre-warp finalized block (parent=None) and pre-complete its - // runtime download so that the natural tree-advance loop will emit + // runtime download so that the natural tree-advance loop emits // `OutputUpdate::Block` followed by `OutputUpdate::Finalized` // to subscribers without waiting for any actual download. - let warp_synced_idx = tree.input_insert_block( + let warp_synced_idx = new_tree.input_insert_block( warp_synced_block.clone(), None, false, true, ); - let async_op = - match tree.next_necessary_async_op(&background.platform.now()) { - async_tree::NextNecessaryAsyncOp::Ready(op) => op, - async_tree::NextNecessaryAsyncOp::NotReady { .. } => { - unreachable!( - "warp-synced block just inserted with pending async op" - ) - } - }; + let async_op = match new_tree + .next_necessary_async_op(&background.platform.now()) + { + async_tree::NextNecessaryAsyncOp::Ready(op) => op, + async_tree::NextNecessaryAsyncOp::NotReady { .. } => unreachable!( + "warp-synced block just inserted with pending async op" + ), + }; debug_assert_eq!(async_op.block_index, warp_synced_idx); - tree.async_op_finished(async_op.id, runtime); - tree.input_finalize(warp_synced_idx); - - for block in subscription.non_finalized_blocks_ancestry_order { - let parent_index = tree - .input_output_iter_unordered() - .find(|b| b.user_data.hash == block.parent_hash) - .map(|b| b.id); - let same_runtime_as_parent = same_runtime_as_parent( - &block.scale_encoded_header, - background.sync_service.block_number_bytes(), - ); - let _ = tree.input_insert_block( - Block { - hash: header::hash_from_scale_encoded_header( - &block.scale_encoded_header, - ), - height: header::decode( - &block.scale_encoded_header, - background.sync_service.block_number_bytes(), - ) - .unwrap() - .number, - scale_encoded_header: block.scale_encoded_header, - }, - parent_index, - same_runtime_as_parent, - block.is_new_best, - ); - } - - (tree, pre_warp_finalized) - } else { - // Legacy path: warp-synced block is the tree's initial finalized. - // No synthetic Block/Finalized notifications are produced. - let mut tree = - async_tree::AsyncTree::<_, Block, _>::new(async_tree::Config { - finalized_async_user_data: runtime, - retry_after_failed: Duration::from_secs(4), - blocks_capacity: 32, - }); - - for block in subscription.non_finalized_blocks_ancestry_order { - let parent_index = if block.parent_hash == warp_synced_hash { - None - } else { - Some( - tree.input_output_iter_unordered() - .find(|b| b.user_data.hash == block.parent_hash) - .unwrap() - .id, - ) - }; + new_tree.async_op_finished(async_op.id, runtime); + new_tree.input_finalize(warp_synced_idx); + } - let same_runtime_as_parent = same_runtime_as_parent( - &block.scale_encoded_header, - background.sync_service.block_number_bytes(), - ); - let _ = tree.input_insert_block( - Block { - hash: header::hash_from_scale_encoded_header( - &block.scale_encoded_header, - ), - height: header::decode( - &block.scale_encoded_header, - background.sync_service.block_number_bytes(), - ) + for block in subscription.non_finalized_blocks_ancestry_order { + // Parent is either the tree's outer finalized block + // (encoded as parent_index = None) or a block already in the tree + // (a previous block in the iteration order, or the warp-synced + // block when use_pre_warp_finalized). + let parent_index = if block.parent_hash == finalized_block.hash { + None + } else { + Some( + new_tree + .input_output_iter_unordered() + .find(|b| b.user_data.hash == block.parent_hash) .unwrap() - .number, - scale_encoded_header: block.scale_encoded_header, - }, - parent_index, - same_runtime_as_parent, - block.is_new_best, - ); - } - - (tree, warp_synced_block) - }; + .id, + ) + }; + let same_runtime_as_parent = same_runtime_as_parent( + &block.scale_encoded_header, + background.sync_service.block_number_bytes(), + ); + let _ = new_tree.input_insert_block( + Block { + hash: header::hash_from_scale_encoded_header( + &block.scale_encoded_header, + ), + height: header::decode( + &block.scale_encoded_header, + background.sync_service.block_number_bytes(), + ) + .unwrap() + .number, + scale_encoded_header: block.scale_encoded_header, + }, + parent_index, + same_runtime_as_parent, + block.is_new_best, + ); + } background.tree = Tree::FinalizedBlockRuntimeKnown { all_blocks_subscriptions: hashbrown::HashMap::with_capacity_and_hasher( From f361eab2dd35659af58224324448b71f3fa28893 Mon Sep 17 00:00:00 2001 From: Lukasz Rubaszewski <117115317+lrubasze@users.noreply.github.com> Date: Thu, 7 May 2026 09:02:46 +0000 Subject: [PATCH 3/6] trim comments --- light-base/src/runtime_service.rs | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/light-base/src/runtime_service.rs b/light-base/src/runtime_service.rs index ac70d318c3..335b8e6070 100644 --- a/light-base/src/runtime_service.rs +++ b/light-base/src/runtime_service.rs @@ -1412,12 +1412,11 @@ async fn run_background( } } - // Pre-warp finalized block: the finalized block from before the warp - // sync. Used as the tree's initial finalized state so that the - // warp-synced block can be inserted as a non-finalized child and then - // finalized, producing the canonical `Notification::Block` then - // `Notification::Finalized` sequence to subscribers. The pre-warp - // finalized block is pruned the moment warp-synced is finalized. + // Used as the tree's initial finalized state so the warp-synced block + // can be inserted as a non-finalized child and then finalized, emitting + // `Block` + `Finalized` to subscribers. Not the real chain parent of + // the warp-synced block (warp sync skips ancestry); only a tree-level + // predecessor. Pruned the moment warp-synced is finalized. let pre_warp_finalized = match &background.tree { Tree::FinalizedBlockRuntimeKnown { finalized_block, .. @@ -1441,14 +1440,10 @@ async fn run_background( .clone(), }), }; - // If for any reason the pre-warp finalized would equal the warp-synced - // block, fall back to the legacy single-block initialization (no - // synthetic Block/Finalized sequence). This shouldn't happen in practice. + // Defensive: if equal, fall back to legacy single-block init. let use_pre_warp_finalized = pre_warp_finalized.hash != warp_synced_hash; - // Placeholder runtime for the pre-warp finalized block. Its runtime - // is never queried for runtime calls because the block is pruned as - // soon as warp-synced is finalized; calls against it fail loudly. + // Placeholder runtime: never used (block pruned immediately); calls fail loudly. let pre_warp_finalized_runtime = if use_pre_warp_finalized { Arc::new(Runtime { runtime: Err(RuntimeError::CodeNotFound), @@ -1482,11 +1477,8 @@ async fn run_background( }); if use_pre_warp_finalized { - // Insert the warp-synced block as a non-finalized child of the - // pre-warp finalized block (parent=None) and pre-complete its - // runtime download so that the natural tree-advance loop emits - // `OutputUpdate::Block` followed by `OutputUpdate::Finalized` - // to subscribers without waiting for any actual download. + // Pre-complete the runtime so the tree-advance loop emits + // `Block` + `Finalized` without an actual download. let warp_synced_idx = new_tree.input_insert_block( warp_synced_block.clone(), None, @@ -1507,10 +1499,7 @@ async fn run_background( } for block in subscription.non_finalized_blocks_ancestry_order { - // Parent is either the tree's outer finalized block - // (encoded as parent_index = None) or a block already in the tree - // (a previous block in the iteration order, or the warp-synced - // block when use_pre_warp_finalized). + // Parent is the tree's outer finalized (None) or already in the tree. let parent_index = if block.parent_hash == finalized_block.hash { None } else { From b6e120dbcf4ffe82cb804497a113d25642f68bbc Mon Sep 17 00:00:00 2001 From: Lukasz Rubaszewski <117115317+lrubasze@users.noreply.github.com> Date: Thu, 7 May 2026 14:34:04 +0000 Subject: [PATCH 4/6] runtime_service: make pre_warp_finalized optional, log decision --- light-base/src/runtime_service.rs | 44 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/light-base/src/runtime_service.rs b/light-base/src/runtime_service.rs index 335b8e6070..d3bb8f7b42 100644 --- a/light-base/src/runtime_service.rs +++ b/light-base/src/runtime_service.rs @@ -1417,31 +1417,42 @@ async fn run_background( // `Block` + `Finalized` to subscribers. Not the real chain parent of // the warp-synced block (warp sync skips ancestry); only a tree-level // predecessor. Pruned the moment warp-synced is finalized. - let pre_warp_finalized = match &background.tree { + // `None` when the previous tree has no usable input-finalized block, + // in which case we fall back to the legacy single-block init. + let pre_warp_finalized: Option = match &background.tree { Tree::FinalizedBlockRuntimeKnown { finalized_block, .. - } => Block { + } => Some(Block { hash: finalized_block.hash, height: finalized_block.height, scale_encoded_header: finalized_block.scale_encoded_header.clone(), - }, - Tree::FinalizedBlockRuntimeUnknown { tree } => tree - .input_finalized_user_data() - .map(|b| Block { + }), + Tree::FinalizedBlockRuntimeUnknown { tree } => { + tree.input_finalized_user_data().map(|b| Block { hash: b.hash, height: b.height, scale_encoded_header: b.scale_encoded_header.clone(), }) - .unwrap_or_else(|| Block { - hash: warp_synced_hash, - height: warp_synced_height, - scale_encoded_header: subscription - .finalized_block_scale_encoded_header - .clone(), - }), + } }; - // Defensive: if equal, fall back to legacy single-block init. - let use_pre_warp_finalized = pre_warp_finalized.hash != warp_synced_hash; + // Disable when missing or degenerately equal to warp-synced. + let use_pre_warp_finalized = pre_warp_finalized + .as_ref() + .is_some_and(|b| b.hash != warp_synced_hash); + + log!( + &background.platform, + Debug, + &background.log_target, + "warp-sync-tree-init", + use_pre_warp_finalized, + pre_warp_finalized_hash = if let Some(b) = pre_warp_finalized.as_ref() { + Cow::Owned(HashDisplay(&b.hash).to_string()) + } else { + Cow::Borrowed("") + }, + warp_synced_hash = HashDisplay(&warp_synced_hash), + ); // Placeholder runtime: never used (block pruned immediately); calls fail loudly. let pre_warp_finalized_runtime = if use_pre_warp_finalized { @@ -1464,7 +1475,8 @@ async fn run_background( let (finalized_async_user_data, finalized_block) = if use_pre_warp_finalized { - (pre_warp_finalized_runtime, pre_warp_finalized) + // use_pre_warp_finalized implies pre_warp_finalized.is_some(). + (pre_warp_finalized_runtime, pre_warp_finalized.unwrap()) } else { (runtime.clone(), warp_synced_block.clone()) }; From d4316ffbed4013f981b41b42f77269cd328d68d8 Mon Sep 17 00:00:00 2001 From: Lukasz Rubaszewski <117115317+lrubasze@users.noreply.github.com> Date: Fri, 8 May 2026 17:31:42 +0000 Subject: [PATCH 5/6] runtime_service: fold pre_warp_finalized_runtime into if-let arm --- light-base/src/runtime_service.rs | 36 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/light-base/src/runtime_service.rs b/light-base/src/runtime_service.rs index d3bb8f7b42..41d4347f67 100644 --- a/light-base/src/runtime_service.rs +++ b/light-base/src/runtime_service.rs @@ -1454,32 +1454,28 @@ async fn run_background( warp_synced_hash = HashDisplay(&warp_synced_hash), ); - // Placeholder runtime: never used (block pruned immediately); calls fail loudly. - let pre_warp_finalized_runtime = if use_pre_warp_finalized { - Arc::new(Runtime { - runtime: Err(RuntimeError::CodeNotFound), - runtime_code: None, - heap_pages: None, - code_merkle_value: None, - closest_ancestor_excluding: None, - }) - } else { - runtime.clone() - }; - let warp_synced_block = Block { hash: warp_synced_hash, height: warp_synced_height, scale_encoded_header: subscription.finalized_block_scale_encoded_header, }; - let (finalized_async_user_data, finalized_block) = if use_pre_warp_finalized - { - // use_pre_warp_finalized implies pre_warp_finalized.is_some(). - (pre_warp_finalized_runtime, pre_warp_finalized.unwrap()) - } else { - (runtime.clone(), warp_synced_block.clone()) - }; + let (finalized_async_user_data, finalized_block) = + if let Some(finalized_block) = + pre_warp_finalized.filter(|_| use_pre_warp_finalized) + { + // Placeholder runtime: never used (block pruned immediately); calls fail loudly. + let pre_warp_finalized_runtime = Arc::new(Runtime { + runtime: Err(RuntimeError::CodeNotFound), + runtime_code: None, + heap_pages: None, + code_merkle_value: None, + closest_ancestor_excluding: None, + }); + (pre_warp_finalized_runtime, finalized_block) + } else { + (runtime.clone(), warp_synced_block.clone()) + }; let mut new_tree = async_tree::AsyncTree::<_, Block, _>::new(async_tree::Config { From f68020c46e1febf4dc92c44c3e39b367465192ff Mon Sep 17 00:00:00 2001 From: Lukasz Rubaszewski <117115317+lrubasze@users.noreply.github.com> Date: Fri, 8 May 2026 17:32:12 +0000 Subject: [PATCH 6/6] comment --- lib/src/chain/async_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/chain/async_tree.rs b/lib/src/chain/async_tree.rs index 0c21bbbcff..807eb9e0d2 100644 --- a/lib/src/chain/async_tree.rs +++ b/lib/src/chain/async_tree.rs @@ -376,8 +376,7 @@ where /// Returns the user data of the current "input" finalized block. /// - /// Returns `None` if the input finalized block is the output finalized block (i.e. the - /// async_tree's output has caught up to the input). + /// Returns `None` if the input finalized block is the output finalized block. pub fn input_finalized_user_data(&self) -> Option<&TBl> { self.input_finalized_index .map(|idx| &self.non_finalized_blocks.get(idx).unwrap().user_data)