referrerpolicy=no-referrer-when-downgrade

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