#![warn(missing_docs)]
use codec::Encode;
use crate::blockchain::{ApplyExtrinsicFailed, Error, HeaderBackend};
use std::marker::PhantomData;
use subsoil::api::{
ApiExt, ApiRef, CallApiAt, Core, ProofRecorder, ProvideRuntimeApi, StorageChanges,
TransactionOutcome,
};
use subsoil::core::traits::CallContext;
use subsoil::externalities::Extensions;
use subsoil::runtime::{
legacy,
traits::{Block as BlockT, Hash, HashingFor, Header as HeaderT, NumberFor, One},
Digest, ExtrinsicInclusionMode,
};
pub use subsoil::block_builder::BlockBuilder as BlockBuilderApi;
pub struct BlockBuilderBuilder<'a, B, C> {
call_api_at: &'a C,
_phantom: PhantomData<B>,
}
impl<'a, B, C> BlockBuilderBuilder<'a, B, C>
where
B: BlockT,
{
pub fn new(call_api_at: &'a C) -> Self {
Self { call_api_at, _phantom: PhantomData }
}
pub fn on_parent_block(self, parent_block: B::Hash) -> BlockBuilderBuilderStage1<'a, B, C> {
BlockBuilderBuilderStage1 { call_api_at: self.call_api_at, parent_block }
}
}
pub struct BlockBuilderBuilderStage1<'a, B: BlockT, C> {
call_api_at: &'a C,
parent_block: B::Hash,
}
impl<'a, B, C> BlockBuilderBuilderStage1<'a, B, C>
where
B: BlockT,
{
pub fn fetch_parent_block_number<H: HeaderBackend<B>>(
self,
header_backend: &H,
) -> Result<BlockBuilderBuilderStage2<'a, B, C>, Error> {
let parent_number = header_backend.number(self.parent_block)?.ok_or_else(|| {
Error::Backend(format!(
"Could not fetch block number for block: {:?}",
self.parent_block
))
})?;
Ok(BlockBuilderBuilderStage2 {
call_api_at: self.call_api_at,
proof_recorder: None,
inherent_digests: Default::default(),
parent_block: self.parent_block,
parent_number,
extra_extensions: Extensions::new(),
})
}
pub fn with_parent_block_number(
self,
parent_number: NumberFor<B>,
) -> BlockBuilderBuilderStage2<'a, B, C> {
BlockBuilderBuilderStage2 {
call_api_at: self.call_api_at,
proof_recorder: None,
inherent_digests: Default::default(),
parent_block: self.parent_block,
parent_number,
extra_extensions: Extensions::new(),
}
}
}
pub struct BlockBuilderBuilderStage2<'a, B: BlockT, C> {
call_api_at: &'a C,
proof_recorder: Option<ProofRecorder<B>>,
inherent_digests: Digest,
parent_block: B::Hash,
parent_number: NumberFor<B>,
extra_extensions: Extensions,
}
impl<'a, B: BlockT, C> BlockBuilderBuilderStage2<'a, B, C> {
pub fn with_proof_recorder(
mut self,
proof_recorder: impl Into<Option<ProofRecorder<B>>>,
) -> Self {
self.proof_recorder = proof_recorder.into();
self
}
pub fn enable_proof_recording(mut self) -> Self {
self.proof_recorder = Some(Default::default());
self
}
pub fn with_inherent_digests(mut self, inherent_digests: Digest) -> Self {
self.inherent_digests = inherent_digests;
self
}
pub fn with_extra_extensions(mut self, extra_extensions: impl Into<Extensions>) -> Self {
self.extra_extensions = extra_extensions.into();
self
}
pub fn build(self) -> Result<BlockBuilder<'a, B, C>, Error>
where
C: CallApiAt<B> + ProvideRuntimeApi<B>,
C::Api: BlockBuilderApi<B>,
{
BlockBuilder::new(
self.call_api_at,
self.parent_block,
self.parent_number,
self.proof_recorder,
self.inherent_digests,
self.extra_extensions,
)
}
}
pub struct BuiltBlock<Block: BlockT> {
pub block: Block,
pub storage_changes: StorageChanges<Block>,
}
impl<Block: BlockT> BuiltBlock<Block> {
pub fn into_inner(self) -> (Block, StorageChanges<Block>) {
(self.block, self.storage_changes)
}
}
pub struct BlockBuilder<'a, Block: BlockT, C: ProvideRuntimeApi<Block> + 'a> {
extrinsics: Vec<Block::Extrinsic>,
api: ApiRef<'a, C::Api>,
call_api_at: &'a C,
version: u32,
parent_hash: Block::Hash,
estimated_header_size: usize,
extrinsic_inclusion_mode: ExtrinsicInclusionMode,
}
impl<'a, Block, C> BlockBuilder<'a, Block, C>
where
Block: BlockT,
C: CallApiAt<Block> + ProvideRuntimeApi<Block> + 'a,
C::Api: BlockBuilderApi<Block>,
{
fn new(
call_api_at: &'a C,
parent_hash: Block::Hash,
parent_number: NumberFor<Block>,
proof_recorder: Option<ProofRecorder<Block>>,
inherent_digests: Digest,
extra_extensions: Extensions,
) -> Result<Self, Error> {
let header = <<Block as BlockT>::Header as HeaderT>::new(
parent_number + One::one(),
Default::default(),
Default::default(),
parent_hash,
inherent_digests,
);
let estimated_header_size = header.encoded_size();
let mut api = call_api_at.runtime_api();
if let Some(recorder) = proof_recorder {
api.record_proof_with_recorder(recorder);
}
extra_extensions.into_extensions().for_each(|e| {
api.register_extension(e);
});
api.set_call_context(CallContext::Onchain);
let core_version = api
.api_version::<dyn Core<Block>>(parent_hash)?
.ok_or_else(|| Error::VersionInvalid("Core".to_string()))?;
let extrinsic_inclusion_mode = if core_version >= 5 {
api.initialize_block(parent_hash, &header)?
} else {
#[allow(deprecated)]
api.initialize_block_before_version_5(parent_hash, &header)?;
ExtrinsicInclusionMode::AllExtrinsics
};
let bb_version = api
.api_version::<dyn BlockBuilderApi<Block>>(parent_hash)?
.ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?;
Ok(Self {
parent_hash,
extrinsics: Vec::new(),
api,
version: bb_version,
estimated_header_size,
call_api_at,
extrinsic_inclusion_mode,
})
}
pub fn extrinsic_inclusion_mode(&self) -> ExtrinsicInclusionMode {
self.extrinsic_inclusion_mode
}
pub fn push(&mut self, xt: <Block as BlockT>::Extrinsic) -> Result<(), Error> {
let parent_hash = self.parent_hash;
let extrinsics = &mut self.extrinsics;
let version = self.version;
self.api.execute_in_transaction(|api| {
let res = if version < 6 {
#[allow(deprecated)]
api.apply_extrinsic_before_version_6(parent_hash, xt.clone())
.map(legacy::byte_sized_error::convert_to_latest)
} else {
api.apply_extrinsic(parent_hash, xt.clone())
};
match res {
Ok(Ok(_)) => {
extrinsics.push(xt);
TransactionOutcome::Commit(Ok(()))
},
Ok(Err(tx_validity)) => TransactionOutcome::Rollback(Err(
ApplyExtrinsicFailed::Validity(tx_validity).into(),
)),
Err(e) => TransactionOutcome::Rollback(Err(Error::from(e))),
}
})
}
pub fn build(self) -> Result<BuiltBlock<Block>, Error> {
let header = self.api.finalize_block(self.parent_hash)?;
debug_assert_eq!(
header.extrinsics_root().clone(),
HashingFor::<Block>::ordered_trie_root(
self.extrinsics.iter().map(Encode::encode).collect(),
self.api.version(self.parent_hash)?.extrinsics_root_state_version(),
),
);
let state = self.call_api_at.state_at(self.parent_hash)?;
let storage_changes = self
.api
.into_storage_changes(&state, self.parent_hash)
.map_err(crate::blockchain::Error::StorageChanges)?;
Ok(BuiltBlock { block: <Block as BlockT>::new(header, self.extrinsics), storage_changes })
}
pub fn create_inherents(
&mut self,
inherent_data: subsoil::inherents::InherentData,
) -> Result<Vec<Block::Extrinsic>, Error> {
let parent_hash = self.parent_hash;
self.api
.execute_in_transaction(move |api| {
TransactionOutcome::Rollback(api.inherent_extrinsics(parent_hash, inherent_data))
})
.map_err(|e| Error::Application(Box::new(e)))
}
pub fn estimate_block_size(&self) -> usize {
let size = self.estimated_header_size + self.extrinsics.encoded_size();
size + self.api.proof_recorder().map_or(0, |pr| pr.estimate_encoded_size())
}
pub fn proof_recorder(&self) -> Option<ProofRecorder<Block>> {
self.api.proof_recorder()
}
}