Skip to content
New issue

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

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

Already on GitHub? # to your account

Implement Randomized leader election based on the VRF #1841

Merged
merged 13 commits into from
Jan 28, 2020
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