sc_tracing/block/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Substrate.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// Substrate is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// Substrate is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with Substrate. If not, see <https://www.gnu.org/licenses/>.
17
18//! Utilities for tracing block execution
19
20use 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
49// Default to only pallet, frame support and state related traces
50const DEFAULT_TARGETS: &str = "pallet,frame,state";
51const TRACE_TARGET: &str = "block_trace";
52// The name of a field required for all events.
53const REQUIRED_EVENT_FIELD: &str = "method";
54
55/// Something that can execute a block in a tracing context.
56pub trait TracingExecuteBlock<Block: BlockT>: Send + Sync {
57	/// Execute the given `block`.
58	///
59	/// The `block` is prepared to be executed right away, this means that any `Seal` was already
60	/// removed from the header. As this changes the `hash` of the block, `orig_hash` is passed
61	/// alongside to the callee.
62	///
63	/// The execution should be done sync on the same thread, because the caller will register
64	/// special tracing collectors.
65	fn execute_block(&self, orig_hash: Block::Hash, block: Block) -> sp_blockchain::Result<()>;
66}
67
68/// Default implementation of [`ExecuteBlock`].
69///
70/// Uses [`Core::execute_block`] to directly execute a block.
71struct DefaultExecuteBlock<Client> {
72	client: Arc<Client>,
73}
74
75impl<Client> DefaultExecuteBlock<Client> {
76	/// Creates a new instance.
77	pub fn new(client: Arc<Client>) -> Self {
78		Self { client }
79	}
80}
81
82impl<Client, Block> TracingExecuteBlock<Block> for DefaultExecuteBlock<Client>
83where
84	Client: ProvideRuntimeApi<Block> + Send + Sync + 'static,
85	Client::Api: Core<Block>,
86	Block: BlockT,
87{
88	fn execute_block(&self, _: Block::Hash, block: Block) -> sp_blockchain::Result<()> {
89		self.client
90			.runtime_api()
91			.execute_block(*block.header().parent_hash(), block.into())
92			.map_err(Into::into)
93	}
94}
95
96/// Tracing Block Result type alias
97pub type TraceBlockResult<T> = Result<T, Error>;
98
99/// Tracing Block error
100#[derive(Debug, thiserror::Error)]
101#[allow(missing_docs)]
102#[non_exhaustive]
103pub enum Error {
104	#[error("Invalid block Id: {0}")]
105	InvalidBlockId(#[from] sp_blockchain::Error),
106	#[error("Missing block component: {0}")]
107	MissingBlockComponent(String),
108	#[error("Dispatch error: {0}")]
109	Dispatch(String),
110}
111
112struct BlockSubscriber {
113	targets: Vec<(String, Level)>,
114	next_id: AtomicU64,
115	spans: Mutex<HashMap<Id, SpanDatum>>,
116	events: Mutex<Vec<TraceEvent>>,
117}
118
119impl BlockSubscriber {
120	fn new(targets: &str) -> Self {
121		let next_id = AtomicU64::new(1);
122		let mut targets: Vec<_> = targets.split(',').map(crate::parse_target).collect();
123		// Ensure that WASM traces are always enabled
124		// Filtering happens when decoding the actual target / level
125		targets.push((WASM_TRACE_IDENTIFIER.to_owned(), Level::TRACE));
126		BlockSubscriber {
127			targets,
128			next_id,
129			spans: Mutex::new(HashMap::new()),
130			events: Mutex::new(Vec::new()),
131		}
132	}
133}
134
135impl Subscriber for BlockSubscriber {
136	fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
137		if !metadata.is_span() && metadata.fields().field(REQUIRED_EVENT_FIELD).is_none() {
138			return false
139		}
140
141		for (target, level) in &self.targets {
142			if metadata.level() <= level && metadata.target().starts_with(target) {
143				return true
144			}
145		}
146
147		false
148	}
149
150	fn new_span(&self, attrs: &Attributes<'_>) -> Id {
151		let id = Id::from_u64(self.next_id.fetch_add(1, Ordering::Relaxed));
152		let mut values = Values::default();
153		attrs.record(&mut values);
154		let parent_id = attrs.parent().cloned();
155		let span = SpanDatum {
156			id: id.clone(),
157			parent_id,
158			name: attrs.metadata().name().to_owned(),
159			target: attrs.metadata().target().to_owned(),
160			level: *attrs.metadata().level(),
161			line: attrs.metadata().line().unwrap_or(0),
162			start_time: Instant::now(),
163			values,
164			overall_time: Default::default(),
165		};
166
167		self.spans.lock().insert(id.clone(), span);
168		id
169	}
170
171	fn record(&self, span: &Id, values: &Record<'_>) {
172		let mut span_data = self.spans.lock();
173		if let Some(s) = span_data.get_mut(span) {
174			values.record(&mut s.values);
175		}
176	}
177
178	fn record_follows_from(&self, _span: &Id, _follows: &Id) {
179		// Not currently used
180		unimplemented!("record_follows_from is not implemented");
181	}
182
183	fn event(&self, event: &tracing::Event<'_>) {
184		let mut values = crate::Values::default();
185		event.record(&mut values);
186		let parent_id = event.parent().cloned();
187		let trace_event = TraceEvent {
188			name: event.metadata().name().to_owned(),
189			target: event.metadata().target().to_owned(),
190			level: *event.metadata().level(),
191			values,
192			parent_id,
193		};
194		self.events.lock().push(trace_event);
195	}
196
197	fn enter(&self, _id: &Id) {}
198
199	fn exit(&self, _span: &Id) {}
200}
201
202/// Holds a reference to the client in order to execute the given block.
203/// Records spans & events for the supplied targets (eg. "pallet,frame,state") and
204/// only records events with the specified hex encoded storage key prefixes.
205/// Note: if `targets` or `storage_keys` is an empty string then nothing is
206/// filtered out.
207pub struct BlockExecutor<Block: BlockT, Client> {
208	client: Arc<Client>,
209	block: Block::Hash,
210	targets: Option<String>,
211	storage_keys: Option<String>,
212	methods: Option<String>,
213	execute_block: Arc<dyn TracingExecuteBlock<Block>>,
214}
215
216impl<Block, Client> BlockExecutor<Block, Client>
217where
218	Block: BlockT + 'static,
219	Client: HeaderBackend<Block>
220		+ BlockBackend<Block>
221		+ ProvideRuntimeApi<Block>
222		+ Send
223		+ Sync
224		+ 'static,
225	Client::Api: Core<Block>,
226{
227	/// Create a new `BlockExecutor`
228	pub fn new(
229		client: Arc<Client>,
230		block: Block::Hash,
231		targets: Option<String>,
232		storage_keys: Option<String>,
233		methods: Option<String>,
234		execute_block: Option<Arc<dyn TracingExecuteBlock<Block>>>,
235	) -> Self {
236		Self {
237			client: client.clone(),
238			block,
239			targets,
240			storage_keys,
241			methods,
242			execute_block: execute_block
243				.unwrap_or_else(|| Arc::new(DefaultExecuteBlock::new(client))),
244		}
245	}
246
247	/// Execute block, record all spans and events belonging to `Self::targets`
248	/// and filter out events which do not have keys starting with one of the
249	/// prefixes in `Self::storage_keys`.
250	pub fn trace_block(&self) -> TraceBlockResult<TraceBlockResponse> {
251		tracing::debug!(target: "state_tracing", "Tracing block: {}", self.block);
252		// Prepare the block
253		let mut header = self
254			.client
255			.header(self.block)
256			.map_err(Error::InvalidBlockId)?
257			.ok_or_else(|| Error::MissingBlockComponent("Header not found".to_string()))?;
258		let extrinsics = self
259			.client
260			.block_body(self.block)
261			.map_err(Error::InvalidBlockId)?
262			.ok_or_else(|| Error::MissingBlockComponent("Extrinsics not found".to_string()))?;
263		tracing::debug!(target: "state_tracing", "Found {} extrinsics", extrinsics.len());
264		let parent_hash = *header.parent_hash();
265		// Remove all `Seal`s as they are added by the consensus engines after building the block.
266		// On import they are normally removed by the consensus engine.
267		header.digest_mut().logs.retain(|d| d.as_seal().is_none());
268		let block = Block::new(header, extrinsics);
269
270		let targets = if let Some(t) = &self.targets { t } else { DEFAULT_TARGETS };
271		let block_subscriber = BlockSubscriber::new(targets);
272		let dispatch = Dispatch::new(block_subscriber);
273
274		{
275			let dispatcher_span = tracing::debug_span!(
276				target: "state_tracing",
277				"execute_block",
278				extrinsics_len = block.extrinsics().len(),
279			);
280			let _guard = dispatcher_span.enter();
281
282			if let Err(e) = dispatcher::with_default(&dispatch, || {
283				let span = tracing::info_span!(target: TRACE_TARGET, "trace_block");
284				let _enter = span.enter();
285				self.execute_block.execute_block(self.block, block)
286			}) {
287				return Err(Error::Dispatch(format!(
288					"Failed to collect traces and execute block: {e:?}"
289				)))
290			}
291		}
292
293		let block_subscriber = dispatch.downcast_ref::<BlockSubscriber>().ok_or_else(|| {
294			Error::Dispatch(
295				"Cannot downcast Dispatch to BlockSubscriber after tracing block".to_string(),
296			)
297		})?;
298		let spans: Vec<_> = block_subscriber
299			.spans
300			.lock()
301			.drain()
302			// Patch wasm identifiers
303			.filter_map(|(_, s)| patch_and_filter(s, targets))
304			.collect();
305		let events: Vec<_> = block_subscriber
306			.events
307			.lock()
308			.drain(..)
309			.filter(|e| {
310				self.storage_keys
311					.as_ref()
312					.map(|keys| event_values_filter(e, "key", keys))
313					.unwrap_or(false)
314			})
315			.filter(|e| {
316				self.methods
317					.as_ref()
318					.map(|methods| event_values_filter(e, "method", methods))
319					.unwrap_or(false)
320			})
321			.map(|s| s.into())
322			.collect();
323		tracing::debug!(target: "state_tracing", "Captured {} spans and {} events", spans.len(), events.len());
324
325		Ok(TraceBlockResponse::BlockTrace(BlockTrace {
326			block_hash: block_id_as_string(BlockId::<Block>::Hash(self.block)),
327			parent_hash: block_id_as_string(BlockId::<Block>::Hash(parent_hash)),
328			tracing_targets: targets.to_string(),
329			storage_keys: self.storage_keys.clone().unwrap_or_default(),
330			methods: self.methods.clone().unwrap_or_default(),
331			spans,
332			events,
333		}))
334	}
335}
336
337fn event_values_filter(event: &TraceEvent, filter_kind: &str, values: &str) -> bool {
338	event
339		.values
340		.string_values
341		.get(filter_kind)
342		.map(|value| check_target(values, value, &event.level))
343		.unwrap_or(false)
344}
345
346/// Filter out spans that do not match our targets and if the span is from WASM update its `name`
347/// and `target` fields to the WASM values for those fields.
348// The `tracing` crate requires trace metadata to be static. This does not work for wasm code in
349// substrate, as it is regularly updated with new code from on-chain events. The workaround for this
350// is for substrate's WASM tracing wrappers to put the `name` and `target` data in the `values` map
351// (normally they would be in the static metadata assembled at compile time). Here, if a special
352// WASM `name` or `target` key is found in the `values` we remove it and put the key value pair in
353// the span's metadata, making it consistent with spans that come from native code.
354fn patch_and_filter(mut span: SpanDatum, targets: &str) -> Option<Span> {
355	if span.name == WASM_TRACE_IDENTIFIER {
356		span.values.bool_values.insert("wasm".to_owned(), true);
357		if let Some(n) = span.values.string_values.remove(WASM_NAME_KEY) {
358			span.name = n;
359		}
360		if let Some(t) = span.values.string_values.remove(WASM_TARGET_KEY) {
361			span.target = t;
362		}
363		if !check_target(targets, &span.target, &span.level) {
364			return None
365		}
366	}
367
368	Some(span.into())
369}
370
371/// Check if a `target` matches any `targets` by prefix
372fn check_target(targets: &str, target: &str, level: &Level) -> bool {
373	for (t, l) in targets.split(',').map(crate::parse_target) {
374		if target.starts_with(t.as_str()) && level <= &l {
375			return true
376		}
377	}
378
379	false
380}
381
382fn block_id_as_string<T: BlockT>(block_id: BlockId<T>) -> String {
383	match block_id {
384		BlockId::Hash(h) => HexDisplay::from(&h.encode()).to_string(),
385		BlockId::Number(n) => HexDisplay::from(&n.encode()).to_string(),
386	}
387}