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, 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 origin: BlobOrigin::Published {
150 chain_id: self.chain_id(),
151 block_height: self.height(),
152 },
153 last_used_by: is_stored_block.then_some(self.0.hash()),
154 epoch: is_stored_block.then_some(self.epoch()),
155 }
156 }
157}
158
159impl From<Hashed<Block>> for ConfirmedBlock {
160 fn from(block: Hashed<Block>) -> Self {
161 Self::from_hashed(block)
162 }
163}
164
165impl From<Hashed<Block>> for ValidatedBlock {
166 fn from(block: Hashed<Block>) -> Self {
167 Self::from_hashed(block)
168 }
169}
170
171#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
172#[serde(transparent)]
173pub struct Timeout(Hashed<TimeoutInner>);
174
175#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
176#[serde(rename = "Timeout")]
177pub(crate) struct TimeoutInner {
178 chain_id: ChainId,
179 height: BlockHeight,
180 epoch: Epoch,
181}
182
183impl Timeout {
184 pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self {
185 let inner = TimeoutInner {
186 chain_id,
187 height,
188 epoch,
189 };
190 Self(Hashed::new(inner))
191 }
192
193 pub fn to_log_str(&self) -> &'static str {
194 "timeout"
195 }
196
197 pub fn chain_id(&self) -> ChainId {
198 self.0.inner().chain_id
199 }
200
201 pub fn height(&self) -> BlockHeight {
202 self.0.inner().height
203 }
204
205 pub fn epoch(&self) -> Epoch {
206 self.0.inner().epoch
207 }
208
209 pub(crate) fn inner(&self) -> &Hashed<TimeoutInner> {
210 &self.0
211 }
212}
213
214impl BcsHashable<'_> for Timeout {}
215impl BcsHashable<'_> for TimeoutInner {}
216
217#[derive(Clone, Copy, Debug, Error)]
219pub enum ConversionError {
220 #[error("Expected a `ConfirmedBlockCertificate` value")]
222 ConfirmedBlock,
223
224 #[error("Expected a `ValidatedBlockCertificate` value")]
226 ValidatedBlock,
227
228 #[error("Expected a `TimeoutCertificate` value")]
230 Timeout,
231}
232
233#[derive(Debug, PartialEq, Eq, Hash, Clone, SimpleObject, Allocative)]
240pub struct Block {
241 pub header: BlockHeader,
243 pub body: BlockBody,
245}
246
247impl Serialize for Block {
248 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
249 let mut state = serializer.serialize_struct("Block", 2)?;
250
251 let header = SerializedHeader {
252 chain_id: self.header.chain_id,
253 epoch: self.header.epoch,
254 height: self.header.height,
255 timestamp: self.header.timestamp,
256 state_hash: self.header.state_hash,
257 previous_block_hash: self.header.previous_block_hash,
258 authenticated_owner: self.header.authenticated_owner,
259 };
260 state.serialize_field("header", &header)?;
261 state.serialize_field("body", &self.body)?;
262 state.end()
263 }
264}
265
266impl<'de> Deserialize<'de> for Block {
267 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
268 #[derive(Deserialize)]
269 #[serde(rename = "Block")]
270 struct Inner {
271 header: SerializedHeader,
272 body: BlockBody,
273 }
274 let inner = Inner::deserialize(deserializer)?;
275
276 let transactions_hash = hashing::hash_vec(&inner.body.transactions);
277 let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
278 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
279 inner: Cow::Borrowed(&inner.body.previous_message_blocks),
280 });
281 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
282 inner: Cow::Borrowed(&inner.body.previous_event_blocks),
283 });
284 let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
285 let events_hash = hashing::hash_vec_vec(&inner.body.events);
286 let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
287 let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
288
289 let header = BlockHeader {
290 chain_id: inner.header.chain_id,
291 epoch: inner.header.epoch,
292 height: inner.header.height,
293 timestamp: inner.header.timestamp,
294 state_hash: inner.header.state_hash,
295 previous_block_hash: inner.header.previous_block_hash,
296 authenticated_owner: inner.header.authenticated_owner,
297 transactions_hash,
298 messages_hash,
299 previous_message_blocks_hash,
300 previous_event_blocks_hash,
301 oracle_responses_hash,
302 events_hash,
303 blobs_hash,
304 operation_results_hash,
305 };
306
307 Ok(Self {
308 header,
309 body: inner.body,
310 })
311 }
312}
313
314#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
318pub struct BlockHeader {
319 pub chain_id: ChainId,
321 pub epoch: Epoch,
323 pub height: BlockHeight,
325 pub timestamp: Timestamp,
327 pub state_hash: CryptoHash,
329 pub previous_block_hash: Option<CryptoHash>,
331 pub authenticated_owner: Option<AccountOwner>,
336
337 pub transactions_hash: CryptoHash,
340
341 pub messages_hash: CryptoHash,
344 pub previous_message_blocks_hash: CryptoHash,
346 pub previous_event_blocks_hash: CryptoHash,
348 pub oracle_responses_hash: CryptoHash,
350 pub events_hash: CryptoHash,
352 pub blobs_hash: CryptoHash,
354 pub operation_results_hash: CryptoHash,
356}
357
358#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
360#[graphql(complex)]
361pub struct BlockBody {
362 #[graphql(skip)]
365 pub transactions: Vec<Transaction>,
366 pub messages: Vec<Vec<OutgoingMessage>>,
368 pub previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
370 pub previous_event_blocks: BTreeMap<StreamId, (CryptoHash, BlockHeight)>,
372 pub oracle_responses: Vec<Vec<OracleResponse>>,
374 pub events: Vec<Vec<Event>>,
376 pub blobs: Vec<Vec<Blob>>,
378 pub operation_results: Vec<OperationResult>,
380}
381
382impl BlockBody {
383 pub fn operations(&self) -> impl Iterator<Item = &Operation> {
385 self.transactions.iter().filter_map(|tx| match tx {
386 Transaction::ExecuteOperation(operation) => Some(operation),
387 Transaction::ReceiveMessages(_) => None,
388 })
389 }
390
391 pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
393 self.transactions.iter().filter_map(|tx| match tx {
394 Transaction::ReceiveMessages(bundle) => Some(bundle),
395 Transaction::ExecuteOperation(_) => None,
396 })
397 }
398
399 pub fn starts_with_checkpoint(&self) -> bool {
402 self.transactions
403 .first()
404 .is_some_and(Transaction::is_checkpoint)
405 }
406}
407
408#[async_graphql::ComplexObject]
409impl BlockBody {
410 async fn transaction_metadata(&self) -> Vec<crate::data_types::TransactionMetadata> {
412 self.transactions
413 .iter()
414 .map(crate::data_types::TransactionMetadata::from_transaction)
415 .collect()
416 }
417}
418
419impl Block {
420 pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
421 let transactions_hash = hashing::hash_vec(&block.transactions);
422 let messages_hash = hashing::hash_vec_vec(&outcome.messages);
423 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
424 inner: Cow::Borrowed(&outcome.previous_message_blocks),
425 });
426 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
427 inner: Cow::Borrowed(&outcome.previous_event_blocks),
428 });
429 let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
430 let events_hash = hashing::hash_vec_vec(&outcome.events);
431 let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
432 let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
433
434 let header = BlockHeader {
435 chain_id: block.chain_id,
436 epoch: block.epoch,
437 height: block.height,
438 timestamp: block.timestamp,
439 state_hash: outcome.state_hash,
440 previous_block_hash: block.previous_block_hash,
441 authenticated_owner: block.authenticated_owner,
442 transactions_hash,
443 messages_hash,
444 previous_message_blocks_hash,
445 previous_event_blocks_hash,
446 oracle_responses_hash,
447 events_hash,
448 blobs_hash,
449 operation_results_hash,
450 };
451
452 let body = BlockBody {
453 transactions: block.transactions,
454 messages: outcome.messages,
455 previous_message_blocks: outcome.previous_message_blocks,
456 previous_event_blocks: outcome.previous_event_blocks,
457 oracle_responses: outcome.oracle_responses,
458 events: outcome.events,
459 blobs: outcome.blobs,
460 operation_results: outcome.operation_results,
461 };
462
463 Self { header, body }
464 }
465
466 pub fn message_bundles_for(
471 &self,
472 recipient: ChainId,
473 certificate_hash: CryptoHash,
474 ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
475 let block_height = self.header.height;
476 let block_timestamp = self.header.timestamp;
477 let block_epoch = self.header.epoch;
478
479 (0u32..)
480 .zip(self.messages())
481 .filter_map(move |(transaction_index, txn_messages)| {
482 let messages = txn_messages
483 .iter()
484 .filter(|message| message.destination == recipient)
485 .map(|message| message.clone().into_posted())
486 .collect::<Vec<_>>();
487 (!messages.is_empty()).then(|| {
488 let bundle = MessageBundle {
489 height: block_height,
490 timestamp: block_timestamp,
491 certificate_hash,
492 transaction_index,
493 messages,
494 };
495 (block_epoch, bundle)
496 })
497 })
498 }
499
500 pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
503 let mut blob_ids = self.oracle_blob_ids();
504 blob_ids.extend(self.published_blob_ids());
505 blob_ids.extend(self.created_blob_ids());
506 if self.header.height == BlockHeight(0) {
507 blob_ids.insert(BlobId::new(
509 self.header.chain_id.0,
510 BlobType::ChainDescription,
511 ));
512 }
513 blob_ids
514 }
515
516 pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
518 self.oracle_blob_ids().contains(blob_id)
519 || self.published_blob_ids().contains(blob_id)
520 || self.created_blob_ids().contains(blob_id)
521 || (self.header.height == BlockHeight(0)
522 && (blob_id.blob_type == BlobType::ChainDescription
523 && blob_id.hash == self.header.chain_id.0))
524 }
525
526 pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
528 self.body
529 .operations()
530 .flat_map(Operation::published_blob_ids)
531 .collect()
532 }
533
534 pub fn starts_with_checkpoint(&self) -> bool {
537 self.body.starts_with_checkpoint()
538 }
539
540 pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
542 self.body
543 .blobs
544 .iter()
545 .flatten()
546 .map(|blob| blob.id())
547 .collect()
548 }
549
550 pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
552 self.body
553 .blobs
554 .iter()
555 .flatten()
556 .map(|blob| (blob.id(), blob.clone()))
557 .collect()
558 }
559
560 pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
562 let mut required_blob_ids = BTreeSet::new();
563 for responses in &self.body.oracle_responses {
564 for response in responses {
565 match response {
566 OracleResponse::Blob(blob_id) => {
567 required_blob_ids.insert(*blob_id);
568 }
569 OracleResponse::Checkpoint { used_blobs, .. } => {
570 required_blob_ids.extend(used_blobs.iter().copied());
571 }
572 _ => {}
573 }
574 }
575 }
576
577 required_blob_ids
578 }
579
580 pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
582 &self.body.messages
583 }
584
585 pub fn recipients(&self) -> BTreeSet<ChainId> {
587 self.body
588 .messages
589 .iter()
590 .flat_map(|messages| messages.iter().map(|message| message.destination))
591 .collect()
592 }
593
594 pub fn has_oracle_responses(&self) -> bool {
596 self.body
597 .oracle_responses
598 .iter()
599 .any(|responses| !responses.is_empty())
600 }
601
602 pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
604 let ProposedBlock {
605 chain_id,
606 epoch,
607 transactions,
608 height,
609 timestamp,
610 authenticated_owner,
611 previous_block_hash,
612 } = block;
613 *chain_id == self.header.chain_id
614 && *epoch == self.header.epoch
615 && *transactions == self.body.transactions
616 && *height == self.header.height
617 && *timestamp == self.header.timestamp
618 && *authenticated_owner == self.header.authenticated_owner
619 && *previous_block_hash == self.header.previous_block_hash
620 }
621
622 pub fn outcome_matches(&self, expected: &BlockExecutionOutcome) -> bool {
624 let BlockExecutionOutcome {
625 state_hash,
626 messages,
627 previous_message_blocks,
628 previous_event_blocks,
629 oracle_responses,
630 events,
631 blobs,
632 operation_results,
633 } = expected;
634 self.header.state_hash == *state_hash
635 && self.body.messages == *messages
636 && self.body.previous_message_blocks == *previous_message_blocks
637 && self.body.previous_event_blocks == *previous_event_blocks
638 && self.body.oracle_responses == *oracle_responses
639 && self.body.events == *events
640 && self.body.blobs == *blobs
641 && self.body.operation_results == *operation_results
642 }
643
644 pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
645 let proposed_block = ProposedBlock {
646 chain_id: self.header.chain_id,
647 epoch: self.header.epoch,
648 transactions: self.body.transactions,
649 height: self.header.height,
650 timestamp: self.header.timestamp,
651 authenticated_owner: self.header.authenticated_owner,
652 previous_block_hash: self.header.previous_block_hash,
653 };
654 let outcome = BlockExecutionOutcome {
655 state_hash: self.header.state_hash,
656 messages: self.body.messages,
657 previous_message_blocks: self.body.previous_message_blocks,
658 previous_event_blocks: self.body.previous_event_blocks,
659 oracle_responses: self.body.oracle_responses,
660 events: self.body.events,
661 blobs: self.body.blobs,
662 operation_results: self.body.operation_results,
663 };
664 (proposed_block, outcome)
665 }
666
667 pub fn event_ids(&self) -> impl Iterator<Item = EventId> + '_ {
669 let to_id = |event: &Event| event.id(self.header.chain_id);
670 self.body.events.iter().flatten().map(to_id)
671 }
672}
673
674impl BcsHashable<'_> for Block {}
675
676#[derive(Serialize, Deserialize)]
677pub struct PreviousMessageBlocksMap<'a> {
678 inner: Cow<'a, BTreeMap<ChainId, (CryptoHash, BlockHeight)>>,
679}
680
681impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
682
683#[derive(Serialize, Deserialize)]
684pub struct PreviousEventBlocksMap<'a> {
685 inner: Cow<'a, BTreeMap<StreamId, (CryptoHash, BlockHeight)>>,
686}
687
688impl<'de> BcsHashable<'de> for PreviousEventBlocksMap<'de> {}
689
690#[derive(Serialize, Deserialize)]
691#[serde(rename = "BlockHeader")]
692struct SerializedHeader {
693 chain_id: ChainId,
694 epoch: Epoch,
695 height: BlockHeight,
696 timestamp: Timestamp,
697 state_hash: CryptoHash,
698 previous_block_hash: Option<CryptoHash>,
699 authenticated_owner: Option<AccountOwner>,
700}
701
702mod hashing {
703 use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
704
705 pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
706 let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
707 CryptoHash::new(&v)
708 }
709
710 pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
711 let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
712 CryptoHash::new(&v)
713 }
714}