cfxcore/pos/consensus/liveness/
vrf_proposer_election.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8use crate::pos::consensus::liveness::proposer_election::ProposerElection;
9use consensus_types::common::{Author, Round};
10
11use cfx_types::U256;
12use consensus_types::{block::Block, block_data::BlockData};
13use diem_crypto::{vrf_number_with_nonce, HashValue, VRFPrivateKey, VRFProof};
14use diem_logger::debug as diem_debug;
15use diem_types::{
16    epoch_state::EpochState,
17    validator_config::{ConsensusVRFPrivateKey, ConsensusVRFProof},
18};
19use parking_lot::Mutex;
20
21/// The round proposer maps a round to author
22pub struct VrfProposer {
23    author: Author,
24    vrf_private_key: ConsensusVRFPrivateKey,
25
26    proposal_threshold: HashValue,
27
28    current_round: Mutex<Round>,
29    current_seed: Mutex<Vec<u8>>,
30    proposal_candidates: Mutex<Option<Block>>,
31
32    // The epoch state of `current_round`, used to verify proposals.
33    epoch_state: EpochState,
34}
35
36impl VrfProposer {
37    pub fn new(
38        author: Author, vrf_private_key: ConsensusVRFPrivateKey,
39        proposal_threshold_u256: U256, epoch_state: EpochState,
40    ) -> Self {
41        let mut proposal_threshold = [0 as u8; HashValue::LENGTH];
42        proposal_threshold_u256.to_big_endian(&mut proposal_threshold);
43        Self {
44            author,
45            vrf_private_key,
46            proposal_threshold: HashValue::new(proposal_threshold),
47            // current_round and current_seed will not be used before
48            // `next_round` is called.
49            current_round: Mutex::new(0),
50            current_seed: Mutex::new(vec![]),
51            proposal_candidates: Default::default(),
52            epoch_state,
53        }
54    }
55
56    pub fn get_vrf_number(&self, block: &Block) -> Option<HashValue> {
57        Some(vrf_number_with_nonce(
58            &block.vrf_proof()?.to_hash().ok()?,
59            block.vrf_nonce()?,
60        ))
61    }
62}
63
64impl ProposerElection for VrfProposer {
65    fn get_valid_proposer(&self, _round: Round) -> Author {
66        unreachable!(
67            "We will never get valid proposer based on round for VRF election"
68        )
69    }
70
71    fn is_valid_proposer(&self, author: Author, round: Round) -> bool {
72        assert_eq!(
73            author, self.author,
74            "VRF election can not check proposer validity without vrf_proof"
75        );
76        assert_eq!(
77            round,
78            *self.current_round.lock(),
79            "VRF election can not generate vrf_proof for other rounds"
80        );
81        let voting_power =
82            match self.epoch_state.verifier().get_voting_power(&author) {
83                None => return false,
84                Some(p) => p,
85            };
86        // TODO(lpl): Unify seed computation and avoid duplicate computation
87        // with `gen_vrf_nonce_and_proof`.
88        let mut round_seed = self.current_seed.lock().clone();
89        let leader_round = (round + 1) / 3;
90        round_seed.extend_from_slice(&leader_round.to_be_bytes());
91        let vrf_output = self
92            .vrf_private_key
93            .compute(round_seed.as_slice())
94            .expect("vrf compute fail")
95            .to_hash()
96            .expect("to hash error");
97        for nonce in 0..=voting_power {
98            let vrf_number = vrf_number_with_nonce(&vrf_output, nonce);
99            if vrf_number <= self.proposal_threshold {
100                return true;
101            }
102        }
103        false
104    }
105
106    fn is_valid_proposal(&self, block: &Block) -> bool {
107        let voting_power = match self
108            .epoch_state
109            .verifier()
110            .get_voting_power(&block.author().expect("checked"))
111        {
112            None => return false,
113            Some(p) => p,
114        };
115        let nonce = block.vrf_nonce().expect("checked");
116        if nonce > voting_power || *self.current_round.lock() != block.round() {
117            return false;
118        }
119        let seed = block
120            .block_data()
121            .vrf_round_seed(self.current_seed.lock().as_slice());
122        let vrf_hash = match self
123            .epoch_state
124            .verifier()
125            .get_vrf_public_key(&block.author().expect("checked"))
126        {
127            Some(Some(vrf_public_key)) => {
128                match block
129                    .vrf_proof()
130                    .unwrap()
131                    .verify(seed.as_slice(), &vrf_public_key)
132                {
133                    Ok(vrf_hash) => vrf_hash,
134                    Err(e) => {
135                        diem_debug!("is_valid_proposal: invalid proposal err={:?}, block={:?}", e, block);
136                        return false;
137                    }
138                }
139            }
140            _ => {
141                diem_debug!(
142                    "Receive block from non-validator: author={:?}",
143                    block.author()
144                );
145                return false;
146            }
147        };
148        vrf_number_with_nonce(&vrf_hash, nonce) <= self.proposal_threshold
149    }
150
151    fn is_random_election(&self) -> bool { true }
152
153    /// Return `Err` for unmatching blocks.
154    /// Return `Ok(true)` if the block has less vrf_output.
155    /// Return `Ok(false)` if the block has a higher or equal vrf_output. This
156    /// block should not be relayed in this case.
157    fn receive_proposal_candidate(
158        &self, block: &Block,
159    ) -> anyhow::Result<bool> {
160        // Proposal validity should have been checked in `process_proposal`.
161        let current_round = *self.current_round.lock();
162        if block.round() < current_round {
163            anyhow::bail!("Incorrect round");
164        } else if block.round() > current_round {
165            // The proposal is in the future, so it should pass
166            // filter_unverified_event and proceed to trigger
167            // sync_up.
168            return Ok(true);
169        }
170        let old_proposal = self.proposal_candidates.lock();
171        if old_proposal.is_none()
172            || self.get_vrf_number(old_proposal.as_ref().unwrap()).unwrap()
173                > self.get_vrf_number(block).unwrap()
174        {
175            Ok(true)
176        } else {
177            Ok(false)
178        }
179    }
180
181    fn set_proposal_candidate(&self, block: Block) {
182        *self.proposal_candidates.lock() = Some(block);
183    }
184
185    /// Choose a proposal from all received proposal candidates to vote for.
186    fn choose_proposal_to_vote(&self) -> Option<Block> {
187        let chosen_proposal = self.proposal_candidates.lock().clone();
188        diem_debug!(
189            "choose_proposal_to_vote: {:?}, data={:?}",
190            chosen_proposal,
191            chosen_proposal.as_ref().map(|b| b.block_data())
192        );
193        chosen_proposal
194    }
195
196    fn next_round(&self, round: Round, new_seed: Vec<u8>) {
197        *self.current_round.lock() = round;
198        self.proposal_candidates.lock().take();
199        *self.current_seed.lock() = new_seed;
200    }
201
202    fn gen_vrf_nonce_and_proof(
203        &self, block_data: &BlockData,
204    ) -> Option<(u64, ConsensusVRFProof)> {
205        let mut min_vrf_number = self.proposal_threshold;
206        let mut best_nonce = None;
207        let voting_power = self
208            .epoch_state
209            .verifier()
210            .get_voting_power(&block_data.author()?)?;
211
212        let vrf_proof = self
213            .vrf_private_key
214            .compute(
215                block_data
216                    .vrf_round_seed(self.current_seed.lock().as_slice())
217                    .as_slice(),
218            )
219            .ok()?;
220        let vrf_output = vrf_proof.to_hash().ok()?;
221        for nonce in 0..=voting_power {
222            let vrf_number = vrf_number_with_nonce(&vrf_output, nonce);
223            if vrf_number <= min_vrf_number {
224                min_vrf_number = vrf_number;
225                best_nonce = Some(nonce);
226            }
227        }
228        best_nonce.map(|nonce| (nonce, vrf_proof))
229    }
230}