diff --git a/Cargo.lock b/Cargo.lock index 0222643f16..c65d4830d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2513,6 +2513,7 @@ dependencies = [ "sp-block-builder", "sp-blockchain", "sp-core", + "sp-externalities", "sp-inherents", "sp-runtime", "sp-state-machine", diff --git a/crates/sp-domains-fraud-proof/src/execution_prover.rs b/crates/sp-domains-fraud-proof/src/execution_prover.rs index 84fe2919a4..3414d1c282 100644 --- a/crates/sp-domains-fraud-proof/src/execution_prover.rs +++ b/crates/sp-domains-fraud-proof/src/execution_prover.rs @@ -6,13 +6,12 @@ use crate::fraud_proof::ExecutionPhase; use domain_block_builder::create_delta_backend; -use hash_db::HashDB; use sc_client_api::backend::Backend; use sp_api::StorageProof; use sp_core::traits::CodeExecutor; use sp_runtime::traits::{Block as BlockT, HashingFor}; use sp_state_machine::backend::AsTrieBackend; -use sp_trie::DBValue; +use sp_state_machine::BackendTransaction; use std::marker::PhantomData; use std::sync::Arc; @@ -40,12 +39,12 @@ where /// Returns a storage proof which can be used to reconstruct a partial state trie to re-run /// the execution by someone who does not own the whole state. - pub fn prove_execution, DBValue>>( + pub fn prove_execution( &self, at: Block::Hash, execution_phase: &ExecutionPhase, call_data: &[u8], - delta_changes: Option<(DB, Block::Hash)>, + delta_changes: Option<(BackendTransaction>, Block::Hash)>, ) -> sp_blockchain::Result { let state = self.backend.state_at(at)?; diff --git a/domains/client/block-builder/Cargo.toml b/domains/client/block-builder/Cargo.toml index 18daefca98..5798ca4122 100644 --- a/domains/client/block-builder/Cargo.toml +++ b/domains/client/block-builder/Cargo.toml @@ -20,6 +20,7 @@ sp-api = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89 sp-blockchain = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } sp-block-builder = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } sp-core = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } +sp-externalities = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } sp-inherents = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } sp-runtime = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } sp-state-machine = { git = "https://github.com/subspace/polkadot-sdk", rev = "94a1a8143a89bbe9f938c1939ff68abc1519a305" } diff --git a/domains/client/block-builder/src/custom_api.rs b/domains/client/block-builder/src/custom_api.rs index f07aba67da..05dd41b94b 100644 --- a/domains/client/block-builder/src/custom_api.rs +++ b/domains/client/block-builder/src/custom_api.rs @@ -1,29 +1,64 @@ //! Custom API that is efficient to collect storage roots. -use codec::Codec; +// TODO: remove in later commits +#![allow(dead_code)] + +use codec::{Codec, Decode, Encode}; use hash_db::{HashDB, Hasher, Prefix}; -use sp_state_machine::{DBValue, TrieBackend, TrieBackendBuilder, TrieBackendStorage}; +use sc_client_api::{backend, ExecutorProvider, StateBackend}; +use sp_core::traits::{CallContext, CodeExecutor, RuntimeCode}; +use sp_runtime::traits::{Block as BlockT, HashingFor, NumberFor}; +use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode, TransactionOutcome}; +use sp_state_machine::backend::AsTrieBackend; +use sp_state_machine::{ + BackendTransaction, DBValue, OverlayedChanges, StateMachine, StorageChanges, TrieBackend, + TrieBackendBuilder, TrieBackendStorage, +}; use std::marker::PhantomData; +use std::sync::Arc; + +type TrieBackendStorageFor = + >>::TrieBackendStorage; + +type TrieDeltaBackendFor<'a, State, Block> = TrieBackend< + DeltaBackend<'a, TrieBackendStorageFor, HashingFor>, + HashingFor, +>; + +type TrieBackendFor = + TrieBackend, HashingFor>; /// Create a new trie backend with memory DB delta changes. /// /// This can be used to verify any extrinsic-specific execution on the combined state of `backend` /// and `delta`. -pub fn create_delta_backend<'a, S, H, DB>( +pub fn create_delta_backend<'a, S, H>( + backend: &'a TrieBackend, + delta: BackendTransaction, + post_delta_root: H::Out, +) -> TrieBackend, H> +where + S: 'a + TrieBackendStorage, + H: 'a + Hasher, + H::Out: Codec, +{ + create_delta_backend_with_maybe_delta(backend, Some(delta), post_delta_root) +} + +fn create_delta_backend_with_maybe_delta<'a, S, H>( backend: &'a TrieBackend, - delta: DB, + maybe_delta: Option>, post_delta_root: H::Out, -) -> TrieBackend, H> +) -> TrieBackend, H> where S: 'a + TrieBackendStorage, H: 'a + Hasher, H::Out: Codec, - DB: HashDB, { let essence = backend.essence(); let delta_backend = DeltaBackend { backend: essence.backend_storage(), - delta, + delta: maybe_delta, _phantom: PhantomData::, }; TrieBackendBuilder::new(delta_backend, post_delta_root).build() @@ -31,27 +66,233 @@ where /// DeltaBackend provides the TrieBackend using main backend and some delta changes /// that are not part of the main backend. -pub struct DeltaBackend<'a, S, H, DB> +pub struct DeltaBackend<'a, S, H> where S: 'a + TrieBackendStorage, H: 'a + Hasher, - DB: HashDB, { backend: &'a S, - delta: DB, + delta: Option>, _phantom: PhantomData, } -impl<'a, S, H, DB> TrieBackendStorage for DeltaBackend<'a, S, H, DB> +impl<'a, S, H> TrieBackendStorage for DeltaBackend<'a, S, H> where S: 'a + TrieBackendStorage, H: 'a + Hasher, - DB: HashDB, { fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { - match HashDB::get(&self.delta, key, prefix) { - Some(v) => Ok(Some(v)), - None => Ok(self.backend.get(key, prefix)?), + if let Some(db) = &self.delta + && let Some(v) = HashDB::get(db, key, prefix) + { + Ok(Some(v)) + } else { + Ok(self.backend.get(key, prefix)?) } } } + +impl<'a, S, H> DeltaBackend<'a, S, H> +where + S: 'a + TrieBackendStorage, + H: 'a + Hasher, +{ + fn consolidate_delta(&mut self, db: BackendTransaction) { + let delta = if let Some(mut delta) = self.delta.take() { + delta.consolidate(db); + delta + } else { + db + }; + self.delta = Some(delta); + } +} + +pub(crate) struct TrieBackendApi, Exec> { + parent_hash: Block::Hash, + parent_number: NumberFor, + client: Arc, + state: Backend::State, + executor: Exec, + maybe_storage_changes: Option>>, +} + +impl TrieBackendApi +where + Block: BlockT, + Backend: backend::Backend, + Client: ExecutorProvider, + Exec: CodeExecutor, +{ + pub(crate) fn new( + parent_hash: Block::Hash, + parent_number: NumberFor, + client: Arc, + backend: Arc, + executor: Exec, + ) -> Result { + let state = backend.state_at(parent_hash)?; + Ok(Self { + parent_hash, + parent_number, + client, + state, + executor, + maybe_storage_changes: None, + }) + } + + fn call_function( + &self, + method: &str, + call_data: Vec, + trie_backend: &TrieDeltaBackendFor, + runtime_code: RuntimeCode, + overlayed_changes: &mut OverlayedChanges>, + ) -> Result { + let mut extensions = self + .client + .execution_extensions() + .extensions(self.parent_hash, self.parent_number); + + let result = StateMachine::<_, _, _>::new( + trie_backend, + overlayed_changes, + &self.executor, + method, + call_data.as_slice(), + &mut extensions, + &runtime_code, + CallContext::Onchain, + ) + .set_parent_hash(self.parent_hash) + .execute()?; + + R::decode(&mut result.as_slice()) + .map_err(|err| sp_blockchain::Error::CallResultDecode("failed to decode Result", err)) + } + + fn consolidate_storage_changes(&mut self, mut changes: StorageChanges>) { + let changes = if let Some(mut current_changes) = self.maybe_storage_changes.take() { + current_changes + .main_storage_changes + .append(&mut changes.main_storage_changes); + current_changes + .child_storage_changes + .append(&mut changes.child_storage_changes); + current_changes + .offchain_storage_changes + .append(&mut changes.offchain_storage_changes); + current_changes.transaction.consolidate(changes.transaction); + current_changes.transaction_storage_root = changes.transaction_storage_root; + current_changes + .transaction_index_changes + .append(&mut changes.transaction_index_changes); + current_changes + } else { + changes + }; + self.maybe_storage_changes = Some(changes) + } + + pub(crate) fn initialize_block( + &self, + header: Block::Header, + backend: &TrieDeltaBackendFor, + runtime_code: RuntimeCode, + overlayed_changes: &mut OverlayedChanges>, + ) -> Result { + let call_data = header.encode(); + self.call_function( + "Core_initialize_block", + call_data, + backend, + runtime_code, + overlayed_changes, + ) + } + + pub(crate) fn apply_extrinsic( + &self, + extrinsic: ::Extrinsic, + backend: &TrieDeltaBackendFor, + runtime_code: RuntimeCode, + overlayed_changes: &mut OverlayedChanges>, + ) -> Result { + let call_data = extrinsic.encode(); + self.call_function( + "BlockBuilder_apply_extrinsic", + call_data, + backend, + runtime_code, + overlayed_changes, + ) + } + + pub(crate) fn finalize_block( + &self, + backend: &TrieDeltaBackendFor, + runtime_code: RuntimeCode, + overlayed_changes: &mut OverlayedChanges>, + ) -> Result { + self.call_function( + "BlockBuilder_finalize_block", + vec![], + backend, + runtime_code, + overlayed_changes, + ) + } + + pub(crate) fn execute_in_transaction( + &mut self, + call: F, + ) -> Result + where + F: FnOnce( + &Self, + &TrieDeltaBackendFor, + RuntimeCode, + &mut OverlayedChanges>, + ) -> TransactionOutcome, + R: Decode, + { + let trie_backend = self.state.as_trie_backend(); + let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend); + let runtime_code = state_runtime_code + .runtime_code() + .map_err(sp_blockchain::Error::RuntimeCode)?; + let mut overlayed_changes = OverlayedChanges::default(); + let (state_root, delta) = if let Some(changes) = &self.maybe_storage_changes { + ( + changes.transaction_storage_root, + Some(changes.transaction.clone()), + ) + } else { + (*trie_backend.root(), None) + }; + let trie_delta_backend = + create_delta_backend_with_maybe_delta(trie_backend, delta, state_root); + + let outcome = call( + self, + &trie_delta_backend, + runtime_code, + &mut overlayed_changes, + ); + + let result = match outcome { + TransactionOutcome::Commit(result) => { + let state_version = sp_core::storage::StateVersion::V1; + let storage_changes = overlayed_changes + .drain_storage_changes(&self.state, state_version) + .map_err(sp_blockchain::Error::StorageChanges)?; + self.consolidate_storage_changes(storage_changes); + result + } + TransactionOutcome::Rollback(result) => result, + }; + + Ok(result) + } +} diff --git a/domains/client/block-builder/src/lib.rs b/domains/client/block-builder/src/lib.rs index 2df5c4f910..f37c51b7e3 100644 --- a/domains/client/block-builder/src/lib.rs +++ b/domains/client/block-builder/src/lib.rs @@ -25,6 +25,7 @@ //! initialize a block, to push extrinsics and to finalize a block. #![warn(missing_docs)] +#![feature(let_chains)] mod custom_api;