mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 13:31:10 +00:00
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:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user