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, EventId, StreamId},
18};
19use linera_execution::{BlobOrigin, 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, Allocative)]
33pub struct ValidatedBlock(Hashed<Block>);
34
35impl Serialize for ValidatedBlock {
36 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
37 self.0.serialize(serializer)
38 }
39}
40
41impl<'de> Deserialize<'de> for ValidatedBlock {
42 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43 Ok(Self::new(Block::deserialize(deserializer)?))
44 }
45}
46
47impl ValidatedBlock {
48 pub fn new(block: Block) -> Self {
50 let hash = block.hash();
51 Self(Hashed::with_hash(block, hash))
52 }
53
54 pub fn from_hashed(block: Hashed<Block>) -> Self {
56 Self(block)
57 }
58
59 pub fn inner(&self) -> &Hashed<Block> {
61 &self.0
62 }
63
64 pub fn block(&self) -> &Block {
66 self.0.inner()
67 }
68
69 pub fn into_inner(self) -> Block {
71 self.0.into_inner()
72 }
73
74 pub fn to_log_str(&self) -> &'static str {
76 "validated_block"
77 }
78
79 pub fn chain_id(&self) -> ChainId {
81 self.0.inner().header.chain_id
82 }
83
84 pub fn height(&self) -> BlockHeight {
86 self.0.inner().header.height
87 }
88
89 pub fn epoch(&self) -> Epoch {
91 self.0.inner().header.epoch
92 }
93}
94
95#[derive(Debug, PartialEq, Eq, Clone, Allocative)]
97pub struct ConfirmedBlock(Hashed<Block>);
98
99impl Serialize for ConfirmedBlock {
100 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
101 self.0.serialize(serializer)
102 }
103}
104
105impl<'de> Deserialize<'de> for ConfirmedBlock {
106 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
107 Ok(Self::new(Block::deserialize(deserializer)?))
108 }
109}
110
111#[async_graphql::Object(cache_control(no_cache))]
112impl ConfirmedBlock {
113 #[graphql(derived(name = "block"))]
114 async fn _block(&self) -> Block {
115 self.0.inner().clone()
116 }
117
118 async fn status(&self) -> String {
119 "confirmed".to_string()
120 }
121
122 async fn hash(&self) -> CryptoHash {
123 self.0.hash()
124 }
125}
126
127impl ConfirmedBlock {
128 pub fn new(block: Block) -> Self {
130 let hash = block.hash();
131 Self(Hashed::with_hash(block, hash))
132 }
133
134 pub fn from_hashed(block: Hashed<Block>) -> Self {
136 Self(block)
137 }
138
139 pub fn inner(&self) -> &Hashed<Block> {
141 &self.0
142 }
143
144 pub fn into_inner(self) -> Hashed<Block> {
146 self.0
147 }
148
149 pub fn block(&self) -> &Block {
151 self.0.inner()
152 }
153
154 pub fn into_block(self) -> Block {
156 self.0.into_inner()
157 }
158
159 pub fn chain_id(&self) -> ChainId {
161 self.0.inner().header.chain_id
162 }
163
164 pub fn height(&self) -> BlockHeight {
166 self.0.inner().header.height
167 }
168
169 pub fn timestamp(&self) -> Timestamp {
171 self.0.inner().header.timestamp
172 }
173
174 pub fn to_log_str(&self) -> &'static str {
176 "confirmed_block"
177 }
178
179 pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
181 self.block().matches_proposed_block(block)
182 }
183
184 pub fn to_blob_state(&self, is_stored_block: bool) -> BlobState {
186 BlobState {
187 origin: BlobOrigin::Published {
188 chain_id: self.chain_id(),
189 block_height: self.height(),
190 },
191 last_used_by: is_stored_block.then_some(self.0.hash()),
192 epoch: is_stored_block.then_some(self.epoch()),
193 }
194 }
195}
196
197impl From<Hashed<Block>> for ConfirmedBlock {
198 fn from(block: Hashed<Block>) -> Self {
199 Self::from_hashed(block)
200 }
201}
202
203impl From<Hashed<Block>> for ValidatedBlock {
204 fn from(block: Hashed<Block>) -> Self {
205 Self::from_hashed(block)
206 }
207}
208
209#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
212#[serde(transparent)]
213pub struct Timeout(Hashed<TimeoutInner>);
214
215#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
216#[serde(rename = "Timeout")]
217pub(crate) struct TimeoutInner {
218 chain_id: ChainId,
219 height: BlockHeight,
220 epoch: Epoch,
221}
222
223impl Timeout {
224 pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self {
226 let inner = TimeoutInner {
227 chain_id,
228 height,
229 epoch,
230 };
231 Self(Hashed::new(inner))
232 }
233
234 pub fn to_log_str(&self) -> &'static str {
236 "timeout"
237 }
238
239 pub fn chain_id(&self) -> ChainId {
241 self.0.inner().chain_id
242 }
243
244 pub fn height(&self) -> BlockHeight {
246 self.0.inner().height
247 }
248
249 pub fn epoch(&self) -> Epoch {
251 self.0.inner().epoch
252 }
253
254 pub(crate) fn inner(&self) -> &Hashed<TimeoutInner> {
255 &self.0
256 }
257}
258
259impl BcsHashable<'_> for Timeout {}
260impl BcsHashable<'_> for TimeoutInner {}
261
262#[derive(Clone, Copy, Debug, Error)]
264pub enum ConversionError {
265 #[error("Expected a `ConfirmedBlockCertificate` value")]
267 ConfirmedBlock,
268
269 #[error("Expected a `ValidatedBlockCertificate` value")]
271 ValidatedBlock,
272
273 #[error("Expected a `TimeoutCertificate` value")]
275 Timeout,
276}
277
278#[derive(Debug, PartialEq, Eq, Hash, Clone, SimpleObject, Allocative)]
285pub struct Block {
286 pub header: BlockHeader,
288 pub body: BlockBody,
290}
291
292impl Serialize for Block {
293 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
294 let mut state = serializer.serialize_struct("Block", 2)?;
295
296 let header = SerializedHeader {
297 chain_id: self.header.chain_id,
298 epoch: self.header.epoch,
299 height: self.header.height,
300 timestamp: self.header.timestamp,
301 state_hash: self.header.state_hash,
302 previous_block_hash: self.header.previous_block_hash,
303 authenticated_owner: self.header.authenticated_owner,
304 };
305 state.serialize_field("header", &header)?;
306 state.serialize_field("body", &self.body)?;
307 state.end()
308 }
309}
310
311impl<'de> Deserialize<'de> for Block {
312 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
313 #[derive(Deserialize)]
314 #[serde(rename = "Block")]
315 struct Inner {
316 header: SerializedHeader,
317 body: BlockBody,
318 }
319 let inner = Inner::deserialize(deserializer)?;
320
321 let transactions_hash = hashing::hash_vec(&inner.body.transactions);
322 let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
323 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
324 inner: Cow::Borrowed(&inner.body.previous_message_blocks),
325 });
326 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
327 inner: Cow::Borrowed(&inner.body.previous_event_blocks),
328 });
329 let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
330 let events_hash = hashing::hash_vec_vec(&inner.body.events);
331 let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
332 let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
333
334 let header = BlockHeader {
335 chain_id: inner.header.chain_id,
336 epoch: inner.header.epoch,
337 height: inner.header.height,
338 timestamp: inner.header.timestamp,
339 state_hash: inner.header.state_hash,
340 previous_block_hash: inner.header.previous_block_hash,
341 authenticated_owner: inner.header.authenticated_owner,
342 transactions_hash,
343 messages_hash,
344 previous_message_blocks_hash,
345 previous_event_blocks_hash,
346 oracle_responses_hash,
347 events_hash,
348 blobs_hash,
349 operation_results_hash,
350 };
351
352 Ok(Self {
353 header,
354 body: inner.body,
355 })
356 }
357}
358
359#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
363pub struct BlockHeader {
364 pub chain_id: ChainId,
366 pub epoch: Epoch,
368 pub height: BlockHeight,
370 pub timestamp: Timestamp,
372 pub state_hash: CryptoHash,
374 pub previous_block_hash: Option<CryptoHash>,
376 pub authenticated_owner: Option<AccountOwner>,
381
382 pub transactions_hash: CryptoHash,
385
386 pub messages_hash: CryptoHash,
389 pub previous_message_blocks_hash: CryptoHash,
391 pub previous_event_blocks_hash: CryptoHash,
393 pub oracle_responses_hash: CryptoHash,
395 pub events_hash: CryptoHash,
397 pub blobs_hash: CryptoHash,
399 pub operation_results_hash: CryptoHash,
401}
402
403#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
405#[graphql(complex)]
406pub struct BlockBody {
407 #[graphql(skip)]
410 pub transactions: Vec<Transaction>,
411 pub messages: Vec<Vec<OutgoingMessage>>,
413 pub previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
415 pub previous_event_blocks: BTreeMap<StreamId, (CryptoHash, BlockHeight)>,
417 pub oracle_responses: Vec<Vec<OracleResponse>>,
419 pub events: Vec<Vec<Event>>,
421 pub blobs: Vec<Vec<Blob>>,
423 pub operation_results: Vec<OperationResult>,
425}
426
427impl BlockBody {
428 pub fn operations(&self) -> impl Iterator<Item = &Operation> {
430 self.transactions.iter().filter_map(|tx| match tx {
431 Transaction::ExecuteOperation(operation) => Some(operation),
432 Transaction::ReceiveMessages(_) => None,
433 })
434 }
435
436 pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
438 self.transactions.iter().filter_map(|tx| match tx {
439 Transaction::ReceiveMessages(bundle) => Some(bundle),
440 Transaction::ExecuteOperation(_) => None,
441 })
442 }
443
444 pub fn starts_with_checkpoint(&self) -> bool {
447 self.transactions
448 .first()
449 .is_some_and(Transaction::is_checkpoint)
450 }
451}
452
453#[async_graphql::ComplexObject]
454impl BlockBody {
455 async fn transaction_metadata(&self) -> Vec<crate::data_types::TransactionMetadata> {
457 self.transactions
458 .iter()
459 .map(crate::data_types::TransactionMetadata::from_transaction)
460 .collect()
461 }
462}
463
464impl Block {
465 pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
467 let transactions_hash = hashing::hash_vec(&block.transactions);
468 let messages_hash = hashing::hash_vec_vec(&outcome.messages);
469 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
470 inner: Cow::Borrowed(&outcome.previous_message_blocks),
471 });
472 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
473 inner: Cow::Borrowed(&outcome.previous_event_blocks),
474 });
475 let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
476 let events_hash = hashing::hash_vec_vec(&outcome.events);
477 let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
478 let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
479
480 let header = BlockHeader {
481 chain_id: block.chain_id,
482 epoch: block.epoch,
483 height: block.height,
484 timestamp: block.timestamp,
485 state_hash: outcome.state_hash,
486 previous_block_hash: block.previous_block_hash,
487 authenticated_owner: block.authenticated_owner,
488 transactions_hash,
489 messages_hash,
490 previous_message_blocks_hash,
491 previous_event_blocks_hash,
492 oracle_responses_hash,
493 events_hash,
494 blobs_hash,
495 operation_results_hash,
496 };
497
498 let body = BlockBody {
499 transactions: block.transactions,
500 messages: outcome.messages,
501 previous_message_blocks: outcome.previous_message_blocks,
502 previous_event_blocks: outcome.previous_event_blocks,
503 oracle_responses: outcome.oracle_responses,
504 events: outcome.events,
505 blobs: outcome.blobs,
506 operation_results: outcome.operation_results,
507 };
508
509 Self { header, body }
510 }
511
512 pub fn hash(&self) -> CryptoHash {
514 CryptoHash::new(&self.header)
515 }
516
517 pub fn message_bundles_for(
522 &self,
523 recipient: ChainId,
524 certificate_hash: CryptoHash,
525 ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
526 let block_height = self.header.height;
527 let block_timestamp = self.header.timestamp;
528 let block_epoch = self.header.epoch;
529
530 (0u32..)
531 .zip(self.messages())
532 .filter_map(move |(transaction_index, txn_messages)| {
533 let messages = txn_messages
534 .iter()
535 .filter(|message| message.destination == recipient)
536 .map(|message| message.clone().into_posted())
537 .collect::<Vec<_>>();
538 (!messages.is_empty()).then(|| {
539 let bundle = MessageBundle {
540 height: block_height,
541 timestamp: block_timestamp,
542 certificate_hash,
543 transaction_index,
544 messages,
545 };
546 (block_epoch, bundle)
547 })
548 })
549 }
550
551 pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
554 let mut blob_ids = self.oracle_blob_ids();
555 blob_ids.extend(self.published_blob_ids());
556 blob_ids.extend(self.created_blob_ids());
557 if self.header.height == BlockHeight(0) {
558 blob_ids.insert(BlobId::new(
560 self.header.chain_id.0,
561 BlobType::ChainDescription,
562 ));
563 }
564 blob_ids
565 }
566
567 pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
569 self.oracle_blob_ids().contains(blob_id)
570 || self.published_blob_ids().contains(blob_id)
571 || self.created_blob_ids().contains(blob_id)
572 || (self.header.height == BlockHeight(0)
573 && (blob_id.blob_type == BlobType::ChainDescription
574 && blob_id.hash == self.header.chain_id.0))
575 }
576
577 pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
579 self.body
580 .operations()
581 .flat_map(Operation::published_blob_ids)
582 .collect()
583 }
584
585 pub fn starts_with_checkpoint(&self) -> bool {
588 self.body.starts_with_checkpoint()
589 }
590
591 pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
593 self.body
594 .blobs
595 .iter()
596 .flatten()
597 .map(|blob| blob.id())
598 .collect()
599 }
600
601 pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
603 self.body
604 .blobs
605 .iter()
606 .flatten()
607 .map(|blob| (blob.id(), blob.clone()))
608 .collect()
609 }
610
611 pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
613 let mut required_blob_ids = BTreeSet::new();
614 for responses in &self.body.oracle_responses {
615 for response in responses {
616 match response {
617 OracleResponse::Blob(blob_id) => {
618 required_blob_ids.insert(*blob_id);
619 }
620 OracleResponse::Checkpoint { used_blobs, .. } => {
621 required_blob_ids.extend(used_blobs.iter().copied());
622 }
623 _ => {}
624 }
625 }
626 }
627
628 required_blob_ids
629 }
630
631 pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
633 &self.body.messages
634 }
635
636 pub fn recipients(&self) -> BTreeSet<ChainId> {
638 self.body
639 .messages
640 .iter()
641 .flat_map(|messages| messages.iter().map(|message| message.destination))
642 .collect()
643 }
644
645 pub fn has_oracle_responses(&self) -> bool {
647 self.body
648 .oracle_responses
649 .iter()
650 .any(|responses| !responses.is_empty())
651 }
652
653 pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
655 let ProposedBlock {
656 chain_id,
657 epoch,
658 transactions,
659 height,
660 timestamp,
661 authenticated_owner,
662 previous_block_hash,
663 } = block;
664 *chain_id == self.header.chain_id
665 && *epoch == self.header.epoch
666 && *transactions == self.body.transactions
667 && *height == self.header.height
668 && *timestamp == self.header.timestamp
669 && *authenticated_owner == self.header.authenticated_owner
670 && *previous_block_hash == self.header.previous_block_hash
671 }
672
673 pub fn outcome_matches(&self, expected: &BlockExecutionOutcome) -> bool {
675 let BlockExecutionOutcome {
676 state_hash,
677 messages,
678 previous_message_blocks,
679 previous_event_blocks,
680 oracle_responses,
681 events,
682 blobs,
683 operation_results,
684 } = expected;
685 self.header.state_hash == *state_hash
686 && self.body.messages == *messages
687 && self.body.previous_message_blocks == *previous_message_blocks
688 && self.body.previous_event_blocks == *previous_event_blocks
689 && self.body.oracle_responses == *oracle_responses
690 && self.body.events == *events
691 && self.body.blobs == *blobs
692 && self.body.operation_results == *operation_results
693 }
694
695 pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
697 let proposed_block = ProposedBlock {
698 chain_id: self.header.chain_id,
699 epoch: self.header.epoch,
700 transactions: self.body.transactions,
701 height: self.header.height,
702 timestamp: self.header.timestamp,
703 authenticated_owner: self.header.authenticated_owner,
704 previous_block_hash: self.header.previous_block_hash,
705 };
706 let outcome = BlockExecutionOutcome {
707 state_hash: self.header.state_hash,
708 messages: self.body.messages,
709 previous_message_blocks: self.body.previous_message_blocks,
710 previous_event_blocks: self.body.previous_event_blocks,
711 oracle_responses: self.body.oracle_responses,
712 events: self.body.events,
713 blobs: self.body.blobs,
714 operation_results: self.body.operation_results,
715 };
716 (proposed_block, outcome)
717 }
718
719 pub fn event_ids(&self) -> impl Iterator<Item = EventId> + '_ {
721 let to_id = |event: &Event| event.id(self.header.chain_id);
722 self.body.events.iter().flatten().map(to_id)
723 }
724}
725
726#[derive(derive_more::From)]
730#[allow(missing_docs)]
731pub enum BlockBodyField {
732 Transactions(Vec<Transaction>),
733 Messages(Vec<Vec<OutgoingMessage>>),
734 PreviousMessageBlocks(BTreeMap<ChainId, (CryptoHash, BlockHeight)>),
735 PreviousEventBlocks(BTreeMap<StreamId, (CryptoHash, BlockHeight)>),
736 OracleResponses(Vec<Vec<OracleResponse>>),
737 Events(Vec<Vec<Event>>),
738 Blobs(Vec<Vec<Blob>>),
739 OperationResults(Vec<OperationResult>),
740}
741
742impl BlockHeader {
743 pub fn verifies(&self, field: impl Into<BlockBodyField>) -> bool {
745 match field.into() {
746 BlockBodyField::Transactions(v) => hashing::hash_vec(v) == self.transactions_hash,
747 BlockBodyField::Messages(v) => hashing::hash_vec_vec(v) == self.messages_hash,
748 BlockBodyField::PreviousMessageBlocks(m) => {
749 CryptoHash::new(&PreviousMessageBlocksMap {
750 inner: Cow::Owned(m),
751 }) == self.previous_message_blocks_hash
752 }
753 BlockBodyField::PreviousEventBlocks(m) => {
754 CryptoHash::new(&PreviousEventBlocksMap {
755 inner: Cow::Owned(m),
756 }) == self.previous_event_blocks_hash
757 }
758 BlockBodyField::OracleResponses(v) => {
759 hashing::hash_vec_vec(v) == self.oracle_responses_hash
760 }
761 BlockBodyField::Events(v) => hashing::hash_vec_vec(v) == self.events_hash,
762 BlockBodyField::Blobs(v) => hashing::hash_vec_vec(v) == self.blobs_hash,
763 BlockBodyField::OperationResults(v) => {
764 hashing::hash_vec(v) == self.operation_results_hash
765 }
766 }
767 }
768}
769
770impl BcsHashable<'_> for BlockHeader {}
771
772#[derive(Serialize, Deserialize)]
775pub struct PreviousMessageBlocksMap<'a> {
776 inner: Cow<'a, BTreeMap<ChainId, (CryptoHash, BlockHeight)>>,
777}
778
779impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
780
781#[derive(Serialize, Deserialize)]
784pub struct PreviousEventBlocksMap<'a> {
785 inner: Cow<'a, BTreeMap<StreamId, (CryptoHash, BlockHeight)>>,
786}
787
788impl<'de> BcsHashable<'de> for PreviousEventBlocksMap<'de> {}
789
790#[derive(Serialize, Deserialize)]
791#[serde(rename = "BlockHeader")]
792struct SerializedHeader {
793 chain_id: ChainId,
794 epoch: Epoch,
795 height: BlockHeight,
796 timestamp: Timestamp,
797 state_hash: CryptoHash,
798 previous_block_hash: Option<CryptoHash>,
799 authenticated_owner: Option<AccountOwner>,
800}
801
802mod hashing {
803 use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
804
805 pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
806 let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
807 CryptoHash::new(&v)
808 }
809
810 pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
811 let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
812 CryptoHash::new(&v)
813 }
814}