cumulus_client_consensus_aura/collators/slot_based/
block_import.rs1use crate::LOG_TARGET;
19use codec::{Decode, Encode};
20use cumulus_client_consensus_common::old_finalized_hash;
21use cumulus_client_proof_size_recording::prepare_proof_size_recording_aux_data;
22use cumulus_primitives_core::{BlockBundleInfo, CoreInfo, CumulusDigestItem, RelayBlockIdentifier};
23use futures::{stream::FusedStream, StreamExt};
24use sc_client_api::{
25 backend::AuxStore,
26 client::{AuxDataOperations, FinalityNotification, PreCommitActions},
27 HeaderBackend,
28};
29use sc_consensus::{BlockImport, StateAction};
30use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
31use sp_api::{
32 ApiExt, CallApiAt, CallContext, Core, ProofRecorder, ProofRecorderIgnoredNodes,
33 ProvideRuntimeApi, StorageProof,
34};
35use sp_blockchain::{Error as ClientError, Result as ClientResult};
36use sp_consensus::BlockOrigin;
37use sp_runtime::traits::{Block as BlockT, HashingFor, Header as _};
38use sp_trie::proof_size_extension::{ProofSizeExt, RecordingProofSizeProvider};
39use std::sync::Arc;
40
41fn ignored_nodes_key<H: Encode>(block_hash: H) -> Vec<u8> {
43 (b"cumulus_slot_based_nodes_to_ignore", block_hash).encode()
44}
45
46fn prepare_ignored_nodes_transaction<Block: BlockT>(
50 block_hash: Block::Hash,
51 ignored_nodes: ProofRecorderIgnoredNodes<Block>,
52) -> impl Iterator<Item = (Vec<u8>, Vec<u8>)> {
53 let key = ignored_nodes_key(block_hash);
54 let encoded_nodes = <ProofRecorderIgnoredNodes<Block> as Encode>::encode(&ignored_nodes);
55
56 [(key, encoded_nodes)].into_iter()
57}
58
59fn load_ignored_nodes<Block: BlockT, B: AuxStore>(
61 backend: &B,
62 block_hash: Block::Hash,
63) -> ClientResult<Option<ProofRecorderIgnoredNodes<Block>>> {
64 match backend.get_aux(&ignored_nodes_key(block_hash))? {
65 None => Ok(None),
66 Some(t) => ProofRecorderIgnoredNodes::<Block>::decode(&mut &t[..])
67 .map(Some)
68 .map_err(|e| ClientError::Backend(format!("Failed to decode ignored nodes: {}", e))),
69 }
70}
71
72pub struct SlotBasedBlockImportHandle<Block> {
77 receiver: TracingUnboundedReceiver<(Block, Arc<StorageProof>)>,
78}
79
80impl<Block> SlotBasedBlockImportHandle<Block> {
81 pub async fn next(&mut self) -> (Block, Arc<StorageProof>) {
85 loop {
86 if self.receiver.is_terminated() {
87 futures::pending!()
88 } else if let Some(res) = self.receiver.next().await {
89 return res;
90 }
91 }
92 }
93}
94
95fn register_ignored_nodes_cleanup<C, Block>(client: Arc<C>)
98where
99 C: PreCommitActions<Block> + HeaderBackend<Block> + 'static,
100 Block: BlockT,
101{
102 let client_for_closure = client.clone();
103 let on_finality = move |notification: &FinalityNotification<Block>| -> AuxDataOperations {
104 let old_finalized = old_finalized_hash::<_, Block>(
105 &*client_for_closure,
106 ¬ification.tree_route,
107 *notification.header.parent_hash(),
108 );
109
110 notification
111 .stale_blocks
112 .iter()
113 .map(|b| (ignored_nodes_key(b.hash), None))
115 .chain(
120 notification
121 .tree_route
122 .iter()
123 .copied()
124 .map(|hash| (ignored_nodes_key(hash), None)),
125 )
126 .chain(std::iter::once((ignored_nodes_key(old_finalized), None)))
127 .collect()
128 };
129
130 client.register_finality_action(Box::new(on_finality));
131}
132
133pub struct SlotBasedBlockImport<Block: BlockT, BI, Client> {
135 inner: BI,
136 client: Arc<Client>,
137 sender: TracingUnboundedSender<(Block, Arc<StorageProof>)>,
138}
139
140impl<Block: BlockT, BI, Client> SlotBasedBlockImport<Block, BI, Client> {
141 pub fn new(inner: BI, client: Arc<Client>) -> (Self, SlotBasedBlockImportHandle<Block>)
147 where
148 Client: PreCommitActions<Block> + HeaderBackend<Block> + 'static,
149 {
150 let (sender, receiver) = tracing_unbounded("SlotBasedBlockImportChannel", 1000);
151
152 register_ignored_nodes_cleanup(client.clone());
153
154 (Self { sender, client, inner }, SlotBasedBlockImportHandle { receiver })
155 }
156
157 fn get_ignored_nodes(
162 &self,
163 parent: Block::Hash,
164 core_info: &CoreInfo,
165 bundle_info: &BlockBundleInfo,
166 relay_block_identifier: &RelayBlockIdentifier,
167 ) -> Option<ProofRecorderIgnoredNodes<Block>>
168 where
169 Client: AuxStore + HeaderBackend<Block> + Send + Sync,
170 {
171 let parent_header = self.client.header(parent).ok().flatten()?;
172 let parent_core_info = CumulusDigestItem::find_core_info(parent_header.digest())?;
173 let parent_bundle_info = CumulusDigestItem::find_block_bundle_info(parent_header.digest())?;
174 let parent_relay_block_identifier =
175 CumulusDigestItem::find_relay_block_identifier(parent_header.digest())?;
176
177 if parent_relay_block_identifier != *relay_block_identifier {
178 tracing::trace!(target: LOG_TARGET, ?parent_relay_block_identifier, ?relay_block_identifier, "Relay block identifier doesn't match");
179 return None;
180 }
181
182 if parent_core_info != *core_info {
183 tracing::trace!(target: LOG_TARGET, ?parent_core_info, ?core_info, "Core info doesn't match");
184 return None;
185 }
186
187 if parent_bundle_info.index.saturating_add(1) != bundle_info.index {
188 tracing::trace!(target: LOG_TARGET, ?parent_bundle_info, ?bundle_info, "Block is not a child, based on the index");
189 return None;
190 }
191
192 match load_ignored_nodes::<Block, _>(&*self.client, parent) {
193 Ok(nodes) => nodes,
194 Err(error) => {
195 tracing::trace!(target: LOG_TARGET, ?parent, ?error, "Failed to load `IgnoredNodes` from aux store");
196 None
197 },
198 }
199 }
200
201 async fn execute_block_and_collect_storage_proof(
211 &self,
212 params: &mut sc_consensus::BlockImportParams<Block>,
213 ) -> Result<Option<(Block, Arc<StorageProof>)>, sp_consensus::Error>
214 where
215 Client: ProvideRuntimeApi<Block>
216 + CallApiAt<Block>
217 + AuxStore
218 + HeaderBackend<Block>
219 + Send
220 + Sync,
221 Client::StateBackend: Send,
222 Client::Api: Core<Block>,
223 {
224 let core_info = CumulusDigestItem::find_core_info(params.header.digest());
225 let bundle_info = CumulusDigestItem::find_block_bundle_info(params.header.digest());
226 let relay_block_identifier =
227 CumulusDigestItem::find_relay_block_identifier(params.header.digest());
228
229 let (Some(core_info), Some(bundle_info), Some(relay_block_identifier)) =
230 (core_info, bundle_info, relay_block_identifier)
231 else {
232 tracing::debug!(
233 target: LOG_TARGET,
234 number = ?params.header.number(),
235 "no bundle digests, skipping execute_block_and_collect_storage_proof",
236 );
237 return Ok(None);
238 };
239
240 let parent_hash = *params.header.parent_hash();
241
242 let mut nodes_to_ignore = self
243 .get_ignored_nodes(parent_hash, &core_info, &bundle_info, &relay_block_identifier)
244 .unwrap_or_default();
245
246 let recorder = ProofRecorder::<Block>::with_ignored_nodes(nodes_to_ignore.clone());
247 let proof_size_recorder = RecordingProofSizeProvider::new(recorder.clone());
248
249 let mut runtime_api = self.client.runtime_api();
250
251 runtime_api.set_call_context(CallContext::Onchain { import: true });
254 runtime_api.record_proof_with_recorder(recorder.clone());
255 runtime_api.register_extension(ProofSizeExt::new(proof_size_recorder.clone()));
256
257 let collect_for_resubmission = matches!(
262 params.origin,
263 BlockOrigin::NetworkBroadcast | BlockOrigin::ConsensusBroadcast
264 );
265
266 let body = params.body.clone().unwrap_or_default();
267 let resubmission_body = collect_for_resubmission.then(|| body.clone());
268 let block = Block::new(params.header.clone(), body);
269
270 tracing::debug!(
271 target: LOG_TARGET,
272 ?parent_hash,
273 number = ?params.header.number(),
274 ?core_info,
275 ?bundle_info,
276 "execute_block_and_collect_storage_proof: calling runtime_api.execute_block",
277 );
278
279 runtime_api
280 .execute_block(parent_hash, block.into())
281 .map_err(|e| Box::new(e) as Box<_>)?;
282
283 let storage_proof =
284 runtime_api.extract_proof().expect("Proof recording was enabled above; qed");
285
286 let state = self.client.state_at(parent_hash).map_err(|e| Box::new(e) as Box<_>)?;
287 let gen_storage_changes = runtime_api
288 .into_storage_changes(&state, parent_hash)
289 .map_err(sp_consensus::Error::ChainLookup)?;
290
291 if params.header.state_root() != &gen_storage_changes.transaction_storage_root {
292 return Err(sp_consensus::Error::Other(Box::new(
293 sp_blockchain::Error::InvalidStateRoot,
294 )));
295 }
296
297 nodes_to_ignore.extend(ProofRecorderIgnoredNodes::<Block>::from_storage_proof::<
301 HashingFor<Block>,
302 >(&storage_proof));
303 nodes_to_ignore.extend(ProofRecorderIgnoredNodes::<Block>::from_memory_db(
304 gen_storage_changes.transaction.clone(),
305 ));
306
307 let block_hash = params.post_hash();
308 prepare_ignored_nodes_transaction::<Block>(block_hash, nodes_to_ignore).for_each(
309 |(k, v)| {
310 params.auxiliary.push((k, Some(v)));
311 },
312 );
313
314 let recorded_sizes = proof_size_recorder
316 .recorded_estimations()
317 .into_iter()
318 .map(|size| size as u32)
319 .collect::<Vec<u32>>();
320
321 if !recorded_sizes.is_empty() {
322 prepare_proof_size_recording_aux_data(block_hash, recorded_sizes).for_each(|(k, v)| {
323 params.auxiliary.push((k, Some(v)));
324 });
325 }
326
327 params.state_action =
328 StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(gen_storage_changes));
329
330 let Some(resubmission_body) = resubmission_body else { return Ok(None) };
331
332 let block = Block::new(params.post_header(), resubmission_body);
333 Ok(Some((block, Arc::new(storage_proof))))
334 }
335}
336
337impl<Block: BlockT, BI: Clone, Client> Clone for SlotBasedBlockImport<Block, BI, Client> {
338 fn clone(&self) -> Self {
339 Self { inner: self.inner.clone(), client: self.client.clone(), sender: self.sender.clone() }
340 }
341}
342
343#[async_trait::async_trait]
344impl<Block, BI, Client> BlockImport<Block> for SlotBasedBlockImport<Block, BI, Client>
345where
346 Block: BlockT,
347 BI: BlockImport<Block> + Send + Sync,
348 BI::Error: Into<sp_consensus::Error>,
349 Client:
350 ProvideRuntimeApi<Block> + CallApiAt<Block> + AuxStore + HeaderBackend<Block> + Send + Sync,
351 Client::StateBackend: Send,
352 Client::Api: Core<Block>,
353{
354 type Error = sp_consensus::Error;
355
356 async fn check_block(
357 &self,
358 block: sc_consensus::BlockCheckParams<Block>,
359 ) -> Result<sc_consensus::ImportResult, Self::Error> {
360 self.inner.check_block(block).await.map_err(Into::into)
361 }
362
363 async fn import_block(
364 &self,
365 mut params: sc_consensus::BlockImportParams<Block>,
366 ) -> Result<sc_consensus::ImportResult, Self::Error> {
367 let origin = params.origin;
368
369 let imported = if !(origin == BlockOrigin::Own ||
370 params.with_state() ||
371 params.state_action.skip_execution_checks())
372 {
373 self.execute_block_and_collect_storage_proof(&mut params).await?
374 } else {
375 None
376 };
377
378 let result = self.inner.import_block(params).await.map_err(Into::into)?;
379
380 if let Some((block, proof)) = imported {
381 let _ = self.sender.unbounded_send((block, proof));
382 }
383
384 Ok(result)
385 }
386}