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