1#[cfg(feature = "memory-storage")]
10use crate::beef_limits;
11
12use async_trait::async_trait;
13
14use crate::types::{AppliedTransaction, Outpoint, Output};
15
16#[derive(Debug, Clone)]
21pub struct TransactionBeef {
22 pub txid: String,
24 pub beef: Vec<u8>,
26}
27
28#[async_trait(?Send)]
33pub trait Storage {
34 async fn insert_output(&self, output: &Output) -> Result<(), StorageError>;
44
45 async fn delete_output(
50 &self,
51 txid: &str,
52 output_index: u32,
53 topic: &str,
54 ) -> Result<(), StorageError>;
55
56 async fn mark_utxo_as_spent(
58 &self,
59 txid: &str,
60 output_index: u32,
61 topic: &str,
62 ) -> Result<(), StorageError>;
63
64 async fn update_consumed_by(
66 &self,
67 txid: &str,
68 output_index: u32,
69 topic: &str,
70 consumed_by: &[Outpoint],
71 ) -> Result<(), StorageError>;
72
73 async fn update_transaction_beef(&self, txid: &str, beef: &[u8]) -> Result<(), StorageError>;
75
76 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
94 let _ = txid;
95 Ok(())
96 }
97
98 async fn mark_transaction_proven_at(
105 &self,
106 txid: &str,
107 height: Option<u64>,
108 ) -> Result<(), StorageError> {
109 let _ = height;
110 self.mark_transaction_proven(txid).await
111 }
112
113 async fn update_output_block_height(
117 &self,
118 txid: &str,
119 output_index: u32,
120 topic: &str,
121 block_height: u32,
122 ) -> Result<(), StorageError> {
123 let _ = (txid, output_index, topic, block_height);
124 Ok(())
125 }
126
127 async fn insert_applied_transaction(&self, tx: &AppliedTransaction)
129 -> Result<(), StorageError>;
130
131 async fn does_applied_transaction_exist(
133 &self,
134 tx: &AppliedTransaction,
135 ) -> Result<bool, StorageError>;
136
137 async fn delete_applied_transaction(&self, tx: &AppliedTransaction)
143 -> Result<(), StorageError>;
144
145 async fn find_output(
154 &self,
155 txid: &str,
156 output_index: u32,
157 topic: Option<&str>,
158 spent: Option<bool>,
159 include_beef: bool,
160 ) -> Result<Option<Output>, StorageError>;
161
162 async fn find_outputs_by_outpoints(
166 &self,
167 outpoints: &[Outpoint],
168 include_beef: bool,
169 ) -> Result<Vec<Output>, StorageError> {
170 let mut results = Vec::with_capacity(outpoints.len());
171 for op in outpoints {
172 if let Some(output) = self
173 .find_output(&op.txid, op.output_index, None, None, include_beef)
174 .await?
175 {
176 results.push(output);
177 }
178 }
179 Ok(results)
180 }
181
182 async fn find_served_outputs(
185 &self,
186 outpoints: &[Outpoint],
187 ) -> Result<Vec<Output>, StorageError> {
188 self.find_outputs_by_outpoints(outpoints, true).await
189 }
190
191 async fn find_outputs_for_transaction(
193 &self,
194 txid: &str,
195 include_beef: bool,
196 ) -> Result<Vec<Output>, StorageError>;
197
198 async fn find_utxos_for_topic(
203 &self,
204 topic: &str,
205 since: Option<f64>,
206 limit: Option<u64>,
207 include_beef: bool,
208 ) -> Result<Vec<Output>, StorageError>;
209
210 async fn find_transactions_for_proof_check(
242 &self,
243 limit: u64,
244 min_age_secs: u64,
245 ) -> Result<Vec<TransactionBeef>, StorageError> {
246 let _ = (limit, min_age_secs);
247 Ok(Vec::new())
248 }
249
250 async fn update_last_interaction(
256 &self,
257 host: &str,
258 topic: &str,
259 since: u64,
260 ) -> Result<(), StorageError>;
261
262 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError>;
264
265 async fn record_peer_sync_outcome(
288 &self,
289 host: &str,
290 topic: &str,
291 success: bool,
292 ) -> Result<(), StorageError>;
293
294 async fn get_peer_sync_health(
299 &self,
300 host: &str,
301 topic: &str,
302 ) -> Result<PeerSyncHealth, StorageError>;
303
304 async fn record_peer_sync_yield(
322 &self,
323 host: &str,
324 topic: &str,
325 yielded: bool,
326 ) -> Result<PeerYieldStreak, StorageError>;
327
328 async fn put_deferred_graph(
361 &self,
362 record: &crate::gasp::DeferredGraph,
363 ) -> Result<crate::gasp::DeferredGraphSave, StorageError>;
364
365 async fn find_deferred_graphs(
369 &self,
370 host: &str,
371 topic: &str,
372 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError>;
373
374 async fn get_deferred_graph(
376 &self,
377 host: &str,
378 topic: &str,
379 outpoint: &str,
380 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError>;
381
382 async fn delete_deferred_graph(
384 &self,
385 host: &str,
386 topic: &str,
387 outpoint: &str,
388 ) -> Result<(), StorageError>;
389}
390
391#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
394pub struct PeerYieldStreak {
395 pub yieldless_syncs: u64,
397 pub secs_since_first: Option<u64>,
400}
401
402#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
405pub struct PeerSyncHealth {
406 pub consecutive_failures: u64,
409 pub secs_since_last_attempt: Option<u64>,
412}
413
414#[async_trait(?Send)]
423impl<T: Storage + ?Sized> Storage for std::rc::Rc<T> {
424 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
425 (**self).insert_output(output).await
426 }
427 async fn delete_output(
428 &self,
429 txid: &str,
430 output_index: u32,
431 topic: &str,
432 ) -> Result<(), StorageError> {
433 (**self).delete_output(txid, output_index, topic).await
434 }
435 async fn mark_utxo_as_spent(
436 &self,
437 txid: &str,
438 output_index: u32,
439 topic: &str,
440 ) -> Result<(), StorageError> {
441 (**self).mark_utxo_as_spent(txid, output_index, topic).await
442 }
443 async fn update_consumed_by(
444 &self,
445 txid: &str,
446 output_index: u32,
447 topic: &str,
448 consumed_by: &[Outpoint],
449 ) -> Result<(), StorageError> {
450 (**self)
451 .update_consumed_by(txid, output_index, topic, consumed_by)
452 .await
453 }
454 async fn update_transaction_beef(&self, txid: &str, beef: &[u8]) -> Result<(), StorageError> {
455 (**self).update_transaction_beef(txid, beef).await
456 }
457 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
458 (**self).mark_transaction_proven(txid).await
459 }
460 async fn mark_transaction_proven_at(
461 &self,
462 txid: &str,
463 height: Option<u64>,
464 ) -> Result<(), StorageError> {
465 (**self).mark_transaction_proven_at(txid, height).await
469 }
470 async fn update_output_block_height(
471 &self,
472 txid: &str,
473 output_index: u32,
474 topic: &str,
475 block_height: u32,
476 ) -> Result<(), StorageError> {
477 (**self)
478 .update_output_block_height(txid, output_index, topic, block_height)
479 .await
480 }
481 async fn insert_applied_transaction(
482 &self,
483 tx: &AppliedTransaction,
484 ) -> Result<(), StorageError> {
485 (**self).insert_applied_transaction(tx).await
486 }
487 async fn does_applied_transaction_exist(
488 &self,
489 tx: &AppliedTransaction,
490 ) -> Result<bool, StorageError> {
491 (**self).does_applied_transaction_exist(tx).await
492 }
493 async fn delete_applied_transaction(
494 &self,
495 tx: &AppliedTransaction,
496 ) -> Result<(), StorageError> {
497 (**self).delete_applied_transaction(tx).await
498 }
499 async fn find_output(
500 &self,
501 txid: &str,
502 output_index: u32,
503 topic: Option<&str>,
504 spent: Option<bool>,
505 include_beef: bool,
506 ) -> Result<Option<Output>, StorageError> {
507 (**self)
508 .find_output(txid, output_index, topic, spent, include_beef)
509 .await
510 }
511 async fn find_served_outputs(
512 &self,
513 outpoints: &[Outpoint],
514 ) -> Result<Vec<Output>, StorageError> {
515 (**self).find_served_outputs(outpoints).await
516 }
517
518 async fn find_outputs_for_transaction(
519 &self,
520 txid: &str,
521 include_beef: bool,
522 ) -> Result<Vec<Output>, StorageError> {
523 (**self)
524 .find_outputs_for_transaction(txid, include_beef)
525 .await
526 }
527 async fn find_utxos_for_topic(
528 &self,
529 topic: &str,
530 since: Option<f64>,
531 limit: Option<u64>,
532 include_beef: bool,
533 ) -> Result<Vec<Output>, StorageError> {
534 (**self)
535 .find_utxos_for_topic(topic, since, limit, include_beef)
536 .await
537 }
538 async fn find_transactions_for_proof_check(
539 &self,
540 limit: u64,
541 min_age_secs: u64,
542 ) -> Result<Vec<TransactionBeef>, StorageError> {
543 (**self)
544 .find_transactions_for_proof_check(limit, min_age_secs)
545 .await
546 }
547 async fn update_last_interaction(
548 &self,
549 host: &str,
550 topic: &str,
551 since: u64,
552 ) -> Result<(), StorageError> {
553 (**self).update_last_interaction(host, topic, since).await
554 }
555 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
556 (**self).get_last_interaction(host, topic).await
557 }
558 async fn record_peer_sync_outcome(
559 &self,
560 host: &str,
561 topic: &str,
562 success: bool,
563 ) -> Result<(), StorageError> {
564 (**self)
565 .record_peer_sync_outcome(host, topic, success)
566 .await
567 }
568 async fn get_peer_sync_health(
569 &self,
570 host: &str,
571 topic: &str,
572 ) -> Result<PeerSyncHealth, StorageError> {
573 (**self).get_peer_sync_health(host, topic).await
574 }
575 async fn record_peer_sync_yield(
576 &self,
577 host: &str,
578 topic: &str,
579 yielded: bool,
580 ) -> Result<PeerYieldStreak, StorageError> {
581 (**self).record_peer_sync_yield(host, topic, yielded).await
582 }
583 async fn put_deferred_graph(
584 &self,
585 record: &crate::gasp::DeferredGraph,
586 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
587 (**self).put_deferred_graph(record).await
588 }
589 async fn find_deferred_graphs(
590 &self,
591 host: &str,
592 topic: &str,
593 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
594 (**self).find_deferred_graphs(host, topic).await
595 }
596 async fn get_deferred_graph(
597 &self,
598 host: &str,
599 topic: &str,
600 outpoint: &str,
601 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
602 (**self).get_deferred_graph(host, topic, outpoint).await
603 }
604 async fn delete_deferred_graph(
605 &self,
606 host: &str,
607 topic: &str,
608 outpoint: &str,
609 ) -> Result<(), StorageError> {
610 (**self).delete_deferred_graph(host, topic, outpoint).await
611 }
612}
613
614#[derive(Debug, thiserror::Error)]
620pub enum StorageError {
621 #[error("not found: {0}")]
622 NotFound(String),
623
624 #[error("duplicate: {0}")]
625 Duplicate(String),
626
627 #[error("database error: {0}")]
628 Database(String),
629
630 #[error("serialization error: {0}")]
631 Serialization(String),
632
633 #[error("{0}")]
634 Other(String),
635}
636
637#[cfg(any(test, feature = "memory-storage"))]
642pub mod memory {
643 use super::*;
644 use std::collections::HashMap;
645 use std::sync::Mutex;
646
647 #[derive(Debug, Default)]
651 pub struct MemoryStorage {
652 outputs: Mutex<HashMap<(String, u32, String), Output>>,
654 transactions: Mutex<HashMap<String, Vec<u8>>>,
656 proven: Mutex<std::collections::HashSet<String>>,
664 applied: Mutex<HashMap<(String, String), bool>>,
666 sync_state: Mutex<HashMap<(String, String), u64>>,
668 clock_secs: Mutex<u64>,
673 created_at: Mutex<HashMap<String, u64>>,
677 peer_health: Mutex<HashMap<(String, String), (u64, u64)>>,
682 proven_at: Mutex<HashMap<String, Option<u64>>>,
686 deferred_graphs: Mutex<HashMap<(String, String, String), crate::gasp::DeferredGraph>>,
689 deferred_graph_writes: Mutex<u64>,
692 deferred_graph_reads: Mutex<u64>,
695 deferred_graph_keys_fault: Mutex<bool>,
697 deferred_graph_ceiling: Mutex<Option<usize>>,
700 deferred_graph_byte_ceiling: Mutex<Option<usize>>,
704 deferred_graph_put_fault: Mutex<bool>,
706 deferred_graph_put_attempts: Mutex<u64>,
709 peer_yieldless: Mutex<HashMap<(String, String), YieldStreakRow>>,
715 }
716
717 type YieldStreakRow = (u64, u64, u64);
720
721 impl MemoryStorage {
722 pub fn new() -> Self {
723 Self::default()
724 }
725
726 pub fn proven_at_height(&self, txid: &str) -> Option<Option<u64>> {
730 self.proven_at.lock().unwrap().get(txid).copied()
731 }
732
733 pub fn advance_clock(&self, secs: u64) {
737 *self.clock_secs.lock().unwrap() += secs;
738 }
739
740 fn stamp_created(&self, txid: &str) {
743 let now = *self.clock_secs.lock().unwrap();
744 self.created_at
745 .lock()
746 .unwrap()
747 .entry(txid.to_string())
748 .or_insert(now);
749 }
750
751 fn age_gate_open(&self, txid: &str, min_age_secs: u64) -> bool {
754 if min_age_secs == 0 {
755 return true;
756 }
757 let now = *self.clock_secs.lock().unwrap();
758 match self.created_at.lock().unwrap().get(txid) {
759 None => true, Some(created) => now.saturating_sub(*created) >= min_age_secs,
761 }
762 }
763
764 pub fn deferred_graphs(&self) -> Vec<crate::gasp::DeferredGraph> {
766 let mut all: Vec<_> = self
767 .deferred_graphs
768 .lock()
769 .unwrap()
770 .values()
771 .cloned()
772 .collect();
773 all.sort_by(|a, b| (a.score, &a.outpoint).cmp(&(b.score, &b.outpoint)));
774 all
775 }
776
777 pub fn deferred_graph_writes(&self) -> u64 {
779 *self.deferred_graph_writes.lock().unwrap()
780 }
781
782 pub fn deferred_graph_reads(&self) -> u64 {
784 *self.deferred_graph_reads.lock().unwrap()
785 }
786
787 pub fn set_deferred_graph_keys_fault(&self, fault: bool) {
790 *self.deferred_graph_keys_fault.lock().unwrap() = fault;
791 }
792
793 pub fn set_deferred_graph_ceiling(&self, ceiling: Option<usize>) {
796 *self.deferred_graph_ceiling.lock().unwrap() = ceiling;
797 }
798
799 pub fn set_deferred_graph_byte_ceiling(&self, ceiling: Option<usize>) {
803 *self.deferred_graph_byte_ceiling.lock().unwrap() = ceiling;
804 }
805
806 pub fn set_deferred_graph_put_fault(&self, fault: bool) {
809 *self.deferred_graph_put_fault.lock().unwrap() = fault;
810 }
811
812 pub fn deferred_graph_put_attempts(&self) -> u64 {
815 *self.deferred_graph_put_attempts.lock().unwrap()
816 }
817
818 pub fn peer_yieldless_syncs(&self, host: &str, topic: &str) -> u64 {
823 self.peer_yieldless
824 .lock()
825 .unwrap()
826 .get(&(crate::gasp::peer_origin(host), topic.to_string()))
827 .map_or(0, |(count, _, _)| *count)
828 }
829
830 pub fn output_count(&self) -> usize {
832 self.outputs.lock().unwrap().len()
833 }
834
835 pub fn transaction_count(&self) -> usize {
837 self.transactions.lock().unwrap().len()
838 }
839 }
840
841 #[async_trait(?Send)]
842 impl Storage for MemoryStorage {
843 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
844 let key = (
845 output.txid.clone(),
846 output.output_index,
847 output.topic.clone(),
848 );
849 let mut outputs = self.outputs.lock().unwrap();
850
851 if outputs.contains_key(&key) {
853 if let Some(ref beef) = output.beef {
855 self.transactions
856 .lock()
857 .unwrap()
858 .entry(output.txid.clone())
859 .or_insert_with(|| beef.clone());
860 }
861 return Ok(());
862 }
863
864 let mut stored = output.clone();
866 stored.beef = None;
867 outputs.insert(key, stored);
868
869 if let Some(ref beef) = output.beef {
871 self.transactions
872 .lock()
873 .unwrap()
874 .entry(output.txid.clone())
875 .or_insert_with(|| beef.clone());
876 self.stamp_created(&output.txid);
877 }
878
879 Ok(())
880 }
881
882 async fn delete_output(
883 &self,
884 txid: &str,
885 output_index: u32,
886 topic: &str,
887 ) -> Result<(), StorageError> {
888 let key = (txid.to_string(), output_index, topic.to_string());
889 let mut outputs = self.outputs.lock().unwrap();
890 outputs.remove(&key);
891
892 let has_remaining = outputs.keys().any(|(t, _, _)| t == txid);
894 if !has_remaining {
895 self.transactions.lock().unwrap().remove(txid);
896 self.proven.lock().unwrap().remove(txid);
897 }
898
899 Ok(())
900 }
901
902 async fn mark_utxo_as_spent(
903 &self,
904 txid: &str,
905 output_index: u32,
906 topic: &str,
907 ) -> Result<(), StorageError> {
908 let key = (txid.to_string(), output_index, topic.to_string());
909 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
910 output.spent = true;
911 }
912 Ok(())
913 }
914
915 async fn update_consumed_by(
916 &self,
917 txid: &str,
918 output_index: u32,
919 topic: &str,
920 consumed_by: &[Outpoint],
921 ) -> Result<(), StorageError> {
922 let key = (txid.to_string(), output_index, topic.to_string());
923 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
924 output.consumed_by = consumed_by.to_vec();
925 }
926 Ok(())
927 }
928
929 async fn update_transaction_beef(
930 &self,
931 txid: &str,
932 beef: &[u8],
933 ) -> Result<(), StorageError> {
934 let has_proof = beef_limits::has_proof(beef, txid);
940 if has_proof {
941 self.proven.lock().unwrap().insert(txid.to_string());
942 }
943 self.transactions
944 .lock()
945 .unwrap()
946 .insert(txid.to_string(), beef.to_vec());
947 self.stamp_created(txid);
950 Ok(())
951 }
952
953 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
954 self.proven.lock().unwrap().insert(txid.to_string());
957 Ok(())
958 }
959
960 async fn mark_transaction_proven_at(
961 &self,
962 txid: &str,
963 height: Option<u64>,
964 ) -> Result<(), StorageError> {
965 self.proven.lock().unwrap().insert(txid.to_string());
969 self.proven_at
970 .lock()
971 .unwrap()
972 .insert(txid.to_string(), height);
973 Ok(())
974 }
975
976 async fn update_output_block_height(
977 &self,
978 txid: &str,
979 output_index: u32,
980 topic: &str,
981 block_height: u32,
982 ) -> Result<(), StorageError> {
983 let key = (txid.to_string(), output_index, topic.to_string());
984 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
985 output.block_height = Some(block_height);
986 }
987 Ok(())
988 }
989
990 async fn insert_applied_transaction(
991 &self,
992 tx: &AppliedTransaction,
993 ) -> Result<(), StorageError> {
994 self.applied
995 .lock()
996 .unwrap()
997 .insert((tx.txid.clone(), tx.topic.clone()), true);
998 Ok(())
999 }
1000
1001 async fn does_applied_transaction_exist(
1002 &self,
1003 tx: &AppliedTransaction,
1004 ) -> Result<bool, StorageError> {
1005 Ok(self
1006 .applied
1007 .lock()
1008 .unwrap()
1009 .contains_key(&(tx.txid.clone(), tx.topic.clone())))
1010 }
1011
1012 async fn delete_applied_transaction(
1013 &self,
1014 tx: &AppliedTransaction,
1015 ) -> Result<(), StorageError> {
1016 self.applied
1017 .lock()
1018 .unwrap()
1019 .remove(&(tx.txid.clone(), tx.topic.clone()));
1020 Ok(())
1021 }
1022
1023 async fn find_output(
1024 &self,
1025 txid: &str,
1026 output_index: u32,
1027 topic: Option<&str>,
1028 spent: Option<bool>,
1029 include_beef: bool,
1030 ) -> Result<Option<Output>, StorageError> {
1031 let outputs = self.outputs.lock().unwrap();
1032
1033 if let Some(topic) = topic {
1035 let key = (txid.to_string(), output_index, topic.to_string());
1036 if let Some(output) = outputs.get(&key) {
1037 if let Some(s) = spent {
1038 if output.spent != s {
1039 return Ok(None);
1040 }
1041 }
1042 let mut result = output.clone();
1043 if include_beef {
1044 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1045 }
1046 return Ok(Some(result));
1047 }
1048 return Ok(None);
1049 }
1050
1051 for ((t, oi, _), output) in outputs.iter() {
1053 if t == txid && *oi == output_index {
1054 if let Some(s) = spent {
1055 if output.spent != s {
1056 continue;
1057 }
1058 }
1059 let mut result = output.clone();
1060 if include_beef {
1061 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1062 }
1063 return Ok(Some(result));
1064 }
1065 }
1066 Ok(None)
1067 }
1068
1069 async fn find_served_outputs(
1070 &self,
1071 outpoints: &[Outpoint],
1072 ) -> Result<Vec<Output>, StorageError> {
1073 let outputs = self.find_outputs_by_outpoints(outpoints, true).await?;
1074 let now = i64::try_from(*self.clock_secs.lock().unwrap())
1075 .unwrap_or(i64::MAX)
1076 .saturating_mul(1000);
1077 let proven = self.proven.lock().unwrap();
1078 let stamps = self.created_at.lock().unwrap();
1079 Ok(outputs
1080 .into_iter()
1081 .filter(|output| {
1082 let seen = stamps
1083 .get(&output.txid)
1084 .and_then(|s| i64::try_from(*s).ok())
1085 .map(|s| s.saturating_mul(1000));
1086 crate::proof_window::verdict(proven.contains(&output.txid), seen, now)
1087 == crate::proof_window::Verdict::Served
1088 })
1089 .collect())
1090 }
1091
1092 async fn find_outputs_for_transaction(
1093 &self,
1094 txid: &str,
1095 include_beef: bool,
1096 ) -> Result<Vec<Output>, StorageError> {
1097 let outputs = self.outputs.lock().unwrap();
1098 let beef = if include_beef {
1099 self.transactions.lock().unwrap().get(txid).cloned()
1100 } else {
1101 None
1102 };
1103
1104 let results: Vec<Output> = outputs
1105 .iter()
1106 .filter(|((t, _, _), _)| t == txid)
1107 .map(|(_, output)| {
1108 let mut o = output.clone();
1109 if include_beef {
1110 o.beef.clone_from(&beef);
1111 }
1112 o
1113 })
1114 .collect();
1115
1116 Ok(results)
1117 }
1118
1119 async fn find_utxos_for_topic(
1120 &self,
1121 topic: &str,
1122 since: Option<f64>,
1123 limit: Option<u64>,
1124 include_beef: bool,
1125 ) -> Result<Vec<Output>, StorageError> {
1126 let outputs = self.outputs.lock().unwrap();
1127 let transactions = self.transactions.lock().unwrap();
1128
1129 let mut results: Vec<Output> = outputs
1130 .iter()
1131 .filter(|((_, _, t), o)| {
1132 t == topic && !o.spent && since.is_none_or(|s| o.score.unwrap_or(0.0) >= s)
1133 })
1134 .map(|(_, output)| {
1135 let mut o = output.clone();
1136 if include_beef {
1137 o.beef = transactions.get(&o.txid).cloned();
1138 }
1139 o
1140 })
1141 .collect();
1142
1143 results.sort_by(|a, b| {
1145 a.score
1146 .unwrap_or(0.0)
1147 .partial_cmp(&b.score.unwrap_or(0.0))
1148 .unwrap_or(std::cmp::Ordering::Equal)
1149 });
1150
1151 if let Some(limit) = limit {
1152 results.truncate(limit as usize);
1153 }
1154
1155 Ok(results)
1156 }
1157
1158 async fn find_transactions_for_proof_check(
1159 &self,
1160 limit: u64,
1161 min_age_secs: u64,
1162 ) -> Result<Vec<TransactionBeef>, StorageError> {
1163 let candidates: Vec<TransactionBeef> = {
1176 let transactions = self.transactions.lock().unwrap();
1177 let proven = self.proven.lock().unwrap();
1178 transactions
1179 .iter()
1180 .filter(|(txid, _)| !proven.contains(*txid))
1181 .map(|(txid, beef)| TransactionBeef {
1182 txid: txid.clone(),
1183 beef: beef.clone(),
1184 })
1185 .collect()
1186 };
1187 Ok(candidates
1188 .into_iter()
1189 .filter(|c| self.age_gate_open(&c.txid, min_age_secs))
1190 .take(limit as usize)
1191 .collect())
1192 }
1193
1194 async fn update_last_interaction(
1195 &self,
1196 host: &str,
1197 topic: &str,
1198 since: u64,
1199 ) -> Result<(), StorageError> {
1200 self.sync_state
1201 .lock()
1202 .unwrap()
1203 .insert((host.to_string(), topic.to_string()), since);
1204 Ok(())
1205 }
1206
1207 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
1208 Ok(*self
1209 .sync_state
1210 .lock()
1211 .unwrap()
1212 .get(&(host.to_string(), topic.to_string()))
1213 .unwrap_or(&0))
1214 }
1215
1216 async fn record_peer_sync_outcome(
1217 &self,
1218 host: &str,
1219 topic: &str,
1220 success: bool,
1221 ) -> Result<(), StorageError> {
1222 let now = *self.clock_secs.lock().unwrap();
1223 let mut health = self.peer_health.lock().unwrap();
1224 let entry = health
1225 .entry((host.to_string(), topic.to_string()))
1226 .or_insert((0, now));
1227 entry.0 = if success { 0 } else { entry.0 + 1 };
1228 entry.1 = now;
1229 Ok(())
1230 }
1231
1232 async fn get_peer_sync_health(
1233 &self,
1234 host: &str,
1235 topic: &str,
1236 ) -> Result<PeerSyncHealth, StorageError> {
1237 let now = *self.clock_secs.lock().unwrap();
1238 Ok(self
1239 .peer_health
1240 .lock()
1241 .unwrap()
1242 .get(&(host.to_string(), topic.to_string()))
1243 .map(|(fails, attempt_at)| PeerSyncHealth {
1244 consecutive_failures: *fails,
1245 secs_since_last_attempt: Some(now.saturating_sub(*attempt_at)),
1246 })
1247 .unwrap_or_default())
1248 }
1249
1250 async fn record_peer_sync_yield(
1251 &self,
1252 host: &str,
1253 topic: &str,
1254 yielded: bool,
1255 ) -> Result<PeerYieldStreak, StorageError> {
1256 let now = *self.clock_secs.lock().unwrap();
1257 let key = (host.to_string(), topic.to_string());
1258 let not_failed = self
1261 .peer_health
1262 .lock()
1263 .unwrap()
1264 .get(&key)
1265 .is_none_or(|(fails, _)| *fails == 0);
1266 let mut held = self.peer_yieldless.lock().unwrap();
1267 if yielded {
1268 held.remove(&key);
1269 return Ok(PeerYieldStreak::default());
1270 }
1271 let streak = held.entry(key).or_insert((0, now, now));
1272 if streak.0 == 0
1273 || (not_failed
1274 && now.saturating_sub(streak.2) > crate::gasp::PEER_YIELDLESS_DECAY_SECS)
1275 {
1276 *streak = (0, now, now);
1277 }
1278 streak.0 += 1;
1279 streak.2 = now;
1280 Ok(PeerYieldStreak {
1281 yieldless_syncs: streak.0,
1282 secs_since_first: Some(now.saturating_sub(streak.1)),
1283 })
1284 }
1285
1286 async fn put_deferred_graph(
1287 &self,
1288 record: &crate::gasp::DeferredGraph,
1289 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
1290 let key = (
1291 record.peer.clone(),
1292 record.topic.clone(),
1293 record.outpoint.clone(),
1294 );
1295 *self.deferred_graph_put_attempts.lock().unwrap() += 1;
1296 if *self.deferred_graph_put_fault.lock().unwrap() {
1297 return Err(StorageError::Database("deferred graph save faulted".into()));
1298 }
1299 if let Some(ceiling) = *self.deferred_graph_ceiling.lock().unwrap() {
1300 let held = self.deferred_graphs.lock().unwrap();
1301 if !held.contains_key(&key) && held.len() >= ceiling {
1302 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1303 }
1304 }
1305 if let Some(ceiling) = *self.deferred_graph_byte_ceiling.lock().unwrap() {
1306 let held = self.deferred_graphs.lock().unwrap();
1307 let others: usize = held
1308 .iter()
1309 .filter(|(k, _)| **k != key)
1310 .map(|(_, r)| r.byte_size())
1311 .sum();
1312 if others + record.byte_size() > ceiling {
1313 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1314 }
1315 }
1316 *self.deferred_graph_writes.lock().unwrap() += 1;
1317 self.deferred_graphs.lock().unwrap().insert(
1318 (
1319 record.peer.clone(),
1320 record.topic.clone(),
1321 record.outpoint.clone(),
1322 ),
1323 record.clone(),
1324 );
1325 Ok(crate::gasp::DeferredGraphSave::Saved)
1326 }
1327
1328 async fn find_deferred_graphs(
1329 &self,
1330 host: &str,
1331 topic: &str,
1332 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
1333 if *self.deferred_graph_keys_fault.lock().unwrap() {
1334 return Err(StorageError::Database("deferred graph keys: fault".into()));
1335 }
1336 Ok(self
1337 .deferred_graphs()
1338 .into_iter()
1339 .filter(|r| r.peer == host && r.topic == topic)
1340 .map(|r| crate::gasp::DeferredGraphKey {
1341 outpoint: r.outpoint,
1342 score: r.score,
1343 })
1344 .collect())
1345 }
1346
1347 async fn get_deferred_graph(
1348 &self,
1349 host: &str,
1350 topic: &str,
1351 outpoint: &str,
1352 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
1353 *self.deferred_graph_reads.lock().unwrap() += 1;
1354 Ok(self
1355 .deferred_graphs
1356 .lock()
1357 .unwrap()
1358 .get(&(host.to_string(), topic.to_string(), outpoint.to_string()))
1359 .cloned())
1360 }
1361
1362 async fn delete_deferred_graph(
1363 &self,
1364 host: &str,
1365 topic: &str,
1366 outpoint: &str,
1367 ) -> Result<(), StorageError> {
1368 self.deferred_graphs.lock().unwrap().remove(&(
1369 host.to_string(),
1370 topic.to_string(),
1371 outpoint.to_string(),
1372 ));
1373 Ok(())
1374 }
1375 }
1376}
1377
1378#[cfg(test)]
1383mod tests {
1384 use super::memory::MemoryStorage;
1385 use super::*;
1386 use crate::types::AppliedTransaction;
1387
1388 #[tokio::test]
1393 async fn the_rc_blanket_forwards_the_anchored_proven_latch() {
1394 let store = std::rc::Rc::new(MemoryStorage::new());
1395 let via_dyn: std::rc::Rc<dyn Storage> = store.clone();
1396 via_dyn
1397 .mark_transaction_proven_at("tx", Some(965_772))
1398 .await
1399 .unwrap();
1400 assert_eq!(
1401 store.proven_at_height("tx"),
1402 Some(Some(965_772)),
1403 "the Rc blanket forwarded the height to the inner store"
1404 );
1405 via_dyn
1407 .mark_transaction_proven_at("tx2", None)
1408 .await
1409 .unwrap();
1410 assert_eq!(store.proven_at_height("tx2"), Some(None));
1411 assert_eq!(
1412 store.proven_at_height("never"),
1413 None,
1414 "a txid never latched is absent"
1415 );
1416 }
1417
1418 fn make_output(txid: &str, index: u32, topic: &str, score: f64) -> Output {
1419 Output {
1420 txid: txid.to_string(),
1421 output_index: index,
1422 output_script: vec![0x76, 0xa9],
1423 satoshis: 1000,
1424 topic: topic.to_string(),
1425 spent: false,
1426 outputs_consumed: vec![],
1427 consumed_by: vec![],
1428 beef: Some(vec![0xBE, 0xEF]),
1429 block_height: None,
1430 score: Some(score),
1431 }
1432 }
1433
1434 #[tokio::test]
1435 async fn test_insert_and_find_output() {
1436 let store = MemoryStorage::new();
1437 let output = make_output("abc", 0, "tm_test", 1.0);
1438 store.insert_output(&output).await.unwrap();
1439
1440 let found = store
1442 .find_output("abc", 0, Some("tm_test"), None, false)
1443 .await
1444 .unwrap()
1445 .unwrap();
1446 assert_eq!(found.txid, "abc");
1447 assert_eq!(found.output_index, 0);
1448 assert!(found.beef.is_none());
1449
1450 let found = store
1452 .find_output("abc", 0, Some("tm_test"), None, true)
1453 .await
1454 .unwrap()
1455 .unwrap();
1456 assert_eq!(found.beef.unwrap(), vec![0xBE, 0xEF]);
1457 }
1458
1459 #[tokio::test]
1460 async fn test_insert_duplicate_is_noop() {
1461 let store = MemoryStorage::new();
1462 let output = make_output("abc", 0, "tm_test", 1.0);
1463 store.insert_output(&output).await.unwrap();
1464 store.insert_output(&output).await.unwrap();
1465 assert_eq!(store.output_count(), 1);
1466 }
1467
1468 #[tokio::test]
1469 async fn test_delete_output_cleans_up_beef() {
1470 let store = MemoryStorage::new();
1471 let output = make_output("abc", 0, "tm_test", 1.0);
1472 store.insert_output(&output).await.unwrap();
1473 assert_eq!(store.transaction_count(), 1);
1474
1475 store.delete_output("abc", 0, "tm_test").await.unwrap();
1476 assert_eq!(store.output_count(), 0);
1477 assert_eq!(store.transaction_count(), 0); }
1479
1480 #[tokio::test]
1481 async fn test_delete_output_keeps_beef_if_other_outputs_exist() {
1482 let store = MemoryStorage::new();
1483 store
1484 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1485 .await
1486 .unwrap();
1487 store
1488 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
1489 .await
1490 .unwrap();
1491 assert_eq!(store.output_count(), 2);
1492 assert_eq!(store.transaction_count(), 1);
1493
1494 store.delete_output("abc", 0, "tm_test").await.unwrap();
1495 assert_eq!(store.output_count(), 1);
1496 assert_eq!(store.transaction_count(), 1); }
1498
1499 #[tokio::test]
1500 async fn test_mark_utxo_as_spent() {
1501 let store = MemoryStorage::new();
1502 store
1503 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1504 .await
1505 .unwrap();
1506
1507 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
1508
1509 let found = store
1510 .find_output("abc", 0, Some("tm_test"), None, false)
1511 .await
1512 .unwrap()
1513 .unwrap();
1514 assert!(found.spent);
1515 }
1516
1517 #[tokio::test]
1518 async fn test_find_output_with_spent_filter() {
1519 let store = MemoryStorage::new();
1520 store
1521 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1522 .await
1523 .unwrap();
1524
1525 assert!(store
1527 .find_output("abc", 0, Some("tm_test"), Some(false), false)
1528 .await
1529 .unwrap()
1530 .is_some());
1531 assert!(store
1532 .find_output("abc", 0, Some("tm_test"), Some(true), false)
1533 .await
1534 .unwrap()
1535 .is_none());
1536
1537 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
1538
1539 assert!(store
1541 .find_output("abc", 0, Some("tm_test"), Some(true), false)
1542 .await
1543 .unwrap()
1544 .is_some());
1545 assert!(store
1546 .find_output("abc", 0, Some("tm_test"), Some(false), false)
1547 .await
1548 .unwrap()
1549 .is_none());
1550 }
1551
1552 #[tokio::test]
1553 async fn test_update_consumed_by() {
1554 let store = MemoryStorage::new();
1555 store
1556 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1557 .await
1558 .unwrap();
1559
1560 let consumed = vec![Outpoint::new("def", 0), Outpoint::new("ghi", 1)];
1561 store
1562 .update_consumed_by("abc", 0, "tm_test", &consumed)
1563 .await
1564 .unwrap();
1565
1566 let found = store
1567 .find_output("abc", 0, Some("tm_test"), None, false)
1568 .await
1569 .unwrap()
1570 .unwrap();
1571 assert_eq!(found.consumed_by.len(), 2);
1572 assert_eq!(found.consumed_by[0].txid, "def");
1573 assert_eq!(found.consumed_by[1].output_index, 1);
1574 }
1575
1576 #[tokio::test]
1577 async fn test_update_transaction_beef() {
1578 let store = MemoryStorage::new();
1579 store
1580 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1581 .await
1582 .unwrap();
1583
1584 store
1585 .update_transaction_beef("abc", &[0xDE, 0xAD])
1586 .await
1587 .unwrap();
1588
1589 let found = store
1590 .find_output("abc", 0, Some("tm_test"), None, true)
1591 .await
1592 .unwrap()
1593 .unwrap();
1594 assert_eq!(found.beef.unwrap(), vec![0xDE, 0xAD]);
1595 }
1596
1597 #[tokio::test]
1598 async fn test_update_output_block_height() {
1599 let store = MemoryStorage::new();
1600 store
1601 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1602 .await
1603 .unwrap();
1604
1605 store
1606 .update_output_block_height("abc", 0, "tm_test", 850_000)
1607 .await
1608 .unwrap();
1609
1610 let found = store
1611 .find_output("abc", 0, Some("tm_test"), None, false)
1612 .await
1613 .unwrap()
1614 .unwrap();
1615 assert_eq!(found.block_height, Some(850_000));
1616 }
1617
1618 #[tokio::test]
1619 async fn test_find_outputs_for_transaction() {
1620 let store = MemoryStorage::new();
1621 store
1622 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1623 .await
1624 .unwrap();
1625 store
1626 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
1627 .await
1628 .unwrap();
1629 store
1630 .insert_output(&make_output("other", 0, "tm_test", 3.0))
1631 .await
1632 .unwrap();
1633
1634 let results = store
1635 .find_outputs_for_transaction("abc", false)
1636 .await
1637 .unwrap();
1638 assert_eq!(results.len(), 2);
1639 }
1640
1641 #[tokio::test]
1642 async fn test_find_utxos_for_topic() {
1643 let store = MemoryStorage::new();
1644 store
1645 .insert_output(&make_output("a", 0, "tm_test", 1.0))
1646 .await
1647 .unwrap();
1648 store
1649 .insert_output(&make_output("b", 0, "tm_test", 2.0))
1650 .await
1651 .unwrap();
1652 store
1653 .insert_output(&make_output("c", 0, "tm_test", 3.0))
1654 .await
1655 .unwrap();
1656 store
1657 .insert_output(&make_output("d", 0, "tm_other", 4.0))
1658 .await
1659 .unwrap();
1660
1661 store.mark_utxo_as_spent("b", 0, "tm_test").await.unwrap();
1663
1664 let results = store
1666 .find_utxos_for_topic("tm_test", None, None, false)
1667 .await
1668 .unwrap();
1669 assert_eq!(results.len(), 2); let results = store
1673 .find_utxos_for_topic("tm_test", Some(2.0), None, false)
1674 .await
1675 .unwrap();
1676 assert_eq!(results.len(), 1); let results = store
1680 .find_utxos_for_topic("tm_test", None, Some(1), false)
1681 .await
1682 .unwrap();
1683 assert_eq!(results.len(), 1);
1684 assert_eq!(results[0].txid, "a"); }
1686
1687 #[tokio::test]
1688 async fn test_find_utxos_for_topic_sorted_by_score() {
1689 let store = MemoryStorage::new();
1690 store
1691 .insert_output(&make_output("c", 0, "tm_test", 30.0))
1692 .await
1693 .unwrap();
1694 store
1695 .insert_output(&make_output("a", 0, "tm_test", 10.0))
1696 .await
1697 .unwrap();
1698 store
1699 .insert_output(&make_output("b", 0, "tm_test", 20.0))
1700 .await
1701 .unwrap();
1702
1703 let results = store
1704 .find_utxos_for_topic("tm_test", None, None, false)
1705 .await
1706 .unwrap();
1707 assert_eq!(results[0].txid, "a");
1708 assert_eq!(results[1].txid, "b");
1709 assert_eq!(results[2].txid, "c");
1710 }
1711
1712 #[tokio::test]
1713 async fn test_applied_transaction() {
1714 let store = MemoryStorage::new();
1715 let tx = AppliedTransaction {
1716 txid: "abc".to_string(),
1717 topic: "tm_test".to_string(),
1718 };
1719
1720 assert!(!store.does_applied_transaction_exist(&tx).await.unwrap());
1721 store.insert_applied_transaction(&tx).await.unwrap();
1722 assert!(store.does_applied_transaction_exist(&tx).await.unwrap());
1723
1724 let tx2 = AppliedTransaction {
1726 txid: "abc".to_string(),
1727 topic: "tm_other".to_string(),
1728 };
1729 assert!(!store.does_applied_transaction_exist(&tx2).await.unwrap());
1730 }
1731
1732 #[tokio::test]
1733 async fn test_sync_state() {
1734 let store = MemoryStorage::new();
1735
1736 assert_eq!(
1737 store
1738 .get_last_interaction("host1", "tm_test")
1739 .await
1740 .unwrap(),
1741 0
1742 );
1743
1744 store
1745 .update_last_interaction("host1", "tm_test", 100)
1746 .await
1747 .unwrap();
1748 assert_eq!(
1749 store
1750 .get_last_interaction("host1", "tm_test")
1751 .await
1752 .unwrap(),
1753 100
1754 );
1755
1756 store
1758 .update_last_interaction("host1", "tm_test", 200)
1759 .await
1760 .unwrap();
1761 assert_eq!(
1762 store
1763 .get_last_interaction("host1", "tm_test")
1764 .await
1765 .unwrap(),
1766 200
1767 );
1768
1769 assert_eq!(
1771 store
1772 .get_last_interaction("host2", "tm_test")
1773 .await
1774 .unwrap(),
1775 0
1776 );
1777 }
1778
1779 #[tokio::test]
1780 async fn test_find_outputs_by_outpoints_batch() {
1781 let store = MemoryStorage::new();
1782 store
1783 .insert_output(&make_output("a", 0, "tm_test", 1.0))
1784 .await
1785 .unwrap();
1786 store
1787 .insert_output(&make_output("b", 0, "tm_test", 2.0))
1788 .await
1789 .unwrap();
1790 store
1791 .insert_output(&make_output("c", 0, "tm_test", 3.0))
1792 .await
1793 .unwrap();
1794
1795 let outpoints = vec![
1796 Outpoint::new("a", 0),
1797 Outpoint::new("c", 0),
1798 Outpoint::new("missing", 0),
1799 ];
1800 let results = store
1801 .find_outputs_by_outpoints(&outpoints, false)
1802 .await
1803 .unwrap();
1804 assert_eq!(results.len(), 2); }
1806
1807 #[tokio::test]
1808 async fn test_find_output_without_topic() {
1809 let store = MemoryStorage::new();
1810 store
1811 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1812 .await
1813 .unwrap();
1814
1815 let found = store
1817 .find_output("abc", 0, None, None, false)
1818 .await
1819 .unwrap();
1820 assert!(found.is_some());
1821 assert_eq!(found.unwrap().topic, "tm_test");
1822 }
1823}