use crate::item_hash::ItemHash;
use crate::message::execution::base::{Encoding, ExecutableContent, Interface};
use crate::message::execution::environment::{FunctionEnvironment, FunctionTriggers};
use crate::toolkit::serde::{default_some_false, default_true};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionRuntime {
#[serde(rename = "ref")]
pub reference: ItemHash,
#[serde(default = "default_true")]
pub use_latest: bool,
pub comment: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodeContent {
pub encoding: Encoding,
pub entrypoint: String,
#[serde(rename = "ref")]
pub reference: ItemHash,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interface: Option<Interface>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub args: Option<Vec<String>>,
#[serde(default)]
pub use_latest: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DataContent {
pub encoding: Encoding,
pub mount: PathBuf,
#[serde(rename = "ref")]
pub reference: ItemHash,
#[serde(
default = "default_some_false",
skip_serializing_if = "Option::is_none"
)]
pub use_latest: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Export {
pub encoding: Encoding,
pub mount: PathBuf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ProgramType {
#[default]
#[serde(rename = "vm-function")]
VmFunction,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProgramContent {
#[serde(default, rename = "type")]
pub program_type: ProgramType,
#[serde(flatten)]
pub base: ExecutableContent,
pub code: CodeContent,
pub runtime: FunctionRuntime,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<DataContent>,
pub environment: FunctionEnvironment,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub export: Option<Export>,
pub on: FunctionTriggers,
}
impl ProgramContent {
pub fn executable_content(&self) -> &ExecutableContent {
&self.base
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain::{Address, Chain, Signature};
use crate::message::base_message::MessageContentEnum;
use crate::message::execution::environment::MachineResources;
use crate::message::execution::volume::{BaseVolume, ImmutableVolume, MachineVolume};
use crate::message::{ContentSource, Message, MessageType};
use crate::timestamp::Timestamp;
use crate::{channel, item_hash};
use assert_matches::assert_matches;
use memsizes::MiB;
use std::collections::HashMap;
const PROGRAM_FIXTURE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../fixtures/messages/program/program.json"
));
const PROGRAM_WITH_EMPTY_ARRAY_AS_METADATA: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../fixtures/messages/program/program-with-array-as-metadata.json"
));
#[test]
fn test_deserialize_program_message() {
let message: Message = serde_json::from_str(PROGRAM_FIXTURE).unwrap();
assert_eq!(
message.sender,
Address::from("0x9C2FD74F9CA2B7C4941690316B0Ebc35ce55c885".to_string())
);
assert_eq!(message.chain, Chain::Ethereum);
assert_eq!(
message.signature,
Some(Signature::from(
"0x421c656709851fba752f323a117bc7a07f175a4dd7faf1d8fc1cd9a99028081a6419f9e8b0a7cd454bfef1c52d1f0675a7a59a7d07eb4ebdb22e18bbaf415f881c".to_string()
))
);
assert_matches!(message.message_type, MessageType::Program);
assert_matches!(
message.content_source,
ContentSource::Inline { item_content: _ }
);
assert_eq!(
&message.item_hash.to_string(),
"acab01087137c68a5e84734e75145482651accf3bea80fb9b723b761639ecc1c"
);
assert_eq!(message.time, Timestamp::from(1757026128.773));
assert_eq!(message.channel, Some(channel!("ALEPH-CLOUDSOLUTIONS")));
assert_eq!(
&message.content.address,
&Address::from("0x9C2FD74F9CA2B7C4941690316B0Ebc35ce55c885".to_string())
);
assert_eq!(&message.content.time, &Timestamp::from(1757026128.773));
assert_eq!(message.sent_at(), &message.content.time);
let program_content = match message.content() {
MessageContentEnum::Program(content) => content,
other => {
panic!("Expected MessageContentEnum::Program, got {:?}", other);
}
};
assert!(!program_content.base.allow_amend);
assert_eq!(
program_content.base.metadata,
Some(HashMap::from([(
"name".to_string(),
serde_json::Value::String("Hoymiles".to_string())
)]))
);
assert_eq!(program_content.base.variables, Some(HashMap::new()));
assert_eq!(
program_content.base.resources,
MachineResources {
vcpus: 2,
memory: MiB::from(4096),
seconds: 30,
published_ports: None,
}
);
assert_matches!(program_content.base.authorized_keys, None);
assert_eq!(
program_content.environment,
FunctionEnvironment {
reproducible: false,
internet: true,
aleph_api: true,
shared_cache: false,
}
);
assert_eq!(
program_content.base.volumes,
vec![MachineVolume::Immutable(ImmutableVolume {
base: BaseVolume {
comment: None,
mount: Some(PathBuf::from("/opt/packages"))
},
reference: item_hash!(
"8df728d560ed6e9103b040a6b5fc5417e0a52e890c12977464ebadf9becf1bf6"
),
use_latest: true,
})]
);
assert_eq!(program_content.base.replaces, None);
assert_eq!(
program_content.code,
CodeContent {
encoding: Encoding::Zip,
entrypoint: "main:app".to_string(),
reference: item_hash!(
"9a4735bca0d3f7032ddd6659c35387b57b470550c931841e6862ece4e9e6523e"
),
interface: None,
args: None,
use_latest: true,
}
);
assert_eq!(
program_content.runtime,
FunctionRuntime {
reference: item_hash!(
"63f07193e6ee9d207b7d1fcf8286f9aee34e6f12f101d2ec77c1229f92964696"
),
use_latest: true,
comment: "Aleph Alpine Linux with Python 3.12".to_string(),
}
);
assert_eq!(program_content.data, None);
assert_eq!(program_content.export, None);
assert_eq!(
program_content.on,
FunctionTriggers {
http: true,
persistent: Some(false)
}
);
assert!(!message.confirmed());
assert!(message.confirmed_at().is_none());
assert!(message.confirmations.is_empty());
message.verify_item_hash().unwrap();
}
#[test]
fn load_program_with_empty_array_as_metadata() {
let message: Message = serde_json::from_str(PROGRAM_WITH_EMPTY_ARRAY_AS_METADATA).unwrap();
let program_content = match message.content() {
MessageContentEnum::Program(content) => content,
other => {
panic!("Expected MessageContentEnum::Program, got {:?}", other);
}
};
assert_matches!(program_content.base.metadata, Some(ref map) if map.is_empty());
}
}