Skip to main content

pop_fork/
builder.rs

1// SPDX-License-Identifier: GPL-3.0
2
3//! Block builder for constructing new blocks on a forked chain.
4//!
5//! This module provides the [`BlockBuilder`] for constructing new blocks by applying
6//! inherent extrinsics, user extrinsics, and finalizing the block.
7//!
8//! # Architecture
9//!
10//! The block building process follows these phases:
11//!
12//! ```text
13//! ┌─────────────────────────────────────────────────────────────────┐
14//! │                      Block Building Flow                        │
15//! │                                                                 │
16//! │   1. new()          Create builder with parent block            │
17//! │         │                                                       │
18//! │         ▼                                                       │
19//! │   2. initialize()   Call Core_initialize_block                  │
20//! │         │                                                       │
21//! │         ▼                                                       │
22//! │   3. apply_inherents()  Apply inherent extrinsics               │
23//! │         │                                                       │
24//! │         ▼                                                       │
25//! │   4. apply_extrinsic()  Apply user extrinsics (repeatable)      │
26//! │         │                                                       │
27//! │         ▼                                                       │
28//! │   5. finalize()     Call BlockBuilder_finalize_block            │
29//! │                     Returns new Block                           │
30//! └─────────────────────────────────────────────────────────────────┘
31//! ```
32//!
33//! # Example
34//!
35//! ```ignore
36//! use pop_fork::{BlockBuilder, Block, RuntimeExecutor};
37//!
38//! // Create a block builder
39//! let mut builder = BlockBuilder::new(parent_block, executor, header, inherent_providers, None, false);
40//!
41//! // Initialize and apply inherents
42//! builder.initialize().await?;
43//! builder.apply_inherents().await?;
44//!
45//! // Apply user extrinsics
46//! for extrinsic in extrinsics {
47//!     match builder.apply_extrinsic(extrinsic).await? {
48//!         ApplyExtrinsicResult::Success { .. } => println!("Applied successfully"),
49//!         ApplyExtrinsicResult::DispatchFailed { error } => println!("Failed: {}", error),
50//!     }
51//! }
52//!
53//! // Finalize the block
54//! let (new_block, _prototype) = builder.finalize().await?;
55//! ```
56
57use crate::{
58	Block, BlockBuilderError, RuntimeCallResult, RuntimeExecutor,
59	inherent::{
60		InherentProvider,
61		relay::{PARA_INHERENT_PALLET, para_inherent_included_key},
62	},
63	strings::{
64		builder::runtime_api,
65		inherent::timestamp::slot_duration::{
66			PARACHAIN_FALLBACK_MS as DEFAULT_PARA_SLOT_DURATION_MS,
67			RELAY_CHAIN_FALLBACK_MS as DEFAULT_RELAY_SLOT_DURATION_MS,
68		},
69	},
70};
71use log::{debug, error, info};
72use scale::{Decode, Encode};
73use smoldot::executor::host::HostVmPrototype;
74use sp_core::blake2_256;
75use subxt::{Metadata, config::substrate::H256, metadata::types::StorageEntryType};
76
77/// Phase of the block building process.
78///
79/// Tracks the current state of the builder to enforce correct ordering:
80/// `Created` → `Initialized` → `InherentsApplied` → (extrinsics) → finalize
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
82pub enum BuilderPhase {
83	/// Builder created, `initialize()` not yet called.
84	#[default]
85	Created,
86	/// Block initialized via `Core_initialize_block`, ready for inherents.
87	Initialized,
88	/// Inherents applied, ready for user extrinsics and finalization.
89	InherentsApplied,
90}
91
92/// Result of applying an extrinsic to the block.
93#[derive(Debug, Clone)]
94pub enum ApplyExtrinsicResult {
95	/// Extrinsic was applied successfully.
96	Success {
97		/// Number of storage keys modified by this extrinsic.
98		storage_changes: usize,
99	},
100	/// Extrinsic dispatch failed.
101	///
102	/// Storage changes from the failed extrinsic are NOT applied.
103	DispatchFailed {
104		/// Error description from the runtime.
105		error: String,
106	},
107}
108
109/// Builder for constructing new blocks on a forked chain.
110///
111/// The `BlockBuilder` orchestrates the block production process by:
112/// 1. Initializing the block with `Core_initialize_block`
113/// 2. Applying inherent extrinsics from registered providers
114/// 3. Applying user extrinsics
115/// 4. Finalizing the block with `BlockBuilder_finalize_block`
116///
117/// # Storage Handling
118///
119/// Storage changes are applied directly to the parent block's storage layer.
120/// For failed extrinsics (dispatch errors), storage changes are NOT applied.
121///
122/// # Thread Safety
123///
124/// `BlockBuilder` is not `Sync` by default. It should be used from a single
125/// async task.
126///
127/// # Example
128///
129/// ```ignore
130/// use pop_fork::{Block, BlockBuilder, RuntimeExecutor, create_next_header};
131///
132/// // Create header for the new block
133/// let header = create_next_header(&parent_block, vec![]);
134///
135/// // Create builder with inherent providers
136/// let mut builder = BlockBuilder::new(parent_block, executor, header, inherent_providers, None, false);
137///
138/// // Build the block
139/// builder.initialize().await?;
140/// builder.apply_inherents().await?;
141///
142/// // Apply user extrinsics
143/// for extrinsic in user_extrinsics {
144///     match builder.apply_extrinsic(extrinsic).await? {
145///         ApplyExtrinsicResult::Success { storage_changes } => {
146///             println!("Applied with {} storage changes", storage_changes);
147///         }
148///         ApplyExtrinsicResult::DispatchFailed { error } => {
149///             println!("Dispatch failed: {}", error);
150///         }
151///     }
152/// }
153///
154/// // Finalize and get the new block
155/// let (new_block, _prototype) = builder.finalize().await?;
156/// ```
157pub struct BlockBuilder {
158	/// The parent block being extended.
159	parent: Block,
160	/// Runtime executor for calling runtime methods.
161	executor: RuntimeExecutor,
162	/// Registered inherent providers.
163	inherent_providers: Vec<Box<dyn InherentProvider>>,
164	/// Successfully applied extrinsics (inherents + user).
165	extrinsics: Vec<Vec<u8>>,
166	/// Encoded header for the new block.
167	header: Vec<u8>,
168	/// Current phase of block building.
169	phase: BuilderPhase,
170	/// Reusable VM prototype to avoid re-parsing WASM on each runtime call.
171	prototype: Option<HostVmPrototype>,
172	/// Whether to skip the storage prefetch during initialization.
173	///
174	/// After the first block build, all `StorageValue` keys and pallet prefix
175	/// pages are already cached in the `RemoteStorageLayer`. Repeating the
176	/// prefetch on every block wastes time iterating metadata and issuing
177	/// no-op cache lookups.
178	skip_prefetch: bool,
179}
180
181impl BlockBuilder {
182	/// Create a new block builder.
183	///
184	/// # Arguments
185	///
186	/// * `parent` - The parent block to build upon
187	/// * `executor` - Runtime executor for calling runtime methods
188	/// * `header` - Encoded header for the new block
189	/// * `inherent_providers` - Providers for generating inherent extrinsics
190	/// * `prototype` - Optional warm VM prototype to reuse from a previous block build. Passing a
191	///   compiled prototype avoids re-parsing and re-compiling the WASM runtime, which is
192	///   significant for large runtimes (~2.5 MB for Asset Hub).
193	/// * `skip_prefetch` - When `true`, skip the storage prefetch during initialization. Set this
194	///   after the first block build since all keys are already cached.
195	///
196	/// # Returns
197	///
198	/// A new `BlockBuilder` ready for initialization.
199	pub fn new(
200		parent: Block,
201		executor: RuntimeExecutor,
202		header: Vec<u8>,
203		inherent_providers: Vec<Box<dyn InherentProvider>>,
204		prototype: Option<HostVmPrototype>,
205		skip_prefetch: bool,
206	) -> Self {
207		Self {
208			parent,
209			executor,
210			inherent_providers,
211			extrinsics: Vec::new(),
212			header,
213			phase: BuilderPhase::Created,
214			prototype,
215			skip_prefetch,
216		}
217	}
218
219	/// Get the current list of successfully applied extrinsics.
220	///
221	/// This includes both inherent extrinsics and user extrinsics that
222	/// were successfully applied.
223	pub fn extrinsics(&self) -> &[Vec<u8>] {
224		&self.extrinsics
225	}
226
227	/// Get the current phase of block building.
228	pub fn phase(&self) -> BuilderPhase {
229		self.phase
230	}
231
232	/// Get the storage layer for the current fork.
233	///
234	/// The storage layer is shared across all blocks in the fork and tracks
235	/// all modifications. This provides access to the current working state.
236	fn storage(&self) -> &crate::LocalStorageLayer {
237		self.parent.storage()
238	}
239
240	/// Reset storage access counters. Call before a phase to measure it in isolation.
241	fn reset_storage_stats(&self) {
242		self.storage().remote().reset_stats();
243	}
244
245	/// Log the current storage access counters for a phase.
246	fn log_storage_stats(&self, phase: &str) {
247		let stats = self.storage().remote().stats();
248		debug!("[BlockBuilder] {phase} storage: {stats}");
249	}
250
251	/// Prefetch storage commonly accessed during block building.
252	///
253	/// Uses two strategies to pre-populate the cache before runtime execution:
254	///
255	/// 1. **StorageValue batch**: Fetches every `Plain` (single-key) storage item from metadata in
256	///    one RPC call. These are cheap and almost always read during block execution.
257	///
258	/// 2. **Single-page pallet scans**: For each pallet, fetches the first page (up to 200 keys) of
259	///    its 16-byte prefix in parallel. This covers small pallets entirely and pre-warms the
260	///    most-accessed slice of larger ones. Individual storage maps that aren't covered get
261	///    picked up by the speculative prefetch in `RemoteStorageLayer::get()` during execution.
262	///
263	/// Together these eliminate the vast majority of individual RPC round-trips
264	/// that would otherwise block WASM execution.
265	async fn prefetch_block_building_storage(&self) -> Result<(), BlockBuilderError> {
266		let remote = self.storage().remote();
267		let block_hash = self.storage().fork_block_hash();
268		let metadata = self.parent.metadata().await?;
269
270		// --- 1. Batch-fetch all StorageValue keys ---
271		let mut value_keys: Vec<Vec<u8>> = Vec::new();
272		let mut pallet_prefixes: Vec<Vec<u8>> = Vec::new();
273
274		for pallet in metadata.pallets() {
275			let pallet_hash = sp_core::twox_128(pallet.name().as_bytes());
276
277			if let Some(storage) = pallet.storage() {
278				for entry in storage.entries() {
279					if matches!(entry.entry_type(), StorageEntryType::Plain(_)) {
280						let entry_hash = sp_core::twox_128(entry.name().as_bytes());
281						value_keys.push([pallet_hash.as_slice(), entry_hash.as_slice()].concat());
282					}
283				}
284				pallet_prefixes.push(pallet_hash.to_vec());
285			}
286		}
287
288		if !value_keys.is_empty() {
289			let key_refs: Vec<&[u8]> = value_keys.iter().map(|k| k.as_slice()).collect();
290			if let Err(e) = remote.get_batch(block_hash, &key_refs).await {
291				log::debug!("[BlockBuilder] StorageValue batch fetch failed (non-fatal): {e}");
292			}
293		}
294
295		// --- 2. Single-page pallet scans (in parallel) ---
296		// Individual storage maps not covered here get picked up by the
297		// speculative prefetch at the 32-byte level during execution.
298		// Scan failures are non-fatal: the speculative prefetch and individual
299		// fetches during execution will pick up any keys we missed here.
300		let page_size = crate::strings::builder::PREFETCH_PAGE_SIZE;
301		let scan_futures: Vec<_> = pallet_prefixes
302			.iter()
303			.map(|prefix| remote.prefetch_prefix_single_page(block_hash, prefix, page_size))
304			.collect();
305		let scan_results = futures::future::join_all(scan_futures).await;
306		let mut scan_keys = 0usize;
307		let mut scan_errors = 0usize;
308		for result in scan_results {
309			match result {
310				Ok(count) => scan_keys += count,
311				Err(e) => {
312					scan_errors += 1;
313					log::debug!("[BlockBuilder] Pallet scan failed (non-fatal): {e}");
314				},
315			}
316		}
317
318		if scan_errors > 0 {
319			debug!(
320				"[BlockBuilder] Prefetched {} StorageValue + {} map keys ({} pallets, {} scans failed)",
321				value_keys.len(),
322				scan_keys,
323				pallet_prefixes.len(),
324				scan_errors,
325			);
326		} else {
327			debug!(
328				"[BlockBuilder] Prefetched {} StorageValue + {} map keys ({} pallets)",
329				value_keys.len(),
330				scan_keys,
331				pallet_prefixes.len(),
332			);
333		}
334		Ok(())
335	}
336
337	/// Initialize the block by calling `Core_initialize_block`.
338	///
339	/// This must be called before applying any inherents or extrinsics.
340	/// Can only be called once (in `Created` phase).
341	///
342	/// # Returns
343	///
344	/// The runtime call result containing storage diff and logs.
345	///
346	/// # Errors
347	///
348	/// Returns an error if:
349	/// - The block has already been initialized
350	/// - The runtime call fails
351	pub async fn initialize(&mut self) -> Result<RuntimeCallResult, BlockBuilderError> {
352		if self.phase != BuilderPhase::Created {
353			// Already past Created phase
354			return Err(BlockBuilderError::AlreadyInitialized);
355		}
356
357		// Prefetch storage keys commonly accessed during block building.
358		// Skipped for subsequent blocks since keys are already cached.
359		if !self.skip_prefetch {
360			debug!("[BlockBuilder] Prefetching block building storage...");
361			self.prefetch_block_building_storage().await?;
362		}
363
364		// Call Core_initialize_block with the header
365		debug!("[BlockBuilder] Calling Core_initialize_block...");
366		self.reset_storage_stats();
367		let (result, proto) = self
368			.executor
369			.call_with_prototype(
370				self.prototype.take(),
371				runtime_api::CORE_INITIALIZE_BLOCK,
372				&self.header,
373				self.storage(),
374			)
375			.await;
376		self.prototype = proto;
377		let result = result.map_err(|e| {
378			error!("[BlockBuilder] Core_initialize_block FAILED: {e}");
379			e
380		})?;
381		self.log_storage_stats("Core_initialize_block");
382		debug!("[BlockBuilder] Core_initialize_block OK");
383		debug!(
384			"[BlockBuilder] Building block on top of #{} (0x{}...)",
385			self.parent.number,
386			hex::encode(&self.parent.hash.0[..4])
387		);
388
389		// Apply storage changes
390		self.apply_storage_diff(&result.storage_diff)?;
391
392		self.phase = BuilderPhase::Initialized;
393		Ok(result)
394	}
395
396	/// Apply inherent extrinsics from all registered providers.
397	///
398	/// This calls each registered `InherentProvider` to generate inherent
399	/// extrinsics, then applies them to the block. Can only be called once,
400	/// after `initialize()` and before any `apply_extrinsic()` calls.
401	///
402	/// # Returns
403	///
404	/// A vector of runtime call results, one for each applied inherent.
405	///
406	/// # Errors
407	///
408	/// Returns an error if:
409	/// - The block has not been initialized
410	/// - Inherents have already been applied
411	/// - Any inherent provider fails
412	/// - Any inherent extrinsic fails to apply
413	pub async fn apply_inherents(&mut self) -> Result<Vec<RuntimeCallResult>, BlockBuilderError> {
414		match self.phase {
415			BuilderPhase::Created => return Err(BlockBuilderError::NotInitialized),
416			BuilderPhase::InherentsApplied => {
417				return Err(BlockBuilderError::InherentsAlreadyApplied);
418			},
419			BuilderPhase::Initialized => {}, // Expected phase
420		}
421
422		self.reset_storage_stats();
423		let mut results = Vec::new();
424
425		// Collect inherents from all providers first to avoid borrow conflicts
426		let mut all_inherents: Vec<(String, Vec<Vec<u8>>)> = Vec::new();
427		for provider in &self.inherent_providers {
428			let id = provider.identifier().to_string();
429			debug!("[BlockBuilder] Getting inherents from provider: {}", id);
430			let inherents = provider.provide(&self.parent, &self.executor).await.map_err(|e| {
431				error!("[BlockBuilder] Provider {} FAILED: {e}", id);
432				BlockBuilderError::InherentProvider { provider: id.clone(), message: e.to_string() }
433			})?;
434			all_inherents.push((id, inherents));
435		}
436
437		// Apply collected inherents
438		for (provider_id, inherents) in &all_inherents {
439			for (i, inherent) in inherents.iter().enumerate() {
440				let result = self.call_apply_extrinsic(inherent).await.map_err(|e| {
441					error!("[BlockBuilder] Inherent {i} from {} FAILED: {e}", provider_id);
442					e
443				})?;
444				// Check dispatch result - format: Result<Result<(), DispatchError>,
445				// TransactionValidityError> First byte: 0x00 = Ok (applied), 0x01 = Err
446				// (transaction invalid) If Ok, second byte: 0x00 = dispatch success, 0x01 =
447				// dispatch error
448				let dispatch_ok = match (result.output.first(), result.output.get(1)) {
449					(Some(0x00), Some(0x00)) => {
450						debug!(
451							"[BlockBuilder] Inherent {i} from {} OK (dispatch success)",
452							provider_id
453						);
454						true
455					},
456					(Some(0x00), Some(0x01)) => {
457						error!(
458							"[BlockBuilder] Inherent {i} from {} DISPATCH FAILED: {:?}",
459							provider_id,
460							hex::encode(&result.output)
461						);
462						false
463					},
464					(Some(0x01), _) => {
465						error!(
466							"[BlockBuilder] Inherent {i} from {} INVALID: {:?}",
467							provider_id,
468							hex::encode(&result.output)
469						);
470						false
471					},
472					_ => false,
473				};
474
475				// For inherents, dispatch failures are fatal
476				if !dispatch_ok {
477					return Err(BlockBuilderError::InherentProvider {
478						provider: provider_id.clone(),
479						message: format!(
480							"Inherent dispatch failed: {}",
481							hex::encode(&result.output)
482						),
483					});
484				}
485
486				// Apply storage changes
487				self.apply_storage_diff(&result.storage_diff)?;
488				self.extrinsics.push(inherent.clone());
489				results.push(result);
490			}
491		}
492
493		// Mock relay chain storage if needed
494		self.mock_relay_chain_inherent().await?;
495
496		self.log_storage_stats("apply_inherents");
497		self.phase = BuilderPhase::InherentsApplied;
498		Ok(results)
499	}
500
501	/// Mock the `ParaInherent::Included` storage for relay chain runtimes.
502	///
503	/// Relay chains require `ParaInherent::Included` to be set every block,
504	/// otherwise `on_finalize` panics. Instead of constructing a valid
505	/// `paras_inherent.enter` extrinsic, we directly set the storage.
506	///
507	/// This is a no-op for non-relay chains (chains without `ParaInherent` pallet).
508	async fn mock_relay_chain_inherent(&self) -> Result<(), BlockBuilderError> {
509		let metadata = self.parent.metadata().await?;
510
511		// Check if this is a relay chain (has ParaInherent pallet)
512		if metadata.pallet_by_name(PARA_INHERENT_PALLET).is_none() {
513			return Ok(());
514		}
515
516		// Set ParaInherent::Included to Some(())
517		// The value is () which encodes to empty bytes, but FRAME stores Some(()) as existing key
518		let key = para_inherent_included_key();
519		self.storage().set(&key, Some(&().encode()))?;
520
521		Ok(())
522	}
523
524	/// Apply a user extrinsic to the block.
525	///
526	/// This calls `BlockBuilder_apply_extrinsic` and checks the dispatch result.
527	/// Storage changes are only applied if the extrinsic succeeds.
528	///
529	/// # Arguments
530	///
531	/// * `extrinsic` - Encoded extrinsic to apply
532	///
533	/// # Returns
534	///
535	/// - `ApplyExtrinsicResult::Success` if the extrinsic was applied
536	/// - `ApplyExtrinsicResult::DispatchFailed` if dispatch failed
537	///
538	/// # Errors
539	///
540	/// Returns an error if:
541	/// - The block has not been initialized
542	/// - Inherents have not been applied yet
543	/// - The runtime call itself fails (not dispatch failure)
544	pub async fn apply_extrinsic(
545		&mut self,
546		extrinsic: Vec<u8>,
547	) -> Result<ApplyExtrinsicResult, BlockBuilderError> {
548		match self.phase {
549			BuilderPhase::Created => return Err(BlockBuilderError::NotInitialized),
550			BuilderPhase::Initialized => return Err(BlockBuilderError::InherentsNotApplied),
551			BuilderPhase::InherentsApplied => {}, // Expected phase
552		}
553
554		self.reset_storage_stats();
555		let result = self.call_apply_extrinsic(&extrinsic).await?;
556
557		// Decode the dispatch result
558		// Format: Result<Result<(), DispatchError>, TransactionValidityError>
559		// For simplicity, we check if the first byte indicates success (0x00 = Ok)
560		let is_success = result.output.first().map(|&b| b == 0x00).unwrap_or(false);
561
562		if is_success {
563			// Success - apply storage changes
564			let storage_changes = result.storage_diff.len();
565			self.apply_storage_diff(&result.storage_diff)?;
566			let ext_hash = blake2_256(&extrinsic);
567			self.log_storage_stats("apply_extrinsic");
568
569			// Log with human-readable pallet/call when decodable.
570			match self.decode_extrinsic_call(&extrinsic).await {
571				Some(decoded) => {
572					let mut msg = format!(
573						"[BlockBuilder] Extrinsic included in block\n  \
574						 Pallet: {}\n  \
575						 Call:   {}\n  \
576						 Hash:   0x{}",
577						decoded.pallet,
578						decoded.call,
579						hex::encode(ext_hash),
580					);
581					for (name, value) in &decoded.args {
582						msg.push_str(&format!("\n  {name}: {value}"));
583					}
584					info!("{msg}");
585				},
586				None => info!(
587					"[BlockBuilder] Extrinsic included in block\n  \
588					 Hash: 0x{}",
589					hex::encode(ext_hash),
590				),
591			}
592
593			self.extrinsics.push(extrinsic);
594			Ok(ApplyExtrinsicResult::Success { storage_changes })
595		} else {
596			// Failed - do NOT apply storage changes.
597			let error = format!("Dispatch failed: {:?}", hex::encode(&result.output));
598			Ok(ApplyExtrinsicResult::DispatchFailed { error })
599		}
600	}
601
602	/// Call the `BlockBuilder_apply_extrinsic` runtime API.
603	///
604	/// This is a helper function that executes the runtime call without
605	/// interpreting the result or applying storage changes.
606	async fn call_apply_extrinsic(
607		&mut self,
608		extrinsic: &[u8],
609	) -> Result<RuntimeCallResult, BlockBuilderError> {
610		let (result, proto) = self
611			.executor
612			.call_with_prototype(
613				self.prototype.take(),
614				runtime_api::BLOCK_BUILDER_APPLY_EXTRINSIC,
615				extrinsic,
616				self.storage(),
617			)
618			.await;
619		self.prototype = proto;
620		result.map_err(Into::into)
621	}
622
623	/// Finalize the block by calling `BlockBuilder_finalize_block`.
624	///
625	/// This consumes the builder and returns the newly constructed block along
626	/// with the warm VM prototype. The prototype can be passed to the next
627	/// [`BlockBuilder::new`] call to avoid re-compiling the WASM runtime.
628	///
629	/// # Returns
630	///
631	/// A tuple of `(block, prototype)` where `prototype` is the warm VM prototype
632	/// that can be reused for the next block build.
633	///
634	/// # Errors
635	///
636	/// Returns an error if:
637	/// - The block has not been initialized
638	/// - Inherents have not been applied
639	/// - The runtime call fails
640	pub async fn finalize(mut self) -> Result<(Block, Option<HostVmPrototype>), BlockBuilderError> {
641		match self.phase {
642			BuilderPhase::Created => return Err(BlockBuilderError::NotInitialized),
643			BuilderPhase::Initialized => return Err(BlockBuilderError::InherentsNotApplied),
644			BuilderPhase::InherentsApplied => {}, // Expected phase
645		}
646
647		// Call BlockBuilder_finalize_block
648		debug!("[BlockBuilder] Calling BlockBuilder_finalize_block...");
649		self.reset_storage_stats();
650		let (result, proto) = self
651			.executor
652			.call_with_prototype(
653				self.prototype.take(),
654				runtime_api::BLOCK_BUILDER_FINALIZE_BLOCK,
655				&[],
656				self.storage(),
657			)
658			.await;
659		self.prototype = proto;
660		let result = result.map_err(|e| {
661			error!("[BlockBuilder] BlockBuilder_finalize_block FAILED: {e}");
662			e
663		})?;
664		self.log_storage_stats("finalize_block");
665		debug!("[BlockBuilder] BlockBuilder_finalize_block OK");
666
667		// Apply final storage changes
668		self.apply_storage_diff(&result.storage_diff)?;
669
670		// Check if runtime code changed in the parent block (runtime upgrade occurred).
671		// If code changed in parent block X, block X+1 (current) uses the new runtime,
672		// so we need to register the new metadata at current block.
673		if self.storage().has_code_changed_at(self.parent.number)? {
674			self.register_new_metadata().await?;
675		}
676
677		// The result contains the final header
678		let final_header = result.output;
679
680		// Compute block hash from header (blake2_256)
681		let block_hash = sp_core::blake2_256(&final_header);
682
683		// Create the new block
684		let new_block = self
685			.parent
686			.child(
687				subxt::config::substrate::H256::from_slice(&block_hash),
688				final_header,
689				self.extrinsics,
690			)
691			.await?;
692
693		// Extract the warm prototype for reuse in the next block build.
694		// If a runtime upgrade occurred in the current block, the prototype
695		// is stale and should be discarded by the caller.
696		let prototype = self.prototype.take();
697
698		Ok((new_block, prototype))
699	}
700
701	/// Check if a runtime upgrade occurred during this block's execution.
702	///
703	/// Returns `true` if `:code` was modified in the current block, meaning
704	/// the next block will need a fresh executor and prototype compiled from
705	/// the new runtime code.
706	pub fn runtime_upgraded(&self) -> bool {
707		let current_block = self.storage().get_current_block_number();
708		self.storage().has_code_changed_at(current_block).unwrap_or(false)
709	}
710
711	/// Register new metadata after a runtime upgrade.
712	///
713	/// This is called when the `:code` storage key was modified in the parent block,
714	/// indicating a runtime upgrade occurred. Since the BlockBuilder's executor is
715	/// already running the new runtime (it was initialized with code from current state),
716	/// we simply call `Metadata_metadata` using the existing executor and register
717	/// the metadata for the current block (the first block using the new runtime).
718	async fn register_new_metadata(&mut self) -> Result<(), BlockBuilderError> {
719		let current_block_number = self.storage().get_current_block_number();
720
721		// The executor is already running the new runtime (initialized from current state
722		// which includes the parent block's code change), so we can use it directly
723		let (result, proto) = self
724			.executor
725			.call_with_prototype(
726				self.prototype.take(),
727				runtime_api::METADATA_METADATA,
728				&[],
729				self.storage(),
730			)
731			.await;
732		self.prototype = proto;
733		let metadata_result = result?;
734
735		// Decode the metadata (output is OpaqueMetadata which is just Vec<u8>)
736		// The actual metadata is SCALE-encoded inside
737		let metadata_bytes: Vec<u8> = Decode::decode(&mut metadata_result.output.as_slice())
738			.map_err(|e| {
739				BlockBuilderError::Codec(format!("Failed to decode metadata wrapper: {}", e))
740			})?;
741
742		let new_metadata = Metadata::decode(&mut metadata_bytes.as_slice())
743			.map_err(|e| BlockBuilderError::Codec(format!("Failed to decode metadata: {}", e)))?;
744
745		// Register the new metadata version for the current block
746		// (the first block using the new runtime)
747		self.storage().register_metadata_version(current_block_number, new_metadata)?;
748
749		Ok(())
750	}
751
752	/// Attempt to decode pallet name, call name, and arguments from raw
753	/// extrinsic bytes. Returns `None` if decoding fails (never panics).
754	///
755	/// ## Extrinsic layout (v4)
756	///
757	/// ```text
758	/// [compact_len] [version_byte] [signer+sig+extensions (signed only)] [pallet_idx call_idx args...]
759	/// ```
760	///
761	/// For unsigned extrinsics the call starts at a fixed offset. For signed
762	/// extrinsics the address and signature are parsed deterministically, then
763	/// a short scan over the extensions area finds the call. Candidates are
764	/// validated by checking the remaining bytes against the minimum encoded
765	/// size of the call's arguments to reject false positives.
766	async fn decode_extrinsic_call(&self, extrinsic: &[u8]) -> Option<DecodedCall> {
767		let metadata = self.parent.metadata().await.ok()?;
768		let remaining = strip_compact_prefix(extrinsic)?;
769
770		let version_byte = *remaining.first()?;
771		let is_signed = version_byte & 0x80 != 0;
772
773		if !is_signed {
774			let pi = *remaining.get(1)?;
775			let ci = *remaining.get(2)?;
776			return try_decode_call(&metadata, pi, ci, remaining.get(3..)?);
777		}
778
779		find_signed_call(&metadata, remaining)
780	}
781
782	/// Apply storage diff to the parent's storage layer.
783	fn apply_storage_diff(
784		&self,
785		diff: &[(Vec<u8>, Option<Vec<u8>>)],
786	) -> Result<(), BlockBuilderError> {
787		if diff.is_empty() {
788			return Ok(());
789		}
790
791		let entries: Vec<(&[u8], Option<&[u8]>)> =
792			diff.iter().map(|(k, v)| (k.as_slice(), v.as_deref())).collect();
793
794		self.storage().set_batch(&entries)?;
795		Ok(())
796	}
797}
798
799// ---------------------------------------------------------------------------
800// Extrinsic call decoding helpers
801// ---------------------------------------------------------------------------
802
803/// Decoded extrinsic call with pallet, call name, and arguments.
804struct DecodedCall {
805	pallet: String,
806	call: String,
807	args: Vec<(String, String)>,
808}
809
810/// Strip the SCALE compact length prefix, returning the remainder.
811fn strip_compact_prefix(bytes: &[u8]) -> Option<&[u8]> {
812	let mode = bytes.first()? & 0b11;
813	match mode {
814		0b00 => bytes.get(1..),
815		0b01 => bytes.get(2..),
816		0b10 => bytes.get(4..),
817		_ => None,
818	}
819}
820
821/// Parse the signed extrinsic header deterministically, then scan the
822/// extensions area for a valid call. False positives are rejected because
823/// `scale_value::scale::decode_as_type` must successfully consume each
824/// argument field.
825fn find_signed_call(metadata: &Metadata, remaining: &[u8]) -> Option<DecodedCall> {
826	// Parse MultiAddress (version byte at offset 0, address variant at 1).
827	let addr_variant = *remaining.get(1)?;
828	let addr_data_len = match addr_variant {
829		0x00 | 0x03 => 32, // Id / Address32
830		0x04 => 20,        // Address20
831		_ => return None,
832	};
833	let after_addr = 1 + 1 + addr_data_len; // version + variant + data
834
835	// Candidate offsets for the start of extensions, trying both:
836	//  - standard MultiSignature with variant byte (1 + 64 or 65)
837	//  - mock format without variant byte (just 64 bytes)
838	let sig_ends: [Option<usize>; 2] = [
839		remaining.get(after_addr).and_then(|&v| match v {
840			0x00 | 0x01 => Some(after_addr + 1 + 64),
841			0x02 => Some(after_addr + 1 + 65),
842			_ => None,
843		}),
844		Some(after_addr + 64),
845	];
846
847	// Extensions are typically 3-20 bytes; scan a generous window.
848	const MAX_EXT_SCAN: usize = 30;
849
850	for ext_start in sig_ends.into_iter().flatten() {
851		let scan_end = (ext_start + MAX_EXT_SCAN).min(remaining.len().saturating_sub(2));
852		for offset in ext_start..=scan_end {
853			let pi = *remaining.get(offset)?;
854			let ci = *remaining.get(offset + 1)?;
855			if let Some(decoded) = try_decode_call(metadata, pi, ci, remaining.get(offset + 2..)?) {
856				return Some(decoded);
857			}
858		}
859	}
860
861	None
862}
863
864/// Try to match `(pallet_index, call_index)` against metadata and validate
865/// by fully decoding all argument fields with `scale_value`. If any field
866/// fails to decode, this candidate is rejected.
867fn try_decode_call(
868	metadata: &Metadata,
869	pallet_index: u8,
870	call_index: u8,
871	args_bytes: &[u8],
872) -> Option<DecodedCall> {
873	let pallet = metadata.pallets().find(|p| p.index() == pallet_index)?;
874	let call = pallet.call_variants()?.iter().find(|v| v.index == call_index)?;
875
876	let registry = metadata.types();
877	let mut cursor: &[u8] = args_bytes;
878	let mut args = Vec::new();
879
880	for field in &call.fields {
881		let value = scale_value::scale::decode_as_type(&mut cursor, field.ty.id, registry).ok()?;
882		let name = field.name.as_deref().unwrap_or("?").to_string();
883		let formatted = format_scale_value(&value)?;
884		args.push((name, formatted));
885	}
886
887	// The call is at the very end of the extrinsic, so all bytes must be consumed.
888	// Remaining bytes indicate a false positive match in the extensions area.
889	if !cursor.is_empty() {
890		return None;
891	}
892
893	Some(DecodedCall { pallet: pallet.name().to_string(), call: call.name.clone(), args })
894}
895
896/// Format byte sequences as UTF-8 strings when valid, otherwise as lowercase hex.
897fn format_bytes<T, W: std::fmt::Write>(
898	value: &scale_value::Value<T>,
899	mut writer: W,
900) -> Option<core::fmt::Result> {
901	let mut hex_buf = String::new();
902	let res = scale_value::stringify::custom_formatters::format_hex(value, &mut hex_buf);
903	match res {
904		Some(Ok(())) => {
905			// format_hex recognized it as a byte sequence. Try UTF-8 first.
906			let hex_str = hex_buf.trim_start_matches("0x");
907			if let Ok(bytes) = hex::decode(hex_str) &&
908				let Ok(s) = std::str::from_utf8(&bytes) &&
909				!s.is_empty() &&
910				s.bytes().all(|b| b.is_ascii_graphic() || b == b' ')
911			{
912				return Some(writer.write_fmt(format_args!("\"{s}\"")));
913			}
914			Some(writer.write_str(&hex_buf.to_lowercase()))
915		},
916		other => other,
917	}
918}
919
920/// Format a decoded `scale_value::Value` into a human-readable string.
921/// Uses the built-in hex formatter so byte arrays render as `0x...`.
922fn format_scale_value<T>(value: &scale_value::Value<T>) -> Option<String> {
923	let mut buf = String::new();
924	scale_value::stringify::to_writer_custom()
925		.compact()
926		.add_custom_formatter(|v, w| format_bytes(v, w))
927		.write(value, &mut buf)
928		.ok()?;
929	Some(buf)
930}
931
932/// Digest item for block headers.
933///
934/// Digest items contain consensus-related information that is included
935/// in the block header but not part of the main block body.
936#[derive(Debug, Clone, Encode, Decode)]
937pub enum DigestItem {
938	/// A pre-runtime digest item.
939	///
940	/// These are produced by the consensus engine before block execution.
941	/// Common uses include slot numbers for Aura/Babe.
942	#[codec(index = 6)]
943	PreRuntime(ConsensusEngineId, Vec<u8>),
944
945	/// A consensus digest item.
946	///
947	/// These are produced during block execution for consensus-related data.
948	#[codec(index = 4)]
949	Consensus(ConsensusEngineId, Vec<u8>),
950
951	/// A seal digest item.
952	///
953	/// These are added after block execution, typically containing signatures.
954	#[codec(index = 5)]
955	Seal(ConsensusEngineId, Vec<u8>),
956
957	/// An "other" digest item.
958	///
959	/// For runtime-specific data that doesn't fit other categories.
960	#[codec(index = 0)]
961	Other(Vec<u8>),
962}
963
964/// Consensus engine identifier (4-byte ASCII).
965///
966/// Common identifiers:
967/// - `*b"aura"` - Aura consensus
968/// - `*b"BABE"` - Babe consensus
969/// - `*b"FRNK"` - GRANDPA finality
970pub type ConsensusEngineId = [u8; 4];
971
972/// Well-known consensus engine identifiers.
973pub mod consensus_engine {
974	use super::ConsensusEngineId;
975
976	/// Aura consensus engine identifier.
977	pub const AURA: ConsensusEngineId = *b"aura";
978
979	/// Babe consensus engine identifier.
980	pub const BABE: ConsensusEngineId = *b"BABE";
981
982	/// GRANDPA finality engine identifier.
983	pub const GRANDPA: ConsensusEngineId = *b"FRNK";
984}
985
986/// Internal header struct for encoding.
987#[derive(Encode)]
988struct Header {
989	parent_hash: H256,
990	#[codec(compact)]
991	number: u32,
992	state_root: H256,
993	extrinsics_root: H256,
994	digest: Vec<DigestItem>,
995}
996
997/// Create a header for the next block.
998///
999/// This helper creates a properly encoded header for use with `BlockBuilder`.
1000/// The header will have:
1001/// - `parent_hash` set to the parent block's hash
1002/// - `number` incremented from the parent
1003/// - `state_root` and `extrinsics_root` set to zero (computed by runtime)
1004/// - `digest` containing the provided digest items
1005///
1006/// # Arguments
1007///
1008/// * `parent` - The parent block to build upon
1009/// * `digest_items` - Digest items to include (e.g., slot information)
1010///
1011/// # Returns
1012///
1013/// Encoded header bytes ready for `BlockBuilder::new()`.
1014///
1015/// # Example
1016///
1017/// ```ignore
1018/// use pop_fork::{create_next_header, DigestItem, consensus_engine};
1019///
1020/// // Create header with Aura slot
1021/// let slot: u64 = 12345;
1022/// let header = create_next_header(
1023///     &parent_block,
1024///     vec![DigestItem::PreRuntime(consensus_engine::AURA, slot.encode())],
1025/// );
1026///
1027/// let builder = BlockBuilder::new(parent_block, executor, header, providers, None, false);
1028/// ```
1029pub fn create_next_header(parent: &Block, digest_items: Vec<DigestItem>) -> Vec<u8> {
1030	let header = Header {
1031		parent_hash: parent.hash,
1032		number: parent.number + 1,
1033		state_root: H256::zero(),      // Will be computed by runtime
1034		extrinsics_root: H256::zero(), // Will be computed by runtime
1035		digest: digest_items,
1036	};
1037	header.encode()
1038}
1039
1040/// Create a header for the next block with automatic slot digest injection.
1041///
1042/// This function automatically detects the consensus type (Aura/Babe) and
1043/// injects the appropriate slot digest into the header. The slot is calculated
1044/// based on the parent block's timestamp and slot duration.
1045///
1046/// # Arguments
1047///
1048/// * `parent` - The parent block to build upon
1049/// * `executor` - Runtime executor for calling runtime APIs
1050/// * `additional_digest_items` - Additional digest items to include (e.g., seal)
1051///
1052/// # Returns
1053///
1054/// Encoded header bytes ready for `BlockBuilder::new()`.
1055///
1056/// # Slot Calculation
1057///
1058/// The slot is calculated as:
1059/// ```text
1060/// next_timestamp = current_timestamp + slot_duration
1061/// next_slot = next_timestamp / slot_duration
1062/// ```
1063///
1064/// # Consensus Detection
1065///
1066/// The function detects the consensus type by checking runtime metadata:
1067/// - If `Aura` pallet exists → inject `PreRuntime(*b"aura", slot)`
1068/// - If `Babe` pallet exists → inject `PreRuntime(*b"BABE", slot)`
1069/// - Otherwise → no slot injection
1070///
1071/// # Example
1072///
1073/// ```ignore
1074/// use pop_fork::{create_next_header_with_slot, Block, RuntimeExecutor};
1075///
1076/// // Create header with automatic slot detection and injection
1077/// let header = create_next_header_with_slot(&parent, &executor, vec![]).await?;
1078/// let builder = BlockBuilder::new(parent_block, executor, header, providers, None, false);
1079/// ```
1080pub async fn create_next_header_with_slot(
1081	parent: &Block,
1082	executor: &RuntimeExecutor,
1083	additional_digest_items: Vec<DigestItem>,
1084	cached_slot_duration: Option<u64>,
1085) -> Result<Vec<u8>, BlockBuilderError> {
1086	use crate::inherent::{
1087		TimestampInherent,
1088		slot::{
1089			ConsensusType, calculate_next_slot, detect_consensus_type, encode_aura_slot,
1090			encode_babe_predigest,
1091		},
1092	};
1093
1094	let metadata = parent.metadata().await?;
1095	let storage = parent.storage();
1096
1097	// Detect consensus type from metadata
1098	let consensus_type = detect_consensus_type(&metadata);
1099
1100	// Build digest items
1101	let mut digest_items = Vec::new();
1102
1103	// Check if caller already provided a PreRuntime digest for this consensus
1104	let has_preruntime = additional_digest_items.iter().any(|item| match item {
1105		DigestItem::PreRuntime(engine, _) =>
1106			(consensus_type == ConsensusType::Aura && *engine == consensus_engine::AURA) ||
1107				(consensus_type == ConsensusType::Babe && *engine == consensus_engine::BABE),
1108		_ => false,
1109	});
1110
1111	// Inject slot digest if needed
1112	if !has_preruntime && consensus_type != ConsensusType::Unknown {
1113		// Get slot duration - use parachain default for Aura (most common),
1114		// relay default for Babe
1115		let default_duration = match consensus_type {
1116			ConsensusType::Aura => DEFAULT_PARA_SLOT_DURATION_MS,
1117			ConsensusType::Babe => DEFAULT_RELAY_SLOT_DURATION_MS,
1118			ConsensusType::Unknown => DEFAULT_RELAY_SLOT_DURATION_MS,
1119		};
1120
1121		let slot_duration = match cached_slot_duration {
1122			Some(d) => d,
1123			None =>
1124				TimestampInherent::get_slot_duration_from_runtime(
1125					executor,
1126					storage,
1127					&metadata,
1128					default_duration,
1129				)
1130				.await,
1131		};
1132
1133		// Read current timestamp from storage
1134		let timestamp_key = TimestampInherent::timestamp_now_key();
1135		let current_timestamp = match storage.get(parent.number, &timestamp_key).await? {
1136			Some(value) if value.value.is_some() => {
1137				let bytes = value.value.as_ref().expect("checked above");
1138				u64::decode(&mut bytes.as_slice()).unwrap_or(0)
1139			},
1140			_ => {
1141				// Genesis or early block: use system time as fallback
1142				std::time::SystemTime::now()
1143					.duration_since(std::time::UNIX_EPOCH)
1144					.map(|d| d.as_millis() as u64)
1145					.unwrap_or(0)
1146			},
1147		};
1148
1149		// Calculate next slot
1150		let next_slot = calculate_next_slot(current_timestamp, slot_duration);
1151
1152		// Create the appropriate PreRuntime digest
1153		// Aura: just the slot encoded as u64
1154		// Babe: a PreDigest::SecondaryPlain struct with authority_index and slot
1155		let (engine, slot_bytes) = match consensus_type {
1156			ConsensusType::Aura => (consensus_engine::AURA, encode_aura_slot(next_slot)),
1157			ConsensusType::Babe => {
1158				// Use authority_index 0 for forked execution (we're not a real validator)
1159				(consensus_engine::BABE, encode_babe_predigest(next_slot, 0))
1160			},
1161			ConsensusType::Unknown => unreachable!("checked above"),
1162		};
1163
1164		digest_items.push(DigestItem::PreRuntime(engine, slot_bytes));
1165	}
1166
1167	// Add caller-provided digest items
1168	digest_items.extend(additional_digest_items);
1169
1170	// Create and encode header using the existing function
1171	Ok(create_next_header(parent, digest_items))
1172}
1173
1174#[cfg(test)]
1175mod tests {
1176	use super::*;
1177
1178	/// Verifies that consensus engine constants have the correct values.
1179	#[test]
1180	fn consensus_engine_constants_are_correct() {
1181		assert_eq!(consensus_engine::AURA, *b"aura");
1182		assert_eq!(consensus_engine::BABE, *b"BABE");
1183		assert_eq!(consensus_engine::GRANDPA, *b"FRNK");
1184	}
1185}