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