1234567891011121314151617use std::sync::Arc;1819use anyhow::anyhow;20use jsonrpsee::{core::RpcResult as Result, proc_macros::rpc};21use parity_scale_codec::{Decode, Encode};22use sc_client_api::backend::Backend;23use sc_executor::NativeElseWasmExecutor;24use sc_rpc_api::DenyUnsafe;25use sc_service::{config::ExecutionStrategy, NativeExecutionDispatch};26use sp_api::{AsTrieBackend, BlockId, BlockT, ProvideRuntimeApi};27use sp_blockchain::HeaderBackend;28use sp_core::{29 offchain::{30 testing::{TestOffchainExt, TestTransactionPoolExt},31 OffchainDbExt, OffchainWorkerExt, TransactionPoolExt,32 },33 testing::TaskExecutor,34 traits::TaskExecutorExt,35 Bytes,36};37use sp_externalities::Extensions;38use sp_keystore::{testing::KeyStore, KeystoreExt};39use sp_runtime::traits::Header;40use sp_state_machine::{StateMachine, TrieBackendBuilder};41use trie_db::{Trie, TrieDBBuilder};42use up_common::types::opaque::RuntimeId;43use up_pov_estimate_rpc::{PovEstimateApi as PovEstimateRuntimeApi, PovInfo, TrieKeyValue};4445use crate::define_struct_for_server_api;4647type HasherOf<Block> = <<Block as BlockT>::Header as Header>::Hashing;48type StateOf<Block> = <sc_service::TFullBackend<Block> as Backend<Block>>::State;4950pub struct ExecutorParams {51 pub wasm_method: sc_service::config::WasmExecutionMethod,52 pub default_heap_pages: Option<u64>,53 pub max_runtime_instances: usize,54 pub runtime_cache_size: u8,55}5657#[cfg(feature = "unique-runtime")]58pub struct UniqueRuntimeExecutor;5960#[cfg(feature = "quartz-runtime")]61pub struct QuartzRuntimeExecutor;6263pub struct OpalRuntimeExecutor;6465#[cfg(feature = "unique-runtime")]66impl NativeExecutionDispatch for UniqueRuntimeExecutor {67 type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions;6869 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {70 unique_runtime::api::dispatch(method, data)71 }7273 fn native_version() -> sc_executor::NativeVersion {74 unique_runtime::native_version()75 }76}7778#[cfg(feature = "quartz-runtime")]79impl NativeExecutionDispatch for QuartzRuntimeExecutor {80 type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions;8182 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {83 quartz_runtime::api::dispatch(method, data)84 }8586 fn native_version() -> sc_executor::NativeVersion {87 quartz_runtime::native_version()88 }89}9091impl NativeExecutionDispatch for OpalRuntimeExecutor {92 type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions;9394 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {95 opal_runtime::api::dispatch(method, data)96 }9798 fn native_version() -> sc_executor::NativeVersion {99 opal_runtime::native_version()100 }101}102103#[cfg(feature = "pov-estimate")]104define_struct_for_server_api! {105 PovEstimate {106 client: Arc<Client>,107 backend: Arc<sc_service::TFullBackend<Block>>,108 deny_unsafe: DenyUnsafe,109 exec_params: ExecutorParams,110 runtime_id: RuntimeId,111 }112}113114#[rpc(server)]115#[async_trait]116pub trait PovEstimateApi<BlockHash> {117 #[method(name = "povinfo_estimateExtrinsicPoV")]118 fn estimate_extrinsic_pov(119 &self,120 encoded_xts: Vec<Bytes>,121 at: Option<BlockHash>,122 ) -> Result<PovInfo>;123}124125#[allow(deprecated)]126#[cfg(feature = "pov-estimate")]127impl<C, Block> PovEstimateApiServer<<Block as BlockT>::Hash> for PovEstimate<C, Block>128where129 Block: BlockT,130 C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,131 C::Api: PovEstimateRuntimeApi<Block>,132{133 fn estimate_extrinsic_pov(134 &self,135 encoded_xts: Vec<Bytes>,136 at: Option<<Block as BlockT>::Hash>,137 ) -> Result<PovInfo> {138 self.deny_unsafe.check_if_safe()?;139140 let at = at.unwrap_or_else(|| self.client.info().best_hash);141 let state = self142 .backend143 .state_at(at)144 .map_err(|_| anyhow!("unable to fetch the state at {at:?}"))?;145146 match &self.runtime_id {147 #[cfg(feature = "unique-runtime")]148 RuntimeId::Unique => execute_extrinsic_in_sandbox::<Block, UniqueRuntimeExecutor>(149 state,150 &self.exec_params,151 encoded_xts,152 ),153154 #[cfg(feature = "quartz-runtime")]155 RuntimeId::Quartz => execute_extrinsic_in_sandbox::<Block, QuartzRuntimeExecutor>(156 state,157 &self.exec_params,158 encoded_xts,159 ),160161 RuntimeId::Opal => execute_extrinsic_in_sandbox::<Block, OpalRuntimeExecutor>(162 state,163 &self.exec_params,164 encoded_xts,165 ),166167 runtime_id => Err(anyhow!("unknown runtime id {:?}", runtime_id).into()),168 }169 }170}171172fn full_extensions() -> Extensions {173 let mut extensions = Extensions::default();174 extensions.register(TaskExecutorExt::new(TaskExecutor::new()));175 let (offchain, _offchain_state) = TestOffchainExt::new();176 let (pool, _pool_state) = TestTransactionPoolExt::new();177 extensions.register(OffchainDbExt::new(offchain.clone()));178 extensions.register(OffchainWorkerExt::new(offchain));179 extensions.register(KeystoreExt(std::sync::Arc::new(KeyStore::new())));180 extensions.register(TransactionPoolExt::new(pool));181182 extensions183}184185fn execute_extrinsic_in_sandbox<Block, D>(186 state: StateOf<Block>,187 exec_params: &ExecutorParams,188 encoded_xts: Vec<Bytes>,189) -> Result<PovInfo>190where191 Block: BlockT,192 D: NativeExecutionDispatch + 'static,193{194 let backend = state.as_trie_backend().clone();195 let mut changes = Default::default();196 let runtime_code_backend = sp_state_machine::backend::BackendRuntimeCode::new(backend);197198 let proving_backend = TrieBackendBuilder::wrap(&backend)199 .with_recorder(Default::default())200 .build();201202 let runtime_code = runtime_code_backend203 .runtime_code()204 .map_err(|_| anyhow!("runtime code backend creation failed"))?;205206 let pre_root = *backend.root();207208 let executor = sc_service::new_native_or_wasm_executor(exec_params);209 let execution = ExecutionStrategy::NativeElseWasm;210211 let mut results = Vec::new();212213 for encoded_xt in encoded_xts {214 let encoded_bytes = encoded_xt.encode();215216 let xt_result = StateMachine::new(217 &proving_backend,218 &mut changes,219 &executor,220 "PovEstimateApi_pov_estimate",221 encoded_bytes.as_slice(),222 full_extensions(),223 &runtime_code,224 sp_core::testing::TaskExecutor::new(),225 )226 .execute(execution.into())227 .map_err(|e| anyhow!("failed to execute the extrinsic {:?}", e))?;228229 let xt_result = Decode::decode(&mut &*xt_result)230 .map_err(|e| anyhow!("failed to decode the extrinsic result {:?}", e))?;231232 results.push(xt_result);233 }234235 let root = proving_backend.root().clone();236237 let proof = proving_backend238 .extract_proof()239 .expect("A recorder was set and thus, a storage proof can be extracted; qed");240 let proof_size = proof.encoded_size();241242 let memory_db = proof.clone().into_memory_db();243244 let tree_db =245 TrieDBBuilder::<sp_trie::LayoutV1<HasherOf<Block>>>::new(&memory_db, &root).build();246247 let key_values = tree_db248 .iter()249 .map_err(|e| anyhow!("failed to retrieve tree db key values: {:?}", e))?250 .filter_map(|item| {251 let item = item.ok()?;252253 Some(TrieKeyValue {254 key: item.0,255 value: item.1,256 })257 })258 .collect();259260 let compact_proof = proof261 .clone()262 .into_compact_proof::<HasherOf<Block>>(pre_root)263 .map_err(|e| anyhow!("failed to generate compact proof {:?}", e))?;264 let compact_proof_size = compact_proof.encoded_size();265266 let compressed_proof = zstd::stream::encode_all(&compact_proof.encode()[..], 0)267 .map_err(|e| anyhow!("failed to generate compact proof {:?}", e))?;268 let compressed_proof_size = compressed_proof.len();269270 Ok(PovInfo {271 proof_size: proof_size as u64,272 compact_proof_size: compact_proof_size as u64,273 compressed_proof_size: compressed_proof_size as u64,274 results,275 key_values,276 })277}