1#[cfg(with_testing)]
8use std::ops;
9use std::{
10 fmt::{self, Display},
11 fs,
12 hash::Hash,
13 io, iter,
14 num::ParseIntError,
15 path::Path,
16 str::FromStr,
17 sync::Arc,
18};
19
20use allocative::{Allocative, Visitor};
21use alloy_primitives::U256;
22use async_graphql::{InputObject, SimpleObject};
23use custom_debug_derive::Debug;
24use linera_witty::{WitLoad, WitStore, WitType};
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26use serde_with::{serde_as, Bytes};
27use thiserror::Error;
28
29#[cfg(with_metrics)]
30use crate::prometheus_util::MeasureLatency as _;
31use crate::{
32 crypto::{BcsHashable, CryptoError, CryptoHash},
33 doc_scalar, hex_debug, http,
34 identifiers::{
35 ApplicationId, BlobId, BlobType, ChainId, EventId, GenericApplicationId, ModuleId, StreamId,
36 },
37 limited_writer::{LimitedWriter, LimitedWriterError},
38 ownership::ChainOwnership,
39 time::{Duration, SystemTime},
40 vm::VmRuntime,
41};
42
43#[derive(
48 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, WitType, WitLoad, WitStore,
49)]
50#[cfg_attr(
51 all(with_testing, not(target_arch = "wasm32")),
52 derive(test_strategy::Arbitrary)
53)]
54pub struct Amount(u128);
55
56impl Allocative for Amount {
57 fn visit<'a, 'b: 'a>(&self, visitor: &'a mut Visitor<'b>) {
58 visitor.visit_simple_sized::<Self>();
59 }
60}
61
62#[derive(Serialize, Deserialize)]
63#[serde(rename = "Amount")]
64struct AmountString(String);
65
66#[derive(Serialize, Deserialize)]
67#[serde(rename = "Amount")]
68struct AmountU128(u128);
69
70impl Serialize for Amount {
71 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
72 if serializer.is_human_readable() {
73 AmountString(self.to_string()).serialize(serializer)
74 } else {
75 AmountU128(self.0).serialize(serializer)
76 }
77 }
78}
79
80impl<'de> Deserialize<'de> for Amount {
81 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
82 if deserializer.is_human_readable() {
83 let AmountString(s) = AmountString::deserialize(deserializer)?;
84 s.parse().map_err(serde::de::Error::custom)
85 } else {
86 Ok(Amount(AmountU128::deserialize(deserializer)?.0))
87 }
88 }
89}
90
91impl From<Amount> for U256 {
92 fn from(amount: Amount) -> U256 {
93 U256::from(amount.0)
94 }
95}
96
97#[derive(
99 Eq,
100 PartialEq,
101 Ord,
102 PartialOrd,
103 Copy,
104 Clone,
105 Hash,
106 Default,
107 Debug,
108 Serialize,
109 Deserialize,
110 WitType,
111 WitLoad,
112 WitStore,
113 Allocative,
114)]
115#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
116pub struct BlockHeight(pub u64);
117
118#[derive(
120 Eq,
121 PartialEq,
122 Ord,
123 PartialOrd,
124 Copy,
125 Clone,
126 Hash,
127 Default,
128 Debug,
129 Serialize,
130 Deserialize,
131 Allocative,
132)]
133#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
134pub enum Round {
135 #[default]
137 Fast,
138 MultiLeader(u32),
140 SingleLeader(u32),
142 Validator(u32),
144}
145
146#[derive(
148 Eq,
149 PartialEq,
150 Ord,
151 PartialOrd,
152 Copy,
153 Clone,
154 Hash,
155 Default,
156 Debug,
157 Serialize,
158 Deserialize,
159 WitType,
160 WitLoad,
161 WitStore,
162 Allocative,
163)]
164pub struct TimeDelta(u64);
165
166impl TimeDelta {
167 pub const fn from_micros(micros: u64) -> Self {
169 TimeDelta(micros)
170 }
171
172 pub const fn from_millis(millis: u64) -> Self {
174 TimeDelta(millis.saturating_mul(1_000))
175 }
176
177 pub const fn from_secs(secs: u64) -> Self {
179 TimeDelta(secs.saturating_mul(1_000_000))
180 }
181
182 pub fn from_duration(duration: Duration) -> Self {
185 TimeDelta::from_micros(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX))
186 }
187
188 pub const fn as_micros(&self) -> u64 {
190 self.0
191 }
192
193 pub const fn as_duration(&self) -> Duration {
195 Duration::from_micros(self.as_micros())
196 }
197}
198
199#[derive(
201 Eq,
202 PartialEq,
203 Ord,
204 PartialOrd,
205 Copy,
206 Clone,
207 Hash,
208 Default,
209 Debug,
210 Serialize,
211 Deserialize,
212 WitType,
213 WitLoad,
214 WitStore,
215 Allocative,
216)]
217pub struct Timestamp(u64);
218
219impl Timestamp {
220 pub fn now() -> Timestamp {
222 Timestamp(
223 SystemTime::UNIX_EPOCH
224 .elapsed()
225 .expect("system time should be after Unix epoch")
226 .as_micros()
227 .try_into()
228 .unwrap_or(u64::MAX),
229 )
230 }
231
232 pub const fn micros(&self) -> u64 {
234 self.0
235 }
236
237 pub const fn delta_since(&self, other: Timestamp) -> TimeDelta {
240 TimeDelta::from_micros(self.0.saturating_sub(other.0))
241 }
242
243 pub const fn duration_since(&self, other: Timestamp) -> Duration {
246 Duration::from_micros(self.0.saturating_sub(other.0))
247 }
248
249 pub const fn saturating_add(&self, duration: TimeDelta) -> Timestamp {
251 Timestamp(self.0.saturating_add(duration.0))
252 }
253
254 pub const fn saturating_sub(&self, duration: TimeDelta) -> Timestamp {
256 Timestamp(self.0.saturating_sub(duration.0))
257 }
258
259 pub const fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
262 Timestamp(self.0.saturating_sub(micros))
263 }
264}
265
266impl From<u64> for Timestamp {
267 fn from(t: u64) -> Timestamp {
268 Timestamp(t)
269 }
270}
271
272impl Display for Timestamp {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 if let Some(date_time) = chrono::DateTime::from_timestamp(
275 (self.0 / 1_000_000) as i64,
276 ((self.0 % 1_000_000) * 1_000) as u32,
277 ) {
278 return date_time.naive_utc().fmt(f);
279 }
280 self.0.fmt(f)
281 }
282}
283
284#[derive(
287 Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, WitLoad, WitStore, WitType,
288)]
289pub struct Resources {
290 pub wasm_fuel: u64,
292 pub evm_fuel: u64,
294 pub read_operations: u32,
296 pub write_operations: u32,
298 pub bytes_runtime: u32,
300 pub bytes_to_read: u32,
302 pub bytes_to_write: u32,
304 pub blobs_to_read: u32,
306 pub blobs_to_publish: u32,
308 pub blob_bytes_to_read: u32,
310 pub blob_bytes_to_publish: u32,
312 pub messages: u32,
314 pub message_size: u32,
317 pub storage_size_delta: u32,
319 pub service_as_oracle_queries: u32,
321 pub http_requests: u32,
323 }
326
327#[derive(Clone, Debug, Deserialize, Serialize, WitLoad, WitType)]
329#[cfg_attr(with_testing, derive(Eq, PartialEq, WitStore))]
330#[witty_specialize_with(Message = Vec<u8>)]
331pub struct SendMessageRequest<Message> {
332 pub destination: ChainId,
334 pub authenticated: bool,
336 pub is_tracked: bool,
338 pub grant: Resources,
340 pub message: Message,
342}
343
344impl<Message> SendMessageRequest<Message>
345where
346 Message: Serialize,
347{
348 pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
350 let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
351
352 SendMessageRequest {
353 destination: self.destination,
354 authenticated: self.authenticated,
355 is_tracked: self.is_tracked,
356 grant: self.grant,
357 message,
358 }
359 }
360}
361
362#[derive(Debug, Error)]
364#[allow(missing_docs)]
365pub enum ArithmeticError {
366 #[error("Number overflow")]
367 Overflow,
368 #[error("Number underflow")]
369 Underflow,
370}
371
372macro_rules! impl_wrapped_number {
373 ($name:ident, $wrapped:ident) => {
374 impl $name {
375 pub const ZERO: Self = Self(0);
377
378 pub const MAX: Self = Self($wrapped::MAX);
380
381 pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
383 let val = self
384 .0
385 .checked_add(other.0)
386 .ok_or(ArithmeticError::Overflow)?;
387 Ok(Self(val))
388 }
389
390 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
392 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
393 Ok(Self(val))
394 }
395
396 pub const fn saturating_add(self, other: Self) -> Self {
398 let val = self.0.saturating_add(other.0);
399 Self(val)
400 }
401
402 pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
404 let val = self
405 .0
406 .checked_sub(other.0)
407 .ok_or(ArithmeticError::Underflow)?;
408 Ok(Self(val))
409 }
410
411 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
413 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
414 Ok(Self(val))
415 }
416
417 pub const fn saturating_sub(self, other: Self) -> Self {
419 let val = self.0.saturating_sub(other.0);
420 Self(val)
421 }
422
423 pub fn abs_diff(self, other: Self) -> Self {
425 Self(self.0.abs_diff(other.0))
426 }
427
428 pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
430 self.0 = self
431 .0
432 .checked_add(other.0)
433 .ok_or(ArithmeticError::Overflow)?;
434 Ok(())
435 }
436
437 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
439 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
440 Ok(())
441 }
442
443 pub const fn saturating_add_assign(&mut self, other: Self) {
445 self.0 = self.0.saturating_add(other.0);
446 }
447
448 pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
450 self.0 = self
451 .0
452 .checked_sub(other.0)
453 .ok_or(ArithmeticError::Underflow)?;
454 Ok(())
455 }
456
457 pub fn saturating_div(&self, other: $wrapped) -> Self {
459 Self(self.0.checked_div(other).unwrap_or($wrapped::MAX))
460 }
461
462 pub const fn saturating_mul(&self, other: $wrapped) -> Self {
464 Self(self.0.saturating_mul(other))
465 }
466
467 pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
469 let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
470 Ok(Self(val))
471 }
472
473 pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
475 self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
476 Ok(())
477 }
478 }
479
480 impl From<$name> for $wrapped {
481 fn from(value: $name) -> Self {
482 value.0
483 }
484 }
485
486 #[cfg(with_testing)]
488 impl From<$wrapped> for $name {
489 fn from(value: $wrapped) -> Self {
490 Self(value)
491 }
492 }
493
494 #[cfg(with_testing)]
495 impl ops::Add for $name {
496 type Output = Self;
497
498 fn add(self, other: Self) -> Self {
499 Self(self.0 + other.0)
500 }
501 }
502
503 #[cfg(with_testing)]
504 impl ops::Sub for $name {
505 type Output = Self;
506
507 fn sub(self, other: Self) -> Self {
508 Self(self.0 - other.0)
509 }
510 }
511
512 #[cfg(with_testing)]
513 impl ops::Mul<$wrapped> for $name {
514 type Output = Self;
515
516 fn mul(self, other: $wrapped) -> Self {
517 Self(self.0 * other)
518 }
519 }
520 };
521}
522
523impl TryFrom<BlockHeight> for usize {
524 type Error = ArithmeticError;
525
526 fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
527 usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
528 }
529}
530
531impl_wrapped_number!(Amount, u128);
532impl_wrapped_number!(BlockHeight, u64);
533impl_wrapped_number!(TimeDelta, u64);
534
535impl Display for Amount {
536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537 let places = Amount::DECIMAL_PLACES as usize;
539 let min_digits = places + 1;
540 let decimals = format!("{:0min_digits$}", self.0);
541 let integer_part = &decimals[..(decimals.len() - places)];
542 let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
543
544 let precision = f.precision().unwrap_or(0).max(fractional_part.len());
546 let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
547 let pad_width = f.width().map_or(0, |w| {
549 w.saturating_sub(precision)
550 .saturating_sub(sign.len() + integer_part.len() + 1)
551 });
552 let left_pad = match f.align() {
553 None | Some(fmt::Alignment::Right) => pad_width,
554 Some(fmt::Alignment::Center) => pad_width / 2,
555 Some(fmt::Alignment::Left) => 0,
556 };
557
558 for _ in 0..left_pad {
559 write!(f, "{}", f.fill())?;
560 }
561 write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
562 for _ in left_pad..pad_width {
563 write!(f, "{}", f.fill())?;
564 }
565 Ok(())
566 }
567}
568
569#[derive(Error, Debug)]
570#[allow(missing_docs)]
571pub enum ParseAmountError {
572 #[error("cannot parse amount")]
573 Parse,
574 #[error("cannot represent amount: number too high")]
575 TooHigh,
576 #[error("cannot represent amount: too many decimal places after the point")]
577 TooManyDigits,
578}
579
580impl FromStr for Amount {
581 type Err = ParseAmountError;
582
583 fn from_str(src: &str) -> Result<Self, Self::Err> {
584 let mut result: u128 = 0;
585 let mut decimals: Option<u8> = None;
586 let mut chars = src.trim().chars().peekable();
587 if chars.peek() == Some(&'+') {
588 chars.next();
589 }
590 for char in chars {
591 match char {
592 '_' => {}
593 '.' if decimals.is_some() => return Err(ParseAmountError::Parse),
594 '.' => decimals = Some(Amount::DECIMAL_PLACES),
595 char => {
596 let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
597 if let Some(d) = &mut decimals {
598 *d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
599 }
600 result = result
601 .checked_mul(10)
602 .and_then(|r| r.checked_add(digit))
603 .ok_or(ParseAmountError::TooHigh)?;
604 }
605 }
606 }
607 result = result
608 .checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
609 .ok_or(ParseAmountError::TooHigh)?;
610 Ok(Amount(result))
611 }
612}
613
614impl Display for BlockHeight {
615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616 self.0.fmt(f)
617 }
618}
619
620impl FromStr for BlockHeight {
621 type Err = ParseIntError;
622
623 fn from_str(src: &str) -> Result<Self, Self::Err> {
624 Ok(Self(u64::from_str(src)?))
625 }
626}
627
628impl Display for Round {
629 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630 match self {
631 Round::Fast => write!(f, "fast round"),
632 Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
633 Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
634 Round::Validator(r) => write!(f, "validator round {}", r),
635 }
636 }
637}
638
639impl Round {
640 pub fn is_multi_leader(&self) -> bool {
642 matches!(self, Round::MultiLeader(_))
643 }
644
645 pub fn multi_leader(&self) -> Option<u32> {
647 match self {
648 Round::MultiLeader(number) => Some(*number),
649 _ => None,
650 }
651 }
652
653 pub fn is_fast(&self) -> bool {
655 matches!(self, Round::Fast)
656 }
657
658 pub fn number(&self) -> u32 {
660 match self {
661 Round::Fast => 0,
662 Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
663 }
664 }
665
666 pub fn type_name(&self) -> &'static str {
668 match self {
669 Round::Fast => "fast",
670 Round::MultiLeader(_) => "multi",
671 Round::SingleLeader(_) => "single",
672 Round::Validator(_) => "validator",
673 }
674 }
675}
676
677impl<'a> iter::Sum<&'a Amount> for Amount {
678 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
679 iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
680 }
681}
682
683impl Amount {
684 pub const DECIMAL_PLACES: u8 = 18;
686
687 pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
689
690 pub const fn from_tokens(tokens: u128) -> Amount {
692 Self::ONE.saturating_mul(tokens)
693 }
694
695 pub const fn from_millis(millitokens: u128) -> Amount {
697 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
698 }
699
700 pub const fn from_micros(microtokens: u128) -> Amount {
702 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
703 }
704
705 pub const fn from_nanos(nanotokens: u128) -> Amount {
707 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
708 }
709
710 pub const fn from_attos(attotokens: u128) -> Amount {
712 Amount(attotokens)
713 }
714
715 pub const fn to_attos(self) -> u128 {
717 self.0
718 }
719
720 pub const fn upper_half(self) -> u64 {
722 (self.0 >> 64) as u64
723 }
724
725 pub const fn lower_half(self) -> u64 {
727 self.0 as u64
728 }
729
730 pub fn saturating_ratio(self, other: Amount) -> u128 {
732 self.0.checked_div(other.0).unwrap_or(u128::MAX)
733 }
734
735 pub fn is_zero(&self) -> bool {
737 *self == Amount::ZERO
738 }
739}
740
741#[derive(
743 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize, Allocative,
744)]
745pub enum ChainOrigin {
746 Root(u32),
748 Child {
750 parent: ChainId,
752 block_height: BlockHeight,
754 chain_index: u32,
757 },
758}
759
760impl ChainOrigin {
761 pub fn is_child(&self) -> bool {
763 matches!(self, ChainOrigin::Child { .. })
764 }
765
766 pub fn root(&self) -> Option<u32> {
768 match self {
769 ChainOrigin::Root(i) => Some(*i),
770 ChainOrigin::Child { .. } => None,
771 }
772 }
773}
774
775#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Allocative)]
777pub struct Epoch(pub u32);
778
779impl Epoch {
780 pub const ZERO: Epoch = Epoch(0);
782}
783
784impl Serialize for Epoch {
785 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
786 where
787 S: serde::ser::Serializer,
788 {
789 if serializer.is_human_readable() {
790 serializer.serialize_str(&self.0.to_string())
791 } else {
792 serializer.serialize_newtype_struct("Epoch", &self.0)
793 }
794 }
795}
796
797impl<'de> Deserialize<'de> for Epoch {
798 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
799 where
800 D: serde::de::Deserializer<'de>,
801 {
802 if deserializer.is_human_readable() {
803 let s = String::deserialize(deserializer)?;
804 Ok(Epoch(u32::from_str(&s).map_err(serde::de::Error::custom)?))
805 } else {
806 #[derive(Deserialize)]
807 #[serde(rename = "Epoch")]
808 struct EpochDerived(u32);
809
810 let value = EpochDerived::deserialize(deserializer)?;
811 Ok(Self(value.0))
812 }
813 }
814}
815
816impl std::fmt::Display for Epoch {
817 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
818 write!(f, "{}", self.0)
819 }
820}
821
822impl std::str::FromStr for Epoch {
823 type Err = CryptoError;
824
825 fn from_str(s: &str) -> Result<Self, Self::Err> {
826 Ok(Epoch(s.parse()?))
827 }
828}
829
830impl From<u32> for Epoch {
831 fn from(value: u32) -> Self {
832 Epoch(value)
833 }
834}
835
836impl Epoch {
837 #[inline]
840 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
841 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
842 Ok(Self(val))
843 }
844
845 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
848 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
849 Ok(Self(val))
850 }
851
852 #[inline]
854 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
855 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
856 Ok(())
857 }
858}
859
860#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
862pub struct InitialChainConfig {
863 pub ownership: ChainOwnership,
865 pub epoch: Epoch,
867 pub min_active_epoch: Epoch,
869 pub max_active_epoch: Epoch,
871 pub balance: Amount,
873 pub application_permissions: ApplicationPermissions,
875}
876
877#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize, Allocative)]
879pub struct ChainDescription {
880 origin: ChainOrigin,
881 timestamp: Timestamp,
882 config: InitialChainConfig,
883}
884
885impl ChainDescription {
886 pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self {
888 Self {
889 origin,
890 config,
891 timestamp,
892 }
893 }
894
895 pub fn id(&self) -> ChainId {
897 ChainId::from(self)
898 }
899
900 pub fn origin(&self) -> ChainOrigin {
902 self.origin
903 }
904
905 pub fn config(&self) -> &InitialChainConfig {
907 &self.config
908 }
909
910 pub fn timestamp(&self) -> Timestamp {
912 self.timestamp
913 }
914
915 pub fn is_child(&self) -> bool {
917 self.origin.is_child()
918 }
919}
920
921impl BcsHashable<'_> for ChainDescription {}
922
923#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
925pub struct NetworkDescription {
926 pub name: String,
928 pub genesis_config_hash: CryptoHash,
930 pub genesis_timestamp: Timestamp,
932 pub genesis_committee_blob_hash: CryptoHash,
934 pub admin_chain_id: ChainId,
936}
937
938#[derive(
940 Default,
941 Debug,
942 PartialEq,
943 Eq,
944 PartialOrd,
945 Ord,
946 Hash,
947 Clone,
948 Serialize,
949 Deserialize,
950 WitType,
951 WitLoad,
952 WitStore,
953 InputObject,
954 Allocative,
955)]
956pub struct ApplicationPermissions {
957 #[debug(skip_if = Option::is_none)]
961 pub execute_operations: Option<Vec<ApplicationId>>,
962 #[graphql(default)]
965 #[debug(skip_if = Vec::is_empty)]
966 pub mandatory_applications: Vec<ApplicationId>,
967 #[graphql(default)]
969 #[debug(skip_if = Vec::is_empty)]
970 pub close_chain: Vec<ApplicationId>,
971 #[graphql(default)]
973 #[debug(skip_if = Vec::is_empty)]
974 pub change_application_permissions: Vec<ApplicationId>,
975 #[graphql(default)]
977 #[debug(skip_if = Option::is_none)]
978 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
979 #[graphql(default)]
981 #[debug(skip_if = Option::is_none)]
982 pub make_http_requests: Option<Vec<ApplicationId>>,
983}
984
985impl ApplicationPermissions {
986 pub fn new_single(app_id: ApplicationId) -> Self {
989 Self {
990 execute_operations: Some(vec![app_id]),
991 mandatory_applications: vec![app_id],
992 close_chain: vec![app_id],
993 change_application_permissions: vec![app_id],
994 call_service_as_oracle: Some(vec![app_id]),
995 make_http_requests: Some(vec![app_id]),
996 }
997 }
998
999 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1002 Self {
1003 execute_operations: Some(app_ids.clone()),
1004 mandatory_applications: app_ids.clone(),
1005 close_chain: app_ids.clone(),
1006 change_application_permissions: app_ids.clone(),
1007 call_service_as_oracle: Some(app_ids.clone()),
1008 make_http_requests: Some(app_ids),
1009 }
1010 }
1011
1012 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1014 match (app_id, &self.execute_operations) {
1015 (_, None) => true,
1016 (GenericApplicationId::System, Some(_)) => false,
1017 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1018 }
1019 }
1020
1021 pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
1023 self.close_chain.contains(app_id)
1024 }
1025
1026 pub fn can_change_application_permissions(&self, app_id: &ApplicationId) -> bool {
1029 self.change_application_permissions.contains(app_id)
1030 }
1031
1032 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1034 self.call_service_as_oracle
1035 .as_ref()
1036 .is_none_or(|app_ids| app_ids.contains(app_id))
1037 }
1038
1039 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1041 self.make_http_requests
1042 .as_ref()
1043 .is_none_or(|app_ids| app_ids.contains(app_id))
1044 }
1045}
1046
1047#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1049pub enum OracleResponse {
1050 Service(
1052 #[debug(with = "hex_debug")]
1053 #[serde(with = "serde_bytes")]
1054 Vec<u8>,
1055 ),
1056 Http(http::Response),
1058 Blob(BlobId),
1060 Assert,
1062 Round(Option<u32>),
1064 Event(
1066 EventId,
1067 #[debug(with = "hex_debug")]
1068 #[serde(with = "serde_bytes")]
1069 Vec<u8>,
1070 ),
1071 EventExists(EventId),
1073}
1074
1075impl BcsHashable<'_> for OracleResponse {}
1076
1077#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
1079pub struct ApplicationDescription {
1080 pub module_id: ModuleId,
1082 pub creator_chain_id: ChainId,
1084 pub block_height: BlockHeight,
1086 pub application_index: u32,
1088 #[serde(with = "serde_bytes")]
1090 #[debug(with = "hex_debug")]
1091 pub parameters: Vec<u8>,
1092 pub required_application_ids: Vec<ApplicationId>,
1094}
1095
1096impl From<&ApplicationDescription> for ApplicationId {
1097 fn from(description: &ApplicationDescription) -> Self {
1098 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1099 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1100 hash.make_evm_compatible();
1101 }
1102 ApplicationId::new(hash)
1103 }
1104}
1105
1106impl BcsHashable<'_> for ApplicationDescription {}
1107
1108impl ApplicationDescription {
1109 pub fn to_bytes(&self) -> Vec<u8> {
1111 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1112 }
1113
1114 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1116 self.module_id.contract_bytecode_blob_id()
1117 }
1118
1119 pub fn service_bytecode_blob_id(&self) -> BlobId {
1121 self.module_id.service_bytecode_blob_id()
1122 }
1123}
1124
1125#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1127pub struct Bytecode {
1128 #[serde(with = "serde_bytes")]
1130 #[debug(with = "hex_debug")]
1131 pub bytes: Vec<u8>,
1132}
1133
1134impl Bytecode {
1135 pub fn new(bytes: Vec<u8>) -> Self {
1137 Bytecode { bytes }
1138 }
1139
1140 pub fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1142 let bytes = fs::read(path)?;
1143 Ok(Bytecode { bytes })
1144 }
1145
1146 #[cfg(not(target_arch = "wasm32"))]
1148 pub fn compress(&self) -> CompressedBytecode {
1149 #[cfg(with_metrics)]
1150 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1151 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1152 .expect("Compressing bytes in memory should not fail");
1153
1154 CompressedBytecode {
1155 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1156 }
1157 }
1158
1159 #[cfg(target_arch = "wasm32")]
1161 pub fn compress(&self) -> CompressedBytecode {
1162 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1163
1164 #[cfg(with_metrics)]
1165 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1166
1167 let mut compressed_bytes_vec = Vec::new();
1168 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1169 compressor.set_source(&*self.bytes);
1170 compressor.set_drain(&mut compressed_bytes_vec);
1171 compressor.compress();
1172
1173 CompressedBytecode {
1174 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1175 }
1176 }
1177}
1178
1179impl AsRef<[u8]> for Bytecode {
1180 fn as_ref(&self) -> &[u8] {
1181 self.bytes.as_ref()
1182 }
1183}
1184
1185#[derive(Error, Debug)]
1187pub enum DecompressionError {
1188 #[error("Bytecode could not be decompressed: {0}")]
1190 InvalidCompressedBytecode(#[from] io::Error),
1191}
1192
1193#[serde_as]
1195#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1196#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1197pub struct CompressedBytecode {
1198 #[serde_as(as = "Arc<Bytes>")]
1200 #[debug(skip)]
1201 pub compressed_bytes: Arc<Box<[u8]>>,
1202}
1203
1204#[cfg(not(target_arch = "wasm32"))]
1205impl CompressedBytecode {
1206 pub fn decompressed_size_at_most(
1208 compressed_bytes: &[u8],
1209 limit: u64,
1210 ) -> Result<bool, DecompressionError> {
1211 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1212 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1213 let mut writer = LimitedWriter::new(io::sink(), limit);
1214 match io::copy(&mut decoder, &mut writer) {
1215 Ok(_) => Ok(true),
1216 Err(error) => {
1217 error.downcast::<LimitedWriterError>()?;
1218 Ok(false)
1219 }
1220 }
1221 }
1222
1223 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1225 #[cfg(with_metrics)]
1226 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1227 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1228
1229 Ok(Bytecode { bytes })
1230 }
1231}
1232
1233#[cfg(target_arch = "wasm32")]
1234impl CompressedBytecode {
1235 pub fn decompressed_size_at_most(
1237 compressed_bytes: &[u8],
1238 limit: u64,
1239 ) -> Result<bool, DecompressionError> {
1240 use ruzstd::decoding::StreamingDecoder;
1241 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1242 let mut writer = LimitedWriter::new(io::sink(), limit);
1243 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1244
1245 match io::copy(&mut decoder, &mut writer) {
1247 Ok(_) => Ok(true),
1248 Err(error) => {
1249 error.downcast::<LimitedWriterError>()?;
1250 Ok(false)
1251 }
1252 }
1253 }
1254
1255 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1257 use ruzstd::{decoding::StreamingDecoder, io::Read};
1258
1259 #[cfg(with_metrics)]
1260 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1261
1262 let compressed_bytes = &*self.compressed_bytes;
1263 let mut bytes = Vec::new();
1264 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1265
1266 while !decoder.get_ref().is_empty() {
1268 decoder
1269 .read_to_end(&mut bytes)
1270 .expect("Reading from a slice in memory should not result in I/O errors");
1271 }
1272
1273 Ok(Bytecode { bytes })
1274 }
1275}
1276
1277impl BcsHashable<'_> for BlobContent {}
1278
1279#[serde_as]
1281#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1282pub struct BlobContent {
1283 blob_type: BlobType,
1285 #[debug(skip)]
1287 #[serde_as(as = "Arc<Bytes>")]
1288 bytes: Arc<Box<[u8]>>,
1289}
1290
1291impl BlobContent {
1292 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1294 let bytes = bytes.into();
1295 BlobContent {
1296 blob_type,
1297 bytes: Arc::new(bytes),
1298 }
1299 }
1300
1301 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1303 BlobContent::new(BlobType::Data, bytes)
1304 }
1305
1306 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1308 BlobContent {
1309 blob_type: BlobType::ContractBytecode,
1310 bytes: compressed_bytecode.compressed_bytes,
1311 }
1312 }
1313
1314 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1316 BlobContent {
1317 blob_type: BlobType::EvmBytecode,
1318 bytes: compressed_bytecode.compressed_bytes,
1319 }
1320 }
1321
1322 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1324 BlobContent {
1325 blob_type: BlobType::ServiceBytecode,
1326 bytes: compressed_bytecode.compressed_bytes,
1327 }
1328 }
1329
1330 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1332 let bytes = application_description.to_bytes();
1333 BlobContent::new(BlobType::ApplicationDescription, bytes)
1334 }
1335
1336 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1338 BlobContent::new(BlobType::Committee, committee)
1339 }
1340
1341 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1343 let bytes = bcs::to_bytes(&chain_description)
1344 .expect("Serializing a ChainDescription should not fail!");
1345 BlobContent::new(BlobType::ChainDescription, bytes)
1346 }
1347
1348 pub fn bytes(&self) -> &[u8] {
1350 &self.bytes
1351 }
1352
1353 pub fn into_vec_or_clone(self) -> Vec<u8> {
1355 let bytes = Arc::unwrap_or_clone(self.bytes);
1356 bytes.into_vec()
1357 }
1358
1359 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1361 self.bytes
1362 }
1363
1364 pub fn blob_type(&self) -> BlobType {
1366 self.blob_type
1367 }
1368}
1369
1370impl From<Blob> for BlobContent {
1371 fn from(blob: Blob) -> BlobContent {
1372 blob.content
1373 }
1374}
1375
1376#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1378pub struct Blob {
1379 hash: CryptoHash,
1381 content: BlobContent,
1383}
1384
1385impl Blob {
1386 pub fn new(content: BlobContent) -> Self {
1388 let mut hash = CryptoHash::new(&content);
1389 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1390 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1391 .expect("to obtain an application description");
1392 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1393 hash.make_evm_compatible();
1394 }
1395 }
1396 Blob { hash, content }
1397 }
1398
1399 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1401 Blob {
1402 hash: blob_id.hash,
1403 content,
1404 }
1405 }
1406
1407 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1409 let bytes = bytes.into();
1410 Blob {
1411 hash: blob_id.hash,
1412 content: BlobContent {
1413 blob_type: blob_id.blob_type,
1414 bytes: Arc::new(bytes),
1415 },
1416 }
1417 }
1418
1419 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1421 Blob::new(BlobContent::new_data(bytes))
1422 }
1423
1424 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1426 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1427 }
1428
1429 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1431 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1432 }
1433
1434 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1436 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1437 }
1438
1439 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1441 Blob::new(BlobContent::new_application_description(
1442 application_description,
1443 ))
1444 }
1445
1446 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1448 Blob::new(BlobContent::new_committee(committee))
1449 }
1450
1451 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1453 Blob::new(BlobContent::new_chain_description(chain_description))
1454 }
1455
1456 pub fn id(&self) -> BlobId {
1458 BlobId {
1459 hash: self.hash,
1460 blob_type: self.content.blob_type,
1461 }
1462 }
1463
1464 pub fn content(&self) -> &BlobContent {
1466 &self.content
1467 }
1468
1469 pub fn into_content(self) -> BlobContent {
1471 self.content
1472 }
1473
1474 pub fn bytes(&self) -> &[u8] {
1476 self.content.bytes()
1477 }
1478
1479 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1481 Ok(Self::new_data(fs::read(path)?))
1482 }
1483
1484 pub fn is_committee_blob(&self) -> bool {
1486 self.content().blob_type().is_committee_blob()
1487 }
1488}
1489
1490impl Serialize for Blob {
1491 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1492 where
1493 S: Serializer,
1494 {
1495 if serializer.is_human_readable() {
1496 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1497 serializer.serialize_str(&hex::encode(blob_bytes))
1498 } else {
1499 BlobContent::serialize(self.content(), serializer)
1500 }
1501 }
1502}
1503
1504impl<'a> Deserialize<'a> for Blob {
1505 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1506 where
1507 D: Deserializer<'a>,
1508 {
1509 if deserializer.is_human_readable() {
1510 let s = String::deserialize(deserializer)?;
1511 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1512 let content: BlobContent =
1513 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1514
1515 Ok(Blob::new(content))
1516 } else {
1517 let content = BlobContent::deserialize(deserializer)?;
1518 Ok(Blob::new(content))
1519 }
1520 }
1521}
1522
1523impl BcsHashable<'_> for Blob {}
1524
1525#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1527pub struct Event {
1528 pub stream_id: StreamId,
1530 pub index: u32,
1532 #[debug(with = "hex_debug")]
1534 #[serde(with = "serde_bytes")]
1535 pub value: Vec<u8>,
1536}
1537
1538impl Event {
1539 pub fn id(&self, chain_id: ChainId) -> EventId {
1541 EventId {
1542 chain_id,
1543 stream_id: self.stream_id.clone(),
1544 index: self.index,
1545 }
1546 }
1547}
1548
1549#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1551pub struct StreamUpdate {
1552 pub chain_id: ChainId,
1554 pub stream_id: StreamId,
1556 pub previous_index: u32,
1558 pub next_index: u32,
1560}
1561
1562impl StreamUpdate {
1563 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1565 self.previous_index..self.next_index
1566 }
1567}
1568
1569impl BcsHashable<'_> for Event {}
1570
1571doc_scalar!(Bytecode, "A WebAssembly module's bytecode");
1572doc_scalar!(Amount, "A non-negative amount of tokens.");
1573doc_scalar!(
1574 Epoch,
1575 "A number identifying the configuration of the chain (aka the committee)"
1576);
1577doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1578doc_scalar!(
1579 Timestamp,
1580 "A timestamp, in microseconds since the Unix epoch"
1581);
1582doc_scalar!(TimeDelta, "A duration in microseconds");
1583doc_scalar!(
1584 Round,
1585 "A number to identify successive attempts to decide a value in a consensus protocol."
1586);
1587doc_scalar!(
1588 ChainDescription,
1589 "Initial chain configuration and chain origin."
1590);
1591doc_scalar!(OracleResponse, "A record of a single oracle response.");
1592doc_scalar!(BlobContent, "A blob of binary data.");
1593doc_scalar!(
1594 Blob,
1595 "A blob of binary data, with its content-addressed blob ID."
1596);
1597doc_scalar!(ApplicationDescription, "Description of a user application");
1598
1599#[cfg(with_metrics)]
1600mod metrics {
1601 use std::sync::LazyLock;
1602
1603 use prometheus::HistogramVec;
1604
1605 use crate::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
1606
1607 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1609 register_histogram_vec(
1610 "bytecode_compression_latency",
1611 "Bytecode compression latency",
1612 &[],
1613 exponential_bucket_latencies(10.0),
1614 )
1615 });
1616
1617 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1619 register_histogram_vec(
1620 "bytecode_decompression_latency",
1621 "Bytecode decompression latency",
1622 &[],
1623 exponential_bucket_latencies(10.0),
1624 )
1625 });
1626}
1627
1628#[cfg(test)]
1629mod tests {
1630 use std::str::FromStr;
1631
1632 use super::{Amount, BlobContent};
1633 use crate::identifiers::BlobType;
1634
1635 #[test]
1636 fn display_amount() {
1637 assert_eq!("1.", Amount::ONE.to_string());
1638 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1639 assert_eq!(
1640 Amount(10_000_000_000_000_000_000),
1641 Amount::from_str("10").unwrap()
1642 );
1643 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1644 assert_eq!(
1645 "1001.3",
1646 (Amount::from_str("1.1")
1647 .unwrap()
1648 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1649 .to_string()
1650 );
1651 assert_eq!(
1652 " 1.00000000000000000000",
1653 format!("{:25.20}", Amount::ONE)
1654 );
1655 assert_eq!(
1656 "~+12.34~~",
1657 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1658 );
1659 }
1660
1661 #[test]
1662 fn blob_content_serialization_deserialization() {
1663 let test_data = b"Hello, world!".as_slice();
1664 let original_blob = BlobContent::new(BlobType::Data, test_data);
1665
1666 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1667 let deserialized: BlobContent =
1668 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1669 assert_eq!(original_blob, deserialized);
1670
1671 let serialized =
1672 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1673 let deserialized: BlobContent =
1674 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1675 assert_eq!(original_blob, deserialized);
1676 }
1677
1678 #[test]
1679 fn blob_content_hash_consistency() {
1680 let test_data = b"Hello, world!";
1681 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1682 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1683
1684 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1686 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1687
1688 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1689 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1690 }
1691}