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
99#[derive(Error, Debug)]
102#[error("Failed to convert U256 to Amount. {0} has more than 128 bits")]
103pub struct AmountConversionError(U256);
104
105impl TryFrom<U256> for Amount {
106 type Error = AmountConversionError;
107 fn try_from(value: U256) -> Result<Amount, Self::Error> {
108 let value = u128::try_from(&value).map_err(|_| AmountConversionError(value))?;
109 Ok(Amount(value))
110 }
111}
112
113#[derive(
115 Eq,
116 PartialEq,
117 Ord,
118 PartialOrd,
119 Copy,
120 Clone,
121 Hash,
122 Default,
123 Debug,
124 Serialize,
125 Deserialize,
126 WitType,
127 WitLoad,
128 WitStore,
129 Allocative,
130)]
131#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
132pub struct BlockHeight(pub u64);
133
134#[derive(
136 Eq,
137 PartialEq,
138 Ord,
139 PartialOrd,
140 Copy,
141 Clone,
142 Hash,
143 Default,
144 Debug,
145 Serialize,
146 Deserialize,
147 Allocative,
148)]
149#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
150pub enum Round {
151 #[default]
153 Fast,
154 MultiLeader(u32),
156 SingleLeader(u32),
158 Validator(u32),
160}
161
162#[derive(
164 Eq,
165 PartialEq,
166 Ord,
167 PartialOrd,
168 Copy,
169 Clone,
170 Hash,
171 Default,
172 Debug,
173 Serialize,
174 Deserialize,
175 WitType,
176 WitLoad,
177 WitStore,
178 Allocative,
179)]
180pub struct TimeDelta(u64);
181
182impl TimeDelta {
183 pub const fn from_micros(micros: u64) -> Self {
185 TimeDelta(micros)
186 }
187
188 pub const fn from_millis(millis: u64) -> Self {
190 TimeDelta(millis.saturating_mul(1_000))
191 }
192
193 pub const fn from_secs(secs: u64) -> Self {
195 TimeDelta(secs.saturating_mul(1_000_000))
196 }
197
198 pub fn from_duration(duration: Duration) -> Self {
201 TimeDelta::from_micros(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX))
202 }
203
204 pub const fn as_micros(&self) -> u64 {
206 self.0
207 }
208
209 pub const fn as_duration(&self) -> Duration {
211 Duration::from_micros(self.as_micros())
212 }
213}
214
215#[derive(
217 Eq,
218 PartialEq,
219 Ord,
220 PartialOrd,
221 Copy,
222 Clone,
223 Hash,
224 Default,
225 Debug,
226 Serialize,
227 Deserialize,
228 WitType,
229 WitLoad,
230 WitStore,
231 Allocative,
232)]
233pub struct Timestamp(u64);
234
235impl Timestamp {
236 pub fn now() -> Timestamp {
238 Timestamp(
239 SystemTime::UNIX_EPOCH
240 .elapsed()
241 .expect("system time should be after Unix epoch")
242 .as_micros()
243 .try_into()
244 .unwrap_or(u64::MAX),
245 )
246 }
247
248 pub const fn micros(&self) -> u64 {
250 self.0
251 }
252
253 pub const fn delta_since(&self, other: Timestamp) -> TimeDelta {
256 TimeDelta::from_micros(self.0.saturating_sub(other.0))
257 }
258
259 pub const fn duration_since(&self, other: Timestamp) -> Duration {
262 Duration::from_micros(self.0.saturating_sub(other.0))
263 }
264
265 pub const fn saturating_add(&self, duration: TimeDelta) -> Timestamp {
267 Timestamp(self.0.saturating_add(duration.0))
268 }
269
270 pub const fn saturating_sub(&self, duration: TimeDelta) -> Timestamp {
272 Timestamp(self.0.saturating_sub(duration.0))
273 }
274
275 pub const fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
278 Timestamp(self.0.saturating_sub(micros))
279 }
280}
281
282impl From<u64> for Timestamp {
283 fn from(t: u64) -> Timestamp {
284 Timestamp(t)
285 }
286}
287
288impl Display for Timestamp {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 if let Some(date_time) = chrono::DateTime::from_timestamp(
291 (self.0 / 1_000_000) as i64,
292 ((self.0 % 1_000_000) * 1_000) as u32,
293 ) {
294 return date_time.naive_utc().fmt(f);
295 }
296 self.0.fmt(f)
297 }
298}
299
300#[derive(
303 Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, WitLoad, WitStore, WitType,
304)]
305pub struct Resources {
306 pub wasm_fuel: u64,
308 pub evm_fuel: u64,
310 pub read_operations: u32,
312 pub write_operations: u32,
314 pub bytes_runtime: u32,
316 pub bytes_to_read: u32,
318 pub bytes_to_write: u32,
320 pub blobs_to_read: u32,
322 pub blobs_to_publish: u32,
324 pub blob_bytes_to_read: u32,
326 pub blob_bytes_to_publish: u32,
328 pub messages: u32,
330 pub message_size: u32,
333 pub storage_size_delta: u32,
335 pub service_as_oracle_queries: u32,
337 pub http_requests: u32,
339 }
342
343#[derive(Clone, Debug, Deserialize, Serialize, WitLoad, WitType)]
345#[cfg_attr(with_testing, derive(Eq, PartialEq, WitStore))]
346#[witty_specialize_with(Message = Vec<u8>)]
347pub struct SendMessageRequest<Message> {
348 pub destination: ChainId,
350 pub authenticated: bool,
352 pub is_tracked: bool,
354 pub grant: Resources,
356 pub message: Message,
358}
359
360impl<Message> SendMessageRequest<Message>
361where
362 Message: Serialize,
363{
364 pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
366 let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
367
368 SendMessageRequest {
369 destination: self.destination,
370 authenticated: self.authenticated,
371 is_tracked: self.is_tracked,
372 grant: self.grant,
373 message,
374 }
375 }
376}
377
378#[derive(Debug, Error)]
380#[allow(missing_docs)]
381pub enum ArithmeticError {
382 #[error("Number overflow")]
383 Overflow,
384 #[error("Number underflow")]
385 Underflow,
386}
387
388macro_rules! impl_wrapped_number {
389 ($name:ident, $wrapped:ident) => {
390 impl $name {
391 pub const ZERO: Self = Self(0);
393
394 pub const MAX: Self = Self($wrapped::MAX);
396
397 pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
399 let val = self
400 .0
401 .checked_add(other.0)
402 .ok_or(ArithmeticError::Overflow)?;
403 Ok(Self(val))
404 }
405
406 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
408 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
409 Ok(Self(val))
410 }
411
412 pub const fn saturating_add(self, other: Self) -> Self {
414 let val = self.0.saturating_add(other.0);
415 Self(val)
416 }
417
418 pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
420 let val = self
421 .0
422 .checked_sub(other.0)
423 .ok_or(ArithmeticError::Underflow)?;
424 Ok(Self(val))
425 }
426
427 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
429 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
430 Ok(Self(val))
431 }
432
433 pub const fn saturating_sub(self, other: Self) -> Self {
435 let val = self.0.saturating_sub(other.0);
436 Self(val)
437 }
438
439 pub fn abs_diff(self, other: Self) -> Self {
441 Self(self.0.abs_diff(other.0))
442 }
443
444 pub const fn midpoint(self, other: Self) -> Self {
446 Self(self.0.midpoint(other.0))
447 }
448
449 pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
451 self.0 = self
452 .0
453 .checked_add(other.0)
454 .ok_or(ArithmeticError::Overflow)?;
455 Ok(())
456 }
457
458 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
460 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
461 Ok(())
462 }
463
464 pub const fn saturating_add_assign(&mut self, other: Self) {
466 self.0 = self.0.saturating_add(other.0);
467 }
468
469 pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
471 self.0 = self
472 .0
473 .checked_sub(other.0)
474 .ok_or(ArithmeticError::Underflow)?;
475 Ok(())
476 }
477
478 pub fn saturating_div(&self, other: $wrapped) -> Self {
480 Self(self.0.checked_div(other).unwrap_or($wrapped::MAX))
481 }
482
483 pub const fn saturating_mul(&self, other: $wrapped) -> Self {
485 Self(self.0.saturating_mul(other))
486 }
487
488 pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
490 let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
491 Ok(Self(val))
492 }
493
494 pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
496 self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
497 Ok(())
498 }
499 }
500
501 impl From<$name> for $wrapped {
502 fn from(value: $name) -> Self {
503 value.0
504 }
505 }
506
507 #[cfg(with_testing)]
509 impl From<$wrapped> for $name {
510 fn from(value: $wrapped) -> Self {
511 Self(value)
512 }
513 }
514
515 #[cfg(with_testing)]
516 impl ops::Add for $name {
517 type Output = Self;
518
519 fn add(self, other: Self) -> Self {
520 Self(self.0 + other.0)
521 }
522 }
523
524 #[cfg(with_testing)]
525 impl ops::Sub for $name {
526 type Output = Self;
527
528 fn sub(self, other: Self) -> Self {
529 Self(self.0 - other.0)
530 }
531 }
532
533 #[cfg(with_testing)]
534 impl ops::Mul<$wrapped> for $name {
535 type Output = Self;
536
537 fn mul(self, other: $wrapped) -> Self {
538 Self(self.0 * other)
539 }
540 }
541 };
542}
543
544impl TryFrom<BlockHeight> for usize {
545 type Error = ArithmeticError;
546
547 fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
548 usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
549 }
550}
551
552impl_wrapped_number!(Amount, u128);
553impl_wrapped_number!(BlockHeight, u64);
554impl_wrapped_number!(TimeDelta, u64);
555
556impl Display for Amount {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 let places = Amount::DECIMAL_PLACES as usize;
560 let min_digits = places + 1;
561 let decimals = format!("{:0min_digits$}", self.0);
562 let integer_part = &decimals[..(decimals.len() - places)];
563 let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
564
565 let precision = f.precision().unwrap_or(0).max(fractional_part.len());
567 let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
568 let pad_width = f.width().map_or(0, |w| {
570 w.saturating_sub(precision)
571 .saturating_sub(sign.len() + integer_part.len() + 1)
572 });
573 let left_pad = match f.align() {
574 None | Some(fmt::Alignment::Right) => pad_width,
575 Some(fmt::Alignment::Center) => pad_width / 2,
576 Some(fmt::Alignment::Left) => 0,
577 };
578
579 for _ in 0..left_pad {
580 write!(f, "{}", f.fill())?;
581 }
582 write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
583 for _ in left_pad..pad_width {
584 write!(f, "{}", f.fill())?;
585 }
586 Ok(())
587 }
588}
589
590#[derive(Error, Debug)]
591#[allow(missing_docs)]
592pub enum ParseAmountError {
593 #[error("cannot parse amount")]
594 Parse,
595 #[error("cannot represent amount: number too high")]
596 TooHigh,
597 #[error("cannot represent amount: too many decimal places after the point")]
598 TooManyDigits,
599}
600
601impl FromStr for Amount {
602 type Err = ParseAmountError;
603
604 fn from_str(src: &str) -> Result<Self, Self::Err> {
605 let mut result: u128 = 0;
606 let mut decimals: Option<u8> = None;
607 let mut chars = src.trim().chars().peekable();
608 if chars.peek() == Some(&'+') {
609 chars.next();
610 }
611 for char in chars {
612 match char {
613 '_' => {}
614 '.' if decimals.is_some() => return Err(ParseAmountError::Parse),
615 '.' => decimals = Some(Amount::DECIMAL_PLACES),
616 char => {
617 let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
618 if let Some(d) = &mut decimals {
619 *d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
620 }
621 result = result
622 .checked_mul(10)
623 .and_then(|r| r.checked_add(digit))
624 .ok_or(ParseAmountError::TooHigh)?;
625 }
626 }
627 }
628 result = result
629 .checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
630 .ok_or(ParseAmountError::TooHigh)?;
631 Ok(Amount(result))
632 }
633}
634
635impl Display for BlockHeight {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 self.0.fmt(f)
638 }
639}
640
641impl FromStr for BlockHeight {
642 type Err = ParseIntError;
643
644 fn from_str(src: &str) -> Result<Self, Self::Err> {
645 Ok(Self(u64::from_str(src)?))
646 }
647}
648
649impl Display for Round {
650 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651 match self {
652 Round::Fast => write!(f, "fast round"),
653 Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
654 Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
655 Round::Validator(r) => write!(f, "validator round {}", r),
656 }
657 }
658}
659
660impl Round {
661 pub fn is_multi_leader(&self) -> bool {
663 matches!(self, Round::MultiLeader(_))
664 }
665
666 pub fn multi_leader(&self) -> Option<u32> {
668 match self {
669 Round::MultiLeader(number) => Some(*number),
670 _ => None,
671 }
672 }
673
674 pub fn is_validator(&self) -> bool {
676 matches!(self, Round::Validator(_))
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)]
996 #[debug(skip_if = Vec::is_empty)]
997 pub manage_chain: Vec<ApplicationId>,
998 #[graphql(default)]
1000 #[debug(skip_if = Option::is_none)]
1001 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
1002 #[graphql(default)]
1004 #[debug(skip_if = Option::is_none)]
1005 pub make_http_requests: Option<Vec<ApplicationId>>,
1006}
1007
1008impl ApplicationPermissions {
1009 pub fn new_single(app_id: ApplicationId) -> Self {
1012 Self {
1013 execute_operations: Some(vec![app_id]),
1014 mandatory_applications: vec![app_id],
1015 manage_chain: vec![app_id],
1016 call_service_as_oracle: Some(vec![app_id]),
1017 make_http_requests: Some(vec![app_id]),
1018 }
1019 }
1020
1021 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1024 Self {
1025 execute_operations: Some(app_ids.clone()),
1026 mandatory_applications: app_ids.clone(),
1027 manage_chain: app_ids.clone(),
1028 call_service_as_oracle: Some(app_ids.clone()),
1029 make_http_requests: Some(app_ids),
1030 }
1031 }
1032
1033 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1035 match (app_id, &self.execute_operations) {
1036 (_, None) => true,
1037 (GenericApplicationId::System, Some(_)) => false,
1038 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1039 }
1040 }
1041
1042 pub fn can_manage_chain(&self, app_id: &ApplicationId) -> bool {
1045 self.manage_chain.contains(app_id)
1046 }
1047
1048 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1050 self.call_service_as_oracle
1051 .as_ref()
1052 .is_none_or(|app_ids| app_ids.contains(app_id))
1053 }
1054
1055 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1057 self.make_http_requests
1058 .as_ref()
1059 .is_none_or(|app_ids| app_ids.contains(app_id))
1060 }
1061}
1062
1063#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1065pub enum OracleResponse {
1066 Service(
1068 #[debug(with = "hex_debug")]
1069 #[serde(with = "serde_bytes")]
1070 Vec<u8>,
1071 ),
1072 Http(http::Response),
1074 Blob(BlobId),
1076 Assert,
1078 Round(Option<u32>),
1080 Event(
1082 EventId,
1083 #[debug(with = "hex_debug")]
1084 #[serde(with = "serde_bytes")]
1085 Vec<u8>,
1086 ),
1087 EventExists(EventId),
1089}
1090
1091impl BcsHashable<'_> for OracleResponse {}
1092
1093#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize, WitType, WitLoad, WitStore)]
1095pub struct ApplicationDescription {
1096 pub module_id: ModuleId,
1098 pub creator_chain_id: ChainId,
1100 pub block_height: BlockHeight,
1102 pub application_index: u32,
1104 #[serde(with = "serde_bytes")]
1106 #[debug(with = "hex_debug")]
1107 pub parameters: Vec<u8>,
1108 pub required_application_ids: Vec<ApplicationId>,
1110}
1111
1112impl From<&ApplicationDescription> for ApplicationId {
1113 fn from(description: &ApplicationDescription) -> Self {
1114 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1115 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1116 hash.make_evm_compatible();
1117 }
1118 ApplicationId::new(hash)
1119 }
1120}
1121
1122impl BcsHashable<'_> for ApplicationDescription {}
1123
1124impl ApplicationDescription {
1125 pub fn to_bytes(&self) -> Vec<u8> {
1127 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1128 }
1129
1130 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1132 self.module_id.contract_bytecode_blob_id()
1133 }
1134
1135 pub fn service_bytecode_blob_id(&self) -> BlobId {
1137 self.module_id.service_bytecode_blob_id()
1138 }
1139}
1140
1141#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1143pub struct Bytecode {
1144 #[serde(with = "serde_bytes")]
1146 #[debug(with = "hex_debug")]
1147 pub bytes: Vec<u8>,
1148}
1149
1150impl Bytecode {
1151 pub fn new(bytes: Vec<u8>) -> Self {
1153 Bytecode { bytes }
1154 }
1155
1156 #[cfg(not(target_arch = "wasm32"))]
1158 pub async fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1159 let path = path.as_ref();
1160 let bytes = tokio::fs::read(path).await.map_err(|error| {
1161 std::io::Error::new(error.kind(), format!("{}: {error}", path.display()))
1162 })?;
1163 Ok(Bytecode { bytes })
1164 }
1165
1166 #[cfg(not(target_arch = "wasm32"))]
1168 pub fn compress(&self) -> CompressedBytecode {
1169 #[cfg(with_metrics)]
1170 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1171 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1172 .expect("Compressing bytes in memory should not fail");
1173
1174 CompressedBytecode {
1175 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1176 }
1177 }
1178
1179 #[cfg(target_arch = "wasm32")]
1181 pub fn compress(&self) -> CompressedBytecode {
1182 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1183
1184 #[cfg(with_metrics)]
1185 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1186
1187 let mut compressed_bytes_vec = Vec::new();
1188 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1189 compressor.set_source(&*self.bytes);
1190 compressor.set_drain(&mut compressed_bytes_vec);
1191 compressor.compress();
1192
1193 CompressedBytecode {
1194 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1195 }
1196 }
1197}
1198
1199impl AsRef<[u8]> for Bytecode {
1200 fn as_ref(&self) -> &[u8] {
1201 self.bytes.as_ref()
1202 }
1203}
1204
1205#[derive(Error, Debug)]
1207pub enum DecompressionError {
1208 #[error("Bytecode could not be decompressed: {0}")]
1210 InvalidCompressedBytecode(#[from] io::Error),
1211}
1212
1213#[serde_as]
1215#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1216#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1217pub struct CompressedBytecode {
1218 #[serde_as(as = "Arc<Bytes>")]
1220 #[debug(skip)]
1221 pub compressed_bytes: Arc<Box<[u8]>>,
1222}
1223
1224#[cfg(not(target_arch = "wasm32"))]
1225impl CompressedBytecode {
1226 pub fn decompressed_size_at_most(
1228 compressed_bytes: &[u8],
1229 limit: u64,
1230 ) -> Result<bool, DecompressionError> {
1231 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1232 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1233 let mut writer = LimitedWriter::new(io::sink(), limit);
1234 match io::copy(&mut decoder, &mut writer) {
1235 Ok(_) => Ok(true),
1236 Err(error) => {
1237 error.downcast::<LimitedWriterError>()?;
1238 Ok(false)
1239 }
1240 }
1241 }
1242
1243 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1245 #[cfg(with_metrics)]
1246 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1247 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1248
1249 Ok(Bytecode { bytes })
1250 }
1251}
1252
1253#[cfg(target_arch = "wasm32")]
1254impl CompressedBytecode {
1255 pub fn decompressed_size_at_most(
1257 compressed_bytes: &[u8],
1258 limit: u64,
1259 ) -> Result<bool, DecompressionError> {
1260 use ruzstd::decoding::StreamingDecoder;
1261 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1262 let mut writer = LimitedWriter::new(io::sink(), limit);
1263 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1264
1265 match io::copy(&mut decoder, &mut writer) {
1267 Ok(_) => Ok(true),
1268 Err(error) => {
1269 error.downcast::<LimitedWriterError>()?;
1270 Ok(false)
1271 }
1272 }
1273 }
1274
1275 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1277 use ruzstd::{decoding::StreamingDecoder, io::Read};
1278
1279 #[cfg(with_metrics)]
1280 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1281
1282 let compressed_bytes = &*self.compressed_bytes;
1283 let mut bytes = Vec::new();
1284 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1285
1286 while !decoder.get_ref().is_empty() {
1288 decoder
1289 .read_to_end(&mut bytes)
1290 .expect("Reading from a slice in memory should not result in I/O errors");
1291 }
1292
1293 Ok(Bytecode { bytes })
1294 }
1295}
1296
1297impl BcsHashable<'_> for BlobContent {}
1298
1299#[serde_as]
1301#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1302pub struct BlobContent {
1303 blob_type: BlobType,
1305 #[debug(skip)]
1307 #[serde_as(as = "Arc<Bytes>")]
1308 bytes: Arc<Box<[u8]>>,
1309}
1310
1311impl BlobContent {
1312 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1314 let bytes = bytes.into();
1315 BlobContent {
1316 blob_type,
1317 bytes: Arc::new(bytes),
1318 }
1319 }
1320
1321 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1323 BlobContent::new(BlobType::Data, bytes)
1324 }
1325
1326 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1328 BlobContent {
1329 blob_type: BlobType::ContractBytecode,
1330 bytes: compressed_bytecode.compressed_bytes,
1331 }
1332 }
1333
1334 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1336 BlobContent {
1337 blob_type: BlobType::EvmBytecode,
1338 bytes: compressed_bytecode.compressed_bytes,
1339 }
1340 }
1341
1342 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1344 BlobContent {
1345 blob_type: BlobType::ServiceBytecode,
1346 bytes: compressed_bytecode.compressed_bytes,
1347 }
1348 }
1349
1350 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1352 let bytes = application_description.to_bytes();
1353 BlobContent::new(BlobType::ApplicationDescription, bytes)
1354 }
1355
1356 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1358 BlobContent::new(BlobType::Committee, committee)
1359 }
1360
1361 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1363 let bytes = bcs::to_bytes(&chain_description)
1364 .expect("Serializing a ChainDescription should not fail!");
1365 BlobContent::new(BlobType::ChainDescription, bytes)
1366 }
1367
1368 pub fn bytes(&self) -> &[u8] {
1370 &self.bytes
1371 }
1372
1373 pub fn into_vec_or_clone(self) -> Vec<u8> {
1375 let bytes = Arc::unwrap_or_clone(self.bytes);
1376 bytes.into_vec()
1377 }
1378
1379 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1381 self.bytes
1382 }
1383
1384 pub fn blob_type(&self) -> BlobType {
1386 self.blob_type
1387 }
1388}
1389
1390impl From<Blob> for BlobContent {
1391 fn from(blob: Blob) -> BlobContent {
1392 blob.content
1393 }
1394}
1395
1396#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1398pub struct Blob {
1399 hash: CryptoHash,
1401 content: BlobContent,
1403}
1404
1405impl Blob {
1406 pub fn new(content: BlobContent) -> Self {
1408 let mut hash = CryptoHash::new(&content);
1409 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1410 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1411 .expect("to obtain an application description");
1412 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1413 hash.make_evm_compatible();
1414 }
1415 }
1416 Blob { hash, content }
1417 }
1418
1419 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1421 Blob {
1422 hash: blob_id.hash,
1423 content,
1424 }
1425 }
1426
1427 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1429 let bytes = bytes.into();
1430 Blob {
1431 hash: blob_id.hash,
1432 content: BlobContent {
1433 blob_type: blob_id.blob_type,
1434 bytes: Arc::new(bytes),
1435 },
1436 }
1437 }
1438
1439 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1441 Blob::new(BlobContent::new_data(bytes))
1442 }
1443
1444 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1446 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1447 }
1448
1449 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1451 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1452 }
1453
1454 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1456 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1457 }
1458
1459 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1461 Blob::new(BlobContent::new_application_description(
1462 application_description,
1463 ))
1464 }
1465
1466 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1468 Blob::new(BlobContent::new_committee(committee))
1469 }
1470
1471 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1473 Blob::new(BlobContent::new_chain_description(chain_description))
1474 }
1475
1476 pub fn id(&self) -> BlobId {
1478 BlobId {
1479 hash: self.hash,
1480 blob_type: self.content.blob_type,
1481 }
1482 }
1483
1484 pub fn content(&self) -> &BlobContent {
1486 &self.content
1487 }
1488
1489 pub fn into_content(self) -> BlobContent {
1491 self.content
1492 }
1493
1494 pub fn bytes(&self) -> &[u8] {
1496 self.content.bytes()
1497 }
1498
1499 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1501 Ok(Self::new_data(fs::read(path)?))
1502 }
1503
1504 pub fn is_committee_blob(&self) -> bool {
1506 self.content().blob_type().is_committee_blob()
1507 }
1508}
1509
1510impl Serialize for Blob {
1511 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1512 where
1513 S: Serializer,
1514 {
1515 if serializer.is_human_readable() {
1516 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1517 serializer.serialize_str(&hex::encode(blob_bytes))
1518 } else {
1519 BlobContent::serialize(self.content(), serializer)
1520 }
1521 }
1522}
1523
1524impl<'a> Deserialize<'a> for Blob {
1525 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1526 where
1527 D: Deserializer<'a>,
1528 {
1529 if deserializer.is_human_readable() {
1530 let s = String::deserialize(deserializer)?;
1531 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1532 let content: BlobContent =
1533 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1534
1535 Ok(Blob::new(content))
1536 } else {
1537 let content = BlobContent::deserialize(deserializer)?;
1538 Ok(Blob::new(content))
1539 }
1540 }
1541}
1542
1543impl BcsHashable<'_> for Blob {}
1544
1545#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1547pub struct Event {
1548 pub stream_id: StreamId,
1550 pub index: u32,
1552 #[debug(with = "hex_debug")]
1554 #[serde(with = "serde_bytes")]
1555 pub value: Vec<u8>,
1556}
1557
1558impl Event {
1559 pub fn id(&self, chain_id: ChainId) -> EventId {
1561 EventId {
1562 chain_id,
1563 stream_id: self.stream_id.clone(),
1564 index: self.index,
1565 }
1566 }
1567}
1568
1569#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1571pub struct StreamUpdate {
1572 pub chain_id: ChainId,
1574 pub stream_id: StreamId,
1576 pub previous_index: u32,
1578 pub next_index: u32,
1580}
1581
1582impl StreamUpdate {
1583 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1585 self.previous_index..self.next_index
1586 }
1587}
1588
1589impl BcsHashable<'_> for Event {}
1590
1591#[derive(
1593 Clone,
1594 Debug,
1595 Default,
1596 PartialEq,
1597 serde::Serialize,
1598 serde::Deserialize,
1599 async_graphql::SimpleObject,
1600)]
1601pub struct MessagePolicy {
1602 pub blanket: BlanketMessagePolicy,
1604 pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
1608 pub reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1611 pub reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1614 pub process_events_from_application_ids: Option<HashSet<GenericApplicationId>>,
1617}
1618
1619#[derive(
1621 Default,
1622 Copy,
1623 Clone,
1624 Debug,
1625 PartialEq,
1626 Eq,
1627 serde::Serialize,
1628 serde::Deserialize,
1629 async_graphql::Enum,
1630)]
1631#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
1632#[cfg_attr(any(web, not(target_arch = "wasm32")), derive(clap::ValueEnum))]
1633pub enum BlanketMessagePolicy {
1634 #[default]
1636 Accept,
1637 Reject,
1640 Ignore,
1643}
1644
1645impl MessagePolicy {
1646 pub fn new(
1648 blanket: BlanketMessagePolicy,
1649 restrict_chain_ids_to: Option<HashSet<ChainId>>,
1650 reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1651 reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1652 process_events_from_application_ids: Option<HashSet<GenericApplicationId>>,
1653 ) -> Self {
1654 Self {
1655 blanket,
1656 restrict_chain_ids_to,
1657 reject_message_bundles_without_application_ids,
1658 reject_message_bundles_with_other_application_ids,
1659 process_events_from_application_ids,
1660 }
1661 }
1662
1663 #[cfg(with_testing)]
1665 pub fn new_accept_all() -> Self {
1666 Self {
1667 blanket: BlanketMessagePolicy::Accept,
1668 restrict_chain_ids_to: None,
1669 reject_message_bundles_without_application_ids: None,
1670 reject_message_bundles_with_other_application_ids: None,
1671 process_events_from_application_ids: None,
1672 }
1673 }
1674
1675 #[instrument(level = "trace", skip(self))]
1677 pub fn is_ignore(&self) -> bool {
1678 matches!(self.blanket, BlanketMessagePolicy::Ignore)
1679 }
1680
1681 #[instrument(level = "trace", skip(self))]
1683 pub fn is_reject(&self) -> bool {
1684 matches!(self.blanket, BlanketMessagePolicy::Reject)
1685 }
1686}
1687
1688doc_scalar!(Bytecode, "A module bytecode (WebAssembly or EVM)");
1689doc_scalar!(Amount, "A non-negative amount of tokens.");
1690doc_scalar!(
1691 Epoch,
1692 "A number identifying the configuration of the chain (aka the committee)"
1693);
1694doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1695doc_scalar!(
1696 Timestamp,
1697 "A timestamp, in microseconds since the Unix epoch"
1698);
1699doc_scalar!(TimeDelta, "A duration in microseconds");
1700doc_scalar!(
1701 Round,
1702 "A number to identify successive attempts to decide a value in a consensus protocol."
1703);
1704doc_scalar!(
1705 ChainDescription,
1706 "Initial chain configuration and chain origin."
1707);
1708doc_scalar!(OracleResponse, "A record of a single oracle response.");
1709doc_scalar!(BlobContent, "A blob of binary data.");
1710doc_scalar!(
1711 Blob,
1712 "A blob of binary data, with its content-addressed blob ID."
1713);
1714doc_scalar!(ApplicationDescription, "Description of a user application");
1715
1716#[cfg(with_metrics)]
1717mod metrics {
1718 use std::sync::LazyLock;
1719
1720 use prometheus::HistogramVec;
1721
1722 use crate::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
1723
1724 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1726 register_histogram_vec(
1727 "bytecode_compression_latency",
1728 "Bytecode compression latency",
1729 &[],
1730 exponential_bucket_latencies(10.0),
1731 )
1732 });
1733
1734 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1736 register_histogram_vec(
1737 "bytecode_decompression_latency",
1738 "Bytecode decompression latency",
1739 &[],
1740 exponential_bucket_latencies(10.0),
1741 )
1742 });
1743}
1744
1745#[cfg(test)]
1746mod tests {
1747 use std::str::FromStr;
1748
1749 use alloy_primitives::U256;
1750
1751 use super::{Amount, BlobContent};
1752 use crate::identifiers::BlobType;
1753
1754 #[test]
1755 fn display_amount() {
1756 assert_eq!("1.", Amount::ONE.to_string());
1757 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1758 assert_eq!(
1759 Amount(10_000_000_000_000_000_000),
1760 Amount::from_str("10").unwrap()
1761 );
1762 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1763 assert_eq!(
1764 "1001.3",
1765 (Amount::from_str("1.1")
1766 .unwrap()
1767 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1768 .to_string()
1769 );
1770 assert_eq!(
1771 " 1.00000000000000000000",
1772 format!("{:25.20}", Amount::ONE)
1773 );
1774 assert_eq!(
1775 "~+12.34~~",
1776 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1777 );
1778 }
1779
1780 #[test]
1781 fn blob_content_serialization_deserialization() {
1782 let test_data = b"Hello, world!".as_slice();
1783 let original_blob = BlobContent::new(BlobType::Data, test_data);
1784
1785 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1786 let deserialized: BlobContent =
1787 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1788 assert_eq!(original_blob, deserialized);
1789
1790 let serialized =
1791 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1792 let deserialized: BlobContent =
1793 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1794 assert_eq!(original_blob, deserialized);
1795 }
1796
1797 #[test]
1798 fn blob_content_hash_consistency() {
1799 let test_data = b"Hello, world!";
1800 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1801 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1802
1803 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1805 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1806
1807 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1808 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1809 }
1810
1811 #[test]
1812 fn test_conversion_amount_u256() {
1813 let value_amount = Amount::from_tokens(15656565652209004332);
1814 let value_u256: U256 = value_amount.into();
1815 let value_amount_rev = Amount::try_from(value_u256).expect("Failed conversion");
1816 assert_eq!(value_amount, value_amount_rev);
1817 }
1818}