diem_types/
trusted_state.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    epoch_change::{EpochChangeProof, Verifier},
10    ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
11    transaction::Version,
12    waypoint::Waypoint,
13};
14use anyhow::{bail, ensure, format_err, Result};
15use std::{convert::TryFrom, sync::Arc};
16
17/// `TrustedState` keeps track of our latest trusted state, including the latest
18/// verified version and the latest verified validator set.
19#[derive(Clone, Debug)]
20pub struct TrustedState {
21    /// The latest verified state is from either a waypoint or a ledger info,
22    /// either inside an epoch or the epoch change ledger info.
23    verified_state: Waypoint,
24    /// The current verifier. If we're starting up fresh, this is probably a
25    /// waypoint from our config. Otherwise, this is generated from the
26    /// validator set in the last known epoch change ledger info.
27    verifier: Arc<dyn Verifier>,
28}
29
30/// `TrustedStateChange` is the result of attempting to ratchet to a new trusted
31/// state. In order to reduce redundant error checking, `TrustedStateChange`
32/// also contains references to relevant items used to ratchet us.
33#[derive(Clone, Debug)]
34pub enum TrustedStateChange<'a> {
35    /// We have a newer `TrustedState` but it's still in the same epoch, so
36    /// only the latest trusted version changed.
37    Version { new_state: TrustedState },
38    /// We have a newer `TrustedState` and there was at least one epoch change,
39    /// so we have a newer trusted version and a newer trusted validator set.
40    Epoch {
41        new_state: TrustedState,
42        latest_epoch_change_li: &'a LedgerInfoWithSignatures,
43    },
44    /// The latest ledger info is at the same version as the trusted state and
45    /// matches the hash.
46    NoChange,
47}
48
49impl TrustedState {
50    /// Verify and ratchet forward our trusted state using a `EpochChangeProof`
51    /// (that moves us into the latest epoch) and a `LedgerInfoWithSignatures`
52    /// inside that epoch.
53    ///
54    /// For example, a client sends an `UpdateToLatestLedgerRequest` to a
55    /// FullNode and receives some epoch change proof along with a latest
56    /// ledger info inside the `UpdateToLatestLedgerResponse`. This function
57    /// verifies the change proof and ratchets the trusted state version forward
58    /// if the response successfully moves us into a new epoch or a new latest
59    /// ledger info within our current epoch.
60    ///
61    /// + If there was a validation error, e.g., the epoch change proof was
62    /// invalid, we return an `Err`.
63    ///
64    /// + If the message was well formed but stale (i.e., the returned latest
65    /// ledger is behind our trusted version), we also return an `Err` since
66    /// stale responses should always be rejected.
67    ///
68    /// + If the response is fresh and there is no epoch change, we just ratchet
69    /// our trusted version to the latest ledger info and return
70    /// `Ok(TrustedStateChange::Version { .. })`.
71    ///
72    /// + If there is a new epoch and the server provides a correct proof, we
73    /// ratchet our trusted version forward, update our verifier to contain
74    /// the new validator set, and return `Ok(TrustedStateChange::Epoch { ..
75    /// })`.
76    pub fn verify_and_ratchet<'a>(
77        &self, latest_li: &'a LedgerInfoWithSignatures,
78        epoch_change_proof: &'a EpochChangeProof,
79    ) -> Result<TrustedStateChange<'a>> {
80        let res_version = latest_li.ledger_info().version();
81        ensure!(
82            res_version >= self.latest_version(),
83            "The target latest ledger info is stale and behind our current trusted version",
84        );
85
86        if self.verifier.epoch_change_verification_required(
87            latest_li.ledger_info().next_block_epoch(),
88        ) {
89            // Verify the EpochChangeProof to move us into the latest epoch.
90            let epoch_change_li =
91                epoch_change_proof.verify(self.verifier.as_ref())?;
92            let new_epoch_state = epoch_change_li
93                .ledger_info()
94                .next_epoch_state()
95                .cloned()
96                .ok_or_else(|| {
97                    format_err!(
98                        "A valid EpochChangeProof will never return a non-epoch change ledger info"
99                    )
100                })?;
101
102            // If the latest ledger info is in the same epoch as the new
103            // verifier, verify it and use it as latest state,
104            // otherwise fallback to the epoch change ledger info.
105            let new_epoch = new_epoch_state.epoch;
106            let new_verifier = Arc::new(new_epoch_state);
107
108            let verified_ledger_info = if epoch_change_li == latest_li {
109                latest_li
110            } else if latest_li.ledger_info().epoch() == new_epoch {
111                new_verifier.verify(latest_li)?;
112                latest_li
113            } else if latest_li.ledger_info().epoch() > new_epoch
114                && epoch_change_proof.more
115            {
116                epoch_change_li
117            } else {
118                bail!("Inconsistent epoch change proof and latest ledger info");
119            };
120            let verified_state =
121                Waypoint::new_any(verified_ledger_info.ledger_info());
122
123            let new_state = TrustedState {
124                verified_state,
125                verifier: new_verifier,
126            };
127
128            Ok(TrustedStateChange::Epoch {
129                new_state,
130                latest_epoch_change_li: epoch_change_li,
131            })
132        } else {
133            // The EpochChangeProof is empty, stale, or only gets us into our
134            // current epoch. We then try to verify that the latest ledger info
135            // is this epoch.
136            let new_waypoint = Waypoint::new_any(latest_li.ledger_info());
137            if new_waypoint.version() == self.verified_state.version() {
138                ensure!(
139                    new_waypoint == self.verified_state,
140                    "LedgerInfo doesn't match verified state"
141                );
142                Ok(TrustedStateChange::NoChange)
143            } else {
144                self.verifier.verify(latest_li)?;
145
146                let new_state = TrustedState {
147                    verified_state: new_waypoint,
148                    verifier: self.verifier.clone(),
149                };
150
151                Ok(TrustedStateChange::Version { new_state })
152            }
153        }
154    }
155
156    pub fn latest_version(&self) -> Version { self.verified_state.version() }
157}
158
159impl From<Waypoint> for TrustedState {
160    fn from(waypoint: Waypoint) -> Self {
161        Self {
162            verified_state: waypoint,
163            verifier: Arc::new(waypoint),
164        }
165    }
166}
167
168impl TryFrom<&LedgerInfo> for TrustedState {
169    type Error = anyhow::Error;
170
171    fn try_from(ledger_info: &LedgerInfo) -> Result<Self> {
172        let epoch_state = ledger_info.next_epoch_state().cloned().ok_or_else(|| {
173            format_err!("No EpochState in LedgerInfo; it must not be on an epoch boundary")
174        })?;
175
176        Ok(Self {
177            verified_state: Waypoint::new_epoch_boundary(ledger_info)?,
178            verifier: Arc::new(epoch_state),
179        })
180    }
181}