From a297e447f2605ddd2b018fbd4861b758fabd51e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 15 Oct 2020 10:32:12 +0200 Subject: [PATCH] babe: make secondary slot randomness available on-chain (#7053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * babe: make secondary slot randomness available on-chain * babe: extract out vrf_output function * babe: add missing comment * babe: fix incorrectly storing primary randomness * babe: add test for onchain author vrf * babe: fix reviewer nits * runtime: bump spec_version * babe: remove outer Option for AuthorVrfRandomness * babe: fix reviewer nits on doc strings * babe: move make_vrf_output to mock.rs * babe: cleanup docs * babe: kill ephemeral entry instead of take * babe: use type alias for maybe randomness Co-authored-by: André Silva --- substrate/bin/node/runtime/src/lib.rs | 4 +- substrate/frame/babe/src/lib.rs | 76 +++++++++------ substrate/frame/babe/src/mock.rs | 35 ++++++- substrate/frame/babe/src/tests.rs | 96 ++++++++++++++++--- .../primitives/consensus/babe/src/digests.rs | 9 ++ 5 files changed, 173 insertions(+), 47 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 2d150e22bd..04381d50a2 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -112,8 +112,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 259, - impl_version: 1, + spec_version: 260, + impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, }; diff --git a/substrate/frame/babe/src/lib.rs b/substrate/frame/babe/src/lib.rs index 3f13fe7e03..efada5f18c 100644 --- a/substrate/frame/babe/src/lib.rs +++ b/substrate/frame/babe/src/lib.rs @@ -210,6 +210,11 @@ decl_storage! { /// if per-block initialization has already been called for current block. Initialized get(fn initialized): Option; + /// Temporary value (cleared at block finalization) that includes the VRF output generated + /// at this block. This field should always be populated during block processing unless + /// secondary plain slots are enabled (which don't contain a VRF output). + AuthorVrfRandomness get(fn author_vrf_randomness): MaybeRandomness; + /// How late the current block is compared to its parent. /// /// This entry is populated as part of block execution and is cleaned up @@ -255,6 +260,9 @@ decl_module! { Self::deposit_randomness(&randomness); } + // The stored author generated VRF output is ephemeral. + AuthorVrfRandomness::kill(); + // remove temporary "environment" entry from storage Lateness::::kill(); } @@ -517,7 +525,9 @@ impl Module { }) .next(); - let maybe_randomness: Option = maybe_pre_digest.and_then(|digest| { + let is_primary = matches!(maybe_pre_digest, Some(PreDigest::Primary(..))); + + let maybe_randomness: MaybeRandomness = maybe_pre_digest.and_then(|digest| { // on the first non-zero block (i.e. block #1) // this is where the first epoch (epoch #0) actually starts. // we need to adjust internal storage accordingly. @@ -546,38 +556,44 @@ impl Module { Lateness::::put(lateness); CurrentSlot::put(current_slot); - if let PreDigest::Primary(primary) = digest { - // place the VRF output into the `Initialized` storage item - // and it'll be put onto the under-construction randomness - // later, once we've decided which epoch this block is in. - // - // Reconstruct the bytes of VRFInOut using the authority id. - Authorities::get() - .get(primary.authority_index as usize) - .and_then(|author| { - schnorrkel::PublicKey::from_bytes(author.0.as_slice()).ok() - }) - .and_then(|pubkey| { - let transcript = sp_consensus_babe::make_transcript( - &Self::randomness(), - current_slot, - EpochIndex::get(), - ); + let authority_index = digest.authority_index(); - primary.vrf_output.0.attach_input_hash( - &pubkey, - transcript - ).ok() - }) - .map(|inout| { - inout.make_bytes(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT) - }) - } else { - None - } + // Extract out the VRF output if we have it + digest + .vrf_output() + .and_then(|vrf_output| { + // Reconstruct the bytes of VRFInOut using the authority id. + Authorities::get() + .get(authority_index as usize) + .and_then(|author| { + schnorrkel::PublicKey::from_bytes(author.0.as_slice()).ok() + }) + .and_then(|pubkey| { + let transcript = sp_consensus_babe::make_transcript( + &Self::randomness(), + current_slot, + EpochIndex::get(), + ); + + vrf_output.0.attach_input_hash( + &pubkey, + transcript + ).ok() + }) + .map(|inout| { + inout.make_bytes(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT) + }) + }) }); - Initialized::put(maybe_randomness); + // For primary VRF output we place it in the `Initialized` storage + // item and it'll be put onto the under-construction randomness later, + // once we've decided which epoch this block is in. + Initialized::put(if is_primary { maybe_randomness } else { None }); + + // Place either the primary or secondary VRF output into the + // `AuthorVrfRandomness` storage item. + AuthorVrfRandomness::put(maybe_randomness); // enact epoch change, if necessary. T::EpochChangeTrigger::trigger::(now) diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index b6dbade24e..9f00a4ddfc 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -32,7 +32,7 @@ use frame_support::{ weights::Weight, }; use sp_io; -use sp_core::{H256, U256, crypto::{KeyTypeId, Pair}}; +use sp_core::{H256, U256, crypto::{IsWrappedBy, KeyTypeId, Pair}}; use sp_consensus_babe::{AuthorityId, AuthorityPair, SlotNumber}; use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use sp_staking::SessionIndex; @@ -330,6 +330,39 @@ pub fn make_secondary_plain_pre_digest( Digest { logs: vec![log] } } +pub fn make_secondary_vrf_pre_digest( + authority_index: sp_consensus_babe::AuthorityIndex, + slot_number: sp_consensus_babe::SlotNumber, + vrf_output: VRFOutput, + vrf_proof: VRFProof, +) -> Digest { + let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryVRF( + sp_consensus_babe::digests::SecondaryVRFPreDigest { + authority_index, + slot_number, + vrf_output, + vrf_proof, + } + ); + let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); + Digest { logs: vec![log] } +} + +pub fn make_vrf_output( + slot_number: u64, + pair: &sp_consensus_babe::AuthorityPair +) -> (VRFOutput, VRFProof, [u8; 32]) { + let pair = sp_core::sr25519::Pair::from_ref(pair).as_ref(); + let transcript = sp_consensus_babe::make_transcript(&Babe::randomness(), slot_number, 0); + let vrf_inout = pair.vrf_sign(transcript); + let vrf_randomness: sp_consensus_vrf::schnorrkel::Randomness = vrf_inout.0 + .make_bytes::<[u8; 32]>(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT); + let vrf_output = VRFOutput(vrf_inout.0.to_output()); + let vrf_proof = VRFProof(vrf_inout.1); + + (vrf_output, vrf_proof, vrf_randomness) +} + pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities { new_test_ext_with_pairs(authorities_len).1 } diff --git a/substrate/frame/babe/src/tests.rs b/substrate/frame/babe/src/tests.rs index 6cfa7e41df..06bf84614c 100644 --- a/substrate/frame/babe/src/tests.rs +++ b/substrate/frame/babe/src/tests.rs @@ -26,8 +26,7 @@ use frame_support::{ use mock::*; use pallet_session::ShouldEndSession; use sp_consensus_babe::AllowedSlots; -use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; -use sp_core::crypto::{IsWrappedBy, Pair}; +use sp_core::crypto::Pair; const EMPTY_RANDOMNESS: [u8; 32] = [ 74, 25, 49, 128, 53, 97, 244, 49, @@ -64,18 +63,7 @@ fn first_block_epoch_zero_start() { ext.execute_with(|| { let genesis_slot = 100; - - let pair = sp_core::sr25519::Pair::from_ref(&pairs[0]).as_ref(); - let transcript = sp_consensus_babe::make_transcript( - &Babe::randomness(), - genesis_slot, - 0, - ); - let vrf_inout = pair.vrf_sign(transcript); - let vrf_randomness: sp_consensus_vrf::schnorrkel::Randomness = vrf_inout.0 - .make_bytes::<[u8; 32]>(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT); - let vrf_output = VRFOutput(vrf_inout.0.to_output()); - let vrf_proof = VRFProof(vrf_inout.1); + let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); let first_vrf = vrf_output; let pre_digest = make_pre_digest( @@ -100,6 +88,7 @@ fn first_block_epoch_zero_start() { assert_eq!(Babe::genesis_slot(), genesis_slot); assert_eq!(Babe::current_slot(), genesis_slot); assert_eq!(Babe::epoch_index(), 0); + assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); Babe::on_finalize(1); let header = System::finalize(); @@ -107,6 +96,7 @@ fn first_block_epoch_zero_start() { assert_eq!(SegmentIndex::get(), 0); assert_eq!(UnderConstruction::get(0), vec![vrf_randomness]); assert_eq!(Babe::randomness(), [0; 32]); + assert_eq!(Babe::author_vrf_randomness(), None); assert_eq!(NextRandomness::get(), [0; 32]); assert_eq!(header.digest.logs.len(), 2); @@ -126,6 +116,84 @@ fn first_block_epoch_zero_start() { }) } +#[test] +fn author_vrf_output_for_primary() { + let (pairs, mut ext) = new_test_ext_with_pairs(1); + + ext.execute_with(|| { + let genesis_slot = 10; + let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); + let primary_pre_digest = make_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + + System::initialize( + &1, + &Default::default(), + &Default::default(), + &primary_pre_digest, + Default::default(), + ); + assert_eq!(Babe::author_vrf_randomness(), None); + + Babe::do_initialize(1); + assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + + Babe::on_finalize(1); + System::finalize(); + assert_eq!(Babe::author_vrf_randomness(), None); + }) +} + +#[test] +fn author_vrf_output_for_secondary_vrf() { + let (pairs, mut ext) = new_test_ext_with_pairs(1); + + ext.execute_with(|| { + let genesis_slot = 10; + let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); + let secondary_vrf_pre_digest = make_secondary_vrf_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + + System::initialize( + &1, + &Default::default(), + &Default::default(), + &secondary_vrf_pre_digest, + Default::default(), + ); + assert_eq!(Babe::author_vrf_randomness(), None); + + Babe::do_initialize(1); + assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + + Babe::on_finalize(1); + System::finalize(); + assert_eq!(Babe::author_vrf_randomness(), None); + }) +} + +#[test] +fn no_author_vrf_output_for_secondary_plain() { + new_test_ext(1).execute_with(|| { + let genesis_slot = 10; + let secondary_plain_pre_digest = make_secondary_plain_pre_digest(0, genesis_slot); + + System::initialize( + &1, + &Default::default(), + &Default::default(), + &secondary_plain_pre_digest, + Default::default(), + ); + assert_eq!(Babe::author_vrf_randomness(), None); + + Babe::do_initialize(1); + assert_eq!(Babe::author_vrf_randomness(), None); + + Babe::on_finalize(1); + System::finalize(); + assert_eq!(Babe::author_vrf_randomness(), None); + }) +} + #[test] fn authority_index() { new_test_ext(4).execute_with(|| { diff --git a/substrate/primitives/consensus/babe/src/digests.rs b/substrate/primitives/consensus/babe/src/digests.rs index a680ca0656..f7ae560aff 100644 --- a/substrate/primitives/consensus/babe/src/digests.rs +++ b/substrate/primitives/consensus/babe/src/digests.rs @@ -110,6 +110,15 @@ impl PreDigest { PreDigest::SecondaryPlain(_) | PreDigest::SecondaryVRF(_) => 0, } } + + /// Returns the VRF output, if it exists. + pub fn vrf_output(&self) -> Option<&VRFOutput> { + match self { + PreDigest::Primary(primary) => Some(&primary.vrf_output), + PreDigest::SecondaryVRF(secondary) => Some(&secondary.vrf_output), + PreDigest::SecondaryPlain(_) => None, + } + } } /// Information about the next epoch. This is broadcast in the first block