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