1use 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
23const WAYPOINT_DELIMITER: char = ':';
25
26#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
32pub struct Waypoint {
33 version: Version,
36 value: HashValue,
38}
39
40impl Waypoint {
41 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 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 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#[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 #[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 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()); 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}