1use std::sync::{
5 OnceLock,
6 atomic::{AtomicBool, Ordering},
7};
8
9use super::{ElectionProof, Error, Ticket, TipsetKey};
10use crate::{
11 beacon::{Beacon as _, 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".into()))?;
89
90 signature.verify(&self.signing_bytes(), addr).map_err(|e| {
91 Error::InvalidSignature(format!("Block signature invalid: {e:#}").into())
92 })?;
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(format!("{e:#}").into()))?;
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(format!("{e:#}").into()))?;
120 if cb_epoch != pb_epoch {
121 if self.beacon_entries.len() != 2 {
123 return Err(Error::Validation(
124 format!(
125 "Expected two beacon entries at beacon fork, got {}",
126 self.beacon_entries.len()
127 )
128 .into(),
129 ));
130 }
131
132 #[allow(clippy::indexing_slicing)]
133 curr_beacon
134 .verify_entries(&self.beacon_entries[1..], &self.beacon_entries[0])
135 .map_err(|e| Error::Validation(format!("{e:#}").into()))?;
136
137 return Ok(());
138 }
139 }
140
141 let max_round = curr_beacon.max_beacon_round_for_epoch(network_version, self.epoch);
142 if max_round == prev_entry.round() {
144 if !self.beacon_entries.is_empty() {
145 return Err(Error::Validation(
146 format!(
147 "expected not to have any beacon entries in this block, got: {}",
148 self.beacon_entries.len()
149 )
150 .into(),
151 ));
152 }
153 return Ok(());
154 }
155
156 if curr_beacon.network().is_chained() && prev_entry.round() == 0 {
158 return Ok(());
161 }
162
163 let last = match self.beacon_entries.last() {
164 Some(last) => last,
165 None => {
166 return Err(Error::Validation(
167 "Block must include at least 1 beacon entry".into(),
168 ));
169 }
170 };
171
172 if last.round() != max_round {
173 return Err(Error::Validation(
174 format!(
175 "expected final beacon entry in block to be at round {}, got: {}",
176 max_round,
177 last.round()
178 )
179 .into(),
180 ));
181 }
182
183 if !curr_beacon
184 .verify_entries(&self.beacon_entries, prev_entry)
185 .map_err(|e| Error::Validation(format!("{e:#}").into()))?
186 {
187 return Err(Error::Validation("beacon entry was invalid".into()));
188 }
189
190 Ok(())
191 }
192
193 pub fn signing_bytes(&self) -> Vec<u8> {
196 let mut blk = self.clone();
197 blk.signature = None;
198 fvm_ipld_encoding::to_vec(&blk).expect("block serialization cannot fail")
199 }
200}
201
202impl GetSize for RawBlockHeader {
204 fn get_heap_size(&self) -> usize {
205 let Self {
206 miner_address,
207 ticket,
208 election_proof,
209 beacon_entries,
210 winning_post_proof,
211 parents,
212 weight,
213 epoch: _,
214 state_root: _,
215 message_receipts: _,
216 messages: _,
217 bls_aggregate,
218 timestamp: _,
219 signature,
220 fork_signal: _,
221 parent_base_fee,
222 } = self;
223 miner_address.get_heap_size()
224 + ticket.get_heap_size()
225 + election_proof.get_heap_size()
226 + beacon_entries.get_heap_size()
227 + winning_post_proof.get_heap_size()
228 + parents.get_heap_size()
229 + big_int_heap_size_helper(weight)
230 + bls_aggregate.get_heap_size()
231 + signature.get_heap_size()
232 + parent_base_fee.get_heap_size()
233 }
234}
235
236#[cfg_attr(test, derive(Default))]
238#[derive(Debug, GetSize, derive_more::Deref)]
239pub struct CachingBlockHeader {
240 #[deref]
241 uncached: RawBlockHeader,
242 #[get_size(ignore)]
243 cid: OnceLock<Cid>,
244 has_ever_been_verified_against_any_signature: AtomicBool,
245}
246
247impl PartialEq for CachingBlockHeader {
248 fn eq(&self, other: &Self) -> bool {
249 self.uncached.epoch == other.uncached.epoch && self.cid() == other.cid()
251 }
252}
253
254impl Eq for CachingBlockHeader {}
255
256impl Clone for CachingBlockHeader {
257 fn clone(&self) -> Self {
258 Self {
259 uncached: self.uncached.clone(),
260 cid: self.cid.clone(),
261 has_ever_been_verified_against_any_signature: AtomicBool::new(
262 self.has_ever_been_verified_against_any_signature
263 .load(Ordering::Acquire),
264 ),
265 }
266 }
267}
268
269impl From<RawBlockHeader> for CachingBlockHeader {
270 fn from(value: RawBlockHeader) -> Self {
271 Self::new(value)
272 }
273}
274
275impl CachingBlockHeader {
276 pub fn new(uncached: RawBlockHeader) -> Self {
277 Self {
278 uncached,
279 cid: OnceLock::new(),
280 has_ever_been_verified_against_any_signature: AtomicBool::new(false),
281 }
282 }
283 pub fn into_raw(self) -> RawBlockHeader {
284 self.uncached
285 }
286 pub fn load(store: &impl Blockstore, cid: Cid) -> anyhow::Result<Option<Self>> {
288 if let Some(uncached) = store.get_cbor::<RawBlockHeader>(&cid)? {
289 Ok(Some(Self {
290 uncached,
291 cid: cid.into(),
292 has_ever_been_verified_against_any_signature: AtomicBool::new(false),
293 }))
294 } else {
295 Ok(None)
296 }
297 }
298 pub fn cid(&self) -> &Cid {
299 self.cid.get_or_init(|| self.uncached.cid())
300 }
301
302 pub fn verify_signature_against(&self, addr: &Address) -> Result<(), Error> {
303 match self
304 .has_ever_been_verified_against_any_signature
305 .load(Ordering::Acquire)
306 {
307 true => Ok(()),
308 false => match self.uncached.verify_signature_against(addr) {
309 Ok(()) => {
310 self.has_ever_been_verified_against_any_signature
311 .store(true, Ordering::Release);
312 Ok(())
313 }
314 Err(e) => Err(e),
315 },
316 }
317 }
318}
319
320impl From<CachingBlockHeader> for RawBlockHeader {
321 fn from(value: CachingBlockHeader) -> Self {
322 value.into_raw()
323 }
324}
325
326impl Serialize for CachingBlockHeader {
327 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
328 where
329 S: serde::Serializer,
330 {
331 self.uncached.serialize(serializer)
332 }
333}
334
335impl<'de> Deserialize<'de> for CachingBlockHeader {
336 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
337 where
338 D: serde::Deserializer<'de>,
339 {
340 RawBlockHeader::deserialize(deserializer).map(Self::new)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use crate::beacon::{BeaconEntry, BeaconPoint, BeaconSchedule, mock_beacon::MockBeacon};
348 use crate::blocks::{CachingBlockHeader, Error};
349 use crate::shim::clock::ChainEpoch;
350 use crate::shim::{address::Address, version::NetworkVersion};
351 use crate::utils::encoding::from_slice_with_fallback;
352 use crate::utils::multihash::MultihashCode;
353 use cid::Cid;
354 use fvm_ipld_encoding::{DAG_CBOR, to_vec};
355
356 impl quickcheck::Arbitrary for CachingBlockHeader {
357 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
358 CachingBlockHeader::new(RawBlockHeader {
360 miner_address: Address::new_id(0),
361 epoch: ChainEpoch::arbitrary(g),
362 ..Default::default()
363 })
364 }
365 }
366
367 #[test]
368 fn symmetric_header_encoding() {
369 let bz = hex::decode("904300e8078158608798de4e49e02ee129920224ea767650aa6e693857431cc95b5a092a57d80ef4d841ebedbf09f7680a5e286cd297f40100b496648e1fa0fd55f899a45d51404a339564e7d4809741ba41d9fcc8ac0261bf521cd5f718389e81354eff2aa52b338201586084d8929eeedc654d6bec8bb750fcc8a1ebf2775d8167d3418825d9e989905a8b7656d906d23dc83e0dad6e7f7a193df70a82d37da0565ce69b776d995eefd50354c85ec896a2173a5efed53a27275e001ad72a3317b2190b98cceb0f01c46b7b81821a00013cbe5860ae1102b76dea635b2f07b7d06e1671d695c4011a73dc33cace159509eac7edc305fa74495505f0cd0046ee0d3b17fabc0fc0560d44d296c6d91bcc94df76266a8e9d5312c617ca72a2e186cadee560477f6d120f6614e21fb07c2390a166a25981820358c0b965705cec77b46200af8fb2e47c0eca175564075061132949f00473dcbe74529c623eb510081e8b8bd34418d21c646485d893f040dcfb7a7e7af9ae4ed7bd06772c24fb0cc5b8915300ab5904fbd90269d523018fbf074620fd3060d55dd6c6057b4195950ac4155a735e8fec79767f659c30ea6ccf0813a4ab2b4e60f36c04c71fb6c58efc123f60c6ea8797ab3706a80a4ccc1c249989934a391803789ab7d04f514ee0401d0f87a1f5262399c451dcf5f7ec3bb307fc6f1a41f5ff3a5ddb81d82a5827000171a0e402209a0640d0620af5d1c458effce4cbb8969779c9072b164d3fe6f5179d6378d8cd4300310001d82a5827000171a0e402208fbc07f7587e2efebab9ff1ab27c928881abf9d1b7e5ad5206781415615867aed82a5827000171a0e40220e5658b3d18cd06e1db9015b4b0ec55c123a24d5be1ea24d83938c5b8397b4f2fd82a5827000171a0e402209967f10c4c0e336b3517d3a972f701dadea5b41ce33defb126b88e650cf884545861028ec8b64e2d93272f97edcab1f56bcad4a2b145ea88c232bfae228e4adbbd807e6a41740cc8cb569197dae6b2cbf8c1a4035e81fd7805ccbe88a5ec476bcfa438db4bd677de06b45e94310533513e9d17c635940ba8fa2650cdb34d445724c5971a5f44387e5861028a45c70a39fe8e526cbb6ba2a850e9063460873d6329f26cc2fc91972256c40249dba289830cc99619109c18e695d78012f760e7fda1b68bc3f1fe20ff8a017044753da38ca6384de652f3ee13aae5b64e6f88f85fd50d5c862fed3c1f594ace004500053724e0").unwrap();
371 let header = from_slice_with_fallback::<CachingBlockHeader>(&bz).unwrap();
372 assert_eq!(to_vec(&header).unwrap(), bz);
373
374 header
378 .verify_signature_against(
379 &"f3vfs6f7tagrcpnwv65wq3leznbajqyg77bmijrpvoyjv3zjyi3urq25vigfbs3ob6ug5xdihajumtgsxnz2pa"
380 .parse()
381 .unwrap())
382 .unwrap();
383 }
384
385 #[test]
386 fn beacon_entry_exists() {
387 let block_header = CachingBlockHeader::new(RawBlockHeader {
389 miner_address: Address::new_id(0),
390 ..Default::default()
391 });
392 let beacon_schedule = BeaconSchedule(vec![BeaconPoint::new(0, <MockBeacon>::default())]);
393 let chain_epoch = 0;
394 let beacon_entry = BeaconEntry::new(1, vec![]);
395 if let Err(e) = block_header.validate_block_drand(
397 NetworkVersion::V16,
398 &beacon_schedule,
399 chain_epoch,
400 &beacon_entry,
401 ) {
402 match e {
404 Error::Validation(why) => {
405 assert_eq!(why, "Block must include at least 1 beacon entry");
406 }
407 _ => {
408 panic!("validate block drand must detect a beacon entry in the block header");
409 }
410 }
411 }
412 }
413
414 #[test]
415 fn test_genesis_parent() {
416 assert_eq!(
417 Cid::new_v1(
418 DAG_CBOR,
419 MultihashCode::Sha2_256.digest(&FILECOIN_GENESIS_BLOCK)
420 ),
421 *FILECOIN_GENESIS_CID
422 );
423 }
424}