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 multi_leader_rounds: i32,
163    pub open_multi_leader_rounds: bool,
164    pub timeout_config: TimeoutConfigMetadata,
165}
166
167/// Owner with weight metadata.
168#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
169pub struct OwnerWithWeight {
170    pub owner: AccountOwner,
171    pub weight: String, // Using String to represent u64 safely in GraphQL
172}
173
174/// Change application permissions operation metadata.
175#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
176pub struct ChangeApplicationPermissionsMetadata {
177    pub permissions: ApplicationPermissionsMetadata,
178}
179
180/// Admin operation metadata.
181#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
182pub struct AdminOperationMetadata {
183    pub admin_operation_type: String,
184    pub epoch: Option<i32>,
185    pub blob_hash: Option<CryptoHash>,
186}
187
188/// Create application operation metadata.
189#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
190pub struct CreateApplicationOperationMetadata {
191    pub module_id: String,
192    pub parameters_hex: String,
193    pub instantiation_argument_hex: String,
194    pub required_application_ids: Vec<ApplicationId>,
195}
196
197/// Publish data blob operation metadata.
198#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
199pub struct PublishDataBlobMetadata {
200    pub blob_hash: CryptoHash,
201}
202
203/// Verify blob operation metadata.
204#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
205pub struct VerifyBlobMetadata {
206    pub blob_id: String,
207}
208
209/// Publish module operation metadata.
210#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
211pub struct PublishModuleMetadata {
212    pub module_id: String,
213}
214
215/// Update stream metadata.
216#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
217pub struct UpdateStreamMetadata {
218    pub chain_id: ChainId,
219    pub stream_id: String,
220    pub next_index: i32,
221}
222
223/// Structured representation of a system message for GraphQL.
224#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
225pub struct SystemMessageMetadata {
226    /// The type of system message
227    pub system_message_type: String,
228    /// Credit message details
229    pub credit: Option<CreditMessageMetadata>,
230    /// Withdraw message details
231    pub withdraw: Option<WithdrawMessageMetadata>,
232}
233
234/// Credit message metadata.
235#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
236pub struct CreditMessageMetadata {
237    pub target: AccountOwner,
238    pub amount: Amount,
239    pub source: AccountOwner,
240}
241
242/// Withdraw message metadata.
243#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
244pub struct WithdrawMessageMetadata {
245    pub owner: AccountOwner,
246    pub amount: Amount,
247    pub recipient: Account,
248}
249
250/// Structured representation of a message for GraphQL.
251#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
252pub struct MessageMetadata {
253    /// The type of message: "System" or "User"
254    pub message_type: String,
255    /// For user messages, the application ID
256    pub application_id: Option<ApplicationId>,
257    /// For user messages, the serialized bytes (as a hex string for GraphQL)
258    pub user_bytes_hex: Option<String>,
259    /// For system messages, structured representation
260    pub system_message: Option<SystemMessageMetadata>,
261}
262
263impl From<&SystemOperation> for SystemOperationMetadata {
264    fn from(sys_op: &SystemOperation) -> Self {
265        match sys_op {
266            SystemOperation::Transfer {
267                owner,
268                recipient,
269                amount,
270            } => SystemOperationMetadata {
271                transfer: Some(TransferOperationMetadata {
272                    owner: *owner,
273                    recipient: *recipient,
274                    amount: *amount,
275                }),
276                ..SystemOperationMetadata::new("Transfer")
277            },
278            SystemOperation::Claim {
279                owner,
280                target_id,
281                recipient,
282                amount,
283            } => SystemOperationMetadata {
284                claim: Some(ClaimOperationMetadata {
285                    owner: *owner,
286                    target_id: *target_id,
287                    recipient: *recipient,
288                    amount: *amount,
289                }),
290                ..SystemOperationMetadata::new("Claim")
291            },
292            SystemOperation::OpenChain(config) => SystemOperationMetadata {
293                open_chain: Some(OpenChainOperationMetadata {
294                    balance: config.balance,
295                    ownership: ChainOwnershipMetadata::from(&config.ownership),
296                    application_permissions: ApplicationPermissionsMetadata::from(
297                        &config.application_permissions,
298                    ),
299                }),
300                ..SystemOperationMetadata::new("OpenChain")
301            },
302            SystemOperation::CloseChain => SystemOperationMetadata::new("CloseChain"),
303            SystemOperation::ChangeOwnership {
304                super_owners,
305                owners,
306                multi_leader_rounds,
307                open_multi_leader_rounds,
308                timeout_config,
309            } => SystemOperationMetadata {
310                change_ownership: Some(ChangeOwnershipOperationMetadata {
311                    super_owners: super_owners.clone(),
312                    owners: owners
313                        .iter()
314                        .map(|(owner, weight)| OwnerWithWeight {
315                            owner: *owner,
316                            weight: weight.to_string(),
317                        })
318                        .collect(),
319                    multi_leader_rounds: *multi_leader_rounds as i32,
320                    open_multi_leader_rounds: *open_multi_leader_rounds,
321                    timeout_config: TimeoutConfigMetadata::from(timeout_config),
322                }),
323                ..SystemOperationMetadata::new("ChangeOwnership")
324            },
325            SystemOperation::ChangeApplicationPermissions(permissions) => SystemOperationMetadata {
326                change_application_permissions: Some(ChangeApplicationPermissionsMetadata {
327                    permissions: ApplicationPermissionsMetadata::from(permissions),
328                }),
329                ..SystemOperationMetadata::new("ChangeApplicationPermissions")
330            },
331            SystemOperation::Admin(admin_op) => SystemOperationMetadata {
332                admin: Some(AdminOperationMetadata::from(admin_op)),
333                ..SystemOperationMetadata::new("Admin")
334            },
335            SystemOperation::CreateApplication {
336                module_id,
337                parameters,
338                instantiation_argument,
339                required_application_ids,
340            } => SystemOperationMetadata {
341                create_application: Some(CreateApplicationOperationMetadata {
342                    module_id: serde_json::to_string(module_id)
343                        .unwrap_or_else(|_| format!("{:?}", module_id)),
344                    parameters_hex: hex::encode(parameters),
345                    instantiation_argument_hex: hex::encode(instantiation_argument),
346                    required_application_ids: required_application_ids.clone(),
347                }),
348                ..SystemOperationMetadata::new("CreateApplication")
349            },
350            SystemOperation::PublishDataBlob { blob_hash } => SystemOperationMetadata {
351                publish_data_blob: Some(PublishDataBlobMetadata {
352                    blob_hash: *blob_hash,
353                }),
354                ..SystemOperationMetadata::new("PublishDataBlob")
355            },
356            SystemOperation::VerifyBlob { blob_id } => SystemOperationMetadata {
357                verify_blob: Some(VerifyBlobMetadata {
358                    blob_id: blob_id.to_string(),
359                }),
360                ..SystemOperationMetadata::new("VerifyBlob")
361            },
362            SystemOperation::PublishModule { module_id } => SystemOperationMetadata {
363                publish_module: Some(PublishModuleMetadata {
364                    module_id: serde_json::to_string(module_id)
365                        .unwrap_or_else(|_| format!("{:?}", module_id)),
366                }),
367                ..SystemOperationMetadata::new("PublishModule")
368            },
369            SystemOperation::ProcessNewEpoch(epoch) => SystemOperationMetadata {
370                epoch: Some(epoch.0 as i32),
371                ..SystemOperationMetadata::new("ProcessNewEpoch")
372            },
373            SystemOperation::ProcessRemovedEpoch(epoch) => SystemOperationMetadata {
374                epoch: Some(epoch.0 as i32),
375                ..SystemOperationMetadata::new("ProcessRemovedEpoch")
376            },
377            SystemOperation::UpdateStreams(streams) => SystemOperationMetadata {
378                update_streams: Some(
379                    streams
380                        .iter()
381                        .map(|(chain_id, stream_id, next_index)| UpdateStreamMetadata {
382                            chain_id: *chain_id,
383                            stream_id: stream_id.to_string(),
384                            next_index: *next_index as i32,
385                        })
386                        .collect(),
387                ),
388                ..SystemOperationMetadata::new("UpdateStreams")
389            },
390        }
391    }
392}
393
394impl From<&AdminOperation> for AdminOperationMetadata {
395    fn from(admin_op: &AdminOperation) -> Self {
396        match admin_op {
397            AdminOperation::PublishCommitteeBlob { blob_hash } => AdminOperationMetadata {
398                admin_operation_type: "PublishCommitteeBlob".to_string(),
399                epoch: None,
400                blob_hash: Some(*blob_hash),
401            },
402            AdminOperation::CreateCommittee { epoch, blob_hash } => AdminOperationMetadata {
403                admin_operation_type: "CreateCommittee".to_string(),
404                epoch: Some(epoch.0 as i32),
405                blob_hash: Some(*blob_hash),
406            },
407            AdminOperation::RemoveCommittee { epoch } => AdminOperationMetadata {
408                admin_operation_type: "RemoveCommittee".to_string(),
409                epoch: Some(epoch.0 as i32),
410                blob_hash: None,
411            },
412        }
413    }
414}
415
416impl From<&Message> for MessageMetadata {
417    fn from(message: &Message) -> Self {
418        match message {
419            Message::System(sys_msg) => MessageMetadata {
420                message_type: "System".to_string(),
421                application_id: None,
422                user_bytes_hex: None,
423                system_message: Some(SystemMessageMetadata::from(sys_msg)),
424            },
425            Message::User {
426                application_id,
427                bytes,
428            } => MessageMetadata {
429                message_type: "User".to_string(),
430                application_id: Some(*application_id),
431                user_bytes_hex: Some(hex::encode(bytes)),
432                system_message: None,
433            },
434        }
435    }
436}
437
438impl From<&SystemMessage> for SystemMessageMetadata {
439    fn from(sys_msg: &SystemMessage) -> Self {
440        match sys_msg {
441            SystemMessage::Credit {
442                target,
443                amount,
444                source,
445            } => SystemMessageMetadata {
446                system_message_type: "Credit".to_string(),
447                credit: Some(CreditMessageMetadata {
448                    target: *target,
449                    amount: *amount,
450                    source: *source,
451                }),
452                withdraw: None,
453            },
454            SystemMessage::Withdraw {
455                owner,
456                amount,
457                recipient,
458            } => SystemMessageMetadata {
459                system_message_type: "Withdraw".to_string(),
460                credit: None,
461                withdraw: Some(WithdrawMessageMetadata {
462                    owner: *owner,
463                    amount: *amount,
464                    recipient: *recipient,
465                }),
466            },
467        }
468    }
469}