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 proposal_threshold = proposal_threshold_u256.to_big_endian();
42        Self {
43            author,
44            vrf_private_key,
45            proposal_threshold: HashValue::new(proposal_threshold),
46            // current_round and current_seed will not be used before
47            // `next_round` is called.
48            current_round: Mutex::new(0),
49            current_seed: Mutex::new(vec![]),
50            proposal_candidates: Default::default(),
51            epoch_state,
52        }
53    }
54
55    pub fn get_vrf_number(&self, block: &Block) -> Option<HashValue> {
56        Some(vrf_number_with_nonce(
57            &block.vrf_proof()?.to_hash().ok()?,
58            block.vrf_nonce()?,
59        ))
60    }
61}
62
63impl ProposerElection for VrfProposer {
64    fn get_valid_proposer(&self, _round: Round) -> Author {
65        unreachable!(
66            "We will never get valid proposer based on round for VRF election"
67        )
68    }
69
70    fn is_valid_proposer(&self, author: Author, round: Round) -> bool {
71        assert_eq!(
72            author, self.author,
73            "VRF election can not check proposer validity without vrf_proof"
74        );
75        assert_eq!(
76            round,
77            *self.current_round.lock(),
78            "VRF election can not generate vrf_proof for other rounds"
79        );
80        let voting_power =
81            match self.epoch_state.verifier().get_voting_power(&author) {
82                None => return false,
83                Some(p) => p,
84            };
85        // TODO(lpl): Unify seed computation and avoid duplicate computation
86        // with `gen_vrf_nonce_and_proof`.
87        let mut round_seed = self.current_seed.lock().clone();
88        let leader_round = (round + 1) / 3;
89        round_seed.extend_from_slice(&leader_round.to_be_bytes());
90        let vrf_output = self
91            .vrf_private_key
92            .compute(round_seed.as_slice())
93            .expect("vrf compute fail")
94            .to_hash()
95            .expect("to hash error");
96        for nonce in 0..=voting_power {
97            let vrf_number = vrf_number_with_nonce(&vrf_output, nonce);
98            if vrf_number <= self.proposal_threshold {
99                return true;
100            }
101        }
102        false
103    }
104
105    fn is_valid_proposal(&self, block: &Block) -> bool {
106        let author = match block.author() {
107            Some(author) => author,
108            None => return false,
109        };
110        let voting_power =
111            match self.epoch_state.verifier().get_voting_power(&author) {
112                None => return false,
113                Some(p) => p,
114            };
115        let (nonce, vrf_proof) = match (block.vrf_nonce(), block.vrf_proof()) {
116            (Some(nonce), Some(vrf_proof)) => (nonce, vrf_proof),
117            _ => return false,
118        };
119        if nonce > voting_power || *self.current_round.lock() != block.round() {
120            return false;
121        }
122        let seed = block
123            .block_data()
124            .vrf_round_seed(self.current_seed.lock().as_slice());
125        let vrf_hash = match self
126            .epoch_state
127            .verifier()
128            .get_vrf_public_key(&author)
129        {
130            Some(Some(vrf_public_key)) => {
131                match vrf_proof.verify(seed.as_slice(), &vrf_public_key) {
132                    Ok(vrf_hash) => vrf_hash,
133                    Err(e) => {
134                        diem_debug!("is_valid_proposal: invalid proposal err={:?}, block={:?}", e, block);
135                        return false;
136                    }
137                }
138            }
139            _ => {
140                diem_debug!(
141                    "Receive block from non-validator: author={:?}",
142                    block.author()
143                );
144                return false;
145            }
146        };
147        vrf_number_with_nonce(&vrf_hash, nonce) <= self.proposal_threshold
148    }
149
150    fn is_random_election(&self) -> bool { true }
151
152    /// Return `Err` for unmatching blocks.
153    /// Return `Ok(true)` if the block has less vrf_output.
154    /// Return `Ok(false)` if the block has a higher or equal vrf_output. This
155    /// block should not be relayed in this case.
156    fn receive_proposal_candidate(
157        &self, block: &Block,
158    ) -> anyhow::Result<bool> {
159        let current_round = *self.current_round.lock();
160        if block.round() < current_round {
161            anyhow::bail!("Incorrect round");
162        } else if block.round() > current_round {
163            // The proposal is in the future, so it should pass
164            // filter_unverified_event and proceed to trigger
165            // sync_up.
166            return Ok(true);
167        }
168        // `block` is not yet verified on the `filter_proposal` path.
169        let block_vrf_number = self.get_vrf_number(block).ok_or_else(|| {
170            anyhow::anyhow!("proposal missing vrf proof/nonce")
171        })?;
172        let old_proposal = self.proposal_candidates.lock();
173        match old_proposal.as_ref().and_then(|b| self.get_vrf_number(b)) {
174            Some(old_vrf_number) => Ok(old_vrf_number > block_vrf_number),
175            None => Ok(true),
176        }
177    }
178
179    fn set_proposal_candidate(&self, block: Block) {
180        *self.proposal_candidates.lock() = Some(block);
181    }
182
183    /// Choose a proposal from all received proposal candidates to vote for.
184    fn choose_proposal_to_vote(&self) -> Option<Block> {
185        let chosen_proposal = self.proposal_candidates.lock().clone();
186        diem_debug!(
187            "choose_proposal_to_vote: {:?}, data={:?}",
188            chosen_proposal,
189            chosen_proposal.as_ref().map(|b| b.block_data())
190        );
191        chosen_proposal
192    }
193
194    fn next_round(&self, round: Round, new_seed: Vec<u8>) {
195        *self.current_round.lock() = round;
196        self.proposal_candidates.lock().take();
197        *self.current_seed.lock() = new_seed;
198    }
199
200    fn gen_vrf_nonce_and_proof(
201        &self, block_data: &BlockData,
202    ) -> Option<(u64, ConsensusVRFProof)> {
203        let mut min_vrf_number = self.proposal_threshold;
204        let mut best_nonce = None;
205        let voting_power = self
206            .epoch_state
207            .verifier()
208            .get_voting_power(&block_data.author()?)?;
209
210        let vrf_proof = self
211            .vrf_private_key
212            .compute(
213                block_data
214                    .vrf_round_seed(self.current_seed.lock().as_slice())
215                    .as_slice(),
216            )
217            .ok()?;
218        let vrf_output = vrf_proof.to_hash().ok()?;
219        for nonce in 0..=voting_power {
220            let vrf_number = vrf_number_with_nonce(&vrf_output, nonce);
221            if vrf_number <= min_vrf_number {
222                min_vrf_number = vrf_number;
223                best_nonce = Some(nonce);
224            }
225        }
226        best_nonce.map(|nonce| (nonce, vrf_proof))
227    }
228}