sc_tracing/block/
mod.rs

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