1use 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 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
38 pub struct Notification {
39 pub chain_id: ChainId,
40 pub reason: Reason,
41 }
42
43 #[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 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 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 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 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}