1#![warn(rust_2018_idioms)]
12
13pub mod inherents;
14pub mod stateless_runtime;
15
16use crate::inherents::is_runtime_upgraded;
17use crate::stateless_runtime::StatelessRuntime;
18use domain_runtime_primitives::opaque::AccountId;
19use domain_runtime_primitives::{CheckExtrinsicsValidityError, opaque};
20use parity_scale_codec::Encode;
21use sc_client_api::{BlockBackend, backend};
22use sc_executor::RuntimeVersionOf;
23use sp_api::{ApiError, ProvideRuntimeApi};
24use sp_blockchain::HeaderBackend;
25use sp_core::H256;
26use sp_core::traits::{CodeExecutor, FetchRuntimeCode};
27use sp_domains::bundle::{InboxedBundle, InvalidBundleType, OpaqueBundle, OpaqueBundles};
28use sp_domains::core_api::DomainCoreApi;
29use sp_domains::execution_receipt::ExecutionReceipt;
30use sp_domains::extrinsics::deduplicate_and_shuffle_extrinsics;
31use sp_domains::{DomainId, DomainsApi, ExtrinsicDigest, HeaderHashingFor, ReceiptValidity};
32use sp_messenger::MessengerApi;
33use sp_mmr_primitives::MmrApi;
34use sp_runtime::traits::{Block as BlockT, Hash as HashT, NumberFor};
35use sp_state_machine::LayoutV1;
36use sp_state_machine::backend::AsTrieBackend;
37use sp_subspace_mmr::ConsensusChainMmrLeafProof;
38use std::collections::VecDeque;
39use std::marker::PhantomData;
40use std::sync::Arc;
41use subspace_core_primitives::{Randomness, U256};
42use subspace_runtime_primitives::{Balance, ExtrinsicFor};
43
44type DomainBlockElements<CBlock> = (Vec<ExtrinsicFor<CBlock>>, Randomness);
45
46enum BundleValidity<Extrinsic> {
48 ValidWithSigner(Vec<(Option<opaque::AccountId>, Extrinsic)>),
50 Invalid(InvalidBundleType),
52}
53
54fn prepare_domain_block_elements<Block, CBlock, CClient>(
56 consensus_client: &CClient,
57 block_hash: CBlock::Hash,
58) -> sp_blockchain::Result<DomainBlockElements<CBlock>>
59where
60 Block: BlockT,
61 CBlock: BlockT,
62 CClient: HeaderBackend<CBlock> + BlockBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
63 CClient::Api: DomainsApi<CBlock, Block::Header>,
64{
65 let extrinsics = consensus_client.block_body(block_hash)?.ok_or_else(|| {
66 sp_blockchain::Error::Backend(format!("BlockBody of {block_hash:?} unavailable"))
67 })?;
68
69 let shuffling_seed = consensus_client
70 .runtime_api()
71 .extrinsics_shuffling_seed(block_hash)?;
72
73 Ok((extrinsics, shuffling_seed))
74}
75
76pub struct PreprocessResult<Block: BlockT> {
77 pub extrinsics: VecDeque<Block::Extrinsic>,
78 pub bundles: Vec<InboxedBundle<Block::Hash>>,
79}
80
81pub struct DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
82{
83 domain_id: DomainId,
84 client: Arc<Client>,
85 consensus_client: Arc<CClient>,
86 executor: Arc<Exec>,
87 backend: Arc<Backend>,
88 receipt_validator: ReceiptValidator,
89 _phantom_data: PhantomData<(Block, CBlock)>,
90}
91
92impl<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator: Clone> Clone
93 for DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
94{
95 fn clone(&self) -> Self {
96 Self {
97 domain_id: self.domain_id,
98 client: self.client.clone(),
99 consensus_client: self.consensus_client.clone(),
100 executor: self.executor.clone(),
101 backend: self.backend.clone(),
102 receipt_validator: self.receipt_validator.clone(),
103 _phantom_data: self._phantom_data,
104 }
105 }
106}
107
108pub trait ValidateReceipt<Block, CBlock>
109where
110 Block: BlockT,
111 CBlock: BlockT,
112{
113 fn validate_receipt(
114 &self,
115 receipt: &ExecutionReceipt<
116 NumberFor<CBlock>,
117 CBlock::Hash,
118 NumberFor<Block>,
119 Block::Hash,
120 Balance,
121 >,
122 ) -> sp_blockchain::Result<ReceiptValidity>;
123}
124
125impl<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
126 DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
127where
128 Block: BlockT,
129 Block::Hash: Into<H256>,
130 CBlock: BlockT,
131 CBlock::Hash: From<Block::Hash>,
132 NumberFor<CBlock>: From<NumberFor<Block>>,
133 Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + 'static,
134 Client::Api: DomainCoreApi<Block> + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>,
135 CClient: HeaderBackend<CBlock>
136 + BlockBackend<CBlock>
137 + ProvideRuntimeApi<CBlock>
138 + Send
139 + Sync
140 + 'static,
141 CClient::Api: DomainsApi<CBlock, Block::Header>
142 + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>
143 + MmrApi<CBlock, H256, NumberFor<CBlock>>,
144 Backend: backend::Backend<Block>,
145 Exec: CodeExecutor + RuntimeVersionOf,
146 ReceiptValidator: ValidateReceipt<Block, CBlock>,
147{
148 pub fn new(
149 domain_id: DomainId,
150 client: Arc<Client>,
151 consensus_client: Arc<CClient>,
152 executor: Arc<Exec>,
153 backend: Arc<Backend>,
154 receipt_validator: ReceiptValidator,
155 ) -> Self {
156 Self {
157 domain_id,
158 client,
159 consensus_client,
160 executor,
161 backend,
162 receipt_validator,
163 _phantom_data: Default::default(),
164 }
165 }
166
167 pub fn preprocess_consensus_block(
168 &self,
169 consensus_block_hash: CBlock::Hash,
170 parent_domain_block: (Block::Hash, NumberFor<Block>),
171 ) -> sp_blockchain::Result<Option<PreprocessResult<Block>>> {
172 let (primary_extrinsics, shuffling_seed) = prepare_domain_block_elements::<Block, CBlock, _>(
173 &*self.consensus_client,
174 consensus_block_hash,
175 )?;
176
177 let runtime_api = self.consensus_client.runtime_api();
178
179 let bundles = runtime_api.extract_successful_bundles(
180 consensus_block_hash,
181 self.domain_id,
182 primary_extrinsics,
183 )?;
184
185 if bundles.is_empty()
186 && !is_runtime_upgraded::<_, _, Block>(
187 &self.consensus_client,
188 consensus_block_hash,
189 self.domain_id,
190 )?
191 {
192 return Ok(None);
193 }
194
195 let tx_range = self
196 .consensus_client
197 .runtime_api()
198 .domain_tx_range(consensus_block_hash, self.domain_id)?;
199
200 let (inboxed_bundles, extrinsics) = self.compile_bundles_to_extrinsics(
201 bundles,
202 tx_range,
203 parent_domain_block,
204 consensus_block_hash,
205 )?;
206
207 let extrinsics =
208 deduplicate_and_shuffle_extrinsics::<Block::Extrinsic>(extrinsics, shuffling_seed);
209
210 Ok(Some(PreprocessResult {
211 extrinsics,
212 bundles: inboxed_bundles,
213 }))
214 }
215
216 #[allow(clippy::type_complexity)]
219 fn compile_bundles_to_extrinsics(
220 &self,
221 bundles: OpaqueBundles<CBlock, Block::Header, Balance>,
222 tx_range: U256,
223 (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
224 at_consensus_hash: CBlock::Hash,
225 ) -> sp_blockchain::Result<(
226 Vec<InboxedBundle<Block::Hash>>,
227 Vec<(Option<AccountId>, Block::Extrinsic)>,
228 )> {
229 let mut inboxed_bundles = Vec::with_capacity(bundles.len());
230 let mut valid_extrinsics = Vec::new();
231
232 for bundle in bundles {
233 if *bundle.receipt_domain_block_number() != parent_domain_number {
244 return Err(sp_blockchain::Error::RuntimeApiError(
245 ApiError::Application(
246 format!(
247 "Unexpected bundle in consensus block: {at_consensus_hash:?}, something must be wrong"
248 )
249 .into(),
250 ),
251 ));
252 }
253
254 let extrinsic_root = bundle.extrinsics_root();
255 let bundle_validity = self.batch_check_bundle_validity(
256 bundle,
257 &tx_range,
258 (parent_domain_hash, parent_domain_number),
259 at_consensus_hash,
260 )?;
261 match bundle_validity {
262 BundleValidity::ValidWithSigner(signer_and_extrinsics) => {
263 let bundle_digest: Vec<_> = signer_and_extrinsics
264 .iter()
265 .map(|(signer, tx)| {
266 (
267 signer.clone(),
268 ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<Block::Header>>>(
269 tx.encode(),
270 ),
271 )
272 })
273 .collect();
274 inboxed_bundles.push(InboxedBundle::valid(
275 HeaderHashingFor::<Block::Header>::hash_of(&bundle_digest),
276 extrinsic_root,
277 ));
278 valid_extrinsics.extend(signer_and_extrinsics);
279 }
280 BundleValidity::Invalid(invalid_bundle_type) => {
281 inboxed_bundles
282 .push(InboxedBundle::invalid(invalid_bundle_type, extrinsic_root));
283 }
284 }
285 }
286
287 Ok((inboxed_bundles, valid_extrinsics))
288 }
289
290 fn stateless_runtime_api(
291 &self,
292 parent_domain_hash: Block::Hash,
293 ) -> sp_blockchain::Result<StatelessRuntime<CBlock, Block, Exec>> {
294 let state = self
296 .backend
297 .state_at(parent_domain_hash, sc_client_api::TrieCacheContext::Trusted)?;
298 let trie_backend = state.as_trie_backend();
299 let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend);
300 let runtime_code = state_runtime_code
301 .runtime_code()
302 .map_err(sp_blockchain::Error::RuntimeCode)?
303 .fetch_runtime_code()
304 .ok_or(sp_blockchain::Error::RuntimeCode("missing runtime code"))?
305 .into_owned();
306
307 Ok(StatelessRuntime::<CBlock, Block, _>::new(
308 self.executor.clone(),
309 runtime_code.into(),
310 ))
311 }
312
313 fn batch_check_bundle_validity(
314 &self,
315 bundle: OpaqueBundle<NumberFor<CBlock>, CBlock::Hash, Block::Header, Balance>,
316 tx_range: &U256,
317 (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
318 at_consensus_hash: CBlock::Hash,
319 ) -> sp_blockchain::Result<BundleValidity<Block::Extrinsic>> {
320 let bundle_vrf_hash = U256::from_be_bytes(*bundle.proof_of_election().vrf_hash());
321 let bundle_length = bundle.body_length();
322 let bundle_estimated_weight = bundle.estimated_weight();
323 let mut maybe_invalid_bundle_type = None;
324
325 let stateless_runtime_api = self.stateless_runtime_api(parent_domain_hash)?;
331 let consensus_runtime_api = self.consensus_client.runtime_api();
332
333 let mut extrinsics =
341 stateless_runtime_api.decode_extrinsics_prefix(bundle.into_extrinsics())?;
342 if extrinsics.len() != bundle_length {
343 maybe_invalid_bundle_type
345 .replace(InvalidBundleType::UndecodableTx(extrinsics.len() as u32));
346 }
347
348 let signers = match stateless_runtime_api.extract_signer_if_all_within_tx_range(
349 &extrinsics,
350 &bundle_vrf_hash,
351 tx_range,
352 )? {
353 Err(index) => {
354 maybe_invalid_bundle_type.replace(InvalidBundleType::OutOfRangeTx(index));
355 extrinsics.truncate(index as usize);
356
357 Vec::default()
359 }
360 Ok(signers) => signers,
361 };
362
363 if let Some(index) = stateless_runtime_api.find_first_inherent_extrinsic(&extrinsics)? {
367 maybe_invalid_bundle_type.replace(InvalidBundleType::InherentExtrinsic(index));
368 extrinsics.truncate(index as usize);
369 }
370
371 let batch_xdm_mmr_proof =
372 stateless_runtime_api.batch_extract_native_xdm_mmr_proof(&extrinsics)?;
373 for (index, xdm_mmr_proof) in batch_xdm_mmr_proof {
374 let ConsensusChainMmrLeafProof {
375 opaque_mmr_leaf,
376 proof,
377 ..
378 } = xdm_mmr_proof;
379
380 if consensus_runtime_api
381 .verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)?
382 .is_err()
383 {
384 maybe_invalid_bundle_type.replace(InvalidBundleType::InvalidXDM(index));
385 extrinsics.truncate(index as usize);
386 break;
387 }
388 }
389
390 if let Err(CheckExtrinsicsValidityError {
393 extrinsic_index, ..
394 }) = self
395 .client
396 .runtime_api()
397 .check_extrinsics_and_do_pre_dispatch(
398 parent_domain_hash,
399 extrinsics.clone(),
400 parent_domain_number,
401 parent_domain_hash,
402 )?
403 {
404 maybe_invalid_bundle_type.replace(InvalidBundleType::IllegalTx(extrinsic_index));
405 }
406
407 if let Some(invalid_bundle_type) = maybe_invalid_bundle_type {
410 return Ok(BundleValidity::Invalid(invalid_bundle_type));
411 }
412
413 if bundle_estimated_weight != stateless_runtime_api.extrinsics_weight(&extrinsics)? {
414 return Ok(BundleValidity::Invalid(
415 InvalidBundleType::InvalidBundleWeight,
416 ));
417 }
418
419 let signer_and_extrinsics = signers.into_iter().zip(extrinsics).collect();
420 Ok(BundleValidity::ValidWithSigner(signer_and_extrinsics))
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use sp_domains::extrinsics::shuffle_extrinsics;
427 use sp_keyring::sr25519::Keyring;
428 use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
429 use subspace_core_primitives::Randomness;
430
431 #[test]
432 fn shuffle_extrinsics_should_work() {
433 let alice = Keyring::Alice.to_account_id();
434 let bob = Keyring::Bob.to_account_id();
435 let charlie = Keyring::Charlie.to_account_id();
436
437 let extrinsics = vec![
438 (Some(alice.clone()), 10),
439 (None, 100),
440 (Some(bob.clone()), 1),
441 (Some(bob), 2),
442 (Some(charlie.clone()), 30),
443 (Some(alice.clone()), 11),
444 (Some(charlie), 31),
445 (None, 101),
446 (None, 102),
447 (Some(alice), 12),
448 ];
449
450 let dummy_seed = Randomness::from(BlakeTwo256::hash_of(&[1u8; 64]).to_fixed_bytes());
451 let shuffled_extrinsics = shuffle_extrinsics(extrinsics, dummy_seed);
452
453 assert_eq!(
454 shuffled_extrinsics,
455 vec![100, 30, 10, 1, 11, 101, 31, 12, 102, 2]
456 );
457 }
458}