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