Make BEEFY payload extensible (#10307)

* make BEEFY payload extensible

* cargo fmt

* cargo fmt

* remove generic payload param in beefy-primitives

* cargo fmt

* Apply suggestions from code review

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* adds Paylaod Type

* remove hex

* fix tests

* Apply suggestions from code review

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* use binary_search_by to sort

* Payload::new()

* fix tests

* Apply suggestions from code review

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* fix tests

* cargo fmt

* fix get_decoded

* fix test

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
This commit is contained in:
Seun Lanlege
2021-12-01 12:45:36 +01:00
committed by GitHub
parent db37fb04e8
commit a18749d315
9 changed files with 152 additions and 99 deletions
+91 -31
View File
@@ -15,10 +15,65 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use codec::{Decode, Encode};
use sp_std::{cmp, prelude::*};
use crate::{crypto::Signature, ValidatorSetId};
/// Id of different payloads in the [`Commitment`] data
pub type BeefyPayloadId = [u8; 2];
/// Registry of all known [`BeefyPayloadId`].
pub mod known_payload_ids {
use crate::BeefyPayloadId;
/// A [`Payload`] identifier for Merkle Mountain Range root hash.
///
/// Encoded value should contain a [`beefy_primitives::MmrRootHash`] type (i.e. 32-bytes hash).
pub const MMR_ROOT_ID: BeefyPayloadId = *b"mh";
}
/// A BEEFY payload type allowing for future extensibility of adding additional kinds of payloads.
///
/// The idea is to store a vector of SCALE-encoded values with an extra identifier.
/// Identifiers MUST be sorted by the [`BeefyPayloadId`] to allow efficient lookup of expected
/// value. Duplicated identifiers are disallowed. It's okay for different implementations to only
/// support a subset of possible values.
#[derive(Decode, Encode, Debug, PartialEq, Eq, Clone, Ord, PartialOrd, Hash)]
pub struct Payload(Vec<(BeefyPayloadId, Vec<u8>)>);
impl Payload {
/// Construct a new payload given an initial vallue
pub fn new(id: BeefyPayloadId, value: Vec<u8>) -> Self {
Self(vec![(id, value)])
}
/// Returns a raw payload under given `id`.
///
/// If the [`BeefyPayloadId`] is not found in the payload `None` is returned.
pub fn get_raw(&self, id: &BeefyPayloadId) -> Option<&Vec<u8>> {
let index = self.0.binary_search_by(|probe| probe.0.cmp(id)).ok()?;
Some(&self.0[index].1)
}
/// Returns a decoded payload value under given `id`.
///
/// In case the value is not there or it cannot be decoded does not match `None` is returned.
pub fn get_decoded<T: Decode>(&self, id: &BeefyPayloadId) -> Option<T> {
self.get_raw(id).and_then(|raw| T::decode(&mut &raw[..]).ok())
}
/// Push a `Vec<u8>` with a given id into the payload vec.
/// This method will internally sort the payload vec after every push.
///
/// Returns self to allow for daisy chaining.
pub fn push_raw(mut self, id: BeefyPayloadId, value: Vec<u8>) -> Self {
self.0.push((id, value));
self.0.sort_by_key(|(id, _)| *id);
self
}
}
/// A commitment signed by GRANDPA validators as part of BEEFY protocol.
///
/// The commitment contains a [payload](Commitment::payload) extracted from the finalized block at
@@ -26,16 +81,17 @@ use crate::{crypto::Signature, ValidatorSetId};
/// GRANDPA validators collect signatures on commitments and a stream of such signed commitments
/// (see [SignedCommitment]) forms the BEEFY protocol.
#[derive(Clone, Debug, PartialEq, Eq, codec::Encode, codec::Decode)]
pub struct Commitment<TBlockNumber, TPayload> {
/// The payload being signed.
pub struct Commitment<TBlockNumber> {
/// A collection of payloads to be signed, see [`Payload`] for details.
///
/// This should be some form of cumulative representation of the chain (think MMR root hash).
/// The payload should also contain some details that allow the light client to verify next
/// validator set. The protocol does not enforce any particular format of this data,
/// nor how often it should be present in commitments, however the light client has to be
/// provided with full validator set whenever it performs the transition (i.e. importing first
/// block with [validator_set_id](Commitment::validator_set_id) incremented).
pub payload: TPayload,
/// One of the payloads should be some form of cumulative representation of the chain (think
/// MMR root hash). Additionally one of the payloads should also contain some details that
/// allow the light client to verify next validator set. The protocol does not enforce any
/// particular format of this data, nor how often it should be present in commitments, however
/// the light client has to be provided with full validator set whenever it performs the
/// transition (i.e. importing first block with
/// [validator_set_id](Commitment::validator_set_id) incremented).
pub payload: Payload,
/// Finalized block number this commitment is for.
///
@@ -57,20 +113,18 @@ pub struct Commitment<TBlockNumber, TPayload> {
pub validator_set_id: ValidatorSetId,
}
impl<TBlockNumber, TPayload> cmp::PartialOrd for Commitment<TBlockNumber, TPayload>
impl<TBlockNumber> cmp::PartialOrd for Commitment<TBlockNumber>
where
TBlockNumber: cmp::Ord,
TPayload: cmp::Eq,
{
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<TBlockNumber, TPayload> cmp::Ord for Commitment<TBlockNumber, TPayload>
impl<TBlockNumber> cmp::Ord for Commitment<TBlockNumber>
where
TBlockNumber: cmp::Ord,
TPayload: cmp::Eq,
{
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.validator_set_id
@@ -81,9 +135,9 @@ where
/// A commitment with matching GRANDPA validators' signatures.
#[derive(Clone, Debug, PartialEq, Eq, codec::Encode, codec::Decode)]
pub struct SignedCommitment<TBlockNumber, TPayload> {
pub struct SignedCommitment<TBlockNumber> {
/// The commitment signatures are collected for.
pub commitment: Commitment<TBlockNumber, TPayload>,
pub commitment: Commitment<TBlockNumber>,
/// GRANDPA validators' signatures for the commitment.
///
/// The length of this `Vec` must match number of validators in the current set (see
@@ -91,7 +145,7 @@ pub struct SignedCommitment<TBlockNumber, TPayload> {
pub signatures: Vec<Option<Signature>>,
}
impl<TBlockNumber, TPayload> SignedCommitment<TBlockNumber, TPayload> {
impl<TBlockNumber> SignedCommitment<TBlockNumber> {
/// Return the number of collected signatures.
pub fn no_of_signatures(&self) -> usize {
self.signatures.iter().filter(|x| x.is_some()).count()
@@ -102,10 +156,10 @@ impl<TBlockNumber, TPayload> SignedCommitment<TBlockNumber, TPayload> {
/// to the block justifications for the block for which the signed commitment
/// has been generated.
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
pub enum VersionedCommitment<N, P> {
pub enum VersionedCommitment<N> {
#[codec(index = 1)]
/// Current active version
V1(SignedCommitment<N, P>),
V1(SignedCommitment<N>),
}
#[cfg(test)]
@@ -119,9 +173,9 @@ mod tests {
use crate::{crypto, KEY_TYPE};
type TestCommitment = Commitment<u128, String>;
type TestSignedCommitment = SignedCommitment<u128, String>;
type TestVersionedCommitment = VersionedCommitment<u128, String>;
type TestCommitment = Commitment<u128>;
type TestSignedCommitment = SignedCommitment<u128>;
type TestVersionedCommitment = VersionedCommitment<u128>;
// The mock signatures are equivalent to the ones produced by the BEEFY keystore
fn mock_signatures() -> (crypto::Signature, crypto::Signature) {
@@ -148,8 +202,9 @@ mod tests {
#[test]
fn commitment_encode_decode() {
// given
let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 };
Commitment { payload, block_number: 5, validator_set_id: 0 };
// when
let encoded = codec::Encode::encode(&commitment);
@@ -160,7 +215,7 @@ mod tests {
assert_eq!(
encoded,
hex_literal::hex!(
"3048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000"
"046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000"
)
);
}
@@ -168,8 +223,9 @@ mod tests {
#[test]
fn signed_commitment_encode_decode() {
// given
let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 };
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
@@ -187,10 +243,11 @@ mod tests {
assert_eq!(
encoded,
hex_literal::hex!(
"3048656c6c6f20576f726c64210500000000000000000000000000000000000000000000001000
0001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d
10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a
2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00"
"046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000
10000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321
f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a86cdd9aaa
cdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a
0046395a71681be3d0c2a00"
)
);
}
@@ -198,8 +255,9 @@ mod tests {
#[test]
fn signed_commitment_count_signatures() {
// given
let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 };
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
@@ -222,7 +280,8 @@ mod tests {
block_number: u128,
validator_set_id: crate::ValidatorSetId,
) -> TestCommitment {
Commitment { payload: "Hello World!".into(), block_number, validator_set_id }
let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode());
Commitment { payload, block_number, validator_set_id }
}
// given
@@ -241,8 +300,9 @@ mod tests {
#[test]
fn versioned_commitment_encode_decode() {
let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload: "Hello World!".into(), block_number: 5, validator_set_id: 0 };
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();