safety_rules/
safety_rules.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::{
9    configurable_validator_signer::ConfigurableValidatorSigner,
10    consensus_state::ConsensusState,
11    error::Error,
12    logging::{LogEntry, LogEvent, SafetyLogSchema},
13    persistent_safety_storage::PersistentSafetyStorage,
14};
15use consensus_types::{
16    block::Block,
17    block_data::BlockData,
18    common::{Author, Round},
19    quorum_cert::QuorumCert,
20    safety_data::SafetyData,
21    timeout::Timeout,
22    vote::Vote,
23    vote_data::VoteData,
24    vote_proposal::{MaybeSignedVoteProposal, VoteProposal},
25};
26use diem_crypto::{
27    hash::{CryptoHash, HashValue},
28    traits::Signature,
29    PrivateKey,
30};
31use diem_logger::prelude::*;
32use diem_types::{
33    account_address::AccountAddress,
34    block_info::BlockInfo,
35    epoch_state::EpochState,
36    ledger_info::LedgerInfo,
37    validator_config::{ConsensusSignature, ConsensusVRFPrivateKey},
38};
39use log::error;
40use serde::Serialize;
41use std::cmp::Ordering;
42
43const SAFETY_STORAGE_SAVE_SUFFIX: &str = "json.save";
44
45/// @TODO consider a cache of verified QCs to cut down on verification costs
46pub struct SafetyRules {
47    persistent_storage: PersistentSafetyStorage,
48    export_consensus_key: bool,
49    validator_signer: Option<ConfigurableValidatorSigner>,
50    epoch_state: Option<EpochState>,
51    vrf_private_key: Option<ConsensusVRFPrivateKey>,
52}
53
54impl SafetyRules {
55    /// Constructs a new instance of SafetyRules with the given persistent
56    /// storage and the consensus private keys
57    pub fn new(
58        persistent_storage: PersistentSafetyStorage,
59        export_consensus_key: bool,
60        vrf_private_key: Option<ConsensusVRFPrivateKey>,
61        author: AccountAddress,
62    ) -> Self {
63        if let Ok(storage_author) = persistent_storage.author() {
64            if storage_author != author {
65                // TODO: After we allow non-voting nodes to not have a pos key,
66                // we can panic here so pos-voting nodes can be aware of this
67                // issue.
68                diem_error!(
69                    "author in secure_storage does not match PoS keys!"
70                );
71                error!("author in secure_storage does not match PoS keys!");
72            }
73        }
74        Self {
75            persistent_storage,
76            export_consensus_key,
77            validator_signer: None,
78            epoch_state: None,
79            vrf_private_key,
80        }
81    }
82
83    fn sign<T: Serialize + CryptoHash>(
84        &self, message: &T,
85    ) -> Result<ConsensusSignature, Error> {
86        let signer = self.signer()?;
87        signer.sign(message, &self.persistent_storage)
88    }
89
90    fn signer(&self) -> Result<&ConfigurableValidatorSigner, Error> {
91        self.validator_signer
92            .as_ref()
93            .ok_or_else(|| Error::NotInitialized("validator_signer".into()))
94    }
95
96    fn epoch_state(&self) -> Result<&EpochState, Error> {
97        self.epoch_state
98            .as_ref()
99            .ok_or_else(|| Error::NotInitialized("epoch_state".into()))
100    }
101
102    /// Check if the executed result extends the parent result.
103    pub fn extension_check(
104        vote_proposal: &VoteProposal,
105    ) -> Result<VoteData, Error> {
106        let proposed_block = vote_proposal.block();
107        let new_tree = vote_proposal
108            .accumulator_extension_proof()
109            .verify(
110                proposed_block
111                    .quorum_cert()
112                    .certified_block()
113                    .executed_state_id(),
114            )
115            .map_err(|e| Error::InvalidAccumulatorExtension(e.to_string()))?;
116        Ok(VoteData::new(
117            proposed_block.gen_block_info(
118                Default::default(),
119                new_tree.version(),
120                vote_proposal.next_epoch_state().cloned(),
121                vote_proposal.pivot_decision().clone(),
122            ),
123            proposed_block.quorum_cert().certified_block().clone(),
124        ))
125    }
126
127    /// Produces a LedgerInfo that either commits a block based upon the 3-chain
128    /// commit rule or an empty LedgerInfo for no commit. The 3-chain commit
129    /// rule is: B0 and its prefixes can be committed if there exist
130    /// certified blocks B1 and B2 that satisfy: 1) B0 <- B1 <- B2 <--
131    /// 2) round(B0) + 1 = round(B1), and
132    /// 3) round(B1) + 1 = round(B2).
133    pub fn construct_ledger_info(
134        proposed_block: &Block, consensus_data_hash: HashValue,
135    ) -> Result<LedgerInfo, Error> {
136        let block2 = proposed_block.round();
137        let block1 = proposed_block.quorum_cert().certified_block().round();
138        let block0 = proposed_block.quorum_cert().parent_block().round();
139
140        // verify 3-chain rule
141        let next_round = |round: u64| {
142            u64::checked_add(round, 1).ok_or(Error::IncorrectRound(round))
143        };
144        let commit =
145            next_round(block0)? == block1 && next_round(block1)? == block2;
146
147        // create a ledger info
148        let commit_info = if commit {
149            proposed_block.quorum_cert().parent_block().clone()
150        } else {
151            BlockInfo::empty()
152        };
153
154        Ok(LedgerInfo::new(commit_info, consensus_data_hash))
155    }
156
157    /// Second voting rule
158    fn verify_and_update_preferred_round(
159        &mut self, quorum_cert: &QuorumCert, safety_data: &mut SafetyData,
160    ) -> Result<bool, Error> {
161        let preferred_round = safety_data.preferred_round;
162        let one_chain_round = quorum_cert.certified_block().round();
163        let two_chain_round = quorum_cert.parent_block().round();
164
165        if one_chain_round < preferred_round {
166            return Err(Error::IncorrectPreferredRound(
167                one_chain_round,
168                preferred_round,
169            ));
170        }
171
172        let updated = match two_chain_round.cmp(&preferred_round) {
173            Ordering::Greater => {
174                safety_data.preferred_round = two_chain_round;
175                diem_info!(SafetyLogSchema::new(
176                    LogEntry::PreferredRound,
177                    LogEvent::Update
178                )
179                .preferred_round(safety_data.preferred_round));
180                true
181            }
182            Ordering::Less => {
183                diem_trace!(
184                "2-chain round {} is lower than preferred round {} but 1-chain round {} is higher.",
185                two_chain_round, preferred_round, one_chain_round
186            );
187                false
188            }
189            Ordering::Equal => false,
190        };
191        Ok(updated)
192    }
193
194    /// This verifies whether the author of one proposal is the validator signer
195    fn verify_author(&self, author: Option<Author>) -> Result<(), Error> {
196        let validator_signer_author = &self.signer()?.author();
197        let author = author.ok_or_else(|| {
198            Error::InvalidProposal("No author found in the proposal".into())
199        })?;
200        if validator_signer_author != &author {
201            return Err(Error::InvalidProposal(
202                "Proposal author is not validator signer!".into(),
203            ));
204        }
205        Ok(())
206    }
207
208    /// This verifies the epoch given against storage for consistent
209    /// verification
210    fn verify_epoch(
211        &self, epoch: u64, safety_data: &SafetyData,
212    ) -> Result<(), Error> {
213        if epoch != safety_data.epoch {
214            return Err(Error::IncorrectEpoch(epoch, safety_data.epoch));
215        }
216
217        Ok(())
218    }
219
220    /// First voting rule
221    fn verify_and_update_last_vote_round(
222        &self, round: Round, safety_data: &mut SafetyData,
223    ) -> Result<(), Error> {
224        if round <= safety_data.last_voted_round {
225            return Err(Error::IncorrectLastVotedRound(
226                round,
227                safety_data.last_voted_round,
228            ));
229        }
230
231        safety_data.last_voted_round = round;
232        diem_info!(SafetyLogSchema::new(
233            LogEntry::LastVotedRound,
234            LogEvent::Update
235        )
236        .last_voted_round(safety_data.last_voted_round));
237
238        Ok(())
239    }
240
241    /// This verifies a QC has valid signatures.
242    fn verify_qc(&self, qc: &QuorumCert) -> Result<(), Error> {
243        let epoch_state = self.epoch_state()?;
244
245        qc.verify(&epoch_state.verifier())
246            .map_err(|e| Error::InvalidQuorumCertificate(e.to_string()))?;
247        Ok(())
248    }
249
250    // Internal functions mapped to the public interface to enable exhaustive
251    // logging and metrics
252
253    fn guarded_consensus_state(&mut self) -> Result<ConsensusState, Error> {
254        let safety_data = self.persistent_storage.safety_data()?;
255
256        diem_info!(SafetyLogSchema::new(LogEntry::State, LogEvent::Update)
257            .author(self.persistent_storage.author()?)
258            .epoch(safety_data.epoch)
259            .last_voted_round(safety_data.last_voted_round)
260            .preferred_round(safety_data.preferred_round));
261
262        Ok(ConsensusState::new(
263            self.persistent_storage.safety_data()?,
264            self.signer().is_ok(),
265        ))
266    }
267
268    fn guarded_initialize(
269        &mut self, epoch_state: &EpochState,
270    ) -> Result<(), Error> {
271        let current_epoch = self.persistent_storage.safety_data()?.epoch;
272
273        if current_epoch < epoch_state.epoch {
274            self.persistent_storage.set_safety_data(SafetyData::new(
275                epoch_state.epoch,
276                0,
277                0,
278                None,
279            ))?;
280
281            diem_info!(SafetyLogSchema::new(LogEntry::Epoch, LogEvent::Update)
282                .epoch(epoch_state.epoch));
283        }
284        self.epoch_state = Some(epoch_state.clone());
285
286        let author = self.persistent_storage.author()?;
287        let expected_key = epoch_state.verifier().get_public_key(&author);
288        let initialize_result = match expected_key {
289            None => {
290                diem_debug!(
291                    "guarded_initialize: not a validator in epoch_set={:?}",
292                    epoch_state
293                );
294                self.validator_signer = None;
295                Ok(())
296            }
297            Some(expected_key) => {
298                let current_key = self.signer().ok().map(|s| s.public_key());
299                if current_key == Some(expected_key.clone()) {
300                    diem_debug!(
301                        SafetyLogSchema::new(
302                            LogEntry::KeyReconciliation,
303                            LogEvent::Success
304                        ),
305                        "in set",
306                    );
307                    Ok(())
308                } else if self.export_consensus_key {
309                    // Try to export the consensus key directly from storage.
310                    match self
311                        .persistent_storage
312                        .consensus_key_for_version(expected_key.clone())
313                    {
314                        Ok(consensus_key) => {
315                            if consensus_key.public_key() != expected_key {
316                                Err(Error::ValidatorKeyNotFound("exported key does not match the expected key".into()))
317                            } else {
318                                self.validator_signer = Some(
319                                    ConfigurableValidatorSigner::new_signer(
320                                        author,
321                                        consensus_key,
322                                        self.vrf_private_key.clone(),
323                                    ),
324                                );
325                                Ok(())
326                            }
327                        }
328                        Err(Error::SecureStorageMissingDataError(error)) => {
329                            Err(Error::ValidatorKeyNotFound(error))
330                        }
331                        Err(error) => Err(error),
332                    }
333                } else {
334                    // Try to generate a signature over a test message to ensure
335                    // the expected key is actually held in
336                    // storage.
337                    self.validator_signer =
338                        Some(ConfigurableValidatorSigner::new_handle(
339                            author,
340                            expected_key.clone(),
341                        ));
342                    self.sign(&Timeout::new(0, 0)).and_then(|signature| {
343                        signature
344                            .verify(&Timeout::new(0, 0), &expected_key)
345                            .map_err(|error| {
346                                Error::ValidatorKeyNotFound(error.to_string())
347                            })
348                    })
349                }
350            }
351        };
352        initialize_result.map_err(|error| {
353            diem_info!(SafetyLogSchema::new(
354                LogEntry::KeyReconciliation,
355                LogEvent::Error
356            )
357            .error(&error),);
358            self.validator_signer = None;
359            error
360        })
361    }
362
363    fn guarded_construct_and_sign_vote(
364        &mut self, maybe_signed_vote_proposal: &MaybeSignedVoteProposal,
365    ) -> Result<Vote, Error> {
366        // Exit early if we cannot sign
367        self.signer()?;
368
369        let vote_proposal = &maybe_signed_vote_proposal.vote_proposal;
370        let proposed_block = vote_proposal.block();
371        let mut safety_data = self.persistent_storage.safety_data()?;
372
373        self.verify_epoch(proposed_block.epoch(), &safety_data)?;
374
375        // if already voted on this round, send back the previous vote
376        // note: this needs to happen after verifying the epoch as we just check
377        // the round here
378        if let Some(vote) = safety_data.last_vote.clone() {
379            if vote.vote_data().proposed().round() == proposed_block.round() {
380                return Ok(vote);
381            }
382        }
383
384        self.verify_qc(proposed_block.quorum_cert())?;
385        proposed_block
386            .validate_signature(&self.epoch_state()?.verifier())
387            .map_err(|error| Error::InternalError(error.to_string()))?;
388
389        self.verify_and_update_preferred_round(
390            proposed_block.quorum_cert(),
391            &mut safety_data,
392        )?;
393        self.verify_and_update_last_vote_round(
394            proposed_block.block_data().round(),
395            &mut safety_data,
396        )?;
397
398        // Construct and sign vote
399        let vote_data = Self::extension_check(vote_proposal)?;
400        let author = self.signer()?.author();
401        let ledger_info =
402            Self::construct_ledger_info(proposed_block, vote_data.hash())?;
403        let signature = self.sign(&ledger_info)?;
404        let vote =
405            Vote::new_with_signature(vote_data, author, ledger_info, signature);
406
407        safety_data.last_vote = Some(vote.clone());
408        self.persistent_storage.set_safety_data(safety_data)?;
409
410        Ok(vote)
411    }
412
413    fn guarded_sign_proposal(
414        &mut self, block_data: BlockData,
415    ) -> Result<Block, Error> {
416        self.signer()?;
417        self.verify_author(block_data.author())?;
418
419        let mut safety_data = self.persistent_storage.safety_data()?;
420        self.verify_epoch(block_data.epoch(), &safety_data)?;
421
422        if block_data.round() <= safety_data.last_voted_round {
423            return Err(Error::InvalidProposal(format!(
424                "Proposed round {} is not higher than last voted round {}",
425                block_data.round(),
426                safety_data.last_voted_round
427            )));
428        }
429
430        self.verify_qc(block_data.quorum_cert())?;
431        if self.verify_and_update_preferred_round(
432            block_data.quorum_cert(),
433            &mut safety_data,
434        )? {
435            self.persistent_storage.set_safety_data(safety_data)?;
436        }
437
438        let signature = self.sign(&block_data)?;
439        Ok(Block::new_proposal_from_block_data_and_signature(
440            block_data, signature, None,
441        ))
442    }
443
444    fn guarded_sign_timeout(
445        &mut self, timeout: &Timeout,
446    ) -> Result<ConsensusSignature, Error> {
447        self.signer()?;
448
449        let mut safety_data = self.persistent_storage.safety_data()?;
450        self.verify_epoch(timeout.epoch(), &safety_data)?;
451
452        if timeout.round() <= safety_data.preferred_round {
453            return Err(Error::IncorrectPreferredRound(
454                timeout.round(),
455                safety_data.preferred_round,
456            ));
457        }
458        if timeout.round() < safety_data.last_voted_round {
459            return Err(Error::IncorrectLastVotedRound(
460                timeout.round(),
461                safety_data.last_voted_round,
462            ));
463        }
464        if timeout.round() > safety_data.last_voted_round {
465            self.verify_and_update_last_vote_round(
466                timeout.round(),
467                &mut safety_data,
468            )?;
469            self.persistent_storage.set_safety_data(safety_data)?;
470        }
471
472        let signature = self.sign(timeout)?;
473        Ok(signature)
474    }
475
476    pub fn start_voting(&mut self, initialize: bool) -> Result<(), Error> {
477        if initialize {
478            // If the node starts voting with its local safety data,
479            // `SafetyRule` just remains the same.
480            Ok(())
481        } else {
482            self.persistent_storage
483                .replace_with_suffix(SAFETY_STORAGE_SAVE_SUFFIX)
484        }
485    }
486
487    pub fn stop_voting(&mut self) -> Result<(), Error> {
488        self.persistent_storage
489            .save_to_suffix(SAFETY_STORAGE_SAVE_SUFFIX)
490    }
491
492    pub fn consensus_state(&mut self) -> Result<ConsensusState, Error> {
493        let cb = || self.guarded_consensus_state();
494        run_and_log(cb, |log| log, LogEntry::ConsensusState)
495    }
496
497    pub fn initialize(
498        &mut self, epoch_state: &EpochState,
499    ) -> Result<(), Error> {
500        let cb = || self.guarded_initialize(epoch_state);
501        run_and_log(cb, |log| log, LogEntry::Initialize)
502    }
503
504    pub fn construct_and_sign_vote(
505        &mut self, maybe_signed_vote_proposal: &MaybeSignedVoteProposal,
506    ) -> Result<Vote, Error> {
507        let round = maybe_signed_vote_proposal.vote_proposal.block().round();
508        let cb =
509            || self.guarded_construct_and_sign_vote(maybe_signed_vote_proposal);
510        run_and_log(cb, |log| log.round(round), LogEntry::ConstructAndSignVote)
511    }
512
513    pub fn sign_proposal(
514        &mut self, block_data: BlockData,
515    ) -> Result<Block, Error> {
516        let round = block_data.round();
517        let cb = || self.guarded_sign_proposal(block_data);
518        run_and_log(cb, |log| log.round(round), LogEntry::SignProposal)
519    }
520
521    pub fn sign_timeout(
522        &mut self, timeout: &Timeout,
523    ) -> Result<ConsensusSignature, Error> {
524        let cb = || self.guarded_sign_timeout(timeout);
525        run_and_log(cb, |log| log.round(timeout.round()), LogEntry::SignTimeout)
526    }
527}
528
529fn run_and_log<F, L, R>(
530    callback: F, log_cb: L, log_entry: LogEntry,
531) -> Result<R, Error>
532where
533    F: FnOnce() -> Result<R, Error>,
534    L: for<'a> Fn(SafetyLogSchema<'a>) -> SafetyLogSchema<'a>,
535{
536    diem_debug!(log_cb(SafetyLogSchema::new(log_entry, LogEvent::Request)));
537    callback()
538        .map(|v| {
539            diem_info!(log_cb(SafetyLogSchema::new(
540                log_entry,
541                LogEvent::Success
542            )));
543            v
544        })
545        .map_err(|err| {
546            diem_error!(log_cb(SafetyLogSchema::new(
547                log_entry,
548                LogEvent::Error
549            ))
550            .error(&err));
551            err
552        })
553}