Skip to main content

subsoil/consensus/beefy/
payload.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7use crate::runtime::traits::Block;
8use alloc::{vec, vec::Vec};
9use codec::{Decode, DecodeWithMemTracking, Encode};
10use scale_info::TypeInfo;
11
12/// Id of different payloads in the [`crate::Commitment`] data.
13pub type BeefyPayloadId = [u8; 2];
14
15/// Registry of all known [`BeefyPayloadId`].
16pub mod known_payloads {
17	use super::BeefyPayloadId;
18
19	/// A [`Payload`](super::Payload) identifier for Merkle Mountain Range root hash.
20	///
21	/// Encoded value should contain a [`crate::MmrRootHash`] type (i.e. 32-bytes hash).
22	pub const MMR_ROOT_ID: BeefyPayloadId = *b"mh";
23}
24
25/// A BEEFY payload type allowing for future extensibility of adding additional kinds of payloads.
26///
27/// The idea is to store a vector of SCALE-encoded values with an extra identifier.
28/// Identifiers MUST be sorted by the [`BeefyPayloadId`] to allow efficient lookup of expected
29/// value. Duplicated identifiers are disallowed. It's okay for different implementations to only
30/// support a subset of possible values.
31#[derive(
32	Decode,
33	DecodeWithMemTracking,
34	Encode,
35	Debug,
36	PartialEq,
37	Eq,
38	Clone,
39	Ord,
40	PartialOrd,
41	Hash,
42	TypeInfo,
43)]
44pub struct Payload(Vec<(BeefyPayloadId, Vec<u8>)>);
45
46impl Payload {
47	/// Construct a new payload given an initial value
48	pub fn from_single_entry(id: BeefyPayloadId, value: Vec<u8>) -> Self {
49		Self(vec![(id, value)])
50	}
51
52	/// Returns a raw payload under given `id`.
53	///
54	/// If the [`BeefyPayloadId`] is not found in the payload `None` is returned.
55	pub fn get_raw(&self, id: &BeefyPayloadId) -> Option<&Vec<u8>> {
56		let index = self.0.binary_search_by(|probe| probe.0.cmp(id)).ok()?;
57		Some(&self.0[index].1)
58	}
59
60	/// Returns all the raw payloads under given `id`.
61	pub fn get_all_raw<'a>(
62		&'a self,
63		id: &'a BeefyPayloadId,
64	) -> impl Iterator<Item = &'a Vec<u8>> + 'a {
65		self.0
66			.iter()
67			.filter_map(move |probe| if &probe.0 != id { return None } else { Some(&probe.1) })
68	}
69
70	/// Returns a decoded payload value under given `id`.
71	///
72	/// In case the value is not there, or it cannot be decoded `None` is returned.
73	pub fn get_decoded<T: Decode>(&self, id: &BeefyPayloadId) -> Option<T> {
74		self.get_raw(id).and_then(|raw| T::decode(&mut &raw[..]).ok())
75	}
76
77	/// Returns all decoded payload values under given `id`.
78	pub fn get_all_decoded<'a, T: Decode>(
79		&'a self,
80		id: &'a BeefyPayloadId,
81	) -> impl Iterator<Item = Option<T>> + 'a {
82		self.get_all_raw(id).map(|raw| T::decode(&mut &raw[..]).ok())
83	}
84
85	/// Push a `Vec<u8>` with a given id into the payload vec.
86	/// This method will internally sort the payload vec after every push.
87	///
88	/// Returns self to allow for daisy chaining.
89	pub fn push_raw(mut self, id: BeefyPayloadId, value: Vec<u8>) -> Self {
90		self.0.push((id, value));
91		self.0.sort_by_key(|(id, _)| *id);
92		self
93	}
94}
95
96/// Trait for custom BEEFY payload providers.
97pub trait PayloadProvider<B: Block> {
98	/// Provide BEEFY payload if available for `header`.
99	fn payload(&self, header: &B::Header) -> Option<Payload>;
100}
101
102#[cfg(test)]
103mod tests {
104	use super::*;
105
106	#[test]
107	fn payload_methods_work_as_expected() {
108		let id1: BeefyPayloadId = *b"hw";
109		let msg1: String = "1. Hello World!".to_string();
110		let id2: BeefyPayloadId = *b"yb";
111		let msg2: String = "2. Yellow Board!".to_string();
112		let id3: BeefyPayloadId = *b"cs";
113		let msg3: String = "3. Cello Cord!".to_string();
114
115		let payload = Payload::from_single_entry(id1, msg1.encode())
116			.push_raw(id2, msg2.encode())
117			.push_raw(id3, msg3.encode());
118
119		assert_eq!(payload.get_decoded(&id1), Some(msg1));
120		assert_eq!(payload.get_decoded(&id2), Some(msg2));
121		assert_eq!(payload.get_raw(&id3), Some(&msg3.encode()));
122		assert_eq!(payload.get_raw(&known_payloads::MMR_ROOT_ID), None);
123	}
124}