Skip to main content

soil_client/block_builder/
mod.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Substrate block builder
8//!
9//! This crate provides the [`BlockBuilder`] utility and the corresponding runtime api
10//! [`BlockBuilder`](subsoil::block_builder::BlockBuilder).
11//!
12//! The block builder utility is used in the node as an abstraction over the runtime api to
13//! initialize a block, to push extrinsics and to finalize a block.
14
15#![warn(missing_docs)]
16
17use codec::Encode;
18
19use crate::blockchain::{ApplyExtrinsicFailed, Error, HeaderBackend};
20use std::marker::PhantomData;
21use subsoil::api::{
22	ApiExt, ApiRef, CallApiAt, Core, ProofRecorder, ProvideRuntimeApi, StorageChanges,
23	TransactionOutcome,
24};
25use subsoil::core::traits::CallContext;
26use subsoil::externalities::Extensions;
27use subsoil::runtime::{
28	legacy,
29	traits::{Block as BlockT, Hash, HashingFor, Header as HeaderT, NumberFor, One},
30	Digest, ExtrinsicInclusionMode,
31};
32
33pub use subsoil::block_builder::BlockBuilder as BlockBuilderApi;
34
35/// A builder for creating an instance of [`BlockBuilder`].
36pub struct BlockBuilderBuilder<'a, B, C> {
37	call_api_at: &'a C,
38	_phantom: PhantomData<B>,
39}
40
41impl<'a, B, C> BlockBuilderBuilder<'a, B, C>
42where
43	B: BlockT,
44{
45	/// Create a new instance of the builder.
46	///
47	/// `call_api_at`: Something that implements [`CallApiAt`].
48	pub fn new(call_api_at: &'a C) -> Self {
49		Self { call_api_at, _phantom: PhantomData }
50	}
51
52	/// Specify the parent block to build on top of.
53	pub fn on_parent_block(self, parent_block: B::Hash) -> BlockBuilderBuilderStage1<'a, B, C> {
54		BlockBuilderBuilderStage1 { call_api_at: self.call_api_at, parent_block }
55	}
56}
57
58/// The second stage of the [`BlockBuilderBuilder`].
59///
60/// This type can not be instantiated directly. To get an instance of it
61/// [`BlockBuilderBuilder::new`] needs to be used.
62pub struct BlockBuilderBuilderStage1<'a, B: BlockT, C> {
63	call_api_at: &'a C,
64	parent_block: B::Hash,
65}
66
67impl<'a, B, C> BlockBuilderBuilderStage1<'a, B, C>
68where
69	B: BlockT,
70{
71	/// Fetch the parent block number from the given `header_backend`.
72	///
73	/// The parent block number is used to initialize the block number of the new block.
74	///
75	/// Returns an error if the parent block specified in
76	/// [`on_parent_block`](BlockBuilderBuilder::on_parent_block) does not exist.
77	pub fn fetch_parent_block_number<H: HeaderBackend<B>>(
78		self,
79		header_backend: &H,
80	) -> Result<BlockBuilderBuilderStage2<'a, B, C>, Error> {
81		let parent_number = header_backend.number(self.parent_block)?.ok_or_else(|| {
82			Error::Backend(format!(
83				"Could not fetch block number for block: {:?}",
84				self.parent_block
85			))
86		})?;
87
88		Ok(BlockBuilderBuilderStage2 {
89			call_api_at: self.call_api_at,
90			proof_recorder: None,
91			inherent_digests: Default::default(),
92			parent_block: self.parent_block,
93			parent_number,
94			extra_extensions: Extensions::new(),
95		})
96	}
97
98	/// Provide the block number for the parent block directly.
99	///
100	/// The parent block is specified in [`on_parent_block`](BlockBuilderBuilder::on_parent_block).
101	/// The parent block number is used to initialize the block number of the new block.
102	pub fn with_parent_block_number(
103		self,
104		parent_number: NumberFor<B>,
105	) -> BlockBuilderBuilderStage2<'a, B, C> {
106		BlockBuilderBuilderStage2 {
107			call_api_at: self.call_api_at,
108			proof_recorder: None,
109			inherent_digests: Default::default(),
110			parent_block: self.parent_block,
111			parent_number,
112			extra_extensions: Extensions::new(),
113		}
114	}
115}
116
117/// The second stage of the [`BlockBuilderBuilder`].
118///
119/// This type can not be instantiated directly. To get an instance of it
120/// [`BlockBuilderBuilder::new`] needs to be used.
121pub struct BlockBuilderBuilderStage2<'a, B: BlockT, C> {
122	call_api_at: &'a C,
123	proof_recorder: Option<ProofRecorder<B>>,
124	inherent_digests: Digest,
125	parent_block: B::Hash,
126	parent_number: NumberFor<B>,
127	extra_extensions: Extensions,
128}
129
130impl<'a, B: BlockT, C> BlockBuilderBuilderStage2<'a, B, C> {
131	/// Enable/disable proof recording for the block builder using the given proof recorder.
132	pub fn with_proof_recorder(
133		mut self,
134		proof_recorder: impl Into<Option<ProofRecorder<B>>>,
135	) -> Self {
136		self.proof_recorder = proof_recorder.into();
137		self
138	}
139
140	/// Enable proof recording.
141	pub fn enable_proof_recording(mut self) -> Self {
142		self.proof_recorder = Some(Default::default());
143		self
144	}
145
146	/// Build the block with the given inherent digests.
147	pub fn with_inherent_digests(mut self, inherent_digests: Digest) -> Self {
148		self.inherent_digests = inherent_digests;
149		self
150	}
151
152	/// Set the extra extensions to be registered with the runtime API during block building.
153	pub fn with_extra_extensions(mut self, extra_extensions: impl Into<Extensions>) -> Self {
154		self.extra_extensions = extra_extensions.into();
155		self
156	}
157
158	/// Create the instance of the [`BlockBuilder`].
159	pub fn build(self) -> Result<BlockBuilder<'a, B, C>, Error>
160	where
161		C: CallApiAt<B> + ProvideRuntimeApi<B>,
162		C::Api: BlockBuilderApi<B>,
163	{
164		BlockBuilder::new(
165			self.call_api_at,
166			self.parent_block,
167			self.parent_number,
168			self.proof_recorder,
169			self.inherent_digests,
170			self.extra_extensions,
171		)
172	}
173}
174
175/// A block that was build by [`BlockBuilder`] plus some additional data.
176///
177/// This additional data includes the `storage_changes`, these changes can be applied to the
178/// backend to get the state of the block.
179pub struct BuiltBlock<Block: BlockT> {
180	/// The actual block that was build.
181	pub block: Block,
182	/// The changes that need to be applied to the backend to get the state of the build block.
183	pub storage_changes: StorageChanges<Block>,
184}
185
186impl<Block: BlockT> BuiltBlock<Block> {
187	/// Convert into the inner values.
188	pub fn into_inner(self) -> (Block, StorageChanges<Block>) {
189		(self.block, self.storage_changes)
190	}
191}
192
193/// Utility for building new (valid) blocks from a stream of extrinsics.
194pub struct BlockBuilder<'a, Block: BlockT, C: ProvideRuntimeApi<Block> + 'a> {
195	extrinsics: Vec<Block::Extrinsic>,
196	api: ApiRef<'a, C::Api>,
197	call_api_at: &'a C,
198	/// Version of the [`BlockBuilderApi`] runtime API.
199	version: u32,
200	parent_hash: Block::Hash,
201	/// The estimated size of the block header.
202	estimated_header_size: usize,
203	extrinsic_inclusion_mode: ExtrinsicInclusionMode,
204}
205
206impl<'a, Block, C> BlockBuilder<'a, Block, C>
207where
208	Block: BlockT,
209	C: CallApiAt<Block> + ProvideRuntimeApi<Block> + 'a,
210	C::Api: BlockBuilderApi<Block>,
211{
212	/// Create a new instance of builder based on the given `parent_hash` and `parent_number`.
213	///
214	/// While proof recording is enabled, all accessed trie nodes are saved.
215	/// These recorded trie nodes can be used by a third party to prove the
216	/// output of this block builder without having access to the full storage.
217	fn new(
218		call_api_at: &'a C,
219		parent_hash: Block::Hash,
220		parent_number: NumberFor<Block>,
221		proof_recorder: Option<ProofRecorder<Block>>,
222		inherent_digests: Digest,
223		extra_extensions: Extensions,
224	) -> Result<Self, Error> {
225		let header = <<Block as BlockT>::Header as HeaderT>::new(
226			parent_number + One::one(),
227			Default::default(),
228			Default::default(),
229			parent_hash,
230			inherent_digests,
231		);
232
233		let estimated_header_size = header.encoded_size();
234
235		let mut api = call_api_at.runtime_api();
236
237		if let Some(recorder) = proof_recorder {
238			api.record_proof_with_recorder(recorder);
239		}
240
241		extra_extensions.into_extensions().for_each(|e| {
242			api.register_extension(e);
243		});
244
245		api.set_call_context(CallContext::Onchain);
246
247		let core_version = api
248			.api_version::<dyn Core<Block>>(parent_hash)?
249			.ok_or_else(|| Error::VersionInvalid("Core".to_string()))?;
250
251		let extrinsic_inclusion_mode = if core_version >= 5 {
252			api.initialize_block(parent_hash, &header)?
253		} else {
254			#[allow(deprecated)]
255			api.initialize_block_before_version_5(parent_hash, &header)?;
256			ExtrinsicInclusionMode::AllExtrinsics
257		};
258
259		let bb_version = api
260			.api_version::<dyn BlockBuilderApi<Block>>(parent_hash)?
261			.ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?;
262
263		Ok(Self {
264			parent_hash,
265			extrinsics: Vec::new(),
266			api,
267			version: bb_version,
268			estimated_header_size,
269			call_api_at,
270			extrinsic_inclusion_mode,
271		})
272	}
273
274	/// The extrinsic inclusion mode of the runtime for this block.
275	pub fn extrinsic_inclusion_mode(&self) -> ExtrinsicInclusionMode {
276		self.extrinsic_inclusion_mode
277	}
278
279	/// Push onto the block's list of extrinsics.
280	///
281	/// This will ensure the extrinsic can be validly executed (by executing it).
282	pub fn push(&mut self, xt: <Block as BlockT>::Extrinsic) -> Result<(), Error> {
283		let parent_hash = self.parent_hash;
284		let extrinsics = &mut self.extrinsics;
285		let version = self.version;
286
287		self.api.execute_in_transaction(|api| {
288			let res = if version < 6 {
289				#[allow(deprecated)]
290				api.apply_extrinsic_before_version_6(parent_hash, xt.clone())
291					.map(legacy::byte_sized_error::convert_to_latest)
292			} else {
293				api.apply_extrinsic(parent_hash, xt.clone())
294			};
295
296			match res {
297				Ok(Ok(_)) => {
298					extrinsics.push(xt);
299					TransactionOutcome::Commit(Ok(()))
300				},
301				Ok(Err(tx_validity)) => TransactionOutcome::Rollback(Err(
302					ApplyExtrinsicFailed::Validity(tx_validity).into(),
303				)),
304				Err(e) => TransactionOutcome::Rollback(Err(Error::from(e))),
305			}
306		})
307	}
308
309	/// Consume the builder to build a valid `Block` containing all pushed extrinsics.
310	///
311	/// Returns the build `Block`, the changes to the storage and an optional `StorageProof`
312	/// supplied by `self.api`, combined as [`BuiltBlock`].
313	/// The storage proof will be `Some(_)` when proof recording was enabled.
314	pub fn build(self) -> Result<BuiltBlock<Block>, Error> {
315		let header = self.api.finalize_block(self.parent_hash)?;
316
317		debug_assert_eq!(
318			header.extrinsics_root().clone(),
319			HashingFor::<Block>::ordered_trie_root(
320				self.extrinsics.iter().map(Encode::encode).collect(),
321				self.api.version(self.parent_hash)?.extrinsics_root_state_version(),
322			),
323		);
324
325		let state = self.call_api_at.state_at(self.parent_hash)?;
326
327		let storage_changes = self
328			.api
329			.into_storage_changes(&state, self.parent_hash)
330			.map_err(crate::blockchain::Error::StorageChanges)?;
331
332		Ok(BuiltBlock { block: <Block as BlockT>::new(header, self.extrinsics), storage_changes })
333	}
334
335	/// Create the inherents for the block.
336	///
337	/// Returns the inherents created by the runtime or an error if something failed.
338	pub fn create_inherents(
339		&mut self,
340		inherent_data: subsoil::inherents::InherentData,
341	) -> Result<Vec<Block::Extrinsic>, Error> {
342		let parent_hash = self.parent_hash;
343		self.api
344			.execute_in_transaction(move |api| {
345				// `create_inherents` should not change any state, to ensure this we always rollback
346				// the transaction.
347				TransactionOutcome::Rollback(api.inherent_extrinsics(parent_hash, inherent_data))
348			})
349			.map_err(|e| Error::Application(Box::new(e)))
350	}
351
352	/// Estimate the size of the block in the current state.
353	///
354	/// If `include_proof` is `true`, the estimated size of the storage proof will be added
355	/// to the estimation.
356	pub fn estimate_block_size(&self) -> usize {
357		let size = self.estimated_header_size + self.extrinsics.encoded_size();
358
359		size + self.api.proof_recorder().map_or(0, |pr| pr.estimate_encoded_size())
360	}
361
362	/// Returns the [`ProofRecorder`] set for the block building.
363	pub fn proof_recorder(&self) -> Option<ProofRecorder<Block>> {
364		self.api.proof_recorder()
365	}
366}
367
368// Runtime-backed integration tests live in the `soil-test` crate to keep
369// `soil-client` free of the `soil-test-node-runtime-client` dev-dependency.