mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
babe: Introduce secondary slots (Aurababeous) (#3380)
* babe: initial implementation of secondary slots * babe: validate secondary slot author * babe: implement weight based fork choice * babe: remove unused * aura: cleanup unused imports * babe: pass in parent weight when authoring and verifying * babe: use epoch randomness for picking secondary slot authors * babe: fix tests * babe: fix wasm build * babe: node-side code for disabling secondary slots * babe: allow enabling/disabling secondary slots from runtime * babe: fix test * babe: use blake2_256 for secondary slot assignment * babe: run block initialization in should_end_session * node: increase slot duration to 6s * babe: add docs * node: bump spec_version * Apply suggestions from code review Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * babe: simplify secondary slot assignment calculation * babe: remove unnecessary comment * node: bump spec_version * babe: fix bad merge
This commit is contained in:
Generated
+4
-4
@@ -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"
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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)?;
|
||||
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;
|
||||
|
||||
// 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,
|
||||
})
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,68 +470,185 @@ 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())
|
||||
})?;
|
||||
|
||||
// the pre-hash of the header doesn't include the seal
|
||||
// and that's what we sign
|
||||
let pre_hash = header.hash();
|
||||
|
||||
let pre_digest = find_pre_digest::<B>(&header)?;
|
||||
|
||||
let BabePreDigest { slot_number, authority_index, ref vrf_proof, ref vrf_output } = pre_digest;
|
||||
|
||||
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");
|
||||
|
||||
if let Some(equivocation_proof) = check_equivocation(
|
||||
client,
|
||||
slot_now,
|
||||
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(),
|
||||
);
|
||||
}
|
||||
let digest = (vrf_output, vrf_proof, *authority_index, *slot_number, *weight);
|
||||
|
||||
let pre_digest = CompatibleDigestItem::babe_pre_digest(pre_digest);
|
||||
Ok(CheckedHeader::Checked(header, (pre_digest, seal)))
|
||||
} else {
|
||||
Err(babe_err!("Bad signature on {:?}", hash))
|
||||
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());
|
||||
}
|
||||
|
||||
let author = &authorities[authority_index as usize].0;
|
||||
|
||||
if AuthorityPair::verify(&signature, pre_hash, &author) {
|
||||
let (inout, _) = {
|
||||
let transcript = make_transcript(
|
||||
&randomness,
|
||||
slot_number,
|
||||
epoch_index,
|
||||
);
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
/// State that must be shared between the import queue and the authoring logic.
|
||||
@@ -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.
|
||||
///
|
||||
/// 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`.
|
||||
/// 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: u64,
|
||||
Epoch { authorities, randomness, epoch_index, .. }: &Epoch,
|
||||
slot_number: SlotNumber,
|
||||
parent_weight: BabeBlockWeight,
|
||||
epoch: &Epoch,
|
||||
c: (u64, u64),
|
||||
keystore: &KeyStorePtr,
|
||||
) -> Option<((VRFInOut, VRFProof, VRFProofBatchable), usize, AuthorityPair)> {
|
||||
) -> 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_primary_slot(
|
||||
slot_number: SlotNumber,
|
||||
parent_weight: BabeBlockWeight,
|
||||
epoch: &Epoch,
|
||||
c: (u64, u64),
|
||||
keystore: &KeyStorePtr,
|
||||
) -> Option<(BabePreDigest, AuthorityPair)> {
|
||||
let Epoch { authorities, randomness, epoch_index, .. } = epoch;
|
||||
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);
|
||||
})
|
||||
{
|
||||
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);
|
||||
// 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);
|
||||
|
||||
get_keypair(&key_pair)
|
||||
.vrf_sign_after_check(transcript, |inout| check(inout, threshold))
|
||||
.map(|s|(s, authority_index, key_pair))
|
||||
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();
|
||||
|
||||
for (pair, authority_index) in authorities.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(i, a)| {
|
||||
keystore.key_pair::<AuthorityPair>(&a.0).ok().map(|kp| (kp, i))
|
||||
})
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user