1use std::{
29 collections::BTreeMap,
30 convert::Infallible,
31 fmt,
32 ops::Deref,
33 str::FromStr,
34 sync::Arc,
35 time::{Duration, SystemTime, UNIX_EPOCH},
36};
37
38use borsh::{BorshDeserialize, BorshSerialize};
39#[cfg(feature = "non-pdk")]
40use clap::Subcommand;
41#[cfg(feature = "non-pdk")]
42use fastcrypto::encoding::{Base64, Encoding};
43use rialo_cli_representable::Representable;
44use rialo_limits::{max_rex_output_serialized_bytes, MIN_VIABLE_LIMIT_OF_REX_OUTPUT_SIZE};
45use rialo_s_pubkey::Pubkey;
46use serde::{Deserialize, Serialize};
47use serde_big_array::BigArray;
48#[cfg(feature = "non-pdk")]
49use url::Url;
50
51use crate::{
52 websocket_op::WebSocketOperation, AttestationReport, AuthorityKeyBytes, Headers, HttpFilter,
53 Nonce, RexDutyConfig,
54};
55
56pub type TimestampMs = u64;
59
60const MIN_UPDATE_PERIOD_MS: TimestampMs = 50;
64
65#[derive(
80 Debug,
81 Default,
82 Clone,
83 Copy,
84 PartialEq,
85 Eq,
86 Hash,
87 PartialOrd,
88 Ord,
89 Serialize,
90 Deserialize,
91 BorshSerialize,
92 BorshDeserialize,
93)]
94pub struct RexId {
95 pub nonce: Nonce,
96 pub creator: Pubkey,
97}
98
99impl RexId {
100 pub fn new(creator: Pubkey, nonce: impl Into<Nonce>) -> Self {
102 Self {
103 nonce: nonce.into(),
104 creator,
105 }
106 }
107}
108
109impl fmt::Display for RexId {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}:{}", &self.nonce, &self.creator)
112 }
113}
114
115impl FromStr for RexId {
116 type Err = String;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 serde_json::from_str(s).map_err(|e| format!("Failed to parse RexId: {}", e))
121 }
122}
123
124#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Representable)]
130#[representable(human_readable = "rex_info_human_readable")]
131pub struct RexInfo {
132 pub id: RexId,
134 pub description: String,
136 pub update_frequency: UpdateFrequency,
138 pub target_rex_programs: Vec<TargetRexProgram>,
140 pub starting_timestamp: StartingTimestamp,
142 pub is_active: bool,
144 pub created_at_ms: i64,
146 #[serde(default = "default_validators_per_duty")]
148 pub validators_per_duty: u32,
149 #[serde(default = "default_rex_request_delay_ms")]
151 pub request_delay_ms: TimestampMs,
152}
153
154fn rex_info_human_readable(info: &RexInfo) -> String {
155 let mut out = String::new();
156
157 out.push_str(&format!("REX ID: {}\n", info.id));
158 out.push_str(&format!("Description: {}\n", info.description));
159 out.push_str(&format!("Active: {}\n", info.is_active));
160 out.push_str(&format!(
161 "Starting Timestamp: {:?}\n",
162 info.starting_timestamp
163 ));
164 out.push_str(&format!("Update Frequency: {:?}\n", info.update_frequency));
165 out.push_str(&format!("Created At: {}\n", info.created_at_ms));
166 out.push_str(&format!(
167 "Validators Per Duty: {}\n",
168 info.validators_per_duty
169 ));
170 out.push_str(&format!("REX Request Delay: {}\n", info.request_delay_ms));
171
172 if !info.target_rex_programs.is_empty() {
173 out.push_str(&format!(
174 "\nTarget REX operations ({}):\n",
175 info.target_rex_programs.len()
176 ));
177 for (i, target) in info.target_rex_programs.iter().enumerate() {
178 out.push_str(&format!(" {}. {:?}\n", i + 1, target));
179 }
180 }
181
182 out
183}
184
185impl Default for RexInfo {
186 fn default() -> Self {
187 Self {
188 id: RexId::default(),
189 description: String::new(),
190 update_frequency: UpdateFrequency::default(),
191 target_rex_programs: Vec::new(),
192 starting_timestamp: StartingTimestamp::default(),
193 is_active: false,
194 created_at_ms: 0,
195 validators_per_duty: default_validators_per_duty(),
196 request_delay_ms: default_rex_request_delay_ms(),
197 }
198 }
199}
200
201impl RexInfo {
202 pub fn is_asap(&self) -> bool {
205 matches!(self.starting_timestamp, StartingTimestamp::Asap)
206 }
207
208 pub fn target_timestamp(&self) -> Option<TimestampMs> {
209 match self.starting_timestamp {
210 StartingTimestamp::Timestamp(timestamp) => Some(timestamp),
211 StartingTimestamp::Asap => None,
212 }
213 }
214
215 pub fn requires_dkg_decryption(&self) -> bool {
221 self.target_rex_programs
222 .iter()
223 .any(TargetRexProgram::is_dkg_encrypted)
224 }
225
226 pub fn validate(&self) -> Result<(), String> {
228 match self.starting_timestamp {
229 StartingTimestamp::Asap => {
230 if !matches!(self.update_frequency, UpdateFrequency::OneShot) {
231 return Err("ASAP REX requests cannot be periodic".to_string());
232 }
233 }
234 StartingTimestamp::Timestamp(starting_timestamp) => {
235 match self.update_frequency {
236 UpdateFrequency::OneShot => {}
237 UpdateFrequency::Periodic(period)
238 | UpdateFrequency::LimitedPeriodic(period, _) => {
239 validate_periodic_frequency(period)?;
240
241 if let UpdateFrequency::LimitedPeriodic(_, end_timestamp) =
243 self.update_frequency
244 {
245 if starting_timestamp >= end_timestamp {
246 return Err("end_timestamp of a LimitedPeriodic REX should be above starting_timestamp".to_string());
247 }
248 }
249 }
250 }
251 }
252 }
253
254 if self.target_rex_programs.is_empty() {
256 return Err("RexTargets cannot be empty".to_string());
257 }
258
259 if self.request_delay_ms < RexDutyConfig::MIN_REX_REQUEST_DELAY {
261 return Err(format!(
262 "rex_request_delay cannot be below {}",
263 RexDutyConfig::MIN_REX_REQUEST_DELAY
264 ));
265 }
266 if self.request_delay_ms > RexDutyConfig::MAX_REX_REQUEST_DELAY_MS {
267 return Err(format!(
268 "rex_request_delay cannot be above {}",
269 RexDutyConfig::MAX_REX_REQUEST_DELAY_MS
270 ));
271 }
272
273 if self.validators_per_duty == 0 {
275 return Err("validators_per_duty cannot be 0".to_string());
276 }
277 let max_rex_output_size = max_rex_output_serialized_bytes(self.validators_per_duty);
278 if max_rex_output_size < MIN_VIABLE_LIMIT_OF_REX_OUTPUT_SIZE {
279 return Err(format!("validators_per_duty is too high, results in max size of REX updates that is too low: {max_rex_output_size} vs {MIN_VIABLE_LIMIT_OF_REX_OUTPUT_SIZE}"));
280 }
281
282 Ok(())
283 }
284
285 pub fn websocket_op(&self) -> Option<WebSocketOperation> {
287 self.target_rex_programs
288 .first()
289 .and_then(|target| target.websocket_op())
290 }
291}
292
293fn validate_periodic_frequency(period_ms: TimestampMs) -> Result<(), String> {
294 if period_ms == 0 {
295 return Err("update frequency cannot be zero".to_string());
296 }
297
298 if period_ms < MIN_UPDATE_PERIOD_MS {
299 return Err(format!(
300 "update frequency {period_ms} cannot be below {MIN_UPDATE_PERIOD_MS}"
301 ));
302 }
303
304 Ok(())
305}
306
307impl TargetRexProgram {
308 pub fn websocket_op(&self) -> Option<WebSocketOperation> {
310 if let TargetRexProgram::WebSocket(ws_op) = self {
311 Some(ws_op.clone())
312 } else {
313 None
314 }
315 }
316
317 pub fn encrypted_payloads(&self) -> Box<dyn Iterator<Item = &[u8]> + '_> {
325 fn enc(v: &RexValue) -> Option<&[u8]> {
326 v.is_encrypted().then(|| v.as_bytes())
327 }
328 match self {
336 TargetRexProgram::HttpGet { url, .. } if url.is_encrypted() => {
337 Box::new(std::iter::once(url.as_bytes()))
338 }
339 TargetRexProgram::HttpGet { .. } => Box::new(std::iter::empty()),
340 TargetRexProgram::HttpPost { body, .. } => Box::new(enc(body).into_iter()),
341 TargetRexProgram::WebSocket(WebSocketOperation::Send { messages, .. }) => {
342 Box::new(messages.iter().filter_map(enc))
343 }
344 TargetRexProgram::WebSocket(_) => Box::new(std::iter::empty()),
345 TargetRexProgram::Wasm { input, .. } => Box::new(input.iter().filter_map(enc)),
346 TargetRexProgram::Time
347 | TargetRexProgram::Number
348 | TargetRexProgram::SecretKeyGeneration { .. }
349 | TargetRexProgram::SecretKeyEncryption { .. }
350 | TargetRexProgram::SecretKeyDecryption { .. } => Box::new(std::iter::empty()),
351 }
352 }
353
354 pub fn is_dkg_encrypted(&self) -> bool {
361 self.encrypted_payloads().next().is_some()
362 }
363}
364
365fn default_validators_per_duty() -> u32 {
366 RexDutyConfig::DEFAULT_VALIDATORS_PER_DUTY
367}
368
369fn default_rex_request_delay_ms() -> TimestampMs {
370 RexDutyConfig::DEFAULT_REQUEST_DELAY_MS
371}
372
373#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
378pub struct RexEntry {
379 rex_info: Arc<RexInfo>,
380 data_hash: [u8; RexEntry::HASH_LENGTH],
381 last_modified_timestamp: u64,
382}
383
384impl RexEntry {
385 const HASH_LENGTH: usize = 32;
386
387 pub fn new(
395 rex_info: RexInfo,
396 data_hash: [u8; Self::HASH_LENGTH],
397 last_modified_round: u64,
398 ) -> Self {
399 Self {
400 rex_info: Arc::new(rex_info),
401 data_hash,
402 last_modified_timestamp: last_modified_round,
403 }
404 }
405
406 pub fn rex_info(&self) -> Arc<RexInfo> {
412 self.rex_info.clone()
413 }
414
415 pub fn last_modified_timestamp(&self) -> u64 {
421 self.last_modified_timestamp
422 }
423
424 pub fn data_hash(&self) -> &[u8; Self::HASH_LENGTH] {
430 &self.data_hash
431 }
432}
433
434fn deserialize_bytes_or_string<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
450where
451 D: serde::Deserializer<'de>,
452{
453 struct BytesOrString;
454
455 impl<'de> serde::de::Visitor<'de> for BytesOrString {
456 type Value = Vec<u8>;
457
458 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 f.write_str("a byte array, byte slice, or a string")
460 }
461
462 fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Vec<u8>, E> {
463 Ok(bytes.to_vec())
464 }
465
466 fn visit_byte_buf<E: serde::de::Error>(self, bytes: Vec<u8>) -> Result<Vec<u8>, E> {
467 Ok(bytes)
468 }
469
470 fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Vec<u8>, E> {
471 Ok(s.as_bytes().to_vec())
472 }
473
474 fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<Vec<u8>, A::Error> {
475 let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0));
476 while let Some(b) = seq.next_element()? {
477 bytes.push(b);
478 }
479 Ok(bytes)
480 }
481 }
482
483 deserializer.deserialize_bytes(BytesOrString)
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
502pub enum RexValue {
503 Plain(#[serde(deserialize_with = "deserialize_bytes_or_string")] Vec<u8>),
504 Encrypted(#[serde(deserialize_with = "deserialize_bytes_or_string")] Vec<u8>),
505}
506
507impl RexValue {
508 pub fn plain(data: Vec<u8>) -> Self {
510 RexValue::Plain(data)
511 }
512
513 pub fn plain_string(s: impl Into<String>) -> Self {
518 RexValue::Plain(s.into().into_bytes())
519 }
520
521 pub fn encrypted(ciphertext: Vec<u8>) -> Self {
523 RexValue::Encrypted(ciphertext)
524 }
525
526 pub fn as_string(&self) -> Option<&str> {
531 match self {
532 RexValue::Plain(bytes) => std::str::from_utf8(bytes).ok(),
533 RexValue::Encrypted(_) => None,
534 }
535 }
536
537 pub fn as_bytes(&self) -> &[u8] {
541 match self {
542 RexValue::Plain(bytes) | RexValue::Encrypted(bytes) => bytes,
543 }
544 }
545
546 pub fn is_plain(&self) -> bool {
548 matches!(self, RexValue::Plain(_))
549 }
550
551 pub fn is_encrypted(&self) -> bool {
553 matches!(self, RexValue::Encrypted(_))
554 }
555}
556
557impl Default for RexValue {
558 fn default() -> Self {
559 RexValue::Plain(vec![])
560 }
561}
562
563pub trait IntoRexValueFor<T> {
582 fn into_rex_value_for(self) -> RexValue;
583}
584
585impl<T: BorshSerialize> IntoRexValueFor<T> for T {
586 fn into_rex_value_for(self) -> RexValue {
587 RexValue::Plain(borsh::to_vec(&self).expect("borsh serialize failed"))
588 }
589}
590
591impl<T> IntoRexValueFor<T> for EncryptedInput<T> {
592 fn into_rex_value_for(self) -> RexValue {
593 RexValue::Encrypted(self.into_bytes())
594 }
595}
596
597impl FromStr for RexValue {
598 type Err = Infallible;
599 fn from_str(s: &str) -> Result<Self, Self::Err> {
600 Ok(RexValue::Plain(s.as_bytes().to_vec()))
601 }
602}
603
604#[deprecated(since = "0.2.0", note = "Use RexValue instead")]
609pub type RexValueBody = RexValue;
610
611#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
633pub struct RexUrl(RexValue);
634
635impl fmt::Display for RexUrl {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 match &self.0 {
638 RexValue::Plain(bytes) => {
639 match std::str::from_utf8(bytes) {
641 Ok(s) => write!(f, "{}", s),
642 #[cfg(feature = "non-pdk")]
643 Err(_) => write!(f, "<binary:{}>", Base64::encode(bytes)),
644 #[cfg(not(feature = "non-pdk"))]
645 Err(_) => write!(f, "<binary:{}>", hex::encode(bytes)),
646 }
647 }
648 RexValue::Encrypted(bytes) => {
649 match std::str::from_utf8(bytes) {
651 Ok(s) => write!(f, "enc://{}", s),
652 #[cfg(feature = "non-pdk")]
653 Err(_) => write!(f, "enc://<binary:{}>", Base64::encode(bytes)),
654 #[cfg(not(feature = "non-pdk"))]
655 Err(_) => write!(f, "enc://<binary:{}>", hex::encode(bytes)),
656 }
657 }
658 }
659 }
660}
661
662impl Deref for RexUrl {
663 type Target = RexValue;
664
665 fn deref(&self) -> &Self::Target {
666 &self.0
667 }
668}
669
670#[cfg(feature = "non-pdk")]
671impl From<Url> for RexUrl {
672 fn from(url: Url) -> Self {
673 url.to_string().into()
674 }
675}
676
677#[cfg(feature = "non-pdk")]
678impl From<&Url> for RexUrl {
679 fn from(url: &Url) -> Self {
680 Self(RexValue::Plain(url.to_string().into_bytes()))
681 }
682}
683
684impl From<String> for RexUrl {
685 fn from(url: String) -> Self {
686 url.as_str().into()
687 }
688}
689
690impl From<&str> for RexUrl {
691 fn from(s: &str) -> Self {
692 if let Some(encrypted) = s.strip_prefix("enc://") {
693 Self(RexValue::Encrypted(encrypted.into()))
694 } else {
695 Self(RexValue::Plain(s.into()))
697 }
698 }
699}
700
701impl FromStr for RexUrl {
702 type Err = Infallible;
703 fn from_str(s: &str) -> Result<Self, Self::Err> {
704 Ok(s.into())
705 }
706}
707
708impl From<RexValue> for RexUrl {
709 fn from(value: RexValue) -> Self {
710 Self(value)
711 }
712}
713
714#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum_macros::AsRefStr)]
723#[cfg_attr(feature = "non-pdk", derive(Subcommand))]
724#[repr(u16)]
725#[non_exhaustive]
726pub enum TargetRexProgram {
727 HttpGet {
731 #[cfg_attr(feature = "non-pdk", clap(
732 long = "target-url",
733 value_parser = clap::value_parser!(RexUrl)
734 ))]
735 url: RexUrl,
736 #[cfg_attr(feature = "non-pdk", clap(long, default_value = None))]
737 filter: Option<Vec<HttpFilter>>,
738 #[cfg_attr(feature = "non-pdk", clap(
739 long,
740 default_value_t = Headers::default(),
741 action = clap::ArgAction::Append
742 ))]
743 headers: Headers,
744 } = 0,
745 HttpPost {
749 #[cfg_attr(feature = "non-pdk", clap(
750 long = "target-url",
751 value_parser = clap::value_parser!(RexUrl)
752 ))]
753 url: RexUrl,
754 #[cfg_attr(feature = "non-pdk", clap(long))]
755 filter: Option<Vec<HttpFilter>>,
756 #[cfg_attr(feature = "non-pdk", clap(
757 long,
758 value_parser = clap::value_parser!(RexValue)
759 ))]
760 body: RexValue,
761 #[cfg_attr(feature = "non-pdk", clap(long))]
762 content_type: String,
763 #[cfg_attr(feature = "non-pdk", clap(
764 long,
765 default_value_t = Headers::default(),
766 action = clap::ArgAction::Append
767 ))]
768 headers: Headers,
769 } = 1,
770 Time = 2,
772 Number = 3,
775 SecretKeyGeneration {
778 #[cfg_attr(feature = "non-pdk", clap(long))]
779 committee_id: String,
780 #[cfg_attr(feature = "non-pdk", clap(long))]
781 committee_members: Vec<String>,
782 } = 4,
783 SecretKeyEncryption {
786 #[cfg_attr(feature = "non-pdk", clap(long))]
787 target_tee_id: String,
788 #[cfg_attr(feature = "non-pdk", clap(long))]
789 secret_data: Vec<u8>,
790 #[cfg_attr(feature = "non-pdk", clap(long))]
791 committee_id: String,
792 } = 5,
793 SecretKeyDecryption {
796 #[cfg_attr(feature = "non-pdk", clap(long))]
797 encrypted_data: Vec<u8>,
798 #[cfg_attr(feature = "non-pdk", clap(long))]
799 source_committee_id: String,
800 } = 6,
801 #[cfg_attr(feature = "non-pdk", clap(subcommand))]
803 WebSocket(WebSocketOperation) = 7,
804 Wasm {
807 #[cfg_attr(feature = "non-pdk", clap(long))]
809 bytecode_account: Pubkey,
810 #[cfg_attr(feature = "non-pdk", clap(long, value_parser = parse_base64_rex_value))]
816 input: Vec<RexValue>,
817 #[cfg_attr(feature = "non-pdk", clap(long))]
819 program_index: Option<u32>,
820 } = 8,
821}
822
823pub struct EncryptedInput<T = ()> {
847 ciphertext: Vec<u8>,
848 _phantom: std::marker::PhantomData<T>,
849}
850
851impl<T> Clone for EncryptedInput<T> {
855 fn clone(&self) -> Self {
856 Self {
857 ciphertext: self.ciphertext.clone(),
858 _phantom: std::marker::PhantomData,
859 }
860 }
861}
862
863impl<T> Default for EncryptedInput<T> {
864 fn default() -> Self {
865 Self {
866 ciphertext: Vec::new(),
867 _phantom: std::marker::PhantomData,
868 }
869 }
870}
871
872impl<T> std::fmt::Debug for EncryptedInput<T> {
873 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
874 f.debug_tuple("EncryptedInput")
875 .field(&format!("[{} bytes]", self.ciphertext.len()))
876 .finish()
877 }
878}
879
880impl<T> PartialEq for EncryptedInput<T> {
881 fn eq(&self, other: &Self) -> bool {
882 self.ciphertext == other.ciphertext
883 }
884}
885
886impl<T> Eq for EncryptedInput<T> {}
887
888impl<T> BorshSerialize for EncryptedInput<T> {
889 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
890 borsh::BorshSerialize::serialize(&self.ciphertext, writer)
891 }
892}
893
894impl<T> BorshDeserialize for EncryptedInput<T> {
895 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
896 Ok(Self {
897 ciphertext: borsh::BorshDeserialize::deserialize_reader(reader)?,
898 _phantom: std::marker::PhantomData,
899 })
900 }
901}
902
903impl<T> Serialize for EncryptedInput<T> {
904 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
905 serde::Serialize::serialize(&self.ciphertext, serializer)
906 }
907}
908
909impl<'de, T> Deserialize<'de> for EncryptedInput<T> {
910 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
911 Ok(Self {
912 ciphertext: <Vec<u8> as serde::Deserialize>::deserialize(deserializer)?,
913 _phantom: std::marker::PhantomData,
914 })
915 }
916}
917
918impl<T> EncryptedInput<T> {
919 pub fn new(ciphertext: Vec<u8>) -> Self {
920 Self {
921 ciphertext,
922 _phantom: std::marker::PhantomData,
923 }
924 }
925
926 pub fn as_bytes(&self) -> &[u8] {
927 &self.ciphertext
928 }
929
930 pub fn into_bytes(self) -> Vec<u8> {
931 self.ciphertext
932 }
933}
934
935impl<T> From<Vec<u8>> for EncryptedInput<T> {
936 fn from(v: Vec<u8>) -> Self {
937 Self::new(v)
938 }
939}
940
941#[cfg(feature = "non-pdk")]
943fn parse_base64_rex_value(s: &str) -> Result<RexValue, String> {
944 use fastcrypto::encoding::{Base64, Encoding};
945 Base64::decode(s)
946 .map(RexValue::Plain)
947 .map_err(|e| format!("Invalid base64 input: {e}"))
948}
949
950impl TargetRexProgram {
951 pub fn is_websocket(&self) -> bool {
953 matches!(self, TargetRexProgram::WebSocket(_))
954 }
955}
956
957impl FromStr for TargetRexProgram {
958 type Err = String;
959
960 fn from_str(s: &str) -> Result<Self, Self::Err> {
961 if s == "Time" {
962 Ok(TargetRexProgram::Time)
963 } else if s == "SecretKeyGeneration" {
964 Err("SecretKeyGeneration REX requires committee_id and committee_members parameters. Use the appropriate API to create this REX type.".to_string())
965 } else if s == "SecretKeyEncryption" {
966 Err("SecretKeyEncryption REX requires target_tee_id, secret_data, and committee_id parameters. Use the appropriate API to create this REX type.".to_string())
967 } else if s == "SecretKeyDecryption" {
968 Err("SecretKeyDecryption REX requires encrypted_data and source_committee_id parameters. Use the appropriate API to create this REX type.".to_string())
969 } else if s == "number" {
970 Err("The 'number' REX is only for testing purposes and should not be used in production.".to_string())
971 } else {
972 if let Some(rest) = s.strip_prefix("HttpGet:") {
973 let parts: Vec<&str> = rest.splitn(2, '|').collect();
974 if parts.is_empty() {
975 return Err(
976 "Invalid HttpGet format. Use 'HttpGet:<url>[|<filter>]'.".to_string()
977 );
978 }
979
980 let url = parts[0].to_string();
981 let filter = if parts.len() > 1 && !parts[1].is_empty() {
982 Some(vec![HttpFilter::from_str(parts[1])?])
983 } else {
984 None
985 };
986
987 #[cfg(feature = "non-pdk")]
989 if Url::parse(&url).is_err() {
990 return Err(format!("Invalid URL: {url}"));
991 }
992
993 return Ok(TargetRexProgram::HttpGet {
994 url: url.into(),
995 filter,
996 headers: Headers::default(),
997 });
998 }
999
1000 Err(format!("Unknown TargetRexProgram type: {s}"))
1001 }
1002 }
1003}
1004
1005pub type InputCommitmentBytes = [u8; 32];
1014
1015pub type SignatureBytes = [u8; 64];
1019
1020#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1024pub struct RexUpdateResult {
1025 pub rex_id: RexId,
1027
1028 pub target_timestamp: TimestampMs,
1030
1031 #[serde(with = "BigArray")]
1033 pub response_hash: [u8; 32],
1034
1035 #[serde(with = "BigArray")]
1039 pub input_commitment: InputCommitmentBytes,
1040
1041 #[serde(with = "BigArray")]
1043 pub signature: SignatureBytes,
1044
1045 pub rex_result: Vec<u8>,
1047
1048 pub attestation_report: Option<AttestationReport>,
1051
1052 #[serde(with = "BigArray")]
1054 pub authority_key: AuthorityKeyBytes,
1055}
1056
1057impl RexUpdateResult {
1058 pub fn new(
1060 rex_id: RexId,
1061 target_timestamp: TimestampMs,
1062 rex_result: Vec<u8>,
1063 input_commitment: InputCommitmentBytes,
1064 signature: SignatureBytes,
1065 attestation_report: Option<AttestationReport>,
1066 authority_key: AuthorityKeyBytes,
1067 ) -> Result<Self, &'static str> {
1068 let hash = blake3::hash(&rex_result);
1069
1070 #[cfg(feature = "non-pdk")]
1072 let rex_result = if rex_result.len() > rialo_limits::MAX_TRANSACTION_SIZE as usize {
1073 tracing::error!(
1074 "REX result size {} exceeds maximum size {}, dropping the result.",
1075 rex_result.len(),
1076 rialo_limits::MAX_TRANSACTION_SIZE
1077 );
1078 return Err("REX result exceeds maximum size");
1079 } else {
1080 rex_result
1082 };
1083
1084 Ok(Self {
1085 rex_id,
1086 target_timestamp,
1087 response_hash: *hash.as_bytes(),
1088 input_commitment,
1089 signature,
1090 rex_result,
1091 attestation_report,
1092 authority_key,
1093 })
1094 }
1095}
1096
1097impl Default for RexUpdateResult {
1098 fn default() -> Self {
1099 Self {
1100 rex_id: RexId::default(),
1101 target_timestamp: 0,
1102 response_hash: [0; 32],
1103 input_commitment: [0xee; 32],
1104 signature: [0; 64],
1105 rex_result: vec![],
1106 attestation_report: None,
1107 authority_key: [0xff; 96],
1108 }
1109 }
1110}
1111
1112#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
1120pub struct RexRequest {
1121 pub rex_id: Option<RexId>,
1123 pub target_timestamp: Option<TimestampMs>,
1124 pub authority_key: AuthorityKeyBytes,
1125 pub include_attestation: bool,
1126 pub max_output_size: u32,
1127
1128 pub params: BTreeMap<String, String>,
1130}
1131
1132impl Default for RexRequest {
1133 fn default() -> Self {
1134 Self {
1135 rex_id: None,
1136 target_timestamp: None,
1137 authority_key: [0; 96],
1138 include_attestation: true,
1139 max_output_size: 0,
1140 params: BTreeMap::default(),
1141 }
1142 }
1143}
1144
1145impl RexRequest {
1146 pub fn input_commitment(&self) -> Result<blake3::Hash, &'static str> {
1147 let request_bytes = borsh::to_vec(self).map_err(|_| "Failed to serialize RexRequest")?;
1148 Ok(blake3::hash(&request_bytes))
1149 }
1150}
1151
1152#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
1154pub enum UpdateFrequency {
1155 #[default]
1157 OneShot,
1158 Periodic(TimestampMs),
1160 LimitedPeriodic(TimestampMs, TimestampMs),
1163}
1164
1165#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
1167pub enum StartingTimestamp {
1168 Timestamp(TimestampMs),
1170 Asap,
1172}
1173
1174impl Default for StartingTimestamp {
1175 fn default() -> Self {
1176 StartingTimestamp::Timestamp(0)
1177 }
1178}
1179
1180impl UpdateFrequency {
1181 pub fn periodic(duration: Duration) -> Self {
1183 Self::Periodic(duration.as_millis() as TimestampMs)
1184 }
1185}
1186
1187impl StartingTimestamp {
1188 pub fn start_offset(offset: Duration) -> Self {
1189 let timestamp = SystemTime::now() + offset;
1190 Self::Timestamp(timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis() as TimestampMs)
1191 }
1192}
1193
1194#[cfg(test)]
1195mod tests {
1196 use super::*;
1197
1198 fn http_post(body: RexValue) -> TargetRexProgram {
1199 TargetRexProgram::HttpPost {
1200 url: "https://example.com".into(),
1201 filter: None,
1202 body,
1203 content_type: "application/json".to_string(),
1204 headers: Headers::default(),
1205 }
1206 }
1207
1208 fn ws_send(messages: Vec<RexValue>) -> TargetRexProgram {
1209 TargetRexProgram::WebSocket(WebSocketOperation::Send {
1210 connection_rex_id: RexId::default(),
1211 messages,
1212 })
1213 }
1214
1215 #[test]
1216 fn test_is_dkg_encrypted_false_for_plain_carriers() {
1217 assert!(!TargetRexProgram::Time.is_dkg_encrypted());
1218 assert!(!TargetRexProgram::HttpGet {
1219 url: "https://example.com".into(),
1220 filter: None,
1221 headers: Headers::default(),
1222 }
1223 .is_dkg_encrypted());
1224 assert!(!http_post(RexValue::plain(vec![1, 2, 3])).is_dkg_encrypted());
1225 assert!(!ws_send(vec![RexValue::plain(vec![1])]).is_dkg_encrypted());
1226 assert!(!TargetRexProgram::Wasm {
1227 bytecode_account: Pubkey::new_unique(),
1228 input: vec![RexValue::plain(vec![1]), RexValue::plain(vec![2])],
1229 program_index: None,
1230 }
1231 .is_dkg_encrypted());
1232 }
1233
1234 #[test]
1238 fn test_encrypted_payloads_yields_ciphertext_from_every_carrier() {
1239 let get = TargetRexProgram::HttpGet {
1240 url: RexValue::Encrypted(vec![0x02, 1, 2]).into(),
1241 filter: None,
1242 headers: Headers::default(),
1243 };
1244 assert_eq!(
1245 get.encrypted_payloads().collect::<Vec<_>>(),
1246 vec![&[0x02, 1, 2][..]]
1247 );
1248 assert!(get.is_dkg_encrypted());
1249
1250 let post = http_post(RexValue::Encrypted(vec![0x02, 7]));
1251 assert_eq!(
1252 post.encrypted_payloads().collect::<Vec<_>>(),
1253 vec![&[0x02, 7][..]]
1254 );
1255 assert!(post.is_dkg_encrypted());
1256
1257 let ws = ws_send(vec![
1259 RexValue::plain(vec![9]),
1260 RexValue::Encrypted(vec![0x02, 8]),
1261 RexValue::Encrypted(vec![0x02, 5]),
1262 ]);
1263 assert_eq!(
1264 ws.encrypted_payloads().collect::<Vec<_>>(),
1265 vec![&[0x02, 8][..], &[0x02, 5][..]]
1266 );
1267 assert!(ws.is_dkg_encrypted());
1268
1269 let wasm = TargetRexProgram::Wasm {
1271 bytecode_account: Pubkey::new_unique(),
1272 input: vec![RexValue::plain(vec![1]), RexValue::Encrypted(vec![0x02, 9])],
1273 program_index: None,
1274 };
1275 assert_eq!(
1276 wasm.encrypted_payloads().collect::<Vec<_>>(),
1277 vec![&[0x02, 9][..]]
1278 );
1279 assert!(wasm.is_dkg_encrypted());
1280
1281 assert!(TargetRexProgram::Time.encrypted_payloads().next().is_none());
1283 }
1284
1285 #[test]
1286 fn test_requires_dkg_decryption_rolls_up_across_programs() {
1287 let mut info = RexInfo {
1288 target_rex_programs: vec![TargetRexProgram::Time],
1289 ..RexInfo::default()
1290 };
1291 assert!(!info.requires_dkg_decryption());
1292 info.target_rex_programs
1293 .push(http_post(RexValue::Encrypted(vec![0x02, 1])));
1294 assert!(info.requires_dkg_decryption());
1295 }
1296
1297 fn base_valid_rex_info() -> RexInfo {
1298 RexInfo {
1299 description: "test".to_string(),
1300 target_rex_programs: vec![TargetRexProgram::Time],
1301 update_frequency: UpdateFrequency::OneShot,
1303 starting_timestamp: StartingTimestamp::Timestamp(0),
1305 ..RexInfo::default()
1307 }
1308 }
1309
1310 #[test]
1311 fn test_is_asap_true_and_false() {
1312 let mut info = base_valid_rex_info();
1313 assert!(!info.is_asap());
1314 info.starting_timestamp = StartingTimestamp::Asap;
1315 assert!(info.is_asap());
1316 }
1317
1318 #[test]
1319 fn test_validate_success_minimal() {
1320 let info = base_valid_rex_info();
1321 assert!(info.validate().is_ok());
1322 }
1323
1324 #[test]
1325 fn test_asap_cannot_be_periodic() {
1326 let mut info = base_valid_rex_info();
1327 info.starting_timestamp = StartingTimestamp::Asap;
1328 info.update_frequency = UpdateFrequency::Periodic(10);
1329 let err = info.validate().unwrap_err();
1330 assert!(err.contains("ASAP REX requests cannot be periodic"));
1331 }
1332
1333 #[test]
1334 fn test_asap_cannot_be_limited_periodic() {
1335 let mut info = base_valid_rex_info();
1336 info.starting_timestamp = StartingTimestamp::Asap;
1337 info.update_frequency = UpdateFrequency::LimitedPeriodic(5, 100);
1338 let err = info.validate().unwrap_err();
1339 assert!(err.contains("ASAP REX requests cannot be periodic"));
1340 }
1341
1342 #[test]
1343 fn test_periodic_with_zero_period_is_invalid() {
1344 let mut info = base_valid_rex_info();
1345 info.starting_timestamp = StartingTimestamp::Timestamp(1);
1346 info.update_frequency = UpdateFrequency::Periodic(0);
1347 let err = info.validate().unwrap_err();
1348 assert!(err.contains("update frequency cannot be zero"));
1349 }
1350
1351 #[test]
1352 fn test_limited_periodic_with_zero_period_is_invalid() {
1353 let mut info = base_valid_rex_info();
1354 info.starting_timestamp = StartingTimestamp::Timestamp(1);
1355 info.update_frequency = UpdateFrequency::LimitedPeriodic(0, 100);
1356 let err = info.validate().unwrap_err();
1357 assert!(err.contains("update frequency cannot be zero"));
1358 }
1359
1360 #[test]
1361 fn test_limited_periodic_end_timestamp_must_be_above_starting_timestamp() {
1362 let mut info = base_valid_rex_info();
1363 info.starting_timestamp = StartingTimestamp::Timestamp(500);
1364 info.update_frequency = UpdateFrequency::LimitedPeriodic(300, 500);
1365 let err = info.validate().unwrap_err();
1366 assert!(err
1367 .contains("end_timestamp of a LimitedPeriodic REX should be above starting_timestamp"));
1368 }
1369
1370 #[test]
1371 fn test_limited_periodic_end_timestamp_below_starting_timestamp_is_invalid() {
1372 let mut info = base_valid_rex_info();
1373 info.starting_timestamp = StartingTimestamp::Timestamp(1000);
1374 info.update_frequency = UpdateFrequency::LimitedPeriodic(300, 900);
1375 let err = info.validate().unwrap_err();
1376 assert!(err
1377 .contains("end_timestamp of a LimitedPeriodic REX should be above starting_timestamp"));
1378 }
1379
1380 #[test]
1381 fn test_target_rex_programs_cannot_be_empty() {
1382 let mut info = base_valid_rex_info();
1383 info.target_rex_programs.clear();
1384 let err = info.validate().unwrap_err();
1385 assert!(err.contains("RexTargets cannot be empty"));
1386 }
1387
1388 #[test]
1389 fn test_rex_request_delay_bounds() {
1390 let mut info = base_valid_rex_info();
1392 info.request_delay_ms = RexDutyConfig::MIN_REX_REQUEST_DELAY - 1;
1393 let err = info.validate().unwrap_err();
1394 assert!(err.contains(&format!(
1395 "rex_request_delay cannot be below {}",
1396 RexDutyConfig::MIN_REX_REQUEST_DELAY
1397 )));
1398
1399 let mut info = base_valid_rex_info();
1401 info.request_delay_ms = RexDutyConfig::MAX_REX_REQUEST_DELAY_MS + 1;
1402 let err = info.validate().unwrap_err();
1403 assert!(err.contains(&format!(
1404 "rex_request_delay cannot be above {}",
1405 RexDutyConfig::MAX_REX_REQUEST_DELAY_MS
1406 )));
1407 }
1408
1409 #[test]
1410 fn test_validators_per_duty_cannot_be_zero() {
1411 let mut info = base_valid_rex_info();
1412 info.validators_per_duty = 0;
1413 let err = info.validate().unwrap_err();
1414 assert!(err.contains("validators_per_duty cannot be 0"));
1415 }
1416
1417 #[test]
1418 fn test_validators_per_duty_too_high_results_in_too_low_output_size() {
1419 let mut info = base_valid_rex_info();
1420 info.validators_per_duty = 1_000_000; let err = info.validate().unwrap_err();
1422 assert!(err.contains("validators_per_duty is too high"));
1423 }
1424
1425 #[test]
1426 fn test_rex_value_plain_deserialize_from_byte_array() {
1427 let json = r#"{"Plain":[104,101,108,108,111]}"#;
1428 let value: RexValue = serde_json::from_str(json).unwrap();
1429 assert_eq!(value, RexValue::Plain(b"hello".to_vec()));
1430 }
1431
1432 #[test]
1433 fn test_rex_value_plain_deserialize_from_string() {
1434 let json = r#"{"Plain":"hello"}"#;
1435 let value: RexValue = serde_json::from_str(json).unwrap();
1436 assert_eq!(value, RexValue::Plain(b"hello".to_vec()));
1437 }
1438
1439 #[test]
1440 fn test_rex_value_encrypted_deserialize_from_string() {
1441 let json = r#"{"Encrypted":"base64data"}"#;
1442 let value: RexValue = serde_json::from_str(json).unwrap();
1443 assert_eq!(value, RexValue::Encrypted(b"base64data".to_vec()));
1444 }
1445
1446 #[test]
1447 fn test_rex_value_encrypted_deserialize_from_byte_array() {
1448 let json = r#"{"Encrypted":[65,66,67]}"#;
1449 let value: RexValue = serde_json::from_str(json).unwrap();
1450 assert_eq!(value, RexValue::Encrypted(b"ABC".to_vec()));
1451 }
1452
1453 #[test]
1455 fn test_into_rex_value_for_plain() {
1456 let out = <u64 as IntoRexValueFor<u64>>::into_rex_value_for(1_000_000u64);
1457 assert_eq!(out, RexValue::Plain(borsh::to_vec(&1_000_000u64).unwrap()));
1458 }
1459
1460 #[test]
1463 fn test_into_rex_value_for_encrypted() {
1464 let ct = vec![0xAAu8; 56];
1465 let wrapped = EncryptedInput::<u64>::new(ct.clone());
1466 let out = <EncryptedInput<u64> as IntoRexValueFor<u64>>::into_rex_value_for(wrapped);
1467 assert_eq!(out, RexValue::Encrypted(ct));
1468 }
1469}