mod tests;
use crate::{
executor::{host, runtime_call},
header, util,
verify::inherents,
};
use alloc::{borrow::ToOwned as _, vec::Vec};
use core::{iter, mem, slice};
pub use runtime_call::{
Nibble, StorageChanges, TrieChange, TrieChangeStorageValue, TrieEntryVersion,
};
pub struct Config<'a> {
pub block_number_bytes: usize,
pub parent_hash: &'a [u8; 32],
pub parent_number: u64,
pub parent_runtime: host::HostVmPrototype,
pub consensus_digest_log_item: ConfigPreRuntime<'a>,
pub block_body_capacity: usize,
pub max_log_level: u32,
pub calculate_trie_changes: bool,
}
pub enum ConfigPreRuntime<'a> {
Aura(header::AuraPreDigest),
Babe(header::BabePreDigestRef<'a>),
}
pub struct Success {
pub scale_encoded_header: Vec<u8>,
pub body: Vec<Vec<u8>>,
pub parent_runtime: host::HostVmPrototype,
pub storage_changes: StorageChanges,
pub state_trie_version: TrieEntryVersion,
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum Error {
#[display("{_0}")]
WasmVm(runtime_call::ErrorDetail),
#[display("{_0}")]
VmInit(host::StartErr),
BlockHeightOverflow,
InitializeBlockNonEmptyOutput,
BadInherentExtrinsicsOutput,
BadApplyExtrinsicOutput,
#[display("Error while applying inherent extrinsic: {error}\nExtrinsic: {extrinsic:?}")]
InherentExtrinsicDispatchError {
extrinsic: Vec<u8>,
#[error(source)]
error: DispatchError,
},
#[display("Error while applying inherent extrinsic: {error}\nExtrinsic: {extrinsic:?}")]
InherentExtrinsicTransactionValidityError {
extrinsic: Vec<u8>,
#[error(source)]
error: TransactionValidityError,
},
}
pub fn build_block(config: Config) -> BlockBuild {
let consensus_digest = match config.consensus_digest_log_item {
ConfigPreRuntime::Aura(item) => header::DigestItem::AuraPreDigest(item),
ConfigPreRuntime::Babe(item) => header::DigestItem::BabePreDigest(item.into()),
};
let init_result = runtime_call::run(runtime_call::Config {
function_to_call: "Core_initialize_block",
parameter: {
header::HeaderRef {
parent_hash: config.parent_hash,
number: match config.parent_number.checked_add(1) {
Some(n) => n,
None => {
return BlockBuild::Finished(Err((
Error::BlockHeightOverflow,
config.parent_runtime,
)));
}
},
extrinsics_root: &[0; 32],
state_root: &[0; 32],
digest: header::DigestRef::from_slice(slice::from_ref(&consensus_digest)).unwrap(),
}
.scale_encoding(config.block_number_bytes)
},
virtual_machine: config.parent_runtime,
storage_main_trie_changes: Default::default(),
storage_proof_size_behavior: runtime_call::StorageProofSizeBehavior::Unimplemented,
max_log_level: config.max_log_level,
calculate_trie_changes: config.calculate_trie_changes,
});
let vm = match init_result {
Ok(vm) => vm,
Err((err, proto)) => return BlockBuild::Finished(Err((Error::VmInit(err), proto))),
};
let shared = Shared {
stage: Stage::InitializeBlock,
block_body: Vec::with_capacity(config.block_body_capacity),
max_log_level: config.max_log_level,
calculate_trie_changes: config.calculate_trie_changes,
};
BlockBuild::from_inner(vm, shared)
}
#[must_use]
pub enum BlockBuild {
Finished(Result<Success, (Error, host::HostVmPrototype)>),
InherentExtrinsics(InherentExtrinsics),
ApplyExtrinsic(ApplyExtrinsic),
ApplyExtrinsicResult {
result: Result<Result<(), DispatchError>, TransactionValidityError>,
resume: ApplyExtrinsic,
},
StorageGet(StorageGet),
ClosestDescendantMerkleValue(ClosestDescendantMerkleValue),
NextKey(NextKey),
OffchainStorageSet(OffchainStorageSet),
}
impl BlockBuild {
fn from_inner(inner: runtime_call::RuntimeCall, mut shared: Shared) -> Self {
enum Inner {
Runtime(runtime_call::RuntimeCall),
Transition(runtime_call::Success),
}
let mut inner = Inner::Runtime(inner);
loop {
match (inner, &mut shared.stage) {
(Inner::Runtime(runtime_call::RuntimeCall::Finished(Err(err))), _) => {
return BlockBuild::Finished(Err((Error::WasmVm(err.detail), err.prototype)));
}
(Inner::Runtime(runtime_call::RuntimeCall::StorageGet(inner)), _) => {
return BlockBuild::StorageGet(StorageGet(inner, shared));
}
(
Inner::Runtime(runtime_call::RuntimeCall::ClosestDescendantMerkleValue(inner)),
_,
) => {
return BlockBuild::ClosestDescendantMerkleValue(ClosestDescendantMerkleValue(
inner, shared,
));
}
(Inner::Runtime(runtime_call::RuntimeCall::NextKey(inner)), _) => {
return BlockBuild::NextKey(NextKey(inner, shared));
}
(Inner::Runtime(runtime_call::RuntimeCall::OffchainStorageSet(inner)), _) => {
return BlockBuild::OffchainStorageSet(OffchainStorageSet(inner, shared));
}
(
Inner::Runtime(runtime_call::RuntimeCall::Finished(Ok(success))),
Stage::InitializeBlock,
) => {
if !success.virtual_machine.value().as_ref().is_empty() {
return BlockBuild::Finished(Err((
Error::InitializeBlockNonEmptyOutput,
success.virtual_machine.into_prototype(),
)));
}
shared.stage = Stage::InherentExtrinsics;
return BlockBuild::InherentExtrinsics(InherentExtrinsics {
shared,
parent_runtime: success.virtual_machine.into_prototype(),
storage_changes: success.storage_changes,
});
}
(
Inner::Runtime(runtime_call::RuntimeCall::Finished(Ok(success))),
Stage::InherentExtrinsics,
) => {
let parse_result =
parse_inherent_extrinsics_output(success.virtual_machine.value().as_ref());
let extrinsics = match parse_result {
Ok(extrinsics) => extrinsics,
Err(err) => {
return BlockBuild::Finished(Err((
err,
success.virtual_machine.into_prototype(),
)));
}
};
shared.block_body.reserve(extrinsics.len());
shared.stage = Stage::ApplyInherentExtrinsic { extrinsics };
inner = Inner::Transition(success);
}
(Inner::Transition(success), Stage::ApplyInherentExtrinsic { extrinsics })
if !extrinsics.is_empty() =>
{
let extrinsic = &extrinsics[0];
let init_result = runtime_call::run(runtime_call::Config {
virtual_machine: success.virtual_machine.into_prototype(),
function_to_call: "BlockBuilder_apply_extrinsic",
parameter: iter::once(extrinsic),
storage_main_trie_changes: success.storage_changes.into_main_trie_diff(),
storage_proof_size_behavior:
runtime_call::StorageProofSizeBehavior::Unimplemented,
max_log_level: shared.max_log_level,
calculate_trie_changes: shared.calculate_trie_changes,
});
inner = Inner::Runtime(match init_result {
Ok(vm) => vm,
Err((err, proto)) => {
return BlockBuild::Finished(Err((Error::VmInit(err), proto)));
}
});
}
(Inner::Transition(success), Stage::ApplyInherentExtrinsic { .. }) => {
return BlockBuild::ApplyExtrinsic(ApplyExtrinsic {
shared,
parent_runtime: success.virtual_machine.into_prototype(),
storage_changes: success.storage_changes,
});
}
(
Inner::Runtime(runtime_call::RuntimeCall::Finished(Ok(success))),
Stage::ApplyInherentExtrinsic { .. },
) => {
let (extrinsic, new_stage) = match shared.stage {
Stage::ApplyInherentExtrinsic { mut extrinsics } => {
let extrinsic = extrinsics.remove(0);
(extrinsic, Stage::ApplyInherentExtrinsic { extrinsics })
}
_ => unreachable!(),
};
shared.stage = new_stage;
let parse_result =
parse_apply_extrinsic_output(success.virtual_machine.value().as_ref());
match parse_result {
Ok(Ok(Ok(()))) => {}
Ok(Ok(Err(error))) => {
return BlockBuild::Finished(Err((
Error::InherentExtrinsicDispatchError { extrinsic, error },
success.virtual_machine.into_prototype(),
)));
}
Ok(Err(error)) => {
return BlockBuild::Finished(Err((
Error::InherentExtrinsicTransactionValidityError {
extrinsic,
error,
},
success.virtual_machine.into_prototype(),
)));
}
Err(err) => {
return BlockBuild::Finished(Err((
err,
success.virtual_machine.into_prototype(),
)));
}
}
shared.block_body.push(extrinsic);
inner = Inner::Transition(success);
}
(
Inner::Runtime(runtime_call::RuntimeCall::Finished(Ok(success))),
Stage::ApplyExtrinsic(_),
) => {
let parse_result =
parse_apply_extrinsic_output(success.virtual_machine.value().as_ref());
let result = match parse_result {
Ok(r) => r,
Err(err) => {
return BlockBuild::Finished(Err((
err,
success.virtual_machine.into_prototype(),
)));
}
};
if result.is_ok() {
shared.block_body.push(match &mut shared.stage {
Stage::ApplyExtrinsic(ext) => mem::take(ext),
_ => unreachable!(),
});
}
return BlockBuild::ApplyExtrinsicResult {
result,
resume: ApplyExtrinsic {
shared,
parent_runtime: success.virtual_machine.into_prototype(),
storage_changes: success.storage_changes,
},
};
}
(
Inner::Runtime(runtime_call::RuntimeCall::Finished(Ok(success))),
Stage::FinalizeBlock,
) => {
let scale_encoded_header = success.virtual_machine.value().as_ref().to_owned();
return BlockBuild::Finished(Ok(Success {
scale_encoded_header,
body: shared.block_body,
parent_runtime: success.virtual_machine.into_prototype(),
storage_changes: success.storage_changes,
state_trie_version: success.state_trie_version,
}));
}
(_, s) => unreachable!("{:?}", s),
}
}
}
}
#[derive(Debug)]
struct Shared {
stage: Stage,
block_body: Vec<Vec<u8>>,
max_log_level: u32,
calculate_trie_changes: bool,
}
#[derive(Debug, Clone)]
enum Stage {
InitializeBlock,
InherentExtrinsics,
ApplyInherentExtrinsic {
extrinsics: Vec<Vec<u8>>,
},
ApplyExtrinsic(Vec<u8>),
FinalizeBlock,
}
#[must_use]
pub struct InherentExtrinsics {
shared: Shared,
parent_runtime: host::HostVmPrototype,
storage_changes: StorageChanges,
}
impl InherentExtrinsics {
pub fn inject_inherents(self, inherents: inherents::InherentData) -> BlockBuild {
self.inject_raw_inherents_list(inherents.as_raw_list())
}
pub fn inject_raw_inherents_list(
self,
list: impl ExactSizeIterator<Item = ([u8; 8], impl AsRef<[u8]> + Clone)> + Clone,
) -> BlockBuild {
debug_assert!(matches!(self.shared.stage, Stage::InherentExtrinsics));
let init_result = runtime_call::run(runtime_call::Config {
virtual_machine: self.parent_runtime,
function_to_call: "BlockBuilder_inherent_extrinsics",
parameter: {
let len = util::encode_scale_compact_usize(list.len());
let encoded_list = list.flat_map(|(id, value)| {
let value_len = util::encode_scale_compact_usize(value.as_ref().len());
let value_and_len = iter::once(value_len)
.map(either::Left)
.chain(iter::once(value).map(either::Right));
iter::once(id)
.map(either::Left)
.chain(value_and_len.map(either::Right))
});
iter::once(len)
.map(either::Left)
.chain(encoded_list.map(either::Right))
},
storage_proof_size_behavior: runtime_call::StorageProofSizeBehavior::Unimplemented,
storage_main_trie_changes: self.storage_changes.into_main_trie_diff(),
max_log_level: self.shared.max_log_level,
calculate_trie_changes: self.shared.calculate_trie_changes,
});
let vm = match init_result {
Ok(vm) => vm,
Err((err, proto)) => return BlockBuild::Finished(Err((Error::VmInit(err), proto))),
};
BlockBuild::from_inner(vm, self.shared)
}
}
#[must_use]
pub struct ApplyExtrinsic {
shared: Shared,
parent_runtime: host::HostVmPrototype,
storage_changes: StorageChanges,
}
impl ApplyExtrinsic {
pub fn add_extrinsic(mut self, extrinsic: Vec<u8>) -> BlockBuild {
let init_result = runtime_call::run(runtime_call::Config {
virtual_machine: self.parent_runtime,
function_to_call: "BlockBuilder_apply_extrinsic",
parameter: iter::once(&extrinsic),
storage_proof_size_behavior: runtime_call::StorageProofSizeBehavior::Unimplemented,
storage_main_trie_changes: self.storage_changes.into_main_trie_diff(),
max_log_level: self.shared.max_log_level,
calculate_trie_changes: self.shared.calculate_trie_changes,
});
self.shared.stage = Stage::ApplyExtrinsic(extrinsic);
let vm = match init_result {
Ok(vm) => vm,
Err((err, proto)) => return BlockBuild::Finished(Err((Error::VmInit(err), proto))),
};
BlockBuild::from_inner(vm, self.shared)
}
pub fn finish(mut self) -> BlockBuild {
self.shared.stage = Stage::FinalizeBlock;
let init_result = runtime_call::run(runtime_call::Config {
virtual_machine: self.parent_runtime,
function_to_call: "BlockBuilder_finalize_block",
parameter: iter::empty::<&[u8]>(),
storage_proof_size_behavior: runtime_call::StorageProofSizeBehavior::Unimplemented,
storage_main_trie_changes: self.storage_changes.into_main_trie_diff(),
max_log_level: self.shared.max_log_level,
calculate_trie_changes: self.shared.calculate_trie_changes,
});
let vm = match init_result {
Ok(vm) => vm,
Err((err, proto)) => return BlockBuild::Finished(Err((Error::VmInit(err), proto))),
};
BlockBuild::from_inner(vm, self.shared)
}
}
#[must_use]
pub struct StorageGet(runtime_call::StorageGet, Shared);
impl StorageGet {
pub fn key(&self) -> impl AsRef<[u8]> {
self.0.key()
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
self.0.child_trie()
}
pub fn inject_value(
self,
value: Option<(impl Iterator<Item = impl AsRef<[u8]>>, TrieEntryVersion)>,
) -> BlockBuild {
BlockBuild::from_inner(self.0.inject_value(value), self.1)
}
}
#[must_use]
pub struct ClosestDescendantMerkleValue(runtime_call::ClosestDescendantMerkleValue, Shared);
impl ClosestDescendantMerkleValue {
pub fn key(&self) -> impl Iterator<Item = Nibble> {
self.0.key()
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
self.0.child_trie()
}
pub fn resume_unknown(self) -> BlockBuild {
BlockBuild::from_inner(self.0.resume_unknown(), self.1)
}
pub fn inject_merkle_value(self, merkle_value: Option<&[u8]>) -> BlockBuild {
BlockBuild::from_inner(self.0.inject_merkle_value(merkle_value), self.1)
}
}
#[must_use]
pub struct NextKey(runtime_call::NextKey, Shared);
impl NextKey {
pub fn key(&self) -> impl Iterator<Item = Nibble> {
self.0.key()
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
self.0.child_trie()
}
pub fn or_equal(&self) -> bool {
self.0.or_equal()
}
pub fn branch_nodes(&self) -> bool {
self.0.branch_nodes()
}
pub fn prefix(&self) -> impl Iterator<Item = Nibble> {
self.0.prefix()
}
pub fn inject_key(self, key: Option<impl Iterator<Item = Nibble>>) -> BlockBuild {
BlockBuild::from_inner(self.0.inject_key(key), self.1)
}
}
#[must_use]
pub struct OffchainStorageSet(runtime_call::OffchainStorageSet, Shared);
impl OffchainStorageSet {
pub fn key(&self) -> impl AsRef<[u8]> {
self.0.key()
}
pub fn value(&self) -> Option<impl AsRef<[u8]>> {
self.0.value()
}
pub fn resume(self) -> BlockBuild {
BlockBuild::from_inner(self.0.resume(), self.1)
}
}
fn parse_inherent_extrinsics_output(output: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
nom::Parser::parse(
&mut nom::combinator::all_consuming(nom::combinator::flat_map(
crate::util::nom_scale_compact_usize,
|num_elems| {
nom::multi::many_m_n(
num_elems,
num_elems,
nom::combinator::map(
nom::combinator::recognize(nom::combinator::flat_map(
crate::util::nom_scale_compact_usize,
nom::bytes::streaming::take,
)),
|v: &[u8]| v.to_vec(),
),
)
},
)),
output,
)
.map(|(_, parse_result)| parse_result)
.map_err(|_: nom::Err<(&[u8], nom::error::ErrorKind)>| Error::BadInherentExtrinsicsOutput)
}
fn parse_apply_extrinsic_output(
output: &[u8],
) -> Result<Result<Result<(), DispatchError>, TransactionValidityError>, Error> {
nom::Parser::parse(
&mut nom::combinator::all_consuming(apply_extrinsic_result),
output,
)
.map(|(_, parse_result)| parse_result)
.map_err(|_: nom::Err<nom::error::Error<&[u8]>>| Error::BadApplyExtrinsicOutput)
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone, PartialEq, Eq)]
pub enum TransactionValidityError {
#[display("Transaction is invalid: {_0}")]
Invalid(InvalidTransaction),
#[display("Transaction validity couldn't be determined: {_0}")]
Unknown(UnknownTransaction),
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone, PartialEq, Eq)]
pub enum InvalidTransaction {
Call,
Payment,
Future,
Stale,
BadProof,
AncientBirthBlock,
ExhaustsResources,
#[display("Other reason (code: {_0})")]
Custom(#[error(not(source))] u8),
BadMandatory,
MandatoryDispatch,
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone, PartialEq, Eq)]
pub enum UnknownTransaction {
CannotLookup,
NoUnsignedValidator,
#[display("Other reason (code: {_0})")]
Custom(#[error(not(source))] u8),
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone, PartialEq, Eq)]
pub enum DispatchError {
CannotLookup,
BadOrigin,
#[display("Error in module #{index}, error number #{error}")]
Module {
index: u8,
error: u8,
},
}
fn apply_extrinsic_result(
bytes: &[u8],
) -> nom::IResult<&[u8], Result<Result<(), DispatchError>, TransactionValidityError>> {
nom::Parser::parse(
&mut nom::error::context(
"apply extrinsic result",
nom::branch::alt((
nom::combinator::map(
nom::sequence::preceded(nom::bytes::streaming::tag(&[0][..]), dispatch_outcome),
Ok,
),
nom::combinator::map(
nom::sequence::preceded(
nom::bytes::streaming::tag(&[1][..]),
transaction_validity_error,
),
Err,
),
)),
),
bytes,
)
}
fn dispatch_outcome(bytes: &[u8]) -> nom::IResult<&[u8], Result<(), DispatchError>> {
nom::Parser::parse(
&mut nom::error::context(
"dispatch outcome",
nom::branch::alt((
nom::combinator::map(nom::bytes::streaming::tag(&[0][..]), |_| Ok(())),
nom::combinator::map(
nom::sequence::preceded(nom::bytes::streaming::tag(&[1][..]), dispatch_error),
Err,
),
)),
),
bytes,
)
}
fn dispatch_error(bytes: &[u8]) -> nom::IResult<&[u8], DispatchError> {
nom::Parser::parse(
&mut nom::error::context(
"dispatch error",
nom::branch::alt((
nom::combinator::map(nom::bytes::streaming::tag(&[0][..]), |_| {
DispatchError::CannotLookup
}),
nom::combinator::map(nom::bytes::streaming::tag(&[1][..]), |_| {
DispatchError::BadOrigin
}),
nom::combinator::map(
nom::sequence::preceded(
nom::bytes::streaming::tag(&[2][..]),
(nom::number::streaming::u8, nom::number::streaming::u8),
),
|(index, error)| DispatchError::Module { index, error },
),
)),
),
bytes,
)
}
fn transaction_validity_error(bytes: &[u8]) -> nom::IResult<&[u8], TransactionValidityError> {
nom::Parser::parse(
&mut nom::error::context(
"transaction validity error",
nom::branch::alt((
nom::combinator::map(
nom::sequence::preceded(
nom::bytes::streaming::tag(&[0][..]),
invalid_transaction,
),
TransactionValidityError::Invalid,
),
nom::combinator::map(
nom::sequence::preceded(
nom::bytes::streaming::tag(&[1][..]),
unknown_transaction,
),
TransactionValidityError::Unknown,
),
)),
),
bytes,
)
}
fn invalid_transaction(bytes: &[u8]) -> nom::IResult<&[u8], InvalidTransaction> {
nom::Parser::parse(
&mut nom::error::context(
"invalid transaction",
nom::branch::alt((
nom::combinator::map(nom::bytes::streaming::tag(&[0][..]), |_| {
InvalidTransaction::Call
}),
nom::combinator::map(nom::bytes::streaming::tag(&[1][..]), |_| {
InvalidTransaction::Payment
}),
nom::combinator::map(nom::bytes::streaming::tag(&[2][..]), |_| {
InvalidTransaction::Future
}),
nom::combinator::map(nom::bytes::streaming::tag(&[3][..]), |_| {
InvalidTransaction::Stale
}),
nom::combinator::map(nom::bytes::streaming::tag(&[4][..]), |_| {
InvalidTransaction::BadProof
}),
nom::combinator::map(nom::bytes::streaming::tag(&[5][..]), |_| {
InvalidTransaction::AncientBirthBlock
}),
nom::combinator::map(nom::bytes::streaming::tag(&[6][..]), |_| {
InvalidTransaction::ExhaustsResources
}),
nom::combinator::map(
nom::sequence::preceded(
nom::bytes::streaming::tag(&[7][..]),
nom::bytes::streaming::take(1u32),
),
|n: &[u8]| InvalidTransaction::Custom(n[0]),
),
nom::combinator::map(nom::bytes::streaming::tag(&[8][..]), |_| {
InvalidTransaction::BadMandatory
}),
nom::combinator::map(nom::bytes::streaming::tag(&[9][..]), |_| {
InvalidTransaction::MandatoryDispatch
}),
)),
),
bytes,
)
}
fn unknown_transaction(bytes: &[u8]) -> nom::IResult<&[u8], UnknownTransaction> {
nom::Parser::parse(
&mut nom::error::context(
"unknown transaction",
nom::branch::alt((
nom::combinator::map(nom::bytes::streaming::tag(&[0][..]), |_| {
UnknownTransaction::CannotLookup
}),
nom::combinator::map(nom::bytes::streaming::tag(&[1][..]), |_| {
UnknownTransaction::NoUnsignedValidator
}),
nom::combinator::map(
nom::sequence::preceded(
nom::bytes::streaming::tag(&[2][..]),
nom::bytes::streaming::take(1u32),
),
|n: &[u8]| UnknownTransaction::Custom(n[0]),
),
)),
),
bytes,
)
}