diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 31433d65cf51de0ac9a689cf3a41ea700baf28a6..c0c3ef6b80b893771e46671c5e5f57a5a27d3745 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -1727,7 +1727,7 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "uint 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uint 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "unsigned-varint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2862,7 +2862,7 @@ dependencies = [ "fixed-hash 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "impl-codec 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "impl-serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uint 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uint 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -5586,7 +5586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "uint" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6470,7 +6470,7 @@ dependencies = [ "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa9b3b49edd3468c0e6565d85783f51af95212b6fa3986a5500954f00b460874" -"checksum uint 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5375d2c574f89adad4108ad525c93e39669853a602560bf5ed4ca9943b10799" +"checksum uint 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8f0f47ed099f0db671ce82c66548c5de012e3c0cba3963514d1db15c7588701" "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" "checksum unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a84e5511b2a947f3ae965dcb29b13b7b1691b6e7332cf5dbc1744138d5acb7f6" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" diff --git a/substrate/core/consensus/aura/src/lib.rs b/substrate/core/consensus/aura/src/lib.rs index 06e57873289011ce891191da95bd6d85080eecd4..e2e36fdbfba15b1136ef0a3b10bc7f5558fbffd2 100644 --- a/substrate/core/consensus/aura/src/lib.rs +++ b/substrate/core/consensus/aura/src/lib.rs @@ -225,7 +225,12 @@ impl<H, B, C, E, I, P, Error, SO> slots::SimpleSlotWorker<B> for AuraWorker<C, E epoch_data.len() } - fn claim_slot(&self, slot_number: u64, epoch_data: &Self::EpochData) -> Option<Self::Claim> { + fn claim_slot( + &self, + _header: &B::Header, + slot_number: u64, + epoch_data: &Self::EpochData, + ) -> Option<Self::Claim> { let expected_author = slot_author::<P>(slot_number, epoch_data); expected_author.and_then(|p| { diff --git a/substrate/core/consensus/babe/primitives/src/digest.rs b/substrate/core/consensus/babe/primitives/src/digest.rs index 3b6e3221bd8a87ab5aa1fe399c4ba5e8edb44e5e..427c4fec57e7df3ee1d435703e340e9bd2c3e315 100644 --- a/substrate/core/consensus/babe/primitives/src/digest.rs +++ b/substrate/core/consensus/babe/primitives/src/digest.rs @@ -22,7 +22,7 @@ use super::AuthoritySignature; use super::{BABE_ENGINE_ID, Epoch}; #[cfg(not(feature = "std"))] use super::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}; -use super::SlotNumber; +use super::{AuthorityIndex, BabeBlockWeight, SlotNumber}; #[cfg(feature = "std")] use sr_primitives::{DigestItem, generic::OpaqueDigestItemId}; #[cfg(feature = "std")] @@ -36,18 +36,61 @@ use schnorrkel::{ vrf::{VRFProof, VRFOutput, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH} }; -/// A BABE pre-digest +/// 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). #[cfg(feature = "std")] #[derive(Clone, Debug)] -pub struct BabePreDigest { - /// VRF output - pub vrf_output: VRFOutput, - /// VRF proof - pub vrf_proof: VRFProof, - /// Authority index - pub authority_index: super::AuthorityIndex, - /// Slot number - pub slot_number: SlotNumber, +pub enum BabePreDigest { + /// A primary VRF-based slot assignment. + Primary { + /// VRF output + vrf_output: VRFOutput, + /// VRF proof + vrf_proof: VRFProof, + /// Authority index + authority_index: super::AuthorityIndex, + /// Slot number + slot_number: SlotNumber, + /// Chain weight (measured in number of Primary blocks) + weight: BabeBlockWeight, + }, + /// A secondary deterministic slot assignment. + Secondary { + /// Authority index + authority_index: super::AuthorityIndex, + /// Slot number + slot_number: SlotNumber, + /// Chain weight (measured in number of Primary blocks) + weight: BabeBlockWeight, + }, +} + +#[cfg(feature = "std")] +impl BabePreDigest { + /// Returns the slot number of the pre digest. + pub fn authority_index(&self) -> AuthorityIndex { + match self { + BabePreDigest::Primary { authority_index, .. } => *authority_index, + BabePreDigest::Secondary { authority_index, .. } => *authority_index, + } + } + + /// Returns the slot number of the pre digest. + pub fn slot_number(&self) -> SlotNumber { + match self { + BabePreDigest::Primary { slot_number, .. } => *slot_number, + BabePreDigest::Secondary { slot_number, .. } => *slot_number, + } + } + + /// Returns the weight of the pre digest. + pub fn weight(&self) -> BabeBlockWeight { + match self { + BabePreDigest::Primary { weight, .. } => *weight, + BabePreDigest::Secondary { weight, .. } => *weight, + } + } } /// The prefix used by BABE for its VRF keys. @@ -55,27 +98,74 @@ pub const BABE_VRF_PREFIX: &'static [u8] = b"substrate-babe-vrf"; /// A raw version of `BabePreDigest`, usable on `no_std`. #[derive(Copy, Clone, Encode, Decode)] -pub struct RawBabePreDigest { - /// Slot number - pub slot_number: SlotNumber, - /// Authority index - pub authority_index: super::AuthorityIndex, - /// VRF output - pub vrf_output: [u8; VRF_OUTPUT_LENGTH], - /// VRF proof - pub vrf_proof: [u8; VRF_PROOF_LENGTH], +pub enum RawBabePreDigest { + /// A primary VRF-based slot assignment. + Primary { + /// Authority index + authority_index: AuthorityIndex, + /// Slot number + slot_number: SlotNumber, + /// Chain weight (measured in number of Primary blocks) + weight: BabeBlockWeight, + /// VRF output + vrf_output: [u8; VRF_OUTPUT_LENGTH], + /// VRF proof + vrf_proof: [u8; VRF_PROOF_LENGTH], + }, + /// A secondary deterministic slot assignment. + Secondary { + /// Authority index + authority_index: AuthorityIndex, + /// Slot number + slot_number: SlotNumber, + /// Chain weight (measured in number of Primary blocks) + weight: BabeBlockWeight, + }, +} + +impl RawBabePreDigest { + /// Returns the slot number of the pre digest. + pub fn slot_number(&self) -> SlotNumber { + match self { + RawBabePreDigest::Primary { slot_number, .. } => *slot_number, + RawBabePreDigest::Secondary { slot_number, .. } => *slot_number, + } + } } #[cfg(feature = "std")] impl Encode for BabePreDigest { fn encode(&self) -> Vec<u8> { - let tmp = RawBabePreDigest { - vrf_output: *self.vrf_output.as_bytes(), - vrf_proof: self.vrf_proof.to_bytes(), - authority_index: self.authority_index, - slot_number: self.slot_number, + let raw = match self { + BabePreDigest::Primary { + vrf_output, + vrf_proof, + authority_index, + slot_number, + weight, + } => { + RawBabePreDigest::Primary { + vrf_output: *vrf_output.as_bytes(), + vrf_proof: vrf_proof.to_bytes(), + authority_index: *authority_index, + slot_number: *slot_number, + weight: *weight, + } + }, + BabePreDigest::Secondary { + authority_index, + slot_number, + weight, + } => { + RawBabePreDigest::Secondary { + authority_index: *authority_index, + slot_number: *slot_number, + weight: *weight, + } + }, }; - codec::Encode::encode(&tmp) + + codec::Encode::encode(&raw) } } @@ -85,19 +175,26 @@ impl codec::EncodeLike for BabePreDigest {} #[cfg(feature = "std")] impl Decode for BabePreDigest { fn decode<R: Input>(i: &mut R) -> Result<Self, Error> { - let RawBabePreDigest { vrf_output, vrf_proof, authority_index, slot_number } = Decode::decode(i)?; - - // Verify (at compile time) that the sizes in babe_primitives are correct - let _: [u8; super::VRF_OUTPUT_LENGTH] = vrf_output; - let _: [u8; super::VRF_PROOF_LENGTH] = vrf_proof; - Ok(BabePreDigest { - vrf_proof: VRFProof::from_bytes(&vrf_proof) - .map_err(convert_error)?, - vrf_output: VRFOutput::from_bytes(&vrf_output) - .map_err(convert_error)?, - authority_index, - slot_number, - }) + let pre_digest = match Decode::decode(i)? { + RawBabePreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number, weight } => { + // Verify (at compile time) that the sizes in babe_primitives are correct + let _: [u8; super::VRF_OUTPUT_LENGTH] = vrf_output; + let _: [u8; super::VRF_PROOF_LENGTH] = vrf_proof; + + BabePreDigest::Primary { + vrf_proof: VRFProof::from_bytes(&vrf_proof).map_err(convert_error)?, + vrf_output: VRFOutput::from_bytes(&vrf_output).map_err(convert_error)?, + authority_index, + slot_number, + weight, + } + }, + RawBabePreDigest::Secondary { authority_index, slot_number, weight } => { + BabePreDigest::Secondary { authority_index, slot_number, weight } + }, + }; + + Ok(pre_digest) } } diff --git a/substrate/core/consensus/babe/primitives/src/lib.rs b/substrate/core/consensus/babe/primitives/src/lib.rs index f4da908080c8b524112827ba935fde7d1c8f1ac5..09ac2f20123ac5b9ba407ae57a451291992b0864 100644 --- a/substrate/core/consensus/babe/primitives/src/lib.rs +++ b/substrate/core/consensus/babe/primitives/src/lib.rs @@ -68,7 +68,10 @@ pub type SlotNumber = u64; /// The weight of an authority. // NOTE: we use a unique name for the weight to avoid conflicts with other // `Weight` types, since the metadata isn't able to disambiguate. -pub type BabeWeight = u64; +pub type BabeAuthorityWeight = u64; + +/// The weight of a BABE block. +pub type BabeBlockWeight = u32; /// BABE epoch information #[derive(Decode, Encode, Default, PartialEq, Eq, Clone)] @@ -81,9 +84,11 @@ pub struct Epoch { /// The duration of this epoch pub duration: SlotNumber, /// The authorities and their weights - pub authorities: Vec<(AuthorityId, BabeWeight)>, + pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// Randomness for this epoch pub randomness: [u8; VRF_OUTPUT_LENGTH], + /// Whether secondary slot assignments should be used during the epoch. + pub secondary_slots: bool, } /// An consensus log item for BABE. diff --git a/substrate/core/consensus/babe/src/lib.rs b/substrate/core/consensus/babe/src/lib.rs index 3fbcd84a005d59f6d3720e8f5c88f925121f7c6f..e29de64de2b77d56f460390425fac493c58034e8 100644 --- a/substrate/core/consensus/babe/src/lib.rs +++ b/substrate/core/consensus/babe/src/lib.rs @@ -14,9 +14,47 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see <http://www.gnu.org/licenses/>. -//! # BABE consensus +//! # BABE (Blind Assignment for Blockchain Extension) //! -//! BABE (Blind Assignment for Blockchain Extension) consensus in Substrate. +//! BABE is a slot-based block production mechanism which uses a VRF PRNG to +//! randomly perform the slot allocation. On every slot, all the authorities +//! generate a new random number with the VRF function and if it is lower than a +//! given threshold (which is proportional to their weight/stake) they have a +//! right to produce a block. The proof of the VRF function execution will be +//! used by other peer to validate the legitimacy of the slot claim. +//! +//! The engine is also responsible for collecting entropy on-chain which will be +//! used to seed the given VRF PRNG. An epoch is a contiguous number of slots +//! under which we will be using the same authority set. During an epoch all VRF +//! outputs produced as a result of block production will be collected on an +//! on-chain randomness pool. Epoch changes are announced one epoch in advance, +//! i.e. when ending epoch N, we announce the parameters (randomness, +//! authorities, etc.) for epoch N+2. +//! +//! Since the slot assignment is randomized, it is possible that a slot is +//! assigned to multiple validators in which case we will have a temporary fork, +//! or that a slot is assigned to no validator in which case no block is +//! produced. Which means that block times are not deterministic. +//! +//! The protocol has a parameter `c` [0, 1] for which `1 - c` is the probability +//! of a slot being empty. The choice of this parameter affects the security of +//! the protocol relating to maximum tolerable network delays. +//! +//! In addition to the VRF-based slot assignment described above, which we will +//! call primary slots, the engine also supports a deterministic secondary slot +//! assignment. Primary slots take precedence over secondary slots, when +//! authoring the node starts by trying to claim a primary slot and falls back +//! to a secondary slot claim attempt. The secondary slot assignment is done +//! by picking the authority at index: +//! +//! `blake2_256(epoch_randomness ++ slot_number) % authorities_len`. +//! +//! 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. +//! +//! An in-depth description and analysis of the protocol can be found here: +//! <https://research.web3.foundation/en/latest/polkadot/BABE/Babe> #![forbid(unsafe_code, missing_docs)] pub use babe_primitives::*; @@ -36,7 +74,7 @@ use sr_primitives::traits::{ use keystore::KeyStorePtr; use codec::{Decode, Encode}; use parking_lot::{Mutex, MutexGuard}; -use primitives::{Blake2Hasher, H256, Pair, Public}; +use primitives::{blake2_256, Blake2Hasher, H256, Pair, Public, U256}; use merlin::Transcript; use inherents::{InherentDataProviders, InherentData}; use substrate_telemetry::{ @@ -47,7 +85,7 @@ use substrate_telemetry::{ use schnorrkel::{ keys::Keypair, vrf::{ - VRFProof, VRFProofBatchable, VRFInOut, + VRFProof, VRFInOut, VRFOutput, }, }; use consensus_common::{ @@ -238,7 +276,7 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I Error: std::error::Error + Send + From<::consensus_common::Error> + From<I::Error> + 'static, { type EpochData = Epoch; - type Claim = (VRFInOut, VRFProof, u32, AuthorityPair); + type Claim = (BabePreDigest, AuthorityPair); type SyncOracle = SO; type Proposer = E::Proposer; type BlockImport = I; @@ -260,27 +298,29 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I epoch_data.authorities.len() } - fn claim_slot(&self, slot_number: u64, epoch_data: &Self::EpochData) -> Option<Self::Claim> { + fn claim_slot( + &self, + header: &B::Header, + slot_number: u64, + epoch_data: &Self::EpochData, + ) -> Option<Self::Claim> { + let parent_weight = { + let pre_digest = find_pre_digest::<B>(&header).ok()?; + pre_digest.weight() + }; + claim_slot( slot_number, + parent_weight, epoch_data, self.c, &self.keystore, - ).map(|((inout, vrf_proof, _), authority_index, key)| { - (inout, vrf_proof, authority_index as u32, key) - }) + ) } - fn pre_digest_data(&self, slot_number: u64, claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>> { - let inherent_digest = BabePreDigest { - vrf_proof: claim.1.clone(), - vrf_output: claim.0.to_output(), - authority_index: claim.2, - slot_number, - }; - + fn pre_digest_data(&self, _slot_number: u64, claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>> { vec![ - <DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(inherent_digest), + <DigestItemFor<B> as CompatibleDigestItem>::babe_pre_digest(claim.0.clone()), ] } @@ -290,12 +330,15 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I Vec<B::Extrinsic>, Self::Claim, ) -> consensus_common::BlockImportParams<B> + Send> { - Box::new(|header, header_hash, body, (_, _, _, pair)| { + Box::new(|header, header_hash, body, (_, pair)| { // sign the pre-sealed hash of the block and then // add it to a digest item. let signature = pair.sign(header_hash.as_ref()); let signature_digest_item = <DigestItemFor<B> as CompatibleDigestItem>::babe_seal(signature); + // When we building our own blocks we always author on top of the + // current best according to `SelectChain`, therefore our own block + // proposal should always become the new best. BlockImportParams { origin: BlockOrigin::Own, header, @@ -304,7 +347,7 @@ impl<H, B, C, E, I, Error, SO> slots::SimpleSlotWorker<B> for BabeWorker<C, E, I body: Some(body), finalized: false, auxiliary: Vec::new(), - fork_choice: ForkChoiceStrategy::LongestChain, + fork_choice: ForkChoiceStrategy::Custom(true), } }) } @@ -356,6 +399,16 @@ macro_rules! babe_err { fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<BabePreDigest, String> where DigestItemFor<B>: CompatibleDigestItem, { + // 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(BabePreDigest::Secondary { + slot_number: 0, + authority_index: 0, + weight: 0, + }); + } + let mut pre_digest: Option<_> = None; for log in header.digest().logs() { trace!(target: "babe", "Checking log {:?}, looking for pre runtime digest", log); @@ -394,16 +447,20 @@ fn find_next_epoch_digest<B: BlockT>(header: &B::Header) -> Result<Option<Epoch> /// unsigned. This is required for security and must not be changed. /// /// This digest item will always return `Some` when used with `as_babe_pre_digest`. +/// +/// The given header can either be from a primary or secondary slot assignment, +/// with each having different validation logic. // FIXME #1018 needs misbehavior types. The `transaction_pool` parameter will be // used to submit such misbehavior reports. fn check_header<B: BlockT + Sized, C: AuxStore, T>( - client: &C, - slot_now: u64, mut header: B::Header, - hash: B::Hash, - authorities: &[(AuthorityId, BabeWeight)], + parent_header: B::Header, + slot_now: u64, + authorities: &[(AuthorityId, BabeAuthorityWeight)], + client: &C, randomness: [u8; 32], epoch_index: u64, + secondary_slots: bool, c: (u64, u64), _transaction_pool: Option<&T>, ) -> Result<CheckedHeader<B::Header, (DigestItemFor<B>, DigestItemFor<B>)>, String> where @@ -413,67 +470,184 @@ fn check_header<B: BlockT + Sized, C: AuxStore, T>( trace!(target: "babe", "Checking header"); let seal = match header.digest_mut().pop() { Some(x) => x, - None => return Err(babe_err!("Header {:?} is unsealed", hash)), + None => return Err(babe_err!("Header {:?} is unsealed", header.hash())), }; let sig = seal.as_babe_seal().ok_or_else(|| { - babe_err!("Header {:?} has a bad seal", hash) + babe_err!("Header {:?} has a bad seal", header.hash()) })?; - let pre_digest = find_pre_digest::<B>(&header)?; + // the pre-hash of the header doesn't include the seal + // and that's what we sign + let pre_hash = header.hash(); - let BabePreDigest { slot_number, authority_index, ref vrf_proof, ref vrf_output } = pre_digest; + let pre_digest = find_pre_digest::<B>(&header)?; - if slot_number > slot_now { + if pre_digest.slot_number() > slot_now { header.digest_mut().push(seal); - Ok(CheckedHeader::Deferred(header, slot_number)) - } else if authority_index > authorities.len() as u32 { - Err(babe_err!("Slot author not found")) - } else { - let (pre_hash, author) = (header.hash(), &authorities[authority_index as usize].0); + return Ok(CheckedHeader::Deferred(header, pre_digest.slot_number())); + } - if AuthorityPair::verify(&sig, pre_hash, &author) { - let (inout, _batchable_proof) = { - let transcript = make_transcript( - &randomness, - slot_number, - epoch_index, - ); + if pre_digest.authority_index() > authorities.len() as u32 { + return Err(babe_err!("Slot author not found")); + } - schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| { - p.vrf_verify(transcript, vrf_output, vrf_proof) - }).map_err(|s| { - babe_err!("VRF verification failed: {:?}", s) - })? - }; + let parent_weight = { + let parent_pre_digest = find_pre_digest::<B>(&parent_header)?; + parent_pre_digest.weight() + }; - let threshold = calculate_threshold(c, authorities, authority_index as usize); - if !check(&inout, threshold) { - return Err(babe_err!("VRF verification of block by author {:?} failed: \ - threshold {} exceeded", author, threshold)); - } + match &pre_digest { + BabePreDigest::Primary { vrf_output, vrf_proof, authority_index, slot_number, weight } => { + debug!(target: "babe", "Verifying Primary block"); + + let digest = (vrf_output, vrf_proof, *authority_index, *slot_number, *weight); + + check_primary_header::<B>( + pre_hash, + digest, + sig, + parent_weight, + authorities, + randomness, + epoch_index, + c, + )?; + }, + BabePreDigest::Secondary { authority_index, slot_number, weight } if secondary_slots => { + debug!(target: "babe", "Verifying Secondary block"); + + let digest = (*authority_index, *slot_number, *weight); + + check_secondary_header::<B>( + pre_hash, + digest, + sig, + parent_weight, + &authorities, + randomness, + )?; + }, + _ => { + return Err(babe_err!("Secondary slot assignments are disabled for the current epoch.")); + } + } + + let author = &authorities[pre_digest.authority_index() as usize].0; + + // the header is valid but let's check if there was something else already + // proposed at the same slot by the given author + if let Some(equivocation_proof) = check_equivocation( + client, + slot_now, + pre_digest.slot_number(), + &header, + author, + ).map_err(|e| e.to_string())? { + info!( + "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}", + author, + pre_digest.slot_number(), + equivocation_proof.fst_header().hash(), + equivocation_proof.snd_header().hash(), + ); + } + + let pre_digest = CompatibleDigestItem::babe_pre_digest(pre_digest); + Ok(CheckedHeader::Checked(header, (pre_digest, seal))) +} + +/// Check a primary slot proposal header. We validate that the given header is +/// properly signed by the expected authority, and that the contained VRF proof +/// is valid. Additionally, the weight of this block must increase compared to +/// its parent since it is a primary block. +fn check_primary_header<B: BlockT + Sized>( + pre_hash: B::Hash, + pre_digest: (&VRFOutput, &VRFProof, AuthorityIndex, SlotNumber, BabeBlockWeight), + signature: AuthoritySignature, + parent_weight: BabeBlockWeight, + authorities: &[(AuthorityId, BabeAuthorityWeight)], + randomness: [u8; 32], + epoch_index: u64, + c: (u64, u64), +) -> Result<(), String> + where DigestItemFor<B>: CompatibleDigestItem, +{ + let (vrf_output, vrf_proof, authority_index, slot_number, weight) = pre_digest; + if weight != parent_weight + 1 { + return Err("Invalid weight: should increase with Primary block.".into()); + } - if let Some(equivocation_proof) = check_equivocation( - client, - slot_now, + let author = &authorities[authority_index as usize].0; + + if AuthorityPair::verify(&signature, pre_hash, &author) { + let (inout, _) = { + let transcript = make_transcript( + &randomness, slot_number, - &header, - author, - ).map_err(|e| e.to_string())? { - info!( - "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}", - author, - slot_number, - equivocation_proof.fst_header().hash(), - equivocation_proof.snd_header().hash(), - ); - } + epoch_index, + ); - let pre_digest = CompatibleDigestItem::babe_pre_digest(pre_digest); - Ok(CheckedHeader::Checked(header, (pre_digest, seal))) - } else { - Err(babe_err!("Bad signature on {:?}", hash)) + schnorrkel::PublicKey::from_bytes(author.as_slice()).and_then(|p| { + p.vrf_verify(transcript, vrf_output, vrf_proof) + }).map_err(|s| { + babe_err!("VRF verification failed: {:?}", s) + })? + }; + + let threshold = calculate_primary_threshold(c, authorities, authority_index as usize); + if !check_primary_threshold(&inout, threshold) { + return Err(babe_err!("VRF verification of block by author {:?} failed: \ + threshold {} exceeded", author, threshold)); } + + Ok(()) + } else { + Err(babe_err!("Bad signature on {:?}", pre_hash)) + } +} + +/// Check a secondary slot proposal header. We validate that the given header is +/// 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>( + pre_hash: B::Hash, + pre_digest: (AuthorityIndex, SlotNumber, BabeBlockWeight), + signature: AuthoritySignature, + parent_weight: BabeBlockWeight, + authorities: &[(AuthorityId, BabeAuthorityWeight)], + randomness: [u8; 32], +) -> Result<(), String> { + let (authority_index, slot_number, weight) = pre_digest; + + if weight != parent_weight { + return Err("Invalid weight: Should stay the same with secondary block.".into()); + } + + // check the signature is valid under the expected authority and + // chain state. + let expected_author = secondary_slot_author( + slot_number, + authorities, + randomness, + ).ok_or_else(|| "No secondary author expected.".to_string())?; + + let author = &authorities[authority_index as usize].0; + + if expected_author != author { + let msg = format!("Invalid author: Expected secondary author: {:?}, got: {:?}.", + expected_author, + author, + ); + + return Err(msg); + } + + if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { + Ok(()) + } else { + Err(format!("Bad signature on {:?}", pre_hash)) } } @@ -482,22 +656,23 @@ fn check_header<B: BlockT + Sized, C: AuxStore, T>( pub struct BabeLink(Arc<Mutex<(Option<Duration>, Vec<(Instant, u64)>)>>); /// A verifier for Babe blocks. -pub struct BabeVerifier<C, T> { - api: Arc<C>, +pub struct BabeVerifier<B, E, Block: BlockT, RA, PRA, T> { + client: Arc<Client<B, E, Block, RA>>, + api: Arc<PRA>, inherent_data_providers: inherents::InherentDataProviders, config: Config, time_source: BabeLink, transaction_pool: Option<Arc<T>>, } -impl<C, T> BabeVerifier<C, T> { - fn check_inherents<B: BlockT>( +impl<B, E, Block: BlockT, RA, PRA, T> BabeVerifier<B, E, Block, RA, PRA, T> { + fn check_inherents( &self, - block: B, - block_id: BlockId<B>, + block: Block, + block_id: BlockId<Block>, inherent_data: InherentData, ) -> Result<(), String> - where C: ProvideRuntimeApi, C::Api: BlockBuilderApi<B> + where PRA: ProvideRuntimeApi, PRA::Api: BlockBuilderApi<Block> { let inherent_res = self.api.runtime_api().check_inherents( &block_id, @@ -560,18 +735,22 @@ fn median_algorithm( } } -impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where - C: ProvideRuntimeApi + Send + Sync + AuxStore + ProvideCache<B>, - C::Api: BlockBuilderApi<B> + BabeApi<B>, +impl<B, E, Block, RA, PRA, T> Verifier<Block> for BabeVerifier<B, E, Block, RA, PRA, T> where + Block: BlockT<Hash=H256>, + B: Backend<Block, Blake2Hasher> + 'static, + E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync, + RA: Send + Sync, + PRA: ProvideRuntimeApi + Send + Sync + AuxStore + ProvideCache<Block>, + PRA::Api: BlockBuilderApi<Block> + BabeApi<Block>, T: Send + Sync + 'static, { fn verify( &mut self, origin: BlockOrigin, - header: B::Header, + header: Block::Header, justification: Option<Justification>, - mut body: Option<Vec<B::Extrinsic>>, - ) -> Result<(BlockImportParams<B>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> { + mut body: Option<Vec<Block::Extrinsic>>, + ) -> Result<(BlockImportParams<Block>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> { trace!( target: "babe", "Verifying origin: {:?} header: {:?} justification: {:?} body: {:?}", @@ -596,18 +775,23 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where let epoch = epoch(self.api.as_ref(), &BlockId::Hash(parent_hash)) .map_err(|e| format!("Could not fetch epoch at {:?}: {:?}", parent_hash, e))?; let (epoch, maybe_next_epoch) = epoch.deconstruct(); - let Epoch { authorities, randomness, epoch_index, .. } = epoch; + let Epoch { authorities, randomness, epoch_index, secondary_slots, .. } = epoch; + + let parent_header = self.client.header(&BlockId::Hash(parent_hash)) + .map_err(|e| format!("Could not fetch parent header {:?}: {:?}", parent_hash, e))? + .ok_or_else(|| format!("Parent header {:?} not found.", parent_hash))?; // We add one to allow for some small drift. // FIXME #1019 in the future, alter this queue to allow deferring of headers - let mut checked_header = check_header::<B, C, T>( - &self.api, - slot_now + 1, + let mut checked_header = check_header::<Block, PRA, T>( header.clone(), - hash, + parent_header.clone(), + slot_now + 1, &authorities, + &self.api, randomness, epoch_index, + secondary_slots, self.config.c(), self.transaction_pool.as_ref().map(|x| &**x), ); @@ -617,14 +801,15 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where // (this is only possible on the light client at epoch#0) if epoch_index == 0 && checked_header.is_err() { if let Some(Epoch { authorities, randomness, epoch_index, .. }) = maybe_next_epoch { - let checked_header_next = check_header::<B, C, T>( - &self.api, - slot_now + 1, + let checked_header_next = check_header::<Block, PRA, T>( header, - hash, + parent_header, + slot_now + 1, &authorities, + &self.api, randomness, epoch_index, + secondary_slots, self.config.c(), self.transaction_pool.as_ref().map(|x| &**x), ); @@ -639,15 +824,17 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where let checked_header = checked_header?; match checked_header { CheckedHeader::Checked(pre_header, (pre_digest, seal)) => { - let BabePreDigest { slot_number, .. } = pre_digest.as_babe_pre_digest() + let babe_pre_digest = pre_digest.as_babe_pre_digest() .expect("check_header always returns a pre-digest digest item; qed"); + let slot_number = babe_pre_digest.slot_number(); + // if the body is passed through, we need to use the runtime // to check that the internally-set timestamp in the inherents // actually matches the slot set in the seal. if let Some(inner_body) = body.take() { inherent_data.babe_replace_inherent_data(slot_number); - let block = B::new(pre_header.clone(), inner_body); + let block = Block::new(pre_header.clone(), inner_body); self.check_inherents( block.clone(), @@ -665,6 +852,34 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where "babe.checked_and_importing"; "pre_header" => ?pre_header); + // The fork choice rule is that we pick the heaviest chain (i.e. + // more primary blocks), if there's a tie we go with the longest + // chain. + let new_best = { + let (last_best, last_best_number) = { + #[allow(deprecated)] + let info = self.client.backend().blockchain().info(); + (info.best_hash, info.best_number) + }; + + let best_header = self.client.header(&BlockId::Hash(last_best)) + .map_err(|_| "Failed fetching best header")? + .expect("parent_header must be imported; qed"); + + let best_weight = find_pre_digest::<Block>(&best_header) + .map(|babe_pre_digest| babe_pre_digest.weight())?; + + let new_weight = babe_pre_digest.weight(); + + if new_weight > best_weight { + true + } else if new_weight == best_weight { + *pre_header.number() > last_best_number + } else { + false + } + }; + let import_block = BlockImportParams { origin, header: pre_header, @@ -673,7 +888,7 @@ impl<B: BlockT, C, T> Verifier<B> for BabeVerifier<C, T> where finalized: false, justification, auxiliary: Vec::new(), - fork_choice: ForkChoiceStrategy::LongestChain, + fork_choice: ForkChoiceStrategy::Custom(new_best), }; Ok((import_block, Default::default())) @@ -797,13 +1012,17 @@ fn make_transcript( transcript } -fn check(inout: &VRFInOut, threshold: u128) -> bool { +/// Returns true if the given VRF output is lower than the given threshold, +/// false otherwise. +fn check_primary_threshold(inout: &VRFInOut, threshold: u128) -> bool { u128::from_le_bytes(inout.make_bytes::<[u8; 16]>(BABE_VRF_PREFIX)) < threshold } -fn calculate_threshold( +/// Calculates the primary selection threshold for a given authority, taking +/// into account `c` (`1 - c` represents the probability of a slot being empty). +fn calculate_primary_threshold( c: (u64, u64), - authorities: &[(AuthorityId, BabeWeight)], + authorities: &[(AuthorityId, BabeAuthorityWeight)], authority_index: usize, ) -> u128 { use num_bigint::BigUint; @@ -826,34 +1045,146 @@ fn calculate_threshold( calc().unwrap_or(u128::max_value()) } -/// Claim a slot if it is our turn. Returns `None` if it is not our turn. -/// +/// Tries to claim the given slot number. This method starts by trying to claim +/// a primary VRF based slot. If we are not able to claim it, then if we have +/// secondary slots enabled for the given epoch, we will fallback to trying to +/// claim a secondary slot. +fn claim_slot( + slot_number: SlotNumber, + parent_weight: BabeBlockWeight, + epoch: &Epoch, + c: (u64, u64), + keystore: &KeyStorePtr, +) -> Option<(BabePreDigest, AuthorityPair)> { + claim_primary_slot(slot_number, parent_weight, epoch, c, keystore) + .or_else(|| { + if epoch.secondary_slots { + claim_secondary_slot( + slot_number, + parent_weight, + &epoch.authorities, + keystore, + epoch.randomness, + ) + } else { + None + } + }) +} + +/// Claim a primary slot if it is our turn. Returns `None` if it is not our turn. /// This hashes the slot number, epoch, genesis hash, and chain randomness into /// the VRF. If the VRF produces a value less than `threshold`, it is our turn, -/// so it returns `Some(_)`. Otherwise, it returns `None`. -fn claim_slot( - slot_number: u64, - Epoch { authorities, randomness, epoch_index, .. }: &Epoch, +/// so it returns `Some(_)`. Otherwise, it returns `None`. +fn claim_primary_slot( + slot_number: SlotNumber, + parent_weight: BabeBlockWeight, + epoch: &Epoch, c: (u64, u64), keystore: &KeyStorePtr, -) -> Option<((VRFInOut, VRFProof, VRFProofBatchable), usize, AuthorityPair)> { +) -> Option<(BabePreDigest, AuthorityPair)> { + let Epoch { authorities, randomness, epoch_index, .. } = epoch; + let keystore = keystore.read(); + + for (pair, authority_index) in authorities.iter() + .enumerate() + .flat_map(|(i, a)| { + keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i)) + }) + { + let transcript = make_transcript(randomness, slot_number, *epoch_index); + + // Compute the threshold we will use. + // + // We already checked that authorities contains `key.public()`, so it can't + // be empty. Therefore, this division in `calculate_threshold` is safe. + let threshold = calculate_primary_threshold(c, authorities, authority_index); + + let pre_digest = get_keypair(&pair) + .vrf_sign_after_check(transcript, |inout| check_primary_threshold(inout, threshold)) + .map(|s| { + BabePreDigest::Primary { + slot_number, + vrf_output: s.0.to_output(), + vrf_proof: s.1, + authority_index: authority_index as u32, + weight: parent_weight + 1, + } + }); + + // early exit on first successful claim + if let Some(pre_digest) = pre_digest { + return Some((pre_digest, pair)); + } + } + + None +} + +/// Get the expected secondary author for the given slot and with given +/// authorities. This should always assign the slot to some authority unless the +/// authorities list is empty. +fn secondary_slot_author( + slot_number: u64, + authorities: &[(AuthorityId, BabeAuthorityWeight)], + randomness: [u8; 32], +) -> Option<&AuthorityId> { + if authorities.is_empty() { + return None; + } + + let rand = U256::from((randomness, slot_number).using_encoded(blake2_256)); + + let authorities_len = U256::from(authorities.len()); + let idx = rand % authorities_len; + + let expected_author = authorities.get(idx.as_u32() as usize) + .expect("authorities not empty; index constrained to list length; \ + this is a valid index; qed"); + + Some(&expected_author.0) +} + +/// Claim a secondary slot if it is our turn to propose, returning the +/// pre-digest to use when authoring the block, or `None` if it is not our turn +/// to propose. +fn claim_secondary_slot( + slot_number: SlotNumber, + parent_weight: BabeBlockWeight, + authorities: &[(AuthorityId, BabeAuthorityWeight)], + keystore: &KeyStorePtr, + randomness: [u8; 32], +) -> Option<(BabePreDigest, AuthorityPair)> { + if authorities.is_empty() { + return None; + } + + let expected_author = secondary_slot_author( + slot_number, + authorities, + randomness, + )?; + let keystore = keystore.read(); - let (key_pair, authority_index) = authorities.iter() + + for (pair, authority_index) in authorities.iter() .enumerate() - .find_map(|(i, a)| { + .flat_map(|(i, a)| { keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i)) - })?; - let transcript = make_transcript(randomness, slot_number, *epoch_index); - - // Compute the threshold we will use. - // - // We already checked that authorities contains `key.public()`, so it can't - // be empty. Therefore, this division in `calculate_threshold` is safe. - let threshold = calculate_threshold(c, authorities, authority_index); - - get_keypair(&key_pair) - .vrf_sign_after_check(transcript, |inout| check(inout, threshold)) - .map(|s|(s, authority_index, key_pair)) + }) + { + if pair.public() == *expected_author { + let pre_digest = BabePreDigest::Secondary { + slot_number, + authority_index: authority_index as u32, + weight: parent_weight, + }; + + return Some((pre_digest, pair)); + } + } + + None } fn initialize_authorities_cache<B, C>(client: &C) -> Result<(), ConsensusError> where @@ -1001,8 +1332,7 @@ impl<B, E, Block, I, RA, PRA> BlockImport<Block> for BabeBlockImport<B, E, Block let pre_digest = find_pre_digest::<Block>(&block.header) .expect("valid babe headers must contain a predigest; \ header has been already verified; qed"); - let BabePreDigest { slot_number, .. } = pre_digest; - slot_number + pre_digest.slot_number() }; // returns a function for checking whether a block is a descendent of another @@ -1158,6 +1488,7 @@ pub fn import_queue<B, E, Block: BlockT<Hash=H256>, I, RA, PRA, T>( initialize_authorities_cache(&*api)?; let verifier = BabeVerifier { + client: client.clone(), api: api.clone(), inherent_data_providers, time_source: Default::default(), @@ -1209,9 +1540,9 @@ pub mod test_helpers { /// Try to claim the given slot and return a `BabePreDigest` if /// successful. pub fn claim_slot<B, C>( - client: &C, - at: &BlockId<B>, slot_number: u64, + parent: &B::Header, + client: &C, c: (u64, u64), keystore: &KeyStorePtr, ) -> Option<BabePreDigest> where @@ -1219,23 +1550,20 @@ pub mod test_helpers { C: ProvideRuntimeApi + ProvideCache<B>, C::Api: BabeApi<B>, { - let epoch = match epoch(client, at).unwrap() { + let epoch = match epoch(client, &BlockId::Hash(parent.hash())).unwrap() { MaybeSpanEpoch::Regular(epoch) => epoch, _ => unreachable!("it is always Regular epoch on full nodes"), }; + let weight = find_pre_digest::<B>(parent).ok() + .map(|d| d.weight())?; + super::claim_slot( slot_number, + weight, &epoch, c, keystore, - ).map(|((inout, vrf_proof, _), authority_index, _)| { - BabePreDigest { - vrf_proof, - vrf_output: inout.to_output(), - authority_index: authority_index as u32, - slot_number, - } - }) + ).map(|(digest, _)| digest) } } diff --git a/substrate/core/consensus/babe/src/tests.rs b/substrate/core/consensus/babe/src/tests.rs index 482842aaaffd0b8df19196b5d575eac7ee78454f..8635473a593f9c3e5d4fc89ca0bef5d980665a06 100644 --- a/substrate/core/consensus/babe/src/tests.rs +++ b/substrate/core/consensus/babe/src/tests.rs @@ -20,14 +20,13 @@ // https://github.com/paritytech/substrate/issues/2532 #![allow(deprecated)] use super::*; -use sr_primitives::generic::{self, DigestItem}; use babe_primitives::AuthorityPair; use client::{LongestChain, block_builder::BlockBuilder}; use consensus_common::NoNetwork as DummyOracle; use network::test::*; use network::test::{Block as TestBlock, PeersClient}; -use sr_primitives::traits::{Block as BlockT, DigestFor}; +use sr_primitives::{generic::DigestItem, traits::{Block as BlockT, DigestFor}}; use network::config::ProtocolConfig; use tokio::runtime::current_thread; use keyring::sr25519::Keyring; @@ -35,7 +34,8 @@ use client::BlockchainEvents; use test_client; use log::debug; use std::{time::Duration, borrow::Borrow, cell::RefCell}; -type Item = generic::DigestItem<Hash>; + +type Item = DigestItem<Hash>; type Error = client::error::Error; @@ -88,7 +88,14 @@ type TestHeader = <TestBlock as BlockT>::Header; type TestExtrinsic = <TestBlock as BlockT>::Extrinsic; pub struct TestVerifier { - inner: BabeVerifier<PeersFullClient, ()>, + inner: BabeVerifier< + test_client::Backend, + test_client::Executor, + TestBlock, + test_client::runtime::RuntimeApi, + PeersFullClient, + (), + >, mutator: Mutator, } @@ -126,9 +133,9 @@ impl TestNetFactory for BabeTestNet { fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig) -> Self::Verifier { - let api = client.as_full().expect("only full clients are used in test"); + let client = client.as_full().expect("only full clients are used in test"); trace!(target: "babe", "Creating a verifier"); - let config = Config::get_or_compute(&*api) + let config = Config::get_or_compute(&*client) .expect("slot duration available"); let inherent_data_providers = InherentDataProviders::new(); register_babe_inherent_data_provider( @@ -139,7 +146,8 @@ impl TestNetFactory for BabeTestNet { TestVerifier { inner: BabeVerifier { - api, + client: client.clone(), + api: client, inherent_data_providers, config, time_source: Default::default(), @@ -317,19 +325,32 @@ fn can_author_block() { .expect("Generates authority pair"); let mut i = 0; - let epoch = Epoch { + let mut epoch = Epoch { start_slot: 0, authorities: vec![(pair.public(), 1)], randomness: [0; 32], epoch_index: 1, duration: 100, + secondary_slots: true, }; + + let parent_weight = 0; + + // with secondary slots enabled it should never be empty + match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) { + None => i += 1, + Some(s) => debug!(target: "babe", "Authored block {:?}", s.0), + } + + // otherwise with only vrf-based primary slots we might need to try a couple + // of times. + epoch.secondary_slots = false; loop { - match claim_slot(i, &epoch.clone(), (3, 10), &keystore) { + match claim_slot(i, parent_weight, &epoch, (3, 10), &keystore) { None => i += 1, Some(s) => { debug!(target: "babe", "Authored block {:?}", s.0); - break + break; } } } diff --git a/substrate/core/consensus/slots/src/lib.rs b/substrate/core/consensus/slots/src/lib.rs index d28510c8c60f976efbb5db7817aa4eef46cf1bd6..2513071a9fe40afb929093429c8cd3ff008ea5f1 100644 --- a/substrate/core/consensus/slots/src/lib.rs +++ b/substrate/core/consensus/slots/src/lib.rs @@ -84,7 +84,12 @@ pub trait SimpleSlotWorker<B: BlockT> { fn authorities_len(&self, epoch_data: &Self::EpochData) -> usize; /// Tries to claim the given slot, returning an object with claim data if successful. - fn claim_slot(&self, slot_number: u64, epoch_data: &Self::EpochData) -> Option<Self::Claim>; + fn claim_slot( + &self, + header: &B::Header, + slot_number: u64, + epoch_data: &Self::EpochData, + ) -> Option<Self::Claim>; /// Return the pre digest data to include in a block authored with the given claim. fn pre_digest_data(&self, slot_number: u64, claim: &Self::Claim) -> Vec<sr_primitives::DigestItem<B::Hash>>; @@ -143,7 +148,7 @@ pub trait SimpleSlotWorker<B: BlockT> { return Box::pin(future::ready(Ok(()))); } - let claim = match self.claim_slot(slot_number, &epoch_data) { + let claim = match self.claim_slot(&chain_head, slot_number, &epoch_data) { None => return Box::pin(future::ready(Ok(()))), Some(claim) => claim, }; diff --git a/substrate/core/test-runtime/src/lib.rs b/substrate/core/test-runtime/src/lib.rs index 8abb4d920fec19a310b8536c9c506c58a47bb9c7..e9ee40af75a6f69d38e60f43c5820a2d1ab3cf2f 100644 --- a/substrate/core/test-runtime/src/lib.rs +++ b/substrate/core/test-runtime/src/lib.rs @@ -617,6 +617,7 @@ cfg_if! { randomness: <srml_babe::Module<Runtime>>::randomness(), epoch_index: <srml_babe::Module<Runtime>>::epoch_index(), duration: EpochDuration::get(), + secondary_slots: <srml_babe::Module<Runtime>>::secondary_slots().0, } } } @@ -831,6 +832,7 @@ cfg_if! { randomness: <srml_babe::Module<Runtime>>::randomness(), epoch_index: <srml_babe::Module<Runtime>>::epoch_index(), duration: EpochDuration::get(), + secondary_slots: <srml_babe::Module<Runtime>>::secondary_slots().0, } } } diff --git a/substrate/node/cli/src/service.rs b/substrate/node/cli/src/service.rs index 90c76eda84cbe2f37215169aa90fe050307ff8cc..ebd2f29fd510bd250a5d3a4ca1d3641a39e688d7 100644 --- a/substrate/node/cli/src/service.rs +++ b/substrate/node/cli/src/service.rs @@ -401,9 +401,9 @@ mod tests { let babe_pre_digest = loop { inherent_data.replace_data(timestamp::INHERENT_IDENTIFIER, &(slot_num * SLOT_DURATION)); if let Some(babe_pre_digest) = babe::test_helpers::claim_slot( - &*service.client(), - &parent_id, slot_num, + &parent_header, + &*service.client(), (278, 1000), &keystore, ) { diff --git a/substrate/node/runtime/src/constants.rs b/substrate/node/runtime/src/constants.rs index f728efb3be89ff096341e257a5422937cfed48a0..4108e66bd61d63dada1dc43e6fca77bf275c12a8 100644 --- a/substrate/node/runtime/src/constants.rs +++ b/substrate/node/runtime/src/constants.rs @@ -44,7 +44,7 @@ pub mod time { pub const MILLISECS_PER_BLOCK: Moment = 6000; pub const SECS_PER_BLOCK: Moment = MILLISECS_PER_BLOCK / 1000; - pub const SLOT_DURATION: Moment = 1650; + pub const SLOT_DURATION: Moment = 6000; pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 8dd62bf407d04b4b7fd40fe8fe456e016a24a8fd..af7533863e08359e057ad96f1b97daa0a25c46db 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -80,7 +80,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 144, + spec_version: 145, impl_version: 145, apis: RUNTIME_API_VERSIONS, }; @@ -559,6 +559,7 @@ impl_runtime_apis! { epoch_index: Babe::epoch_index(), randomness: Babe::randomness(), duration: EpochDuration::get(), + secondary_slots: Babe::secondary_slots().0, } } } diff --git a/substrate/srml/babe/src/lib.rs b/substrate/srml/babe/src/lib.rs index 65c589ecab674634a27a6d34409b0bf5bacfb464..b0db04d12c7c113bcb95ecfb3d514b12a1862ea4 100644 --- a/substrate/srml/babe/src/lib.rs +++ b/substrate/srml/babe/src/lib.rs @@ -14,7 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see <http://www.gnu.org/licenses/>. -//! Consensus extension module for BABE consensus. +//! Consensus extension module for BABE consensus. Collects on-chain randomness +//! from VRF outputs and manages epoch transitions. #![cfg_attr(not(feature = "std"), no_std)] #![forbid(unused_must_use, unsafe_code, unused_variables, dead_code)] @@ -26,14 +27,16 @@ use srml_support::{decl_storage, decl_module, StorageValue, StorageMap, traits:: use timestamp::{OnTimestampSet}; use sr_primitives::{generic::DigestItem, ConsensusEngineId}; use sr_primitives::traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon}; +use sr_primitives::weights::SimpleDispatchInfo; #[cfg(feature = "std")] use timestamp::TimestampInherentData; use codec::{Encode, Decode}; use inherents::{RuntimeString, InherentIdentifier, InherentData, ProvideInherent, MakeFatalError}; #[cfg(feature = "std")] use inherents::{InherentDataProviders, ProvideInherentData}; -use babe_primitives::{BABE_ENGINE_ID, ConsensusLog, BabeWeight, Epoch, RawBabePreDigest}; +use babe_primitives::{BABE_ENGINE_ID, ConsensusLog, BabeAuthorityWeight, Epoch, RawBabePreDigest}; pub use babe_primitives::{AuthorityId, VRF_OUTPUT_LENGTH, PUBLIC_KEY_LENGTH}; +use system::ensure_root; /// The BABE inherent identifier. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot"; @@ -123,7 +126,7 @@ decl_storage! { pub EpochIndex get(epoch_index): u64; /// Current epoch authorities. - pub Authorities get(authorities): Vec<(AuthorityId, BabeWeight)>; + pub Authorities get(authorities): Vec<(AuthorityId, BabeAuthorityWeight)>; /// Slot at which the current epoch started. It is possible that no /// block was authored at the given slot and the epoch change was @@ -133,6 +136,14 @@ decl_storage! { /// Current slot number. pub CurrentSlot get(current_slot): u64; + /// Whether secondary slots are enabled in case the VRF-based slot is + /// empty for the current epoch and the next epoch, respectively. + pub SecondarySlots get(secondary_slots): (bool, bool) = (true, true); + + /// Pending change to enable/disable secondary slots which will be + /// triggered at `current_epoch + 2`. + pub PendingSecondarySlotsChange get(pending_secondary_slots_change): Option<bool> = None; + /// The epoch randomness for the *current* epoch. /// /// # Security @@ -168,7 +179,7 @@ decl_storage! { Initialized get(initialized): Option<bool>; } add_extra_genesis { - config(authorities): Vec<(AuthorityId, BabeWeight)>; + config(authorities): Vec<(AuthorityId, BabeAuthorityWeight)>; build(| storage: &mut (sr_primitives::StorageOverlay, sr_primitives::ChildrenStorageOverlay), config: &GenesisConfig @@ -204,6 +215,20 @@ decl_module! { fn on_finalize() { Initialized::kill(); } + + /// Sets a pending change to enable / disable secondary slot assignment. + /// The pending change will be set at the end of the current epoch and + /// will be enacted at `current_epoch + 2`. + #[weight = SimpleDispatchInfo::FixedOperational(10_000)] + fn set_pending_secondary_slots_change(origin, change: Option<bool>) { + ensure_root(origin)?; + match change { + Some(change) => PendingSecondarySlotsChange::put(change), + None => { + PendingSecondarySlotsChange::take(); + }, + } + } } } @@ -222,9 +247,16 @@ impl<T: Trait> FindAuthor<u32> for Module<T> { { for (id, mut data) in digests.into_iter() { if id == BABE_ENGINE_ID { - return Some(RawBabePreDigest::decode(&mut data).ok()?.authority_index); + let pre_digest = RawBabePreDigest::decode(&mut data).ok()?; + return Some(match pre_digest { + RawBabePreDigest::Primary { authority_index, .. } => + authority_index, + RawBabePreDigest::Secondary { authority_index, .. } => + authority_index, + }); } } + return None; } } @@ -302,11 +334,14 @@ impl<T: Trait> Module<T> { }) { if EpochStartSlot::get() == 0 { - EpochStartSlot::put(digest.slot_number); + EpochStartSlot::put(digest.slot_number()); } - CurrentSlot::put(digest.slot_number); - Self::deposit_vrf_output(&digest.vrf_output); + CurrentSlot::put(digest.slot_number()); + + if let RawBabePreDigest::Primary { vrf_output, .. } = digest { + Self::deposit_vrf_output(&vrf_output); + } return; } @@ -331,7 +366,7 @@ impl<T: Trait> Module<T> { this_randomness } - fn initialize_authorities(authorities: &[(AuthorityId, BabeWeight)]) { + fn initialize_authorities(authorities: &[(AuthorityId, BabeAuthorityWeight)]) { if !authorities.is_empty() { assert!(Authorities::get().is_empty(), "Authorities are already initialized!"); Authorities::put_ref(authorities); @@ -404,12 +439,31 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> { let next_epoch_start_slot = EpochStartSlot::get().saturating_add(T::EpochDuration::get()); let next_randomness = NextRandomness::get(); + // Update any pending secondary slots change + let mut secondary_slots = SecondarySlots::get(); + + // change for E + 1 now becomes change at E + secondary_slots.0 = secondary_slots.1; + + if let Some(change) = PendingSecondarySlotsChange::take() { + // if there's a pending change schedule it for E + 1 + secondary_slots.1 = change; + } else { + // otherwise E + 1 will have the same value as E + secondary_slots.1 = secondary_slots.0; + } + + SecondarySlots::mutate(|secondary| { + *secondary = secondary_slots; + }); + let next = Epoch { epoch_index: next_epoch_index, start_slot: next_epoch_start_slot, duration: T::EpochDuration::get(), authorities: next_authorities, randomness: next_randomness, + secondary_slots: secondary_slots.1, }; Self::deposit_consensus(ConsensusLog::NextEpochData(next))