arete_server/
program_runtime.rs1use anyhow::{bail, Result};
2use std::collections::HashMap;
3use std::sync::Arc;
4
5pub type ProgramSpecHash = arete_hash::HashId<arete_hash::ProgramSpec>;
6pub type IdlContentHash = arete_hash::HashId<arete_hash::IdlContent>;
7pub type NormalizedIdlHash = arete_hash::HashId<arete_hash::IdlNormalized>;
8pub type ProgramReleaseHash = arete_hash::HashId<arete_hash::ProgramRelease>;
9
10pub type ProgramAccountReaderFn =
15 Arc<dyn Fn(&str, &[u8]) -> Result<serde_json::Value> + Send + Sync>;
16
17#[derive(Clone)]
18pub struct ProgramRuntimeDefinition {
19 pub program_id: String,
20 pub program_spec_hash: ProgramSpecHash,
21 pub idl_content_hash: IdlContentHash,
22 pub normalized_idl_hash: NormalizedIdlHash,
23 pub program_release_hash: ProgramReleaseHash,
24 pub account_reader: ProgramAccountReaderFn,
25}
26
27impl ProgramRuntimeDefinition {
28 fn validate(&self) -> Result<()> {
29 let expected = arete_hash::OssGeneratedProgramReleaseV1::new(
30 self.program_id.clone(),
31 self.program_spec_hash,
32 self.idl_content_hash,
33 self.normalized_idl_hash,
34 )
35 .hash()?;
36 if expected != self.program_release_hash {
37 bail!(
38 "program runtime definition release hash does not match its public identity fields"
39 );
40 }
41 Ok(())
42 }
43
44 fn is_exact_duplicate_of(&self, other: &Self) -> bool {
45 self.program_id == other.program_id
46 && self.program_spec_hash == other.program_spec_hash
47 && self.idl_content_hash == other.idl_content_hash
48 && self.normalized_idl_hash == other.normalized_idl_hash
49 && Arc::ptr_eq(&self.account_reader, &other.account_reader)
50 }
51}
52
53#[derive(Clone, Default)]
54pub struct ProgramRuntimeCatalog {
55 definitions: HashMap<ProgramReleaseHash, ProgramRuntimeDefinition>,
56}
57
58impl ProgramRuntimeCatalog {
59 pub fn try_new(definitions: Vec<ProgramRuntimeDefinition>) -> Result<Self> {
60 let mut catalog = Self::default();
61 for definition in definitions {
62 definition.validate()?;
63 if let Some(existing) = catalog.definitions.get(&definition.program_release_hash) {
64 if !existing.is_exact_duplicate_of(&definition) {
65 bail!("conflicting program runtime definitions use the same release hash");
66 }
67 continue;
68 }
69 catalog
70 .definitions
71 .insert(definition.program_release_hash, definition);
72 }
73 Ok(catalog)
74 }
75
76 pub fn get(&self, release_hash: &ProgramReleaseHash) -> Option<&ProgramRuntimeDefinition> {
77 self.definitions.get(release_hash)
78 }
79
80 pub fn len(&self) -> usize {
81 self.definitions.len()
82 }
83
84 pub fn is_empty(&self) -> bool {
85 self.definitions.is_empty()
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn definition(program_id: &str, reader: ProgramAccountReaderFn) -> ProgramRuntimeDefinition {
94 let program_spec_hash = ProgramSpecHash::from_digest([1; 32]);
95 let idl_content_hash = IdlContentHash::from_digest([2; 32]);
96 let normalized_idl_hash = NormalizedIdlHash::from_digest([3; 32]);
97 let program_release_hash = arete_hash::OssGeneratedProgramReleaseV1::new(
98 program_id,
99 program_spec_hash,
100 idl_content_hash,
101 normalized_idl_hash,
102 )
103 .hash()
104 .unwrap();
105 ProgramRuntimeDefinition {
106 program_id: program_id.to_string(),
107 program_spec_hash,
108 idl_content_hash,
109 normalized_idl_hash,
110 program_release_hash,
111 account_reader: reader,
112 }
113 }
114
115 #[test]
116 fn catalog_accepts_exact_duplicates_and_rejects_conflicts() {
117 let reader: ProgramAccountReaderFn = Arc::new(|_, _| Ok(serde_json::Value::Null));
118 let first = definition("Program111", reader.clone());
119 let duplicate = first.clone();
120 assert_eq!(
121 ProgramRuntimeCatalog::try_new(vec![first.clone(), duplicate])
122 .unwrap()
123 .len(),
124 1
125 );
126
127 let mut conflict = first.clone();
128 conflict.account_reader = Arc::new(|_, _| Ok(serde_json::Value::Bool(true)));
129 assert!(ProgramRuntimeCatalog::try_new(vec![first, conflict]).is_err());
130 }
131
132 #[test]
133 fn catalog_rejects_a_release_hash_mismatch() {
134 let reader: ProgramAccountReaderFn = Arc::new(|_, _| Ok(serde_json::Value::Null));
135 let mut definition = definition("Program111", reader);
136 definition.program_release_hash = ProgramReleaseHash::from_digest([9; 32]);
137 assert!(ProgramRuntimeCatalog::try_new(vec![definition]).is_err());
138 }
139}