use crate::{
Block, BlockBuilderError, RuntimeCallResult, RuntimeExecutor,
inherent::{
InherentProvider,
relay::{PARA_INHERENT_PALLET, para_inherent_included_key},
},
strings::{
builder::runtime_api,
inherent::timestamp::slot_duration::{
PARACHAIN_FALLBACK_MS as DEFAULT_PARA_SLOT_DURATION_MS,
RELAY_CHAIN_FALLBACK_MS as DEFAULT_RELAY_SLOT_DURATION_MS,
},
},
};
use log::{debug, error, info};
use scale::{Decode, Encode};
use smoldot::executor::host::HostVmPrototype;
use sp_core::blake2_256;
use subxt::{Metadata, config::substrate::H256, metadata::types::StorageEntryType};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BuilderPhase {
#[default]
Created,
Initialized,
InherentsApplied,
}
#[derive(Debug, Clone)]
pub enum ApplyExtrinsicResult {
Success {
storage_changes: usize,
},
DispatchFailed {
error: String,
},
}
pub struct BlockBuilder {
parent: Block,
executor: RuntimeExecutor,
inherent_providers: Vec<Box<dyn InherentProvider>>,
extrinsics: Vec<Vec<u8>>,
header: Vec<u8>,
phase: BuilderPhase,
prototype: Option<HostVmPrototype>,
skip_prefetch: bool,
}
impl BlockBuilder {
pub fn new(
parent: Block,
executor: RuntimeExecutor,
header: Vec<u8>,
inherent_providers: Vec<Box<dyn InherentProvider>>,
prototype: Option<HostVmPrototype>,
skip_prefetch: bool,
) -> Self {
Self {
parent,
executor,
inherent_providers,
extrinsics: Vec::new(),
header,
phase: BuilderPhase::Created,
prototype,
skip_prefetch,
}
}
pub fn extrinsics(&self) -> &[Vec<u8>] {
&self.extrinsics
}
pub fn phase(&self) -> BuilderPhase {
self.phase
}
fn storage(&self) -> &crate::LocalStorageLayer {
self.parent.storage()
}
fn reset_storage_stats(&self) {
self.storage().remote().reset_stats();
}
fn log_storage_stats(&self, phase: &str) {
let stats = self.storage().remote().stats();
debug!("[BlockBuilder] {phase} storage: {stats}");
}
async fn prefetch_block_building_storage(&self) -> Result<(), BlockBuilderError> {
let remote = self.storage().remote();
let block_hash = self.storage().fork_block_hash();
let metadata = self.parent.metadata().await?;
let mut value_keys: Vec<Vec<u8>> = Vec::new();
let mut pallet_prefixes: Vec<Vec<u8>> = Vec::new();
for pallet in metadata.pallets() {
let pallet_hash = sp_core::twox_128(pallet.name().as_bytes());
if let Some(storage) = pallet.storage() {
for entry in storage.entries() {
if matches!(entry.entry_type(), StorageEntryType::Plain(_)) {
let entry_hash = sp_core::twox_128(entry.name().as_bytes());
value_keys.push([pallet_hash.as_slice(), entry_hash.as_slice()].concat());
}
}
pallet_prefixes.push(pallet_hash.to_vec());
}
}
if !value_keys.is_empty() {
let key_refs: Vec<&[u8]> = value_keys.iter().map(|k| k.as_slice()).collect();
if let Err(e) = remote.get_batch(block_hash, &key_refs).await {
log::debug!("[BlockBuilder] StorageValue batch fetch failed (non-fatal): {e}");
}
}
let page_size = crate::strings::builder::PREFETCH_PAGE_SIZE;
let scan_futures: Vec<_> = pallet_prefixes
.iter()
.map(|prefix| remote.prefetch_prefix_single_page(block_hash, prefix, page_size))
.collect();
let scan_results = futures::future::join_all(scan_futures).await;
let mut scan_keys = 0usize;
let mut scan_errors = 0usize;
for result in scan_results {
match result {
Ok(count) => scan_keys += count,
Err(e) => {
scan_errors += 1;
log::debug!("[BlockBuilder] Pallet scan failed (non-fatal): {e}");
},
}
}
if scan_errors > 0 {
debug!(
"[BlockBuilder] Prefetched {} StorageValue + {} map keys ({} pallets, {} scans failed)",
value_keys.len(),
scan_keys,
pallet_prefixes.len(),
scan_errors,
);
} else {
debug!(
"[BlockBuilder] Prefetched {} StorageValue + {} map keys ({} pallets)",
value_keys.len(),
scan_keys,
pallet_prefixes.len(),
);
}
Ok(())
}
pub async fn initialize(&mut self) -> Result<RuntimeCallResult, BlockBuilderError> {
if self.phase != BuilderPhase::Created {
return Err(BlockBuilderError::AlreadyInitialized);
}
if !self.skip_prefetch {
debug!("[BlockBuilder] Prefetching block building storage...");
self.prefetch_block_building_storage().await?;
}
debug!("[BlockBuilder] Calling Core_initialize_block...");
self.reset_storage_stats();
let (result, proto) = self
.executor
.call_with_prototype(
self.prototype.take(),
runtime_api::CORE_INITIALIZE_BLOCK,
&self.header,
self.storage(),
)
.await;
self.prototype = proto;
let result = result.map_err(|e| {
error!("[BlockBuilder] Core_initialize_block FAILED: {e}");
e
})?;
self.log_storage_stats("Core_initialize_block");
debug!("[BlockBuilder] Core_initialize_block OK");
debug!(
"[BlockBuilder] Building block on top of #{} (0x{}...)",
self.parent.number,
hex::encode(&self.parent.hash.0[..4])
);
self.apply_storage_diff(&result.storage_diff)?;
self.phase = BuilderPhase::Initialized;
Ok(result)
}
pub async fn apply_inherents(&mut self) -> Result<Vec<RuntimeCallResult>, BlockBuilderError> {
match self.phase {
BuilderPhase::Created => return Err(BlockBuilderError::NotInitialized),
BuilderPhase::InherentsApplied =>
return Err(BlockBuilderError::InherentsAlreadyApplied),
BuilderPhase::Initialized => {}, }
self.reset_storage_stats();
let mut results = Vec::new();
let mut all_inherents: Vec<(String, Vec<Vec<u8>>)> = Vec::new();
for provider in &self.inherent_providers {
let id = provider.identifier().to_string();
debug!("[BlockBuilder] Getting inherents from provider: {}", id);
let inherents = provider.provide(&self.parent, &self.executor).await.map_err(|e| {
error!("[BlockBuilder] Provider {} FAILED: {e}", id);
BlockBuilderError::InherentProvider { provider: id.clone(), message: e.to_string() }
})?;
all_inherents.push((id, inherents));
}
for (provider_id, inherents) in &all_inherents {
for (i, inherent) in inherents.iter().enumerate() {
let result = self.call_apply_extrinsic(inherent).await.map_err(|e| {
error!("[BlockBuilder] Inherent {i} from {} FAILED: {e}", provider_id);
e
})?;
let dispatch_ok = match (result.output.first(), result.output.get(1)) {
(Some(0x00), Some(0x00)) => {
debug!(
"[BlockBuilder] Inherent {i} from {} OK (dispatch success)",
provider_id
);
true
},
(Some(0x00), Some(0x01)) => {
error!(
"[BlockBuilder] Inherent {i} from {} DISPATCH FAILED: {:?}",
provider_id,
hex::encode(&result.output)
);
false
},
(Some(0x01), _) => {
error!(
"[BlockBuilder] Inherent {i} from {} INVALID: {:?}",
provider_id,
hex::encode(&result.output)
);
false
},
_ => false,
};
if !dispatch_ok {
return Err(BlockBuilderError::InherentProvider {
provider: provider_id.clone(),
message: format!(
"Inherent dispatch failed: {}",
hex::encode(&result.output)
),
});
}
self.apply_storage_diff(&result.storage_diff)?;
self.extrinsics.push(inherent.clone());
results.push(result);
}
}
self.mock_relay_chain_inherent().await?;
self.log_storage_stats("apply_inherents");
self.phase = BuilderPhase::InherentsApplied;
Ok(results)
}
async fn mock_relay_chain_inherent(&self) -> Result<(), BlockBuilderError> {
let metadata = self.parent.metadata().await?;
if metadata.pallet_by_name(PARA_INHERENT_PALLET).is_none() {
return Ok(());
}
let key = para_inherent_included_key();
self.storage().set(&key, Some(&().encode()))?;
Ok(())
}
pub async fn apply_extrinsic(
&mut self,
extrinsic: Vec<u8>,
) -> Result<ApplyExtrinsicResult, BlockBuilderError> {
match self.phase {
BuilderPhase::Created => return Err(BlockBuilderError::NotInitialized),
BuilderPhase::Initialized => return Err(BlockBuilderError::InherentsNotApplied),
BuilderPhase::InherentsApplied => {}, }
self.reset_storage_stats();
let result = self.call_apply_extrinsic(&extrinsic).await?;
let is_success = result.output.first().map(|&b| b == 0x00).unwrap_or(false);
if is_success {
let storage_changes = result.storage_diff.len();
self.apply_storage_diff(&result.storage_diff)?;
let ext_hash = blake2_256(&extrinsic);
self.log_storage_stats("apply_extrinsic");
match self.decode_extrinsic_call(&extrinsic).await {
Some(decoded) => {
let mut msg = format!(
"[BlockBuilder] Extrinsic included in block\n \
Pallet: {}\n \
Call: {}\n \
Hash: 0x{}",
decoded.pallet,
decoded.call,
hex::encode(ext_hash),
);
for (name, value) in &decoded.args {
msg.push_str(&format!("\n {name}: {value}"));
}
info!("{msg}");
},
None => info!(
"[BlockBuilder] Extrinsic included in block\n \
Hash: 0x{}",
hex::encode(ext_hash),
),
}
self.extrinsics.push(extrinsic);
Ok(ApplyExtrinsicResult::Success { storage_changes })
} else {
let error = format!("Dispatch failed: {:?}", hex::encode(&result.output));
Ok(ApplyExtrinsicResult::DispatchFailed { error })
}
}
async fn call_apply_extrinsic(
&mut self,
extrinsic: &[u8],
) -> Result<RuntimeCallResult, BlockBuilderError> {
let (result, proto) = self
.executor
.call_with_prototype(
self.prototype.take(),
runtime_api::BLOCK_BUILDER_APPLY_EXTRINSIC,
extrinsic,
self.storage(),
)
.await;
self.prototype = proto;
result.map_err(Into::into)
}
pub async fn finalize(mut self) -> Result<(Block, Option<HostVmPrototype>), BlockBuilderError> {
match self.phase {
BuilderPhase::Created => return Err(BlockBuilderError::NotInitialized),
BuilderPhase::Initialized => return Err(BlockBuilderError::InherentsNotApplied),
BuilderPhase::InherentsApplied => {}, }
debug!("[BlockBuilder] Calling BlockBuilder_finalize_block...");
self.reset_storage_stats();
let (result, proto) = self
.executor
.call_with_prototype(
self.prototype.take(),
runtime_api::BLOCK_BUILDER_FINALIZE_BLOCK,
&[],
self.storage(),
)
.await;
self.prototype = proto;
let result = result.map_err(|e| {
error!("[BlockBuilder] BlockBuilder_finalize_block FAILED: {e}");
e
})?;
self.log_storage_stats("finalize_block");
debug!("[BlockBuilder] BlockBuilder_finalize_block OK");
self.apply_storage_diff(&result.storage_diff)?;
if self.storage().has_code_changed_at(self.parent.number)? {
self.register_new_metadata().await?;
}
let final_header = result.output;
let block_hash = sp_core::blake2_256(&final_header);
let new_block = self
.parent
.child(
subxt::config::substrate::H256::from_slice(&block_hash),
final_header,
self.extrinsics,
)
.await?;
let prototype = self.prototype.take();
Ok((new_block, prototype))
}
pub fn runtime_upgraded(&self) -> bool {
let current_block = self.storage().get_current_block_number();
self.storage().has_code_changed_at(current_block).unwrap_or(false)
}
async fn register_new_metadata(&mut self) -> Result<(), BlockBuilderError> {
let current_block_number = self.storage().get_current_block_number();
let (result, proto) = self
.executor
.call_with_prototype(
self.prototype.take(),
runtime_api::METADATA_METADATA,
&[],
self.storage(),
)
.await;
self.prototype = proto;
let metadata_result = result?;
let metadata_bytes: Vec<u8> = Decode::decode(&mut metadata_result.output.as_slice())
.map_err(|e| {
BlockBuilderError::Codec(format!("Failed to decode metadata wrapper: {}", e))
})?;
let new_metadata = Metadata::decode(&mut metadata_bytes.as_slice())
.map_err(|e| BlockBuilderError::Codec(format!("Failed to decode metadata: {}", e)))?;
self.storage().register_metadata_version(current_block_number, new_metadata)?;
Ok(())
}
async fn decode_extrinsic_call(&self, extrinsic: &[u8]) -> Option<DecodedCall> {
let metadata = self.parent.metadata().await.ok()?;
let remaining = strip_compact_prefix(extrinsic)?;
let version_byte = *remaining.first()?;
let is_signed = version_byte & 0x80 != 0;
if !is_signed {
let pi = *remaining.get(1)?;
let ci = *remaining.get(2)?;
return try_decode_call(&metadata, pi, ci, remaining.get(3..)?);
}
find_signed_call(&metadata, remaining)
}
fn apply_storage_diff(
&self,
diff: &[(Vec<u8>, Option<Vec<u8>>)],
) -> Result<(), BlockBuilderError> {
if diff.is_empty() {
return Ok(());
}
let entries: Vec<(&[u8], Option<&[u8]>)> =
diff.iter().map(|(k, v)| (k.as_slice(), v.as_deref())).collect();
self.storage().set_batch(&entries)?;
Ok(())
}
}
struct DecodedCall {
pallet: String,
call: String,
args: Vec<(String, String)>,
}
fn strip_compact_prefix(bytes: &[u8]) -> Option<&[u8]> {
let mode = bytes.first()? & 0b11;
match mode {
0b00 => bytes.get(1..),
0b01 => bytes.get(2..),
0b10 => bytes.get(4..),
_ => None,
}
}
fn find_signed_call(metadata: &Metadata, remaining: &[u8]) -> Option<DecodedCall> {
let addr_variant = *remaining.get(1)?;
let addr_data_len = match addr_variant {
0x00 | 0x03 => 32, 0x04 => 20, _ => return None,
};
let after_addr = 1 + 1 + addr_data_len;
let sig_ends: [Option<usize>; 2] = [
remaining.get(after_addr).and_then(|&v| match v {
0x00 | 0x01 => Some(after_addr + 1 + 64),
0x02 => Some(after_addr + 1 + 65),
_ => None,
}),
Some(after_addr + 64),
];
const MAX_EXT_SCAN: usize = 30;
for ext_start in sig_ends.into_iter().flatten() {
let scan_end = (ext_start + MAX_EXT_SCAN).min(remaining.len().saturating_sub(2));
for offset in ext_start..=scan_end {
let pi = *remaining.get(offset)?;
let ci = *remaining.get(offset + 1)?;
if let Some(decoded) = try_decode_call(metadata, pi, ci, remaining.get(offset + 2..)?) {
return Some(decoded);
}
}
}
None
}
fn try_decode_call(
metadata: &Metadata,
pallet_index: u8,
call_index: u8,
args_bytes: &[u8],
) -> Option<DecodedCall> {
let pallet = metadata.pallets().find(|p| p.index() == pallet_index)?;
let call = pallet.call_variants()?.iter().find(|v| v.index == call_index)?;
let registry = metadata.types();
let mut cursor: &[u8] = args_bytes;
let mut args = Vec::new();
for field in &call.fields {
let value = scale_value::scale::decode_as_type(&mut cursor, field.ty.id, registry).ok()?;
let name = field.name.as_deref().unwrap_or("?").to_string();
let formatted = format_scale_value(&value)?;
args.push((name, formatted));
}
if !cursor.is_empty() {
return None;
}
Some(DecodedCall { pallet: pallet.name().to_string(), call: call.name.clone(), args })
}
fn format_bytes<T, W: std::fmt::Write>(
value: &scale_value::Value<T>,
mut writer: W,
) -> Option<core::fmt::Result> {
let mut hex_buf = String::new();
let res = scale_value::stringify::custom_formatters::format_hex(value, &mut hex_buf);
match res {
Some(Ok(())) => {
let hex_str = hex_buf.trim_start_matches("0x");
if let Ok(bytes) = hex::decode(hex_str) &&
let Ok(s) = std::str::from_utf8(&bytes) &&
!s.is_empty() &&
s.bytes().all(|b| b.is_ascii_graphic() || b == b' ')
{
return Some(writer.write_fmt(format_args!("\"{s}\"")));
}
Some(writer.write_str(&hex_buf.to_lowercase()))
},
other => other,
}
}
fn format_scale_value<T>(value: &scale_value::Value<T>) -> Option<String> {
let mut buf = String::new();
scale_value::stringify::to_writer_custom()
.compact()
.add_custom_formatter(|v, w| format_bytes(v, w))
.write(value, &mut buf)
.ok()?;
Some(buf)
}
#[derive(Debug, Clone, Encode, Decode)]
pub enum DigestItem {
#[codec(index = 6)]
PreRuntime(ConsensusEngineId, Vec<u8>),
#[codec(index = 4)]
Consensus(ConsensusEngineId, Vec<u8>),
#[codec(index = 5)]
Seal(ConsensusEngineId, Vec<u8>),
#[codec(index = 0)]
Other(Vec<u8>),
}
pub type ConsensusEngineId = [u8; 4];
pub mod consensus_engine {
use super::ConsensusEngineId;
pub const AURA: ConsensusEngineId = *b"aura";
pub const BABE: ConsensusEngineId = *b"BABE";
pub const GRANDPA: ConsensusEngineId = *b"FRNK";
}
#[derive(Encode)]
struct Header {
parent_hash: H256,
#[codec(compact)]
number: u32,
state_root: H256,
extrinsics_root: H256,
digest: Vec<DigestItem>,
}
pub fn create_next_header(parent: &Block, digest_items: Vec<DigestItem>) -> Vec<u8> {
let header = Header {
parent_hash: parent.hash,
number: parent.number + 1,
state_root: H256::zero(), extrinsics_root: H256::zero(), digest: digest_items,
};
header.encode()
}
pub async fn create_next_header_with_slot(
parent: &Block,
executor: &RuntimeExecutor,
additional_digest_items: Vec<DigestItem>,
cached_slot_duration: Option<u64>,
) -> Result<Vec<u8>, BlockBuilderError> {
use crate::inherent::{
TimestampInherent,
slot::{
ConsensusType, calculate_next_slot, detect_consensus_type, encode_aura_slot,
encode_babe_predigest,
},
};
let metadata = parent.metadata().await?;
let storage = parent.storage();
let consensus_type = detect_consensus_type(&metadata);
let mut digest_items = Vec::new();
let has_preruntime = additional_digest_items.iter().any(|item| match item {
DigestItem::PreRuntime(engine, _) =>
(consensus_type == ConsensusType::Aura && *engine == consensus_engine::AURA) ||
(consensus_type == ConsensusType::Babe && *engine == consensus_engine::BABE),
_ => false,
});
if !has_preruntime && consensus_type != ConsensusType::Unknown {
let default_duration = match consensus_type {
ConsensusType::Aura => DEFAULT_PARA_SLOT_DURATION_MS,
ConsensusType::Babe => DEFAULT_RELAY_SLOT_DURATION_MS,
ConsensusType::Unknown => DEFAULT_RELAY_SLOT_DURATION_MS,
};
let slot_duration = match cached_slot_duration {
Some(d) => d,
None =>
TimestampInherent::get_slot_duration_from_runtime(
executor,
storage,
&metadata,
default_duration,
)
.await,
};
let timestamp_key = TimestampInherent::timestamp_now_key();
let current_timestamp = match storage.get(parent.number, ×tamp_key).await? {
Some(value) if value.value.is_some() => {
let bytes = value.value.as_ref().expect("checked above");
u64::decode(&mut bytes.as_slice()).unwrap_or(0)
},
_ => {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
},
};
let next_slot = calculate_next_slot(current_timestamp, slot_duration);
let (engine, slot_bytes) = match consensus_type {
ConsensusType::Aura => (consensus_engine::AURA, encode_aura_slot(next_slot)),
ConsensusType::Babe => {
(consensus_engine::BABE, encode_babe_predigest(next_slot, 0))
},
ConsensusType::Unknown => unreachable!("checked above"),
};
digest_items.push(DigestItem::PreRuntime(engine, slot_bytes));
}
digest_items.extend(additional_digest_items);
Ok(create_next_header(parent, digest_items))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn consensus_engine_constants_are_correct() {
assert_eq!(consensus_engine::AURA, *b"aura");
assert_eq!(consensus_engine::BABE, *b"BABE");
assert_eq!(consensus_engine::GRANDPA, *b"FRNK");
}
mod sequential {
use super::*;
use crate::{Block, ExecutorConfig, RuntimeExecutor, testing::TestContext};
struct BlockBuilderTestContext {
#[allow(dead_code)]
base: TestContext,
block: Block,
executor: RuntimeExecutor,
}
async fn create_test_context() -> BlockBuilderTestContext {
create_test_context_with_config(None).await
}
async fn create_test_context_with_config(
config: Option<ExecutorConfig>,
) -> BlockBuilderTestContext {
let base = TestContext::for_storage().await;
let block_hash = base.block_hash();
let runtime_code =
base.rpc().runtime_code(block_hash).await.expect("Failed to fetch runtime code");
let block = Block::fork_point(&base.endpoint, base.cache().clone(), block_hash.into())
.await
.expect("Failed to create fork point");
let executor = match config {
Some(cfg) => RuntimeExecutor::with_config(runtime_code, None, cfg),
None => RuntimeExecutor::new(runtime_code, None),
}
.expect("Failed to create executor");
BlockBuilderTestContext { base, block, executor }
}
#[tokio::test(flavor = "multi_thread")]
async fn new_creates_builder_with_empty_extrinsics() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let builder = BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
assert!(builder.extrinsics().is_empty());
}
#[tokio::test(flavor = "multi_thread")]
async fn initialize_succeeds_and_modifies_storage() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
let result = builder.initialize().await.expect("initialize failed");
assert!(!result.storage_diff.is_empty());
}
#[tokio::test(flavor = "multi_thread")]
async fn initialize_twice_fails() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
builder.initialize().await.expect("first initialize failed");
let result = builder.initialize().await;
assert!(matches!(result, Err(BlockBuilderError::AlreadyInitialized)));
}
#[tokio::test(flavor = "multi_thread")]
async fn apply_inherents_without_providers_returns_empty() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
builder.initialize().await.expect("initialize failed");
let results = builder.apply_inherents().await.expect("apply_inherents failed");
assert!(results.is_empty());
}
#[tokio::test(flavor = "multi_thread")]
async fn apply_inherents_before_initialize_fails() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
let result = builder.apply_inherents().await;
assert!(matches!(result, Err(BlockBuilderError::NotInitialized)));
}
#[tokio::test(flavor = "multi_thread")]
async fn apply_extrinsic_before_initialize_fails() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
let result = builder.apply_extrinsic(vec![0x00]).await;
assert!(matches!(result, Err(BlockBuilderError::NotInitialized)));
}
#[tokio::test(flavor = "multi_thread")]
async fn finalize_before_initialize_fails() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let builder = BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
let result = builder.finalize().await;
assert!(matches!(result, Err(BlockBuilderError::NotInitialized)));
}
#[tokio::test(flavor = "multi_thread")]
async fn apply_inherents_twice_fails() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
builder.initialize().await.expect("initialize failed");
builder.apply_inherents().await.expect("first apply_inherents failed");
let result = builder.apply_inherents().await;
assert!(matches!(result, Err(BlockBuilderError::InherentsAlreadyApplied)));
}
#[tokio::test(flavor = "multi_thread")]
async fn apply_extrinsic_before_inherents_fails() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
builder.initialize().await.expect("initialize failed");
let result = builder.apply_extrinsic(vec![0x00]).await;
assert!(matches!(result, Err(BlockBuilderError::InherentsNotApplied)));
}
#[tokio::test(flavor = "multi_thread")]
async fn finalize_before_inherents_fails() {
let ctx = create_test_context().await;
let header = create_next_header(&ctx.block, vec![]);
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, vec![], None, false);
builder.initialize().await.expect("initialize failed");
let result = builder.finalize().await;
assert!(matches!(result, Err(BlockBuilderError::InherentsNotApplied)));
}
#[tokio::test(flavor = "multi_thread")]
async fn finalize_produces_child_block() {
use crate::inherent::TimestampInherent;
let ctx = create_test_context().await;
let parent_number = ctx.block.number;
let parent_hash = ctx.block.hash;
let header = create_next_header(&ctx.block, vec![]);
let providers: Vec<Box<dyn crate::InherentProvider>> =
vec![Box::new(TimestampInherent::default_relay())];
let mut builder =
BlockBuilder::new(ctx.block, ctx.executor, header, providers, None, false);
builder.initialize().await.expect("initialize failed");
builder.apply_inherents().await.expect("apply_inherents failed");
let (new_block, _prototype) = builder.finalize().await.expect("finalize failed");
assert_eq!(new_block.number, parent_number + 1);
assert_eq!(new_block.parent_hash, parent_hash);
assert!(new_block.parent.is_some());
assert!(!new_block.header.is_empty());
}
#[tokio::test(flavor = "multi_thread")]
async fn create_next_header_increments_block_number() {
let ctx = create_test_context().await;
let header_bytes = create_next_header(&ctx.block, vec![]);
assert!(!header_bytes.is_empty());
assert_eq!(&header_bytes[0..32], ctx.block.hash.as_bytes());
}
#[tokio::test(flavor = "multi_thread")]
async fn create_next_header_includes_digest_items() {
let ctx = create_test_context().await;
let slot: u64 = 12345;
let digest_items = vec![DigestItem::PreRuntime(consensus_engine::AURA, slot.encode())];
let header_bytes = create_next_header(&ctx.block, digest_items);
let empty_header = create_next_header(&ctx.block, vec![]);
assert!(header_bytes.len() > empty_header.len());
}
}
}