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,
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 to_log_str(&self) -> &'static str {
133 "confirmed_block"
134 }
135
136 pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
138 self.block().matches_proposed_block(block)
139 }
140
141 pub fn to_blob_state(&self, is_stored_block: bool) -> BlobState {
143 BlobState {
144 last_used_by: is_stored_block.then_some(self.0.hash()),
145 chain_id: self.chain_id(),
146 block_height: self.height(),
147 epoch: is_stored_block.then_some(self.epoch()),
148 }
149 }
150}
151
152#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
153#[serde(transparent)]
154pub struct Timeout(Hashed<TimeoutInner>);
155
156#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
157#[serde(rename = "Timeout")]
158pub struct TimeoutInner {
159 pub chain_id: ChainId,
160 pub height: BlockHeight,
161 pub epoch: Epoch,
162}
163
164impl Timeout {
165 pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self {
166 let inner = TimeoutInner {
167 chain_id,
168 height,
169 epoch,
170 };
171 Self(Hashed::new(inner))
172 }
173
174 pub fn to_log_str(&self) -> &'static str {
175 "timeout"
176 }
177
178 pub fn chain_id(&self) -> ChainId {
179 self.0.inner().chain_id
180 }
181
182 pub fn height(&self) -> BlockHeight {
183 self.0.inner().height
184 }
185
186 pub fn epoch(&self) -> Epoch {
187 self.0.inner().epoch
188 }
189
190 pub fn inner(&self) -> &Hashed<TimeoutInner> {
191 &self.0
192 }
193}
194
195impl BcsHashable<'_> for Timeout {}
196impl BcsHashable<'_> for TimeoutInner {}
197
198#[derive(Clone, Copy, Debug, Error)]
200pub enum ConversionError {
201 #[error("Expected a `ConfirmedBlockCertificate` value")]
203 ConfirmedBlock,
204
205 #[error("Expected a `ValidatedBlockCertificate` value")]
207 ValidatedBlock,
208
209 #[error("Expected a `TimeoutCertificate` value")]
211 Timeout,
212}
213
214#[derive(Debug, PartialEq, Eq, Hash, Clone, SimpleObject)]
221pub struct Block {
222 pub header: BlockHeader,
224 pub body: BlockBody,
226}
227
228impl Serialize for Block {
229 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
230 let mut state = serializer.serialize_struct("Block", 2)?;
231
232 let header = SerializedHeader {
233 chain_id: self.header.chain_id,
234 epoch: self.header.epoch,
235 height: self.header.height,
236 timestamp: self.header.timestamp,
237 state_hash: self.header.state_hash,
238 previous_block_hash: self.header.previous_block_hash,
239 authenticated_signer: self.header.authenticated_signer,
240 };
241 state.serialize_field("header", &header)?;
242 state.serialize_field("body", &self.body)?;
243 state.end()
244 }
245}
246
247impl<'de> Deserialize<'de> for Block {
248 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
249 #[derive(Deserialize)]
250 #[serde(rename = "Block")]
251 struct Inner {
252 header: SerializedHeader,
253 body: BlockBody,
254 }
255 let inner = Inner::deserialize(deserializer)?;
256
257 let bundles_hash = hashing::hash_vec(&inner.body.incoming_bundles);
258 let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
259 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
260 inner: Cow::Borrowed(&inner.body.previous_message_blocks),
261 });
262 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
263 inner: Cow::Borrowed(&inner.body.previous_event_blocks),
264 });
265 let operations_hash = hashing::hash_vec(&inner.body.operations);
266 let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
267 let events_hash = hashing::hash_vec_vec(&inner.body.events);
268 let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
269 let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
270
271 let header = BlockHeader {
272 chain_id: inner.header.chain_id,
273 epoch: inner.header.epoch,
274 height: inner.header.height,
275 timestamp: inner.header.timestamp,
276 state_hash: inner.header.state_hash,
277 previous_block_hash: inner.header.previous_block_hash,
278 authenticated_signer: inner.header.authenticated_signer,
279 bundles_hash,
280 operations_hash,
281 messages_hash,
282 previous_message_blocks_hash,
283 previous_event_blocks_hash,
284 oracle_responses_hash,
285 events_hash,
286 blobs_hash,
287 operation_results_hash,
288 };
289
290 Ok(Self {
291 header,
292 body: inner.body,
293 })
294 }
295}
296
297#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
301pub struct BlockHeader {
302 pub chain_id: ChainId,
304 pub epoch: Epoch,
306 pub height: BlockHeight,
308 pub timestamp: Timestamp,
310 pub state_hash: CryptoHash,
312 pub previous_block_hash: Option<CryptoHash>,
314 pub authenticated_signer: Option<AccountOwner>,
319
320 pub bundles_hash: CryptoHash,
323 pub operations_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)]
345pub struct BlockBody {
346 pub incoming_bundles: Vec<IncomingBundle>,
349 pub operations: Vec<Operation>,
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 Block {
368 pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
369 let bundles_hash = hashing::hash_vec(&block.incoming_bundles);
370 let messages_hash = hashing::hash_vec_vec(&outcome.messages);
371 let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
372 inner: Cow::Borrowed(&outcome.previous_message_blocks),
373 });
374 let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
375 inner: Cow::Borrowed(&outcome.previous_event_blocks),
376 });
377 let operations_hash = hashing::hash_vec(&block.operations);
378 let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
379 let events_hash = hashing::hash_vec_vec(&outcome.events);
380 let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
381 let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
382
383 let header = BlockHeader {
384 chain_id: block.chain_id,
385 epoch: block.epoch,
386 height: block.height,
387 timestamp: block.timestamp,
388 state_hash: outcome.state_hash,
389 previous_block_hash: block.previous_block_hash,
390 authenticated_signer: block.authenticated_signer,
391 bundles_hash,
392 operations_hash,
393 messages_hash,
394 previous_message_blocks_hash,
395 previous_event_blocks_hash,
396 oracle_responses_hash,
397 events_hash,
398 blobs_hash,
399 operation_results_hash,
400 };
401
402 let body = BlockBody {
403 incoming_bundles: block.incoming_bundles,
404 operations: block.operations,
405 messages: outcome.messages,
406 previous_message_blocks: outcome.previous_message_blocks,
407 previous_event_blocks: outcome.previous_event_blocks,
408 oracle_responses: outcome.oracle_responses,
409 events: outcome.events,
410 blobs: outcome.blobs,
411 operation_results: outcome.operation_results,
412 };
413
414 Self { header, body }
415 }
416
417 pub fn message_bundles_for(
422 &self,
423 recipient: ChainId,
424 certificate_hash: CryptoHash,
425 ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
426 let mut index = 0u32;
427 let block_height = self.header.height;
428 let block_timestamp = self.header.timestamp;
429 let block_epoch = self.header.epoch;
430
431 (0u32..)
432 .zip(self.messages())
433 .filter_map(move |(transaction_index, txn_messages)| {
434 let messages = (index..)
435 .zip(txn_messages)
436 .filter(|(_, message)| message.destination == recipient)
437 .map(|(idx, message)| message.clone().into_posted(idx))
438 .collect::<Vec<_>>();
439 index += txn_messages.len() as u32;
440 (!messages.is_empty()).then(|| {
441 let bundle = MessageBundle {
442 height: block_height,
443 timestamp: block_timestamp,
444 certificate_hash,
445 transaction_index,
446 messages,
447 };
448 (block_epoch, bundle)
449 })
450 })
451 }
452
453 pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
456 let mut blob_ids = self.oracle_blob_ids();
457 blob_ids.extend(self.published_blob_ids());
458 blob_ids.extend(self.created_blob_ids());
459 if self.header.height == BlockHeight(0) {
460 blob_ids.insert(BlobId::new(
462 self.header.chain_id.0,
463 BlobType::ChainDescription,
464 ));
465 }
466 blob_ids
467 }
468
469 pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
471 self.oracle_blob_ids().contains(blob_id)
472 || self.published_blob_ids().contains(blob_id)
473 || self.created_blob_ids().contains(blob_id)
474 || (self.header.height == BlockHeight(0)
475 && (blob_id.blob_type == BlobType::ChainDescription
476 && blob_id.hash == self.header.chain_id.0))
477 }
478
479 pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
481 self.body
482 .operations
483 .iter()
484 .flat_map(Operation::published_blob_ids)
485 .collect()
486 }
487
488 pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
490 self.body
491 .blobs
492 .iter()
493 .flatten()
494 .map(|blob| blob.id())
495 .collect()
496 }
497
498 pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
500 self.body
501 .blobs
502 .iter()
503 .flatten()
504 .map(|blob| (blob.id(), blob.clone()))
505 .collect()
506 }
507
508 pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
510 let mut required_blob_ids = BTreeSet::new();
511 for responses in &self.body.oracle_responses {
512 for response in responses {
513 if let OracleResponse::Blob(blob_id) = response {
514 required_blob_ids.insert(*blob_id);
515 }
516 }
517 }
518
519 required_blob_ids
520 }
521
522 pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
524 &self.body.messages
525 }
526
527 pub fn recipients(&self) -> BTreeSet<ChainId> {
529 self.body
530 .messages
531 .iter()
532 .flat_map(|messages| messages.iter().map(|message| message.destination))
533 .collect()
534 }
535
536 pub fn has_oracle_responses(&self) -> bool {
538 self.body
539 .oracle_responses
540 .iter()
541 .any(|responses| !responses.is_empty())
542 }
543
544 pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
546 let ProposedBlock {
547 chain_id,
548 epoch,
549 incoming_bundles,
550 operations,
551 height,
552 timestamp,
553 authenticated_signer,
554 previous_block_hash,
555 } = block;
556 *chain_id == self.header.chain_id
557 && *epoch == self.header.epoch
558 && *incoming_bundles == self.body.incoming_bundles
559 && *operations == self.body.operations
560 && *height == self.header.height
561 && *timestamp == self.header.timestamp
562 && *authenticated_signer == self.header.authenticated_signer
563 && *previous_block_hash == self.header.previous_block_hash
564 }
565
566 pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
567 let proposed_block = ProposedBlock {
568 chain_id: self.header.chain_id,
569 epoch: self.header.epoch,
570 incoming_bundles: self.body.incoming_bundles,
571 operations: self.body.operations,
572 height: self.header.height,
573 timestamp: self.header.timestamp,
574 authenticated_signer: self.header.authenticated_signer,
575 previous_block_hash: self.header.previous_block_hash,
576 };
577 let outcome = BlockExecutionOutcome {
578 state_hash: self.header.state_hash,
579 messages: self.body.messages,
580 previous_message_blocks: self.body.previous_message_blocks,
581 previous_event_blocks: self.body.previous_event_blocks,
582 oracle_responses: self.body.oracle_responses,
583 events: self.body.events,
584 blobs: self.body.blobs,
585 operation_results: self.body.operation_results,
586 };
587 (proposed_block, outcome)
588 }
589
590 pub fn iter_created_blobs(&self) -> impl Iterator<Item = (BlobId, Blob)> + '_ {
591 self.body
592 .blobs
593 .iter()
594 .flatten()
595 .map(|blob| (blob.id(), blob.clone()))
596 }
597}
598
599impl BcsHashable<'_> for Block {}
600
601#[derive(Serialize, Deserialize)]
602pub struct PreviousMessageBlocksMap<'a> {
603 inner: Cow<'a, BTreeMap<ChainId, (CryptoHash, BlockHeight)>>,
604}
605
606impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
607
608#[derive(Serialize, Deserialize)]
609pub struct PreviousEventBlocksMap<'a> {
610 inner: Cow<'a, BTreeMap<StreamId, (CryptoHash, BlockHeight)>>,
611}
612
613impl<'de> BcsHashable<'de> for PreviousEventBlocksMap<'de> {}
614
615#[derive(Serialize, Deserialize)]
616#[serde(rename = "BlockHeader")]
617struct SerializedHeader {
618 chain_id: ChainId,
619 epoch: Epoch,
620 height: BlockHeight,
621 timestamp: Timestamp,
622 state_hash: CryptoHash,
623 previous_block_hash: Option<CryptoHash>,
624 authenticated_signer: Option<AccountOwner>,
625}
626
627mod hashing {
628 use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
629
630 pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
631 let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
632 CryptoHash::new(&v)
633 }
634
635 pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
636 let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
637 CryptoHash::new(&v)
638 }
639}