1#![warn(rust_2018_idioms)]
12#![feature(let_chains)]
13
14pub mod inherents;
15pub mod stateless_runtime;
16
17use crate::inherents::is_runtime_upgraded;
18use crate::stateless_runtime::StatelessRuntime;
19use domain_runtime_primitives::opaque::AccountId;
20use domain_runtime_primitives::{opaque, CheckExtrinsicsValidityError};
21use parity_scale_codec::Encode;
22use sc_client_api::{backend, BlockBackend};
23use sc_executor::RuntimeVersionOf;
24use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
25use sp_blockchain::HeaderBackend;
26use sp_core::traits::{CodeExecutor, FetchRuntimeCode};
27use sp_core::H256;
28use sp_domains::core_api::DomainCoreApi;
29use sp_domains::extrinsics::deduplicate_and_shuffle_extrinsics;
30use sp_domains::{
31 DomainId, DomainsApi, ExecutionReceipt, ExtrinsicDigest, HeaderHashingFor, InboxedBundle,
32 InvalidBundleType, OpaqueBundle, OpaqueBundles, ReceiptValidity,
33};
34use sp_messenger::MessengerApi;
35use sp_mmr_primitives::MmrApi;
36use sp_runtime::traits::{Block as BlockT, Hash as HashT, NumberFor};
37use sp_state_machine::backend::AsTrieBackend;
38use sp_state_machine::LayoutV1;
39use sp_subspace_mmr::ConsensusChainMmrLeafProof;
40use sp_weights::Weight;
41use std::collections::VecDeque;
42use std::marker::PhantomData;
43use std::sync::Arc;
44use subspace_core_primitives::{Randomness, U256};
45use subspace_runtime_primitives::{Balance, ExtrinsicFor};
46
47type DomainBlockElements<CBlock> = (Vec<ExtrinsicFor<CBlock>>, Randomness);
48
49enum BundleValidity<Extrinsic> {
51 Valid(Vec<Extrinsic>),
53 ValidWithSigner(Vec<(Option<opaque::AccountId>, Extrinsic)>),
55 Invalid(InvalidBundleType),
57}
58
59fn prepare_domain_block_elements<Block, CBlock, CClient>(
61 consensus_client: &CClient,
62 block_hash: CBlock::Hash,
63) -> sp_blockchain::Result<DomainBlockElements<CBlock>>
64where
65 Block: BlockT,
66 CBlock: BlockT,
67 CClient: HeaderBackend<CBlock> + BlockBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
68 CClient::Api: DomainsApi<CBlock, Block::Header>,
69{
70 let extrinsics = consensus_client.block_body(block_hash)?.ok_or_else(|| {
71 sp_blockchain::Error::Backend(format!("BlockBody of {block_hash:?} unavailable"))
72 })?;
73
74 let shuffling_seed = consensus_client
75 .runtime_api()
76 .extrinsics_shuffling_seed(block_hash)?;
77
78 Ok((extrinsics, shuffling_seed))
79}
80
81pub struct PreprocessResult<Block: BlockT> {
82 pub extrinsics: VecDeque<Block::Extrinsic>,
83 pub bundles: Vec<InboxedBundle<Block::Hash>>,
84}
85
86pub struct DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
87{
88 domain_id: DomainId,
89 client: Arc<Client>,
90 consensus_client: Arc<CClient>,
91 executor: Arc<Exec>,
92 backend: Arc<Backend>,
93 receipt_validator: ReceiptValidator,
94 _phantom_data: PhantomData<(Block, CBlock)>,
95}
96
97impl<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator: Clone> Clone
98 for DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
99{
100 fn clone(&self) -> Self {
101 Self {
102 domain_id: self.domain_id,
103 client: self.client.clone(),
104 consensus_client: self.consensus_client.clone(),
105 executor: self.executor.clone(),
106 backend: self.backend.clone(),
107 receipt_validator: self.receipt_validator.clone(),
108 _phantom_data: self._phantom_data,
109 }
110 }
111}
112
113pub trait ValidateReceipt<Block, CBlock>
114where
115 Block: BlockT,
116 CBlock: BlockT,
117{
118 fn validate_receipt(
119 &self,
120 receipt: &ExecutionReceipt<
121 NumberFor<CBlock>,
122 CBlock::Hash,
123 NumberFor<Block>,
124 Block::Hash,
125 Balance,
126 >,
127 ) -> sp_blockchain::Result<ReceiptValidity>;
128}
129
130impl<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
131 DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
132where
133 Block: BlockT,
134 Block::Hash: Into<H256>,
135 CBlock: BlockT,
136 CBlock::Hash: From<Block::Hash>,
137 NumberFor<CBlock>: From<NumberFor<Block>>,
138 Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + 'static,
139 Client::Api: DomainCoreApi<Block> + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>,
140 CClient: HeaderBackend<CBlock>
141 + BlockBackend<CBlock>
142 + ProvideRuntimeApi<CBlock>
143 + Send
144 + Sync
145 + 'static,
146 CClient::Api: DomainsApi<CBlock, Block::Header>
147 + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>
148 + MmrApi<CBlock, H256, NumberFor<CBlock>>,
149 Backend: backend::Backend<Block>,
150 Exec: CodeExecutor + RuntimeVersionOf,
151 ReceiptValidator: ValidateReceipt<Block, CBlock>,
152{
153 pub fn new(
154 domain_id: DomainId,
155 client: Arc<Client>,
156 consensus_client: Arc<CClient>,
157 executor: Arc<Exec>,
158 backend: Arc<Backend>,
159 receipt_validator: ReceiptValidator,
160 ) -> Self {
161 Self {
162 domain_id,
163 client,
164 consensus_client,
165 executor,
166 backend,
167 receipt_validator,
168 _phantom_data: Default::default(),
169 }
170 }
171
172 pub fn preprocess_consensus_block(
173 &self,
174 consensus_block_hash: CBlock::Hash,
175 parent_domain_block: (Block::Hash, NumberFor<Block>),
176 ) -> sp_blockchain::Result<Option<PreprocessResult<Block>>> {
177 let (primary_extrinsics, shuffling_seed) = prepare_domain_block_elements::<Block, CBlock, _>(
178 &*self.consensus_client,
179 consensus_block_hash,
180 )?;
181
182 let bundles = self
183 .consensus_client
184 .runtime_api()
185 .extract_successful_bundles(consensus_block_hash, self.domain_id, primary_extrinsics)?;
186
187 if bundles.is_empty()
188 && !is_runtime_upgraded::<_, _, Block>(
189 &self.consensus_client,
190 consensus_block_hash,
191 self.domain_id,
192 )?
193 {
194 return Ok(None);
195 }
196
197 let tx_range = self
198 .consensus_client
199 .runtime_api()
200 .domain_tx_range(consensus_block_hash, self.domain_id)?;
201
202 let (inboxed_bundles, extrinsics) = self.compile_bundles_to_extrinsics(
203 bundles,
204 tx_range,
205 parent_domain_block,
206 consensus_block_hash,
207 )?;
208
209 let extrinsics =
210 deduplicate_and_shuffle_extrinsics::<Block::Extrinsic>(extrinsics, shuffling_seed);
211
212 Ok(Some(PreprocessResult {
213 extrinsics,
214 bundles: inboxed_bundles,
215 }))
216 }
217
218 fn is_batch_api_available(
220 &self,
221 parent_domain_hash: Block::Hash,
222 ) -> sp_blockchain::Result<bool> {
223 let domain_runtime_api = self.client.runtime_api();
224
225 let domain_core_api_version = domain_runtime_api
226 .api_version::<dyn DomainCoreApi<Block>>(parent_domain_hash)?
227 .ok_or(sp_blockchain::Error::Application(Box::from(format!(
228 "DomainCoreApi not found at {parent_domain_hash:?}"
229 ))))?;
230
231 let messenger_api_version = domain_runtime_api
232 .api_version::<dyn MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>>(
233 parent_domain_hash,
234 )?
235 .ok_or(sp_blockchain::Error::Application(Box::from(format!(
236 "MessengerApi not found at {parent_domain_hash:?}"
237 ))))?;
238
239 Ok(domain_core_api_version >= 2 && messenger_api_version >= 3)
240 }
241
242 #[allow(clippy::type_complexity)]
245 fn compile_bundles_to_extrinsics(
246 &self,
247 bundles: OpaqueBundles<CBlock, Block::Header, Balance>,
248 tx_range: U256,
249 (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
250 at_consensus_hash: CBlock::Hash,
251 ) -> sp_blockchain::Result<(
252 Vec<InboxedBundle<Block::Hash>>,
253 Vec<(Option<AccountId>, Block::Extrinsic)>,
254 )> {
255 let mut inboxed_bundles = Vec::with_capacity(bundles.len());
256 let mut valid_extrinsics = Vec::new();
257
258 let runtime_api = self.client.runtime_api();
259 for bundle in bundles {
260 if bundle.receipt().domain_block_number != parent_domain_number {
271 return Err(sp_blockchain::Error::RuntimeApiError(
272 ApiError::Application(
273 format!(
274 "Unexpected bundle in consensus block: {:?}, something must be wrong",
275 at_consensus_hash
276 )
277 .into(),
278 ),
279 ));
280 }
281
282 let extrinsic_root = bundle.extrinsics_root();
283 let bundle_validity = if self.is_batch_api_available(parent_domain_hash)? {
284 self.batch_check_bundle_validity(
285 bundle,
286 &tx_range,
287 (parent_domain_hash, parent_domain_number),
288 at_consensus_hash,
289 )?
290 } else {
291 self.check_bundle_validity(
292 &bundle,
293 &tx_range,
294 (parent_domain_hash, parent_domain_number),
295 at_consensus_hash,
296 )?
297 };
298 match bundle_validity {
299 BundleValidity::Valid(extrinsics) => {
300 let extrinsics: Vec<_> = match runtime_api
301 .extract_signer(parent_domain_hash, extrinsics)
302 {
303 Ok(res) => res,
304 Err(e) => {
305 tracing::error!(error = ?e, "Error at calling runtime api: extract_signer");
306 return Err(e.into());
307 }
308 };
309 let bundle_digest: Vec<_> = extrinsics
310 .iter()
311 .map(|(signer, tx)| {
312 (
313 signer.clone(),
314 ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<Block::Header>>>(
315 tx.encode(),
316 ),
317 )
318 })
319 .collect();
320 inboxed_bundles.push(InboxedBundle::valid(
321 HeaderHashingFor::<Block::Header>::hash_of(&bundle_digest),
322 extrinsic_root,
323 ));
324 valid_extrinsics.extend(extrinsics);
325 }
326 BundleValidity::ValidWithSigner(signer_and_extrinsics) => {
327 let bundle_digest: Vec<_> = signer_and_extrinsics
328 .iter()
329 .map(|(signer, tx)| {
330 (
331 signer.clone(),
332 ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<Block::Header>>>(
333 tx.encode(),
334 ),
335 )
336 })
337 .collect();
338 inboxed_bundles.push(InboxedBundle::valid(
339 HeaderHashingFor::<Block::Header>::hash_of(&bundle_digest),
340 extrinsic_root,
341 ));
342 valid_extrinsics.extend(signer_and_extrinsics);
343 }
344 BundleValidity::Invalid(invalid_bundle_type) => {
345 inboxed_bundles
346 .push(InboxedBundle::invalid(invalid_bundle_type, extrinsic_root));
347 }
348 }
349 }
350
351 Ok((inboxed_bundles, valid_extrinsics))
352 }
353
354 fn stateless_runtime_api(
355 &self,
356 parent_domain_hash: Block::Hash,
357 ) -> sp_blockchain::Result<StatelessRuntime<CBlock, Block, Exec>> {
358 let state = self.backend.state_at(parent_domain_hash)?;
359 let trie_backend = state.as_trie_backend();
360 let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend);
361 let runtime_code = state_runtime_code
362 .runtime_code()
363 .map_err(sp_blockchain::Error::RuntimeCode)?
364 .fetch_runtime_code()
365 .ok_or(sp_blockchain::Error::RuntimeCode("missing runtime code"))?
366 .into_owned();
367
368 Ok(StatelessRuntime::<CBlock, Block, _>::new(
369 self.executor.clone(),
370 runtime_code.into(),
371 ))
372 }
373
374 fn check_bundle_validity(
375 &self,
376 bundle: &OpaqueBundle<NumberFor<CBlock>, CBlock::Hash, Block::Header, Balance>,
377 tx_range: &U256,
378 (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
379 at_consensus_hash: CBlock::Hash,
380 ) -> sp_blockchain::Result<BundleValidity<Block::Extrinsic>> {
381 let bundle_vrf_hash =
382 U256::from_be_bytes(*bundle.sealed_header.header.proof_of_election.vrf_hash());
383
384 let mut extrinsics = Vec::with_capacity(bundle.extrinsics.len());
385 let mut estimated_bundle_weight = Weight::default();
386 let mut maybe_invalid_bundle_type = None;
387
388 let stateless_runtime_api = self.stateless_runtime_api(parent_domain_hash)?;
389 let consensus_runtime_api = self.consensus_client.runtime_api();
390
391 for (index, opaque_extrinsic) in bundle.extrinsics.iter().enumerate() {
395 let decode_result = stateless_runtime_api.decode_extrinsic(opaque_extrinsic.clone())?;
396 let extrinsic = match decode_result {
397 Ok(extrinsic) => extrinsic,
398 Err(err) => {
399 tracing::error!(
400 ?opaque_extrinsic,
401 ?err,
402 "Undecodable extrinsic in bundle({})",
403 bundle.hash()
404 );
405 maybe_invalid_bundle_type
406 .replace(InvalidBundleType::UndecodableTx(index as u32));
407 break;
408 }
409 };
410
411 let is_within_tx_range =
412 stateless_runtime_api.is_within_tx_range(&extrinsic, &bundle_vrf_hash, tx_range)?;
413
414 if !is_within_tx_range {
415 maybe_invalid_bundle_type.replace(InvalidBundleType::OutOfRangeTx(index as u32));
416 break;
417 }
418
419 if stateless_runtime_api.is_inherent_extrinsic(&extrinsic)? {
423 maybe_invalid_bundle_type
424 .replace(InvalidBundleType::InherentExtrinsic(index as u32));
425 break;
426 }
427
428 if let Some(xdm_mmr_proof) =
429 stateless_runtime_api.extract_native_xdm_mmr_proof(&extrinsic)?
430 {
431 let ConsensusChainMmrLeafProof {
432 opaque_mmr_leaf,
433 proof,
434 ..
435 } = xdm_mmr_proof;
436
437 if consensus_runtime_api
438 .verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)?
439 .is_err()
440 {
441 maybe_invalid_bundle_type.replace(InvalidBundleType::InvalidXDM(index as u32));
442 break;
443 }
444 }
445
446 let tx_weight = stateless_runtime_api.extrinsic_weight(&extrinsic)?;
447 estimated_bundle_weight = estimated_bundle_weight.saturating_add(tx_weight);
448
449 extrinsics.push(extrinsic);
450 }
451
452 if let Err(CheckExtrinsicsValidityError {
458 extrinsic_index, ..
459 }) = self
460 .client
461 .runtime_api()
462 .check_extrinsics_and_do_pre_dispatch(
463 parent_domain_hash,
464 extrinsics.clone(),
465 parent_domain_number,
466 parent_domain_hash,
467 )?
468 {
469 return Ok(BundleValidity::Invalid(InvalidBundleType::IllegalTx(
474 extrinsic_index,
475 )));
476 }
477
478 if let Some(invalid_bundle_type) = maybe_invalid_bundle_type {
479 return Ok(BundleValidity::Invalid(invalid_bundle_type));
480 }
481
482 if estimated_bundle_weight != bundle.estimated_weight() {
483 return Ok(BundleValidity::Invalid(
484 InvalidBundleType::InvalidBundleWeight,
485 ));
486 }
487
488 Ok(BundleValidity::Valid(extrinsics))
489 }
490
491 fn batch_check_bundle_validity(
492 &self,
493 bundle: OpaqueBundle<NumberFor<CBlock>, CBlock::Hash, Block::Header, Balance>,
494 tx_range: &U256,
495 (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
496 at_consensus_hash: CBlock::Hash,
497 ) -> sp_blockchain::Result<BundleValidity<Block::Extrinsic>> {
498 let bundle_vrf_hash =
499 U256::from_be_bytes(*bundle.sealed_header.header.proof_of_election.vrf_hash());
500 let bundle_length = bundle.extrinsics.len();
501 let bundle_estimated_weight = bundle.estimated_weight();
502 let mut maybe_invalid_bundle_type = None;
503
504 let stateless_runtime_api = self.stateless_runtime_api(parent_domain_hash)?;
505 let consensus_runtime_api = self.consensus_client.runtime_api();
506
507 let mut extrinsics = stateless_runtime_api.decode_extrinsics_prefix(bundle.extrinsics)?;
515 if extrinsics.len() != bundle_length {
516 maybe_invalid_bundle_type
518 .replace(InvalidBundleType::UndecodableTx(extrinsics.len() as u32));
519 }
520
521 let signers = match stateless_runtime_api.extract_signer_if_all_within_tx_range(
522 &extrinsics,
523 &bundle_vrf_hash,
524 tx_range,
525 )? {
526 Err(index) => {
527 maybe_invalid_bundle_type.replace(InvalidBundleType::OutOfRangeTx(index));
528 extrinsics.truncate(index as usize);
529
530 Vec::default()
532 }
533 Ok(signers) => signers,
534 };
535
536 if let Some(index) = stateless_runtime_api.find_first_inherent_extrinsic(&extrinsics)? {
540 maybe_invalid_bundle_type.replace(InvalidBundleType::InherentExtrinsic(index));
541 extrinsics.truncate(index as usize);
542 }
543
544 let batch_xdm_mmr_proof =
545 stateless_runtime_api.batch_extract_native_xdm_mmr_proof(&extrinsics)?;
546 for (index, xdm_mmr_proof) in batch_xdm_mmr_proof {
547 let ConsensusChainMmrLeafProof {
548 opaque_mmr_leaf,
549 proof,
550 ..
551 } = xdm_mmr_proof;
552
553 if consensus_runtime_api
554 .verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)?
555 .is_err()
556 {
557 maybe_invalid_bundle_type.replace(InvalidBundleType::InvalidXDM(index));
558 extrinsics.truncate(index as usize);
559 break;
560 }
561 }
562
563 if let Err(CheckExtrinsicsValidityError {
566 extrinsic_index, ..
567 }) = self
568 .client
569 .runtime_api()
570 .check_extrinsics_and_do_pre_dispatch(
571 parent_domain_hash,
572 extrinsics.clone(),
573 parent_domain_number,
574 parent_domain_hash,
575 )?
576 {
577 maybe_invalid_bundle_type.replace(InvalidBundleType::IllegalTx(extrinsic_index));
578 }
579
580 if let Some(invalid_bundle_type) = maybe_invalid_bundle_type {
583 return Ok(BundleValidity::Invalid(invalid_bundle_type));
584 }
585
586 if bundle_estimated_weight != stateless_runtime_api.extrinsics_weight(&extrinsics)? {
587 return Ok(BundleValidity::Invalid(
588 InvalidBundleType::InvalidBundleWeight,
589 ));
590 }
591
592 let signer_and_extrinsics = signers.into_iter().zip(extrinsics).collect();
593 Ok(BundleValidity::ValidWithSigner(signer_and_extrinsics))
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use sp_domains::extrinsics::shuffle_extrinsics;
600 use sp_keyring::sr25519::Keyring;
601 use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
602 use subspace_core_primitives::Randomness;
603
604 #[test]
605 fn shuffle_extrinsics_should_work() {
606 let alice = Keyring::Alice.to_account_id();
607 let bob = Keyring::Bob.to_account_id();
608 let charlie = Keyring::Charlie.to_account_id();
609
610 let extrinsics = vec![
611 (Some(alice.clone()), 10),
612 (None, 100),
613 (Some(bob.clone()), 1),
614 (Some(bob), 2),
615 (Some(charlie.clone()), 30),
616 (Some(alice.clone()), 11),
617 (Some(charlie), 31),
618 (None, 101),
619 (None, 102),
620 (Some(alice), 12),
621 ];
622
623 let dummy_seed = Randomness::from(BlakeTwo256::hash_of(&[1u8; 64]).to_fixed_bytes());
624 let shuffled_extrinsics = shuffle_extrinsics(extrinsics, dummy_seed);
625
626 assert_eq!(
627 shuffled_extrinsics,
628 vec![100, 30, 10, 1, 11, 101, 31, 12, 102, 2]
629 );
630 }
631}