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, ×tamp_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}