consensus_types/
proposal_msg.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::{block::Block, common::Author, sync_info::SyncInfo};
9use anyhow::{anyhow, ensure, format_err, Context, Result};
10use diem_types::validator_verifier::ValidatorVerifier;
11use serde::{Deserialize, Serialize};
12use std::fmt;
13
14/// ProposalMsg contains the required information for the proposer election
15/// protocol to make its choice (typically depends on round and proposer info).
16#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
17pub struct ProposalMsg {
18    proposal: Block,
19    sync_info: SyncInfo,
20}
21
22impl ProposalMsg {
23    /// Creates a new proposal.
24    pub fn new(proposal: Block, sync_info: SyncInfo) -> Self {
25        Self {
26            proposal,
27            sync_info,
28        }
29    }
30
31    pub fn epoch(&self) -> u64 { self.proposal.epoch() }
32
33    /// Verifies that the ProposalMsg is well-formed.
34    pub fn verify_well_formed(&self) -> Result<()> {
35        ensure!(
36            !self.proposal.is_nil_block(),
37            "Proposal {} for a NIL block",
38            self.proposal
39        );
40        self.proposal
41            .verify_well_formed()
42            .context("Fail to verify ProposalMsg's block")?;
43        ensure!(
44            self.proposal.round() > 0,
45            "Proposal for {} has an incorrect round of 0",
46            self.proposal,
47        );
48        ensure!(
49            self.proposal.epoch() == self.sync_info.epoch(),
50            "ProposalMsg has different epoch number from SyncInfo"
51        );
52        ensure!(
53            self.proposal.parent_id()
54                == self.sync_info.highest_quorum_cert().certified_block().id(),
55            "Proposal HQC in SyncInfo certifies {}, but block parent id is {}",
56            self.sync_info.highest_quorum_cert().certified_block().id(),
57            self.proposal.parent_id(),
58        );
59        let previous_round = self
60            .proposal
61            .round()
62            .checked_sub(1)
63            .ok_or_else(|| anyhow!("proposal round overflowed!"))?;
64
65        let highest_certified_round = std::cmp::max(
66            self.proposal.quorum_cert().certified_block().round(),
67            self.sync_info
68                .highest_timeout_certificate()
69                .map_or(0, |tc| tc.round()),
70        );
71        ensure!(
72            previous_round == highest_certified_round,
73            "Proposal {} does not have a certified round {}",
74            self.proposal,
75            previous_round
76        );
77        ensure!(
78            self.proposal.author().is_some(),
79            "Proposal {} does not define an author",
80            self.proposal
81        );
82        Ok(())
83    }
84
85    pub fn verify(
86        &self, validator: &ValidatorVerifier, epoch_vrf_seed: &[u8],
87    ) -> Result<()> {
88        // Run well-formedness first: it rejects NilBlock/Genesis and
89        // guarantees `author().is_some()` for the VRF branch below.
90        self.verify_well_formed()?;
91        self.proposal
92            .validate_signature(validator)
93            .map_err(|e| format_err!("{:?}", e))?;
94
95        if let Some(vrf_proof) = self.proposal.vrf_proof() {
96            let author = self.proposal.author().ok_or_else(|| {
97                format_err!("VRF proof present but block has no author")
98            })?;
99            validator.verify_vrf(
100                author,
101                &self.proposal.block_data().vrf_round_seed(epoch_vrf_seed),
102                vrf_proof,
103            )?;
104        }
105        if let Some(tc) = self.sync_info.highest_timeout_certificate() {
106            tc.verify(validator).map_err(|e| format_err!("{:?}", e))?;
107        }
108        Ok(())
109    }
110
111    pub fn proposal(&self) -> &Block { &self.proposal }
112
113    pub fn take_proposal(self) -> Block { self.proposal }
114
115    pub fn sync_info(&self) -> &SyncInfo { &self.sync_info }
116
117    /// `None` for `NilBlock` / `Genesis`. Peer messages may carry either
118    /// before validation, so we don't panic here.
119    pub fn proposer(&self) -> Option<Author> { self.proposal.author() }
120}
121
122impl fmt::Display for ProposalMsg {
123    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124        write!(f, "[proposal {} from ", self.proposal)?;
125        match self.proposal.author() {
126            Some(author) => write!(f, "{}]", hex::encode(&author[..4])),
127            None => write!(f, "NIL]"),
128        }
129    }
130}