soil_client/tracing/block/
mod.rs1use std::{
10 collections::HashMap,
11 sync::{
12 atomic::{AtomicU64, Ordering},
13 Arc,
14 },
15 time::Instant,
16};
17
18use ::tracing::{
19 dispatcher,
20 span::{Attributes, Id, Record},
21 Dispatch, Level, Subscriber,
22};
23use codec::Encode;
24use parking_lot::Mutex;
25
26use super::{SpanDatum, TraceEvent, Values};
27use crate::blockchain::HeaderBackend;
28use crate::client_api::BlockBackend;
29use subsoil::api::{Core, ProvideRuntimeApi};
30use subsoil::core::hexdisplay::HexDisplay;
31use subsoil::runtime::{
32 generic::BlockId,
33 traits::{Block as BlockT, Header},
34};
35use subsoil::tracing::rpc::{BlockTrace, Span, TraceBlockResponse};
36use subsoil::tracing::{WASM_NAME_KEY, WASM_TARGET_KEY, WASM_TRACE_IDENTIFIER};
37
38const DEFAULT_TARGETS: &str = "pallet,frame,state";
40const TRACE_TARGET: &str = "block_trace";
41const REQUIRED_EVENT_FIELD: &str = "method";
43
44pub trait TracingExecuteBlock<Block: BlockT>: Send + Sync {
46 fn execute_block(&self, orig_hash: Block::Hash, block: Block) -> crate::blockchain::Result<()>;
55}
56
57struct DefaultExecuteBlock<Client> {
61 client: Arc<Client>,
62}
63
64impl<Client> DefaultExecuteBlock<Client> {
65 pub fn new(client: Arc<Client>) -> Self {
67 Self { client }
68 }
69}
70
71impl<Client, Block> TracingExecuteBlock<Block> for DefaultExecuteBlock<Client>
72where
73 Client: ProvideRuntimeApi<Block> + Send + Sync + 'static,
74 Client::Api: Core<Block>,
75 Block: BlockT,
76{
77 fn execute_block(&self, _: Block::Hash, block: Block) -> crate::blockchain::Result<()> {
78 self.client
79 .runtime_api()
80 .execute_block(*block.header().parent_hash(), block.into())
81 .map_err(Into::into)
82 }
83}
84
85pub type TraceBlockResult<T> = Result<T, Error>;
87
88#[derive(Debug, thiserror::Error)]
90#[allow(missing_docs)]
91#[non_exhaustive]
92pub enum Error {
93 #[error("Invalid block Id: {0}")]
94 InvalidBlockId(#[from] crate::blockchain::Error),
95 #[error("Missing block component: {0}")]
96 MissingBlockComponent(String),
97 #[error("Dispatch error: {0}")]
98 Dispatch(String),
99}
100
101struct BlockSubscriber {
102 targets: Vec<(String, Level)>,
103 next_id: AtomicU64,
104 spans: Mutex<HashMap<Id, SpanDatum>>,
105 events: Mutex<Vec<TraceEvent>>,
106}
107
108impl BlockSubscriber {
109 fn new(targets: &str) -> Self {
110 let next_id = AtomicU64::new(1);
111 let mut targets: Vec<_> = targets.split(',').map(super::parse_target).collect();
112 targets.push((WASM_TRACE_IDENTIFIER.to_owned(), Level::TRACE));
115 BlockSubscriber {
116 targets,
117 next_id,
118 spans: Mutex::new(HashMap::new()),
119 events: Mutex::new(Vec::new()),
120 }
121 }
122}
123
124impl Subscriber for BlockSubscriber {
125 fn enabled(&self, metadata: &::tracing::Metadata<'_>) -> bool {
126 if !metadata.is_span() && metadata.fields().field(REQUIRED_EVENT_FIELD).is_none() {
127 return false;
128 }
129
130 for (target, level) in &self.targets {
131 if metadata.level() <= level && metadata.target().starts_with(target) {
132 return true;
133 }
134 }
135
136 false
137 }
138
139 fn new_span(&self, attrs: &Attributes<'_>) -> Id {
140 let id = Id::from_u64(self.next_id.fetch_add(1, Ordering::Relaxed));
141 let mut values = Values::default();
142 attrs.record(&mut values);
143 let parent_id = attrs.parent().cloned();
144 let span = SpanDatum {
145 id: id.clone(),
146 parent_id,
147 name: attrs.metadata().name().to_owned(),
148 target: attrs.metadata().target().to_owned(),
149 level: *attrs.metadata().level(),
150 line: attrs.metadata().line().unwrap_or(0),
151 start_time: Instant::now(),
152 values,
153 overall_time: Default::default(),
154 };
155
156 self.spans.lock().insert(id.clone(), span);
157 id
158 }
159
160 fn record(&self, span: &Id, values: &Record<'_>) {
161 let mut span_data = self.spans.lock();
162 if let Some(s) = span_data.get_mut(span) {
163 values.record(&mut s.values);
164 }
165 }
166
167 fn record_follows_from(&self, _span: &Id, _follows: &Id) {
168 unimplemented!("record_follows_from is not implemented");
170 }
171
172 fn event(&self, event: &::tracing::Event<'_>) {
173 let mut values = Values::default();
174 event.record(&mut values);
175 let parent_id = event.parent().cloned();
176 let trace_event = TraceEvent {
177 name: event.metadata().name().to_owned(),
178 target: event.metadata().target().to_owned(),
179 level: *event.metadata().level(),
180 values,
181 parent_id,
182 };
183 self.events.lock().push(trace_event);
184 }
185
186 fn enter(&self, _id: &Id) {}
187
188 fn exit(&self, _span: &Id) {}
189}
190
191pub struct BlockExecutor<Block: BlockT, Client> {
197 client: Arc<Client>,
198 block: Block::Hash,
199 targets: Option<String>,
200 storage_keys: Option<String>,
201 methods: Option<String>,
202 execute_block: Arc<dyn TracingExecuteBlock<Block>>,
203}
204
205impl<Block, Client> BlockExecutor<Block, Client>
206where
207 Block: BlockT + 'static,
208 Client: HeaderBackend<Block>
209 + BlockBackend<Block>
210 + ProvideRuntimeApi<Block>
211 + Send
212 + Sync
213 + 'static,
214 Client::Api: Core<Block>,
215{
216 pub fn new(
218 client: Arc<Client>,
219 block: Block::Hash,
220 targets: Option<String>,
221 storage_keys: Option<String>,
222 methods: Option<String>,
223 execute_block: Option<Arc<dyn TracingExecuteBlock<Block>>>,
224 ) -> Self {
225 Self {
226 client: client.clone(),
227 block,
228 targets,
229 storage_keys,
230 methods,
231 execute_block: execute_block
232 .unwrap_or_else(|| Arc::new(DefaultExecuteBlock::new(client))),
233 }
234 }
235
236 pub fn trace_block(&self) -> TraceBlockResult<TraceBlockResponse> {
240 ::tracing::debug!(target: "state_tracing", "Tracing block: {}", self.block);
241 let mut header = self
243 .client
244 .header(self.block)
245 .map_err(Error::InvalidBlockId)?
246 .ok_or_else(|| Error::MissingBlockComponent("Header not found".to_string()))?;
247 let extrinsics = self
248 .client
249 .block_body(self.block)
250 .map_err(Error::InvalidBlockId)?
251 .ok_or_else(|| Error::MissingBlockComponent("Extrinsics not found".to_string()))?;
252 ::tracing::debug!(target: "state_tracing", "Found {} extrinsics", extrinsics.len());
253 let parent_hash = *header.parent_hash();
254 header.digest_mut().logs.retain(|d| d.as_seal().is_none());
257 let block = Block::new(header, extrinsics);
258
259 let targets = if let Some(t) = &self.targets { t } else { DEFAULT_TARGETS };
260 let block_subscriber = BlockSubscriber::new(targets);
261 let dispatch = Dispatch::new(block_subscriber);
262
263 {
264 let dispatcher_span = ::tracing::debug_span!(
265 target: "state_tracing",
266 "execute_block",
267 extrinsics_len = block.extrinsics().len(),
268 );
269 let _guard = dispatcher_span.enter();
270
271 if let Err(e) = dispatcher::with_default(&dispatch, || {
272 let span = ::tracing::info_span!(target: TRACE_TARGET, "trace_block");
273 let _enter = span.enter();
274 self.execute_block.execute_block(self.block, block)
275 }) {
276 return Err(Error::Dispatch(format!(
277 "Failed to collect traces and execute block: {e:?}"
278 )));
279 }
280 }
281
282 let block_subscriber = dispatch.downcast_ref::<BlockSubscriber>().ok_or_else(|| {
283 Error::Dispatch(
284 "Cannot downcast Dispatch to BlockSubscriber after tracing block".to_string(),
285 )
286 })?;
287 let spans: Vec<_> = block_subscriber
288 .spans
289 .lock()
290 .drain()
291 .filter_map(|(_, s)| patch_and_filter(s, targets))
293 .collect();
294 let events: Vec<_> = block_subscriber
295 .events
296 .lock()
297 .drain(..)
298 .filter(|e| {
299 self.storage_keys
300 .as_ref()
301 .map(|keys| event_values_filter(e, "key", keys))
302 .unwrap_or(false)
303 })
304 .filter(|e| {
305 self.methods
306 .as_ref()
307 .map(|methods| event_values_filter(e, "method", methods))
308 .unwrap_or(false)
309 })
310 .map(|s| s.into())
311 .collect();
312 ::tracing::debug!(target: "state_tracing", "Captured {} spans and {} events", spans.len(), events.len());
313
314 Ok(TraceBlockResponse::BlockTrace(BlockTrace {
315 block_hash: block_id_as_string(BlockId::<Block>::Hash(self.block)),
316 parent_hash: block_id_as_string(BlockId::<Block>::Hash(parent_hash)),
317 tracing_targets: targets.to_string(),
318 storage_keys: self.storage_keys.clone().unwrap_or_default(),
319 methods: self.methods.clone().unwrap_or_default(),
320 spans,
321 events,
322 }))
323 }
324}
325
326fn event_values_filter(event: &TraceEvent, filter_kind: &str, values: &str) -> bool {
327 event
328 .values
329 .string_values
330 .get(filter_kind)
331 .map(|value| check_target(values, value, &event.level))
332 .unwrap_or(false)
333}
334
335fn patch_and_filter(mut span: SpanDatum, targets: &str) -> Option<Span> {
344 if span.name == WASM_TRACE_IDENTIFIER {
345 span.values.bool_values.insert("wasm".to_owned(), true);
346 if let Some(n) = span.values.string_values.remove(WASM_NAME_KEY) {
347 span.name = n;
348 }
349 if let Some(t) = span.values.string_values.remove(WASM_TARGET_KEY) {
350 span.target = t;
351 }
352 if !check_target(targets, &span.target, &span.level) {
353 return None;
354 }
355 }
356
357 Some(span.into())
358}
359
360fn check_target(targets: &str, target: &str, level: &Level) -> bool {
362 for (t, l) in targets.split(',').map(super::parse_target) {
363 if target.starts_with(t.as_str()) && level <= &l {
364 return true;
365 }
366 }
367
368 false
369}
370
371fn block_id_as_string<T: BlockT>(block_id: BlockId<T>) -> String {
372 match block_id {
373 BlockId::Hash(h) => HexDisplay::from(&h.encode()).to_string(),
374 BlockId::Number(n) => HexDisplay::from(&n.encode()).to_string(),
375 }
376}