Skip to main content

linera_service_graphql_client/
service.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use graphql_client::GraphQLQuery;
5use linera_base::{
6    crypto::CryptoHash,
7    data_types::{Amount, Blob, BlockHeight, ChainDescription, OracleResponse, Round, Timestamp},
8    identifiers::{Account, AccountOwner, BlobId, ChainId, GenericApplicationId, StreamName},
9};
10use thiserror::Error;
11
12pub type JSONObject = serde_json::Value;
13
14#[cfg(target_arch = "wasm32")]
15mod types {
16    use std::collections::BTreeSet;
17
18    use linera_base::identifiers::StreamId;
19    use serde::{Deserialize, Serialize};
20    use serde_json::Value;
21
22    use super::{BlockHeight, ChainId, CryptoHash, Round};
23
24    pub type ChainManager = Value;
25    pub type ChainOwnership = Value;
26    pub type Epoch = Value;
27    pub type MessageBundle = Value;
28    pub type MessageKind = Value;
29    pub type Message = Value;
30    pub type MessageAction = Value;
31    pub type Operation = Value;
32    pub type ApplicationDescription = Value;
33    pub type OperationResult = Value;
34
35    /// Mirrors `linera_core::worker::Notification`.
36    /// Duplicated because `linera-core` doesn't compile for wasm32.
37    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
38    pub struct Notification {
39        pub chain_id: ChainId,
40        pub reason: Reason,
41    }
42
43    /// Mirrors `linera_core::worker::Reason`.
44    /// Duplicated because `linera-core` doesn't compile for wasm32.
45    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
46    pub enum Reason {
47        NewBlock {
48            height: BlockHeight,
49            hash: CryptoHash,
50            event_streams: BTreeSet<StreamId>,
51        },
52        NewIncomingBundle {
53            origin: ChainId,
54            height: BlockHeight,
55        },
56        NewRound {
57            height: BlockHeight,
58            round: Round,
59        },
60        BlockExecuted {
61            height: BlockHeight,
62            hash: CryptoHash,
63        },
64        NewEvents {
65            height: BlockHeight,
66            hash: CryptoHash,
67            event_streams: BTreeSet<StreamId>,
68        },
69    }
70}
71
72#[cfg(not(target_arch = "wasm32"))]
73mod types {
74    pub use linera_base::{
75        data_types::{ApplicationDescription, Epoch},
76        ownership::ChainOwnership,
77    };
78    pub use linera_chain::{
79        data_types::{IncomingBundle, MessageAction, MessageBundle, OperationResult, Transaction},
80        manager::ChainManager,
81    };
82    pub use linera_core::worker::{Notification, Reason};
83    pub use linera_execution::{Message, MessageKind, Operation, SystemOperation};
84}
85
86pub use types::*;
87pub type ApplicationId = String;
88
89#[derive(GraphQLQuery)]
90#[graphql(
91    schema_path = "gql/service_schema.graphql",
92    query_path = "gql/service_requests.graphql",
93    response_derives = "Debug, Serialize, Clone"
94)]
95pub struct Chain;
96
97#[derive(GraphQLQuery)]
98#[graphql(
99    schema_path = "gql/service_schema.graphql",
100    query_path = "gql/service_requests.graphql",
101    response_derives = "Debug, Serialize, Clone"
102)]
103pub struct Chains;
104
105#[derive(GraphQLQuery)]
106#[graphql(
107    schema_path = "gql/service_schema.graphql",
108    query_path = "gql/service_requests.graphql",
109    response_derives = "Debug, Serialize, Clone, PartialEq"
110)]
111pub struct Applications;
112
113#[derive(GraphQLQuery)]
114#[graphql(
115    schema_path = "gql/service_schema.graphql",
116    query_path = "gql/service_requests.graphql",
117    response_derives = "Debug, Serialize, Clone, PartialEq"
118)]
119pub struct Blocks;
120
121#[derive(GraphQLQuery)]
122#[graphql(
123    schema_path = "gql/service_schema.graphql",
124    query_path = "gql/service_requests.graphql",
125    response_derives = "Debug, Serialize, Clone, PartialEq"
126)]
127pub struct Block;
128
129#[derive(GraphQLQuery)]
130#[graphql(
131    schema_path = "gql/service_schema.graphql",
132    query_path = "gql/service_requests.graphql",
133    response_derives = "Debug, Serialize, Clone, PartialEq"
134)]
135pub struct Notifications;
136
137#[derive(GraphQLQuery)]
138#[graphql(
139    schema_path = "gql/service_schema.graphql",
140    query_path = "gql/service_requests.graphql",
141    response_derives = "Debug, Serialize, Clone"
142)]
143pub struct Transfer;
144
145#[derive(Error, Debug)]
146pub enum ConversionError {
147    #[error(transparent)]
148    Serde(#[from] serde_json::Error),
149    #[error("Unexpected certificate type: {0}")]
150    UnexpectedCertificateType(String),
151}
152
153#[cfg(not(target_arch = "wasm32"))]
154mod from {
155    use linera_base::{
156        data_types::{ApplicationPermissions, Event, TimeDelta},
157        identifiers::{Account, ApplicationId as RealApplicationId, ModuleId, StreamId},
158        ownership::{ChainOwnership, TimeoutConfig},
159    };
160    use linera_chain::{
161        block::{Block, BlockBody, BlockHeader},
162        types::ConfirmedBlock,
163    };
164    use linera_execution::{
165        system::{AdminOperation, OpenChainConfig},
166        OutgoingMessage,
167    };
168
169    use super::*;
170
171    /// Convert GraphQL system operation metadata to a SystemOperation object.
172    fn convert_system_operation(
173        system_op: block::BlockBlockBlockBodyTransactionMetadataOperationSystemOperation,
174    ) -> Result<SystemOperation, ConversionError> {
175        match system_op.system_operation_type.as_str() {
176            "Transfer" => {
177                let transfer = system_op.transfer.ok_or_else(|| {
178                    ConversionError::UnexpectedCertificateType(
179                        "Missing transfer metadata for Transfer operation".to_string(),
180                    )
181                })?;
182                Ok(SystemOperation::Transfer {
183                    owner: transfer.owner,
184                    recipient: Account {
185                        chain_id: transfer.recipient.chain_id,
186                        owner: transfer.recipient.owner,
187                    },
188                    amount: transfer.amount,
189                })
190            }
191            "Claim" => {
192                let claim = system_op.claim.ok_or_else(|| {
193                    ConversionError::UnexpectedCertificateType(
194                        "Missing claim metadata for Claim operation".to_string(),
195                    )
196                })?;
197                Ok(SystemOperation::Claim {
198                    owner: claim.owner,
199                    target_id: claim.target_id,
200                    recipient: Account {
201                        chain_id: claim.recipient.chain_id,
202                        owner: claim.recipient.owner,
203                    },
204                    amount: claim.amount,
205                })
206            }
207            "OpenChain" => {
208                let open_chain = system_op.open_chain.ok_or_else(|| {
209                    ConversionError::UnexpectedCertificateType(
210                        "Missing open_chain metadata for OpenChain operation".to_string(),
211                    )
212                })?;
213
214                let ownership: ChainOwnership =
215                    serde_json::from_str(&open_chain.ownership.ownership_json)
216                        .map_err(ConversionError::Serde)?;
217
218                let application_permissions: ApplicationPermissions =
219                    serde_json::from_str(&open_chain.application_permissions.permissions_json)
220                        .map_err(ConversionError::Serde)?;
221
222                Ok(SystemOperation::OpenChain(OpenChainConfig {
223                    ownership,
224                    balance: open_chain.balance,
225                    application_permissions,
226                }))
227            }
228            "CloseChain" => Ok(SystemOperation::CloseChain),
229            "ChangeOwnership" => {
230                let change_ownership = system_op.change_ownership.ok_or_else(|| {
231                    ConversionError::UnexpectedCertificateType(
232                        "Missing change_ownership metadata for ChangeOwnership operation"
233                            .to_string(),
234                    )
235                })?;
236
237                let timeout_config = TimeoutConfig {
238                    fast_round_duration: change_ownership
239                        .timeout_config
240                        .fast_round_ms
241                        .as_ref()
242                        .map(|s| {
243                            s.parse::<u64>().map_err(|_| {
244                                ConversionError::UnexpectedCertificateType(
245                                    "Invalid fast_round_ms value".to_string(),
246                                )
247                            })
248                        })
249                        .transpose()?
250                        .map(|ms| TimeDelta::from_micros(ms * 1000)),
251                    base_timeout: TimeDelta::from_micros(
252                        change_ownership
253                            .timeout_config
254                            .base_timeout_ms
255                            .parse::<u64>()
256                            .map_err(|_| {
257                                ConversionError::UnexpectedCertificateType(
258                                    "Invalid base_timeout_ms value".to_string(),
259                                )
260                            })?
261                            * 1000,
262                    ),
263                    timeout_increment: TimeDelta::from_micros(
264                        change_ownership
265                            .timeout_config
266                            .timeout_increment_ms
267                            .parse::<u64>()
268                            .map_err(|_| {
269                                ConversionError::UnexpectedCertificateType(
270                                    "Invalid timeout_increment_ms value".to_string(),
271                                )
272                            })?
273                            * 1000,
274                    ),
275                    fallback_duration: TimeDelta::from_micros(
276                        change_ownership
277                            .timeout_config
278                            .fallback_duration_ms
279                            .parse::<u64>()
280                            .map_err(|_| {
281                                ConversionError::UnexpectedCertificateType(
282                                    "Invalid fallback_duration_ms value".to_string(),
283                                )
284                            })?
285                            * 1000,
286                    ),
287                };
288
289                Ok(SystemOperation::ChangeOwnership {
290                    super_owners: change_ownership.super_owners,
291                    owners: change_ownership
292                        .owners
293                        .into_iter()
294                        .map(|ow| {
295                            let weight = ow.weight.parse::<u64>().map_err(|_| {
296                                ConversionError::UnexpectedCertificateType(
297                                    "Invalid owner weight value".to_string(),
298                                )
299                            })?;
300                            Ok((ow.owner, weight))
301                        })
302                        .collect::<Result<Vec<_>, ConversionError>>()?,
303                    multi_leader_rounds: change_ownership.multi_leader_rounds as u32,
304                    open_multi_leader_rounds: change_ownership.open_multi_leader_rounds,
305                    timeout_config,
306                })
307            }
308            "ChangeApplicationPermissions" => {
309                let change_permissions =
310                    system_op.change_application_permissions.ok_or_else(|| {
311                        ConversionError::UnexpectedCertificateType(
312                            "Missing change_application_permissions metadata".to_string(),
313                        )
314                    })?;
315
316                let permissions: ApplicationPermissions =
317                    serde_json::from_str(&change_permissions.permissions.permissions_json)
318                        .map_err(ConversionError::Serde)?;
319
320                Ok(SystemOperation::ChangeApplicationPermissions(permissions))
321            }
322            "PublishModule" => {
323                let publish_module = system_op.publish_module.ok_or_else(|| {
324                    ConversionError::UnexpectedCertificateType(
325                        "Missing publish_module metadata for PublishModule operation".to_string(),
326                    )
327                })?;
328
329                let module_id: ModuleId = publish_module.module_id.parse().map_err(|e| {
330                    ConversionError::UnexpectedCertificateType(format!(
331                        "Invalid module_id format: {e}"
332                    ))
333                })?;
334
335                Ok(SystemOperation::PublishModule { module_id })
336            }
337            "PublishDataBlob" => {
338                let publish_data_blob = system_op.publish_data_blob.ok_or_else(|| {
339                    ConversionError::UnexpectedCertificateType(
340                        "Missing publish_data_blob metadata".to_string(),
341                    )
342                })?;
343                Ok(SystemOperation::PublishDataBlob {
344                    blob_hash: publish_data_blob.blob_hash,
345                })
346            }
347            "VerifyBlob" => {
348                let verify_blob = system_op.verify_blob.ok_or_else(|| {
349                    ConversionError::UnexpectedCertificateType(
350                        "Missing verify_blob metadata for VerifyBlob operation".to_string(),
351                    )
352                })?;
353                let blob_id: BlobId = verify_blob.blob_id.parse().map_err(|_| {
354                    ConversionError::UnexpectedCertificateType("Invalid blob_id format".to_string())
355                })?;
356                Ok(SystemOperation::VerifyBlob { blob_id })
357            }
358            "CreateApplication" => {
359                let create_application = system_op.create_application.ok_or_else(|| {
360                    ConversionError::UnexpectedCertificateType(
361                        "Missing create_application metadata".to_string(),
362                    )
363                })?;
364
365                let module_id: ModuleId = create_application.module_id.parse().map_err(|e| {
366                    ConversionError::UnexpectedCertificateType(format!(
367                        "Invalid module_id format: {e}"
368                    ))
369                })?;
370
371                let parameters = hex::decode(create_application.parameters_hex).map_err(|_| {
372                    ConversionError::UnexpectedCertificateType(
373                        "Invalid hex in parameters_hex".to_string(),
374                    )
375                })?;
376
377                let instantiation_argument =
378                    hex::decode(create_application.instantiation_argument_hex).map_err(|_| {
379                        ConversionError::UnexpectedCertificateType(
380                            "Invalid hex in instantiation_argument_hex".to_string(),
381                        )
382                    })?;
383
384                let required_application_ids = create_application
385                    .required_application_ids
386                    .into_iter()
387                    .map(|id| {
388                        id.parse::<RealApplicationId>().map_err(|_| {
389                            ConversionError::UnexpectedCertificateType(
390                                "Invalid required_application_id format".to_string(),
391                            )
392                        })
393                    })
394                    .collect::<Result<Vec<_>, _>>()?;
395
396                Ok(SystemOperation::CreateApplication {
397                    module_id,
398                    parameters,
399                    instantiation_argument,
400                    required_application_ids,
401                })
402            }
403            "Admin" => {
404                let admin = system_op.admin.ok_or_else(|| {
405                    ConversionError::UnexpectedCertificateType(
406                        "Missing admin metadata for Admin operation".to_string(),
407                    )
408                })?;
409
410                let admin_op = match admin.admin_operation_type.as_str() {
411                    "PublishCommitteeBlob" => {
412                        let blob_hash = admin.blob_hash.ok_or_else(|| {
413                            ConversionError::UnexpectedCertificateType(
414                                "Missing blob_hash for PublishCommitteeBlob".to_string(),
415                            )
416                        })?;
417                        AdminOperation::PublishCommitteeBlob { blob_hash }
418                    }
419                    "CreateCommittee" => {
420                        let epoch_val = admin.epoch.ok_or_else(|| {
421                            ConversionError::UnexpectedCertificateType(
422                                "Missing epoch for CreateCommittee".to_string(),
423                            )
424                        })?;
425                        let epoch = Epoch(epoch_val as u32);
426                        let blob_hash = admin.blob_hash.ok_or_else(|| {
427                            ConversionError::UnexpectedCertificateType(
428                                "Missing blob_hash for CreateCommittee".to_string(),
429                            )
430                        })?;
431                        AdminOperation::CreateCommittee { epoch, blob_hash }
432                    }
433                    "RemoveCommittee" => {
434                        let epoch_val = admin.epoch.ok_or_else(|| {
435                            ConversionError::UnexpectedCertificateType(
436                                "Missing epoch for RemoveCommittee".to_string(),
437                            )
438                        })?;
439                        let epoch = Epoch(epoch_val as u32);
440                        AdminOperation::RemoveCommittee { epoch }
441                    }
442                    _ => {
443                        return Err(ConversionError::UnexpectedCertificateType(format!(
444                            "Unknown admin operation type: {}",
445                            admin.admin_operation_type
446                        )));
447                    }
448                };
449
450                Ok(SystemOperation::Admin(admin_op))
451            }
452            "ProcessNewEpoch" => {
453                let epoch_val = system_op.epoch.ok_or_else(|| {
454                    ConversionError::UnexpectedCertificateType(
455                        "Missing epoch for ProcessNewEpoch operation".to_string(),
456                    )
457                })?;
458                Ok(SystemOperation::ProcessNewEpoch(Epoch(epoch_val as u32)))
459            }
460            "ProcessRemovedEpoch" => {
461                let epoch_val = system_op.epoch.ok_or_else(|| {
462                    ConversionError::UnexpectedCertificateType(
463                        "Missing epoch for ProcessRemovedEpoch operation".to_string(),
464                    )
465                })?;
466                Ok(SystemOperation::ProcessRemovedEpoch(Epoch(
467                    epoch_val as u32,
468                )))
469            }
470            "UpdateStreams" => {
471                let update_streams = system_op.update_streams.ok_or_else(|| {
472                    ConversionError::UnexpectedCertificateType(
473                        "Missing update_streams metadata for UpdateStreams operation".to_string(),
474                    )
475                })?;
476
477                let streams = update_streams
478                    .into_iter()
479                    .map(|stream| {
480                        let stream_id_parsed: StreamId =
481                            stream.stream_id.parse().map_err(|_| {
482                                ConversionError::UnexpectedCertificateType(
483                                    "Invalid stream_id format".to_string(),
484                                )
485                            })?;
486                        Ok((stream.chain_id, stream_id_parsed, stream.next_index as u32))
487                    })
488                    .collect::<Result<Vec<_>, ConversionError>>()?;
489
490                Ok(SystemOperation::UpdateStreams(streams))
491            }
492            _ => Err(ConversionError::UnexpectedCertificateType(format!(
493                "Unknown system operation type: {}",
494                system_op.system_operation_type
495            ))),
496        }
497    }
498
499    /// Convert GraphQL transaction metadata to a Transaction object.
500    fn convert_transaction_metadata(
501        metadata: block::BlockBlockBlockBodyTransactionMetadata,
502    ) -> Result<Transaction, ConversionError> {
503        match metadata.transaction_type.as_str() {
504            "ReceiveMessages" => {
505                let incoming_bundle = metadata.incoming_bundle.ok_or_else(|| {
506                    ConversionError::UnexpectedCertificateType(
507                        "Missing incoming_bundle for ReceiveMessages transaction".to_string(),
508                    )
509                })?;
510
511                let bundle = IncomingBundle {
512                    origin: incoming_bundle.origin,
513                    bundle: MessageBundle {
514                        height: incoming_bundle.bundle.height,
515                        timestamp: incoming_bundle.bundle.timestamp,
516                        certificate_hash: incoming_bundle.bundle.certificate_hash,
517                        transaction_index: incoming_bundle.bundle.transaction_index as u32,
518                        messages: incoming_bundle
519                            .bundle
520                            .messages
521                            .into_iter()
522                            .map(|msg| linera_chain::data_types::PostedMessage {
523                                authenticated_signer: msg.authenticated_signer,
524                                grant: msg.grant,
525                                refund_grant_to: msg.refund_grant_to,
526                                kind: msg.kind,
527                                index: msg.index as u32,
528                                message: msg.message,
529                            })
530                            .collect(),
531                    },
532                    action: incoming_bundle.action,
533                };
534
535                Ok(Transaction::ReceiveMessages(bundle))
536            }
537            "ExecuteOperation" => {
538                let graphql_operation = metadata.operation.ok_or_else(|| {
539                    ConversionError::UnexpectedCertificateType(
540                        "Missing operation for ExecuteOperation transaction".to_string(),
541                    )
542                })?;
543
544                let operation = match graphql_operation.operation_type.as_str() {
545                    "System" => {
546                        let system_op = graphql_operation.system_operation.ok_or_else(|| {
547                            ConversionError::UnexpectedCertificateType(
548                                "Missing system_operation for System operation".to_string(),
549                            )
550                        })?;
551
552                        let sys_op = convert_system_operation(system_op)?;
553                        Operation::System(Box::new(sys_op))
554                    }
555                    "User" => {
556                        let application_id = graphql_operation.application_id.ok_or_else(|| {
557                            ConversionError::UnexpectedCertificateType(
558                                "Missing application_id for User operation".to_string(),
559                            )
560                        })?;
561
562                        let bytes_hex = graphql_operation.user_bytes_hex.ok_or_else(|| {
563                            ConversionError::UnexpectedCertificateType(
564                                "Missing user_bytes_hex for User operation".to_string(),
565                            )
566                        })?;
567
568                        // Convert hex string to bytes
569                        let bytes = hex::decode(bytes_hex).map_err(|_| {
570                            ConversionError::UnexpectedCertificateType(
571                                "Invalid hex in user_bytes_hex".to_string(),
572                            )
573                        })?;
574
575                        Operation::User {
576                            application_id: application_id.parse().map_err(|_| {
577                                ConversionError::UnexpectedCertificateType(
578                                    "Invalid application_id format".to_string(),
579                                )
580                            })?,
581                            bytes,
582                        }
583                    }
584                    _ => {
585                        return Err(ConversionError::UnexpectedCertificateType(format!(
586                            "Unknown operation type: {}",
587                            graphql_operation.operation_type
588                        )));
589                    }
590                };
591
592                Ok(Transaction::ExecuteOperation(operation))
593            }
594            _ => Err(ConversionError::UnexpectedCertificateType(format!(
595                "Unknown transaction type: {}",
596                metadata.transaction_type
597            ))),
598        }
599    }
600
601    impl From<block::BlockBlockBlockBodyMessages> for OutgoingMessage {
602        fn from(val: block::BlockBlockBlockBodyMessages) -> Self {
603            let block::BlockBlockBlockBodyMessages {
604                destination,
605                authenticated_signer,
606                grant,
607                refund_grant_to,
608                kind,
609                message,
610            } = val;
611            OutgoingMessage {
612                destination,
613                authenticated_signer,
614                grant,
615                refund_grant_to,
616                kind,
617                message,
618            }
619        }
620    }
621
622    impl TryFrom<block::BlockBlockBlock> for Block {
623        type Error = ConversionError;
624
625        fn try_from(val: block::BlockBlockBlock) -> Result<Self, Self::Error> {
626            let block::BlockBlockBlock { header, body } = val;
627            let block::BlockBlockBlockHeader {
628                chain_id,
629                epoch,
630                height,
631                timestamp,
632                authenticated_signer,
633                previous_block_hash,
634                state_hash,
635                transactions_hash,
636                messages_hash,
637                previous_message_blocks_hash,
638                previous_event_blocks_hash,
639                oracle_responses_hash,
640                events_hash,
641                blobs_hash,
642                operation_results_hash,
643            } = header;
644            let block::BlockBlockBlockBody {
645                messages,
646                previous_message_blocks,
647                previous_event_blocks,
648                oracle_responses,
649                events,
650                blobs,
651                operation_results,
652                transaction_metadata,
653            } = body;
654
655            let block_header = BlockHeader {
656                chain_id,
657                epoch,
658                height,
659                timestamp,
660                authenticated_signer,
661                previous_block_hash,
662                state_hash,
663                transactions_hash,
664                messages_hash,
665                previous_message_blocks_hash,
666                previous_event_blocks_hash,
667                oracle_responses_hash,
668                events_hash,
669                blobs_hash,
670                operation_results_hash,
671            };
672
673            // Convert GraphQL transaction metadata to Transaction objects
674            let transactions = transaction_metadata
675                .into_iter()
676                .map(convert_transaction_metadata)
677                .collect::<Result<Vec<_>, _>>()?;
678
679            let block_body = BlockBody {
680                transactions,
681                messages: messages
682                    .into_iter()
683                    .map(|messages| messages.into_iter().map(Into::into).collect())
684                    .collect::<Vec<Vec<_>>>(),
685                previous_message_blocks: serde_json::from_value(previous_message_blocks)
686                    .map_err(ConversionError::Serde)?,
687                previous_event_blocks: serde_json::from_value(previous_event_blocks)
688                    .map_err(ConversionError::Serde)?,
689                oracle_responses: oracle_responses.into_iter().collect(),
690                events: events
691                    .into_iter()
692                    .map(|events| events.into_iter().map(Into::into).collect())
693                    .collect(),
694                blobs: blobs
695                    .into_iter()
696                    .map(|blobs| blobs.into_iter().collect())
697                    .collect(),
698                operation_results,
699            };
700
701            Ok(Block {
702                header: block_header,
703                body: block_body,
704            })
705        }
706    }
707
708    impl From<block::BlockBlockBlockBodyEvents> for Event {
709        fn from(event: block::BlockBlockBlockBodyEvents) -> Self {
710            Event {
711                stream_id: event.stream_id.into(),
712                index: event.index as u32,
713                value: event.value.into_iter().map(|byte| byte as u8).collect(),
714            }
715        }
716    }
717
718    impl From<block::BlockBlockBlockBodyEventsStreamId> for StreamId {
719        fn from(stream_id: block::BlockBlockBlockBodyEventsStreamId) -> Self {
720            StreamId {
721                application_id: stream_id.application_id,
722                stream_name: stream_id.stream_name,
723            }
724        }
725    }
726
727    impl TryFrom<block::BlockBlock> for ConfirmedBlock {
728        type Error = ConversionError;
729
730        fn try_from(val: block::BlockBlock) -> Result<Self, Self::Error> {
731            match (val.status.as_str(), val.block) {
732                ("confirmed", block) => Ok(ConfirmedBlock::new(block.try_into()?)),
733                _ => Err(ConversionError::UnexpectedCertificateType(val.status)),
734            }
735        }
736    }
737}