Add a 2/3 quorum option to Authority Round. (#10909)
* Add a 2/3 quorum option to Authority Round. This prevents the "Attack of the Clones": https://arxiv.org/pdf/1902.10244.pdf * Make RollingFinality::remove_signers more tolerant. * Rename parameter to two_thirds_majority_transition.
This commit is contained in:
parent
0a654afecc
commit
175051bac7
@ -20,6 +20,7 @@ use std::collections::{VecDeque};
|
|||||||
use std::collections::hash_map::{HashMap, Entry};
|
use std::collections::hash_map::{HashMap, Entry};
|
||||||
|
|
||||||
use ethereum_types::{H256, Address};
|
use ethereum_types::{H256, Address};
|
||||||
|
use types::BlockNumber;
|
||||||
|
|
||||||
use engines::validator_set::SimpleList;
|
use engines::validator_set::SimpleList;
|
||||||
|
|
||||||
@ -30,21 +31,24 @@ pub struct UnknownValidator;
|
|||||||
/// Rolling finality checker for authority round consensus.
|
/// Rolling finality checker for authority round consensus.
|
||||||
/// Stores a chain of unfinalized hashes that can be pushed onto.
|
/// Stores a chain of unfinalized hashes that can be pushed onto.
|
||||||
pub struct RollingFinality {
|
pub struct RollingFinality {
|
||||||
headers: VecDeque<(H256, Vec<Address>)>,
|
headers: VecDeque<(H256, BlockNumber, Vec<Address>)>,
|
||||||
signers: SimpleList,
|
signers: SimpleList,
|
||||||
sign_count: HashMap<Address, usize>,
|
sign_count: HashMap<Address, usize>,
|
||||||
last_pushed: Option<H256>,
|
last_pushed: Option<H256>,
|
||||||
|
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
||||||
|
two_thirds_majority_transition: BlockNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RollingFinality {
|
impl RollingFinality {
|
||||||
/// Create a blank finality checker under the given validator set.
|
/// Create a blank finality checker under the given validator set.
|
||||||
pub fn blank(signers: Vec<Address>) -> Self {
|
pub fn blank(signers: Vec<Address>, two_thirds_majority_transition: BlockNumber) -> Self {
|
||||||
trace!(target: "finality", "Instantiating blank RollingFinality with {} signers: {:?}", signers.len(), signers);
|
trace!(target: "finality", "Instantiating blank RollingFinality with {} signers: {:?}", signers.len(), signers);
|
||||||
RollingFinality {
|
RollingFinality {
|
||||||
headers: VecDeque::new(),
|
headers: VecDeque::new(),
|
||||||
signers: SimpleList::new(signers),
|
signers: SimpleList::new(signers),
|
||||||
sign_count: HashMap::new(),
|
sign_count: HashMap::new(),
|
||||||
last_pushed: None,
|
last_pushed: None,
|
||||||
|
two_thirds_majority_transition,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,38 +57,28 @@ impl RollingFinality {
|
|||||||
///
|
///
|
||||||
/// Fails if any provided signature isn't part of the signers set.
|
/// Fails if any provided signature isn't part of the signers set.
|
||||||
pub fn build_ancestry_subchain<I>(&mut self, iterable: I) -> Result<(), UnknownValidator>
|
pub fn build_ancestry_subchain<I>(&mut self, iterable: I) -> Result<(), UnknownValidator>
|
||||||
where I: IntoIterator<Item=(H256, Vec<Address>)>
|
where I: IntoIterator<Item=(H256, BlockNumber, Vec<Address>)>,
|
||||||
{
|
{
|
||||||
self.clear();
|
self.clear();
|
||||||
for (hash, signers) in iterable {
|
for (hash, number, signers) in iterable {
|
||||||
if signers.iter().any(|s| !self.signers.contains(s)) { return Err(UnknownValidator) }
|
if signers.iter().any(|s| !self.signers.contains(s)) { return Err(UnknownValidator) }
|
||||||
if self.last_pushed.is_none() { self.last_pushed = Some(hash) }
|
if self.last_pushed.is_none() { self.last_pushed = Some(hash) }
|
||||||
|
self.add_signers(&signers);
|
||||||
|
self.headers.push_front((hash, number, signers));
|
||||||
// break when we've got our first finalized block.
|
// break when we've got our first finalized block.
|
||||||
{
|
if self.is_finalized() {
|
||||||
let current_signed = self.sign_count.len();
|
let (hash, _, signers) = self.headers.pop_front().expect("we just pushed a block; qed");
|
||||||
|
self.remove_signers(&signers);
|
||||||
let new_signers = signers.iter().filter(|s| !self.sign_count.contains_key(s)).count();
|
|
||||||
let would_be_finalized = (current_signed + new_signers) * 2 > self.signers.len();
|
|
||||||
|
|
||||||
if would_be_finalized {
|
|
||||||
trace!(target: "finality", "Encountered already finalized block {}", hash);
|
trace!(target: "finality", "Encountered already finalized block {}", hash);
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
for signer in signers.iter() {
|
|
||||||
*self.sign_count.entry(*signer).or_insert(0) += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.headers.push_front((hash, signers));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!(target: "finality", "Rolling finality state: {:?}", self.headers);
|
trace!(target: "finality", "Rolling finality state: {:?}", self.headers);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the finality status, but keeps the validator set.
|
/// Clears the finality status, but keeps the validator set.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.headers.clear();
|
self.headers.clear();
|
||||||
self.sign_count.clear();
|
self.sign_count.clear();
|
||||||
@ -99,7 +93,7 @@ impl RollingFinality {
|
|||||||
/// Get an iterator over stored hashes in order.
|
/// Get an iterator over stored hashes in order.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn unfinalized_hashes(&self) -> impl Iterator<Item=&H256> {
|
pub fn unfinalized_hashes(&self) -> impl Iterator<Item=&H256> {
|
||||||
self.headers.iter().map(|(h, _)| h)
|
self.headers.iter().map(|(h, _, _)| h)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the validator set.
|
/// Get the validator set.
|
||||||
@ -110,7 +104,9 @@ impl RollingFinality {
|
|||||||
/// Fails if `signer` isn't a member of the active validator set.
|
/// Fails if `signer` isn't a member of the active validator set.
|
||||||
/// Returns a list of all newly finalized headers.
|
/// Returns a list of all newly finalized headers.
|
||||||
// TODO: optimize with smallvec.
|
// TODO: optimize with smallvec.
|
||||||
pub fn push_hash(&mut self, head: H256, signers: Vec<Address>) -> Result<Vec<H256>, UnknownValidator> {
|
pub fn push_hash(&mut self, head: H256, number: BlockNumber, signers: Vec<Address>)
|
||||||
|
-> Result<Vec<H256>, UnknownValidator>
|
||||||
|
{
|
||||||
for their_signer in signers.iter() {
|
for their_signer in signers.iter() {
|
||||||
if !self.signers.contains(their_signer) {
|
if !self.signers.contains(their_signer) {
|
||||||
warn!(target: "finality", "Unknown validator: {}", their_signer);
|
warn!(target: "finality", "Unknown validator: {}", their_signer);
|
||||||
@ -118,33 +114,16 @@ impl RollingFinality {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for signer in signers.iter() {
|
self.add_signers(&signers);
|
||||||
*self.sign_count.entry(*signer).or_insert(0) += 1;
|
self.headers.push_back((head, number, signers));
|
||||||
}
|
|
||||||
|
|
||||||
self.headers.push_back((head, signers));
|
|
||||||
|
|
||||||
let mut newly_finalized = Vec::new();
|
let mut newly_finalized = Vec::new();
|
||||||
|
|
||||||
while self.sign_count.len() * 2 > self.signers.len() {
|
while self.is_finalized() {
|
||||||
let (hash, signers) = self.headers.pop_front()
|
let (hash, _, signers) = self.headers.pop_front()
|
||||||
.expect("headers length always greater than sign count length; qed");
|
.expect("headers length always greater than sign count length; qed");
|
||||||
|
self.remove_signers(&signers);
|
||||||
newly_finalized.push(hash);
|
newly_finalized.push(hash);
|
||||||
|
|
||||||
for signer in signers {
|
|
||||||
match self.sign_count.entry(signer) {
|
|
||||||
Entry::Occupied(mut entry) => {
|
|
||||||
// decrement count for this signer and purge on zero.
|
|
||||||
*entry.get_mut() -= 1;
|
|
||||||
|
|
||||||
if *entry.get() == 0 {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Entry::Vacant(_) => panic!("all hashes in `header` should have entries in `sign_count` for their signers; qed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!(target: "finality", "{} Blocks finalized by {:?}: {:?}", newly_finalized.len(), head, newly_finalized);
|
trace!(target: "finality", "{} Blocks finalized by {:?}: {:?}", newly_finalized.len(), head, newly_finalized);
|
||||||
@ -152,55 +131,100 @@ impl RollingFinality {
|
|||||||
self.last_pushed = Some(head);
|
self.last_pushed = Some(head);
|
||||||
Ok(newly_finalized)
|
Ok(newly_finalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the first block for which a 2/3 quorum (instead of 1/2) is required.
|
||||||
|
pub fn two_thirds_majority_transition(&self) -> BlockNumber {
|
||||||
|
self.two_thirds_majority_transition
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the first entry in `self.headers` is finalized.
|
||||||
|
fn is_finalized(&self) -> bool {
|
||||||
|
match self.headers.front() {
|
||||||
|
None => false,
|
||||||
|
Some((_, number, _)) if *number < self.two_thirds_majority_transition => {
|
||||||
|
self.sign_count.len() * 2 > self.signers.len()
|
||||||
|
}
|
||||||
|
Some((_, _, _)) => {
|
||||||
|
self.sign_count.len() * 3 > self.signers.len() * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the signers to the sign count.
|
||||||
|
fn add_signers(&mut self, signers: &[Address]) {
|
||||||
|
for signer in signers {
|
||||||
|
*self.sign_count.entry(*signer).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the signers from the sign count.
|
||||||
|
fn remove_signers(&mut self, signers: &[Address]) {
|
||||||
|
for signer in signers {
|
||||||
|
match self.sign_count.entry(*signer) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
// decrement count for this signer and purge on zero.
|
||||||
|
if *entry.get() <= 1 {
|
||||||
|
entry.remove();
|
||||||
|
} else {
|
||||||
|
*entry.get_mut() -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Vacant(_) => {
|
||||||
|
panic!("all hashes in `header` should have entries in `sign_count` for their signers; qed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ethereum_types::{H256, Address};
|
use ethereum_types::{H256, Address};
|
||||||
|
use types::BlockNumber;
|
||||||
use super::RollingFinality;
|
use super::RollingFinality;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_unknown_signers() {
|
fn rejects_unknown_signers() {
|
||||||
let signers = (0..3).map(|_| Address::random()).collect::<Vec<_>>();
|
let signers = (0..3).map(|_| Address::random()).collect::<Vec<_>>();
|
||||||
let mut finality = RollingFinality::blank(signers.clone());
|
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
|
||||||
assert!(finality.push_hash(H256::random(), vec![signers[0], Address::random()]).is_err());
|
assert!(finality.push_hash(H256::random(), 0, vec![signers[0], Address::random()]).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn finalize_multiple() {
|
fn finalize_multiple() {
|
||||||
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
||||||
|
|
||||||
let mut finality = RollingFinality::blank(signers.clone());
|
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
|
||||||
let hashes: Vec<_> = (0..7).map(|_| H256::random()).collect();
|
let hashes: Vec<_> = (0..7).map(|_| H256::random()).collect();
|
||||||
|
|
||||||
// 3 / 6 signers is < 51% so no finality.
|
// 3 / 6 signers is < 51% so no finality.
|
||||||
for (i, hash) in hashes.iter().take(6).cloned().enumerate() {
|
for (i, hash) in hashes.iter().take(6).cloned().enumerate() {
|
||||||
let i = i % 3;
|
let i = i % 3;
|
||||||
assert!(finality.push_hash(hash, vec![signers[i]]).unwrap().len() == 0);
|
assert!(finality.push_hash(hash, i as u64, vec![signers[i]]).unwrap().len() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// after pushing a block signed by a fourth validator, the first four
|
// after pushing a block signed by a fourth validator, the first four
|
||||||
// blocks of the unverified chain become verified.
|
// blocks of the unverified chain become verified.
|
||||||
assert_eq!(finality.push_hash(hashes[6], vec![signers[4]]).unwrap(),
|
assert_eq!(finality.push_hash(hashes[6], 6, vec![signers[4]]).unwrap(),
|
||||||
vec![hashes[0], hashes[1], hashes[2], hashes[3]]);
|
vec![hashes[0], hashes[1], hashes[2], hashes[3]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn finalize_multiple_signers() {
|
fn finalize_multiple_signers() {
|
||||||
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
||||||
let mut finality = RollingFinality::blank(signers.clone());
|
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
|
||||||
let hash = H256::random();
|
let hash = H256::random();
|
||||||
|
|
||||||
// after pushing a block signed by four validators, it becomes verified right away.
|
// after pushing a block signed by four validators, it becomes verified right away.
|
||||||
assert_eq!(finality.push_hash(hash, signers[0..4].to_vec()).unwrap(), vec![hash]);
|
assert_eq!(finality.push_hash(hash, 0, signers[0..4].to_vec()).unwrap(), vec![hash]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_ancestry() {
|
fn from_ancestry() {
|
||||||
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
||||||
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), vec![signers[i % 6]])).collect();
|
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), i as u64, vec![signers[i % 6]])).collect();
|
||||||
|
|
||||||
let mut finality = RollingFinality::blank(signers.clone());
|
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
|
||||||
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
|
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
|
||||||
|
|
||||||
assert_eq!(finality.unfinalized_hashes().count(), 3);
|
assert_eq!(finality.unfinalized_hashes().count(), 3);
|
||||||
@ -211,10 +235,10 @@ mod tests {
|
|||||||
fn from_ancestry_multiple_signers() {
|
fn from_ancestry_multiple_signers() {
|
||||||
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
||||||
let hashes: Vec<_> = (0..12).map(|i| {
|
let hashes: Vec<_> = (0..12).map(|i| {
|
||||||
(H256::random(), vec![signers[i % 6], signers[(i + 1) % 6], signers[(i + 2) % 6]])
|
(H256::random(), i as u64, vec![signers[i % 6], signers[(i + 1) % 6], signers[(i + 2) % 6]])
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
let mut finality = RollingFinality::blank(signers.clone());
|
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
|
||||||
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
|
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
|
||||||
|
|
||||||
// only the last hash has < 51% of authorities' signatures
|
// only the last hash has < 51% of authorities' signatures
|
||||||
@ -222,4 +246,70 @@ mod tests {
|
|||||||
assert_eq!(finality.unfinalized_hashes().next(), Some(&hashes[11].0));
|
assert_eq!(finality.unfinalized_hashes().next(), Some(&hashes[11].0));
|
||||||
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
|
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_unknown_signers_2_3() {
|
||||||
|
let signers = (0..3).map(|_| Address::random()).collect::<Vec<_>>();
|
||||||
|
let mut finality = RollingFinality::blank(signers.clone(), 0);
|
||||||
|
assert!(finality.push_hash(H256::random(), 0, vec![signers[0], Address::random()]).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finalize_multiple_2_3() {
|
||||||
|
let signers: Vec<_> = (0..7).map(|_| Address::random()).collect();
|
||||||
|
|
||||||
|
let mut finality = RollingFinality::blank(signers.clone(), 0);
|
||||||
|
let hashes: Vec<_> = (0..9).map(|_| H256::random()).collect();
|
||||||
|
|
||||||
|
// 4 / 7 signers is < 67% so no finality.
|
||||||
|
for (i, hash) in hashes.iter().take(8).cloned().enumerate() {
|
||||||
|
let i = i % 4;
|
||||||
|
assert!(finality.push_hash(hash, i as u64, vec![signers[i]]).unwrap().len() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// after pushing a block signed by a fifth validator, the first five
|
||||||
|
// blocks of the unverified chain become verified.
|
||||||
|
assert_eq!(finality.push_hash(hashes[8], 8, vec![signers[4]]).unwrap(),
|
||||||
|
vec![hashes[0], hashes[1], hashes[2], hashes[3], hashes[4]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finalize_multiple_signers_2_3() {
|
||||||
|
let signers: Vec<_> = (0..5).map(|_| Address::random()).collect();
|
||||||
|
let mut finality = RollingFinality::blank(signers.clone(), 0);
|
||||||
|
let hash = H256::random();
|
||||||
|
|
||||||
|
// after pushing a block signed by four validators, it becomes verified right away.
|
||||||
|
assert_eq!(finality.push_hash(hash, 0, signers[0..4].to_vec()).unwrap(), vec![hash]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_ancestry_2_3() {
|
||||||
|
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
||||||
|
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), i as u64, vec![signers[i % 6]])).collect();
|
||||||
|
|
||||||
|
let mut finality = RollingFinality::blank(signers, 0);
|
||||||
|
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
|
||||||
|
|
||||||
|
// The last four hashes, with index 11, 10, 9, and 8, have been pushed. 7 would have finalized a block.
|
||||||
|
assert_eq!(finality.unfinalized_hashes().count(), 4);
|
||||||
|
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_ancestry_multiple_signers_2_3() {
|
||||||
|
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
|
||||||
|
let hashes: Vec<_> = (0..12).map(|i| {
|
||||||
|
let hash_signers = signers.iter().cycle().skip(i).take(4).cloned().collect();
|
||||||
|
(H256::random(), i as u64, hash_signers)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let mut finality = RollingFinality::blank(signers.clone(), 0);
|
||||||
|
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
|
||||||
|
|
||||||
|
// only the last hash has < 67% of authorities' signatures
|
||||||
|
assert_eq!(finality.unfinalized_hashes().count(), 1);
|
||||||
|
assert_eq!(finality.unfinalized_hashes().next(), Some(&hashes[11].0));
|
||||||
|
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! A blockchain engine that supports a non-instant BFT proof-of-authority.
|
//! A blockchain engine that supports a non-instant BFT proof-of-authority.
|
||||||
|
//!
|
||||||
|
//! It is recommended to use the `two_thirds_majority_transition` option, to defend against the
|
||||||
|
//! ["Attack of the Clones"](https://arxiv.org/pdf/1902.10244.pdf). Newly started networks can
|
||||||
|
//! set this option to `0`, to use a 2/3 quorum from the beginning.
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||||
use std::{cmp, fmt};
|
use std::{cmp, fmt};
|
||||||
@ -93,6 +97,8 @@ pub struct AuthorityRoundParams {
|
|||||||
pub maximum_uncle_count: usize,
|
pub maximum_uncle_count: usize,
|
||||||
/// Empty step messages transition block.
|
/// Empty step messages transition block.
|
||||||
pub empty_steps_transition: u64,
|
pub empty_steps_transition: u64,
|
||||||
|
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
||||||
|
pub two_thirds_majority_transition: BlockNumber,
|
||||||
/// Number of accepted empty steps.
|
/// Number of accepted empty steps.
|
||||||
pub maximum_empty_steps: usize,
|
pub maximum_empty_steps: usize,
|
||||||
/// Transition block to strict empty steps validation.
|
/// Transition block to strict empty steps validation.
|
||||||
@ -126,6 +132,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
|||||||
maximum_uncle_count: p.maximum_uncle_count.map_or(0, Into::into),
|
maximum_uncle_count: p.maximum_uncle_count.map_or(0, Into::into),
|
||||||
empty_steps_transition: p.empty_steps_transition.map_or(u64::max_value(), |n| ::std::cmp::max(n.into(), 1)),
|
empty_steps_transition: p.empty_steps_transition.map_or(u64::max_value(), |n| ::std::cmp::max(n.into(), 1)),
|
||||||
maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into),
|
maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into),
|
||||||
|
two_thirds_majority_transition: p.two_thirds_majority_transition.map_or_else(BlockNumber::max_value, Into::into),
|
||||||
strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into),
|
strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,11 +228,11 @@ struct EpochManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EpochManager {
|
impl EpochManager {
|
||||||
fn blank() -> Self {
|
fn blank(two_thirds_majority_transition: BlockNumber) -> Self {
|
||||||
EpochManager {
|
EpochManager {
|
||||||
epoch_transition_hash: H256::zero(),
|
epoch_transition_hash: H256::zero(),
|
||||||
epoch_transition_number: 0,
|
epoch_transition_number: 0,
|
||||||
finality_checker: RollingFinality::blank(Vec::new()),
|
finality_checker: RollingFinality::blank(Vec::new(), two_thirds_majority_transition),
|
||||||
force: true,
|
force: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,7 +296,8 @@ impl EpochManager {
|
|||||||
})
|
})
|
||||||
.expect("proof produced by this engine; therefore it is valid; qed");
|
.expect("proof produced by this engine; therefore it is valid; qed");
|
||||||
|
|
||||||
self.finality_checker = RollingFinality::blank(epoch_set);
|
let two_thirds_majority_transition = self.finality_checker.two_thirds_majority_transition();
|
||||||
|
self.finality_checker = RollingFinality::blank(epoch_set, two_thirds_majority_transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.epoch_transition_hash = last_transition.block_hash;
|
self.epoch_transition_hash = last_transition.block_hash;
|
||||||
@ -452,6 +460,7 @@ pub struct AuthorityRound {
|
|||||||
maximum_uncle_count: usize,
|
maximum_uncle_count: usize,
|
||||||
empty_steps_transition: u64,
|
empty_steps_transition: u64,
|
||||||
strict_empty_steps_transition: u64,
|
strict_empty_steps_transition: u64,
|
||||||
|
two_thirds_majority_transition: BlockNumber,
|
||||||
maximum_empty_steps: usize,
|
maximum_empty_steps: usize,
|
||||||
machine: Machine,
|
machine: Machine,
|
||||||
}
|
}
|
||||||
@ -461,6 +470,8 @@ struct EpochVerifier {
|
|||||||
step: Arc<PermissionedStep>,
|
step: Arc<PermissionedStep>,
|
||||||
subchain_validators: SimpleList,
|
subchain_validators: SimpleList,
|
||||||
empty_steps_transition: u64,
|
empty_steps_transition: u64,
|
||||||
|
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
||||||
|
two_thirds_majority_transition: BlockNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl engine::EpochVerifier for EpochVerifier {
|
impl engine::EpochVerifier for EpochVerifier {
|
||||||
@ -473,7 +484,8 @@ impl engine::EpochVerifier for EpochVerifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> {
|
fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> {
|
||||||
let mut finality_checker = RollingFinality::blank(self.subchain_validators.clone().into_inner());
|
let signers = self.subchain_validators.clone().into_inner();
|
||||||
|
let mut finality_checker = RollingFinality::blank(signers, self.two_thirds_majority_transition);
|
||||||
let mut finalized = Vec::new();
|
let mut finalized = Vec::new();
|
||||||
|
|
||||||
let headers: Vec<Header> = Rlp::new(proof).as_list().ok()?;
|
let headers: Vec<Header> = Rlp::new(proof).as_list().ok()?;
|
||||||
@ -498,7 +510,8 @@ impl engine::EpochVerifier for EpochVerifier {
|
|||||||
};
|
};
|
||||||
signers.push(*parent_header.author());
|
signers.push(*parent_header.author());
|
||||||
|
|
||||||
let newly_finalized = finality_checker.push_hash(parent_header.hash(), signers).ok()?;
|
let newly_finalized =
|
||||||
|
finality_checker.push_hash(parent_header.hash(), parent_header.number(), signers).ok()?;
|
||||||
finalized.extend(newly_finalized);
|
finalized.extend(newly_finalized);
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
@ -708,7 +721,7 @@ impl AuthorityRound {
|
|||||||
validate_score_transition: our_params.validate_score_transition,
|
validate_score_transition: our_params.validate_score_transition,
|
||||||
validate_step_transition: our_params.validate_step_transition,
|
validate_step_transition: our_params.validate_step_transition,
|
||||||
empty_steps: Default::default(),
|
empty_steps: Default::default(),
|
||||||
epoch_manager: Mutex::new(EpochManager::blank()),
|
epoch_manager: Mutex::new(EpochManager::blank(our_params.two_thirds_majority_transition)),
|
||||||
immediate_transitions: our_params.immediate_transitions,
|
immediate_transitions: our_params.immediate_transitions,
|
||||||
block_reward: our_params.block_reward,
|
block_reward: our_params.block_reward,
|
||||||
block_reward_contract_transition: our_params.block_reward_contract_transition,
|
block_reward_contract_transition: our_params.block_reward_contract_transition,
|
||||||
@ -717,6 +730,7 @@ impl AuthorityRound {
|
|||||||
maximum_uncle_count: our_params.maximum_uncle_count,
|
maximum_uncle_count: our_params.maximum_uncle_count,
|
||||||
empty_steps_transition: our_params.empty_steps_transition,
|
empty_steps_transition: our_params.empty_steps_transition,
|
||||||
maximum_empty_steps: our_params.maximum_empty_steps,
|
maximum_empty_steps: our_params.maximum_empty_steps,
|
||||||
|
two_thirds_majority_transition: our_params.two_thirds_majority_transition,
|
||||||
strict_empty_steps_transition: our_params.strict_empty_steps_transition,
|
strict_empty_steps_transition: our_params.strict_empty_steps_transition,
|
||||||
machine,
|
machine,
|
||||||
});
|
});
|
||||||
@ -884,7 +898,7 @@ impl AuthorityRound {
|
|||||||
signers.extend(parent_empty_steps_signers.drain(..));
|
signers.extend(parent_empty_steps_signers.drain(..));
|
||||||
|
|
||||||
if let Ok(empty_step_signers) = header_empty_steps_signers(&header, self.empty_steps_transition) {
|
if let Ok(empty_step_signers) = header_empty_steps_signers(&header, self.empty_steps_transition) {
|
||||||
let res = (header.hash(), signers);
|
let res = (header.hash(), header.number(), signers);
|
||||||
trace!(target: "finality", "Ancestry iteration: yielding {:?}", res);
|
trace!(target: "finality", "Ancestry iteration: yielding {:?}", res);
|
||||||
|
|
||||||
parent_empty_steps_signers = empty_step_signers;
|
parent_empty_steps_signers = empty_step_signers;
|
||||||
@ -897,7 +911,7 @@ impl AuthorityRound {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.while_some()
|
.while_some()
|
||||||
.take_while(|&(h, _)| h != epoch_transition_hash);
|
.take_while(|&(h, _, _)| h != epoch_transition_hash);
|
||||||
|
|
||||||
if let Err(e) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) {
|
if let Err(e) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) {
|
||||||
debug!(target: "engine", "inconsistent validator set within epoch: {:?}", e);
|
debug!(target: "engine", "inconsistent validator set within epoch: {:?}", e);
|
||||||
@ -905,7 +919,8 @@ impl AuthorityRound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let finalized = epoch_manager.finality_checker.push_hash(chain_head.hash(), vec![*chain_head.author()]);
|
let finalized = epoch_manager.finality_checker.push_hash(
|
||||||
|
chain_head.hash(), chain_head.number(), vec![*chain_head.author()]);
|
||||||
finalized.unwrap_or_default()
|
finalized.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1255,6 +1270,11 @@ impl Engine for AuthorityRound {
|
|||||||
parent: &Header,
|
parent: &Header,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut beneficiaries = Vec::new();
|
let mut beneficiaries = Vec::new();
|
||||||
|
|
||||||
|
if block.header.number() == self.two_thirds_majority_transition {
|
||||||
|
info!(target: "engine", "Block {}: Transitioning to 2/3 quorum.", self.two_thirds_majority_transition);
|
||||||
|
}
|
||||||
|
|
||||||
if block.header.number() >= self.empty_steps_transition {
|
if block.header.number() >= self.empty_steps_transition {
|
||||||
let empty_steps = if block.header.seal().is_empty() {
|
let empty_steps = if block.header.seal().is_empty() {
|
||||||
// this is a new block, calculate rewards based on the empty steps messages we have accumulated
|
// this is a new block, calculate rewards based on the empty steps messages we have accumulated
|
||||||
@ -1567,6 +1587,7 @@ impl Engine for AuthorityRound {
|
|||||||
step: self.step.clone(),
|
step: self.step.clone(),
|
||||||
subchain_validators: list,
|
subchain_validators: list,
|
||||||
empty_steps_transition: self.empty_steps_transition,
|
empty_steps_transition: self.empty_steps_transition,
|
||||||
|
two_thirds_majority_transition: self.two_thirds_majority_transition,
|
||||||
});
|
});
|
||||||
|
|
||||||
match finalize {
|
match finalize {
|
||||||
@ -1666,6 +1687,7 @@ mod tests {
|
|||||||
block_reward_contract_transition: 0,
|
block_reward_contract_transition: 0,
|
||||||
block_reward_contract: Default::default(),
|
block_reward_contract: Default::default(),
|
||||||
strict_empty_steps_transition: 0,
|
strict_empty_steps_transition: 0,
|
||||||
|
two_thirds_majority_transition: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutate aura params
|
// mutate aura params
|
||||||
|
@ -58,6 +58,8 @@ pub struct AuthorityRoundParams {
|
|||||||
pub maximum_empty_steps: Option<Uint>,
|
pub maximum_empty_steps: Option<Uint>,
|
||||||
/// Strict validation of empty steps transition block.
|
/// Strict validation of empty steps transition block.
|
||||||
pub strict_empty_steps_transition: Option<Uint>,
|
pub strict_empty_steps_transition: Option<Uint>,
|
||||||
|
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
||||||
|
pub two_thirds_majority_transition: Option<Uint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authority engine deserialization.
|
/// Authority engine deserialization.
|
||||||
|
Loading…
Reference in New Issue
Block a user