diff --git a/lib/src/chain/async_tree.rs b/lib/src/chain/async_tree.rs index e45e6ef757..807eb9e0d2 100644 --- a/lib/src/chain/async_tree.rs +++ b/lib/src/chain/async_tree.rs @@ -374,6 +374,14 @@ 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. + 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..41d4347f67 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,144 @@ 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, + // 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. + // `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, .. + } => 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 { + hash: b.hash, + height: b.height, + scale_encoded_header: b.scale_encoded_header.clone(), + }) + } + }; + // 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("") }, - 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, - }); + warp_synced_hash = HashDisplay(&warp_synced_hash), + ); - 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 same_runtime_as_parent = same_runtime_as_parent( + 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 { + finalized_async_user_data, + retry_after_failed: Duration::from_secs(4), // TODO: hardcoded + blocks_capacity: 32, + }); + + if use_pre_warp_finalized { + // 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, + false, + true, + ); + 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); + new_tree.async_op_finished(async_op.id, runtime); + new_tree.input_finalize(warp_synced_idx); + } + + for block in subscription.non_finalized_blocks_ancestry_order { + // 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 { + Some( + new_tree + .input_output_iter_unordered() + .find(|b| b.user_data.hash == block.parent_hash) + .unwrap() + .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(), - ); - 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, // 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, - ); - } + ) + .unwrap() + .number, + scale_encoded_header: block.scale_encoded_header, + }, + parent_index, + same_runtime_as_parent, + block.is_new_best, + ); + } - tree - }, + 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 {