referrerpolicy=no-referrer-when-downgrade

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	/// Run `method` re-enacting `block` at its parent state, with a proof-size recorder registered
68	/// and `block`'s stored recording replayed when available; returns the SCALE-encoded result.
69	/// `call_data` is the complete SCALE-encoded argument list, passed through verbatim.
70	///
71	/// The default implementation errors: nodes that do not record proof size do not need it.
72	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
82/// Default implementation of [`ExecuteBlock`].
83///
84/// Uses [`Core::execute_block`] to directly execute a block.
85struct DefaultExecuteBlock<Client> {
86	client: Arc<Client>,
87}
88
89impl<Client> DefaultExecuteBlock<Client> {
90	/// Creates a new instance.
91	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
110/// Tracing Block Result type alias
111pub type TraceBlockResult<T> = Result<T, Error>;
112
113/// Typed marker returned by the default [`TracingExecuteBlock::call_recorded`], letting callers
114/// recognise "this node has no proof-recording hook" by downcast and map it to their own error.
115#[derive(Debug, thiserror::Error)]
116#[error("Recorded runtime calls are not supported by this node")]
117pub struct CallRecordedUnsupported;
118
119/// Tracing Block error
120#[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		// Ensure that WASM traces are always enabled
144		// Filtering happens when decoding the actual target / level
145		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		// Not currently used
200		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
222/// Holds a reference to the client in order to execute the given block.
223/// Records spans & events for the supplied targets (eg. "pallet,frame,state") and
224/// only records events with the specified hex encoded storage key prefixes.
225/// Note: if `targets` or `storage_keys` is an empty string then nothing is
226/// filtered out.
227pub 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	/// Create a new `BlockExecutor`
248	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	/// Execute block, record all spans and events belonging to `Self::targets`
285	/// and filter out events which do not have keys starting with one of the
286	/// prefixes in `Self::storage_keys`.
287	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			// Patch wasm identifiers
325			.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
368/// Filter out spans that do not match our targets and if the span is from WASM update its `name`
369/// and `target` fields to the WASM values for those fields.
370// The `tracing` crate requires trace metadata to be static. This does not work for wasm code in
371// substrate, as it is regularly updated with new code from on-chain events. The workaround for this
372// is for substrate's WASM tracing wrappers to put the `name` and `target` data in the `values` map
373// (normally they would be in the static metadata assembled at compile time). Here, if a special
374// WASM `name` or `target` key is found in the `values` we remove it and put the key value pair in
375// the span's metadata, making it consistent with spans that come from native code.
376fn 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
393/// Check if a `target` matches any `targets` by prefix
394fn 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}