hotshot_types/simple_certificate/
timeout3.rs1use std::collections::BTreeMap;
4
5use alloy_primitives::U256;
6use bitvec::prelude::*;
7use committable::{Commitment, Committable};
8use hotshot_utils::anytrace::*;
9use serde::{Deserialize, Serialize};
10
11use super::QuorumCertificate2;
12use crate::{
13 data::{EpochNumber, ViewNumber, serialize_signature2},
14 message::UpgradeLock,
15 simple_vote::{HasEpoch, LockView, TimeoutData3, TimeoutLock, VersionedVoteData},
16 traits::{
17 node_implementation::NodeType,
18 signature_key::{SignatureKey, StakeTableEntryType},
19 },
20 vote::HasViewNumber,
21};
22
23#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
33#[serde(bound(deserialize = ""))]
34pub struct TimeoutCertificate3<T: NodeType> {
35 pub view_number: ViewNumber,
37 pub epoch: EpochNumber,
39 pub latest: LatestTimeoutSignatures<T>,
41 pub earlier: Vec<TimeoutSignatures<T>>,
43}
44
45#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
47#[serde(bound(deserialize = ""))]
48pub struct LatestTimeoutSignatures<T: NodeType> {
49 pub lock_cert: Option<QuorumCertificate2<T>>,
57 pub signatures: <T::SignatureKey as SignatureKey>::QcType,
59}
60
61#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
64#[serde(bound(deserialize = ""))]
65pub struct TimeoutSignatures<T: NodeType> {
66 pub lock: Option<LockView>,
68 pub signatures: <T::SignatureKey as SignatureKey>::QcType,
70}
71
72impl<T: NodeType> TimeoutCertificate3<T> {
73 pub fn assemble(
79 view: ViewNumber,
80 epoch: EpochNumber,
81 stake_table: &[<T::SignatureKey as SignatureKey>::StakeTableEntry],
82 votes: impl IntoIterator<
83 Item = (
84 Option<LockView>,
85 T::SignatureKey,
86 <T::SignatureKey as SignatureKey>::PureAssembledSignatureType,
87 ),
88 >,
89 lock_cert: Option<QuorumCertificate2<T>>,
90 ) -> Result<Self> {
91 let mut by_lock: BTreeMap<Option<LockView>, BTreeMap<usize, _>> = BTreeMap::new();
92 for (lock, key, signature) in votes {
93 let Some(index) = stake_table
94 .iter()
95 .position(|entry| entry.public_key() == key)
96 else {
97 bail!("timeout vote signer {key} is not in the stake table");
98 };
99 by_lock.entry(lock).or_default().insert(index, signature);
100 }
101 let Some((latest_lock, latest)) = by_lock.pop_last() else {
102 bail!("no timeout votes to assemble");
103 };
104 ensure!(
105 lock_cert.as_ref().map(LockView::of) == latest_lock,
106 "the lock certificate does not certify the latest lock of the votes"
107 );
108 let params = T::SignatureKey::public_parameter(stake_table, U256::ZERO);
109 let aggregate = |signers: BTreeMap<usize, _>| {
110 let mut bits = bitvec![0; stake_table.len()];
111 for index in signers.keys() {
112 bits.set(*index, true);
113 }
114 let signatures: Vec<_> = signers.into_values().collect();
115 T::SignatureKey::assemble(¶ms, &bits, &signatures)
116 };
117 let earlier = by_lock
118 .into_iter()
119 .map(|(lock, signers)| TimeoutSignatures {
120 lock,
121 signatures: aggregate(signers),
122 })
123 .collect();
124 Ok(Self {
125 view_number: view,
126 epoch,
127 latest: LatestTimeoutSignatures {
128 lock_cert,
129 signatures: aggregate(latest),
130 },
131 earlier,
132 })
133 }
134
135 pub fn lock(&self) -> Option<LockView> {
137 self.lock_cert().map(LockView::of)
138 }
139
140 pub fn lock_cert(&self) -> Option<&QuorumCertificate2<T>> {
145 self.latest.lock_cert.as_ref()
146 }
147
148 pub fn timeout_lock(&self) -> TimeoutLock<'_, T> {
150 TimeoutLock::of(self.lock_cert(), self.epoch)
151 }
152
153 pub fn data(&self, lock: Option<LockView>) -> TimeoutData3 {
155 TimeoutData3 {
156 view: self.view_number,
157 epoch: self.epoch,
158 lock,
159 }
160 }
161
162 fn groups(
164 &self,
165 ) -> impl Iterator<Item = (Option<LockView>, &<T::SignatureKey as SignatureKey>::QcType)> {
166 self.earlier
167 .iter()
168 .map(|group| (group.lock, &group.signatures))
169 .chain([(self.lock(), &self.latest.signatures)])
170 }
171
172 pub fn check_lock(&self) -> Result<()> {
178 let Some(lock) = self.lock() else {
179 return Ok(());
180 };
181 ensure!(
182 lock.view <= self.view_number,
183 "timeout certificate lock {lock} is later than its view {}",
184 self.view_number
185 );
186 ensure!(
187 lock.epoch <= self.epoch,
188 "timeout certificate lock {lock} is later than its epoch {}",
189 self.epoch
190 );
191 Ok(())
192 }
193
194 pub fn check_signatures(
205 &self,
206 stake_table: &[<T::SignatureKey as SignatureKey>::StakeTableEntry],
207 threshold: U256,
208 upgrade_lock: &UpgradeLock<T>,
209 ) -> Result<()> {
210 let locks: Vec<_> = self.groups().map(|(lock, _)| lock).collect();
211 ensure!(
212 locks.windows(2).all(|w| w[0] < w[1]),
213 "timeout certificate groups are not in strict lock order"
214 );
215 self.check_lock()?;
216
217 let mut seen = bitvec![0; stake_table.len()];
220 let mut weight = U256::ZERO;
221 for (_, signatures) in self.groups() {
222 let (_, signers) = T::SignatureKey::sig_proof(signatures);
223 ensure!(
224 signers.len() == stake_table.len(),
225 "signer bit vector does not match the stake table"
226 );
227 ensure!(signers.any(), "a timeout certificate group has no signer");
228 for (index, entry) in stake_table.iter().enumerate() {
229 if signers[index] {
230 ensure!(
231 !seen[index],
232 "a timeout certificate signer is in two groups"
233 );
234 seen.set(index, true);
235 weight += entry.stake();
236 }
237 }
238 }
239 ensure!(
240 weight >= threshold,
241 "timeout certificate weight {weight} is below threshold {threshold}"
242 );
243
244 let params = T::SignatureKey::public_parameter(stake_table, U256::ZERO);
245 for (lock, signatures) in self.groups() {
246 let commit =
247 VersionedVoteData::new(self.data(lock), self.view_number, upgrade_lock)?.commit();
248 T::SignatureKey::check(¶ms, commit.as_ref(), signatures)
249 .wrap()
250 .context(|e| warn!("timeout certificate signature check failed: {e}"))?;
251 }
252 Ok(())
253 }
254
255 pub fn lock_epoch(&self) -> Option<EpochNumber> {
257 self.lock().map(|lock| lock.epoch)
258 }
259}
260
261impl<T: NodeType> HasViewNumber for TimeoutCertificate3<T> {
262 fn view_number(&self) -> ViewNumber {
263 self.view_number
264 }
265}
266
267impl<T: NodeType> HasEpoch for TimeoutCertificate3<T> {
268 fn epoch(&self) -> Option<EpochNumber> {
269 Some(self.epoch)
270 }
271}
272
273impl<T: NodeType> Committable for TimeoutCertificate3<T> {
280 fn commit(&self) -> Commitment<Self> {
281 let mut builder = committable::RawCommitmentBuilder::new("Timeout certificate v3")
282 .u64_field("view number", *self.view_number)
283 .u64_field("epoch number", *self.epoch)
284 .u64_field("earlier groups", self.earlier.len() as u64);
285 for (lock, signatures) in self.groups() {
286 builder = builder
287 .field("data", self.data(lock).commit())
288 .var_size_field("signatures", &serialize_signature2::<T>(signatures));
289 }
290 builder
291 .optional("lock certificate", &self.latest.lock_cert)
292 .finalize()
293 }
294}