1use std::{
6 borrow::Cow,
7 collections::{BTreeMap, BTreeSet},
8 fmt::Debug,
9};
10
11use allocative::Allocative;
12use async_graphql::SimpleObject;
13use linera_base::{
14 crypto::{BcsHashable, CryptoHash},
15 data_types::{Blob, BlockHeight, Epoch, Event, OracleResponse, Timestamp},
16 hashed::Hashed,
17 identifiers::{AccountOwner, BlobId, BlobType, ChainId, StreamId},
18};
19use linera_execution::{BlobState, Operation, OutgoingMessage};
20use serde::{ser::SerializeStruct, Deserialize, Serialize};
21use thiserror::Error;
22
23use crate::{
24 data_types::{
25 BlockExecutionOutcome, IncomingBundle, MessageBundle, OperationResult, OutgoingMessageExt,
26 ProposedBlock, Transaction,
27 },
28 types::CertificateValue,
29};
30
31#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
33#[serde(transparent)]
34pub struct ValidatedBlock(Hashed<Block>);
35
36impl ValidatedBlock {
37 pub fn new(block: Block) -> Self {
39 Self(Hashed::new(block))
40 }
41
42 pub fn from_hashed(block: Hashed<Block>) -> Self {
43 Self(block)
44 }
45
46 pub fn inner(&self) -> &Hashed<Block> {
47 &self.0
48 }
49
50 pub fn block(&self) -> &Block {
52 self.0.inner()
53 }
54
55 pub fn into_inner(self) -> Block {
57 self.0.into_inner()
58 }
59
60 pub fn to_log_str(&self) -> &'static str {
61 "validated_block"
62 }
63
64 pub fn chain_id(&self) -> ChainId {
65 self.0.inner().header.chain_id
66 }
67
68 pub fn height(&self) -> BlockHeight {
69 self.0.inner().header.height
70 }
71
72 pub fn epoch(&self) -> Epoch {
73 self.0.inner().header.epoch
74 }
75}
76
77#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
79#[serde(transparent)]
80pub struct ConfirmedBlock(Hashed<Block>);
81
82#[async_graphql::Object(cache_control(no_cache))]
83impl ConfirmedBlock {
84 #[graphql(derived(name = "block"))]
85 async fn _block(&self) -> Block {
86 self.0.inner().clone()
87 }
88
89 async fn status(&self) -> String {
90 "confirmed".to_string()
91 }
92
93 async fn hash(&self) -> CryptoHash {
94 self.0.hash()
95 }
96}
97
98impl ConfirmedBlock {
99 pub fn new(block: Block) -> Self {
100 Self(Hashed::new(block))
101 }
102
103 pub fn from_hashed(block: Hashed<Block>) -> Self {
104 Self(block)
105 }
106
107 pub fn inner(&self) -> &Hashed<Block> {
108 &self.0
109 }
110
111 pub fn into_inner(self) -> Hashed<Block> {
112 self.0
113 }
114
115 pub fn block(&self) -> &Block {
117 self.0.inner()
118 }
119
120 pub fn into_block(self) -> Block {
122 self.0.into_inner()
123 }
124
125 pub fn chain_id(&self) -> ChainId {
126 self.0.inner().header.chain_id
127 }
128
129 pub fn height(&self) -> BlockHeight {
130 self.0.inner().header.height
131 }
132
133 pub fn timestamp(&self) -> Timestamp {
134 self.0.inner().header.timestamp
135 }
136
137 pub fn to_log_str(&self) -> &'static str {
138 "confirmed_block"
139 }
140
141 pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
143 self.block().matches_proposed_block(block)
144 }
145
146 pub fn to_blob_state(&self, is_stored_block: bool) -> BlobState {
148 BlobState {
149 last_used_by: is_stored_block.then_some(self.0.hash()),
150 chain_id: self.chain_id(),
151 block_height: self.height(),
152 epoch: is_stored_block.then_some(self.epoch()),
153 }
154 }
155}
156
157#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
158#[serde(transparent)]
159pub struct Timeout(Hashed<TimeoutInner>);
160
161#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
162#[serde(rename = "Timeout")]
163pub(crate) struct TimeoutInner {
164 chain_id: ChainId,
165 height: BlockHeight,
166 epoch: Epoch,
167}
168
169impl Timeout {
170 pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self {
171 let inner = TimeoutInner {
172 chain_id,
173 height,
174 epoch,
175 };
176 Self(Hashed::new(inner))
177 }
178
179 pub fn to_log_str(&self) -> &'static str {
180 "timeout"
181 }
182
183 pub fn chain_id(&self) -> ChainId {
184 self.0.inner().chain_id
185 }
186
187 pub fn height(&self) -> BlockHeight {
188 self.0.inner().height
189 }
190
191 pub fn epoch(&self) -> Epoch {
192 self.0.inner().epoch
193 }
194
195 pub(crate) fn inner(&self) -> &Hashed<TimeoutInner> {
196 &self.0
197 }
198}
199
200impl BcsHashable<'_> for Timeout {}
201impl BcsHashable<'_> for TimeoutInner {}
202
203#[derive(Clone, Copy, Debug, Error)]
205pub enum ConversionError {
206 #[error("Expected a `ConfirmedBlockCertificate` value")]
208 ConfirmedBlock,
209
210 #[error("Expected a `ValidatedBlockCertificate` value")]
212 ValidatedBlock,
213
214 #[error("Expected a `TimeoutCertificate` value")]
216 Timeout,
217}
218
219#[derive(Debug, PartialEq, Eq, Hash, Clone, SimpleObject, Allocative)]
226pub struct Block {
227 pub header: BlockHeader,
229 pub body: BlockBody,
231}
232
233impl Serialize for Block {
234 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
235 let mut state = serializer.serialize_struct("Block", 2)?;
236
237 let header = SerializedHeader {
238 chain_id: self.header.chain_id,
239 epoch: self.header.epoch,
240 height: self.header.height,
241 timestamp: self.header.timestamp,
242 state_hash: self.header.state_hash,
243 previous_block_hash: self.header.previous_block_hash,
244 authenticated_owner: self.header.authenticated_owner,
245 };
246 state.serialize_field("header", &header)?;
247 state.serialize_field("body", &self.body)?;
248 state.end()
249 }
250}
251
252impl<'de> Deserialize<'de> for Block {
253 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
254 #[derive(Deserialize)]
255 #[serde(rename = "Block")]
256 struct Inner {
257 header: SerializedHeader,
258 body: BlockBody,
259 }
260 let inner = Inner::deserialize(deserializer)?;
261
262 let transactions_hash = hashing::hash_vec(&inner.body.transactions);
263 let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
264 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
265 inner: Cow::Borrowed(&inner.body.previous_message_blocks),
266 });
267 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
268 inner: Cow::Borrowed(&inner.body.previous_event_blocks),
269 });
270 let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
271 let events_hash = hashing::hash_vec_vec(&inner.body.events);
272 let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
273 let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
274
275 let header = BlockHeader {
276 chain_id: inner.header.chain_id,
277 epoch: inner.header.epoch,
278 height: inner.header.height,
279 timestamp: inner.header.timestamp,
280 state_hash: inner.header.state_hash,
281 previous_block_hash: inner.header.previous_block_hash,
282 authenticated_owner: inner.header.authenticated_owner,
283 transactions_hash,
284 messages_hash,
285 previous_message_blocks_hash,
286 previous_event_blocks_hash,
287 oracle_responses_hash,
288 events_hash,
289 blobs_hash,
290 operation_results_hash,
291 };
292
293 Ok(Self {
294 header,
295 body: inner.body,
296 })
297 }
298}
299
300#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
304pub struct BlockHeader {
305 pub chain_id: ChainId,
307 pub epoch: Epoch,
309 pub height: BlockHeight,
311 pub timestamp: Timestamp,
313 pub state_hash: CryptoHash,
315 pub previous_block_hash: Option<CryptoHash>,
317 pub authenticated_owner: Option<AccountOwner>,
322
323 pub transactions_hash: CryptoHash,
326
327 pub messages_hash: CryptoHash,
330 pub previous_message_blocks_hash: CryptoHash,
332 pub previous_event_blocks_hash: CryptoHash,
334 pub oracle_responses_hash: CryptoHash,
336 pub events_hash: CryptoHash,
338 pub blobs_hash: CryptoHash,
340 pub operation_results_hash: CryptoHash,
342}
343
344#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
346#[graphql(complex)]
347pub struct BlockBody {
348 #[graphql(skip)]
351 pub transactions: Vec<Transaction>,
352 pub messages: Vec<Vec<OutgoingMessage>>,
354 pub previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
356 pub previous_event_blocks: BTreeMap<StreamId, (CryptoHash, BlockHeight)>,
358 pub oracle_responses: Vec<Vec<OracleResponse>>,
360 pub events: Vec<Vec<Event>>,
362 pub blobs: Vec<Vec<Blob>>,
364 pub operation_results: Vec<OperationResult>,
366}
367
368impl BlockBody {
369 pub fn operations(&self) -> impl Iterator<Item = &Operation> {
371 self.transactions.iter().filter_map(|tx| match tx {
372 Transaction::ExecuteOperation(operation) => Some(operation),
373 Transaction::ReceiveMessages(_) => None,
374 })
375 }
376
377 pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
379 self.transactions.iter().filter_map(|tx| match tx {
380 Transaction::ReceiveMessages(bundle) => Some(bundle),
381 Transaction::ExecuteOperation(_) => None,
382 })
383 }
384}
385
386#[async_graphql::ComplexObject]
387impl BlockBody {
388 async fn transaction_metadata(&self) -> Vec<crate::data_types::TransactionMetadata> {
390 self.transactions
391 .iter()
392 .map(crate::data_types::TransactionMetadata::from_transaction)
393 .collect()
394 }
395}
396
397impl Block {
398 pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
399 let transactions_hash = hashing::hash_vec(&block.transactions);
400 let messages_hash = hashing::hash_vec_vec(&outcome.messages);
401 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
402 inner: Cow::Borrowed(&outcome.previous_message_blocks),
403 });
404 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
405 inner: Cow::Borrowed(&outcome.previous_event_blocks),
406 });
407 let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
408 let events_hash = hashing::hash_vec_vec(&outcome.events);
409 let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
410 let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
411
412 let header = BlockHeader {
413 chain_id: block.chain_id,
414 epoch: block.epoch,
415 height: block.height,
416 timestamp: block.timestamp,
417 state_hash: outcome.state_hash,
418 previous_block_hash: block.previous_block_hash,
419 authenticated_owner: block.authenticated_owner,
420 transactions_hash,
421 messages_hash,
422 previous_message_blocks_hash,
423 previous_event_blocks_hash,
424 oracle_responses_hash,
425 events_hash,
426 blobs_hash,
427 operation_results_hash,
428 };
429
430 let body = BlockBody {
431 transactions: block.transactions,
432 messages: outcome.messages,
433 previous_message_blocks: outcome.previous_message_blocks,
434 previous_event_blocks: outcome.previous_event_blocks,
435 oracle_responses: outcome.oracle_responses,
436 events: outcome.events,
437 blobs: outcome.blobs,
438 operation_results: outcome.operation_results,
439 };
440
441 Self { header, body }
442 }
443
444 pub fn message_bundles_for(
449 &self,
450 recipient: ChainId,
451 certificate_hash: CryptoHash,
452 ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
453 let mut index = 0u32;
454 let block_height = self.header.height;
455 let block_timestamp = self.header.timestamp;
456 let block_epoch = self.header.epoch;
457
458 (0u32..)
459 .zip(self.messages())
460 .filter_map(move |(transaction_index, txn_messages)| {
461 let messages = (index..)
462 .zip(txn_messages)
463 .filter(|(_, message)| message.destination == recipient)
464 .map(|(idx, message)| message.clone().into_posted(idx))
465 .collect::<Vec<_>>();
466 index += txn_messages.len() as u32;
467 (!messages.is_empty()).then(|| {
468 let bundle = MessageBundle {
469 height: block_height,
470 timestamp: block_timestamp,
471 certificate_hash,
472 transaction_index,
473 messages,
474 };
475 (block_epoch, bundle)
476 })
477 })
478 }
479
480 pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
483 let mut blob_ids = self.oracle_blob_ids();
484 blob_ids.extend(self.published_blob_ids());
485 blob_ids.extend(self.created_blob_ids());
486 if self.header.height == BlockHeight(0) {
487 blob_ids.insert(BlobId::new(
489 self.header.chain_id.0,
490 BlobType::ChainDescription,
491 ));
492 }
493 blob_ids
494 }
495
496 pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
498 self.oracle_blob_ids().contains(blob_id)
499 || self.published_blob_ids().contains(blob_id)
500 || self.created_blob_ids().contains(blob_id)
501 || (self.header.height == BlockHeight(0)
502 && (blob_id.blob_type == BlobType::ChainDescription
503 && blob_id.hash == self.header.chain_id.0))
504 }
505
506 pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
508 self.body
509 .operations()
510 .flat_map(Operation::published_blob_ids)
511 .collect()
512 }
513
514 pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
516 self.body
517 .blobs
518 .iter()
519 .flatten()
520 .map(|blob| blob.id())
521 .collect()
522 }
523
524 pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
526 self.body
527 .blobs
528 .iter()
529 .flatten()
530 .map(|blob| (blob.id(), blob.clone()))
531 .collect()
532 }
533
534 pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
536 let mut required_blob_ids = BTreeSet::new();
537 for responses in &self.body.oracle_responses {
538 for response in responses {
539 if let OracleResponse::Blob(blob_id) = response {
540 required_blob_ids.insert(*blob_id);
541 }
542 }
543 }
544
545 required_blob_ids
546 }
547
548 pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
550 &self.body.messages
551 }
552
553 pub fn recipients(&self) -> BTreeSet<ChainId> {
555 self.body
556 .messages
557 .iter()
558 .flat_map(|messages| messages.iter().map(|message| message.destination))
559 .collect()
560 }
561
562 pub fn has_oracle_responses(&self) -> bool {
564 self.body
565 .oracle_responses
566 .iter()
567 .any(|responses| !responses.is_empty())
568 }
569
570 pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
572 let ProposedBlock {
573 chain_id,
574 epoch,
575 transactions,
576 height,
577 timestamp,
578 authenticated_owner,
579 previous_block_hash,
580 } = block;
581 *chain_id == self.header.chain_id
582 && *epoch == self.header.epoch
583 && *transactions == self.body.transactions
584 && *height == self.header.height
585 && *timestamp == self.header.timestamp
586 && *authenticated_owner == self.header.authenticated_owner
587 && *previous_block_hash == self.header.previous_block_hash
588 }
589
590 #[cfg(with_testing)]
592 #[expect(clippy::too_many_arguments)]
593 pub fn outcome_matches(
594 &self,
595 expected_messages: Vec<Vec<OutgoingMessage>>,
596 expected_previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
597 expected_previous_event_blocks: BTreeMap<StreamId, (CryptoHash, BlockHeight)>,
598 expected_oracle_responses: Vec<Vec<OracleResponse>>,
599 expected_events: Vec<Vec<Event>>,
600 expected_blobs: Vec<Vec<Blob>>,
601 expected_operation_results: Vec<OperationResult>,
602 ) -> bool {
603 let BlockBody {
604 transactions: _,
605 messages,
606 previous_message_blocks,
607 previous_event_blocks,
608 oracle_responses,
609 events,
610 blobs,
611 operation_results,
612 } = &self.body;
613 *messages == expected_messages
614 && *previous_message_blocks == expected_previous_message_blocks
615 && *previous_event_blocks == expected_previous_event_blocks
616 && *oracle_responses == expected_oracle_responses
617 && *events == expected_events
618 && *blobs == expected_blobs
619 && *operation_results == expected_operation_results
620 }
621
622 pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
623 let proposed_block = ProposedBlock {
624 chain_id: self.header.chain_id,
625 epoch: self.header.epoch,
626 transactions: self.body.transactions,
627 height: self.header.height,
628 timestamp: self.header.timestamp,
629 authenticated_owner: self.header.authenticated_owner,
630 previous_block_hash: self.header.previous_block_hash,
631 };
632 let outcome = BlockExecutionOutcome {
633 state_hash: self.header.state_hash,
634 messages: self.body.messages,
635 previous_message_blocks: self.body.previous_message_blocks,
636 previous_event_blocks: self.body.previous_event_blocks,
637 oracle_responses: self.body.oracle_responses,
638 events: self.body.events,
639 blobs: self.body.blobs,
640 operation_results: self.body.operation_results,
641 };
642 (proposed_block, outcome)
643 }
644
645 pub fn iter_created_blobs(&self) -> impl Iterator<Item = (BlobId, Blob)> + '_ {
646 self.body
647 .blobs
648 .iter()
649 .flatten()
650 .map(|blob| (blob.id(), blob.clone()))
651 }
652}
653
654impl BcsHashable<'_> for Block {}
655
656#[derive(Serialize, Deserialize)]
657pub struct PreviousMessageBlocksMap<'a> {
658 inner: Cow<'a, BTreeMap<ChainId, (CryptoHash, BlockHeight)>>,
659}
660
661impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
662
663#[derive(Serialize, Deserialize)]
664pub struct PreviousEventBlocksMap<'a> {
665 inner: Cow<'a, BTreeMap<StreamId, (CryptoHash, BlockHeight)>>,
666}
667
668impl<'de> BcsHashable<'de> for PreviousEventBlocksMap<'de> {}
669
670#[derive(Serialize, Deserialize)]
671#[serde(rename = "BlockHeader")]
672struct SerializedHeader {
673 chain_id: ChainId,
674 epoch: Epoch,
675 height: BlockHeight,
676 timestamp: Timestamp,
677 state_hash: CryptoHash,
678 previous_block_hash: Option<CryptoHash>,
679 authenticated_owner: Option<AccountOwner>,
680}
681
682mod hashing {
683 use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
684
685 pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
686 let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
687 CryptoHash::new(&v)
688 }
689
690 pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
691 let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
692 CryptoHash::new(&v)
693 }
694}