#[cfg(with_testing)]
use std::ops;
#[cfg(with_metrics)]
use std::sync::LazyLock;
use std::{
fmt::{self, Display},
fs,
hash::{Hash, Hasher},
io, iter,
num::ParseIntError,
path::Path,
str::FromStr,
};
use anyhow::Context as _;
use async_graphql::InputObject;
use base64::engine::{general_purpose::STANDARD_NO_PAD, Engine as _};
use custom_debug_derive::Debug;
use linera_witty::{WitLoad, WitStore, WitType};
#[cfg(with_metrics)]
use prometheus::HistogramVec;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
#[cfg(with_metrics)]
use crate::prometheus_util::{self, MeasureLatency};
use crate::{
crypto::BcsHashable,
doc_scalar, hex_debug,
identifiers::{
ApplicationId, BlobId, BlobType, BytecodeId, Destination, GenericApplicationId, MessageId,
UserApplicationId,
},
limited_writer::{LimitedWriter, LimitedWriterError},
time::{Duration, SystemTime},
};
#[derive(
Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, WitType, WitLoad, WitStore,
)]
pub struct Amount(u128);
#[derive(Serialize, Deserialize)]
#[serde(rename = "Amount")]
struct AmountString(String);
#[derive(Serialize, Deserialize)]
#[serde(rename = "Amount")]
struct AmountU128(u128);
impl Serialize for Amount {
fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
AmountString(self.to_string()).serialize(serializer)
} else {
AmountU128(self.0).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for Amount {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let AmountString(s) = AmountString::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
} else {
Ok(Amount(AmountU128::deserialize(deserializer)?.0))
}
}
}
#[derive(
Eq,
PartialEq,
Ord,
PartialOrd,
Copy,
Clone,
Hash,
Default,
Debug,
Serialize,
Deserialize,
WitType,
WitLoad,
WitStore,
)]
#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
pub struct BlockHeight(pub u64);
#[derive(
Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Serialize, Deserialize,
)]
pub enum Round {
#[default]
Fast,
MultiLeader(u32),
SingleLeader(u32),
Validator(u32),
}
#[derive(
Eq,
PartialEq,
Ord,
PartialOrd,
Copy,
Clone,
Hash,
Default,
Debug,
Serialize,
Deserialize,
WitType,
WitLoad,
WitStore,
)]
pub struct TimeDelta(u64);
impl TimeDelta {
pub fn from_micros(micros: u64) -> Self {
TimeDelta(micros)
}
pub fn from_millis(millis: u64) -> Self {
TimeDelta(millis.saturating_mul(1_000))
}
pub fn from_secs(secs: u64) -> Self {
TimeDelta(secs.saturating_mul(1_000_000))
}
pub fn from_duration(duration: Duration) -> Self {
TimeDelta::from_micros(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX))
}
pub fn as_micros(&self) -> u64 {
self.0
}
pub fn as_duration(&self) -> Duration {
Duration::from_micros(self.as_micros())
}
}
#[derive(
Eq,
PartialEq,
Ord,
PartialOrd,
Copy,
Clone,
Hash,
Default,
Debug,
Serialize,
Deserialize,
WitType,
WitLoad,
WitStore,
)]
pub struct Timestamp(u64);
impl Timestamp {
pub fn now() -> Timestamp {
Timestamp(
SystemTime::UNIX_EPOCH
.elapsed()
.expect("system time should be after Unix epoch")
.as_micros()
.try_into()
.unwrap_or(u64::MAX),
)
}
pub fn micros(&self) -> u64 {
self.0
}
pub fn delta_since(&self, other: Timestamp) -> TimeDelta {
TimeDelta::from_micros(self.0.saturating_sub(other.0))
}
pub fn duration_since(&self, other: Timestamp) -> Duration {
Duration::from_micros(self.0.saturating_sub(other.0))
}
pub fn saturating_add(&self, duration: TimeDelta) -> Timestamp {
Timestamp(self.0.saturating_add(duration.0))
}
pub fn saturating_sub(&self, duration: TimeDelta) -> Timestamp {
Timestamp(self.0.saturating_sub(duration.0))
}
pub fn saturating_add_micros(&self, micros: u64) -> Timestamp {
Timestamp(self.0.saturating_add(micros))
}
pub fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
Timestamp(self.0.saturating_sub(micros))
}
}
impl From<u64> for Timestamp {
fn from(t: u64) -> Timestamp {
Timestamp(t)
}
}
impl Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(date_time) = chrono::DateTime::from_timestamp(
(self.0 / 1_000_000) as i64,
((self.0 % 1_000_000) * 1_000) as u32,
) {
return date_time.naive_utc().fmt(f);
}
self.0.fmt(f)
}
}
#[derive(
Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, WitLoad, WitStore, WitType,
)]
pub struct Resources {
pub fuel: u64,
pub read_operations: u32,
pub write_operations: u32,
pub bytes_to_read: u32,
pub bytes_to_write: u32,
pub messages: u32,
pub message_size: u32,
pub storage_size_delta: u32,
}
#[derive(Clone, Debug, Deserialize, Serialize, WitLoad, WitType)]
#[cfg_attr(with_testing, derive(Eq, PartialEq, WitStore))]
#[witty_specialize_with(Message = Vec<u8>)]
pub struct SendMessageRequest<Message> {
pub destination: Destination,
pub authenticated: bool,
pub is_tracked: bool,
pub grant: Resources,
pub message: Message,
}
impl<Message> SendMessageRequest<Message>
where
Message: Serialize,
{
pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
SendMessageRequest {
destination: self.destination,
authenticated: self.authenticated,
is_tracked: self.is_tracked,
grant: self.grant,
message,
}
}
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum ArithmeticError {
#[error("Number overflow")]
Overflow,
#[error("Number underflow")]
Underflow,
}
macro_rules! impl_wrapped_number {
($name:ident, $wrapped:ident) => {
impl $name {
pub const ZERO: Self = Self(0);
pub const MAX: Self = Self($wrapped::MAX);
pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
let val = self
.0
.checked_add(other.0)
.ok_or(ArithmeticError::Overflow)?;
Ok(Self(val))
}
pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
Ok(Self(val))
}
pub fn saturating_add(self, other: Self) -> Self {
let val = self.0.saturating_add(other.0);
Self(val)
}
pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
let val = self
.0
.checked_sub(other.0)
.ok_or(ArithmeticError::Underflow)?;
Ok(Self(val))
}
pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
Ok(Self(val))
}
pub fn saturating_sub(self, other: Self) -> Self {
let val = self.0.saturating_sub(other.0);
Self(val)
}
pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
self.0 = self
.0
.checked_add(other.0)
.ok_or(ArithmeticError::Overflow)?;
Ok(())
}
pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
Ok(())
}
pub fn saturating_add_assign(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0);
}
pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
self.0 = self
.0
.checked_sub(other.0)
.ok_or(ArithmeticError::Underflow)?;
Ok(())
}
pub fn saturating_mul(&self, other: $wrapped) -> Self {
Self(self.0.saturating_mul(other))
}
pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
Ok(Self(val))
}
pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
Ok(())
}
}
impl From<$name> for $wrapped {
fn from(value: $name) -> Self {
value.0
}
}
#[cfg(with_testing)]
impl From<$wrapped> for $name {
fn from(value: $wrapped) -> Self {
Self(value)
}
}
#[cfg(with_testing)]
impl ops::Add for $name {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
#[cfg(with_testing)]
impl ops::Sub for $name {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self(self.0 - other.0)
}
}
#[cfg(with_testing)]
impl ops::Mul<$wrapped> for $name {
type Output = Self;
fn mul(self, other: $wrapped) -> Self {
Self(self.0 * other)
}
}
};
}
impl TryFrom<BlockHeight> for usize {
type Error = ArithmeticError;
fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
}
}
#[cfg(not(with_testing))]
impl From<u64> for BlockHeight {
fn from(value: u64) -> Self {
Self(value)
}
}
impl_wrapped_number!(Amount, u128);
impl_wrapped_number!(BlockHeight, u64);
impl_wrapped_number!(TimeDelta, u64);
impl Display for Amount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let places = Amount::DECIMAL_PLACES as usize;
let min_digits = places + 1;
let decimals = format!("{:0min_digits$}", self.0);
let integer_part = &decimals[..(decimals.len() - places)];
let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
let precision = f.precision().unwrap_or(0).max(fractional_part.len());
let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
let pad_width = f.width().map_or(0, |w| {
w.saturating_sub(precision)
.saturating_sub(sign.len() + integer_part.len() + 1)
});
let left_pad = match f.align() {
None | Some(fmt::Alignment::Right) => pad_width,
Some(fmt::Alignment::Center) => pad_width / 2,
Some(fmt::Alignment::Left) => 0,
};
for _ in 0..left_pad {
write!(f, "{}", f.fill())?;
}
write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
for _ in left_pad..pad_width {
write!(f, "{}", f.fill())?;
}
Ok(())
}
}
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum ParseAmountError {
#[error("cannot parse amount")]
Parse,
#[error("cannot represent amount: number too high")]
TooHigh,
#[error("cannot represent amount: too many decimal places after the point")]
TooManyDigits,
}
impl FromStr for Amount {
type Err = ParseAmountError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
let mut result: u128 = 0;
let mut decimals: Option<u8> = None;
let mut chars = src.trim().chars().peekable();
if chars.peek() == Some(&'+') {
chars.next();
}
for char in chars {
match char {
'_' => {}
'.' if decimals.is_some() => return Err(ParseAmountError::Parse),
'.' => decimals = Some(Amount::DECIMAL_PLACES),
char => {
let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
if let Some(d) = &mut decimals {
*d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
}
result = result
.checked_mul(10)
.and_then(|r| r.checked_add(digit))
.ok_or(ParseAmountError::TooHigh)?;
}
}
}
result = result
.checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
.ok_or(ParseAmountError::TooHigh)?;
Ok(Amount(result))
}
}
impl Display for BlockHeight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for BlockHeight {
type Err = ParseIntError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(Self(u64::from_str(src)?))
}
}
impl Display for Round {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Round::Fast => write!(f, "fast round"),
Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
Round::Validator(r) => write!(f, "validator round {}", r),
}
}
}
impl Round {
pub fn is_multi_leader(&self) -> bool {
matches!(self, Round::MultiLeader(_))
}
pub fn is_fast(&self) -> bool {
matches!(self, Round::Fast)
}
pub fn number(&self) -> u32 {
match self {
Round::Fast => 0,
Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Round::Fast => "fast",
Round::MultiLeader(_) => "multi",
Round::SingleLeader(_) => "single",
Round::Validator(_) => "validator",
}
}
}
impl<'a> iter::Sum<&'a Amount> for Amount {
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
}
}
impl Amount {
pub const DECIMAL_PLACES: u8 = 18;
pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
pub fn from_tokens(tokens: u128) -> Amount {
Self::ONE.saturating_mul(tokens)
}
pub fn from_millis(millitokens: u128) -> Amount {
Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
}
pub fn from_micros(microtokens: u128) -> Amount {
Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
}
pub fn from_nanos(nanotokens: u128) -> Amount {
Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
}
pub fn from_attos(attotokens: u128) -> Amount {
Amount(attotokens)
}
pub fn upper_half(self) -> u64 {
(self.0 >> 64) as u64
}
pub fn lower_half(self) -> u64 {
self.0 as u64
}
pub fn saturating_div(self, other: Amount) -> u128 {
self.0.checked_div(other.0).unwrap_or(u128::MAX)
}
}
#[derive(
Default,
Debug,
PartialEq,
Eq,
Hash,
Clone,
Serialize,
Deserialize,
WitType,
WitLoad,
WitStore,
InputObject,
)]
pub struct ApplicationPermissions {
pub execute_operations: Option<Vec<ApplicationId>>,
#[graphql(default)]
pub mandatory_applications: Vec<ApplicationId>,
#[graphql(default)]
pub close_chain: Vec<ApplicationId>,
}
impl ApplicationPermissions {
pub fn new_single(app_id: ApplicationId) -> Self {
Self {
execute_operations: Some(vec![app_id]),
mandatory_applications: vec![app_id],
close_chain: vec![app_id],
}
}
pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
match (app_id, &self.execute_operations) {
(_, None) => true,
(GenericApplicationId::System, Some(_)) => false,
(GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
}
}
pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
self.close_chain.contains(app_id)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum OracleResponse {
Service(Vec<u8>),
Post(Vec<u8>),
Blob(BlobId),
Assert,
}
impl OracleResponse {
pub fn is_permitted_in_fast_blocks(&self) -> bool {
matches!(self, OracleResponse::Blob(_))
}
}
impl Display for OracleResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OracleResponse::Service(bytes) => {
write!(f, "Service:{}", STANDARD_NO_PAD.encode(bytes))?
}
OracleResponse::Post(bytes) => write!(f, "Post:{}", STANDARD_NO_PAD.encode(bytes))?,
OracleResponse::Blob(blob_id) => write!(f, "Blob:{}", blob_id)?,
OracleResponse::Assert => write!(f, "Assert")?,
};
Ok(())
}
}
impl FromStr for OracleResponse {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(string) = s.strip_prefix("Service:") {
return Ok(OracleResponse::Service(
STANDARD_NO_PAD.decode(string).context("Invalid base64")?,
));
}
if let Some(string) = s.strip_prefix("Post:") {
return Ok(OracleResponse::Post(
STANDARD_NO_PAD.decode(string).context("Invalid base64")?,
));
}
if let Some(string) = s.strip_prefix("Blob:") {
return Ok(OracleResponse::Blob(
BlobId::from_str(string).context("Invalid BlobId")?,
));
}
Err(anyhow::anyhow!("Invalid enum! Enum: {}", s))
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
pub struct UserApplicationDescription {
pub bytecode_id: BytecodeId,
pub creation: MessageId,
#[serde(with = "serde_bytes")]
#[debug(with = "hex_debug")]
pub parameters: Vec<u8>,
pub required_application_ids: Vec<UserApplicationId>,
}
impl From<&UserApplicationDescription> for UserApplicationId {
fn from(description: &UserApplicationDescription) -> Self {
UserApplicationId {
bytecode_id: description.bytecode_id,
creation: description.creation,
}
}
}
#[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Bytecode {
#[serde(with = "serde_bytes")]
pub bytes: Vec<u8>,
}
impl Bytecode {
pub fn new(bytes: Vec<u8>) -> Self {
Bytecode { bytes }
}
pub async fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
let bytes = fs::read(path)?;
Ok(Bytecode { bytes })
}
#[cfg(not(target_arch = "wasm32"))]
pub fn compress(&self) -> CompressedBytecode {
#[cfg(with_metrics)]
let _compression_latency = BYTECODE_COMPRESSION_LATENCY.measure_latency();
let compressed_bytes = zstd::stream::encode_all(&*self.bytes, 19)
.expect("Compressing bytes in memory should not fail");
CompressedBytecode { compressed_bytes }
}
}
impl AsRef<[u8]> for Bytecode {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl fmt::Debug for Bytecode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("Bytecode").finish_non_exhaustive()
}
}
#[derive(Error, Debug)]
pub enum DecompressionError {
#[error("Bytecode could not be decompressed: {0}")]
InvalidCompressedBytecode(#[from] io::Error),
}
#[derive(Clone, Deserialize, Hash, Serialize, WitType, WitStore)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub struct CompressedBytecode {
#[serde(with = "serde_bytes")]
pub compressed_bytes: Vec<u8>,
}
#[cfg(not(target_arch = "wasm32"))]
impl CompressedBytecode {
pub fn decompressed_size_at_most(&self, limit: u64) -> Result<bool, DecompressionError> {
let mut decoder = zstd::stream::Decoder::new(&*self.compressed_bytes)?;
let limit = usize::try_from(limit).unwrap_or(usize::MAX);
let mut writer = LimitedWriter::new(io::sink(), limit);
match io::copy(&mut decoder, &mut writer) {
Ok(_) => Ok(true),
Err(error) => {
error.downcast::<LimitedWriterError>()?;
Ok(false)
}
}
}
pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
#[cfg(with_metrics)]
let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
let bytes = zstd::stream::decode_all(&*self.compressed_bytes)?;
Ok(Bytecode { bytes })
}
}
#[cfg(target_arch = "wasm32")]
impl CompressedBytecode {
pub fn decompressed_size_at_most(&self, limit: u64) -> Result<bool, DecompressionError> {
let compressed_bytes = &*self.compressed_bytes;
let limit = usize::try_from(limit).unwrap_or(usize::MAX);
let mut writer = LimitedWriter::new(io::sink(), limit);
let mut decoder = ruzstd::streaming_decoder::StreamingDecoder::new(compressed_bytes)
.map_err(io::Error::other)?;
match io::copy(&mut decoder, &mut writer) {
Ok(_) => Ok(true),
Err(error) => {
error.downcast::<LimitedWriterError>()?;
Ok(false)
}
}
}
pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
use ruzstd::{io::Read, streaming_decoder::StreamingDecoder};
#[cfg(with_metrics)]
let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
let compressed_bytes = &*self.compressed_bytes;
let mut bytes = Vec::new();
let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
while !decoder.get_ref().is_empty() {
decoder
.read_to_end(&mut bytes)
.expect("Reading from a slice in memory should not result in IO errors");
}
Ok(Bytecode { bytes })
}
}
impl fmt::Debug for CompressedBytecode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CompressedBytecode").finish_non_exhaustive()
}
}
#[derive(Clone, Serialize, Deserialize, WitType, WitStore)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
#[repr(transparent)]
pub struct BlobBytes(#[serde(with = "serde_bytes")] pub Vec<u8>);
impl BcsHashable for BlobBytes {}
impl Hash for BlobBytes {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[derive(Hash, Clone, Serialize, Deserialize, WitType, WitStore)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub enum BlobContent {
Data(#[serde(with = "serde_bytes")] Vec<u8>),
ContractBytecode(CompressedBytecode),
ServiceBytecode(CompressedBytecode),
}
impl fmt::Debug for BlobContent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BlobContent::Data(_) => write!(f, "BlobContent::Data"),
BlobContent::ContractBytecode(_) => write!(f, "BlobContent::ContractBytecode"),
BlobContent::ServiceBytecode(_) => write!(f, "BlobContent::ServiceBytecode"),
}
}
}
impl BlobContent {
pub fn new_with_id_unchecked(id: BlobId, bytes: Vec<u8>) -> Self {
match id.blob_type {
BlobType::Data => BlobContent::Data(bytes),
BlobType::ContractBytecode => BlobContent::ContractBytecode(CompressedBytecode {
compressed_bytes: bytes,
}),
BlobType::ServiceBytecode => BlobContent::ServiceBytecode(CompressedBytecode {
compressed_bytes: bytes,
}),
}
}
pub fn new_data(bytes: Vec<u8>) -> Self {
BlobContent::Data(bytes)
}
pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
BlobContent::ContractBytecode(compressed_bytecode)
}
pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
BlobContent::ServiceBytecode(compressed_bytecode)
}
pub fn with_blob_id_unchecked(self, blob_id: BlobId) -> Blob {
Blob {
id: blob_id,
content: self,
}
}
pub fn with_blob_id_checked(self, blob_id: BlobId) -> Option<Blob> {
match blob_id.blob_type {
BlobType::Data if matches!(&self, BlobContent::Data(_)) => Some(()),
BlobType::ContractBytecode if matches!(&self, BlobContent::ContractBytecode(_)) => {
Some(())
}
BlobType::ServiceBytecode if matches!(&self, BlobContent::ServiceBytecode(_)) => {
Some(())
}
_ => None,
}?;
let expected_blob_id = BlobId::from_content(&self);
if blob_id == expected_blob_id {
Some(self.with_blob_id_unchecked(expected_blob_id))
} else {
None
}
}
pub fn inner_bytes(&self) -> Vec<u8> {
match self {
BlobContent::Data(bytes) => bytes,
BlobContent::ContractBytecode(compressed_bytecode) => {
&compressed_bytecode.compressed_bytes
}
BlobContent::ServiceBytecode(compressed_bytecode) => {
&compressed_bytecode.compressed_bytes
}
}
.clone()
}
pub fn blob_bytes(&self) -> BlobBytes {
BlobBytes(self.inner_bytes())
}
pub fn size(&self) -> usize {
match self {
BlobContent::Data(bytes) => bytes.len(),
BlobContent::ContractBytecode(compressed_bytecode)
| BlobContent::ServiceBytecode(compressed_bytecode) => {
compressed_bytecode.compressed_bytes.len()
}
}
}
}
impl From<Blob> for BlobContent {
fn from(blob: Blob) -> BlobContent {
blob.content
}
}
impl From<BlobContent> for Blob {
fn from(content: BlobContent) -> Blob {
Self {
id: BlobId::from_content(&content),
content,
}
}
}
#[derive(Debug, Hash, Clone, WitType, WitStore)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub struct Blob {
id: BlobId,
content: BlobContent,
}
impl Blob {
pub fn new_with_id_unchecked(id: BlobId, bytes: Vec<u8>) -> Self {
BlobContent::new_with_id_unchecked(id, bytes).into()
}
pub fn new_data(bytes: Vec<u8>) -> Self {
BlobContent::new_data(bytes).into()
}
pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
BlobContent::new_contract_bytecode(compressed_bytecode).into()
}
pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
BlobContent::new_service_bytecode(compressed_bytecode).into()
}
pub fn id(&self) -> BlobId {
self.id
}
pub fn content(&self) -> &BlobContent {
&self.content
}
pub fn into_inner_content(self) -> BlobContent {
self.content
}
pub fn inner_bytes(&self) -> Vec<u8> {
self.content.inner_bytes()
}
pub async fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
Ok(Self::new_data(fs::read(path)?))
}
}
impl Serialize for Blob {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&hex::encode(blob_bytes))
} else {
BlobContent::serialize(self.content(), serializer)
}
}
}
impl<'a> Deserialize<'a> for Blob {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
let content: BlobContent =
bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
Ok(Blob {
id: BlobId::from_content(&content),
content,
})
} else {
let content = BlobContent::deserialize(deserializer)?;
Ok(Blob {
id: BlobId::from_content(&content),
content,
})
}
}
}
doc_scalar!(Bytecode, "A WebAssembly module's bytecode");
doc_scalar!(Amount, "A non-negative amount of tokens.");
doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
doc_scalar!(
Timestamp,
"A timestamp, in microseconds since the Unix epoch"
);
doc_scalar!(TimeDelta, "A duration in microseconds");
doc_scalar!(
Round,
"A number to identify successive attempts to decide a value in a consensus protocol."
);
doc_scalar!(OracleResponse, "A record of a single oracle response.");
doc_scalar!(BlobContent, "A blob of binary data.");
doc_scalar!(
Blob,
"A blob of binary data, with its content-addressed blob ID."
);
doc_scalar!(
UserApplicationDescription,
"Description of the necessary information to run a user application"
);
#[cfg(with_metrics)]
static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
prometheus_util::register_histogram_vec(
"bytecode_compression_latency",
"Bytecode compression latency",
&[],
Some(vec![
0.000_1, 0.000_25, 0.000_5, 0.001, 0.002_5, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5,
1.0, 2.5, 5.0, 10.0,
]),
)
.expect("Histogram creation should not fail")
});
#[cfg(with_metrics)]
static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
prometheus_util::register_histogram_vec(
"bytecode_decompression_latency",
"Bytecode decompression latency",
&[],
Some(vec![
0.000_1, 0.000_25, 0.000_5, 0.001, 0.002_5, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5,
1.0, 2.5, 5.0, 10.0,
]),
)
.expect("Histogram creation should not fail")
});
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::Amount;
#[test]
fn display_amount() {
assert_eq!("1.", Amount::ONE.to_string());
assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
assert_eq!(
Amount(10_000_000_000_000_000_000),
Amount::from_str("10").unwrap()
);
assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
assert_eq!(
"1001.3",
(Amount::from_str("1.1")
.unwrap()
.saturating_add(Amount::from_str("1_000.2").unwrap()))
.to_string()
);
assert_eq!(
" 1.00000000000000000000",
format!("{:25.20}", Amount::ONE)
);
assert_eq!(
"~+12.34~~",
format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
);
}
}