1use std::{
21 collections::HashMap,
22 sync::{
23 atomic::{AtomicU64, Ordering},
24 Arc,
25 },
26 time::Instant,
27};
28
29use codec::Encode;
30use parking_lot::Mutex;
31use tracing::{
32 dispatcher,
33 span::{Attributes, Id, Record},
34 Dispatch, Level, Subscriber,
35};
36
37use crate::{SpanDatum, TraceEvent, Values};
38use sc_client_api::BlockBackend;
39use sp_api::{Core, ProvideRuntimeApi};
40use sp_blockchain::HeaderBackend;
41use sp_core::hexdisplay::HexDisplay;
42use sp_rpc::tracing::{BlockTrace, Span, TraceBlockResponse};
43use sp_runtime::{
44 generic::BlockId,
45 traits::{Block as BlockT, Header},
46};
47use sp_tracing::{WASM_NAME_KEY, WASM_TARGET_KEY, WASM_TRACE_IDENTIFIER};
48
49const DEFAULT_TARGETS: &str = "pallet,frame,state";
51const TRACE_TARGET: &str = "block_trace";
52const REQUIRED_EVENT_FIELD: &str = "method";
54
55pub trait TracingExecuteBlock<Block: BlockT>: Send + Sync {
57 fn execute_block(&self, orig_hash: Block::Hash, block: Block) -> sp_blockchain::Result<()>;
66
67 fn call_recorded(
73 &self,
74 _block: Block::Hash,
75 _method: &str,
76 _call_data: &[u8],
77 ) -> sp_blockchain::Result<Vec<u8>> {
78 Err(sp_blockchain::Error::Application(Box::new(CallRecordedUnsupported)))
79 }
80}
81
82struct DefaultExecuteBlock<Client> {
86 client: Arc<Client>,
87}
88
89impl<Client> DefaultExecuteBlock<Client> {
90 pub fn new(client: Arc<Client>) -> Self {
92 Self { client }
93 }
94}
95
96impl<Client, Block> TracingExecuteBlock<Block> for DefaultExecuteBlock<Client>
97where
98 Client: ProvideRuntimeApi<Block> + Send + Sync + 'static,
99 Client::Api: Core<Block>,
100 Block: BlockT,
101{
102 fn execute_block(&self, _: Block::Hash, block: Block) -> sp_blockchain::Result<()> {
103 self.client
104 .runtime_api()
105 .execute_block(*block.header().parent_hash(), block.into())
106 .map_err(Into::into)
107 }
108}
109
110pub type TraceBlockResult<T> = Result<T, Error>;
112
113#[derive(Debug, thiserror::Error)]
116#[error("Recorded runtime calls are not supported by this node")]
117pub struct CallRecordedUnsupported;
118
119#[derive(Debug, thiserror::Error)]
121#[allow(missing_docs)]
122#[non_exhaustive]
123pub enum Error {
124 #[error("Invalid block Id: {0}")]
125 InvalidBlockId(#[from] sp_blockchain::Error),
126 #[error("Missing block component: {0}")]
127 MissingBlockComponent(String),
128 #[error("Dispatch error: {0}")]
129 Dispatch(String),
130}
131
132struct BlockSubscriber {
133 targets: Vec<(String, Level)>,
134 next_id: AtomicU64,
135 spans: Mutex<HashMap<Id, SpanDatum>>,
136 events: Mutex<Vec<TraceEvent>>,
137}
138
139impl BlockSubscriber {
140 fn new(targets: &str) -> Self {
141 let next_id = AtomicU64::new(1);
142 let mut targets: Vec<_> = targets.split(',').map(crate::parse_target).collect();
143 targets.push((WASM_TRACE_IDENTIFIER.to_owned(), Level::TRACE));
146 BlockSubscriber {
147 targets,
148 next_id,
149 spans: Mutex::new(HashMap::new()),
150 events: Mutex::new(Vec::new()),
151 }
152 }
153}
154
155impl Subscriber for BlockSubscriber {
156 fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
157 if !metadata.is_span() && metadata.fields().field(REQUIRED_EVENT_FIELD).is_none() {
158 return false;
159 }
160
161 for (target, level) in &self.targets {
162 if metadata.level() <= level && metadata.target().starts_with(target) {
163 return true;
164 }
165 }
166
167 false
168 }
169
170 fn new_span(&self, attrs: &Attributes<'_>) -> Id {
171 let id = Id::from_u64(self.next_id.fetch_add(1, Ordering::Relaxed));
172 let mut values = Values::default();
173 attrs.record(&mut values);
174 let parent_id = attrs.parent().cloned();
175 let span = SpanDatum {
176 id: id.clone(),
177 parent_id,
178 name: attrs.metadata().name().to_owned(),
179 target: attrs.metadata().target().to_owned(),
180 level: *attrs.metadata().level(),
181 line: attrs.metadata().line().unwrap_or(0),
182 start_time: Instant::now(),
183 values,
184 overall_time: Default::default(),
185 };
186
187 self.spans.lock().insert(id.clone(), span);
188 id
189 }
190
191 fn record(&self, span: &Id, values: &Record<'_>) {
192 let mut span_data = self.spans.lock();
193 if let Some(s) = span_data.get_mut(span) {
194 values.record(&mut s.values);
195 }
196 }
197
198 fn record_follows_from(&self, _span: &Id, _follows: &Id) {
199 unimplemented!("record_follows_from is not implemented");
201 }
202
203 fn event(&self, event: &tracing::Event<'_>) {
204 let mut values = crate::Values::default();
205 event.record(&mut values);
206 let parent_id = event.parent().cloned();
207 let trace_event = TraceEvent {
208 name: event.metadata().name().to_owned(),
209 target: event.metadata().target().to_owned(),
210 level: *event.metadata().level(),
211 values,
212 parent_id,
213 };
214 self.events.lock().push(trace_event);
215 }
216
217 fn enter(&self, _id: &Id) {}
218
219 fn exit(&self, _span: &Id) {}
220}
221
222pub struct BlockExecutor<Block: BlockT, Client> {
228 client: Arc<Client>,
229 block: Block::Hash,
230 targets: Option<String>,
231 storage_keys: Option<String>,
232 methods: Option<String>,
233 execute_block: Arc<dyn TracingExecuteBlock<Block>>,
234}
235
236impl<Block, Client> BlockExecutor<Block, Client>
237where
238 Block: BlockT + 'static,
239 Client: HeaderBackend<Block>
240 + BlockBackend<Block>
241 + ProvideRuntimeApi<Block>
242 + Send
243 + Sync
244 + 'static,
245 Client::Api: Core<Block>,
246{
247 pub fn new(
249 client: Arc<Client>,
250 block: Block::Hash,
251 targets: Option<String>,
252 storage_keys: Option<String>,
253 methods: Option<String>,
254 execute_block: Option<Arc<dyn TracingExecuteBlock<Block>>>,
255 ) -> Self {
256 Self {
257 client: client.clone(),
258 block,
259 targets,
260 storage_keys,
261 methods,
262 execute_block: execute_block
263 .unwrap_or_else(|| Arc::new(DefaultExecuteBlock::new(client))),
264 }
265 }
266
267 fn prepared_block(&self) -> TraceBlockResult<Block> {
268 let mut header = self
269 .client
270 .header(self.block)
271 .map_err(Error::InvalidBlockId)?
272 .ok_or_else(|| Error::MissingBlockComponent("Header not found".to_string()))?;
273 let extrinsics = self
274 .client
275 .block_body(self.block)
276 .map_err(Error::InvalidBlockId)?
277 .ok_or_else(|| Error::MissingBlockComponent("Extrinsics not found".to_string()))?;
278 tracing::debug!(target: "state_tracing", "Found {} extrinsics", extrinsics.len());
279
280 header.digest_mut().logs.retain(|d| d.as_seal().is_none());
281 Ok(Block::new(header, extrinsics))
282 }
283
284 pub fn trace_block(&self) -> TraceBlockResult<TraceBlockResponse> {
288 tracing::debug!(target: "state_tracing", "Tracing block: {}", self.block);
289 let block = self.prepared_block()?;
290 let parent_hash = *block.header().parent_hash();
291
292 let targets = if let Some(t) = &self.targets { t } else { DEFAULT_TARGETS };
293 let block_subscriber = BlockSubscriber::new(targets);
294 let dispatch = Dispatch::new(block_subscriber);
295
296 {
297 let dispatcher_span = tracing::debug_span!(
298 target: "state_tracing",
299 "execute_block",
300 extrinsics_len = block.extrinsics().len(),
301 );
302 let _guard = dispatcher_span.enter();
303
304 if let Err(e) = dispatcher::with_default(&dispatch, || {
305 let span = tracing::info_span!(target: TRACE_TARGET, "trace_block");
306 let _enter = span.enter();
307 self.execute_block.execute_block(self.block, block)
308 }) {
309 return Err(Error::Dispatch(format!(
310 "Failed to collect traces and execute block: {e:?}"
311 )));
312 }
313 }
314
315 let block_subscriber = dispatch.downcast_ref::<BlockSubscriber>().ok_or_else(|| {
316 Error::Dispatch(
317 "Cannot downcast Dispatch to BlockSubscriber after tracing block".to_string(),
318 )
319 })?;
320 let spans: Vec<_> = block_subscriber
321 .spans
322 .lock()
323 .drain()
324 .filter_map(|(_, s)| patch_and_filter(s, targets))
326 .collect();
327 let events: Vec<_> = block_subscriber
328 .events
329 .lock()
330 .drain(..)
331 .filter(|e| {
332 self.storage_keys
333 .as_ref()
334 .map(|keys| event_values_filter(e, "key", keys))
335 .unwrap_or(false)
336 })
337 .filter(|e| {
338 self.methods
339 .as_ref()
340 .map(|methods| event_values_filter(e, "method", methods))
341 .unwrap_or(false)
342 })
343 .map(|s| s.into())
344 .collect();
345 tracing::debug!(target: "state_tracing", "Captured {} spans and {} events", spans.len(), events.len());
346
347 Ok(TraceBlockResponse::BlockTrace(BlockTrace {
348 block_hash: block_id_as_string(BlockId::<Block>::Hash(self.block)),
349 parent_hash: block_id_as_string(BlockId::<Block>::Hash(parent_hash)),
350 tracing_targets: targets.to_string(),
351 storage_keys: self.storage_keys.clone().unwrap_or_default(),
352 methods: self.methods.clone().unwrap_or_default(),
353 spans,
354 events,
355 }))
356 }
357}
358
359fn event_values_filter(event: &TraceEvent, filter_kind: &str, values: &str) -> bool {
360 event
361 .values
362 .string_values
363 .get(filter_kind)
364 .map(|value| check_target(values, value, &event.level))
365 .unwrap_or(false)
366}
367
368fn patch_and_filter(mut span: SpanDatum, targets: &str) -> Option<Span> {
377 if span.name == WASM_TRACE_IDENTIFIER {
378 span.values.bool_values.insert("wasm".to_owned(), true);
379 if let Some(n) = span.values.string_values.remove(WASM_NAME_KEY) {
380 span.name = n;
381 }
382 if let Some(t) = span.values.string_values.remove(WASM_TARGET_KEY) {
383 span.target = t;
384 }
385 if !check_target(targets, &span.target, &span.level) {
386 return None;
387 }
388 }
389
390 Some(span.into())
391}
392
393fn check_target(targets: &str, target: &str, level: &Level) -> bool {
395 for (t, l) in targets.split(',').map(crate::parse_target) {
396 if target.starts_with(t.as_str()) && level <= &l {
397 return true;
398 }
399 }
400
401 false
402}
403
404fn block_id_as_string<T: BlockT>(block_id: BlockId<T>) -> String {
405 match block_id {
406 BlockId::Hash(h) => HexDisplay::from(&h.encode()).to_string(),
407 BlockId::Number(n) => HexDisplay::from(&n.encode()).to_string(),
408 }
409}