diem_types/
waypoint.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::Verifier,
10    epoch_state::EpochState,
11    ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
12    transaction::Version,
13};
14use anyhow::{ensure, format_err, Error, Result};
15use diem_crypto::hash::{CryptoHash, HashValue};
16use diem_crypto_derive::{BCSCryptoHash, CryptoHasher};
17use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
18use std::{
19    fmt::{Display, Formatter},
20    str::FromStr,
21};
22
23// The delimiter between the version and the hash.
24const WAYPOINT_DELIMITER: char = ':';
25
26/// Waypoint keeps information about the LedgerInfo on a given version, which
27/// provides an off-chain mechanism to verify the sync process right after the
28/// restart. At high level, a trusted waypoint verifies the LedgerInfo for a
29/// certain epoch change. For more information, please refer to the Waypoints
30/// documentation.
31#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
32pub struct Waypoint {
33    /// The version of the reconfiguration transaction that is being approved
34    /// by this waypoint.
35    version: Version,
36    /// The hash of the chosen fields of LedgerInfo.
37    value: HashValue,
38}
39
40impl Waypoint {
41    /// Generate a new waypoint given any LedgerInfo.
42    pub fn new_any(ledger_info: &LedgerInfo) -> Self {
43        let converter = Ledger2WaypointConverter::new(ledger_info);
44        Self {
45            version: ledger_info.version(),
46            value: converter.hash(),
47        }
48    }
49
50    /// Generates a new waypoint given the epoch change LedgerInfo.
51    pub fn new_epoch_boundary(ledger_info: &LedgerInfo) -> Result<Self> {
52        ensure!(ledger_info.ends_epoch(), "No validator set");
53        Ok(Self::new_any(ledger_info))
54    }
55
56    pub fn version(&self) -> Version { self.version }
57
58    pub fn value(&self) -> HashValue { self.value }
59
60    /// Errors in case the given ledger info does not match the waypoint.
61    pub fn verify(&self, ledger_info: &LedgerInfo) -> Result<()> {
62        ensure!(
63            ledger_info.version() == self.version(),
64            "Waypoint version mismatch: waypoint version = {}, given version = {}",
65            self.version(),
66            ledger_info.version()
67        );
68        let converter = Ledger2WaypointConverter::new(ledger_info);
69        ensure!(
70            converter.hash() == self.value(),
71            format!(
72                "Waypoint value mismatch: waypoint value = {}, given value = {}",
73                self.value().to_hex(),
74                converter.hash().to_hex()
75            )
76        );
77        Ok(())
78    }
79}
80
81impl Verifier for Waypoint {
82    fn verify(&self, ledger_info: &LedgerInfoWithSignatures) -> Result<()> {
83        self.verify(ledger_info.ledger_info())
84    }
85
86    fn epoch_change_verification_required(&self, _epoch: u64) -> bool { true }
87
88    fn is_ledger_info_stale(&self, ledger_info: &LedgerInfo) -> bool {
89        ledger_info.version() < self.version()
90    }
91}
92
93impl Display for Waypoint {
94    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
95        write!(
96            f,
97            "{}{}{}",
98            self.version(),
99            WAYPOINT_DELIMITER,
100            self.value().to_hex()
101        )
102    }
103}
104
105impl FromStr for Waypoint {
106    type Err = Error;
107
108    fn from_str(s: &str) -> Result<Self> {
109        let mut split = s.split(WAYPOINT_DELIMITER);
110        let version = split
111            .next()
112            .ok_or_else(|| {
113                format_err!("Failed to parse waypoint string {}", s)
114            })?
115            .parse::<Version>()?;
116        let value = HashValue::from_hex(split.next().ok_or_else(|| {
117            format_err!("Failed to parse waypoint string {}", s)
118        })?)?;
119        Ok(Self { version, value })
120    }
121}
122
123/// Keeps the fields of LedgerInfo that are hashed for generating a waypoint.
124/// Note that not all the fields of LedgerInfo are included: some
125/// consensus-related fields might not be the same for all the participants.
126#[derive(Deserialize, Serialize, CryptoHasher, BCSCryptoHash)]
127struct Ledger2WaypointConverter {
128    epoch: u64,
129    root_hash: HashValue,
130    version: Version,
131    timestamp_usecs: u64,
132    next_epoch_state: Option<EpochState>,
133}
134
135impl Ledger2WaypointConverter {
136    pub fn new(ledger_info: &LedgerInfo) -> Self {
137        Self {
138            epoch: ledger_info.epoch(),
139            root_hash: ledger_info.transaction_accumulator_hash(),
140            version: ledger_info.version(),
141            timestamp_usecs: ledger_info.timestamp_usecs(),
142            next_epoch_state: ledger_info.next_epoch_state().cloned(),
143        }
144    }
145}
146
147impl<'de> Deserialize<'de> for Waypoint {
148    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
149    where D: Deserializer<'de> {
150        if deserializer.is_human_readable() {
151            let s = <String>::deserialize(deserializer)?;
152            Waypoint::from_str(&s).map_err(D::Error::custom)
153        } else {
154            // In order to preserve the Serde data model and help analysis
155            // tools, make sure to wrap our value in a container
156            // with the same name as the original type.
157            #[derive(::serde::Deserialize)]
158            #[serde(rename = "Waypoint")]
159            struct Value(Version, HashValue);
160
161            let value = Value::deserialize(deserializer)?;
162            Ok(Waypoint {
163                version: value.0,
164                value: value.1,
165            })
166        }
167    }
168}
169
170impl Serialize for Waypoint {
171    fn serialize<S>(
172        &self, serializer: S,
173    ) -> std::result::Result<S::Ok, S::Error>
174    where S: Serializer {
175        if serializer.is_human_readable() {
176            self.to_string().serialize(serializer)
177        } else {
178            // See comment in deserialize.
179            serializer.serialize_newtype_struct(
180                "Waypoint",
181                &(self.version, self.value),
182            )
183        }
184    }
185}
186
187#[cfg(test)]
188mod test {
189    use super::*;
190    use crate::block_info::BlockInfo;
191    use std::str::FromStr;
192
193    #[test]
194    fn test_waypoint_parsing() {
195        let waypoint = Waypoint {
196            version: 123,
197            value: HashValue::random(),
198        };
199        let waypoint_str = waypoint.to_string();
200        let parsed_waypoint = Waypoint::from_str(&waypoint_str).unwrap();
201        assert_eq!(waypoint, parsed_waypoint);
202    }
203
204    #[test]
205    fn test_waypoint_li_verification() {
206        let empty_li = LedgerInfo::new(BlockInfo::empty(), HashValue::zero());
207        assert!(Waypoint::new_epoch_boundary(&empty_li).is_err()); // no validator set in empty LI
208        let li = LedgerInfo::new(
209            BlockInfo::new(
210                1,
211                10,
212                HashValue::random(),
213                HashValue::random(),
214                123,
215                1000,
216                Some(EpochState::empty()),
217                None,
218            ),
219            HashValue::zero(),
220        );
221        let waypoint = Waypoint::new_epoch_boundary(&li).unwrap();
222        assert!(waypoint.verify(&li).is_ok());
223    }
224}