Skip to main content

domain_block_builder/
custom_api.rs

1//! Custom API that is efficient to collect storage roots.
2
3use hash_db::{HashDB, Hasher, Prefix};
4use parity_scale_codec::{Codec, Decode, Encode};
5use sc_client_api::{ExecutorProvider, StateBackend, backend};
6use sp_core::offchain::OffchainOverlayedChange;
7use sp_core::traits::{CallContext, CodeExecutor, FetchRuntimeCode};
8use sp_inherents::InherentData;
9use sp_runtime::traits::{Block as BlockT, HashingFor, NumberFor};
10use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode, TransactionOutcome};
11use sp_state_machine::backend::AsTrieBackend;
12use sp_state_machine::{
13    BackendTransaction, DBValue, IndexOperation, OverlayedChanges, StateMachine, StorageChanges,
14    StorageKey, StorageValue, TrieBackend, TrieBackendBuilder, TrieBackendStorage,
15};
16use std::borrow::Cow;
17use std::collections::HashMap;
18use std::marker::PhantomData;
19use std::mem;
20use std::sync::Arc;
21use subspace_runtime_primitives::ExtrinsicFor;
22
23type TrieBackendStorageFor<State, Block> =
24    <State as StateBackend<HashingFor<Block>>>::TrieBackendStorage;
25
26pub(crate) type TrieDeltaBackendFor<'a, State, Block> = TrieBackend<
27    DeltaBackend<'a, TrieBackendStorageFor<State, Block>, HashingFor<Block>>,
28    HashingFor<Block>,
29>;
30
31struct MappedStorageChanges<H: Hasher> {
32    /// All changes to the main storage.
33    ///
34    /// A value of `None` means that it was deleted.
35    pub main_storage_changes: HashMap<StorageKey, Option<StorageValue>>,
36    /// All changes to the child storages.
37    pub child_storage_changes: HashMap<StorageKey, HashMap<StorageKey, Option<StorageValue>>>,
38    /// Offchain state changes to write to the offchain database.
39    pub offchain_storage_changes: HashMap<(Vec<u8>, Vec<u8>), OffchainOverlayedChange>,
40    /// A transaction for the backend that contains all changes from
41    /// [`main_storage_changes`](StorageChanges::main_storage_changes) and from
42    /// [`child_storage_changes`](StorageChanges::child_storage_changes) but excluding
43    /// [`offchain_storage_changes`](StorageChanges::offchain_storage_changes).
44    pub transaction: BackendTransaction<H>,
45    /// The storage root after applying the transaction.
46    pub transaction_storage_root: H::Out,
47    /// Changes to the transaction index,
48    pub transaction_index_changes: Vec<IndexOperation>,
49}
50
51#[derive(Clone)]
52struct RuntimeCode {
53    code: Vec<u8>,
54    heap_pages: Option<u64>,
55    hash: Vec<u8>,
56}
57
58impl<H: Hasher> From<MappedStorageChanges<H>> for StorageChanges<H> {
59    fn from(value: MappedStorageChanges<H>) -> Self {
60        let MappedStorageChanges {
61            main_storage_changes,
62            child_storage_changes,
63            offchain_storage_changes,
64            transaction,
65            transaction_storage_root,
66            transaction_index_changes,
67        } = value;
68        StorageChanges {
69            main_storage_changes: main_storage_changes.into_iter().collect(),
70            child_storage_changes: child_storage_changes
71                .into_iter()
72                .map(|(k, v)| (k, v.into_iter().collect()))
73                .collect(),
74            offchain_storage_changes: offchain_storage_changes.into_iter().collect(),
75            transaction,
76            transaction_storage_root,
77            transaction_index_changes,
78        }
79    }
80}
81
82impl<H: Hasher> From<StorageChanges<H>> for MappedStorageChanges<H> {
83    fn from(value: StorageChanges<H>) -> Self {
84        let StorageChanges {
85            main_storage_changes,
86            child_storage_changes,
87            offchain_storage_changes,
88            transaction,
89            transaction_storage_root,
90            transaction_index_changes,
91        } = value;
92        MappedStorageChanges {
93            main_storage_changes: main_storage_changes.into_iter().collect(),
94            child_storage_changes: child_storage_changes
95                .into_iter()
96                .map(|(k, v)| (k, v.into_iter().collect()))
97                .collect(),
98            offchain_storage_changes: offchain_storage_changes.into_iter().collect(),
99            transaction,
100            transaction_storage_root,
101            transaction_index_changes,
102        }
103    }
104}
105
106impl<H: Hasher> MappedStorageChanges<H> {
107    fn consolidate_storage_changes(&mut self, changes: StorageChanges<H>) -> H::Out {
108        let StorageChanges {
109            main_storage_changes,
110            child_storage_changes,
111            offchain_storage_changes,
112            transaction,
113            transaction_storage_root,
114            mut transaction_index_changes,
115        } = changes;
116        self.main_storage_changes.extend(main_storage_changes);
117        child_storage_changes
118            .into_iter()
119            .for_each(|(k, v)| self.child_storage_changes.entry(k).or_default().extend(v));
120        self.offchain_storage_changes
121            .extend(offchain_storage_changes);
122        self.transaction.consolidate(transaction);
123        self.transaction.purge();
124        self.transaction_index_changes
125            .append(&mut transaction_index_changes);
126        let previous_storage_root = self.transaction_storage_root;
127        self.transaction_storage_root = transaction_storage_root;
128        previous_storage_root
129    }
130}
131
132/// Storage changes are the collected throughout the execution.
133pub struct CollectedStorageChanges<H: Hasher> {
134    /// Storage changes that are captured during the execution.
135    /// Can be used for block import.
136    pub storage_changes: StorageChanges<H>,
137    /// Intermediate storage roots in between the execution.
138    pub intermediate_roots: Vec<H::Out>,
139}
140
141/// Create a new trie backend with memory DB delta changes.
142///
143/// This can be used to verify any extrinsic-specific execution on the combined state of `backend`
144/// and `delta`.
145pub fn create_delta_backend<'a, S, H>(
146    backend: &'a TrieBackend<S, H>,
147    delta: &'a BackendTransaction<H>,
148    post_delta_root: H::Out,
149) -> TrieBackend<DeltaBackend<'a, S, H>, H>
150where
151    S: 'a + TrieBackendStorage<H>,
152    H: 'a + Hasher,
153    H::Out: Codec,
154{
155    create_delta_backend_with_maybe_delta(backend, Some(delta), post_delta_root)
156}
157
158fn create_delta_backend_with_maybe_delta<'a, S, H>(
159    backend: &'a TrieBackend<S, H>,
160    maybe_delta: Option<&'a BackendTransaction<H>>,
161    post_delta_root: H::Out,
162) -> TrieBackend<DeltaBackend<'a, S, H>, H>
163where
164    S: 'a + TrieBackendStorage<H>,
165    H: 'a + Hasher,
166    H::Out: Codec,
167{
168    let essence = backend.essence();
169    let delta_backend = DeltaBackend {
170        backend: essence.backend_storage(),
171        delta: maybe_delta,
172        _phantom: PhantomData::<H>,
173    };
174    TrieBackendBuilder::new(delta_backend, post_delta_root).build()
175}
176
177/// DeltaBackend provides the TrieBackend using main backend and some delta changes
178/// that are not part of the main backend.
179pub struct DeltaBackend<'a, S, H>
180where
181    S: 'a + TrieBackendStorage<H>,
182    H: 'a + Hasher,
183{
184    backend: &'a S,
185    delta: Option<&'a BackendTransaction<H>>,
186    _phantom: PhantomData<H>,
187}
188
189impl<'a, S, H> TrieBackendStorage<H> for DeltaBackend<'a, S, H>
190where
191    S: 'a + TrieBackendStorage<H>,
192    H: 'a + Hasher,
193{
194    fn get(&self, key: &H::Out, prefix: Prefix) -> Result<Option<DBValue>, String> {
195        if let Some(db) = &self.delta
196            && let Some(v) = HashDB::get(*db, key, prefix)
197        {
198            Ok(Some(v))
199        } else {
200            Ok(self.backend.get(key, prefix)?)
201        }
202    }
203}
204
205pub(crate) struct TrieBackendApi<Client, Block: BlockT, Backend: backend::Backend<Block>, Exec> {
206    parent_hash: Block::Hash,
207    parent_number: NumberFor<Block>,
208    client: Arc<Client>,
209    state: Backend::State,
210    executor: Arc<Exec>,
211    maybe_storage_changes: Option<MappedStorageChanges<HashingFor<Block>>>,
212    intermediate_roots: Vec<Block::Hash>,
213    runtime_code: RuntimeCode,
214}
215
216impl<Client, Block, Backend, Exec> FetchRuntimeCode for TrieBackendApi<Client, Block, Backend, Exec>
217where
218    Block: BlockT,
219    Backend: backend::Backend<Block>,
220{
221    fn fetch_runtime_code(&self) -> Option<Cow<'_, [u8]>> {
222        Some(Cow::from(&self.runtime_code.code))
223    }
224}
225
226impl<Client, Block, Backend, Exec> TrieBackendApi<Client, Block, Backend, Exec>
227where
228    Block: BlockT,
229    Backend: backend::Backend<Block>,
230    Client: ExecutorProvider<Block>,
231    Exec: CodeExecutor,
232{
233    pub(crate) fn new(
234        parent_hash: Block::Hash,
235        parent_number: NumberFor<Block>,
236        client: Arc<Client>,
237        backend: Arc<Backend>,
238        executor: Arc<Exec>,
239    ) -> Result<Self, sp_blockchain::Error> {
240        // Trusted: bounded block-building context
241        let state = backend.state_at(parent_hash, sc_client_api::TrieCacheContext::Trusted)?;
242        let trie_backend = state.as_trie_backend();
243        let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend);
244        let sp_core::traits::RuntimeCode {
245            code_fetcher,
246            heap_pages,
247            hash,
248        } = state_runtime_code
249            .runtime_code()
250            .map_err(sp_blockchain::Error::RuntimeCode)?;
251        let runtime_code = RuntimeCode {
252            code: code_fetcher
253                .fetch_runtime_code()
254                .map(|c| c.to_vec())
255                .ok_or(sp_blockchain::Error::RuntimeCode("missing runtime code"))?,
256            heap_pages,
257            hash,
258        };
259        Ok(Self {
260            parent_hash,
261            parent_number,
262            client,
263            state,
264            executor,
265            maybe_storage_changes: None,
266            intermediate_roots: vec![],
267            runtime_code,
268        })
269    }
270
271    fn runtime_code(&self) -> sp_core::traits::RuntimeCode<'_> {
272        sp_core::traits::RuntimeCode {
273            code_fetcher: self,
274            heap_pages: self.runtime_code.heap_pages,
275            hash: self.runtime_code.hash.clone(),
276        }
277    }
278
279    fn call_function<R: Decode>(
280        &self,
281        method: &str,
282        call_data: Vec<u8>,
283        trie_backend: &TrieDeltaBackendFor<Backend::State, Block>,
284        overlayed_changes: &mut OverlayedChanges<HashingFor<Block>>,
285    ) -> Result<R, sp_blockchain::Error> {
286        let mut extensions = self
287            .client
288            .execution_extensions()
289            .extensions(self.parent_hash, self.parent_number);
290
291        let runtime_code = self.runtime_code();
292
293        let result = StateMachine::<_, _, _>::new(
294            trie_backend,
295            overlayed_changes,
296            &*self.executor,
297            method,
298            call_data.as_slice(),
299            &mut extensions,
300            &runtime_code,
301            CallContext::Onchain,
302        )
303        .set_parent_hash(self.parent_hash)
304        .execute()?;
305
306        R::decode(&mut result.as_slice())
307            .map_err(|err| sp_blockchain::Error::CallResultDecode("failed to decode Result", err))
308    }
309
310    fn consolidate_storage_changes(&mut self, changes: StorageChanges<HashingFor<Block>>) {
311        let changes = if let Some(mut mapped_changes) = self.maybe_storage_changes.take() {
312            let previous_root = mapped_changes.consolidate_storage_changes(changes);
313            self.intermediate_roots.push(previous_root);
314            mapped_changes
315        } else {
316            changes.into()
317        };
318        self.maybe_storage_changes = Some(changes)
319    }
320
321    pub(crate) fn initialize_block(
322        &self,
323        header: Block::Header,
324        backend: &TrieDeltaBackendFor<Backend::State, Block>,
325        overlayed_changes: &mut OverlayedChanges<HashingFor<Block>>,
326    ) -> Result<ExtrinsicInclusionMode, sp_blockchain::Error> {
327        let call_data = header.encode();
328        self.call_function(
329            "Core_initialize_block",
330            call_data,
331            backend,
332            overlayed_changes,
333        )
334    }
335
336    pub(crate) fn apply_extrinsic(
337        &self,
338        extrinsic: ExtrinsicFor<Block>,
339        backend: &TrieDeltaBackendFor<Backend::State, Block>,
340        overlayed_changes: &mut OverlayedChanges<HashingFor<Block>>,
341    ) -> Result<ApplyExtrinsicResult, sp_blockchain::Error> {
342        let call_data = extrinsic.encode();
343        self.call_function(
344            "BlockBuilder_apply_extrinsic",
345            call_data,
346            backend,
347            overlayed_changes,
348        )
349    }
350
351    pub(crate) fn finalize_block(
352        &self,
353        backend: &TrieDeltaBackendFor<Backend::State, Block>,
354        overlayed_changes: &mut OverlayedChanges<HashingFor<Block>>,
355    ) -> Result<Block::Header, sp_blockchain::Error> {
356        self.call_function(
357            "BlockBuilder_finalize_block",
358            vec![],
359            backend,
360            overlayed_changes,
361        )
362    }
363
364    pub(crate) fn inherent_extrinsics(
365        &self,
366        inherent: InherentData,
367        backend: &TrieDeltaBackendFor<Backend::State, Block>,
368        overlayed_changes: &mut OverlayedChanges<HashingFor<Block>>,
369    ) -> Result<Vec<ExtrinsicFor<Block>>, sp_blockchain::Error> {
370        let call_data = inherent.encode();
371        self.call_function(
372            "BlockBuilder_inherent_extrinsics",
373            call_data,
374            backend,
375            overlayed_changes,
376        )
377    }
378
379    /// Collect storage changes returns the storage changes and intermediate roots collected so far.
380    /// The changes are reset after this call.
381    /// Could return None if there were no execution done.
382    pub(crate) fn collect_storage_changes(
383        &mut self,
384    ) -> Option<CollectedStorageChanges<HashingFor<Block>>> {
385        let mut intermediate_roots = mem::take(&mut self.intermediate_roots);
386        // we did not add the last transaction storage root.
387        // include that and then return
388        let maybe_storage_changes = self.maybe_storage_changes.take();
389        if let Some(storage_changes) = maybe_storage_changes {
390            intermediate_roots.push(storage_changes.transaction_storage_root);
391            Some(CollectedStorageChanges {
392                storage_changes: storage_changes.into(),
393                intermediate_roots,
394            })
395        } else {
396            None
397        }
398    }
399
400    pub(crate) fn execute_in_transaction<F, R>(
401        &mut self,
402        call: F,
403    ) -> Result<R, sp_blockchain::Error>
404    where
405        F: FnOnce(
406            &Self,
407            &TrieDeltaBackendFor<Backend::State, Block>,
408            &mut OverlayedChanges<HashingFor<Block>>,
409        ) -> TransactionOutcome<Result<R, sp_blockchain::Error>>,
410        R: Decode,
411    {
412        let trie_backend = self.state.as_trie_backend();
413        let mut overlayed_changes = OverlayedChanges::default();
414        let (state_root, delta) = if let Some(changes) = &self.maybe_storage_changes {
415            (changes.transaction_storage_root, Some(&changes.transaction))
416        } else {
417            (*trie_backend.root(), None)
418        };
419        let trie_delta_backend =
420            create_delta_backend_with_maybe_delta(trie_backend, delta, state_root);
421
422        let outcome = call(self, &trie_delta_backend, &mut overlayed_changes);
423        match outcome {
424            TransactionOutcome::Commit(result) => {
425                let state_version = sp_core::storage::StateVersion::V1;
426                let storage_changes = overlayed_changes
427                    .drain_storage_changes(&trie_delta_backend, state_version)
428                    .map_err(sp_blockchain::Error::StorageChanges)?;
429                self.consolidate_storage_changes(storage_changes);
430                result
431            }
432            TransactionOutcome::Rollback(result) => result,
433        }
434    }
435}