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