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.backend.state_at(parent_domain_hash)?;
295 let trie_backend = state.as_trie_backend();
296 let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend);
297 let runtime_code = state_runtime_code
298 .runtime_code()
299 .map_err(sp_blockchain::Error::RuntimeCode)?
300 .fetch_runtime_code()
301 .ok_or(sp_blockchain::Error::RuntimeCode("missing runtime code"))?
302 .into_owned();
303
304 Ok(StatelessRuntime::<CBlock, Block, _>::new(
305 self.executor.clone(),
306 runtime_code.into(),
307 ))
308 }
309
310 fn batch_check_bundle_validity(
311 &self,
312 bundle: OpaqueBundle<NumberFor<CBlock>, CBlock::Hash, Block::Header, Balance>,
313 tx_range: &U256,
314 (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
315 at_consensus_hash: CBlock::Hash,
316 ) -> sp_blockchain::Result<BundleValidity<Block::Extrinsic>> {
317 let bundle_vrf_hash = U256::from_be_bytes(*bundle.proof_of_election().vrf_hash());
318 let bundle_length = bundle.body_length();
319 let bundle_estimated_weight = bundle.estimated_weight();
320 let mut maybe_invalid_bundle_type = None;
321
322 let stateless_runtime_api = self.stateless_runtime_api(parent_domain_hash)?;
328 let consensus_runtime_api = self.consensus_client.runtime_api();
329
330 let mut extrinsics =
338 stateless_runtime_api.decode_extrinsics_prefix(bundle.into_extrinsics())?;
339 if extrinsics.len() != bundle_length {
340 maybe_invalid_bundle_type
342 .replace(InvalidBundleType::UndecodableTx(extrinsics.len() as u32));
343 }
344
345 let signers = match stateless_runtime_api.extract_signer_if_all_within_tx_range(
346 &extrinsics,
347 &bundle_vrf_hash,
348 tx_range,
349 )? {
350 Err(index) => {
351 maybe_invalid_bundle_type.replace(InvalidBundleType::OutOfRangeTx(index));
352 extrinsics.truncate(index as usize);
353
354 Vec::default()
356 }
357 Ok(signers) => signers,
358 };
359
360 if let Some(index) = stateless_runtime_api.find_first_inherent_extrinsic(&extrinsics)? {
364 maybe_invalid_bundle_type.replace(InvalidBundleType::InherentExtrinsic(index));
365 extrinsics.truncate(index as usize);
366 }
367
368 let batch_xdm_mmr_proof =
369 stateless_runtime_api.batch_extract_native_xdm_mmr_proof(&extrinsics)?;
370 for (index, xdm_mmr_proof) in batch_xdm_mmr_proof {
371 let ConsensusChainMmrLeafProof {
372 opaque_mmr_leaf,
373 proof,
374 ..
375 } = xdm_mmr_proof;
376
377 if consensus_runtime_api
378 .verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)?
379 .is_err()
380 {
381 maybe_invalid_bundle_type.replace(InvalidBundleType::InvalidXDM(index));
382 extrinsics.truncate(index as usize);
383 break;
384 }
385 }
386
387 if let Err(CheckExtrinsicsValidityError {
390 extrinsic_index, ..
391 }) = self
392 .client
393 .runtime_api()
394 .check_extrinsics_and_do_pre_dispatch(
395 parent_domain_hash,
396 extrinsics.clone(),
397 parent_domain_number,
398 parent_domain_hash,
399 )?
400 {
401 maybe_invalid_bundle_type.replace(InvalidBundleType::IllegalTx(extrinsic_index));
402 }
403
404 if let Some(invalid_bundle_type) = maybe_invalid_bundle_type {
407 return Ok(BundleValidity::Invalid(invalid_bundle_type));
408 }
409
410 if bundle_estimated_weight != stateless_runtime_api.extrinsics_weight(&extrinsics)? {
411 return Ok(BundleValidity::Invalid(
412 InvalidBundleType::InvalidBundleWeight,
413 ));
414 }
415
416 let signer_and_extrinsics = signers.into_iter().zip(extrinsics).collect();
417 Ok(BundleValidity::ValidWithSigner(signer_and_extrinsics))
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use sp_domains::extrinsics::shuffle_extrinsics;
424 use sp_keyring::sr25519::Keyring;
425 use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
426 use subspace_core_primitives::Randomness;
427
428 #[test]
429 fn shuffle_extrinsics_should_work() {
430 let alice = Keyring::Alice.to_account_id();
431 let bob = Keyring::Bob.to_account_id();
432 let charlie = Keyring::Charlie.to_account_id();
433
434 let extrinsics = vec![
435 (Some(alice.clone()), 10),
436 (None, 100),
437 (Some(bob.clone()), 1),
438 (Some(bob), 2),
439 (Some(charlie.clone()), 30),
440 (Some(alice.clone()), 11),
441 (Some(charlie), 31),
442 (None, 101),
443 (None, 102),
444 (Some(alice), 12),
445 ];
446
447 let dummy_seed = Randomness::from(BlakeTwo256::hash_of(&[1u8; 64]).to_fixed_bytes());
448 let shuffled_extrinsics = shuffle_extrinsics(extrinsics, dummy_seed);
449
450 assert_eq!(
451 shuffled_extrinsics,
452 vec![100, 30, 10, 1, 11, 101, 31, 12, 102, 2]
453 );
454 }
455}