linera_chain/data_types/
metadata.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! GraphQL-compatible structured metadata representations for operations and messages.
5
6use async_graphql::SimpleObject;
7use linera_base::{
8    crypto::CryptoHash,
9    data_types::{Amount, ApplicationPermissions},
10    hex,
11    identifiers::{Account, AccountOwner, ApplicationId, ChainId},
12    ownership::{ChainOwnership, TimeoutConfig},
13};
14use linera_execution::{system::AdminOperation, Message, SystemMessage, SystemOperation};
15use serde::{Deserialize, Serialize};
16
17/// Timeout configuration metadata for GraphQL.
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
19pub struct TimeoutConfigMetadata {
20    /// The duration of the fast round in milliseconds.
21    pub fast_round_ms: Option<String>,
22    /// The duration of the first single-leader and all multi-leader rounds in milliseconds.
23    pub base_timeout_ms: String,
24    /// The duration by which the timeout increases after each single-leader round in milliseconds.
25    pub timeout_increment_ms: String,
26    /// The age of an incoming tracked or protected message after which validators start
27    /// transitioning to fallback mode, in milliseconds.
28    pub fallback_duration_ms: String,
29}
30
31impl From<&TimeoutConfig> for TimeoutConfigMetadata {
32    fn from(config: &TimeoutConfig) -> Self {
33        TimeoutConfigMetadata {
34            fast_round_ms: config
35                .fast_round_duration
36                .map(|d| (d.as_micros() / 1000).to_string()),
37            base_timeout_ms: (config.base_timeout.as_micros() / 1000).to_string(),
38            timeout_increment_ms: (config.timeout_increment.as_micros() / 1000).to_string(),
39            fallback_duration_ms: (config.fallback_duration.as_micros() / 1000).to_string(),
40        }
41    }
42}
43
44/// Chain ownership metadata for GraphQL.
45#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
46pub struct ChainOwnershipMetadata {
47    /// JSON serialized `ChainOwnership` for full representation.
48    pub ownership_json: String,
49}
50
51impl From<&ChainOwnership> for ChainOwnershipMetadata {
52    fn from(ownership: &ChainOwnership) -> Self {
53        ChainOwnershipMetadata {
54            // Fallback to Debug format should never be needed, as ChainOwnership implements Serialize.
55            // But we include it as a safety measure for GraphQL responses to always succeed.
56            ownership_json: serde_json::to_string(ownership)
57                .unwrap_or_else(|_| format!("{:?}", ownership)),
58        }
59    }
60}
61
62/// Application permissions metadata for GraphQL.
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
64pub struct ApplicationPermissionsMetadata {
65    /// JSON serialized `ApplicationPermissions`.
66    pub permissions_json: String,
67}
68
69impl From<&ApplicationPermissions> for ApplicationPermissionsMetadata {
70    fn from(permissions: &ApplicationPermissions) -> Self {
71        ApplicationPermissionsMetadata {
72            // Fallback to Debug format should never be needed, as ApplicationPermissions implements Serialize.
73            // But we include it as a safety measure for GraphQL responses to always succeed.
74            permissions_json: serde_json::to_string(permissions)
75                .unwrap_or_else(|_| format!("{:?}", permissions)),
76        }
77    }
78}
79
80/// Structured representation of a system operation for GraphQL.
81#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
82pub struct SystemOperationMetadata {
83    /// The type of system operation
84    pub system_operation_type: String,
85    /// Transfer operation details
86    pub transfer: Option<TransferOperationMetadata>,
87    /// Claim operation details
88    pub claim: Option<ClaimOperationMetadata>,
89    /// Open chain operation details
90    pub open_chain: Option<OpenChainOperationMetadata>,
91    /// Change ownership operation details
92    pub change_ownership: Option<ChangeOwnershipOperationMetadata>,
93    /// Change application permissions operation details
94    pub change_application_permissions: Option<ChangeApplicationPermissionsMetadata>,
95    /// Admin operation details
96    pub admin: Option<AdminOperationMetadata>,
97    /// Create application operation details
98    pub create_application: Option<CreateApplicationOperationMetadata>,
99    /// Publish data blob operation details
100    pub publish_data_blob: Option<PublishDataBlobMetadata>,
101    /// Verify blob operation details
102    pub verify_blob: Option<VerifyBlobMetadata>,
103    /// Publish module operation details
104    pub publish_module: Option<PublishModuleMetadata>,
105    /// Epoch operation details (`ProcessNewEpoch`, `ProcessRemovedEpoch`)
106    pub epoch: Option<i32>,
107    /// `UpdateStreams` operation details
108    pub update_streams: Option<Vec<UpdateStreamMetadata>>,
109}
110
111impl SystemOperationMetadata {
112    /// Creates a new metadata with the given operation type and all fields set to None.
113    fn new(system_operation_type: &str) -> Self {
114        SystemOperationMetadata {
115            system_operation_type: system_operation_type.to_string(),
116            transfer: None,
117            claim: None,
118            open_chain: None,
119            change_ownership: None,
120            change_application_permissions: None,
121            admin: None,
122            create_application: None,
123            publish_data_blob: None,
124            verify_blob: None,
125            publish_module: None,
126            epoch: None,
127            update_streams: None,
128        }
129    }
130}
131
132/// Transfer operation metadata.
133#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
134pub struct TransferOperationMetadata {
135    pub owner: AccountOwner,
136    pub recipient: Account,
137    pub amount: Amount,
138}
139
140/// Claim operation metadata.
141#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
142pub struct ClaimOperationMetadata {
143    pub owner: AccountOwner,
144    pub target_id: ChainId,
145    pub recipient: Account,
146    pub amount: Amount,
147}
148
149/// Open chain operation metadata.
150#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
151pub struct OpenChainOperationMetadata {
152    pub balance: Amount,
153    pub ownership: ChainOwnershipMetadata,
154    pub application_permissions: ApplicationPermissionsMetadata,
155}
156
157/// Change ownership operation metadata.
158#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
159pub struct ChangeOwnershipOperationMetadata {
160    pub super_owners: Vec<AccountOwner>,
161    pub owners: Vec<OwnerWithWeight>,
162    pub first_leader: Option<AccountOwner>,
163    pub multi_leader_rounds: i32,
164    pub open_multi_leader_rounds: bool,
165    pub timeout_config: TimeoutConfigMetadata,
166}
167
168/// Owner with weight metadata.
169#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
170pub struct OwnerWithWeight {
171    pub owner: AccountOwner,
172    pub weight: String, // Using String to represent u64 safely in GraphQL
173}
174
175/// Change application permissions operation metadata.
176#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
177pub struct ChangeApplicationPermissionsMetadata {
178    pub permissions: ApplicationPermissionsMetadata,
179}
180
181/// Admin operation metadata.
182#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
183pub struct AdminOperationMetadata {
184    pub admin_operation_type: String,
185    pub epoch: Option<i32>,
186    pub blob_hash: Option<CryptoHash>,
187}
188
189/// Create application operation metadata.
190#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
191pub struct CreateApplicationOperationMetadata {
192    pub module_id: String,
193    pub parameters_hex: String,
194    pub instantiation_argument_hex: String,
195    pub required_application_ids: Vec<ApplicationId>,
196}
197
198/// Publish data blob operation metadata.
199#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
200pub struct PublishDataBlobMetadata {
201    pub blob_hash: CryptoHash,
202}
203
204/// Verify blob operation metadata.
205#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
206pub struct VerifyBlobMetadata {
207    pub blob_id: String,
208}
209
210/// Publish module operation metadata.
211#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
212pub struct PublishModuleMetadata {
213    pub module_id: String,
214}
215
216/// Update stream metadata.
217#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
218pub struct UpdateStreamMetadata {
219    pub chain_id: ChainId,
220    pub stream_id: String,
221    pub next_index: i32,
222}
223
224/// Structured representation of a system message for GraphQL.
225#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
226pub struct SystemMessageMetadata {
227    /// The type of system message
228    pub system_message_type: String,
229    /// Credit message details
230    pub credit: Option<CreditMessageMetadata>,
231    /// Withdraw message details
232    pub withdraw: Option<WithdrawMessageMetadata>,
233}
234
235/// Credit message metadata.
236#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
237pub struct CreditMessageMetadata {
238    pub target: AccountOwner,
239    pub amount: Amount,
240    pub source: AccountOwner,
241}
242
243/// Withdraw message metadata.
244#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
245pub struct WithdrawMessageMetadata {
246    pub owner: AccountOwner,
247    pub amount: Amount,
248    pub recipient: Account,
249}
250
251/// Structured representation of a message for GraphQL.
252#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
253pub struct MessageMetadata {
254    /// The type of message: "System" or "User"
255    pub message_type: String,
256    /// For user messages, the application ID
257    pub application_id: Option<ApplicationId>,
258    /// For user messages, the serialized bytes (as a hex string for GraphQL)
259    pub user_bytes_hex: Option<String>,
260    /// For system messages, structured representation
261    pub system_message: Option<SystemMessageMetadata>,
262}
263
264impl From<&SystemOperation> for SystemOperationMetadata {
265    fn from(sys_op: &SystemOperation) -> Self {
266        match sys_op {
267            SystemOperation::Transfer {
268                owner,
269                recipient,
270                amount,
271            } => SystemOperationMetadata {
272                transfer: Some(TransferOperationMetadata {
273                    owner: *owner,
274                    recipient: *recipient,
275                    amount: *amount,
276                }),
277                ..SystemOperationMetadata::new("Transfer")
278            },
279            SystemOperation::Claim {
280                owner,
281                target_id,
282                recipient,
283                amount,
284            } => SystemOperationMetadata {
285                claim: Some(ClaimOperationMetadata {
286                    owner: *owner,
287                    target_id: *target_id,
288                    recipient: *recipient,
289                    amount: *amount,
290                }),
291                ..SystemOperationMetadata::new("Claim")
292            },
293            SystemOperation::OpenChain(config) => SystemOperationMetadata {
294                open_chain: Some(OpenChainOperationMetadata {
295                    balance: config.balance,
296                    ownership: ChainOwnershipMetadata::from(&config.ownership),
297                    application_permissions: ApplicationPermissionsMetadata::from(
298                        &config.application_permissions,
299                    ),
300                }),
301                ..SystemOperationMetadata::new("OpenChain")
302            },
303            SystemOperation::CloseChain => SystemOperationMetadata::new("CloseChain"),
304            SystemOperation::ChangeOwnership {
305                super_owners,
306                owners,
307                first_leader,
308                multi_leader_rounds,
309                open_multi_leader_rounds,
310                timeout_config,
311            } => SystemOperationMetadata {
312                change_ownership: Some(ChangeOwnershipOperationMetadata {
313                    super_owners: super_owners.clone(),
314                    owners: owners
315                        .iter()
316                        .map(|(owner, weight)| OwnerWithWeight {
317                            owner: *owner,
318                            weight: weight.to_string(),
319                        })
320                        .collect(),
321                    first_leader: *first_leader,
322                    multi_leader_rounds: *multi_leader_rounds as i32,
323                    open_multi_leader_rounds: *open_multi_leader_rounds,
324                    timeout_config: TimeoutConfigMetadata::from(timeout_config),
325                }),
326                ..SystemOperationMetadata::new("ChangeOwnership")
327            },
328            SystemOperation::ChangeApplicationPermissions(permissions) => SystemOperationMetadata {
329                change_application_permissions: Some(ChangeApplicationPermissionsMetadata {
330                    permissions: ApplicationPermissionsMetadata::from(permissions),
331                }),
332                ..SystemOperationMetadata::new("ChangeApplicationPermissions")
333            },
334            SystemOperation::Admin(admin_op) => SystemOperationMetadata {
335                admin: Some(AdminOperationMetadata::from(admin_op)),
336                ..SystemOperationMetadata::new("Admin")
337            },
338            SystemOperation::CreateApplication {
339                module_id,
340                parameters,
341                instantiation_argument,
342                required_application_ids,
343            } => SystemOperationMetadata {
344                create_application: Some(CreateApplicationOperationMetadata {
345                    module_id: serde_json::to_string(module_id)
346                        .unwrap_or_else(|_| format!("{:?}", module_id)),
347                    parameters_hex: hex::encode(parameters),
348                    instantiation_argument_hex: hex::encode(instantiation_argument),
349                    required_application_ids: required_application_ids.clone(),
350                }),
351                ..SystemOperationMetadata::new("CreateApplication")
352            },
353            SystemOperation::PublishDataBlob { blob_hash } => SystemOperationMetadata {
354                publish_data_blob: Some(PublishDataBlobMetadata {
355                    blob_hash: *blob_hash,
356                }),
357                ..SystemOperationMetadata::new("PublishDataBlob")
358            },
359            SystemOperation::VerifyBlob { blob_id } => SystemOperationMetadata {
360                verify_blob: Some(VerifyBlobMetadata {
361                    blob_id: blob_id.to_string(),
362                }),
363                ..SystemOperationMetadata::new("VerifyBlob")
364            },
365            SystemOperation::PublishModule { module_id } => SystemOperationMetadata {
366                publish_module: Some(PublishModuleMetadata {
367                    module_id: serde_json::to_string(module_id)
368                        .unwrap_or_else(|_| format!("{:?}", module_id)),
369                }),
370                ..SystemOperationMetadata::new("PublishModule")
371            },
372            SystemOperation::ProcessNewEpoch(epoch) => SystemOperationMetadata {
373                epoch: Some(epoch.0 as i32),
374                ..SystemOperationMetadata::new("ProcessNewEpoch")
375            },
376            SystemOperation::ProcessRemovedEpoch(epoch) => SystemOperationMetadata {
377                epoch: Some(epoch.0 as i32),
378                ..SystemOperationMetadata::new("ProcessRemovedEpoch")
379            },
380            SystemOperation::UpdateStreams(streams) => SystemOperationMetadata {
381                update_streams: Some(
382                    streams
383                        .iter()
384                        .map(|(chain_id, stream_id, next_index)| UpdateStreamMetadata {
385                            chain_id: *chain_id,
386                            stream_id: stream_id.to_string(),
387                            next_index: *next_index as i32,
388                        })
389                        .collect(),
390                ),
391                ..SystemOperationMetadata::new("UpdateStreams")
392            },
393        }
394    }
395}
396
397impl From<&AdminOperation> for AdminOperationMetadata {
398    fn from(admin_op: &AdminOperation) -> Self {
399        match admin_op {
400            AdminOperation::PublishCommitteeBlob { blob_hash } => AdminOperationMetadata {
401                admin_operation_type: "PublishCommitteeBlob".to_string(),
402                epoch: None,
403                blob_hash: Some(*blob_hash),
404            },
405            AdminOperation::CreateCommittee { epoch, blob_hash } => AdminOperationMetadata {
406                admin_operation_type: "CreateCommittee".to_string(),
407                epoch: Some(epoch.0 as i32),
408                blob_hash: Some(*blob_hash),
409            },
410            AdminOperation::RemoveCommittee { epoch } => AdminOperationMetadata {
411                admin_operation_type: "RemoveCommittee".to_string(),
412                epoch: Some(epoch.0 as i32),
413                blob_hash: None,
414            },
415        }
416    }
417}
418
419impl From<&Message> for MessageMetadata {
420    fn from(message: &Message) -> Self {
421        match message {
422            Message::System(sys_msg) => MessageMetadata {
423                message_type: "System".to_string(),
424                application_id: None,
425                user_bytes_hex: None,
426                system_message: Some(SystemMessageMetadata::from(sys_msg)),
427            },
428            Message::User {
429                application_id,
430                bytes,
431            } => MessageMetadata {
432                message_type: "User".to_string(),
433                application_id: Some(*application_id),
434                user_bytes_hex: Some(hex::encode(bytes)),
435                system_message: None,
436            },
437        }
438    }
439}
440
441impl From<&SystemMessage> for SystemMessageMetadata {
442    fn from(sys_msg: &SystemMessage) -> Self {
443        match sys_msg {
444            SystemMessage::Credit {
445                target,
446                amount,
447                source,
448            } => SystemMessageMetadata {
449                system_message_type: "Credit".to_string(),
450                credit: Some(CreditMessageMetadata {
451                    target: *target,
452                    amount: *amount,
453                    source: *source,
454                }),
455                withdraw: None,
456            },
457            SystemMessage::Withdraw {
458                owner,
459                amount,
460                recipient,
461            } => SystemMessageMetadata {
462                system_message_type: "Withdraw".to_string(),
463                credit: None,
464                withdraw: Some(WithdrawMessageMetadata {
465                    owner: *owner,
466                    amount: *amount,
467                    recipient: *recipient,
468                }),
469            },
470        }
471    }
472}