1use std::{future::Future, sync::Arc};
2
3use alloy::primitives::U256;
4use anyhow::{Context, Result, bail, ensure};
5use committable::Committable;
6use espresso_types::{Leaf2, PubKey, SeqTypes};
7use hotshot_types::{
8 epoch_membership::EpochMembership,
9 message::UpgradeLock,
10 simple_certificate::CertificatePair,
11 stake_table::{HSStakeTable, StakeTableEntries, StakeTableEntry, supermajority_threshold},
12 vote::{self, HasViewNumber},
13};
14use tracing::Instrument;
15use vbs::version::{StaticVersion, StaticVersionType, Version};
16use versions::{EPOCH_VERSION, MAX_SUPPORTED_VERSION, Upgrade, version};
17
18pub type Certificate = CertificatePair<SeqTypes>;
19
20pub trait Quorum: Sync {
21 fn verify(
23 &self,
24 cert: &Certificate,
25 version: Version,
26 ) -> impl Send + Future<Output = Result<()>> {
27 async move {
28 match (version.major, version.minor) {
29 (0, 1) => self.verify_static::<StaticVersion<0, 1>>(cert).await,
30 (0, 2) => self.verify_static::<StaticVersion<0, 2>>(cert).await,
31 (0, 3) => self.verify_static::<StaticVersion<0, 3>>(cert).await,
32 (0, 4) => self.verify_static::<StaticVersion<0, 4>>(cert).await,
33 (0, 5) => self.verify_static::<StaticVersion<0, 5>>(cert).await,
34 (0, 6) => self.verify_static::<StaticVersion<0, 6>>(cert).await,
35 (0, 7) => self.verify_static::<StaticVersion<0, 7>>(cert).await,
36 (0, 8) => self.verify_static::<StaticVersion<0, 8>>(cert).await,
37 _ => {
38 const {
39 assert!(MAX_SUPPORTED_VERSION.major == 0);
40 assert!(MAX_SUPPORTED_VERSION.minor == 8);
41 }
42 bail!("unsupported version {version}");
43 },
44 }
45 }
46 }
47
48 fn verify_static<V: StaticVersionType + 'static>(
50 &self,
51 qc: &Certificate,
52 ) -> impl Send + Future<Output = Result<()>>;
53
54 fn verify_qc_chain_and_get_version<'a>(
59 &self,
60 leaf: &Leaf2,
61 certs: impl Send + IntoIterator<Item = &'a Certificate, IntoIter: Send>,
62 ) -> impl Send + Future<Output = Result<Version>> {
63 let span = tracing::trace_span!(
64 "verify_qc_chain_and_get_version",
65 height = leaf.block_header().height()
66 );
67 async move {
68 let version = leaf.block_header().version();
76 let upgrade = leaf.upgrade_certificate();
81 ensure!(version <= MAX_SUPPORTED_VERSION);
85 if let Some(cert) = &upgrade {
86 ensure!(cert.data.new_version <= MAX_SUPPORTED_VERSION);
87 }
88 tracing::debug!(
89 %version,
90 ?leaf,
91 "verify QC chain for leaf"
92 );
93
94 let mut first = None;
96 let mut curr: Option<&Certificate> = None;
97 for cert in certs {
98 tracing::trace!(?cert, "verify cert");
99
100 let version = match &upgrade {
102 Some(upgrade) if cert.view_number() >= upgrade.data.new_version_first_view => {
103 tracing::debug!(?upgrade, view = ?cert.view_number(), "using upgraded version");
104 upgrade.data.new_version
105 },
106 _ => version,
107 };
108
109 self.verify(cert, version).await?;
111
112 if let Some(prev) = curr {
114 ensure!(cert.view_number() == prev.view_number() + 1);
115 }
116 curr = Some(cert);
117
118 if first.is_none() {
120 first = Some(cert);
121 }
122 }
123
124 let first_qc = first.context("empty QC chain")?;
126 ensure!(first_qc.leaf_commit() == leaf.commit());
127
128 Ok(version)
129 }
130 .instrument(span)
131 }
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
136pub struct StakeTable {
137 entries: Vec<StakeTableEntry<PubKey>>,
138 threshold: U256,
139}
140
141impl From<HSStakeTable<SeqTypes>> for StakeTable {
142 fn from(table: HSStakeTable<SeqTypes>) -> Self {
143 StakeTableEntries::from(table).into()
144 }
145}
146
147impl From<Vec<StakeTableEntry<PubKey>>> for StakeTable {
148 fn from(entries: Vec<StakeTableEntry<PubKey>>) -> Self {
149 StakeTableEntries(entries).into()
150 }
151}
152
153impl From<StakeTableEntries<SeqTypes>> for StakeTable {
154 fn from(entries: StakeTableEntries<SeqTypes>) -> Self {
155 Self::from_iter(entries.0)
156 }
157}
158
159impl FromIterator<StakeTableEntry<PubKey>> for StakeTable {
160 fn from_iter<T: IntoIterator<Item = StakeTableEntry<PubKey>>>(entries: T) -> Self {
161 let mut total_stake = U256::ZERO;
162 let entries = entries
163 .into_iter()
164 .inspect(|entry| {
165 total_stake += entry.stake_amount;
166 })
167 .collect();
168 Self {
169 entries,
170 threshold: supermajority_threshold(total_stake),
171 }
172 }
173}
174
175impl StakeTable {
176 pub async fn from_membership(membership: &EpochMembership<SeqTypes>) -> Self {
178 membership.stake_table().await.into()
179 }
180
181 pub async fn verify_cert<V, T>(&self, cert: &impl vote::Certificate<SeqTypes, T>) -> Result<()>
183 where
184 V: StaticVersionType + 'static,
185 {
186 let upgrade = Upgrade::trivial(version(V::MAJOR, V::MINOR));
187 cert.is_valid_cert(&self.entries, self.threshold, &UpgradeLock::new(upgrade))
188 .context("invalid threshold signature")
189 }
190}
191
192pub trait StakeTablePair {
198 fn stake_table(&self) -> impl Send + Future<Output = Result<Arc<StakeTable>>>;
200
201 fn next_epoch_stake_table(&self) -> impl Send + Future<Output = Result<Arc<StakeTable>>>;
203}
204
205impl StakeTablePair for EpochMembership<SeqTypes> {
206 async fn stake_table(&self) -> Result<Arc<StakeTable>> {
207 Ok(Arc::new(StakeTable::from_membership(self).await))
208 }
209
210 async fn next_epoch_stake_table(&self) -> Result<Arc<StakeTable>> {
211 let membership = self.next_epoch_stake_table().await?;
212 Ok(Arc::new(StakeTable::from_membership(&membership).await))
213 }
214}
215
216impl StakeTablePair for (Arc<StakeTable>, Arc<StakeTable>) {
217 async fn stake_table(&self) -> Result<Arc<StakeTable>> {
218 Ok(self.0.clone())
219 }
220
221 async fn next_epoch_stake_table(&self) -> Result<Arc<StakeTable>> {
222 Ok(self.1.clone())
223 }
224}
225
226#[derive(Clone, Debug)]
228pub struct StakeTableQuorum<T> {
229 membership: T,
230 epoch_height: u64,
231}
232
233impl<T> StakeTableQuorum<T> {
234 pub fn new(membership: T, epoch_height: u64) -> Self {
236 Self {
237 membership,
238 epoch_height,
239 }
240 }
241}
242
243impl<T> Quorum for StakeTableQuorum<T>
244where
245 T: StakeTablePair + Sync,
246{
247 async fn verify_static<V: StaticVersionType + 'static>(
248 &self,
249 cert: &Certificate,
250 ) -> Result<()> {
251 let stake_table = self.membership.stake_table().await?;
252 stake_table
253 .verify_cert::<V, _>(cert.qc())
254 .await
255 .context("verifying QC")?;
256
257 if version(V::MAJOR, V::MINOR) >= EPOCH_VERSION {
258 if let Some(next_epoch_qc) = cert.verify_next_epoch_qc(self.epoch_height)? {
261 let stake_table = self.membership.next_epoch_stake_table().await?;
262 stake_table
263 .verify_cert::<V, _>(next_epoch_qc)
264 .await
265 .context("verifying next epoch QC")?;
266 }
267 }
268
269 Ok(())
270 }
271}
272
273#[cfg(test)]
274mod test {
275 use pretty_assertions::assert_eq;
276
277 use super::*;
278 use crate::testing::{
279 AlwaysFalseQuorum, AlwaysTrueQuorum, ENABLE_EPOCHS, EpochChangeQuorum, LEGACY_VERSION,
280 VersionCheckQuorum, custom_epoch_change_leaf_chain, custom_leaf_chain_with_upgrade,
281 epoch_change_leaf_chain, leaf_chain, leaf_chain_with_upgrade, qc_chain_from_leaf_chain,
282 };
283
284 #[test_log::test(tokio::test(flavor = "multi_thread"))]
285 async fn test_valid_chain() {
286 let leaves = leaf_chain(1..=3, EPOCH_VERSION).await;
287 let version = AlwaysTrueQuorum
288 .verify_qc_chain_and_get_version(
289 leaves[0].leaf(),
290 &qc_chain_from_leaf_chain(&leaves[1..]),
291 )
292 .await
293 .unwrap();
294 assert_eq!(version, leaves[0].header().version());
295 }
296
297 #[test_log::test(tokio::test(flavor = "multi_thread"))]
298 async fn test_wrong_leaf() {
299 let leaves = leaf_chain(1..=3, EPOCH_VERSION).await;
300 AlwaysTrueQuorum
301 .verify_qc_chain_and_get_version(
302 leaves[2].leaf(),
303 &qc_chain_from_leaf_chain(&leaves[1..]),
304 )
305 .await
306 .unwrap_err();
307 }
308
309 #[test_log::test(tokio::test(flavor = "multi_thread"))]
310 async fn test_invalid_qc() {
311 let leaves = leaf_chain(1..=2, EPOCH_VERSION).await;
312 AlwaysFalseQuorum
313 .verify_qc_chain_and_get_version(
314 leaves[0].leaf(),
315 &[Certificate::for_parent(leaves[1].leaf())],
316 )
317 .await
318 .unwrap_err();
319 }
320
321 #[test_log::test(tokio::test(flavor = "multi_thread"))]
322 async fn test_non_consecutive() {
323 let leaves = leaf_chain(1..=4, EPOCH_VERSION).await;
324 AlwaysTrueQuorum
325 .verify_qc_chain_and_get_version(
326 leaves[0].leaf(),
327 &qc_chain_from_leaf_chain([&leaves[1], &leaves[3]]),
328 )
329 .await
330 .unwrap_err();
331 }
332
333 #[test_log::test(tokio::test(flavor = "multi_thread"))]
334 async fn test_upgrade() {
335 let leaves = leaf_chain_with_upgrade(1..=3, 2, ENABLE_EPOCHS).await;
336 let version = VersionCheckQuorum::new(leaves.iter().map(|leaf| leaf.leaf().clone()))
337 .verify_qc_chain_and_get_version(
338 leaves[0].leaf(),
339 &qc_chain_from_leaf_chain(&leaves[1..]),
340 )
341 .await
342 .unwrap();
343 assert_eq!(version, leaves[0].header().version());
344 }
345
346 #[test_log::test(tokio::test(flavor = "multi_thread"))]
347 async fn test_illegal_upgrade() {
348 let leaves = custom_leaf_chain_with_upgrade(1..=3, 2, ENABLE_EPOCHS, |proposal| {
349 proposal.upgrade_certificate = None;
352 })
353 .await;
354 VersionCheckQuorum::new(leaves.iter().map(|leaf| leaf.leaf().clone()))
355 .verify_qc_chain_and_get_version(
356 leaves[0].leaf(),
357 &qc_chain_from_leaf_chain(&leaves[1..]),
358 )
359 .await
360 .unwrap_err();
361 }
362
363 #[test_log::test(tokio::test(flavor = "multi_thread"))]
364 async fn test_epoch_change() {
365 let leaves = epoch_change_leaf_chain(1..=5, 5, EPOCH_VERSION).await;
366 let version = EpochChangeQuorum::new(5)
367 .verify_qc_chain_and_get_version(
368 leaves[0].leaf(),
369 &qc_chain_from_leaf_chain(&leaves[1..]),
370 )
371 .await
372 .unwrap();
373 assert_eq!(version, leaves[0].header().version());
374 }
375
376 #[test_log::test(tokio::test(flavor = "multi_thread"))]
377 async fn test_epoch_change_missing_eqc() {
378 let leaves = custom_epoch_change_leaf_chain(1..=5, 5, EPOCH_VERSION, |proposal| {
379 proposal.next_epoch_justify_qc = None;
381 })
382 .await;
383 EpochChangeQuorum::new(5)
384 .verify_qc_chain_and_get_version(
385 leaves[0].leaf(),
386 &qc_chain_from_leaf_chain(&leaves[1..]),
387 )
388 .await
389 .unwrap_err();
390 }
391
392 #[test_log::test(tokio::test(flavor = "multi_thread"))]
393 async fn test_epoch_change_inconsistent_eqc_view_number() {
394 let leaves = custom_epoch_change_leaf_chain(1..=5, 5, EPOCH_VERSION, |proposal| {
395 if let Some(next_epoch_justify_qc) = &mut proposal.next_epoch_justify_qc {
397 next_epoch_justify_qc.view_number += 1;
398 }
399 })
400 .await;
401 EpochChangeQuorum::new(5)
402 .verify_qc_chain_and_get_version(
403 leaves[0].leaf(),
404 &qc_chain_from_leaf_chain(&leaves[1..]),
405 )
406 .await
407 .unwrap_err();
408 }
409
410 #[test_log::test(tokio::test(flavor = "multi_thread"))]
411 async fn test_epoch_change_inconsistent_eqc_data() {
412 let leaves = custom_epoch_change_leaf_chain(1..=5, 5, EPOCH_VERSION, |proposal| {
413 if let Some(next_epoch_justify_qc) = &mut proposal.next_epoch_justify_qc {
415 *next_epoch_justify_qc.data.block_number.as_mut().unwrap() += 1;
416 }
417 })
418 .await;
419 EpochChangeQuorum::new(5)
420 .verify_qc_chain_and_get_version(
421 leaves[0].leaf(),
422 &qc_chain_from_leaf_chain(&leaves[1..]),
423 )
424 .await
425 .unwrap_err();
426 }
427
428 #[test_log::test(tokio::test(flavor = "multi_thread"))]
429 async fn test_epoch_change_absent_eqc_before_upgrade() {
430 let leaves = custom_epoch_change_leaf_chain(1..=5, 5, LEGACY_VERSION, |proposal| {
431 proposal.next_epoch_justify_qc = None;
433 })
434 .await;
435 let version = EpochChangeQuorum::new(5)
436 .verify_qc_chain_and_get_version(
437 leaves[0].leaf(),
438 &qc_chain_from_leaf_chain(&leaves[1..]),
439 )
440 .await
441 .unwrap();
442 assert_eq!(version, leaves[0].header().version());
443 }
444}