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}