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 get_peer_cadence(
267 &self,
268 _host: &str,
269 _topic: &str,
270 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
271 Ok(crate::sync_load::PeerCadence::default())
272 }
273
274 async fn set_peer_cadence(
275 &self,
276 _host: &str,
277 _topic: &str,
278 _state: &crate::sync_load::PeerCadence,
279 ) -> Result<(), StorageError> {
280 Ok(())
281 }
282
283 async fn record_peer_sync_outcome(
306 &self,
307 host: &str,
308 topic: &str,
309 success: bool,
310 ) -> Result<(), StorageError>;
311
312 async fn get_peer_sync_health(
317 &self,
318 host: &str,
319 topic: &str,
320 ) -> Result<PeerSyncHealth, StorageError>;
321
322 async fn record_peer_sync_yield(
340 &self,
341 host: &str,
342 topic: &str,
343 yielded: bool,
344 ) -> Result<PeerYieldStreak, StorageError>;
345
346 async fn put_deferred_graph(
379 &self,
380 record: &crate::gasp::DeferredGraph,
381 ) -> Result<crate::gasp::DeferredGraphSave, StorageError>;
382
383 async fn reserve_unanchorable_retry(
387 &self,
388 record: &crate::gasp::DeferredGraph,
389 ) -> Result<bool, StorageError>;
390
391 async fn find_deferred_graphs(
395 &self,
396 host: &str,
397 topic: &str,
398 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError>;
399
400 async fn get_deferred_graph(
402 &self,
403 host: &str,
404 topic: &str,
405 outpoint: &str,
406 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError>;
407
408 async fn delete_deferred_graph(
410 &self,
411 host: &str,
412 topic: &str,
413 outpoint: &str,
414 ) -> Result<(), StorageError>;
415
416 async fn remember_expired_outpoint(
433 &self,
434 _topic: &str,
435 _outpoint: &str,
436 _expiry: u64,
437 ) -> Result<(), StorageError> {
438 Ok(())
439 }
440
441 async fn find_expired_outpoints(
444 &self,
445 _topic: &str,
446 _outpoints: &[String],
447 ) -> Result<Vec<String>, StorageError> {
448 Ok(Vec::new())
449 }
450}
451
452#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
455pub struct PeerYieldStreak {
456 pub yieldless_syncs: u64,
458 pub secs_since_first: Option<u64>,
461}
462
463#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
466pub struct PeerSyncHealth {
467 pub consecutive_failures: u64,
470 pub secs_since_last_attempt: Option<u64>,
473}
474
475#[async_trait(?Send)]
484impl<T: Storage + ?Sized> Storage for std::rc::Rc<T> {
485 async fn get_peer_cadence(
486 &self,
487 host: &str,
488 topic: &str,
489 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
490 (**self).get_peer_cadence(host, topic).await
491 }
492 async fn set_peer_cadence(
493 &self,
494 host: &str,
495 topic: &str,
496 state: &crate::sync_load::PeerCadence,
497 ) -> Result<(), StorageError> {
498 (**self).set_peer_cadence(host, topic, state).await
499 }
500 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
501 (**self).insert_output(output).await
502 }
503 async fn delete_output(
504 &self,
505 txid: &str,
506 output_index: u32,
507 topic: &str,
508 ) -> Result<(), StorageError> {
509 (**self).delete_output(txid, output_index, topic).await
510 }
511 async fn mark_utxo_as_spent(
512 &self,
513 txid: &str,
514 output_index: u32,
515 topic: &str,
516 ) -> Result<(), StorageError> {
517 (**self).mark_utxo_as_spent(txid, output_index, topic).await
518 }
519 async fn update_consumed_by(
520 &self,
521 txid: &str,
522 output_index: u32,
523 topic: &str,
524 consumed_by: &[Outpoint],
525 ) -> Result<(), StorageError> {
526 (**self)
527 .update_consumed_by(txid, output_index, topic, consumed_by)
528 .await
529 }
530 async fn update_transaction_beef(&self, txid: &str, beef: &[u8]) -> Result<(), StorageError> {
531 (**self).update_transaction_beef(txid, beef).await
532 }
533 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
534 (**self).mark_transaction_proven(txid).await
535 }
536 async fn mark_transaction_proven_at(
537 &self,
538 txid: &str,
539 height: Option<u64>,
540 ) -> Result<(), StorageError> {
541 (**self).mark_transaction_proven_at(txid, height).await
545 }
546 async fn update_output_block_height(
547 &self,
548 txid: &str,
549 output_index: u32,
550 topic: &str,
551 block_height: u32,
552 ) -> Result<(), StorageError> {
553 (**self)
554 .update_output_block_height(txid, output_index, topic, block_height)
555 .await
556 }
557 async fn insert_applied_transaction(
558 &self,
559 tx: &AppliedTransaction,
560 ) -> Result<(), StorageError> {
561 (**self).insert_applied_transaction(tx).await
562 }
563 async fn does_applied_transaction_exist(
564 &self,
565 tx: &AppliedTransaction,
566 ) -> Result<bool, StorageError> {
567 (**self).does_applied_transaction_exist(tx).await
568 }
569 async fn delete_applied_transaction(
570 &self,
571 tx: &AppliedTransaction,
572 ) -> Result<(), StorageError> {
573 (**self).delete_applied_transaction(tx).await
574 }
575 async fn find_output(
576 &self,
577 txid: &str,
578 output_index: u32,
579 topic: Option<&str>,
580 spent: Option<bool>,
581 include_beef: bool,
582 ) -> Result<Option<Output>, StorageError> {
583 (**self)
584 .find_output(txid, output_index, topic, spent, include_beef)
585 .await
586 }
587 async fn find_served_outputs(
588 &self,
589 outpoints: &[Outpoint],
590 ) -> Result<Vec<Output>, StorageError> {
591 (**self).find_served_outputs(outpoints).await
592 }
593
594 async fn find_outputs_for_transaction(
595 &self,
596 txid: &str,
597 include_beef: bool,
598 ) -> Result<Vec<Output>, StorageError> {
599 (**self)
600 .find_outputs_for_transaction(txid, include_beef)
601 .await
602 }
603 async fn find_utxos_for_topic(
604 &self,
605 topic: &str,
606 since: Option<f64>,
607 limit: Option<u64>,
608 include_beef: bool,
609 ) -> Result<Vec<Output>, StorageError> {
610 (**self)
611 .find_utxos_for_topic(topic, since, limit, include_beef)
612 .await
613 }
614 async fn find_transactions_for_proof_check(
615 &self,
616 limit: u64,
617 min_age_secs: u64,
618 ) -> Result<Vec<TransactionBeef>, StorageError> {
619 (**self)
620 .find_transactions_for_proof_check(limit, min_age_secs)
621 .await
622 }
623 async fn update_last_interaction(
624 &self,
625 host: &str,
626 topic: &str,
627 since: u64,
628 ) -> Result<(), StorageError> {
629 (**self).update_last_interaction(host, topic, since).await
630 }
631 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
632 (**self).get_last_interaction(host, topic).await
633 }
634 async fn record_peer_sync_outcome(
635 &self,
636 host: &str,
637 topic: &str,
638 success: bool,
639 ) -> Result<(), StorageError> {
640 (**self)
641 .record_peer_sync_outcome(host, topic, success)
642 .await
643 }
644 async fn get_peer_sync_health(
645 &self,
646 host: &str,
647 topic: &str,
648 ) -> Result<PeerSyncHealth, StorageError> {
649 (**self).get_peer_sync_health(host, topic).await
650 }
651 async fn record_peer_sync_yield(
652 &self,
653 host: &str,
654 topic: &str,
655 yielded: bool,
656 ) -> Result<PeerYieldStreak, StorageError> {
657 (**self).record_peer_sync_yield(host, topic, yielded).await
658 }
659 async fn put_deferred_graph(
660 &self,
661 record: &crate::gasp::DeferredGraph,
662 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
663 (**self).put_deferred_graph(record).await
664 }
665 async fn reserve_unanchorable_retry(
666 &self,
667 record: &crate::gasp::DeferredGraph,
668 ) -> Result<bool, StorageError> {
669 (**self).reserve_unanchorable_retry(record).await
670 }
671 async fn find_deferred_graphs(
672 &self,
673 host: &str,
674 topic: &str,
675 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
676 (**self).find_deferred_graphs(host, topic).await
677 }
678 async fn get_deferred_graph(
679 &self,
680 host: &str,
681 topic: &str,
682 outpoint: &str,
683 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
684 (**self).get_deferred_graph(host, topic, outpoint).await
685 }
686 async fn delete_deferred_graph(
687 &self,
688 host: &str,
689 topic: &str,
690 outpoint: &str,
691 ) -> Result<(), StorageError> {
692 (**self).delete_deferred_graph(host, topic, outpoint).await
693 }
694 async fn remember_expired_outpoint(
695 &self,
696 topic: &str,
697 outpoint: &str,
698 expiry: u64,
699 ) -> Result<(), StorageError> {
700 (**self)
701 .remember_expired_outpoint(topic, outpoint, expiry)
702 .await
703 }
704 async fn find_expired_outpoints(
705 &self,
706 topic: &str,
707 outpoints: &[String],
708 ) -> Result<Vec<String>, StorageError> {
709 (**self).find_expired_outpoints(topic, outpoints).await
710 }
711}
712
713#[derive(Debug, thiserror::Error)]
719pub enum StorageError {
720 #[error("not found: {0}")]
721 NotFound(String),
722
723 #[error("duplicate: {0}")]
724 Duplicate(String),
725
726 #[error("database error: {0}")]
727 Database(String),
728
729 #[error("serialization error: {0}")]
730 Serialization(String),
731
732 #[error("{0}")]
733 Other(String),
734}
735
736#[cfg(any(test, feature = "memory-storage"))]
741pub mod memory {
742 use super::*;
743 use std::collections::HashMap;
744 use std::sync::Mutex;
745
746 #[derive(Debug, Default)]
750 pub struct MemoryStorage {
751 cadence: Mutex<HashMap<(String, String), (crate::sync_load::PeerCadence, u64)>>,
752 outputs: Mutex<HashMap<(String, u32, String), Output>>,
754 transactions: Mutex<HashMap<String, Vec<u8>>>,
756 proven: Mutex<std::collections::HashSet<String>>,
764 applied: Mutex<HashMap<(String, String), bool>>,
766 sync_state: Mutex<HashMap<(String, String), u64>>,
768 clock_secs: Mutex<u64>,
773 created_at: Mutex<HashMap<String, u64>>,
777 peer_health: Mutex<HashMap<(String, String), (u64, u64)>>,
782 proven_at: Mutex<HashMap<String, Option<u64>>>,
786 deferred_graphs: Mutex<HashMap<(String, String, String), crate::gasp::DeferredGraph>>,
789 deferred_graph_writes: Mutex<u64>,
792 deferred_graph_reads: Mutex<u64>,
795 deferred_graph_keys_fault: Mutex<bool>,
797 deferred_graph_ceiling: Mutex<Option<usize>>,
800 deferred_graph_byte_ceiling: Mutex<Option<usize>>,
804 deferred_graph_put_fault: Mutex<bool>,
806 expired_outpoints: Mutex<HashMap<(String, String), (u64, u64)>>,
810 deferred_graph_put_attempts: Mutex<u64>,
813 peer_yieldless: Mutex<HashMap<(String, String), YieldStreakRow>>,
819 }
820
821 type YieldStreakRow = (u64, u64, u64);
824
825 impl MemoryStorage {
826 pub fn new() -> Self {
827 Self::default()
828 }
829
830 pub fn proven_at_height(&self, txid: &str) -> Option<Option<u64>> {
834 self.proven_at.lock().unwrap().get(txid).copied()
835 }
836
837 pub fn expired_outpoints(&self, topic: &str) -> Vec<String> {
842 let now = *self.clock_secs.lock().unwrap();
843 let mut held: Vec<String> = self
844 .expired_outpoints
845 .lock()
846 .unwrap()
847 .iter()
848 .filter(|((t, _), (keep, _))| t == topic && *keep > now)
849 .map(|((_, o), _)| o.clone())
850 .collect();
851 held.sort();
852 held
853 }
854
855 pub fn advance_clock(&self, secs: u64) {
856 *self.clock_secs.lock().unwrap() += secs;
857 }
858
859 fn stamp_created(&self, txid: &str) {
862 let now = *self.clock_secs.lock().unwrap();
863 self.created_at
864 .lock()
865 .unwrap()
866 .entry(txid.to_string())
867 .or_insert(now);
868 }
869
870 fn age_gate_open(&self, txid: &str, min_age_secs: u64) -> bool {
873 if min_age_secs == 0 {
874 return true;
875 }
876 let now = *self.clock_secs.lock().unwrap();
877 match self.created_at.lock().unwrap().get(txid) {
878 None => true, Some(created) => now.saturating_sub(*created) >= min_age_secs,
880 }
881 }
882
883 pub fn deferred_graphs(&self) -> Vec<crate::gasp::DeferredGraph> {
885 let mut all: Vec<_> = self
886 .deferred_graphs
887 .lock()
888 .unwrap()
889 .values()
890 .cloned()
891 .collect();
892 all.sort_by(|a, b| (a.score, &a.outpoint).cmp(&(b.score, &b.outpoint)));
893 all
894 }
895
896 pub fn deferred_graph_writes(&self) -> u64 {
898 *self.deferred_graph_writes.lock().unwrap()
899 }
900
901 pub fn deferred_graph_reads(&self) -> u64 {
903 *self.deferred_graph_reads.lock().unwrap()
904 }
905
906 pub fn set_deferred_graph_keys_fault(&self, fault: bool) {
909 *self.deferred_graph_keys_fault.lock().unwrap() = fault;
910 }
911
912 pub fn set_deferred_graph_ceiling(&self, ceiling: Option<usize>) {
915 *self.deferred_graph_ceiling.lock().unwrap() = ceiling;
916 }
917
918 pub fn set_deferred_graph_byte_ceiling(&self, ceiling: Option<usize>) {
922 *self.deferred_graph_byte_ceiling.lock().unwrap() = ceiling;
923 }
924
925 pub fn set_deferred_graph_put_fault(&self, fault: bool) {
928 *self.deferred_graph_put_fault.lock().unwrap() = fault;
929 }
930
931 pub fn deferred_graph_put_attempts(&self) -> u64 {
934 *self.deferred_graph_put_attempts.lock().unwrap()
935 }
936
937 pub fn peer_yieldless_syncs(&self, host: &str, topic: &str) -> u64 {
942 self.peer_yieldless
943 .lock()
944 .unwrap()
945 .get(&(crate::gasp::peer_origin(host), topic.to_string()))
946 .map_or(0, |(count, _, _)| *count)
947 }
948
949 pub fn output_count(&self) -> usize {
951 self.outputs.lock().unwrap().len()
952 }
953
954 pub fn transaction_count(&self) -> usize {
956 self.transactions.lock().unwrap().len()
957 }
958 }
959
960 #[async_trait(?Send)]
961 impl Storage for MemoryStorage {
962 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
963 let key = (
964 output.txid.clone(),
965 output.output_index,
966 output.topic.clone(),
967 );
968 let mut outputs = self.outputs.lock().unwrap();
969
970 if outputs.contains_key(&key) {
972 if let Some(ref beef) = output.beef {
974 self.transactions
975 .lock()
976 .unwrap()
977 .entry(output.txid.clone())
978 .or_insert_with(|| beef.clone());
979 }
980 return Ok(());
981 }
982
983 let mut stored = output.clone();
985 stored.beef = None;
986 outputs.insert(key, stored);
987
988 if let Some(ref beef) = output.beef {
990 self.transactions
991 .lock()
992 .unwrap()
993 .entry(output.txid.clone())
994 .or_insert_with(|| beef.clone());
995 self.stamp_created(&output.txid);
996 }
997
998 Ok(())
999 }
1000
1001 async fn delete_output(
1002 &self,
1003 txid: &str,
1004 output_index: u32,
1005 topic: &str,
1006 ) -> Result<(), StorageError> {
1007 let key = (txid.to_string(), output_index, topic.to_string());
1008 let mut outputs = self.outputs.lock().unwrap();
1009 outputs.remove(&key);
1010
1011 let has_remaining = outputs.keys().any(|(t, _, _)| t == txid);
1013 if !has_remaining {
1014 self.transactions.lock().unwrap().remove(txid);
1015 self.proven.lock().unwrap().remove(txid);
1016 }
1017
1018 Ok(())
1019 }
1020
1021 async fn mark_utxo_as_spent(
1022 &self,
1023 txid: &str,
1024 output_index: u32,
1025 topic: &str,
1026 ) -> Result<(), StorageError> {
1027 let key = (txid.to_string(), output_index, topic.to_string());
1028 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1029 output.spent = true;
1030 }
1031 Ok(())
1032 }
1033
1034 async fn update_consumed_by(
1035 &self,
1036 txid: &str,
1037 output_index: u32,
1038 topic: &str,
1039 consumed_by: &[Outpoint],
1040 ) -> Result<(), StorageError> {
1041 let key = (txid.to_string(), output_index, topic.to_string());
1042 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1043 output.consumed_by = consumed_by.to_vec();
1044 }
1045 Ok(())
1046 }
1047
1048 async fn update_transaction_beef(
1049 &self,
1050 txid: &str,
1051 beef: &[u8],
1052 ) -> Result<(), StorageError> {
1053 let has_proof = beef_limits::has_proof(beef, txid);
1059 if has_proof {
1060 self.proven.lock().unwrap().insert(txid.to_string());
1061 }
1062 self.transactions
1063 .lock()
1064 .unwrap()
1065 .insert(txid.to_string(), beef.to_vec());
1066 self.stamp_created(txid);
1069 Ok(())
1070 }
1071
1072 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
1073 self.proven.lock().unwrap().insert(txid.to_string());
1076 Ok(())
1077 }
1078
1079 async fn mark_transaction_proven_at(
1080 &self,
1081 txid: &str,
1082 height: Option<u64>,
1083 ) -> Result<(), StorageError> {
1084 self.proven.lock().unwrap().insert(txid.to_string());
1088 self.proven_at
1089 .lock()
1090 .unwrap()
1091 .insert(txid.to_string(), height);
1092 Ok(())
1093 }
1094
1095 async fn update_output_block_height(
1096 &self,
1097 txid: &str,
1098 output_index: u32,
1099 topic: &str,
1100 block_height: u32,
1101 ) -> Result<(), StorageError> {
1102 let key = (txid.to_string(), output_index, topic.to_string());
1103 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1104 output.block_height = Some(block_height);
1105 }
1106 Ok(())
1107 }
1108
1109 async fn insert_applied_transaction(
1110 &self,
1111 tx: &AppliedTransaction,
1112 ) -> Result<(), StorageError> {
1113 self.applied
1114 .lock()
1115 .unwrap()
1116 .insert((tx.txid.clone(), tx.topic.clone()), true);
1117 Ok(())
1118 }
1119
1120 async fn does_applied_transaction_exist(
1121 &self,
1122 tx: &AppliedTransaction,
1123 ) -> Result<bool, StorageError> {
1124 Ok(self
1125 .applied
1126 .lock()
1127 .unwrap()
1128 .contains_key(&(tx.txid.clone(), tx.topic.clone())))
1129 }
1130
1131 async fn delete_applied_transaction(
1132 &self,
1133 tx: &AppliedTransaction,
1134 ) -> Result<(), StorageError> {
1135 self.applied
1136 .lock()
1137 .unwrap()
1138 .remove(&(tx.txid.clone(), tx.topic.clone()));
1139 Ok(())
1140 }
1141
1142 async fn find_output(
1143 &self,
1144 txid: &str,
1145 output_index: u32,
1146 topic: Option<&str>,
1147 spent: Option<bool>,
1148 include_beef: bool,
1149 ) -> Result<Option<Output>, StorageError> {
1150 let outputs = self.outputs.lock().unwrap();
1151
1152 if let Some(topic) = topic {
1154 let key = (txid.to_string(), output_index, topic.to_string());
1155 if let Some(output) = outputs.get(&key) {
1156 if let Some(s) = spent {
1157 if output.spent != s {
1158 return Ok(None);
1159 }
1160 }
1161 let mut result = output.clone();
1162 if include_beef {
1163 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1164 }
1165 return Ok(Some(result));
1166 }
1167 return Ok(None);
1168 }
1169
1170 for ((t, oi, _), output) in outputs.iter() {
1172 if t == txid && *oi == output_index {
1173 if let Some(s) = spent {
1174 if output.spent != s {
1175 continue;
1176 }
1177 }
1178 let mut result = output.clone();
1179 if include_beef {
1180 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1181 }
1182 return Ok(Some(result));
1183 }
1184 }
1185 Ok(None)
1186 }
1187
1188 async fn find_served_outputs(
1189 &self,
1190 outpoints: &[Outpoint],
1191 ) -> Result<Vec<Output>, StorageError> {
1192 let outputs = self.find_outputs_by_outpoints(outpoints, true).await?;
1193 let now = i64::try_from(*self.clock_secs.lock().unwrap())
1194 .unwrap_or(i64::MAX)
1195 .saturating_mul(1000);
1196 let proven = self.proven.lock().unwrap();
1197 let stamps = self.created_at.lock().unwrap();
1198 Ok(outputs
1199 .into_iter()
1200 .filter(|output| {
1201 let seen = stamps
1202 .get(&output.txid)
1203 .and_then(|s| i64::try_from(*s).ok())
1204 .map(|s| s.saturating_mul(1000));
1205 crate::proof_window::verdict(proven.contains(&output.txid), seen, now)
1206 == crate::proof_window::Verdict::Served
1207 })
1208 .collect())
1209 }
1210
1211 async fn find_outputs_for_transaction(
1212 &self,
1213 txid: &str,
1214 include_beef: bool,
1215 ) -> Result<Vec<Output>, StorageError> {
1216 let outputs = self.outputs.lock().unwrap();
1217 let beef = if include_beef {
1218 self.transactions.lock().unwrap().get(txid).cloned()
1219 } else {
1220 None
1221 };
1222
1223 let results: Vec<Output> = outputs
1224 .iter()
1225 .filter(|((t, _, _), _)| t == txid)
1226 .map(|(_, output)| {
1227 let mut o = output.clone();
1228 if include_beef {
1229 o.beef.clone_from(&beef);
1230 }
1231 o
1232 })
1233 .collect();
1234
1235 Ok(results)
1236 }
1237
1238 async fn find_utxos_for_topic(
1239 &self,
1240 topic: &str,
1241 since: Option<f64>,
1242 limit: Option<u64>,
1243 include_beef: bool,
1244 ) -> Result<Vec<Output>, StorageError> {
1245 let outputs = self.outputs.lock().unwrap();
1246 let transactions = self.transactions.lock().unwrap();
1247
1248 let mut results: Vec<Output> = outputs
1249 .iter()
1250 .filter(|((_, _, t), o)| {
1251 t == topic && !o.spent && since.is_none_or(|s| o.score.unwrap_or(0.0) >= s)
1252 })
1253 .map(|(_, output)| {
1254 let mut o = output.clone();
1255 if include_beef {
1256 o.beef = transactions.get(&o.txid).cloned();
1257 }
1258 o
1259 })
1260 .collect();
1261
1262 results.sort_by(|a, b| {
1264 a.score
1265 .unwrap_or(0.0)
1266 .partial_cmp(&b.score.unwrap_or(0.0))
1267 .unwrap_or(std::cmp::Ordering::Equal)
1268 });
1269
1270 if let Some(limit) = limit {
1271 results.truncate(limit as usize);
1272 }
1273
1274 Ok(results)
1275 }
1276
1277 async fn find_transactions_for_proof_check(
1278 &self,
1279 limit: u64,
1280 min_age_secs: u64,
1281 ) -> Result<Vec<TransactionBeef>, StorageError> {
1282 let candidates: Vec<TransactionBeef> = {
1295 let transactions = self.transactions.lock().unwrap();
1296 let proven = self.proven.lock().unwrap();
1297 transactions
1298 .iter()
1299 .filter(|(txid, _)| !proven.contains(*txid))
1300 .map(|(txid, beef)| TransactionBeef {
1301 txid: txid.clone(),
1302 beef: beef.clone(),
1303 })
1304 .collect()
1305 };
1306 Ok(candidates
1307 .into_iter()
1308 .filter(|c| self.age_gate_open(&c.txid, min_age_secs))
1309 .take(limit as usize)
1310 .collect())
1311 }
1312
1313 async fn update_last_interaction(
1314 &self,
1315 host: &str,
1316 topic: &str,
1317 since: u64,
1318 ) -> Result<(), StorageError> {
1319 self.sync_state
1320 .lock()
1321 .unwrap()
1322 .insert((host.to_string(), topic.to_string()), since);
1323 Ok(())
1324 }
1325
1326 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
1327 Ok(*self
1328 .sync_state
1329 .lock()
1330 .unwrap()
1331 .get(&(host.to_string(), topic.to_string()))
1332 .unwrap_or(&0))
1333 }
1334
1335 async fn get_peer_cadence(
1336 &self,
1337 host: &str,
1338 topic: &str,
1339 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
1340 let now = *self.clock_secs.lock().unwrap();
1341 Ok(self
1342 .cadence
1343 .lock()
1344 .unwrap()
1345 .get(&(host.into(), topic.into()))
1346 .map_or_else(Default::default, |(s, at)| {
1347 let mut state = s.clone();
1348 state.retry_after_secs = at.saturating_sub(now);
1349 state
1350 }))
1351 }
1352 async fn set_peer_cadence(
1353 &self,
1354 host: &str,
1355 topic: &str,
1356 state: &crate::sync_load::PeerCadence,
1357 ) -> Result<(), StorageError> {
1358 let now = *self.clock_secs.lock().unwrap();
1359 self.cadence.lock().unwrap().insert(
1360 (host.into(), topic.into()),
1361 (state.clone(), now.saturating_add(state.retry_after_secs)),
1362 );
1363 Ok(())
1364 }
1365 async fn record_peer_sync_outcome(
1366 &self,
1367 host: &str,
1368 topic: &str,
1369 success: bool,
1370 ) -> Result<(), StorageError> {
1371 let now = *self.clock_secs.lock().unwrap();
1372 let mut health = self.peer_health.lock().unwrap();
1373 let entry = health
1374 .entry((host.to_string(), topic.to_string()))
1375 .or_insert((0, now));
1376 entry.0 = if success { 0 } else { entry.0 + 1 };
1377 entry.1 = now;
1378 Ok(())
1379 }
1380
1381 async fn get_peer_sync_health(
1382 &self,
1383 host: &str,
1384 topic: &str,
1385 ) -> Result<PeerSyncHealth, StorageError> {
1386 let now = *self.clock_secs.lock().unwrap();
1387 Ok(self
1388 .peer_health
1389 .lock()
1390 .unwrap()
1391 .get(&(host.to_string(), topic.to_string()))
1392 .map(|(fails, attempt_at)| PeerSyncHealth {
1393 consecutive_failures: *fails,
1394 secs_since_last_attempt: Some(now.saturating_sub(*attempt_at)),
1395 })
1396 .unwrap_or_default())
1397 }
1398
1399 async fn record_peer_sync_yield(
1400 &self,
1401 host: &str,
1402 topic: &str,
1403 yielded: bool,
1404 ) -> Result<PeerYieldStreak, StorageError> {
1405 let now = *self.clock_secs.lock().unwrap();
1406 let key = (host.to_string(), topic.to_string());
1407 let not_failed = self
1410 .peer_health
1411 .lock()
1412 .unwrap()
1413 .get(&key)
1414 .is_none_or(|(fails, _)| *fails == 0);
1415 let mut held = self.peer_yieldless.lock().unwrap();
1416 if yielded {
1417 held.remove(&key);
1418 return Ok(PeerYieldStreak::default());
1419 }
1420 let streak = held.entry(key).or_insert((0, now, now));
1421 if streak.0 == 0
1422 || (not_failed
1423 && now.saturating_sub(streak.2) > crate::gasp::PEER_YIELDLESS_DECAY_SECS)
1424 {
1425 *streak = (0, now, now);
1426 }
1427 streak.0 += 1;
1428 streak.2 = now;
1429 Ok(PeerYieldStreak {
1430 yieldless_syncs: streak.0,
1431 secs_since_first: Some(now.saturating_sub(streak.1)),
1432 })
1433 }
1434
1435 async fn put_deferred_graph(
1436 &self,
1437 record: &crate::gasp::DeferredGraph,
1438 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
1439 let key = (
1440 record.peer.clone(),
1441 record.topic.clone(),
1442 record.outpoint.clone(),
1443 );
1444 *self.deferred_graph_put_attempts.lock().unwrap() += 1;
1445 if *self.deferred_graph_put_fault.lock().unwrap() {
1446 return Err(StorageError::Database("deferred graph save faulted".into()));
1447 }
1448 if let Some(ceiling) = *self.deferred_graph_ceiling.lock().unwrap() {
1449 let held = self.deferred_graphs.lock().unwrap();
1450 if !held.contains_key(&key) && held.len() >= ceiling {
1451 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1452 }
1453 }
1454 if let Some(ceiling) = *self.deferred_graph_byte_ceiling.lock().unwrap() {
1455 let held = self.deferred_graphs.lock().unwrap();
1456 let others: usize = held
1457 .iter()
1458 .filter(|(k, _)| **k != key)
1459 .map(|(_, r)| r.byte_size())
1460 .sum();
1461 if others + record.byte_size() > ceiling {
1462 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1463 }
1464 }
1465 *self.deferred_graph_writes.lock().unwrap() += 1;
1466 self.deferred_graphs.lock().unwrap().insert(
1467 (
1468 record.peer.clone(),
1469 record.topic.clone(),
1470 record.outpoint.clone(),
1471 ),
1472 record.clone(),
1473 );
1474 Ok(crate::gasp::DeferredGraphSave::Saved)
1475 }
1476
1477 async fn reserve_unanchorable_retry(
1478 &self,
1479 record: &crate::gasp::DeferredGraph,
1480 ) -> Result<bool, StorageError> {
1481 *self.deferred_graph_put_attempts.lock().unwrap() += 1;
1482 if *self.deferred_graph_put_fault.lock().unwrap() {
1483 return Err(StorageError::Database("retry reservation faulted".into()));
1484 }
1485 let ceiling = *self.deferred_graph_byte_ceiling.lock().unwrap();
1486 let key = (
1487 record.peer.clone(),
1488 record.topic.clone(),
1489 record.outpoint.clone(),
1490 );
1491 let mut held = self.deferred_graphs.lock().unwrap();
1492 let Some(prior) = held.get(&key) else {
1493 return Ok(false);
1494 };
1495 let now = (crate::engine::current_timestamp_ms() / 1000.0) as u64;
1496 if !prior.is_unanchorable()
1497 || prior.reason != record.reason
1498 || prior.score != record.score
1499 || prior.retry_at > now
1500 || prior.unanchorable_retries.checked_add(1) != Some(record.unanchorable_retries)
1501 || record.unanchorable_retries > crate::gasp::UNANCHORABLE_RETRIES
1502 {
1503 return Ok(false);
1504 }
1505 if ceiling.is_some_and(|limit| {
1506 held.iter()
1507 .filter(|(k, _)| **k != key)
1508 .map(|(_, r)| r.byte_size())
1509 .sum::<usize>()
1510 + record.byte_size()
1511 > limit
1512 }) {
1513 return Ok(false);
1514 }
1515 held.insert(key, record.clone());
1516 *self.deferred_graph_writes.lock().unwrap() += 1;
1517 Ok(true)
1518 }
1519
1520 async fn find_deferred_graphs(
1521 &self,
1522 host: &str,
1523 topic: &str,
1524 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
1525 if *self.deferred_graph_keys_fault.lock().unwrap() {
1526 return Err(StorageError::Database("deferred graph keys: fault".into()));
1527 }
1528 Ok(self
1529 .deferred_graphs()
1530 .into_iter()
1531 .filter(|r| r.peer == host && r.topic == topic)
1532 .map(|r| crate::gasp::DeferredGraphKey {
1533 outpoint: r.outpoint,
1534 score: r.score,
1535 })
1536 .collect())
1537 }
1538
1539 async fn get_deferred_graph(
1540 &self,
1541 host: &str,
1542 topic: &str,
1543 outpoint: &str,
1544 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
1545 *self.deferred_graph_reads.lock().unwrap() += 1;
1546 Ok(self
1547 .deferred_graphs
1548 .lock()
1549 .unwrap()
1550 .get(&(host.to_string(), topic.to_string(), outpoint.to_string()))
1551 .cloned())
1552 }
1553
1554 async fn delete_deferred_graph(
1555 &self,
1556 host: &str,
1557 topic: &str,
1558 outpoint: &str,
1559 ) -> Result<(), StorageError> {
1560 self.deferred_graphs.lock().unwrap().remove(&(
1561 host.to_string(),
1562 topic.to_string(),
1563 outpoint.to_string(),
1564 ));
1565 Ok(())
1566 }
1567
1568 async fn remember_expired_outpoint(
1569 &self,
1570 topic: &str,
1571 outpoint: &str,
1572 expiry: u64,
1573 ) -> Result<(), StorageError> {
1574 let now = *self.clock_secs.lock().unwrap();
1575 let mut held = self.expired_outpoints.lock().unwrap();
1576 held.retain(|_, (keep, _)| *keep > now);
1577 held.insert(
1578 (topic.to_string(), outpoint.to_string()),
1579 (
1580 expiry.max(now) + crate::gasp::EXPIRED_OUTPOINT_MEMO_SECS,
1581 now,
1582 ),
1583 );
1584 let mut of_topic: Vec<(u64, String)> = held
1586 .iter()
1587 .filter(|((t, _), _)| t == topic)
1588 .map(|((_, o), (_, at))| (*at, o.clone()))
1589 .collect();
1590 let cap = crate::gasp::EXPIRED_OUTPOINT_MEMO_MAX_ROWS;
1591 if of_topic.len() > cap {
1592 of_topic.sort();
1593 for (_, o) in &of_topic[..of_topic.len() - cap] {
1594 held.remove(&(topic.to_string(), o.clone()));
1595 }
1596 }
1597 Ok(())
1598 }
1599
1600 async fn find_expired_outpoints(
1601 &self,
1602 topic: &str,
1603 outpoints: &[String],
1604 ) -> Result<Vec<String>, StorageError> {
1605 let now = *self.clock_secs.lock().unwrap();
1606 let held = self.expired_outpoints.lock().unwrap();
1607 Ok(outpoints
1608 .iter()
1609 .filter(|o| {
1610 held.get(&(topic.to_string(), (*o).clone()))
1611 .is_some_and(|(keep, _)| *keep > now)
1612 })
1613 .cloned()
1614 .collect())
1615 }
1616 }
1617}
1618
1619#[cfg(test)]
1624mod tests {
1625 use super::memory::MemoryStorage;
1626 use super::*;
1627 use crate::types::AppliedTransaction;
1628
1629 #[tokio::test]
1634 async fn the_rc_blanket_forwards_the_anchored_proven_latch() {
1635 let store = std::rc::Rc::new(MemoryStorage::new());
1636 let via_dyn: std::rc::Rc<dyn Storage> = store.clone();
1637 via_dyn
1638 .mark_transaction_proven_at("tx", Some(965_772))
1639 .await
1640 .unwrap();
1641 assert_eq!(
1642 store.proven_at_height("tx"),
1643 Some(Some(965_772)),
1644 "the Rc blanket forwarded the height to the inner store"
1645 );
1646 via_dyn
1648 .mark_transaction_proven_at("tx2", None)
1649 .await
1650 .unwrap();
1651 assert_eq!(store.proven_at_height("tx2"), Some(None));
1652 assert_eq!(
1653 store.proven_at_height("never"),
1654 None,
1655 "a txid never latched is absent"
1656 );
1657 }
1658
1659 fn make_output(txid: &str, index: u32, topic: &str, score: f64) -> Output {
1660 Output {
1661 txid: txid.to_string(),
1662 output_index: index,
1663 output_script: vec![0x76, 0xa9],
1664 satoshis: 1000,
1665 topic: topic.to_string(),
1666 spent: false,
1667 outputs_consumed: vec![],
1668 consumed_by: vec![],
1669 beef: Some(vec![0xBE, 0xEF]),
1670 block_height: None,
1671 score: Some(score),
1672 }
1673 }
1674
1675 #[tokio::test]
1676 async fn test_insert_and_find_output() {
1677 let store = MemoryStorage::new();
1678 let output = make_output("abc", 0, "tm_test", 1.0);
1679 store.insert_output(&output).await.unwrap();
1680
1681 let found = store
1683 .find_output("abc", 0, Some("tm_test"), None, false)
1684 .await
1685 .unwrap()
1686 .unwrap();
1687 assert_eq!(found.txid, "abc");
1688 assert_eq!(found.output_index, 0);
1689 assert!(found.beef.is_none());
1690
1691 let found = store
1693 .find_output("abc", 0, Some("tm_test"), None, true)
1694 .await
1695 .unwrap()
1696 .unwrap();
1697 assert_eq!(found.beef.unwrap(), vec![0xBE, 0xEF]);
1698 }
1699
1700 #[tokio::test]
1701 async fn test_insert_duplicate_is_noop() {
1702 let store = MemoryStorage::new();
1703 let output = make_output("abc", 0, "tm_test", 1.0);
1704 store.insert_output(&output).await.unwrap();
1705 store.insert_output(&output).await.unwrap();
1706 assert_eq!(store.output_count(), 1);
1707 }
1708
1709 #[tokio::test]
1710 async fn test_delete_output_cleans_up_beef() {
1711 let store = MemoryStorage::new();
1712 let output = make_output("abc", 0, "tm_test", 1.0);
1713 store.insert_output(&output).await.unwrap();
1714 assert_eq!(store.transaction_count(), 1);
1715
1716 store.delete_output("abc", 0, "tm_test").await.unwrap();
1717 assert_eq!(store.output_count(), 0);
1718 assert_eq!(store.transaction_count(), 0); }
1720
1721 #[tokio::test]
1722 async fn test_delete_output_keeps_beef_if_other_outputs_exist() {
1723 let store = MemoryStorage::new();
1724 store
1725 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1726 .await
1727 .unwrap();
1728 store
1729 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
1730 .await
1731 .unwrap();
1732 assert_eq!(store.output_count(), 2);
1733 assert_eq!(store.transaction_count(), 1);
1734
1735 store.delete_output("abc", 0, "tm_test").await.unwrap();
1736 assert_eq!(store.output_count(), 1);
1737 assert_eq!(store.transaction_count(), 1); }
1739
1740 #[tokio::test]
1741 async fn test_mark_utxo_as_spent() {
1742 let store = MemoryStorage::new();
1743 store
1744 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1745 .await
1746 .unwrap();
1747
1748 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
1749
1750 let found = store
1751 .find_output("abc", 0, Some("tm_test"), None, false)
1752 .await
1753 .unwrap()
1754 .unwrap();
1755 assert!(found.spent);
1756 }
1757
1758 #[tokio::test]
1759 async fn test_find_output_with_spent_filter() {
1760 let store = MemoryStorage::new();
1761 store
1762 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1763 .await
1764 .unwrap();
1765
1766 assert!(store
1768 .find_output("abc", 0, Some("tm_test"), Some(false), false)
1769 .await
1770 .unwrap()
1771 .is_some());
1772 assert!(store
1773 .find_output("abc", 0, Some("tm_test"), Some(true), false)
1774 .await
1775 .unwrap()
1776 .is_none());
1777
1778 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
1779
1780 assert!(store
1782 .find_output("abc", 0, Some("tm_test"), Some(true), false)
1783 .await
1784 .unwrap()
1785 .is_some());
1786 assert!(store
1787 .find_output("abc", 0, Some("tm_test"), Some(false), false)
1788 .await
1789 .unwrap()
1790 .is_none());
1791 }
1792
1793 #[tokio::test]
1794 async fn test_update_consumed_by() {
1795 let store = MemoryStorage::new();
1796 store
1797 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1798 .await
1799 .unwrap();
1800
1801 let consumed = vec![Outpoint::new("def", 0), Outpoint::new("ghi", 1)];
1802 store
1803 .update_consumed_by("abc", 0, "tm_test", &consumed)
1804 .await
1805 .unwrap();
1806
1807 let found = store
1808 .find_output("abc", 0, Some("tm_test"), None, false)
1809 .await
1810 .unwrap()
1811 .unwrap();
1812 assert_eq!(found.consumed_by.len(), 2);
1813 assert_eq!(found.consumed_by[0].txid, "def");
1814 assert_eq!(found.consumed_by[1].output_index, 1);
1815 }
1816
1817 #[tokio::test]
1818 async fn test_update_transaction_beef() {
1819 let store = MemoryStorage::new();
1820 store
1821 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1822 .await
1823 .unwrap();
1824
1825 store
1826 .update_transaction_beef("abc", &[0xDE, 0xAD])
1827 .await
1828 .unwrap();
1829
1830 let found = store
1831 .find_output("abc", 0, Some("tm_test"), None, true)
1832 .await
1833 .unwrap()
1834 .unwrap();
1835 assert_eq!(found.beef.unwrap(), vec![0xDE, 0xAD]);
1836 }
1837
1838 #[tokio::test]
1839 async fn test_update_output_block_height() {
1840 let store = MemoryStorage::new();
1841 store
1842 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1843 .await
1844 .unwrap();
1845
1846 store
1847 .update_output_block_height("abc", 0, "tm_test", 850_000)
1848 .await
1849 .unwrap();
1850
1851 let found = store
1852 .find_output("abc", 0, Some("tm_test"), None, false)
1853 .await
1854 .unwrap()
1855 .unwrap();
1856 assert_eq!(found.block_height, Some(850_000));
1857 }
1858
1859 #[tokio::test]
1860 async fn test_find_outputs_for_transaction() {
1861 let store = MemoryStorage::new();
1862 store
1863 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1864 .await
1865 .unwrap();
1866 store
1867 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
1868 .await
1869 .unwrap();
1870 store
1871 .insert_output(&make_output("other", 0, "tm_test", 3.0))
1872 .await
1873 .unwrap();
1874
1875 let results = store
1876 .find_outputs_for_transaction("abc", false)
1877 .await
1878 .unwrap();
1879 assert_eq!(results.len(), 2);
1880 }
1881
1882 #[tokio::test]
1883 async fn test_find_utxos_for_topic() {
1884 let store = MemoryStorage::new();
1885 store
1886 .insert_output(&make_output("a", 0, "tm_test", 1.0))
1887 .await
1888 .unwrap();
1889 store
1890 .insert_output(&make_output("b", 0, "tm_test", 2.0))
1891 .await
1892 .unwrap();
1893 store
1894 .insert_output(&make_output("c", 0, "tm_test", 3.0))
1895 .await
1896 .unwrap();
1897 store
1898 .insert_output(&make_output("d", 0, "tm_other", 4.0))
1899 .await
1900 .unwrap();
1901
1902 store.mark_utxo_as_spent("b", 0, "tm_test").await.unwrap();
1904
1905 let results = store
1907 .find_utxos_for_topic("tm_test", None, None, false)
1908 .await
1909 .unwrap();
1910 assert_eq!(results.len(), 2); let results = store
1914 .find_utxos_for_topic("tm_test", Some(2.0), None, false)
1915 .await
1916 .unwrap();
1917 assert_eq!(results.len(), 1); let results = store
1921 .find_utxos_for_topic("tm_test", None, Some(1), false)
1922 .await
1923 .unwrap();
1924 assert_eq!(results.len(), 1);
1925 assert_eq!(results[0].txid, "a"); }
1927
1928 #[tokio::test]
1929 async fn test_find_utxos_for_topic_sorted_by_score() {
1930 let store = MemoryStorage::new();
1931 store
1932 .insert_output(&make_output("c", 0, "tm_test", 30.0))
1933 .await
1934 .unwrap();
1935 store
1936 .insert_output(&make_output("a", 0, "tm_test", 10.0))
1937 .await
1938 .unwrap();
1939 store
1940 .insert_output(&make_output("b", 0, "tm_test", 20.0))
1941 .await
1942 .unwrap();
1943
1944 let results = store
1945 .find_utxos_for_topic("tm_test", None, None, false)
1946 .await
1947 .unwrap();
1948 assert_eq!(results[0].txid, "a");
1949 assert_eq!(results[1].txid, "b");
1950 assert_eq!(results[2].txid, "c");
1951 }
1952
1953 #[tokio::test]
1954 async fn test_applied_transaction() {
1955 let store = MemoryStorage::new();
1956 let tx = AppliedTransaction {
1957 txid: "abc".to_string(),
1958 topic: "tm_test".to_string(),
1959 };
1960
1961 assert!(!store.does_applied_transaction_exist(&tx).await.unwrap());
1962 store.insert_applied_transaction(&tx).await.unwrap();
1963 assert!(store.does_applied_transaction_exist(&tx).await.unwrap());
1964
1965 let tx2 = AppliedTransaction {
1967 txid: "abc".to_string(),
1968 topic: "tm_other".to_string(),
1969 };
1970 assert!(!store.does_applied_transaction_exist(&tx2).await.unwrap());
1971 }
1972
1973 #[tokio::test]
1974 async fn test_sync_state() {
1975 let store = MemoryStorage::new();
1976
1977 assert_eq!(
1978 store
1979 .get_last_interaction("host1", "tm_test")
1980 .await
1981 .unwrap(),
1982 0
1983 );
1984
1985 store
1986 .update_last_interaction("host1", "tm_test", 100)
1987 .await
1988 .unwrap();
1989 assert_eq!(
1990 store
1991 .get_last_interaction("host1", "tm_test")
1992 .await
1993 .unwrap(),
1994 100
1995 );
1996
1997 store
1999 .update_last_interaction("host1", "tm_test", 200)
2000 .await
2001 .unwrap();
2002 assert_eq!(
2003 store
2004 .get_last_interaction("host1", "tm_test")
2005 .await
2006 .unwrap(),
2007 200
2008 );
2009
2010 assert_eq!(
2012 store
2013 .get_last_interaction("host2", "tm_test")
2014 .await
2015 .unwrap(),
2016 0
2017 );
2018 }
2019
2020 #[tokio::test]
2021 async fn test_find_outputs_by_outpoints_batch() {
2022 let store = MemoryStorage::new();
2023 store
2024 .insert_output(&make_output("a", 0, "tm_test", 1.0))
2025 .await
2026 .unwrap();
2027 store
2028 .insert_output(&make_output("b", 0, "tm_test", 2.0))
2029 .await
2030 .unwrap();
2031 store
2032 .insert_output(&make_output("c", 0, "tm_test", 3.0))
2033 .await
2034 .unwrap();
2035
2036 let outpoints = vec![
2037 Outpoint::new("a", 0),
2038 Outpoint::new("c", 0),
2039 Outpoint::new("missing", 0),
2040 ];
2041 let results = store
2042 .find_outputs_by_outpoints(&outpoints, false)
2043 .await
2044 .unwrap();
2045 assert_eq!(results.len(), 2); }
2047
2048 #[tokio::test]
2049 async fn test_find_output_without_topic() {
2050 let store = MemoryStorage::new();
2051 store
2052 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2053 .await
2054 .unwrap();
2055
2056 let found = store
2058 .find_output("abc", 0, None, None, false)
2059 .await
2060 .unwrap();
2061 assert!(found.is_some());
2062 assert_eq!(found.unwrap().topic, "tm_test");
2063 }
2064}