1use std::sync::{
5 OnceLock,
6 atomic::{AtomicBool, Ordering},
7};
8
9use super::{ElectionProof, Error, Ticket, TipsetKey};
10use crate::{
11 beacon::{BeaconEntry, BeaconSchedule},
12 shim::{
13 address::Address, clock::ChainEpoch, crypto::Signature, econ::TokenAmount,
14 sector::PoStProof, version::NetworkVersion,
15 },
16 utils::{encoding::blake2b_256, get_size::big_int_heap_size_helper, multihash::MultihashCode},
17};
18use cid::Cid;
19use fvm_ipld_blockstore::Blockstore;
20use fvm_ipld_encoding::CborStore as _;
21use fvm_ipld_encoding::tuple::*;
22use get_size2::GetSize;
23use multihash_derive::MultihashDigest as _;
24use num::BigInt;
25use serde::{Deserialize, Serialize};
26
27#[cfg(test)]
28mod test;
29#[cfg(test)]
30pub use test::*;
31
32#[derive(Deserialize_tuple, Serialize_tuple, Clone, Hash, Eq, PartialEq, Debug)]
33pub struct RawBlockHeader {
34 pub miner_address: Address,
36 pub ticket: Option<Ticket>,
37 pub election_proof: Option<ElectionProof>,
38 pub beacon_entries: Vec<BeaconEntry>,
40 pub winning_post_proof: Vec<PoStProof>,
41 pub parents: TipsetKey,
45 #[serde(with = "crate::shim::fvm_shared_latest::bigint::bigint_ser")]
47 pub weight: BigInt,
48 pub epoch: ChainEpoch,
51 pub state_root: Cid,
53 pub message_receipts: Cid,
55 pub messages: Cid,
57 pub bls_aggregate: Option<Signature>,
59 pub timestamp: u64,
61 pub signature: Option<Signature>,
62 pub fork_signal: u64,
63 pub parent_base_fee: TokenAmount,
65}
66
67impl RawBlockHeader {
68 pub fn cid(&self) -> Cid {
69 self.car_block().expect("CBOR serialization failed").0
70 }
71 pub fn car_block(&self) -> anyhow::Result<(Cid, Vec<u8>)> {
72 let data = fvm_ipld_encoding::to_vec(self)?;
73 let cid = Cid::new_v1(
74 fvm_ipld_encoding::DAG_CBOR,
75 MultihashCode::Blake2b256.digest(&data),
76 );
77 Ok((cid, data))
78 }
79 pub(super) fn tipset_sort_key(&self) -> Option<([u8; 32], Vec<u8>)> {
80 let ticket_hash = blake2b_256(self.ticket.as_ref()?.vrfproof.as_bytes());
81 Some((ticket_hash, self.cid().to_bytes()))
82 }
83 pub fn verify_signature_against(&self, addr: &Address) -> Result<(), Error> {
85 let signature = self
86 .signature
87 .as_ref()
88 .ok_or_else(|| Error::InvalidSignature("Signature is nil in header".to_owned()))?;
89
90 signature
91 .verify(&self.signing_bytes(), addr)
92 .map_err(|e| Error::InvalidSignature(format!("Block signature invalid: {e}")))?;
93
94 Ok(())
95 }
96
97 pub fn validate_block_drand(
100 &self,
101 network_version: NetworkVersion,
102 b_schedule: &BeaconSchedule,
103 parent_epoch: ChainEpoch,
104 prev_entry: &BeaconEntry,
105 ) -> Result<(), Error> {
106 let (cb_epoch, curr_beacon) = b_schedule
107 .beacon_for_epoch(self.epoch)
108 .map_err(|e| Error::Validation(e.to_string()))?;
109 tracing::trace!(
110 "beacon network at {}: {:?}, is_chained: {}",
111 self.epoch,
112 curr_beacon.network(),
113 curr_beacon.network().is_chained()
114 );
115 if curr_beacon.network().is_chained() {
117 let (pb_epoch, _) = b_schedule
118 .beacon_for_epoch(parent_epoch)
119 .map_err(|e| Error::Validation(e.to_string()))?;
120 if cb_epoch != pb_epoch {
121 if self.beacon_entries.len() != 2 {
123 return Err(Error::Validation(format!(
124 "Expected two beacon entries at beacon fork, got {}",
125 self.beacon_entries.len()
126 )));
127 }
128
129 #[allow(clippy::indexing_slicing)]
130 curr_beacon
131 .verify_entries(&self.beacon_entries[1..], &self.beacon_entries[0])
132 .map_err(|e| Error::Validation(e.to_string()))?;
133
134 return Ok(());
135 }
136 }
137
138 let max_round = curr_beacon.max_beacon_round_for_epoch(network_version, self.epoch);
139 if max_round == prev_entry.round() {
141 if !self.beacon_entries.is_empty() {
142 return Err(Error::Validation(format!(
143 "expected not to have any beacon entries in this block, got: {}",
144 self.beacon_entries.len()
145 )));
146 }
147 return Ok(());
148 }
149
150 if curr_beacon.network().is_chained() && prev_entry.round() == 0 {
152 return Ok(());
155 }
156
157 let last = match self.beacon_entries.last() {
158 Some(last) => last,
159 None => {
160 return Err(Error::Validation(
161 "Block must include at least 1 beacon entry".to_string(),
162 ));
163 }
164 };
165
166 if last.round() != max_round {
167 return Err(Error::Validation(format!(
168 "expected final beacon entry in block to be at round {}, got: {}",
169 max_round,
170 last.round()
171 )));
172 }
173
174 if !curr_beacon
175 .verify_entries(&self.beacon_entries, prev_entry)
176 .map_err(|e| Error::Validation(e.to_string()))?
177 {
178 return Err(Error::Validation("beacon entry was invalid".into()));
179 }
180
181 Ok(())
182 }
183
184 pub fn signing_bytes(&self) -> Vec<u8> {
187 let mut blk = self.clone();
188 blk.signature = None;
189 fvm_ipld_encoding::to_vec(&blk).expect("block serialization cannot fail")
190 }
191}
192
193impl GetSize for RawBlockHeader {
195 fn get_heap_size(&self) -> usize {
196 let Self {
197 miner_address,
198 ticket,
199 election_proof,
200 beacon_entries,
201 winning_post_proof,
202 parents,
203 weight,
204 epoch: _,
205 state_root: _,
206 message_receipts: _,
207 messages: _,
208 bls_aggregate,
209 timestamp: _,
210 signature,
211 fork_signal: _,
212 parent_base_fee,
213 } = self;
214 miner_address.get_heap_size()
215 + ticket.get_heap_size()
216 + election_proof.get_heap_size()
217 + beacon_entries.get_heap_size()
218 + winning_post_proof.get_heap_size()
219 + parents.get_heap_size()
220 + big_int_heap_size_helper(weight)
221 + bls_aggregate.get_heap_size()
222 + signature.get_heap_size()
223 + parent_base_fee.get_heap_size()
224 }
225}
226
227#[cfg_attr(test, derive(Default))]
229#[derive(Debug, GetSize, derive_more::Deref)]
230pub struct CachingBlockHeader {
231 #[deref]
232 uncached: RawBlockHeader,
233 #[get_size(ignore)]
234 cid: OnceLock<Cid>,
235 has_ever_been_verified_against_any_signature: AtomicBool,
236}
237
238impl PartialEq for CachingBlockHeader {
239 fn eq(&self, other: &Self) -> bool {
240 self.uncached.epoch == other.uncached.epoch && self.cid() == other.cid()
242 }
243}
244
245impl Eq for CachingBlockHeader {}
246
247impl Clone for CachingBlockHeader {
248 fn clone(&self) -> Self {
249 Self {
250 uncached: self.uncached.clone(),
251 cid: self.cid.clone(),
252 has_ever_been_verified_against_any_signature: AtomicBool::new(
253 self.has_ever_been_verified_against_any_signature
254 .load(Ordering::Acquire),
255 ),
256 }
257 }
258}
259
260impl From<RawBlockHeader> for CachingBlockHeader {
261 fn from(value: RawBlockHeader) -> Self {
262 Self::new(value)
263 }
264}
265
266impl CachingBlockHeader {
267 pub fn new(uncached: RawBlockHeader) -> Self {
268 Self {
269 uncached,
270 cid: OnceLock::new(),
271 has_ever_been_verified_against_any_signature: AtomicBool::new(false),
272 }
273 }
274 pub fn into_raw(self) -> RawBlockHeader {
275 self.uncached
276 }
277 pub fn load(store: &impl Blockstore, cid: Cid) -> anyhow::Result<Option<Self>> {
279 if let Some(uncached) = store.get_cbor::<RawBlockHeader>(&cid)? {
280 Ok(Some(Self {
281 uncached,
282 cid: cid.into(),
283 has_ever_been_verified_against_any_signature: AtomicBool::new(false),
284 }))
285 } else {
286 Ok(None)
287 }
288 }
289 pub fn cid(&self) -> &Cid {
290 self.cid.get_or_init(|| self.uncached.cid())
291 }
292
293 pub fn verify_signature_against(&self, addr: &Address) -> Result<(), Error> {
294 match self
295 .has_ever_been_verified_against_any_signature
296 .load(Ordering::Acquire)
297 {
298 true => Ok(()),
299 false => match self.uncached.verify_signature_against(addr) {
300 Ok(()) => {
301 self.has_ever_been_verified_against_any_signature
302 .store(true, Ordering::Release);
303 Ok(())
304 }
305 Err(e) => Err(e),
306 },
307 }
308 }
309}
310
311impl From<CachingBlockHeader> for RawBlockHeader {
312 fn from(value: CachingBlockHeader) -> Self {
313 value.into_raw()
314 }
315}
316
317impl Serialize for CachingBlockHeader {
318 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
319 where
320 S: serde::Serializer,
321 {
322 self.uncached.serialize(serializer)
323 }
324}
325
326impl<'de> Deserialize<'de> for CachingBlockHeader {
327 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328 where
329 D: serde::Deserializer<'de>,
330 {
331 RawBlockHeader::deserialize(deserializer).map(Self::new)
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use crate::beacon::{BeaconEntry, BeaconPoint, BeaconSchedule, mock_beacon::MockBeacon};
339 use crate::blocks::{CachingBlockHeader, Error};
340 use crate::shim::clock::ChainEpoch;
341 use crate::shim::{address::Address, version::NetworkVersion};
342 use crate::utils::encoding::from_slice_with_fallback;
343 use crate::utils::multihash::MultihashCode;
344 use cid::Cid;
345 use fvm_ipld_encoding::{DAG_CBOR, to_vec};
346
347 impl quickcheck::Arbitrary for CachingBlockHeader {
348 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
349 CachingBlockHeader::new(RawBlockHeader {
351 miner_address: Address::new_id(0),
352 epoch: ChainEpoch::arbitrary(g),
353 ..Default::default()
354 })
355 }
356 }
357
358 #[test]
359 fn symmetric_header_encoding() {
360 let bz = hex::decode("904300e8078158608798de4e49e02ee129920224ea767650aa6e693857431cc95b5a092a57d80ef4d841ebedbf09f7680a5e286cd297f40100b496648e1fa0fd55f899a45d51404a339564e7d4809741ba41d9fcc8ac0261bf521cd5f718389e81354eff2aa52b338201586084d8929eeedc654d6bec8bb750fcc8a1ebf2775d8167d3418825d9e989905a8b7656d906d23dc83e0dad6e7f7a193df70a82d37da0565ce69b776d995eefd50354c85ec896a2173a5efed53a27275e001ad72a3317b2190b98cceb0f01c46b7b81821a00013cbe5860ae1102b76dea635b2f07b7d06e1671d695c4011a73dc33cace159509eac7edc305fa74495505f0cd0046ee0d3b17fabc0fc0560d44d296c6d91bcc94df76266a8e9d5312c617ca72a2e186cadee560477f6d120f6614e21fb07c2390a166a25981820358c0b965705cec77b46200af8fb2e47c0eca175564075061132949f00473dcbe74529c623eb510081e8b8bd34418d21c646485d893f040dcfb7a7e7af9ae4ed7bd06772c24fb0cc5b8915300ab5904fbd90269d523018fbf074620fd3060d55dd6c6057b4195950ac4155a735e8fec79767f659c30ea6ccf0813a4ab2b4e60f36c04c71fb6c58efc123f60c6ea8797ab3706a80a4ccc1c249989934a391803789ab7d04f514ee0401d0f87a1f5262399c451dcf5f7ec3bb307fc6f1a41f5ff3a5ddb81d82a5827000171a0e402209a0640d0620af5d1c458effce4cbb8969779c9072b164d3fe6f5179d6378d8cd4300310001d82a5827000171a0e402208fbc07f7587e2efebab9ff1ab27c928881abf9d1b7e5ad5206781415615867aed82a5827000171a0e40220e5658b3d18cd06e1db9015b4b0ec55c123a24d5be1ea24d83938c5b8397b4f2fd82a5827000171a0e402209967f10c4c0e336b3517d3a972f701dadea5b41ce33defb126b88e650cf884545861028ec8b64e2d93272f97edcab1f56bcad4a2b145ea88c232bfae228e4adbbd807e6a41740cc8cb569197dae6b2cbf8c1a4035e81fd7805ccbe88a5ec476bcfa438db4bd677de06b45e94310533513e9d17c635940ba8fa2650cdb34d445724c5971a5f44387e5861028a45c70a39fe8e526cbb6ba2a850e9063460873d6329f26cc2fc91972256c40249dba289830cc99619109c18e695d78012f760e7fda1b68bc3f1fe20ff8a017044753da38ca6384de652f3ee13aae5b64e6f88f85fd50d5c862fed3c1f594ace004500053724e0").unwrap();
362 let header = from_slice_with_fallback::<CachingBlockHeader>(&bz).unwrap();
363 assert_eq!(to_vec(&header).unwrap(), bz);
364
365 header
369 .verify_signature_against(
370 &"f3vfs6f7tagrcpnwv65wq3leznbajqyg77bmijrpvoyjv3zjyi3urq25vigfbs3ob6ug5xdihajumtgsxnz2pa"
371 .parse()
372 .unwrap())
373 .unwrap();
374 }
375
376 #[test]
377 fn beacon_entry_exists() {
378 let block_header = CachingBlockHeader::new(RawBlockHeader {
380 miner_address: Address::new_id(0),
381 ..Default::default()
382 });
383 let beacon_schedule = BeaconSchedule(vec![BeaconPoint {
384 height: 0,
385 beacon: Box::<MockBeacon>::default(),
386 }]);
387 let chain_epoch = 0;
388 let beacon_entry = BeaconEntry::new(1, vec![]);
389 if let Err(e) = block_header.validate_block_drand(
391 NetworkVersion::V16,
392 &beacon_schedule,
393 chain_epoch,
394 &beacon_entry,
395 ) {
396 match e {
398 Error::Validation(why) => {
399 assert_eq!(why, "Block must include at least 1 beacon entry");
400 }
401 _ => {
402 panic!("validate block drand must detect a beacon entry in the block header");
403 }
404 }
405 }
406 }
407
408 #[test]
409 fn test_genesis_parent() {
410 assert_eq!(
411 Cid::new_v1(
412 DAG_CBOR,
413 MultihashCode::Sha2_256.digest(&FILECOIN_GENESIS_BLOCK)
414 ),
415 *FILECOIN_GENESIS_CID
416 );
417 }
418}