sp_runtime/generic/
digest.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Generic implementation of a digest.
19
20#[cfg(all(not(feature = "std"), feature = "serde"))]
21use alloc::format;
22use alloc::vec::Vec;
23use codec::DecodeAll;
24#[cfg(feature = "serde")]
25use serde::{Deserialize, Serialize};
26
27use crate::{
28	codec::{Decode, DecodeWithMemTracking, Encode, Error, Input},
29	scale_info::{
30		build::{Fields, Variants},
31		Path, Type, TypeInfo,
32	},
33	ConsensusEngineId,
34};
35use sp_core::RuntimeDebug;
36
37/// Generic header digest.
38#[derive(
39	PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, Default,
40)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42pub struct Digest {
43	/// A list of logs in the digest.
44	pub logs: Vec<DigestItem>,
45}
46
47impl Digest {
48	/// Get reference to all digest items.
49	pub fn logs(&self) -> &[DigestItem] {
50		&self.logs
51	}
52
53	/// Push new digest item.
54	pub fn push(&mut self, item: DigestItem) {
55		self.logs.push(item);
56	}
57
58	/// Pop a digest item.
59	pub fn pop(&mut self) -> Option<DigestItem> {
60		self.logs.pop()
61	}
62
63	/// Get reference to the first digest item that matches the passed predicate.
64	pub fn log<T: ?Sized, F: Fn(&DigestItem) -> Option<&T>>(&self, predicate: F) -> Option<&T> {
65		self.logs().iter().find_map(predicate)
66	}
67
68	/// Get a conversion of the first digest item that successfully converts using the function.
69	pub fn convert_first<T, F: Fn(&DigestItem) -> Option<T>>(&self, predicate: F) -> Option<T> {
70		self.logs().iter().find_map(predicate)
71	}
72}
73
74/// Digest item that is able to encode/decode 'system' digest items and
75/// provide opaque access to other items.
76#[derive(PartialEq, Eq, Clone, DecodeWithMemTracking, RuntimeDebug)]
77pub enum DigestItem {
78	/// A pre-runtime digest.
79	///
80	/// These are messages from the consensus engine to the runtime, although
81	/// the consensus engine can (and should) read them itself to avoid
82	/// code and state duplication. It is erroneous for a runtime to produce
83	/// these, but this is not (yet) checked.
84	///
85	/// NOTE: the runtime is not allowed to panic or fail in an `on_initialize`
86	/// call if an expected `PreRuntime` digest is not present. It is the
87	/// responsibility of a external block verifier to check this. Runtime API calls
88	/// will initialize the block without pre-runtime digests, so initialization
89	/// cannot fail when they are missing.
90	PreRuntime(ConsensusEngineId, Vec<u8>),
91
92	/// A message from the runtime to the consensus engine. This should *never*
93	/// be generated by the native code of any consensus engine, but this is not
94	/// checked (yet).
95	Consensus(ConsensusEngineId, Vec<u8>),
96
97	/// Put a Seal on it. This is only used by native code, and is never seen
98	/// by runtimes.
99	Seal(ConsensusEngineId, Vec<u8>),
100
101	/// Some other thing. Unsupported and experimental.
102	Other(Vec<u8>),
103
104	/// An indication for the light clients that the runtime execution
105	/// environment is updated.
106	///
107	/// Currently this is triggered when:
108	/// 1. Runtime code blob is changed or
109	/// 2. `heap_pages` value is changed.
110	RuntimeEnvironmentUpdated,
111}
112
113#[cfg(feature = "serde")]
114impl serde::Serialize for DigestItem {
115	fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
116	where
117		S: serde::Serializer,
118	{
119		self.using_encoded(|bytes| sp_core::bytes::serialize(bytes, seq))
120	}
121}
122
123#[cfg(feature = "serde")]
124impl<'a> serde::Deserialize<'a> for DigestItem {
125	fn deserialize<D>(de: D) -> Result<Self, D::Error>
126	where
127		D: serde::Deserializer<'a>,
128	{
129		let r = sp_core::bytes::deserialize(de)?;
130		Decode::decode(&mut &r[..])
131			.map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e)))
132	}
133}
134
135impl TypeInfo for DigestItem {
136	type Identity = Self;
137
138	fn type_info() -> Type {
139		Type::builder().path(Path::new("DigestItem", module_path!())).variant(
140			Variants::new()
141				.variant("PreRuntime", |v| {
142					v.index(DigestItemType::PreRuntime as u8).fields(
143						Fields::unnamed()
144							.field(|f| f.ty::<ConsensusEngineId>().type_name("ConsensusEngineId"))
145							.field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")),
146					)
147				})
148				.variant("Consensus", |v| {
149					v.index(DigestItemType::Consensus as u8).fields(
150						Fields::unnamed()
151							.field(|f| f.ty::<ConsensusEngineId>().type_name("ConsensusEngineId"))
152							.field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")),
153					)
154				})
155				.variant("Seal", |v| {
156					v.index(DigestItemType::Seal as u8).fields(
157						Fields::unnamed()
158							.field(|f| f.ty::<ConsensusEngineId>().type_name("ConsensusEngineId"))
159							.field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")),
160					)
161				})
162				.variant("Other", |v| {
163					v.index(DigestItemType::Other as u8)
164						.fields(Fields::unnamed().field(|f| f.ty::<Vec<u8>>().type_name("Vec<u8>")))
165				})
166				.variant("RuntimeEnvironmentUpdated", |v| {
167					v.index(DigestItemType::RuntimeEnvironmentUpdated as u8).fields(Fields::unit())
168				}),
169		)
170	}
171}
172
173/// A 'referencing view' for digest item. Does not own its contents. Used by
174/// final runtime implementations for encoding/decoding its log items.
175#[derive(PartialEq, Eq, Clone, RuntimeDebug)]
176pub enum DigestItemRef<'a> {
177	/// A pre-runtime digest.
178	///
179	/// These are messages from the consensus engine to the runtime, although
180	/// the consensus engine can (and should) read them itself to avoid
181	/// code and state duplication.  It is erroneous for a runtime to produce
182	/// these, but this is not (yet) checked.
183	PreRuntime(&'a ConsensusEngineId, &'a [u8]),
184	/// A message from the runtime to the consensus engine. This should *never*
185	/// be generated by the native code of any consensus engine, but this is not
186	/// checked (yet).
187	Consensus(&'a ConsensusEngineId, &'a [u8]),
188	/// Put a Seal on it. This is only used by native code, and is never seen
189	/// by runtimes.
190	Seal(&'a ConsensusEngineId, &'a [u8]),
191	/// Any 'non-system' digest item, opaque to the native code.
192	Other(&'a [u8]),
193	/// Runtime code or heap pages updated.
194	RuntimeEnvironmentUpdated,
195}
196
197/// Type of the digest item. Used to gain explicit control over `DigestItem` encoding
198/// process. We need an explicit control, because final runtimes are encoding their own
199/// digest items using `DigestItemRef` type and we can't auto-derive `Decode`
200/// trait for `DigestItemRef`.
201#[repr(u32)]
202#[derive(Encode, Decode)]
203pub enum DigestItemType {
204	Other = 0,
205	Consensus = 4,
206	Seal = 5,
207	PreRuntime = 6,
208	RuntimeEnvironmentUpdated = 8,
209}
210
211/// Type of a digest item that contains raw data; this also names the consensus engine ID where
212/// applicable. Used to identify one or more digest items of interest.
213#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
214pub enum OpaqueDigestItemId<'a> {
215	/// Type corresponding to DigestItem::PreRuntime.
216	PreRuntime(&'a ConsensusEngineId),
217	/// Type corresponding to DigestItem::Consensus.
218	Consensus(&'a ConsensusEngineId),
219	/// Type corresponding to DigestItem::Seal.
220	Seal(&'a ConsensusEngineId),
221	/// Some other (non-prescribed) type.
222	Other,
223}
224
225impl DigestItem {
226	/// Returns a 'referencing view' for this digest item.
227	pub fn dref(&self) -> DigestItemRef {
228		match *self {
229			Self::PreRuntime(ref v, ref s) => DigestItemRef::PreRuntime(v, s),
230			Self::Consensus(ref v, ref s) => DigestItemRef::Consensus(v, s),
231			Self::Seal(ref v, ref s) => DigestItemRef::Seal(v, s),
232			Self::Other(ref v) => DigestItemRef::Other(v),
233			Self::RuntimeEnvironmentUpdated => DigestItemRef::RuntimeEnvironmentUpdated,
234		}
235	}
236
237	/// Returns `Some` if this entry is the `PreRuntime` entry.
238	pub fn as_pre_runtime(&self) -> Option<(ConsensusEngineId, &[u8])> {
239		self.dref().as_pre_runtime()
240	}
241
242	/// Returns `Some` if this entry is the `Consensus` entry.
243	pub fn as_consensus(&self) -> Option<(ConsensusEngineId, &[u8])> {
244		self.dref().as_consensus()
245	}
246
247	/// Returns `Some` if this entry is the `Seal` entry.
248	pub fn as_seal(&self) -> Option<(ConsensusEngineId, &[u8])> {
249		self.dref().as_seal()
250	}
251
252	/// Returns Some if `self` is a `DigestItem::Other`.
253	pub fn as_other(&self) -> Option<&[u8]> {
254		self.dref().as_other()
255	}
256
257	/// Returns the opaque data contained in the item if `Some` if this entry has the id given.
258	pub fn try_as_raw(&self, id: OpaqueDigestItemId) -> Option<&[u8]> {
259		self.dref().try_as_raw(id)
260	}
261
262	/// Returns the data decoded as `T`, if the `id` is matching.
263	pub fn try_to<T: Decode>(&self, id: OpaqueDigestItemId) -> Option<T> {
264		self.dref().try_to::<T>(id)
265	}
266
267	/// Try to match this to a `Self::Seal`, check `id` matches and decode it.
268	///
269	/// Returns `None` if this isn't a seal item, the `id` doesn't match or when the decoding fails.
270	pub fn seal_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
271		self.dref().seal_try_to(id)
272	}
273
274	/// Try to match this to a `Self::Consensus`, check `id` matches and decode it.
275	///
276	/// Returns `None` if this isn't a consensus item, the `id` doesn't match or
277	/// when the decoding fails.
278	pub fn consensus_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
279		self.dref().consensus_try_to(id)
280	}
281
282	/// Try to match this to a `Self::PreRuntime`, check `id` matches and decode it.
283	///
284	/// Returns `None` if this isn't a pre-runtime item, the `id` doesn't match or
285	/// when the decoding fails.
286	pub fn pre_runtime_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
287		self.dref().pre_runtime_try_to(id)
288	}
289}
290
291impl Encode for DigestItem {
292	fn encode(&self) -> Vec<u8> {
293		self.dref().encode()
294	}
295}
296
297impl codec::EncodeLike for DigestItem {}
298
299impl Decode for DigestItem {
300	#[allow(deprecated)]
301	fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
302		let item_type: DigestItemType = Decode::decode(input)?;
303		match item_type {
304			DigestItemType::PreRuntime => {
305				let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
306				Ok(Self::PreRuntime(vals.0, vals.1))
307			},
308			DigestItemType::Consensus => {
309				let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
310				Ok(Self::Consensus(vals.0, vals.1))
311			},
312			DigestItemType::Seal => {
313				let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
314				Ok(Self::Seal(vals.0, vals.1))
315			},
316			DigestItemType::Other => Ok(Self::Other(Decode::decode(input)?)),
317			DigestItemType::RuntimeEnvironmentUpdated => Ok(Self::RuntimeEnvironmentUpdated),
318		}
319	}
320}
321
322impl<'a> DigestItemRef<'a> {
323	/// Cast this digest item into `PreRuntime`
324	pub fn as_pre_runtime(&self) -> Option<(ConsensusEngineId, &'a [u8])> {
325		match *self {
326			Self::PreRuntime(consensus_engine_id, data) => Some((*consensus_engine_id, data)),
327			_ => None,
328		}
329	}
330
331	/// Cast this digest item into `Consensus`
332	pub fn as_consensus(&self) -> Option<(ConsensusEngineId, &'a [u8])> {
333		match *self {
334			Self::Consensus(consensus_engine_id, data) => Some((*consensus_engine_id, data)),
335			_ => None,
336		}
337	}
338
339	/// Cast this digest item into `Seal`
340	pub fn as_seal(&self) -> Option<(ConsensusEngineId, &'a [u8])> {
341		match *self {
342			Self::Seal(consensus_engine_id, data) => Some((*consensus_engine_id, data)),
343			_ => None,
344		}
345	}
346
347	/// Cast this digest item into `PreRuntime`
348	pub fn as_other(&self) -> Option<&'a [u8]> {
349		match *self {
350			Self::Other(data) => Some(data),
351			_ => None,
352		}
353	}
354
355	/// Try to match this digest item to the given opaque item identifier; if it matches, then
356	/// return the opaque data it contains.
357	pub fn try_as_raw(&self, id: OpaqueDigestItemId) -> Option<&'a [u8]> {
358		match (id, self) {
359			(OpaqueDigestItemId::Consensus(w), &Self::Consensus(v, s)) |
360			(OpaqueDigestItemId::Seal(w), &Self::Seal(v, s)) |
361			(OpaqueDigestItemId::PreRuntime(w), &Self::PreRuntime(v, s))
362				if v == w =>
363				Some(s),
364			(OpaqueDigestItemId::Other, &Self::Other(s)) => Some(s),
365			_ => None,
366		}
367	}
368
369	/// Try to match this digest item to the given opaque item identifier; if it matches, then
370	/// try to cast to the given data type; if that works, return it.
371	pub fn try_to<T: Decode>(&self, id: OpaqueDigestItemId) -> Option<T> {
372		self.try_as_raw(id).and_then(|mut x| DecodeAll::decode_all(&mut x).ok())
373	}
374
375	/// Try to match this to a `Self::Seal`, check `id` matches and decode it.
376	///
377	/// Returns `None` if this isn't a seal item, the `id` doesn't match or when the decoding fails.
378	pub fn seal_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
379		self.as_seal()
380			.filter(|s| s.0 == *id)
381			.and_then(|mut d| DecodeAll::decode_all(&mut d.1).ok())
382	}
383
384	/// Try to match this to a `Self::Consensus`, check `id` matches and decode it.
385	///
386	/// Returns `None` if this isn't a consensus item, the `id` doesn't match or
387	/// when the decoding fails.
388	pub fn consensus_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
389		self.as_consensus()
390			.filter(|s| s.0 == *id)
391			.and_then(|mut d| DecodeAll::decode_all(&mut d.1).ok())
392	}
393
394	/// Try to match this to a `Self::PreRuntime`, check `id` matches and decode it.
395	///
396	/// Returns `None` if this isn't a pre-runtime item, the `id` doesn't match or
397	/// when the decoding fails.
398	pub fn pre_runtime_try_to<T: Decode>(&self, id: &ConsensusEngineId) -> Option<T> {
399		self.as_pre_runtime()
400			.filter(|s| s.0 == *id)
401			.and_then(|mut d| DecodeAll::decode_all(&mut d.1).ok())
402	}
403}
404
405impl<'a> Encode for DigestItemRef<'a> {
406	fn encode(&self) -> Vec<u8> {
407		match *self {
408			Self::Consensus(val, data) => (DigestItemType::Consensus, val, data).encode(),
409			Self::Seal(val, sig) => (DigestItemType::Seal, val, sig).encode(),
410			Self::PreRuntime(val, data) => (DigestItemType::PreRuntime, val, data).encode(),
411			Self::Other(val) => (DigestItemType::Other, val).encode(),
412			Self::RuntimeEnvironmentUpdated => DigestItemType::RuntimeEnvironmentUpdated.encode(),
413		}
414	}
415}
416
417impl<'a> codec::EncodeLike for DigestItemRef<'a> {}
418
419#[cfg(test)]
420mod tests {
421	use super::*;
422
423	#[test]
424	fn should_serialize_digest() {
425		let digest = Digest {
426			logs: vec![DigestItem::Other(vec![1, 2, 3]), DigestItem::Seal(*b"test", vec![1, 2, 3])],
427		};
428
429		assert_eq!(
430			serde_json::to_string(&digest).unwrap(),
431			r#"{"logs":["0x000c010203","0x05746573740c010203"]}"#
432		);
433	}
434
435	#[test]
436	fn digest_item_type_info() {
437		let type_info = DigestItem::type_info();
438		let variants = if let scale_info::TypeDef::Variant(variant) = type_info.type_def {
439			variant.variants
440		} else {
441			panic!("Should be a TypeDef::TypeDefVariant")
442		};
443
444		// ensure that all variants are covered by manual TypeInfo impl
445		let check = |digest_item_type: DigestItemType| {
446			let (variant_name, digest_item) = match digest_item_type {
447				DigestItemType::Other => ("Other", DigestItem::Other(Default::default())),
448				DigestItemType::Consensus =>
449					("Consensus", DigestItem::Consensus(Default::default(), Default::default())),
450				DigestItemType::Seal =>
451					("Seal", DigestItem::Seal(Default::default(), Default::default())),
452				DigestItemType::PreRuntime =>
453					("PreRuntime", DigestItem::PreRuntime(Default::default(), Default::default())),
454				DigestItemType::RuntimeEnvironmentUpdated =>
455					("RuntimeEnvironmentUpdated", DigestItem::RuntimeEnvironmentUpdated),
456			};
457			let encoded = digest_item.encode();
458			let variant = variants
459				.iter()
460				.find(|v| v.name == variant_name)
461				.expect(&format!("Variant {} not found", variant_name));
462
463			assert_eq!(encoded[0], variant.index)
464		};
465
466		check(DigestItemType::Other);
467		check(DigestItemType::Consensus);
468		check(DigestItemType::Seal);
469		check(DigestItemType::PreRuntime);
470		check(DigestItemType::RuntimeEnvironmentUpdated);
471	}
472}