Skip to content

Implement Randomized leader election based on the VRF #1841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/consensus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub trait ConsensusEngine: Sync + Send {
}

/// Phase 3 verification. Check block information against parent. Returns either a null `Ok` or a general error detailing the problem with import.
/// The verifiaction must be conducted only with the two headers' information.
fn verify_block_family(&self, _header: &Header, _parent: &Header) -> Result<(), Error> {
Ok(())
}
Expand Down
5 changes: 0 additions & 5 deletions core/src/consensus/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,6 @@ impl EngineSigner {
self.signer.as_ref().map(|(address, _)| address)
}

/// Check if the given address is the signing address.
pub fn is_address(&self, a: &Address) -> bool {
self.signer.map_or(false, |(address, _public)| *a == address)
}

/// Check if the signing address was set.
pub fn is_some(&self) -> bool {
self.signer.is_some()
Expand Down
2 changes: 1 addition & 1 deletion core/src/consensus/sortition/vrf_sortition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub struct VRFSortition {
pub vrf_inst: ECVRF,
}

#[derive(Eq, PartialEq, Clone, Debug, RlpEncodable, RlpDecodable)]
#[derive(Eq, PartialEq, Clone, Default, Debug, RlpEncodable, RlpDecodable)]
pub struct PriorityInfo {
signer_idx: usize,
priority: Priority,
Expand Down
34 changes: 0 additions & 34 deletions core/src/consensus/stake/action_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ impl<'a> Delegation<'a> {

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, RlpDecodable, RlpEncodable)]
pub struct Validator {
weight: StakeQuantity,
delegation: StakeQuantity,
deposit: Deposit,
pubkey: Public,
Expand All @@ -245,7 +244,6 @@ pub struct Validator {
impl Validator {
pub fn new_for_test(delegation: StakeQuantity, deposit: Deposit, pubkey: Public) -> Self {
Self {
weight: delegation,
delegation,
deposit,
pubkey,
Expand All @@ -254,17 +252,12 @@ impl Validator {

fn new(delegation: StakeQuantity, deposit: Deposit, pubkey: Public) -> Self {
Self {
weight: delegation,
delegation,
deposit,
pubkey,
}
}

fn reset(&mut self) {
self.weight = self.delegation;
}

pub fn pubkey(&self) -> &Public {
&self.pubkey
}
Expand Down Expand Up @@ -345,28 +338,6 @@ impl Validators {
Ok(())
}

pub fn update_weight(&mut self, block_author: &Address) {
let min_delegation = self.min_delegation();
for Validator {
weight,
pubkey,
..
} in self.0.iter_mut().rev()
{
if public_to_address(pubkey) == *block_author {
// block author
*weight = weight.saturating_sub(min_delegation);
break
}
// neglecting validators
*weight = weight.saturating_sub(min_delegation * 2);
}
if self.0.iter().all(|validator| validator.weight == 0) {
self.0.iter_mut().for_each(Validator::reset);
}
self.0.sort_unstable();
}

pub fn remove(&mut self, target: &Address) {
self.0.retain(
|Validator {
Expand All @@ -379,10 +350,6 @@ impl Validators {
pub fn delegation(&self, pubkey: &Public) -> Option<StakeQuantity> {
self.0.iter().find(|validator| validator.pubkey == *pubkey).map(|&validator| validator.delegation)
}

fn min_delegation(&self) -> StakeQuantity {
self.0.iter().map(|&validator| validator.delegation).min().expect("There must be at least one validators")
}
}

impl Deref for Validators {
Expand Down Expand Up @@ -1758,7 +1725,6 @@ mod tests {
pubkey: *pubkey,
deposit: 0,
delegation: 0,
weight: 0,
})
.collect(),
);
Expand Down
26 changes: 11 additions & 15 deletions core/src/consensus/stake/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,6 @@ pub fn move_current_to_previous_intermediate_rewards(state: &mut TopLevelState)
rewards.save_to_state(state)
}

pub fn update_validator_weights(state: &mut TopLevelState, block_author: &Address) -> StateResult<()> {
let mut validators = Validators::load_from_state(state)?;
validators.update_weight(block_author);
validators.save_to_state(state)
}

fn change_params(
state: &mut TopLevelState,
metadata_seq: u64,
Expand Down Expand Up @@ -392,17 +386,16 @@ pub fn on_term_close(
let current_term = metadata.current_term_id();
ctrace!(ENGINE, "on_term_close. current_term: {}", current_term);

let metadata = metadata.params().expect(
"Term close events can be called after the ChangeParams called, \
so the metadata always has CommonParams",
);
let custody_period = metadata.custody_period();
let release_period = metadata.release_period();

let (nomination_expiration, custody_until, kick_at) = {
let metadata = metadata.params().expect(
"Term close events can be called after the ChangeParams called, \
so the metadata always has CommonParams",
);
let nomination_expiration = metadata.nomination_expiration();
assert_ne!(0, nomination_expiration);
let custody_period = metadata.custody_period();
assert_ne!(0, custody_period);
let release_period = metadata.release_period();
assert_ne!(0, release_period);
(nomination_expiration, current_term + custody_period, current_term + release_period)
};

Expand All @@ -412,7 +405,10 @@ pub fn on_term_close(
let reverted: Vec<_> = expired.into_iter().chain(released).collect();
revert_delegations(state, &reverted)?;

jail(state, inactive_validators, custody_until, kick_at)?;
let jail_enabled = custody_period != 0 || release_period != 0;
if jail_enabled {
jail(state, inactive_validators, custody_until, kick_at)?;
}

let validators = Validators::elect(state)?;
validators.save_to_state(state)?;
Expand Down
120 changes: 51 additions & 69 deletions core/src/consensus/tendermint/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,77 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use ctypes::BlockHash;
use kvdb::{DBTransaction, KeyValueDB};
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};

use super::message::ConsensusMessage;
use super::message::{ConsensusMessage, SortitionRound};
use super::types::{Height, Step, View};
use crate::consensus::PriorityInfo;
use crate::db;
use crate::db_version;

const BACKUP_KEY: &[u8] = b"tendermint-backup";
const BACKUP_VERSION: u32 = 1;

pub struct PriorityInfoProjection(pub (SortitionRound, PriorityInfo));

impl PriorityInfoProjection {
pub fn get_view(&self) -> PriorityInfoProjectionView {
let (ref sortition_round, ref priority_info) = self.0;
PriorityInfoProjectionView((sortition_round, priority_info))
}
}

impl Decodable for PriorityInfoProjection {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
let item_count = rlp.item_count()?;
if item_count != 2 {
Err(DecoderError::RlpIncorrectListLen {
got: item_count,
expected: 2,
})
} else {
Ok(Self((rlp.val_at(0)?, rlp.val_at(1)?)))
}
}
}

pub struct PriorityInfoProjectionView<'a>(pub (&'a SortitionRound, &'a PriorityInfo));

impl Encodable for PriorityInfoProjectionView<'_> {
fn rlp_append(&self, s: &mut RlpStream) {
let (sortition_round, priority_info) = self.0;
s.begin_list(2).append(sortition_round).append(priority_info);
}
}

pub struct BackupView<'a> {
pub height: &'a Height,
pub view: &'a View,
pub step: &'a Step,
pub votes: &'a [ConsensusMessage],
pub priority_infos: &'a [PriorityInfoProjectionView<'a>],
pub finalized_view_of_previous_block: &'a View,
pub finalized_view_of_current_block: &'a Option<View>,
}

#[derive(RlpDecodable)]
pub struct BackupDataV0 {
pub height: Height,
pub view: View,
pub step: Step,
pub votes: Vec<ConsensusMessage>,
pub proposal: Option<BlockHash>,
pub priority_infos: Vec<PriorityInfoProjection>,
pub last_confirmed_view: View,
}

#[derive(RlpDecodable)]
pub struct BackupDataV1 {
pub height: Height,
pub view: View,
pub step: Step,
pub votes: Vec<ConsensusMessage>,
pub proposal: Option<BlockHash>,
pub priority_infos: Vec<PriorityInfoProjection>,
pub finalized_view_of_previous_block: View,
pub finalized_view_of_current_block: Option<View>,
}
Expand All @@ -59,12 +95,14 @@ pub fn backup(db: &dyn KeyValueDB, backup_data: BackupView) {
view,
step,
votes,
priority_infos,
finalized_view_of_previous_block,
finalized_view_of_current_block,
} = backup_data;
let mut s = rlp::RlpStream::new();
s.begin_list(6);
s.begin_list(7);
s.append(height).append(view).append(step).append_list(votes);
s.append_list(priority_infos);
s.append(finalized_view_of_previous_block);
s.append(finalized_view_of_current_block);

Expand All @@ -83,19 +121,7 @@ pub fn restore(db: &dyn KeyValueDB) -> Option<BackupDataV1> {
if version < BACKUP_VERSION {
migrate(db);
}
load_v1(db)
}

fn find_proposal(votes: &[ConsensusMessage], height: Height, view: View) -> Option<BlockHash> {
votes
.iter()
.rev()
.map(|vote| &vote.on)
.find(|vote_on| {
vote_on.step.step == Step::Propose && vote_on.step.view == view && vote_on.step.height == height
})
.map(|vote_on| vote_on.block_hash)
.unwrap_or(None)
load_with_version::<BackupDataV1>(db)
}

fn migrate(db: &dyn KeyValueDB) {
Expand All @@ -114,7 +140,7 @@ fn migrate(db: &dyn KeyValueDB) {
}

fn migrate_from_0_to_1(db: &dyn KeyValueDB) {
let v0 = if let Some(v0) = load_v0(db) {
let v0 = if let Some(v0) = load_with_version::<BackupDataV0>(db) {
v0
} else {
return
Expand All @@ -125,7 +151,7 @@ fn migrate_from_0_to_1(db: &dyn KeyValueDB) {
view: v0.view,
step: v0.step,
votes: v0.votes,
proposal: v0.proposal,
priority_infos: v0.priority_infos,
// This is not a correct behavior if step == Step::Commit.
// In Commit state, the Tendermint module overwrote the last_confirmed_view to finalized_view_of_current_block.
// So we can't restore finalized_view_of_previous block.
Expand All @@ -142,59 +168,15 @@ fn migrate_from_0_to_1(db: &dyn KeyValueDB) {
view: &v1.view,
step: &v1.step,
votes: &v1.votes,
priority_infos: &v1.priority_infos.iter().map(|projection| projection.get_view()).collect::<Vec<_>>(),
finalized_view_of_previous_block: &v1.finalized_view_of_previous_block,
finalized_view_of_current_block: &v1.finalized_view_of_current_block,
})
}

fn load_v0(db: &dyn KeyValueDB) -> Option<BackupDataV0> {
let value = db.get(db::COL_EXTRA, BACKUP_KEY).expect("Low level database error. Some issue with disk?");
let (height, view, step, votes, last_confirmed_view) = value.map(|bytes| {
let rlp = rlp::Rlp::new(&bytes);
(
rlp.val_at(0).unwrap(),
rlp.val_at(1).unwrap(),
rlp.val_at(2).unwrap(),
rlp.at(3).unwrap().as_list().unwrap(),
rlp.val_at(4).unwrap(),
)
})?;

let proposal = find_proposal(&votes, height, view);

Some(BackupDataV0 {
height,
view,
step,
votes,
proposal,
last_confirmed_view,
})
}

fn load_v1(db: &dyn KeyValueDB) -> Option<BackupDataV1> {
#[derive(RlpDecodable)]
struct Backup {
height: Height,
view: View,
step: Step,
votes: Vec<ConsensusMessage>,
finalized_view_of_previous_block: View,
finalized_view_of_current_block: Option<View>,
}

fn load_with_version<T: Decodable>(db: &dyn KeyValueDB) -> Option<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, it is a fantastic way to load data.

let value = db.get(db::COL_EXTRA, BACKUP_KEY).expect("Low level database error. Some issue with disk?")?;
let backup: Backup = rlp::decode(&value).unwrap();

let proposal = find_proposal(&backup.votes, backup.height, backup.view);

Some(BackupDataV1 {
height: backup.height,
view: backup.view,
step: backup.step,
votes: backup.votes,
proposal,
finalized_view_of_previous_block: backup.finalized_view_of_previous_block,
finalized_view_of_current_block: backup.finalized_view_of_current_block,
})
let backup: T = rlp::decode(&value).unwrap();

Some(backup)
}
1 change: 0 additions & 1 deletion core/src/consensus/tendermint/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ impl ConsensusEngine for Tendermint {
self.machine.add_balance(block, &author, block_author_reward)?;
}
_ => {
stake::update_validator_weights(block.state_mut(), &author)?;
stake::add_intermediate_rewards(block.state_mut(), author, block_author_reward)?;
}
}
Expand Down
Loading