From 969720c2adb4413662e544c8ccf80f953ed31ee5 Mon Sep 17 00:00:00 2001
From: Wei Tang <hi@that.world>
Date: Fri, 24 Apr 2020 17:03:03 +0200
Subject: [PATCH] babe: secondary blocks with VRF (#5501)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* babe: secondary blocks with VRF

* Fix node runtime compile

* Fix test-utils runtime interface

* Fix babe tests

* typo: v == 2

* babe: support online configuration upgrades

* Fix rpc tests

* Fix runtime version tests

* Switch to use NextConfigDescriptor instead of changing runtime interface

* Fix tests

* epoch-changes: map function that allows converting with different epoch types

* Add migration script for the epoch config change

* Fix docs for PrimaryAndSecondaryVRFSlots

* Add docs of `SecondaryVRF` in babe crate

* babe-primitives: Secondary -> SecondaryPlain

* babe-client: Secondary -> SecondaryPlain

* Fix migration tests

* test-utils-runtime: Secondary -> SecondaryPlain

* Fix missing name change in test-utils-runtime

* Fix migration: Epoch should be EpochV0

* Update client/consensus/babe/src/lib.rs

Co-Authored-By: André Silva <123550+andresilva@users.noreply.github.com>

* Fix new epochChanges version

* Fix babe-primitives naming changes

* Fix merge issues in babe-client

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: André Silva <andre.beat@gmail.com>
---
 substrate/bin/node/runtime/src/lib.rs         |  2 +-
 .../client/consensus/babe/rpc/src/lib.rs      |  9 ++-
 .../client/consensus/babe/src/authorship.rs   | 45 ++++++++---
 .../client/consensus/babe/src/aux_schema.rs   |  4 +-
 substrate/client/consensus/babe/src/lib.rs    | 38 +++++++--
 .../client/consensus/babe/src/migration.rs    |  2 +-
 substrate/client/consensus/babe/src/tests.rs  | 10 +--
 .../client/consensus/babe/src/verification.rs | 71 ++++++++++++++---
 substrate/client/rpc/src/state/tests.rs       |  2 +-
 .../primitives/consensus/babe/src/digests.rs  | 49 ++++++++++--
 .../primitives/consensus/babe/src/lib.rs      | 79 ++++++++++++++++++-
 substrate/test-utils/runtime/src/lib.rs       |  6 +-
 12 files changed, 265 insertions(+), 52 deletions(-)

diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index bb8a007ed80..06de573666b 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -805,7 +805,7 @@ impl_runtime_apis! {
 				c: PRIMARY_PROBABILITY,
 				genesis_authorities: Babe::authorities(),
 				randomness: Babe::randomness(),
-				secondary_slots: true,
+				allowed_slots: sp_consensus_babe::AllowedSlots::PrimaryAndSecondaryPlainSlots,
 			}
 		}
 
diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs
index 296b5cf2378..fa5421761ce 100644
--- a/substrate/client/consensus/babe/rpc/src/lib.rs
+++ b/substrate/client/consensus/babe/rpc/src/lib.rs
@@ -123,9 +123,12 @@ impl<B, C, SC> BabeApi for BabeRPCHandler<B, C, SC>
 						PreDigest::Primary { .. } => {
 							claims.entry(key.public()).or_default().primary.push(slot_number);
 						}
-						PreDigest::Secondary { .. } => {
+						PreDigest::SecondaryPlain { .. } => {
 							claims.entry(key.public()).or_default().secondary.push(slot_number);
 						}
+						PreDigest::SecondaryVRF { .. } => {
+							claims.entry(key.public()).or_default().secondary_vrf.push(slot_number);
+						},
 					};
 				}
 			}
@@ -144,6 +147,8 @@ pub struct EpochAuthorship {
 	primary: Vec<u64>,
 	/// the array of secondary slots that can be claimed
 	secondary: Vec<u64>,
+	/// The array of secondary VRF slots that can be claimed.
+	secondary_vrf: Vec<u64>,
 }
 
 /// Errors encountered by the RPC
@@ -236,7 +241,7 @@ mod tests {
 
 		io.extend_with(BabeApi::to_delegate(handler));
 		let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#;
-		let response = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4]}},"id":1}"#;
+		let response = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#;
 
 		assert_eq!(Some(response.into()), io.handle_request_sync(request));
 	}
diff --git a/substrate/client/consensus/babe/src/authorship.rs b/substrate/client/consensus/babe/src/authorship.rs
index 165ff0ca4fe..1b4bf5683ec 100644
--- a/substrate/client/consensus/babe/src/authorship.rs
+++ b/substrate/client/consensus/babe/src/authorship.rs
@@ -21,7 +21,9 @@ use sp_consensus_babe::{
 	AuthorityId, BabeAuthorityWeight, BABE_ENGINE_ID, BABE_VRF_PREFIX,
 	SlotNumber, AuthorityPair,
 };
-use sp_consensus_babe::digests::{PreDigest, PrimaryPreDigest, SecondaryPreDigest};
+use sp_consensus_babe::digests::{
+	PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest,
+};
 use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof};
 use sp_core::{U256, blake2_256};
 use codec::Encode;
@@ -105,10 +107,12 @@ pub(super) fn make_transcript(
 /// to propose.
 fn claim_secondary_slot(
 	slot_number: SlotNumber,
-	authorities: &[(AuthorityId, BabeAuthorityWeight)],
+	epoch: &Epoch,
 	keystore: &KeyStorePtr,
-	randomness: [u8; 32],
+	author_secondary_vrf: bool,
 ) -> Option<(PreDigest, AuthorityPair)> {
+	let Epoch { authorities, randomness, epoch_index, .. } = epoch;
+
 	if authorities.is_empty() {
 		return None;
 	}
@@ -116,7 +120,7 @@ fn claim_secondary_slot(
 	let expected_author = super::authorship::secondary_slot_author(
 		slot_number,
 		authorities,
-		randomness,
+		*randomness,
 	)?;
 
 	let keystore = keystore.read();
@@ -128,10 +132,27 @@ fn claim_secondary_slot(
 		})
 	{
 		if pair.public() == *expected_author {
-			let pre_digest = PreDigest::Secondary(SecondaryPreDigest {
-				slot_number,
-				authority_index: authority_index as u32,
-			});
+			let pre_digest = if author_secondary_vrf {
+				let transcript = super::authorship::make_transcript(
+					randomness,
+					slot_number,
+					*epoch_index,
+				);
+
+				let s = get_keypair(&pair).vrf_sign(transcript);
+
+				PreDigest::SecondaryVRF(SecondaryVRFPreDigest {
+					slot_number,
+					vrf_output: VRFOutput(s.0.to_output()),
+					vrf_proof: VRFProof(s.1),
+					authority_index: authority_index as u32,
+				})
+			} else {
+				PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
+					slot_number,
+					authority_index: authority_index as u32,
+				})
+			};
 
 			return Some((pre_digest, pair));
 		}
@@ -151,12 +172,14 @@ pub fn claim_slot(
 ) -> Option<(PreDigest, AuthorityPair)> {
 	claim_primary_slot(slot_number, epoch, epoch.config.c, keystore)
 		.or_else(|| {
-			if epoch.config.secondary_slots {
+			if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() ||
+				epoch.config.allowed_slots.is_secondary_vrf_slots_allowed()
+			{
 				claim_secondary_slot(
 					slot_number,
-					&epoch.authorities,
+					&epoch,
 					keystore,
-					epoch.randomness,
+					epoch.config.allowed_slots.is_secondary_vrf_slots_allowed(),
 				)
 			} else {
 				None
diff --git a/substrate/client/consensus/babe/src/aux_schema.rs b/substrate/client/consensus/babe/src/aux_schema.rs
index 9907fcbd724..2a3f23981dc 100644
--- a/substrate/client/consensus/babe/src/aux_schema.rs
+++ b/substrate/client/consensus/babe/src/aux_schema.rs
@@ -141,7 +141,7 @@ mod test {
 	use substrate_test_runtime_client;
 	use sp_core::H256;
 	use sp_runtime::traits::NumberFor;
-	use sp_consensus_babe::BabeGenesisConfiguration;
+	use sp_consensus_babe::{AllowedSlots, BabeGenesisConfiguration};
 	use sc_consensus_epochs::{PersistedEpoch, PersistedEpochHeader, EpochHeader};
 	use sp_consensus::Error as ConsensusError;
 	use sc_network_test::Block as TestBlock;
@@ -182,7 +182,7 @@ mod test {
 				c: (3, 10),
 				genesis_authorities: Vec::new(),
 				randomness: Default::default(),
-				secondary_slots: true,
+				allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots,
 			},
 		).unwrap();
 
diff --git a/substrate/client/consensus/babe/src/lib.rs b/substrate/client/consensus/babe/src/lib.rs
index bac4e02897d..e314e7a43c7 100644
--- a/substrate/client/consensus/babe/src/lib.rs
+++ b/substrate/client/consensus/babe/src/lib.rs
@@ -49,6 +49,11 @@
 //!
 //! `blake2_256(epoch_randomness ++ slot_number) % authorities_len`.
 //!
+//! The secondary slots supports either a `SecondaryPlain` or `SecondaryVRF`
+//! variant. Comparing with `SecondaryPlain` variant, the `SecondaryVRF` variant
+//! generates an additional VRF output. The output is not included in beacon
+//! randomness, but can be consumed by parachains.
+//!
 //! The fork choice rule is weight-based, where weight equals the number of
 //! primary blocks in the chain. We will pick the heaviest chain (more primary
 //! blocks) and will go with the longest one in case of a tie.
@@ -64,8 +69,8 @@ pub use sp_consensus_babe::{
 	AuthorityId, AuthorityPair, AuthoritySignature,
 	BabeAuthorityWeight, VRF_OUTPUT_LENGTH,
 	digests::{
-		CompatibleDigestItem, NextEpochDescriptor, NextConfigDescriptor,
-		PreDigest, PrimaryPreDigest, SecondaryPreDigest,
+		CompatibleDigestItem, NextEpochDescriptor, NextConfigDescriptor, PreDigest,
+		PrimaryPreDigest, SecondaryPlainPreDigest,
 	},
 };
 pub use sp_consensus::SyncOracle;
@@ -184,7 +189,7 @@ impl Epoch {
 			randomness: genesis_config.randomness.clone(),
 			config: BabeEpochConfiguration {
 				c: genesis_config.c,
-				secondary_slots: genesis_config.secondary_slots,
+				allowed_slots: genesis_config.allowed_slots,
 			},
 		}
 	}
@@ -279,7 +284,26 @@ impl Config {
 		C: AuxStore + ProvideRuntimeApi<B>, C::Api: BabeApi<B, Error = sp_blockchain::Error>,
 	{
 		trace!(target: "babe", "Getting slot duration");
-		match sc_consensus_slots::SlotDuration::get_or_compute(client, |a, b| a.configuration(b)).map(Self) {
+		match sc_consensus_slots::SlotDuration::get_or_compute(client, |a, b| {
+			let has_api_v1 = a.has_api_with::<dyn BabeApi<B, Error = sp_blockchain::Error>, _>(
+				&b, |v| v == 1,
+			)?;
+			let has_api_v2 = a.has_api_with::<dyn BabeApi<B, Error = sp_blockchain::Error>, _>(
+				&b, |v| v == 2,
+			)?;
+
+			if has_api_v1 {
+				#[allow(deprecated)] {
+					Ok(a.configuration_before_version_2(b)?.into())
+				}
+			} else if has_api_v2 {
+				a.configuration(b)
+			} else {
+				Err(sp_blockchain::Error::VersionInvalid(
+					"Unsupported or invalid BabeApi version".to_string()
+				))
+			}
+		}).map(Self) {
 			Ok(s) => Ok(s),
 			Err(s) => {
 				warn!(target: "babe", "Failed to get slot duration");
@@ -583,7 +607,7 @@ fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<PreDigest, Error<B>>
 	// genesis block doesn't contain a pre digest so let's generate a
 	// dummy one to not break any invariants in the rest of the code
 	if header.number().is_zero() {
-		return Ok(PreDigest::Secondary(SecondaryPreDigest {
+		return Ok(PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
 			slot_number: 0,
 			authority_index: 0,
 		}));
@@ -1044,7 +1068,7 @@ impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client,
 			let epoch_config = next_config_digest.unwrap_or_else(
 				|| viable_epoch.as_ref().config.clone()
 			);
-      
+
 			// restrict info logging during initial sync to avoid spam
 			let log_level = if block.origin == BlockOrigin::NetworkInitialSync {
 				log::Level::Debug
@@ -1053,7 +1077,7 @@ impl<Block, Client, Inner> BlockImport<Block> for BabeBlockImport<Block, Client,
 			};
 
 			log!(target: "babe",
-				log_level, 
+				log_level,
 				"👶 New epoch {} launching at block {} (block slot {} >= start slot {}).",
 				viable_epoch.as_ref().epoch_index,
 				hash,
diff --git a/substrate/client/consensus/babe/src/migration.rs b/substrate/client/consensus/babe/src/migration.rs
index 837704abb1b..2a5a8749cc3 100644
--- a/substrate/client/consensus/babe/src/migration.rs
+++ b/substrate/client/consensus/babe/src/migration.rs
@@ -57,7 +57,7 @@ impl EpochV0 {
 			randomness: self.randomness,
 			config: BabeEpochConfiguration {
 				c: config.c,
-				secondary_slots: config.secondary_slots,
+				allowed_slots: config.allowed_slots,
 			},
 		}
 	}
diff --git a/substrate/client/consensus/babe/src/tests.rs b/substrate/client/consensus/babe/src/tests.rs
index 3c433b40305..5e20c8b5e99 100644
--- a/substrate/client/consensus/babe/src/tests.rs
+++ b/substrate/client/consensus/babe/src/tests.rs
@@ -22,7 +22,7 @@
 use super::*;
 use authorship::claim_slot;
 
-use sp_consensus_babe::{AuthorityPair, SlotNumber};
+use sp_consensus_babe::{AuthorityPair, SlotNumber, AllowedSlots};
 use sc_block_builder::{BlockBuilder, BlockBuilderProvider};
 use sp_consensus::{
 	NoNetwork as DummyOracle, Proposal, RecordProof,
@@ -507,7 +507,7 @@ fn can_author_block() {
 		duration: 100,
 		config: BabeEpochConfiguration {
 			c: (3, 10),
-			secondary_slots: true,
+			allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots,
 		},
 	};
 
@@ -517,7 +517,7 @@ fn can_author_block() {
 		c: (3, 10),
 		genesis_authorities: Vec::new(),
 		randomness: [0; 32],
-		secondary_slots: true,
+		allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots,
 	};
 
 	// with secondary slots enabled it should never be empty
@@ -528,7 +528,7 @@ fn can_author_block() {
 
 	// otherwise with only vrf-based primary slots we might need to try a couple
 	// of times.
-	config.secondary_slots = false;
+	config.allowed_slots = AllowedSlots::PrimarySlots;
 	loop {
 		match claim_slot(i, &epoch, &keystore) {
 			None => i += 1,
@@ -557,7 +557,7 @@ fn propose_and_import_block<Transaction>(
 	let pre_digest = sp_runtime::generic::Digest {
 		logs: vec![
 			Item::babe_pre_digest(
-				PreDigest::Secondary(SecondaryPreDigest {
+				PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
 					authority_index: 0,
 					slot_number,
 				}),
diff --git a/substrate/client/consensus/babe/src/verification.rs b/substrate/client/consensus/babe/src/verification.rs
index 1a3eba45843..1b89bbc643f 100644
--- a/substrate/client/consensus/babe/src/verification.rs
+++ b/substrate/client/consensus/babe/src/verification.rs
@@ -19,7 +19,8 @@ use sp_runtime::{traits::Header, traits::DigestItemFor};
 use sp_core::{Pair, Public};
 use sp_consensus_babe::{AuthoritySignature, SlotNumber, AuthorityPair, AuthorityId};
 use sp_consensus_babe::digests::{
-	PreDigest, PrimaryPreDigest, SecondaryPreDigest, CompatibleDigestItem
+	PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest,
+	CompatibleDigestItem
 };
 use sc_consensus_slots::CheckedHeader;
 use log::{debug, trace};
@@ -28,15 +29,15 @@ use super::authorship::{make_transcript, calculate_primary_threshold, check_prim
 
 /// BABE verification parameters
 pub(super) struct VerificationParams<'a, B: 'a + BlockT> {
-	/// the header being verified.
+	/// The header being verified.
 	pub(super) header: B::Header,
-	/// the pre-digest of the header being verified. this is optional - if prior
+	/// The pre-digest of the header being verified. this is optional - if prior
 	/// verification code had to read it, it can be included here to avoid duplicate
 	/// work.
 	pub(super) pre_digest: Option<PreDigest>,
-	/// the slot number of the current time.
+	/// The slot number of the current time.
 	pub(super) slot_now: SlotNumber,
-	/// epoch descriptor of the epoch this block _should_ be under, if it's valid.
+	/// Epoch descriptor of the epoch this block _should_ be under, if it's valid.
 	pub(super) epoch: &'a Epoch,
 }
 
@@ -102,10 +103,18 @@ pub(super) fn check_header<B: BlockT + Sized>(
 				epoch.config.c,
 			)?;
 		},
-		PreDigest::Secondary(secondary) if epoch.config.secondary_slots => {
-			debug!(target: "babe", "Verifying Secondary block");
-
-			check_secondary_header::<B>(
+		PreDigest::SecondaryPlain(secondary) if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() => {
+			debug!(target: "babe", "Verifying Secondary plain block");
+			check_secondary_plain_header::<B>(
+				pre_hash,
+				secondary,
+				sig,
+				&epoch,
+			)?;
+		},
+		PreDigest::SecondaryVRF(secondary) if epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() => {
+			debug!(target: "babe", "Verifying Secondary VRF block");
+			check_secondary_vrf_header::<B>(
 				pre_hash,
 				secondary,
 				sig,
@@ -179,9 +188,9 @@ fn check_primary_header<B: BlockT + Sized>(
 /// properly signed by the expected authority, which we have a deterministic way
 /// of computing. Additionally, the weight of this block must stay the same
 /// compared to its parent since it is a secondary block.
-fn check_secondary_header<B: BlockT>(
+fn check_secondary_plain_header<B: BlockT>(
 	pre_hash: B::Hash,
-	pre_digest: &SecondaryPreDigest,
+	pre_digest: &SecondaryPlainPreDigest,
 	signature: AuthoritySignature,
 	epoch: &Epoch,
 ) -> Result<(), Error<B>> {
@@ -205,3 +214,43 @@ fn check_secondary_header<B: BlockT>(
 		Err(Error::BadSignature(pre_hash))
 	}
 }
+
+/// Check a secondary VRF slot proposal header.
+fn check_secondary_vrf_header<B: BlockT>(
+	pre_hash: B::Hash,
+	pre_digest: &SecondaryVRFPreDigest,
+	signature: AuthoritySignature,
+	epoch: &Epoch,
+) -> Result<(), Error<B>> {
+	// check the signature is valid under the expected authority and
+	// chain state.
+	let expected_author = secondary_slot_author(
+		pre_digest.slot_number,
+		&epoch.authorities,
+		epoch.randomness,
+	).ok_or_else(|| Error::NoSecondaryAuthorExpected)?;
+
+	let author = &epoch.authorities[pre_digest.authority_index as usize].0;
+
+	if expected_author != author {
+		return Err(Error::InvalidAuthor(expected_author.clone(), author.clone()));
+	}
+
+	if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) {
+		let transcript = make_transcript(
+			&epoch.randomness,
+			pre_digest.slot_number,
+			epoch.epoch_index,
+		);
+
+		schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| {
+			p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof)
+		}).map_err(|s| {
+			babe_err(Error::VRFVerificationFailed(s))
+		})?;
+
+		Ok(())
+	} else {
+		Err(Error::BadSignature(pre_hash))
+	}
+}
diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs
index f9c6982929b..da904f3fdc6 100644
--- a/substrate/client/rpc/src/state/tests.rs
+++ b/substrate/client/rpc/src/state/tests.rs
@@ -432,7 +432,7 @@ fn should_return_runtime_version() {
 	let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\
 		\"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",3],\
 		[\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",2],[\"0x40fe3ad401f8959a\",4],\
-		[\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",1],\
+		[\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",2],\
 		[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\
 		\"transactionVersion\":1}";
 
diff --git a/substrate/primitives/consensus/babe/src/digests.rs b/substrate/primitives/consensus/babe/src/digests.rs
index 291b32cf499..f113912d835 100644
--- a/substrate/primitives/consensus/babe/src/digests.rs
+++ b/substrate/primitives/consensus/babe/src/digests.rs
@@ -65,7 +65,7 @@ impl TryFrom<RawPrimaryPreDigest> for PrimaryPreDigest {
 
 /// BABE secondary slot assignment pre-digest.
 #[derive(Clone, RuntimeDebug, Encode, Decode)]
-pub struct SecondaryPreDigest {
+pub struct SecondaryPlainPreDigest {
 	/// Authority index
 	///
 	/// This is not strictly-speaking necessary, since the secondary slots
@@ -77,6 +77,37 @@ pub struct SecondaryPreDigest {
 	pub slot_number: SlotNumber,
 }
 
+/// BABE secondary deterministic slot assignment with VRF outputs.
+#[derive(Clone, RuntimeDebug, Encode, Decode)]
+pub struct RawSecondaryVRFPreDigest<VRFOutput=schnorrkel::RawVRFOutput, VRFProof=schnorrkel::RawVRFProof> {
+	/// Authority index
+	pub authority_index: super::AuthorityIndex,
+	/// Slot number
+	pub slot_number: SlotNumber,
+	/// VRF output
+	pub vrf_output: VRFOutput,
+	/// VRF proof
+	pub vrf_proof: VRFProof,
+}
+
+#[cfg(feature = "std")]
+/// BABE secondary slot assignment with VRF outputs pre-digest, for std environment.
+pub type SecondaryVRFPreDigest = RawSecondaryVRFPreDigest<schnorrkel::VRFOutput, schnorrkel::VRFProof>;
+
+#[cfg(feature = "std")]
+impl TryFrom<RawSecondaryVRFPreDigest> for SecondaryVRFPreDigest {
+	type Error = SignatureError;
+
+	fn try_from(raw: RawSecondaryVRFPreDigest) -> Result<SecondaryVRFPreDigest, SignatureError> {
+		Ok(SecondaryVRFPreDigest {
+			authority_index: raw.authority_index,
+			slot_number: raw.slot_number,
+			vrf_output: raw.vrf_output.try_into()?,
+			vrf_proof: raw.vrf_proof.try_into()?,
+		})
+	}
+}
+
 /// A BABE pre-runtime digest. This contains all data required to validate a
 /// block and for the BABE runtime module. Slots can be assigned to a primary
 /// (VRF based) and to a secondary (slot number based).
@@ -87,7 +118,10 @@ pub enum RawPreDigest<VRFOutput=schnorrkel::RawVRFOutput, VRFProof=schnorrkel::R
 	Primary(RawPrimaryPreDigest<VRFOutput, VRFProof>),
 	/// A secondary deterministic slot assignment.
 	#[codec(index = "2")]
-	Secondary(SecondaryPreDigest),
+	SecondaryPlain(SecondaryPlainPreDigest),
+	/// A secondary deterministic slot assignment with VRF outputs.
+	#[codec(index = "3")]
+	SecondaryVRF(RawSecondaryVRFPreDigest<VRFOutput, VRFProof>),
 }
 
 #[cfg(feature = "std")]
@@ -99,7 +133,8 @@ impl<VRFOutput, VRFProof> RawPreDigest<VRFOutput, VRFProof> {
 	pub fn authority_index(&self) -> AuthorityIndex {
 		match self {
 			RawPreDigest::Primary(primary) => primary.authority_index,
-			RawPreDigest::Secondary(secondary) => secondary.authority_index,
+			RawPreDigest::SecondaryPlain(secondary) => secondary.authority_index,
+			RawPreDigest::SecondaryVRF(secondary) => secondary.authority_index,
 		}
 	}
 
@@ -107,7 +142,8 @@ impl<VRFOutput, VRFProof> RawPreDigest<VRFOutput, VRFProof> {
 	pub fn slot_number(&self) -> SlotNumber {
 		match self {
 			RawPreDigest::Primary(primary) => primary.slot_number,
-			RawPreDigest::Secondary(secondary) => secondary.slot_number,
+			RawPreDigest::SecondaryPlain(secondary) => secondary.slot_number,
+			RawPreDigest::SecondaryVRF(secondary) => secondary.slot_number,
 		}
 	}
 
@@ -116,7 +152,7 @@ impl<VRFOutput, VRFProof> RawPreDigest<VRFOutput, VRFProof> {
 	pub fn added_weight(&self) -> crate::BabeBlockWeight {
 		match self {
 			RawPreDigest::Primary(_) => 1,
-			RawPreDigest::Secondary(_) => 0,
+			RawPreDigest::SecondaryPlain(_) | RawPreDigest::SecondaryVRF(_) => 0,
 		}
 	}
 }
@@ -128,7 +164,8 @@ impl TryFrom<RawPreDigest> for PreDigest {
 	fn try_from(raw: RawPreDigest) -> Result<PreDigest, SignatureError> {
 		Ok(match raw {
 			RawPreDigest::Primary(primary) => PreDigest::Primary(primary.try_into()?),
-			RawPreDigest::Secondary(secondary) => PreDigest::Secondary(secondary),
+			RawPreDigest::SecondaryPlain(secondary) => PreDigest::SecondaryPlain(secondary),
+			RawPreDigest::SecondaryVRF(secondary) => PreDigest::SecondaryVRF(secondary.try_into()?),
 		})
 	}
 }
diff --git a/substrate/primitives/consensus/babe/src/lib.rs b/substrate/primitives/consensus/babe/src/lib.rs
index 37109b69be7..7cf9483e6f6 100644
--- a/substrate/primitives/consensus/babe/src/lib.rs
+++ b/substrate/primitives/consensus/babe/src/lib.rs
@@ -95,7 +95,7 @@ pub enum ConsensusLog {
 
 /// Configuration data used by the BABE consensus engine.
 #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
-pub struct BabeGenesisConfiguration {
+pub struct BabeGenesisConfigurationV1 {
 	/// The slot duration in milliseconds for BABE. Currently, only
 	/// the value provided by this type at genesis will be used.
 	///
@@ -124,6 +124,76 @@ pub struct BabeGenesisConfiguration {
 	pub secondary_slots: bool,
 }
 
+impl From<BabeGenesisConfigurationV1> for BabeGenesisConfiguration {
+	fn from(v1: BabeGenesisConfigurationV1) -> Self {
+		Self {
+			slot_duration: v1.slot_duration,
+			epoch_length: v1.epoch_length,
+			c: v1.c,
+			genesis_authorities: v1.genesis_authorities,
+			randomness: v1.randomness,
+			allowed_slots: if v1.secondary_slots {
+				AllowedSlots::PrimaryAndSecondaryPlainSlots
+			} else {
+				AllowedSlots::PrimarySlots
+			},
+		}
+	}
+}
+
+/// Configuration data used by the BABE consensus engine.
+#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
+pub struct BabeGenesisConfiguration {
+	/// The slot duration in milliseconds for BABE. Currently, only
+	/// the value provided by this type at genesis will be used.
+	///
+	/// Dynamic slot duration may be supported in the future.
+	pub slot_duration: u64,
+
+	/// The duration of epochs in slots.
+	pub epoch_length: SlotNumber,
+
+	/// A constant value that is used in the threshold calculation formula.
+	/// Expressed as a rational where the first member of the tuple is the
+	/// numerator and the second is the denominator. The rational should
+	/// represent a value between 0 and 1.
+	/// In the threshold formula calculation, `1 - c` represents the probability
+	/// of a slot being empty.
+	pub c: (u64, u64),
+
+	/// The authorities for the genesis epoch.
+	pub genesis_authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
+
+	/// The randomness for the genesis epoch.
+	pub randomness: Randomness,
+
+	/// Type of allowed slots.
+	pub allowed_slots: AllowedSlots,
+}
+
+/// Types of allowed slots.
+#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
+pub enum AllowedSlots {
+	/// Only allow primary slots.
+	PrimarySlots,
+	/// Allow primary and secondary plain slots.
+	PrimaryAndSecondaryPlainSlots,
+	/// Allow primary and secondary VRF slots.
+	PrimaryAndSecondaryVRFSlots,
+}
+
+impl AllowedSlots {
+	/// Whether plain secondary slots are allowed.
+	pub fn is_secondary_plain_slots_allowed(&self) -> bool {
+		*self == Self::PrimaryAndSecondaryPlainSlots
+	}
+
+	/// Whether VRF secondary slots are allowed.
+	pub fn is_secondary_vrf_slots_allowed(&self) -> bool {
+		*self == Self::PrimaryAndSecondaryVRFSlots
+	}
+}
+
 #[cfg(feature = "std")]
 impl sp_consensus::SlotData for BabeGenesisConfiguration {
 	fn slot_duration(&self) -> u64 {
@@ -146,15 +216,20 @@ pub struct BabeEpochConfiguration {
 
 	/// Whether this chain should run with secondary slots, which are assigned
 	/// in round-robin manner.
-	pub secondary_slots: bool,
+	pub allowed_slots: AllowedSlots,
 }
 
 sp_api::decl_runtime_apis! {
 	/// API necessary for block authorship with BABE.
+	#[api_version(2)]
 	pub trait BabeApi {
 		/// Return the genesis configuration for BABE. The configuration is only read on genesis.
 		fn configuration() -> BabeGenesisConfiguration;
 
+		/// Return the configuration for BABE. Version 1.
+		#[changed_in(2)]
+		fn configuration() -> BabeGenesisConfigurationV1;
+
 		/// Returns the slot number that started the current epoch.
 		fn current_epoch_start() -> SlotNumber;
 	}
diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs
index c5c438e4de5..94d3e8db3e8 100644
--- a/substrate/test-utils/runtime/src/lib.rs
+++ b/substrate/test-utils/runtime/src/lib.rs
@@ -52,7 +52,7 @@ use sp_inherents::{CheckInherentsResult, InherentData};
 use cfg_if::cfg_if;
 
 // Ensure Babe and Aura use the same crypto to simplify things a bit.
-pub use sp_consensus_babe::{AuthorityId, SlotNumber};
+pub use sp_consensus_babe::{AuthorityId, SlotNumber, AllowedSlots};
 pub type AuraId = sp_consensus_aura::sr25519::AuthorityId;
 
 // Include the WASM binary
@@ -641,7 +641,7 @@ cfg_if! {
 						genesis_authorities: system::authorities()
 							.into_iter().map(|x|(x, 1)).collect(),
 						randomness: <pallet_babe::Module<Runtime>>::randomness(),
-						secondary_slots: true,
+						allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots,
 					}
 				}
 
@@ -835,7 +835,7 @@ cfg_if! {
 						genesis_authorities: system::authorities()
 							.into_iter().map(|x|(x, 1)).collect(),
 						randomness: <pallet_babe::Module<Runtime>>::randomness(),
-						secondary_slots: true,
+						allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots,
 					}
 				}
 
-- 
GitLab