1#[cfg(with_testing)]
8use std::ops;
9use std::{
10 collections::HashSet,
11 fmt::{self, Display},
12 fs,
13 hash::Hash,
14 io, iter,
15 num::ParseIntError,
16 path::Path,
17 str::FromStr,
18 sync::Arc,
19};
20
21use allocative::{Allocative, Visitor};
22use alloy_primitives::U256;
23use async_graphql::{InputObject, SimpleObject};
24use custom_debug_derive::Debug;
25use linera_witty::{WitLoad, WitStore, WitType};
26use serde::{Deserialize, Deserializer, Serialize, Serializer};
27use serde_with::{serde_as, Bytes};
28use thiserror::Error;
29use tracing::instrument;
30
31#[cfg(with_metrics)]
32use crate::prometheus_util::MeasureLatency as _;
33use crate::{
34 crypto::{BcsHashable, CryptoError, CryptoHash},
35 doc_scalar, hex_debug, http,
36 identifiers::{
37 ApplicationId, BlobId, BlobType, ChainId, EventId, GenericApplicationId, ModuleId, StreamId,
38 },
39 limited_writer::{LimitedWriter, LimitedWriterError},
40 ownership::ChainOwnership,
41 time::{Duration, SystemTime},
42 vm::VmRuntime,
43};
44
45#[derive(
50 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, WitType, WitLoad, WitStore,
51)]
52#[cfg_attr(
53 all(with_testing, not(target_arch = "wasm32")),
54 derive(test_strategy::Arbitrary)
55)]
56pub struct Amount(u128);
57
58impl Allocative for Amount {
59 fn visit<'a, 'b: 'a>(&self, visitor: &'a mut Visitor<'b>) {
60 visitor.visit_simple_sized::<Self>();
61 }
62}
63
64#[derive(Serialize, Deserialize)]
65#[serde(rename = "Amount")]
66struct AmountString(String);
67
68#[derive(Serialize, Deserialize)]
69#[serde(rename = "Amount")]
70struct AmountU128(u128);
71
72impl Serialize for Amount {
73 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
74 if serializer.is_human_readable() {
75 AmountString(self.to_string()).serialize(serializer)
76 } else {
77 AmountU128(self.0).serialize(serializer)
78 }
79 }
80}
81
82impl<'de> Deserialize<'de> for Amount {
83 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
84 if deserializer.is_human_readable() {
85 let AmountString(s) = AmountString::deserialize(deserializer)?;
86 s.parse().map_err(serde::de::Error::custom)
87 } else {
88 Ok(Amount(AmountU128::deserialize(deserializer)?.0))
89 }
90 }
91}
92
93impl From<Amount> for U256 {
94 fn from(amount: Amount) -> U256 {
95 U256::from(amount.0)
96 }
97}
98
99impl TryFrom<U256> for Amount {
100 type Error = ArithmeticError;
101
102 fn try_from(value: U256) -> Result<Amount, ArithmeticError> {
103 let value: u128 = value.try_into().map_err(|_| ArithmeticError::Overflow)?;
104 Ok(Amount::from_attos(value))
105 }
106}
107
108#[derive(
110 Eq,
111 PartialEq,
112 Ord,
113 PartialOrd,
114 Copy,
115 Clone,
116 Hash,
117 Default,
118 Debug,
119 Serialize,
120 Deserialize,
121 WitType,
122 WitLoad,
123 WitStore,
124 Allocative,
125)]
126#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
127pub struct BlockHeight(pub u64);
128
129#[derive(
131 Eq,
132 PartialEq,
133 Ord,
134 PartialOrd,
135 Copy,
136 Clone,
137 Hash,
138 Default,
139 Debug,
140 Serialize,
141 Deserialize,
142 Allocative,
143)]
144#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
145pub enum Round {
146 #[default]
148 Fast,
149 MultiLeader(u32),
151 SingleLeader(u32),
153 Validator(u32),
155}
156
157#[derive(
159 Eq,
160 PartialEq,
161 Ord,
162 PartialOrd,
163 Copy,
164 Clone,
165 Hash,
166 Default,
167 Debug,
168 Serialize,
169 Deserialize,
170 WitType,
171 WitLoad,
172 WitStore,
173 Allocative,
174)]
175pub struct TimeDelta(u64);
176
177impl TimeDelta {
178 pub const fn from_micros(micros: u64) -> Self {
180 TimeDelta(micros)
181 }
182
183 pub const fn from_millis(millis: u64) -> Self {
185 TimeDelta(millis.saturating_mul(1_000))
186 }
187
188 pub const fn from_secs(secs: u64) -> Self {
190 TimeDelta(secs.saturating_mul(1_000_000))
191 }
192
193 pub fn from_duration(duration: Duration) -> Self {
196 TimeDelta::from_micros(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX))
197 }
198
199 pub const fn as_micros(&self) -> u64 {
201 self.0
202 }
203
204 pub const fn as_duration(&self) -> Duration {
206 Duration::from_micros(self.as_micros())
207 }
208}
209
210#[derive(
212 Eq,
213 PartialEq,
214 Ord,
215 PartialOrd,
216 Copy,
217 Clone,
218 Hash,
219 Default,
220 Debug,
221 Serialize,
222 Deserialize,
223 WitType,
224 WitLoad,
225 WitStore,
226 Allocative,
227)]
228pub struct Timestamp(u64);
229
230impl Timestamp {
231 pub fn now() -> Timestamp {
233 Timestamp(
234 SystemTime::UNIX_EPOCH
235 .elapsed()
236 .expect("system time should be after Unix epoch")
237 .as_micros()
238 .try_into()
239 .unwrap_or(u64::MAX),
240 )
241 }
242
243 pub const fn micros(&self) -> u64 {
245 self.0
246 }
247
248 pub const fn delta_since(&self, other: Timestamp) -> TimeDelta {
251 TimeDelta::from_micros(self.0.saturating_sub(other.0))
252 }
253
254 pub const fn duration_since(&self, other: Timestamp) -> Duration {
257 Duration::from_micros(self.0.saturating_sub(other.0))
258 }
259
260 pub const fn saturating_add(&self, duration: TimeDelta) -> Timestamp {
262 Timestamp(self.0.saturating_add(duration.0))
263 }
264
265 pub const fn saturating_sub(&self, duration: TimeDelta) -> Timestamp {
267 Timestamp(self.0.saturating_sub(duration.0))
268 }
269
270 pub const fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
273 Timestamp(self.0.saturating_sub(micros))
274 }
275}
276
277impl From<u64> for Timestamp {
278 fn from(t: u64) -> Timestamp {
279 Timestamp(t)
280 }
281}
282
283impl Display for Timestamp {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 if let Some(date_time) = chrono::DateTime::from_timestamp(
286 (self.0 / 1_000_000) as i64,
287 ((self.0 % 1_000_000) * 1_000) as u32,
288 ) {
289 return date_time.naive_utc().fmt(f);
290 }
291 self.0.fmt(f)
292 }
293}
294
295impl FromStr for Timestamp {
296 type Err = chrono::ParseError;
297
298 fn from_str(s: &str) -> Result<Self, Self::Err> {
299 let naive = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
300 .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S"))?;
301 let micros = naive
302 .and_utc()
303 .timestamp_micros()
304 .try_into()
305 .unwrap_or(u64::MAX);
306 Ok(Timestamp(micros))
307 }
308}
309
310#[derive(
313 Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, WitLoad, WitStore, WitType,
314)]
315pub struct Resources {
316 pub wasm_fuel: u64,
318 pub evm_fuel: u64,
320 pub read_operations: u32,
322 pub write_operations: u32,
324 pub bytes_runtime: u32,
326 pub bytes_to_read: u32,
328 pub bytes_to_write: u32,
330 pub blobs_to_read: u32,
332 pub blobs_to_publish: u32,
334 pub blob_bytes_to_read: u32,
336 pub blob_bytes_to_publish: u32,
338 pub messages: u32,
340 pub message_size: u32,
343 pub storage_size_delta: u32,
345 pub service_as_oracle_queries: u32,
347 pub http_requests: u32,
349 }
352
353#[derive(Clone, Debug, Deserialize, Serialize, WitLoad, WitType)]
355#[cfg_attr(with_testing, derive(Eq, PartialEq, WitStore))]
356#[witty_specialize_with(Message = Vec<u8>)]
357pub struct SendMessageRequest<Message> {
358 pub destination: ChainId,
360 pub authenticated: bool,
362 pub is_tracked: bool,
364 pub grant: Resources,
366 pub message: Message,
368}
369
370impl<Message> SendMessageRequest<Message>
371where
372 Message: Serialize,
373{
374 pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
376 let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
377
378 SendMessageRequest {
379 destination: self.destination,
380 authenticated: self.authenticated,
381 is_tracked: self.is_tracked,
382 grant: self.grant,
383 message,
384 }
385 }
386}
387
388#[derive(Debug, Error)]
390#[allow(missing_docs)]
391pub enum ArithmeticError {
392 #[error("Number overflow")]
393 Overflow,
394 #[error("Number underflow")]
395 Underflow,
396}
397
398macro_rules! impl_wrapped_number {
399 ($name:ident, $wrapped:ident) => {
400 impl $name {
401 pub const ZERO: Self = Self(0);
403
404 pub const MAX: Self = Self($wrapped::MAX);
406
407 pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
409 let val = self
410 .0
411 .checked_add(other.0)
412 .ok_or(ArithmeticError::Overflow)?;
413 Ok(Self(val))
414 }
415
416 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
418 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
419 Ok(Self(val))
420 }
421
422 pub const fn saturating_add(self, other: Self) -> Self {
424 let val = self.0.saturating_add(other.0);
425 Self(val)
426 }
427
428 pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
430 let val = self
431 .0
432 .checked_sub(other.0)
433 .ok_or(ArithmeticError::Underflow)?;
434 Ok(Self(val))
435 }
436
437 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
439 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
440 Ok(Self(val))
441 }
442
443 pub const fn saturating_sub(self, other: Self) -> Self {
445 let val = self.0.saturating_sub(other.0);
446 Self(val)
447 }
448
449 pub fn abs_diff(self, other: Self) -> Self {
451 Self(self.0.abs_diff(other.0))
452 }
453
454 pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
456 self.0 = self
457 .0
458 .checked_add(other.0)
459 .ok_or(ArithmeticError::Overflow)?;
460 Ok(())
461 }
462
463 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
465 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
466 Ok(())
467 }
468
469 pub const fn saturating_add_assign(&mut self, other: Self) {
471 self.0 = self.0.saturating_add(other.0);
472 }
473
474 pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
476 self.0 = self
477 .0
478 .checked_sub(other.0)
479 .ok_or(ArithmeticError::Underflow)?;
480 Ok(())
481 }
482
483 pub fn saturating_div(&self, other: $wrapped) -> Self {
485 Self(self.0.checked_div(other).unwrap_or($wrapped::MAX))
486 }
487
488 pub const fn saturating_mul(&self, other: $wrapped) -> Self {
490 Self(self.0.saturating_mul(other))
491 }
492
493 pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
495 let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
496 Ok(Self(val))
497 }
498
499 pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
501 self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
502 Ok(())
503 }
504 }
505
506 impl From<$name> for $wrapped {
507 fn from(value: $name) -> Self {
508 value.0
509 }
510 }
511
512 #[cfg(with_testing)]
514 impl From<$wrapped> for $name {
515 fn from(value: $wrapped) -> Self {
516 Self(value)
517 }
518 }
519
520 #[cfg(with_testing)]
521 impl ops::Add for $name {
522 type Output = Self;
523
524 fn add(self, other: Self) -> Self {
525 Self(self.0 + other.0)
526 }
527 }
528
529 #[cfg(with_testing)]
530 impl ops::Sub for $name {
531 type Output = Self;
532
533 fn sub(self, other: Self) -> Self {
534 Self(self.0 - other.0)
535 }
536 }
537
538 #[cfg(with_testing)]
539 impl ops::Mul<$wrapped> for $name {
540 type Output = Self;
541
542 fn mul(self, other: $wrapped) -> Self {
543 Self(self.0 * other)
544 }
545 }
546 };
547}
548
549impl TryFrom<BlockHeight> for usize {
550 type Error = ArithmeticError;
551
552 fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
553 usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
554 }
555}
556
557impl_wrapped_number!(Amount, u128);
558impl_wrapped_number!(BlockHeight, u64);
559impl_wrapped_number!(TimeDelta, u64);
560
561impl Display for Amount {
562 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563 let places = Amount::DECIMAL_PLACES as usize;
565 let min_digits = places + 1;
566 let decimals = format!("{:0min_digits$}", self.0);
567 let integer_part = &decimals[..(decimals.len() - places)];
568 let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
569
570 let precision = f.precision().unwrap_or(0).max(fractional_part.len());
572 let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
573 let pad_width = f.width().map_or(0, |w| {
575 w.saturating_sub(precision)
576 .saturating_sub(sign.len() + integer_part.len() + 1)
577 });
578 let left_pad = match f.align() {
579 None | Some(fmt::Alignment::Right) => pad_width,
580 Some(fmt::Alignment::Center) => pad_width / 2,
581 Some(fmt::Alignment::Left) => 0,
582 };
583
584 for _ in 0..left_pad {
585 write!(f, "{}", f.fill())?;
586 }
587 write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
588 for _ in left_pad..pad_width {
589 write!(f, "{}", f.fill())?;
590 }
591 Ok(())
592 }
593}
594
595#[derive(Error, Debug)]
596#[allow(missing_docs)]
597pub enum ParseAmountError {
598 #[error("cannot parse amount")]
599 Parse,
600 #[error("cannot represent amount: number too high")]
601 TooHigh,
602 #[error("cannot represent amount: too many decimal places after the point")]
603 TooManyDigits,
604}
605
606impl FromStr for Amount {
607 type Err = ParseAmountError;
608
609 fn from_str(src: &str) -> Result<Self, Self::Err> {
610 let mut result: u128 = 0;
611 let mut decimals: Option<u8> = None;
612 let mut chars = src.trim().chars().peekable();
613 if chars.peek() == Some(&'+') {
614 chars.next();
615 }
616 for char in chars {
617 match char {
618 '_' => {}
619 '.' if decimals.is_some() => return Err(ParseAmountError::Parse),
620 '.' => decimals = Some(Amount::DECIMAL_PLACES),
621 char => {
622 let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
623 if let Some(d) = &mut decimals {
624 *d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
625 }
626 result = result
627 .checked_mul(10)
628 .and_then(|r| r.checked_add(digit))
629 .ok_or(ParseAmountError::TooHigh)?;
630 }
631 }
632 }
633 result = result
634 .checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
635 .ok_or(ParseAmountError::TooHigh)?;
636 Ok(Amount(result))
637 }
638}
639
640impl Display for BlockHeight {
641 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642 self.0.fmt(f)
643 }
644}
645
646impl FromStr for BlockHeight {
647 type Err = ParseIntError;
648
649 fn from_str(src: &str) -> Result<Self, Self::Err> {
650 Ok(Self(u64::from_str(src)?))
651 }
652}
653
654impl Display for Round {
655 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656 match self {
657 Round::Fast => write!(f, "fast round"),
658 Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
659 Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
660 Round::Validator(r) => write!(f, "validator round {}", r),
661 }
662 }
663}
664
665impl Round {
666 pub fn is_multi_leader(&self) -> bool {
668 matches!(self, Round::MultiLeader(_))
669 }
670
671 pub fn multi_leader(&self) -> Option<u32> {
673 match self {
674 Round::MultiLeader(number) => Some(*number),
675 _ => None,
676 }
677 }
678
679 pub fn is_fast(&self) -> bool {
681 matches!(self, Round::Fast)
682 }
683
684 pub fn number(&self) -> u32 {
686 match self {
687 Round::Fast => 0,
688 Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
689 }
690 }
691
692 pub fn type_name(&self) -> &'static str {
694 match self {
695 Round::Fast => "fast",
696 Round::MultiLeader(_) => "multi",
697 Round::SingleLeader(_) => "single",
698 Round::Validator(_) => "validator",
699 }
700 }
701}
702
703impl<'a> iter::Sum<&'a Amount> for Amount {
704 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
705 iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
706 }
707}
708
709impl Amount {
710 pub const DECIMAL_PLACES: u8 = 18;
712
713 pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
715
716 pub const fn from_tokens(tokens: u128) -> Amount {
718 Self::ONE.saturating_mul(tokens)
719 }
720
721 pub const fn from_millis(millitokens: u128) -> Amount {
723 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
724 }
725
726 pub const fn from_micros(microtokens: u128) -> Amount {
728 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
729 }
730
731 pub const fn from_nanos(nanotokens: u128) -> Amount {
733 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
734 }
735
736 pub const fn from_attos(attotokens: u128) -> Amount {
738 Amount(attotokens)
739 }
740
741 pub const fn to_attos(self) -> u128 {
743 self.0
744 }
745
746 pub const fn upper_half(self) -> u64 {
748 (self.0 >> 64) as u64
749 }
750
751 pub const fn lower_half(self) -> u64 {
753 self.0 as u64
754 }
755
756 pub fn saturating_ratio(self, other: Amount) -> u128 {
758 self.0.checked_div(other.0).unwrap_or(u128::MAX)
759 }
760
761 pub fn is_zero(&self) -> bool {
763 *self == Amount::ZERO
764 }
765}
766
767#[derive(
769 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize, Allocative,
770)]
771pub enum ChainOrigin {
772 Root(u32),
774 Child {
776 parent: ChainId,
778 block_height: BlockHeight,
780 chain_index: u32,
783 },
784}
785
786impl ChainOrigin {
787 pub fn is_child(&self) -> bool {
789 matches!(self, ChainOrigin::Child { .. })
790 }
791
792 pub fn root(&self) -> Option<u32> {
794 match self {
795 ChainOrigin::Root(i) => Some(*i),
796 ChainOrigin::Child { .. } => None,
797 }
798 }
799}
800
801#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Allocative)]
803pub struct Epoch(pub u32);
804
805impl Epoch {
806 pub const ZERO: Epoch = Epoch(0);
808}
809
810impl Serialize for Epoch {
811 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
812 where
813 S: serde::ser::Serializer,
814 {
815 if serializer.is_human_readable() {
816 serializer.serialize_str(&self.0.to_string())
817 } else {
818 serializer.serialize_newtype_struct("Epoch", &self.0)
819 }
820 }
821}
822
823impl<'de> Deserialize<'de> for Epoch {
824 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
825 where
826 D: serde::de::Deserializer<'de>,
827 {
828 if deserializer.is_human_readable() {
829 let s = String::deserialize(deserializer)?;
830 Ok(Epoch(u32::from_str(&s).map_err(serde::de::Error::custom)?))
831 } else {
832 #[derive(Deserialize)]
833 #[serde(rename = "Epoch")]
834 struct EpochDerived(u32);
835
836 let value = EpochDerived::deserialize(deserializer)?;
837 Ok(Self(value.0))
838 }
839 }
840}
841
842impl std::fmt::Display for Epoch {
843 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
844 write!(f, "{}", self.0)
845 }
846}
847
848impl std::str::FromStr for Epoch {
849 type Err = CryptoError;
850
851 fn from_str(s: &str) -> Result<Self, Self::Err> {
852 Ok(Epoch(s.parse()?))
853 }
854}
855
856impl From<u32> for Epoch {
857 fn from(value: u32) -> Self {
858 Epoch(value)
859 }
860}
861
862impl Epoch {
863 #[inline]
866 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
867 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
868 Ok(Self(val))
869 }
870
871 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
874 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
875 Ok(Self(val))
876 }
877
878 #[inline]
880 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
881 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
882 Ok(())
883 }
884}
885
886#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
888pub struct InitialChainConfig {
889 pub ownership: ChainOwnership,
891 pub epoch: Epoch,
893 pub min_active_epoch: Epoch,
895 pub max_active_epoch: Epoch,
897 pub balance: Amount,
899 pub application_permissions: ApplicationPermissions,
901}
902
903#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize, Allocative)]
905pub struct ChainDescription {
906 origin: ChainOrigin,
907 timestamp: Timestamp,
908 config: InitialChainConfig,
909}
910
911impl ChainDescription {
912 pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self {
914 Self {
915 origin,
916 config,
917 timestamp,
918 }
919 }
920
921 pub fn id(&self) -> ChainId {
923 ChainId::from(self)
924 }
925
926 pub fn origin(&self) -> ChainOrigin {
928 self.origin
929 }
930
931 pub fn config(&self) -> &InitialChainConfig {
933 &self.config
934 }
935
936 pub fn timestamp(&self) -> Timestamp {
938 self.timestamp
939 }
940
941 pub fn is_child(&self) -> bool {
943 self.origin.is_child()
944 }
945}
946
947impl BcsHashable<'_> for ChainDescription {}
948
949#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
951pub struct NetworkDescription {
952 pub name: String,
954 pub genesis_config_hash: CryptoHash,
956 pub genesis_timestamp: Timestamp,
958 pub genesis_committee_blob_hash: CryptoHash,
960 pub admin_chain_id: ChainId,
962}
963
964#[derive(
966 Default,
967 Debug,
968 PartialEq,
969 Eq,
970 PartialOrd,
971 Ord,
972 Hash,
973 Clone,
974 Serialize,
975 Deserialize,
976 WitType,
977 WitLoad,
978 WitStore,
979 InputObject,
980 Allocative,
981)]
982pub struct ApplicationPermissions {
983 #[debug(skip_if = Option::is_none)]
987 pub execute_operations: Option<Vec<ApplicationId>>,
988 #[graphql(default)]
991 #[debug(skip_if = Vec::is_empty)]
992 pub mandatory_applications: Vec<ApplicationId>,
993 #[graphql(default)]
995 #[debug(skip_if = Vec::is_empty)]
996 pub close_chain: Vec<ApplicationId>,
997 #[graphql(default)]
999 #[debug(skip_if = Vec::is_empty)]
1000 pub change_application_permissions: Vec<ApplicationId>,
1001 #[graphql(default)]
1003 #[debug(skip_if = Option::is_none)]
1004 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
1005 #[graphql(default)]
1007 #[debug(skip_if = Option::is_none)]
1008 pub make_http_requests: Option<Vec<ApplicationId>>,
1009}
1010
1011impl ApplicationPermissions {
1012 pub fn new_single(app_id: ApplicationId) -> Self {
1015 Self {
1016 execute_operations: Some(vec![app_id]),
1017 mandatory_applications: vec![app_id],
1018 close_chain: vec![app_id],
1019 change_application_permissions: vec![app_id],
1020 call_service_as_oracle: Some(vec![app_id]),
1021 make_http_requests: Some(vec![app_id]),
1022 }
1023 }
1024
1025 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1028 Self {
1029 execute_operations: Some(app_ids.clone()),
1030 mandatory_applications: app_ids.clone(),
1031 close_chain: app_ids.clone(),
1032 change_application_permissions: app_ids.clone(),
1033 call_service_as_oracle: Some(app_ids.clone()),
1034 make_http_requests: Some(app_ids),
1035 }
1036 }
1037
1038 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1040 match (app_id, &self.execute_operations) {
1041 (_, None) => true,
1042 (GenericApplicationId::System, Some(_)) => false,
1043 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1044 }
1045 }
1046
1047 pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
1049 self.close_chain.contains(app_id)
1050 }
1051
1052 pub fn can_change_application_permissions(&self, app_id: &ApplicationId) -> bool {
1055 self.change_application_permissions.contains(app_id)
1056 }
1057
1058 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1060 self.call_service_as_oracle
1061 .as_ref()
1062 .is_none_or(|app_ids| app_ids.contains(app_id))
1063 }
1064
1065 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1067 self.make_http_requests
1068 .as_ref()
1069 .is_none_or(|app_ids| app_ids.contains(app_id))
1070 }
1071}
1072
1073#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1075pub enum OracleResponse {
1076 Service(
1078 #[debug(with = "hex_debug")]
1079 #[serde(with = "serde_bytes")]
1080 Vec<u8>,
1081 ),
1082 Http(http::Response),
1084 Blob(BlobId),
1086 Assert,
1088 Round(Option<u32>),
1090 Event(
1092 EventId,
1093 #[debug(with = "hex_debug")]
1094 #[serde(with = "serde_bytes")]
1095 Vec<u8>,
1096 ),
1097 EventExists(EventId),
1099}
1100
1101impl BcsHashable<'_> for OracleResponse {}
1102
1103#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize, WitType, WitLoad, WitStore)]
1105pub struct ApplicationDescription {
1106 pub module_id: ModuleId,
1108 pub creator_chain_id: ChainId,
1110 pub block_height: BlockHeight,
1112 pub application_index: u32,
1114 #[serde(with = "serde_bytes")]
1116 #[debug(with = "hex_debug")]
1117 pub parameters: Vec<u8>,
1118 pub required_application_ids: Vec<ApplicationId>,
1120}
1121
1122impl From<&ApplicationDescription> for ApplicationId {
1123 fn from(description: &ApplicationDescription) -> Self {
1124 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1125 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1126 hash.make_evm_compatible();
1127 }
1128 ApplicationId::new(hash)
1129 }
1130}
1131
1132impl BcsHashable<'_> for ApplicationDescription {}
1133
1134impl ApplicationDescription {
1135 pub fn to_bytes(&self) -> Vec<u8> {
1137 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1138 }
1139
1140 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1142 self.module_id.contract_bytecode_blob_id()
1143 }
1144
1145 pub fn service_bytecode_blob_id(&self) -> BlobId {
1147 self.module_id.service_bytecode_blob_id()
1148 }
1149}
1150
1151#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1153pub struct Bytecode {
1154 #[serde(with = "serde_bytes")]
1156 #[debug(with = "hex_debug")]
1157 pub bytes: Vec<u8>,
1158}
1159
1160impl Bytecode {
1161 pub fn new(bytes: Vec<u8>) -> Self {
1163 Bytecode { bytes }
1164 }
1165
1166 pub fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1168 let path = path.as_ref();
1169 let bytes = fs::read(path).map_err(|error| {
1170 std::io::Error::new(error.kind(), format!("{}: {error}", path.display()))
1171 })?;
1172 Ok(Bytecode { bytes })
1173 }
1174
1175 #[cfg(not(target_arch = "wasm32"))]
1177 pub fn compress(&self) -> CompressedBytecode {
1178 #[cfg(with_metrics)]
1179 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1180 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1181 .expect("Compressing bytes in memory should not fail");
1182
1183 CompressedBytecode {
1184 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1185 }
1186 }
1187
1188 #[cfg(target_arch = "wasm32")]
1190 pub fn compress(&self) -> CompressedBytecode {
1191 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1192
1193 #[cfg(with_metrics)]
1194 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1195
1196 let mut compressed_bytes_vec = Vec::new();
1197 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1198 compressor.set_source(&*self.bytes);
1199 compressor.set_drain(&mut compressed_bytes_vec);
1200 compressor.compress();
1201
1202 CompressedBytecode {
1203 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1204 }
1205 }
1206}
1207
1208impl AsRef<[u8]> for Bytecode {
1209 fn as_ref(&self) -> &[u8] {
1210 self.bytes.as_ref()
1211 }
1212}
1213
1214#[derive(Error, Debug)]
1216pub enum DecompressionError {
1217 #[error("Bytecode could not be decompressed: {0}")]
1219 InvalidCompressedBytecode(#[from] io::Error),
1220}
1221
1222#[serde_as]
1224#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1225#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1226pub struct CompressedBytecode {
1227 #[serde_as(as = "Arc<Bytes>")]
1229 #[debug(skip)]
1230 pub compressed_bytes: Arc<Box<[u8]>>,
1231}
1232
1233#[cfg(not(target_arch = "wasm32"))]
1234impl CompressedBytecode {
1235 pub fn decompressed_size_at_most(
1237 compressed_bytes: &[u8],
1238 limit: u64,
1239 ) -> Result<bool, DecompressionError> {
1240 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1241 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1242 let mut writer = LimitedWriter::new(io::sink(), limit);
1243 match io::copy(&mut decoder, &mut writer) {
1244 Ok(_) => Ok(true),
1245 Err(error) => {
1246 error.downcast::<LimitedWriterError>()?;
1247 Ok(false)
1248 }
1249 }
1250 }
1251
1252 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1254 #[cfg(with_metrics)]
1255 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1256 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1257
1258 #[cfg(with_metrics)]
1259 metrics::BYTECODE_DECOMPRESSED_SIZE_BYTES
1260 .with_label_values(&[])
1261 .observe(bytes.len() as f64);
1262
1263 Ok(Bytecode { bytes })
1264 }
1265}
1266
1267#[cfg(target_arch = "wasm32")]
1268impl CompressedBytecode {
1269 pub fn decompressed_size_at_most(
1271 compressed_bytes: &[u8],
1272 limit: u64,
1273 ) -> Result<bool, DecompressionError> {
1274 use ruzstd::decoding::StreamingDecoder;
1275 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1276 let mut writer = LimitedWriter::new(io::sink(), limit);
1277 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1278
1279 match io::copy(&mut decoder, &mut writer) {
1281 Ok(_) => Ok(true),
1282 Err(error) => {
1283 error.downcast::<LimitedWriterError>()?;
1284 Ok(false)
1285 }
1286 }
1287 }
1288
1289 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1291 use ruzstd::{decoding::StreamingDecoder, io::Read};
1292
1293 #[cfg(with_metrics)]
1294 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1295
1296 let compressed_bytes = &*self.compressed_bytes;
1297 let mut bytes = Vec::new();
1298 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1299
1300 while !decoder.get_ref().is_empty() {
1302 decoder
1303 .read_to_end(&mut bytes)
1304 .expect("Reading from a slice in memory should not result in I/O errors");
1305 }
1306
1307 #[cfg(with_metrics)]
1308 BYTECODE_DECOMPRESSED_SIZE_BYTES
1309 .with_label_values(&[])
1310 .observe(bytes.len() as f64);
1311
1312 Ok(Bytecode { bytes })
1313 }
1314}
1315
1316impl BcsHashable<'_> for BlobContent {}
1317
1318#[serde_as]
1320#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1321pub struct BlobContent {
1322 blob_type: BlobType,
1324 #[debug(skip)]
1326 #[serde_as(as = "Arc<Bytes>")]
1327 bytes: Arc<Box<[u8]>>,
1328}
1329
1330impl BlobContent {
1331 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1333 let bytes = bytes.into();
1334 BlobContent {
1335 blob_type,
1336 bytes: Arc::new(bytes),
1337 }
1338 }
1339
1340 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1342 BlobContent::new(BlobType::Data, bytes)
1343 }
1344
1345 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1347 BlobContent {
1348 blob_type: BlobType::ContractBytecode,
1349 bytes: compressed_bytecode.compressed_bytes,
1350 }
1351 }
1352
1353 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1355 BlobContent {
1356 blob_type: BlobType::EvmBytecode,
1357 bytes: compressed_bytecode.compressed_bytes,
1358 }
1359 }
1360
1361 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1363 BlobContent {
1364 blob_type: BlobType::ServiceBytecode,
1365 bytes: compressed_bytecode.compressed_bytes,
1366 }
1367 }
1368
1369 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1371 let bytes = application_description.to_bytes();
1372 BlobContent::new(BlobType::ApplicationDescription, bytes)
1373 }
1374
1375 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1377 BlobContent::new(BlobType::Committee, committee)
1378 }
1379
1380 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1382 let bytes = bcs::to_bytes(&chain_description)
1383 .expect("Serializing a ChainDescription should not fail!");
1384 BlobContent::new(BlobType::ChainDescription, bytes)
1385 }
1386
1387 pub fn bytes(&self) -> &[u8] {
1389 &self.bytes
1390 }
1391
1392 pub fn into_vec_or_clone(self) -> Vec<u8> {
1394 let bytes = Arc::unwrap_or_clone(self.bytes);
1395 bytes.into_vec()
1396 }
1397
1398 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1400 self.bytes
1401 }
1402
1403 pub fn blob_type(&self) -> BlobType {
1405 self.blob_type
1406 }
1407}
1408
1409impl From<Blob> for BlobContent {
1410 fn from(blob: Blob) -> BlobContent {
1411 blob.content
1412 }
1413}
1414
1415#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1417pub struct Blob {
1418 hash: CryptoHash,
1420 content: BlobContent,
1422}
1423
1424impl Blob {
1425 pub fn new(content: BlobContent) -> Self {
1427 let mut hash = CryptoHash::new(&content);
1428 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1429 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1430 .expect("to obtain an application description");
1431 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1432 hash.make_evm_compatible();
1433 }
1434 }
1435 Blob { hash, content }
1436 }
1437
1438 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1440 Blob {
1441 hash: blob_id.hash,
1442 content,
1443 }
1444 }
1445
1446 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1448 let bytes = bytes.into();
1449 Blob {
1450 hash: blob_id.hash,
1451 content: BlobContent {
1452 blob_type: blob_id.blob_type,
1453 bytes: Arc::new(bytes),
1454 },
1455 }
1456 }
1457
1458 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1460 Blob::new(BlobContent::new_data(bytes))
1461 }
1462
1463 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1465 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1466 }
1467
1468 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1470 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1471 }
1472
1473 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1475 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1476 }
1477
1478 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1480 Blob::new(BlobContent::new_application_description(
1481 application_description,
1482 ))
1483 }
1484
1485 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1487 Blob::new(BlobContent::new_committee(committee))
1488 }
1489
1490 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1492 Blob::new(BlobContent::new_chain_description(chain_description))
1493 }
1494
1495 pub fn id(&self) -> BlobId {
1497 BlobId {
1498 hash: self.hash,
1499 blob_type: self.content.blob_type,
1500 }
1501 }
1502
1503 pub fn content(&self) -> &BlobContent {
1505 &self.content
1506 }
1507
1508 pub fn into_content(self) -> BlobContent {
1510 self.content
1511 }
1512
1513 pub fn bytes(&self) -> &[u8] {
1515 self.content.bytes()
1516 }
1517
1518 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1520 Ok(Self::new_data(fs::read(path)?))
1521 }
1522
1523 pub fn is_committee_blob(&self) -> bool {
1525 self.content().blob_type().is_committee_blob()
1526 }
1527}
1528
1529impl Serialize for Blob {
1530 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1531 where
1532 S: Serializer,
1533 {
1534 if serializer.is_human_readable() {
1535 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1536 serializer.serialize_str(&hex::encode(blob_bytes))
1537 } else {
1538 BlobContent::serialize(self.content(), serializer)
1539 }
1540 }
1541}
1542
1543impl<'a> Deserialize<'a> for Blob {
1544 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1545 where
1546 D: Deserializer<'a>,
1547 {
1548 if deserializer.is_human_readable() {
1549 let s = String::deserialize(deserializer)?;
1550 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1551 let content: BlobContent =
1552 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1553
1554 Ok(Blob::new(content))
1555 } else {
1556 let content = BlobContent::deserialize(deserializer)?;
1557 Ok(Blob::new(content))
1558 }
1559 }
1560}
1561
1562impl BcsHashable<'_> for Blob {}
1563
1564#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1566pub struct Event {
1567 pub stream_id: StreamId,
1569 pub index: u32,
1571 #[debug(with = "hex_debug")]
1573 #[serde(with = "serde_bytes")]
1574 pub value: Vec<u8>,
1575}
1576
1577impl Event {
1578 pub fn id(&self, chain_id: ChainId) -> EventId {
1580 EventId {
1581 chain_id,
1582 stream_id: self.stream_id.clone(),
1583 index: self.index,
1584 }
1585 }
1586}
1587
1588#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1590pub struct StreamUpdate {
1591 pub chain_id: ChainId,
1593 pub stream_id: StreamId,
1595 pub previous_index: u32,
1597 pub next_index: u32,
1599}
1600
1601impl StreamUpdate {
1602 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1604 self.previous_index..self.next_index
1605 }
1606}
1607
1608impl BcsHashable<'_> for Event {}
1609
1610#[derive(
1612 Clone, Debug, Default, serde::Serialize, serde::Deserialize, async_graphql::SimpleObject,
1613)]
1614pub struct MessagePolicy {
1615 pub blanket: BlanketMessagePolicy,
1617 pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
1621 pub reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1624 pub reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1627 pub process_events_from_application_ids: Option<HashSet<GenericApplicationId>>,
1630}
1631
1632#[derive(
1634 Default,
1635 Copy,
1636 Clone,
1637 Debug,
1638 PartialEq,
1639 Eq,
1640 serde::Serialize,
1641 serde::Deserialize,
1642 async_graphql::Enum,
1643)]
1644#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
1645#[cfg_attr(any(web, not(target_arch = "wasm32")), derive(clap::ValueEnum))]
1646pub enum BlanketMessagePolicy {
1647 #[default]
1649 Accept,
1650 Reject,
1653 Ignore,
1656}
1657
1658impl MessagePolicy {
1659 pub fn new(
1661 blanket: BlanketMessagePolicy,
1662 restrict_chain_ids_to: Option<HashSet<ChainId>>,
1663 reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1664 reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1665 process_events_from_application_ids: Option<HashSet<GenericApplicationId>>,
1666 ) -> Self {
1667 Self {
1668 blanket,
1669 restrict_chain_ids_to,
1670 reject_message_bundles_without_application_ids,
1671 reject_message_bundles_with_other_application_ids,
1672 process_events_from_application_ids,
1673 }
1674 }
1675
1676 #[cfg(with_testing)]
1678 pub fn new_accept_all() -> Self {
1679 Self {
1680 blanket: BlanketMessagePolicy::Accept,
1681 restrict_chain_ids_to: None,
1682 reject_message_bundles_without_application_ids: None,
1683 reject_message_bundles_with_other_application_ids: None,
1684 process_events_from_application_ids: None,
1685 }
1686 }
1687
1688 #[instrument(level = "trace", skip(self))]
1690 pub fn is_ignore(&self) -> bool {
1691 matches!(self.blanket, BlanketMessagePolicy::Ignore)
1692 }
1693
1694 #[instrument(level = "trace", skip(self))]
1696 pub fn is_reject(&self) -> bool {
1697 matches!(self.blanket, BlanketMessagePolicy::Reject)
1698 }
1699}
1700
1701doc_scalar!(Bytecode, "A WebAssembly module's bytecode");
1702doc_scalar!(Amount, "A non-negative amount of tokens.");
1703doc_scalar!(
1704 Epoch,
1705 "A number identifying the configuration of the chain (aka the committee)"
1706);
1707doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1708doc_scalar!(
1709 Timestamp,
1710 "A timestamp, in microseconds since the Unix epoch"
1711);
1712doc_scalar!(TimeDelta, "A duration in microseconds");
1713doc_scalar!(
1714 Round,
1715 "A number to identify successive attempts to decide a value in a consensus protocol."
1716);
1717doc_scalar!(
1718 ChainDescription,
1719 "Initial chain configuration and chain origin."
1720);
1721doc_scalar!(OracleResponse, "A record of a single oracle response.");
1722doc_scalar!(BlobContent, "A blob of binary data.");
1723doc_scalar!(
1724 Blob,
1725 "A blob of binary data, with its content-addressed blob ID."
1726);
1727doc_scalar!(ApplicationDescription, "Description of a user application");
1728
1729#[cfg(with_metrics)]
1730mod metrics {
1731 use std::sync::LazyLock;
1732
1733 use prometheus::HistogramVec;
1734
1735 use crate::prometheus_util::{
1736 exponential_bucket_interval, exponential_bucket_latencies, register_histogram_vec,
1737 };
1738
1739 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1741 register_histogram_vec(
1742 "bytecode_compression_latency",
1743 "Bytecode compression latency",
1744 &[],
1745 exponential_bucket_latencies(10.0),
1746 )
1747 });
1748
1749 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1751 register_histogram_vec(
1752 "bytecode_decompression_latency",
1753 "Bytecode decompression latency",
1754 &[],
1755 exponential_bucket_latencies(10.0),
1756 )
1757 });
1758
1759 pub static BYTECODE_DECOMPRESSED_SIZE_BYTES: LazyLock<HistogramVec> = LazyLock::new(|| {
1760 register_histogram_vec(
1761 "wasm_bytecode_decompressed_size_bytes",
1762 "Decompressed size in bytes of WASM bytecodes stored on-chain",
1763 &[],
1764 exponential_bucket_interval(10_000.0, 100_000_000.0),
1765 )
1766 });
1767}
1768
1769#[cfg(test)]
1770mod tests {
1771 use std::str::FromStr;
1772
1773 use super::{Amount, BlobContent};
1774 use crate::identifiers::BlobType;
1775
1776 #[test]
1777 fn display_amount() {
1778 assert_eq!("1.", Amount::ONE.to_string());
1779 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1780 assert_eq!(
1781 Amount(10_000_000_000_000_000_000),
1782 Amount::from_str("10").unwrap()
1783 );
1784 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1785 assert_eq!(
1786 "1001.3",
1787 (Amount::from_str("1.1")
1788 .unwrap()
1789 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1790 .to_string()
1791 );
1792 assert_eq!(
1793 " 1.00000000000000000000",
1794 format!("{:25.20}", Amount::ONE)
1795 );
1796 assert_eq!(
1797 "~+12.34~~",
1798 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1799 );
1800 }
1801
1802 #[test]
1803 fn blob_content_serialization_deserialization() {
1804 let test_data = b"Hello, world!".as_slice();
1805 let original_blob = BlobContent::new(BlobType::Data, test_data);
1806
1807 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1808 let deserialized: BlobContent =
1809 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1810 assert_eq!(original_blob, deserialized);
1811
1812 let serialized =
1813 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1814 let deserialized: BlobContent =
1815 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1816 assert_eq!(original_blob, deserialized);
1817 }
1818
1819 #[test]
1820 fn blob_content_hash_consistency() {
1821 let test_data = b"Hello, world!";
1822 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1823 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1824
1825 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1827 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1828
1829 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1830 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1831 }
1832}