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 pub early_gasp: bool,
28}
29
30#[async_trait(?Send)]
35pub trait Storage {
36 async fn insert_output(&self, output: &Output) -> Result<(), StorageError>;
46
47 async fn insert_output_with_origin(
51 &self,
52 output: &Output,
53 gasp: bool,
54 ) -> Result<(), StorageError>;
55
56 async fn delete_output(
61 &self,
62 txid: &str,
63 output_index: u32,
64 topic: &str,
65 ) -> Result<(), StorageError>;
66
67 async fn mark_utxo_as_spent(
69 &self,
70 txid: &str,
71 output_index: u32,
72 topic: &str,
73 ) -> Result<(), StorageError>;
74
75 async fn update_consumed_by(
77 &self,
78 txid: &str,
79 output_index: u32,
80 topic: &str,
81 consumed_by: &[Outpoint],
82 ) -> Result<(), StorageError>;
83
84 async fn update_transaction_beef(&self, txid: &str, beef: &[u8]) -> Result<(), StorageError>;
86
87 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
105 let _ = txid;
106 Ok(())
107 }
108
109 async fn mark_transaction_proven_at(
116 &self,
117 txid: &str,
118 height: Option<u64>,
119 ) -> Result<(), StorageError> {
120 let _ = height;
121 self.mark_transaction_proven(txid).await
122 }
123
124 async fn update_output_block_height(
128 &self,
129 txid: &str,
130 output_index: u32,
131 topic: &str,
132 block_height: u32,
133 ) -> Result<(), StorageError> {
134 let _ = (txid, output_index, topic, block_height);
135 Ok(())
136 }
137
138 async fn insert_applied_transaction(&self, tx: &AppliedTransaction)
140 -> Result<(), StorageError>;
141
142 async fn does_applied_transaction_exist(
144 &self,
145 tx: &AppliedTransaction,
146 ) -> Result<bool, StorageError>;
147
148 async fn delete_applied_transaction(&self, tx: &AppliedTransaction)
154 -> Result<(), StorageError>;
155
156 async fn find_output(
165 &self,
166 txid: &str,
167 output_index: u32,
168 topic: Option<&str>,
169 spent: Option<bool>,
170 include_beef: bool,
171 ) -> Result<Option<Output>, StorageError>;
172
173 async fn find_outputs_by_outpoints(
177 &self,
178 outpoints: &[Outpoint],
179 include_beef: bool,
180 ) -> Result<Vec<Output>, StorageError> {
181 let mut results = Vec::with_capacity(outpoints.len());
182 for op in outpoints {
183 if let Some(output) = self
184 .find_output(&op.txid, op.output_index, None, None, include_beef)
185 .await?
186 {
187 results.push(output);
188 }
189 }
190 Ok(results)
191 }
192
193 async fn find_served_outputs(
196 &self,
197 outpoints: &[Outpoint],
198 ) -> Result<Vec<Output>, StorageError> {
199 self.find_outputs_by_outpoints(outpoints, true).await
200 }
201
202 async fn find_outputs_for_transaction(
204 &self,
205 txid: &str,
206 include_beef: bool,
207 ) -> Result<Vec<Output>, StorageError>;
208
209 async fn find_utxos_for_topic(
214 &self,
215 topic: &str,
216 since: Option<f64>,
217 limit: Option<u64>,
218 include_beef: bool,
219 ) -> Result<Vec<Output>, StorageError>;
220
221 async fn find_transactions_for_proof_check(
253 &self,
254 limit: u64,
255 min_age_secs: u64,
256 ) -> Result<Vec<TransactionBeef>, StorageError> {
257 let _ = (limit, min_age_secs);
258 Ok(Vec::new())
259 }
260
261 async fn update_last_interaction(
267 &self,
268 host: &str,
269 topic: &str,
270 since: u64,
271 ) -> Result<(), StorageError>;
272
273 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError>;
275
276 async fn get_peer_cadence(
278 &self,
279 _host: &str,
280 _topic: &str,
281 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
282 Ok(crate::sync_load::PeerCadence::default())
283 }
284
285 async fn set_peer_cadence(
286 &self,
287 _host: &str,
288 _topic: &str,
289 _state: &crate::sync_load::PeerCadence,
290 ) -> Result<(), StorageError> {
291 Ok(())
292 }
293
294 async fn record_peer_sync_outcome(
317 &self,
318 host: &str,
319 topic: &str,
320 success: bool,
321 ) -> Result<(), StorageError>;
322
323 async fn get_peer_sync_health(
328 &self,
329 host: &str,
330 topic: &str,
331 ) -> Result<PeerSyncHealth, StorageError>;
332
333 async fn record_peer_sync_yield(
351 &self,
352 host: &str,
353 topic: &str,
354 yielded: bool,
355 ) -> Result<PeerYieldStreak, StorageError>;
356
357 async fn put_deferred_graph(
390 &self,
391 record: &crate::gasp::DeferredGraph,
392 ) -> Result<crate::gasp::DeferredGraphSave, StorageError>;
393
394 async fn reserve_unanchorable_retry(
398 &self,
399 record: &crate::gasp::DeferredGraph,
400 ) -> Result<bool, StorageError>;
401
402 async fn read_fault_memos(
408 &self,
409 host: &str,
410 topic: &str,
411 outpoints: Option<&[String]>,
412 ) -> Result<crate::fault_memo::FaultMemoBatch, StorageError>;
413
414 async fn find_deferred_graphs(
418 &self,
419 host: &str,
420 topic: &str,
421 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError>;
422
423 async fn get_deferred_graph(
425 &self,
426 host: &str,
427 topic: &str,
428 outpoint: &str,
429 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError>;
430
431 async fn delete_deferred_graph(
433 &self,
434 host: &str,
435 topic: &str,
436 outpoint: &str,
437 ) -> Result<(), StorageError>;
438
439 async fn remember_expired_outpoint(
456 &self,
457 _topic: &str,
458 _outpoint: &str,
459 _expiry: u64,
460 ) -> Result<(), StorageError> {
461 Ok(())
462 }
463
464 async fn find_expired_outpoints(
467 &self,
468 _topic: &str,
469 _outpoints: &[String],
470 ) -> Result<Vec<String>, StorageError> {
471 Ok(Vec::new())
472 }
473}
474
475#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
478pub struct PeerYieldStreak {
479 pub yieldless_syncs: u64,
481 pub secs_since_first: Option<u64>,
484}
485
486#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
489pub struct PeerSyncHealth {
490 pub consecutive_failures: u64,
493 pub secs_since_last_attempt: Option<u64>,
496}
497
498#[async_trait(?Send)]
507impl<T: Storage + ?Sized> Storage for std::rc::Rc<T> {
508 async fn insert_output_with_origin(
509 &self,
510 output: &Output,
511 gasp: bool,
512 ) -> Result<(), StorageError> {
513 (**self).insert_output_with_origin(output, gasp).await
514 }
515 async fn read_fault_memos(
516 &self,
517 host: &str,
518 topic: &str,
519 outpoints: Option<&[String]>,
520 ) -> Result<crate::fault_memo::FaultMemoBatch, StorageError> {
521 (**self).read_fault_memos(host, topic, outpoints).await
522 }
523
524 async fn get_peer_cadence(
525 &self,
526 host: &str,
527 topic: &str,
528 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
529 (**self).get_peer_cadence(host, topic).await
530 }
531 async fn set_peer_cadence(
532 &self,
533 host: &str,
534 topic: &str,
535 state: &crate::sync_load::PeerCadence,
536 ) -> Result<(), StorageError> {
537 (**self).set_peer_cadence(host, topic, state).await
538 }
539 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
540 (**self).insert_output(output).await
541 }
542 async fn delete_output(
543 &self,
544 txid: &str,
545 output_index: u32,
546 topic: &str,
547 ) -> Result<(), StorageError> {
548 (**self).delete_output(txid, output_index, topic).await
549 }
550 async fn mark_utxo_as_spent(
551 &self,
552 txid: &str,
553 output_index: u32,
554 topic: &str,
555 ) -> Result<(), StorageError> {
556 (**self).mark_utxo_as_spent(txid, output_index, topic).await
557 }
558 async fn update_consumed_by(
559 &self,
560 txid: &str,
561 output_index: u32,
562 topic: &str,
563 consumed_by: &[Outpoint],
564 ) -> Result<(), StorageError> {
565 (**self)
566 .update_consumed_by(txid, output_index, topic, consumed_by)
567 .await
568 }
569 async fn update_transaction_beef(&self, txid: &str, beef: &[u8]) -> Result<(), StorageError> {
570 (**self).update_transaction_beef(txid, beef).await
571 }
572 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
573 (**self).mark_transaction_proven(txid).await
574 }
575 async fn mark_transaction_proven_at(
576 &self,
577 txid: &str,
578 height: Option<u64>,
579 ) -> Result<(), StorageError> {
580 (**self).mark_transaction_proven_at(txid, height).await
584 }
585 async fn update_output_block_height(
586 &self,
587 txid: &str,
588 output_index: u32,
589 topic: &str,
590 block_height: u32,
591 ) -> Result<(), StorageError> {
592 (**self)
593 .update_output_block_height(txid, output_index, topic, block_height)
594 .await
595 }
596 async fn insert_applied_transaction(
597 &self,
598 tx: &AppliedTransaction,
599 ) -> Result<(), StorageError> {
600 (**self).insert_applied_transaction(tx).await
601 }
602 async fn does_applied_transaction_exist(
603 &self,
604 tx: &AppliedTransaction,
605 ) -> Result<bool, StorageError> {
606 (**self).does_applied_transaction_exist(tx).await
607 }
608 async fn delete_applied_transaction(
609 &self,
610 tx: &AppliedTransaction,
611 ) -> Result<(), StorageError> {
612 (**self).delete_applied_transaction(tx).await
613 }
614 async fn find_output(
615 &self,
616 txid: &str,
617 output_index: u32,
618 topic: Option<&str>,
619 spent: Option<bool>,
620 include_beef: bool,
621 ) -> Result<Option<Output>, StorageError> {
622 (**self)
623 .find_output(txid, output_index, topic, spent, include_beef)
624 .await
625 }
626 async fn find_served_outputs(
627 &self,
628 outpoints: &[Outpoint],
629 ) -> Result<Vec<Output>, StorageError> {
630 (**self).find_served_outputs(outpoints).await
631 }
632
633 async fn find_outputs_for_transaction(
634 &self,
635 txid: &str,
636 include_beef: bool,
637 ) -> Result<Vec<Output>, StorageError> {
638 (**self)
639 .find_outputs_for_transaction(txid, include_beef)
640 .await
641 }
642 async fn find_utxos_for_topic(
643 &self,
644 topic: &str,
645 since: Option<f64>,
646 limit: Option<u64>,
647 include_beef: bool,
648 ) -> Result<Vec<Output>, StorageError> {
649 (**self)
650 .find_utxos_for_topic(topic, since, limit, include_beef)
651 .await
652 }
653 async fn find_transactions_for_proof_check(
654 &self,
655 limit: u64,
656 min_age_secs: u64,
657 ) -> Result<Vec<TransactionBeef>, StorageError> {
658 (**self)
659 .find_transactions_for_proof_check(limit, min_age_secs)
660 .await
661 }
662 async fn update_last_interaction(
663 &self,
664 host: &str,
665 topic: &str,
666 since: u64,
667 ) -> Result<(), StorageError> {
668 (**self).update_last_interaction(host, topic, since).await
669 }
670 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
671 (**self).get_last_interaction(host, topic).await
672 }
673 async fn record_peer_sync_outcome(
674 &self,
675 host: &str,
676 topic: &str,
677 success: bool,
678 ) -> Result<(), StorageError> {
679 (**self)
680 .record_peer_sync_outcome(host, topic, success)
681 .await
682 }
683 async fn get_peer_sync_health(
684 &self,
685 host: &str,
686 topic: &str,
687 ) -> Result<PeerSyncHealth, StorageError> {
688 (**self).get_peer_sync_health(host, topic).await
689 }
690 async fn record_peer_sync_yield(
691 &self,
692 host: &str,
693 topic: &str,
694 yielded: bool,
695 ) -> Result<PeerYieldStreak, StorageError> {
696 (**self).record_peer_sync_yield(host, topic, yielded).await
697 }
698 async fn put_deferred_graph(
699 &self,
700 record: &crate::gasp::DeferredGraph,
701 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
702 (**self).put_deferred_graph(record).await
703 }
704 async fn reserve_unanchorable_retry(
705 &self,
706 record: &crate::gasp::DeferredGraph,
707 ) -> Result<bool, StorageError> {
708 (**self).reserve_unanchorable_retry(record).await
709 }
710 async fn find_deferred_graphs(
711 &self,
712 host: &str,
713 topic: &str,
714 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
715 (**self).find_deferred_graphs(host, topic).await
716 }
717 async fn get_deferred_graph(
718 &self,
719 host: &str,
720 topic: &str,
721 outpoint: &str,
722 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
723 (**self).get_deferred_graph(host, topic, outpoint).await
724 }
725 async fn delete_deferred_graph(
726 &self,
727 host: &str,
728 topic: &str,
729 outpoint: &str,
730 ) -> Result<(), StorageError> {
731 (**self).delete_deferred_graph(host, topic, outpoint).await
732 }
733 async fn remember_expired_outpoint(
734 &self,
735 topic: &str,
736 outpoint: &str,
737 expiry: u64,
738 ) -> Result<(), StorageError> {
739 (**self)
740 .remember_expired_outpoint(topic, outpoint, expiry)
741 .await
742 }
743 async fn find_expired_outpoints(
744 &self,
745 topic: &str,
746 outpoints: &[String],
747 ) -> Result<Vec<String>, StorageError> {
748 (**self).find_expired_outpoints(topic, outpoints).await
749 }
750}
751
752#[derive(Debug, thiserror::Error)]
758pub enum StorageError {
759 #[error("not found: {0}")]
760 NotFound(String),
761
762 #[error("duplicate: {0}")]
763 Duplicate(String),
764
765 #[error("database error: {0}")]
766 Database(String),
767
768 #[error("serialization error: {0}")]
769 Serialization(String),
770
771 #[error("{0}")]
772 Other(String),
773}
774
775#[cfg(any(test, feature = "memory-storage"))]
780pub mod memory {
781 use super::*;
782 use std::collections::HashMap;
783 use std::sync::Mutex;
784
785 #[derive(Debug, Default)]
789 pub struct MemoryStorage {
790 fault_memos: Mutex<HashMap<(String, String, String), crate::fault_memo::FaultMemo>>,
791 fault_memo_ceiling: Mutex<Option<usize>>,
792 gasp_transactions: Mutex<std::collections::HashSet<String>>,
793 cadence: Mutex<HashMap<(String, String), (crate::sync_load::PeerCadence, u64)>>,
794 outputs: Mutex<HashMap<(String, u32, String), Output>>,
796 transactions: Mutex<HashMap<String, Vec<u8>>>,
798 proven: Mutex<std::collections::HashSet<String>>,
806 applied: Mutex<HashMap<(String, String), bool>>,
808 sync_state: Mutex<HashMap<(String, String), u64>>,
810 clock_secs: Mutex<u64>,
815 created_at: Mutex<HashMap<String, u64>>,
819 peer_health: Mutex<HashMap<(String, String), (u64, u64)>>,
824 proven_at: Mutex<HashMap<String, Option<u64>>>,
828 deferred_graphs: Mutex<HashMap<(String, String, String), crate::gasp::DeferredGraph>>,
831 deferred_graph_writes: Mutex<u64>,
834 deferred_graph_reads: Mutex<u64>,
837 deferred_graph_keys_fault: Mutex<bool>,
839 deferred_graph_ceiling: Mutex<Option<usize>>,
842 deferred_graph_byte_ceiling: Mutex<Option<usize>>,
846 deferred_graph_put_fault: Mutex<bool>,
848 expired_outpoints: Mutex<HashMap<(String, String), (u64, u64)>>,
852 deferred_graph_put_attempts: Mutex<u64>,
855 peer_yieldless: Mutex<HashMap<(String, String), YieldStreakRow>>,
861 }
862
863 type YieldStreakRow = (u64, u64, u64);
866
867 impl MemoryStorage {
868 pub fn fault_memos(&self) -> Vec<crate::fault_memo::FaultMemo> {
869 self.fault_memos.lock().unwrap().values().cloned().collect()
870 }
871
872 pub fn set_fault_memo_ceiling(&self, ceiling: Option<usize>) {
873 *self.fault_memo_ceiling.lock().unwrap() = ceiling;
874 }
875
876 pub fn seed_legacy_fault(&self, record: crate::gasp::DeferredGraph) {
877 self.deferred_graphs.lock().unwrap().insert(
878 (
879 record.peer.clone(),
880 record.topic.clone(),
881 record.outpoint.clone(),
882 ),
883 record,
884 );
885 }
886
887 fn cap_fault_memos(
888 held: &mut HashMap<(String, String, String), crate::fault_memo::FaultMemo>,
889 topic: &str,
890 ) {
891 if held.len() <= crate::fault_memo::FAULT_MEMO_MAX_ROWS {
892 return;
893 }
894 let mut keys: Vec<_> = held
895 .iter()
896 .filter(|(_, r)| r.topic == topic)
897 .map(|(k, r)| (r.first_at, k.clone()))
898 .collect();
899 let excess = keys
900 .len()
901 .saturating_sub(crate::fault_memo::FAULT_MEMO_MAX_ROWS);
902 if excess > 0 {
903 keys.sort();
904 for (_, key) in keys.into_iter().take(excess) {
905 held.remove(&key);
906 }
907 }
908 }
909
910 pub fn new() -> Self {
911 Self::default()
912 }
913
914 pub fn proven_at_height(&self, txid: &str) -> Option<Option<u64>> {
918 self.proven_at.lock().unwrap().get(txid).copied()
919 }
920
921 pub fn expired_outpoints(&self, topic: &str) -> Vec<String> {
926 let now = *self.clock_secs.lock().unwrap();
927 let mut held: Vec<String> = self
928 .expired_outpoints
929 .lock()
930 .unwrap()
931 .iter()
932 .filter(|((t, _), (keep, _))| t == topic && *keep > now)
933 .map(|((_, o), _)| o.clone())
934 .collect();
935 held.sort();
936 held
937 }
938
939 pub fn advance_clock(&self, secs: u64) {
940 *self.clock_secs.lock().unwrap() += secs;
941 }
942
943 fn stamp_created(&self, txid: &str) {
946 let now = *self.clock_secs.lock().unwrap();
947 self.created_at
948 .lock()
949 .unwrap()
950 .entry(txid.to_string())
951 .or_insert(now);
952 }
953
954 fn age_gate_open(&self, txid: &str, min_age_secs: u64) -> bool {
957 if min_age_secs == 0 {
958 return true;
959 }
960 let now = *self.clock_secs.lock().unwrap();
961 match self.created_at.lock().unwrap().get(txid) {
962 None => true, Some(created) => now.saturating_sub(*created) >= min_age_secs,
964 }
965 }
966
967 pub fn deferred_graphs(&self) -> Vec<crate::gasp::DeferredGraph> {
969 let mut all: Vec<_> = self
970 .deferred_graphs
971 .lock()
972 .unwrap()
973 .values()
974 .cloned()
975 .collect();
976 all.sort_by(|a, b| (a.score, &a.outpoint).cmp(&(b.score, &b.outpoint)));
977 all
978 }
979
980 pub fn deferred_graph_writes(&self) -> u64 {
982 *self.deferred_graph_writes.lock().unwrap()
983 }
984
985 pub fn deferred_graph_reads(&self) -> u64 {
987 *self.deferred_graph_reads.lock().unwrap()
988 }
989
990 pub fn set_deferred_graph_keys_fault(&self, fault: bool) {
993 *self.deferred_graph_keys_fault.lock().unwrap() = fault;
994 }
995
996 pub fn set_deferred_graph_ceiling(&self, ceiling: Option<usize>) {
999 *self.deferred_graph_ceiling.lock().unwrap() = ceiling;
1000 }
1001
1002 pub fn set_deferred_graph_byte_ceiling(&self, ceiling: Option<usize>) {
1006 *self.deferred_graph_byte_ceiling.lock().unwrap() = ceiling;
1007 }
1008
1009 pub fn set_deferred_graph_put_fault(&self, fault: bool) {
1012 *self.deferred_graph_put_fault.lock().unwrap() = fault;
1013 }
1014
1015 pub fn deferred_graph_put_attempts(&self) -> u64 {
1018 *self.deferred_graph_put_attempts.lock().unwrap()
1019 }
1020
1021 pub fn peer_yieldless_syncs(&self, host: &str, topic: &str) -> u64 {
1026 self.peer_yieldless
1027 .lock()
1028 .unwrap()
1029 .get(&(crate::gasp::peer_origin(host), topic.to_string()))
1030 .map_or(0, |(count, _, _)| *count)
1031 }
1032
1033 pub fn output_count(&self) -> usize {
1035 self.outputs.lock().unwrap().len()
1036 }
1037
1038 pub fn transaction_count(&self) -> usize {
1040 self.transactions.lock().unwrap().len()
1041 }
1042 }
1043
1044 #[async_trait(?Send)]
1045 impl Storage for MemoryStorage {
1046 async fn insert_output_with_origin(
1047 &self,
1048 output: &Output,
1049 gasp: bool,
1050 ) -> Result<(), StorageError> {
1051 self.insert_output(output).await?;
1052 if gasp && output.beef.is_some() {
1053 self.gasp_transactions
1054 .lock()
1055 .unwrap()
1056 .insert(output.txid.clone());
1057 }
1058 Ok(())
1059 }
1060
1061 async fn read_fault_memos(
1062 &self,
1063 host: &str,
1064 topic: &str,
1065 outpoints: Option<&[String]>,
1066 ) -> Result<crate::fault_memo::FaultMemoBatch, StorageError> {
1067 use crate::fault_memo::{FaultMemo, FaultMemoBatch, FAULT_MEMO_DUE_LIMIT};
1068 let now = (crate::engine::current_timestamp_ms() / 1000.0) as u64;
1069 let mut batch = FaultMemoBatch::default();
1070 let mut held = self.fault_memos.lock().unwrap();
1071 if outpoints.is_none() {
1072 let mut legacy = self.deferred_graphs.lock().unwrap();
1073 legacy.retain(|key, record| {
1074 if record.peer != host || record.topic != topic {
1075 return true;
1076 }
1077 let Some(memo) = FaultMemo::from_record(record, now) else {
1078 return true;
1079 };
1080 held.entry(key.clone()).or_insert(memo);
1081 batch.folded += 1;
1082 false
1083 });
1084 Self::cap_fault_memos(&mut held, topic);
1085 let mut expired = self.expired_outpoints.lock().unwrap();
1086 let logical_now = *self.clock_secs.lock().unwrap();
1087 for r in held.values().filter(|r| {
1088 r.peer == host
1089 && r.topic == topic
1090 && (r.dead || r.unanchorable)
1091 && r.expiry.is_some_and(|t| t <= now)
1092 }) {
1093 expired.insert(
1094 (topic.into(), r.outpoint.clone()),
1095 (
1096 logical_now + crate::gasp::EXPIRED_OUTPOINT_MEMO_SECS,
1097 logical_now,
1098 ),
1099 );
1100 }
1101 held.retain(|_, r| {
1102 let remove = r.peer == host
1103 && r.topic == topic
1104 && (r.dead || r.unanchorable)
1105 && (r.expiry.is_some_and(|t| t <= now)
1106 || expired
1107 .get(&(topic.into(), r.outpoint.clone()))
1108 .is_some_and(|(until, _)| *until > logical_now));
1109 if remove {
1110 batch.swept += 1;
1111 }
1112 !remove
1113 });
1114 }
1115 batch.records = held
1116 .values()
1117 .filter(|r| r.peer == host && r.topic == topic)
1118 .filter(|r| {
1119 outpoints.map_or(!r.dead && r.next_at <= now, |keys| {
1120 keys.contains(&r.outpoint)
1121 })
1122 })
1123 .cloned()
1124 .collect();
1125 batch.records.sort_by(|a, b| {
1126 (a.next_at, a.score, &a.outpoint).cmp(&(b.next_at, b.score, &b.outpoint))
1127 });
1128 if outpoints.is_none() {
1129 batch.records.truncate(FAULT_MEMO_DUE_LIMIT);
1130 }
1131 Ok(batch)
1132 }
1133
1134 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
1135 let key = (
1136 output.txid.clone(),
1137 output.output_index,
1138 output.topic.clone(),
1139 );
1140 let mut outputs = self.outputs.lock().unwrap();
1141
1142 if outputs.contains_key(&key) {
1144 if let Some(ref beef) = output.beef {
1146 self.transactions
1147 .lock()
1148 .unwrap()
1149 .entry(output.txid.clone())
1150 .or_insert_with(|| beef.clone());
1151 }
1152 return Ok(());
1153 }
1154
1155 let mut stored = output.clone();
1157 stored.beef = None;
1158 outputs.insert(key, stored);
1159
1160 if let Some(ref beef) = output.beef {
1162 self.transactions
1163 .lock()
1164 .unwrap()
1165 .entry(output.txid.clone())
1166 .or_insert_with(|| beef.clone());
1167 self.stamp_created(&output.txid);
1168 }
1169
1170 Ok(())
1171 }
1172
1173 async fn delete_output(
1174 &self,
1175 txid: &str,
1176 output_index: u32,
1177 topic: &str,
1178 ) -> Result<(), StorageError> {
1179 let key = (txid.to_string(), output_index, topic.to_string());
1180 let mut outputs = self.outputs.lock().unwrap();
1181 outputs.remove(&key);
1182
1183 let has_remaining = outputs.keys().any(|(t, _, _)| t == txid);
1185 if !has_remaining {
1186 self.transactions.lock().unwrap().remove(txid);
1187 self.proven.lock().unwrap().remove(txid);
1188 self.gasp_transactions.lock().unwrap().remove(txid);
1189 }
1190
1191 Ok(())
1192 }
1193
1194 async fn mark_utxo_as_spent(
1195 &self,
1196 txid: &str,
1197 output_index: u32,
1198 topic: &str,
1199 ) -> Result<(), StorageError> {
1200 let key = (txid.to_string(), output_index, topic.to_string());
1201 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1202 output.spent = true;
1203 }
1204 Ok(())
1205 }
1206
1207 async fn update_consumed_by(
1208 &self,
1209 txid: &str,
1210 output_index: u32,
1211 topic: &str,
1212 consumed_by: &[Outpoint],
1213 ) -> Result<(), StorageError> {
1214 let key = (txid.to_string(), output_index, topic.to_string());
1215 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1216 output.consumed_by = consumed_by.to_vec();
1217 }
1218 Ok(())
1219 }
1220
1221 async fn update_transaction_beef(
1222 &self,
1223 txid: &str,
1224 beef: &[u8],
1225 ) -> Result<(), StorageError> {
1226 let has_proof = beef_limits::has_proof(beef, txid);
1232 if has_proof {
1233 self.proven.lock().unwrap().insert(txid.to_string());
1234 }
1235 self.transactions
1236 .lock()
1237 .unwrap()
1238 .insert(txid.to_string(), beef.to_vec());
1239 self.stamp_created(txid);
1242 Ok(())
1243 }
1244
1245 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
1246 self.proven.lock().unwrap().insert(txid.to_string());
1249 Ok(())
1250 }
1251
1252 async fn mark_transaction_proven_at(
1253 &self,
1254 txid: &str,
1255 height: Option<u64>,
1256 ) -> Result<(), StorageError> {
1257 self.proven.lock().unwrap().insert(txid.to_string());
1261 self.proven_at
1262 .lock()
1263 .unwrap()
1264 .insert(txid.to_string(), height);
1265 Ok(())
1266 }
1267
1268 async fn update_output_block_height(
1269 &self,
1270 txid: &str,
1271 output_index: u32,
1272 topic: &str,
1273 block_height: u32,
1274 ) -> Result<(), StorageError> {
1275 let key = (txid.to_string(), output_index, topic.to_string());
1276 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1277 output.block_height = Some(block_height);
1278 }
1279 Ok(())
1280 }
1281
1282 async fn insert_applied_transaction(
1283 &self,
1284 tx: &AppliedTransaction,
1285 ) -> Result<(), StorageError> {
1286 self.applied
1287 .lock()
1288 .unwrap()
1289 .insert((tx.txid.clone(), tx.topic.clone()), true);
1290 Ok(())
1291 }
1292
1293 async fn does_applied_transaction_exist(
1294 &self,
1295 tx: &AppliedTransaction,
1296 ) -> Result<bool, StorageError> {
1297 Ok(self
1298 .applied
1299 .lock()
1300 .unwrap()
1301 .contains_key(&(tx.txid.clone(), tx.topic.clone())))
1302 }
1303
1304 async fn delete_applied_transaction(
1305 &self,
1306 tx: &AppliedTransaction,
1307 ) -> Result<(), StorageError> {
1308 self.applied
1309 .lock()
1310 .unwrap()
1311 .remove(&(tx.txid.clone(), tx.topic.clone()));
1312 Ok(())
1313 }
1314
1315 async fn find_output(
1316 &self,
1317 txid: &str,
1318 output_index: u32,
1319 topic: Option<&str>,
1320 spent: Option<bool>,
1321 include_beef: bool,
1322 ) -> Result<Option<Output>, StorageError> {
1323 let outputs = self.outputs.lock().unwrap();
1324
1325 if let Some(topic) = topic {
1327 let key = (txid.to_string(), output_index, topic.to_string());
1328 if let Some(output) = outputs.get(&key) {
1329 if let Some(s) = spent {
1330 if output.spent != s {
1331 return Ok(None);
1332 }
1333 }
1334 let mut result = output.clone();
1335 if include_beef {
1336 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1337 }
1338 return Ok(Some(result));
1339 }
1340 return Ok(None);
1341 }
1342
1343 for ((t, oi, _), output) in outputs.iter() {
1345 if t == txid && *oi == output_index {
1346 if let Some(s) = spent {
1347 if output.spent != s {
1348 continue;
1349 }
1350 }
1351 let mut result = output.clone();
1352 if include_beef {
1353 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1354 }
1355 return Ok(Some(result));
1356 }
1357 }
1358 Ok(None)
1359 }
1360
1361 async fn find_served_outputs(
1362 &self,
1363 outpoints: &[Outpoint],
1364 ) -> Result<Vec<Output>, StorageError> {
1365 let outputs = self.find_outputs_by_outpoints(outpoints, true).await?;
1366 let now = i64::try_from(*self.clock_secs.lock().unwrap())
1367 .unwrap_or(i64::MAX)
1368 .saturating_mul(1000);
1369 let proven = self.proven.lock().unwrap();
1370 let stamps = self.created_at.lock().unwrap();
1371 Ok(outputs
1372 .into_iter()
1373 .filter(|output| {
1374 let seen = stamps
1375 .get(&output.txid)
1376 .and_then(|s| i64::try_from(*s).ok())
1377 .map(|s| s.saturating_mul(1000));
1378 crate::proof_window::verdict(proven.contains(&output.txid), seen, now)
1379 == crate::proof_window::Verdict::Served
1380 })
1381 .collect())
1382 }
1383
1384 async fn find_outputs_for_transaction(
1385 &self,
1386 txid: &str,
1387 include_beef: bool,
1388 ) -> Result<Vec<Output>, StorageError> {
1389 let outputs = self.outputs.lock().unwrap();
1390 let beef = if include_beef {
1391 self.transactions.lock().unwrap().get(txid).cloned()
1392 } else {
1393 None
1394 };
1395
1396 let results: Vec<Output> = outputs
1397 .iter()
1398 .filter(|((t, _, _), _)| t == txid)
1399 .map(|(_, output)| {
1400 let mut o = output.clone();
1401 if include_beef {
1402 o.beef.clone_from(&beef);
1403 }
1404 o
1405 })
1406 .collect();
1407
1408 Ok(results)
1409 }
1410
1411 async fn find_utxos_for_topic(
1412 &self,
1413 topic: &str,
1414 since: Option<f64>,
1415 limit: Option<u64>,
1416 include_beef: bool,
1417 ) -> Result<Vec<Output>, StorageError> {
1418 let outputs = self.outputs.lock().unwrap();
1419 let transactions = self.transactions.lock().unwrap();
1420
1421 let mut results: Vec<Output> = outputs
1422 .iter()
1423 .filter(|((_, _, t), o)| {
1424 t == topic && !o.spent && since.is_none_or(|s| o.score.unwrap_or(0.0) >= s)
1425 })
1426 .map(|(_, output)| {
1427 let mut o = output.clone();
1428 if include_beef {
1429 o.beef = transactions.get(&o.txid).cloned();
1430 }
1431 o
1432 })
1433 .collect();
1434
1435 results.sort_by(|a, b| {
1437 a.score
1438 .unwrap_or(0.0)
1439 .partial_cmp(&b.score.unwrap_or(0.0))
1440 .unwrap_or(std::cmp::Ordering::Equal)
1441 });
1442
1443 if let Some(limit) = limit {
1444 results.truncate(limit as usize);
1445 }
1446
1447 Ok(results)
1448 }
1449
1450 async fn find_transactions_for_proof_check(
1451 &self,
1452 limit: u64,
1453 min_age_secs: u64,
1454 ) -> Result<Vec<TransactionBeef>, StorageError> {
1455 let candidates: Vec<TransactionBeef> = {
1468 let transactions = self.transactions.lock().unwrap();
1469 let proven = self.proven.lock().unwrap();
1470 transactions
1471 .iter()
1472 .filter(|(txid, _)| !proven.contains(*txid))
1473 .map(|(txid, beef)| TransactionBeef {
1474 txid: txid.clone(),
1475 beef: beef.clone(),
1476 early_gasp: self.gasp_transactions.lock().unwrap().contains(txid)
1477 && !self.age_gate_open(txid, min_age_secs),
1478 })
1479 .collect()
1480 };
1481 Ok(candidates
1482 .into_iter()
1483 .filter(|c| c.early_gasp || self.age_gate_open(&c.txid, min_age_secs))
1484 .take(limit as usize)
1485 .collect())
1486 }
1487
1488 async fn update_last_interaction(
1489 &self,
1490 host: &str,
1491 topic: &str,
1492 since: u64,
1493 ) -> Result<(), StorageError> {
1494 self.sync_state
1495 .lock()
1496 .unwrap()
1497 .insert((host.to_string(), topic.to_string()), since);
1498 Ok(())
1499 }
1500
1501 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
1502 Ok(*self
1503 .sync_state
1504 .lock()
1505 .unwrap()
1506 .get(&(host.to_string(), topic.to_string()))
1507 .unwrap_or(&0))
1508 }
1509
1510 async fn get_peer_cadence(
1511 &self,
1512 host: &str,
1513 topic: &str,
1514 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
1515 let now = *self.clock_secs.lock().unwrap();
1516 Ok(self
1517 .cadence
1518 .lock()
1519 .unwrap()
1520 .get(&(host.into(), topic.into()))
1521 .map_or_else(Default::default, |(s, at)| {
1522 let mut state = s.clone();
1523 state.retry_after_secs = at.saturating_sub(now);
1524 state
1525 }))
1526 }
1527 async fn set_peer_cadence(
1528 &self,
1529 host: &str,
1530 topic: &str,
1531 state: &crate::sync_load::PeerCadence,
1532 ) -> Result<(), StorageError> {
1533 let now = *self.clock_secs.lock().unwrap();
1534 self.cadence.lock().unwrap().insert(
1535 (host.into(), topic.into()),
1536 (state.clone(), now.saturating_add(state.retry_after_secs)),
1537 );
1538 Ok(())
1539 }
1540 async fn record_peer_sync_outcome(
1541 &self,
1542 host: &str,
1543 topic: &str,
1544 success: bool,
1545 ) -> Result<(), StorageError> {
1546 let now = *self.clock_secs.lock().unwrap();
1547 let mut health = self.peer_health.lock().unwrap();
1548 let entry = health
1549 .entry((host.to_string(), topic.to_string()))
1550 .or_insert((0, now));
1551 entry.0 = if success { 0 } else { entry.0 + 1 };
1552 entry.1 = now;
1553 Ok(())
1554 }
1555
1556 async fn get_peer_sync_health(
1557 &self,
1558 host: &str,
1559 topic: &str,
1560 ) -> Result<PeerSyncHealth, StorageError> {
1561 let now = *self.clock_secs.lock().unwrap();
1562 Ok(self
1563 .peer_health
1564 .lock()
1565 .unwrap()
1566 .get(&(host.to_string(), topic.to_string()))
1567 .map(|(fails, attempt_at)| PeerSyncHealth {
1568 consecutive_failures: *fails,
1569 secs_since_last_attempt: Some(now.saturating_sub(*attempt_at)),
1570 })
1571 .unwrap_or_default())
1572 }
1573
1574 async fn record_peer_sync_yield(
1575 &self,
1576 host: &str,
1577 topic: &str,
1578 yielded: bool,
1579 ) -> Result<PeerYieldStreak, StorageError> {
1580 let now = *self.clock_secs.lock().unwrap();
1581 let key = (host.to_string(), topic.to_string());
1582 let not_failed = self
1585 .peer_health
1586 .lock()
1587 .unwrap()
1588 .get(&key)
1589 .is_none_or(|(fails, _)| *fails == 0);
1590 let mut held = self.peer_yieldless.lock().unwrap();
1591 if yielded {
1592 held.remove(&key);
1593 return Ok(PeerYieldStreak::default());
1594 }
1595 let streak = held.entry(key).or_insert((0, now, now));
1596 if streak.0 == 0
1597 || (not_failed
1598 && now.saturating_sub(streak.2) > crate::gasp::PEER_YIELDLESS_DECAY_SECS)
1599 {
1600 *streak = (0, now, now);
1601 }
1602 streak.0 += 1;
1603 streak.2 = now;
1604 Ok(PeerYieldStreak {
1605 yieldless_syncs: streak.0,
1606 secs_since_first: Some(now.saturating_sub(streak.1)),
1607 })
1608 }
1609
1610 async fn put_deferred_graph(
1611 &self,
1612 record: &crate::gasp::DeferredGraph,
1613 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
1614 let key = (
1615 record.peer.clone(),
1616 record.topic.clone(),
1617 record.outpoint.clone(),
1618 );
1619 *self.deferred_graph_put_attempts.lock().unwrap() += 1;
1620 if *self.deferred_graph_put_fault.lock().unwrap() {
1621 return Err(StorageError::Database("deferred graph save faulted".into()));
1622 }
1623 if let Some(mut memo) = crate::fault_memo::FaultMemo::from_record(
1624 record,
1625 (crate::engine::current_timestamp_ms() / 1000.0) as u64,
1626 ) {
1627 let mut held = self.fault_memos.lock().unwrap();
1628 if self
1629 .fault_memo_ceiling
1630 .lock()
1631 .unwrap()
1632 .is_some_and(|n| !held.contains_key(&key) && held.len() >= n)
1633 {
1634 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1635 }
1636 if let Some(prior) = held.get(&key).filter(|r| r.score == memo.score) {
1637 memo.first_at = prior.first_at;
1638 }
1639 held.insert(key.clone(), memo);
1640 Self::cap_fault_memos(&mut held, &record.topic);
1641 self.deferred_graphs.lock().unwrap().remove(&key);
1642 *self.deferred_graph_writes.lock().unwrap() += 1;
1643 return Ok(crate::gasp::DeferredGraphSave::Saved);
1644 }
1645 if let Some(ceiling) = *self.deferred_graph_ceiling.lock().unwrap() {
1646 let held = self.deferred_graphs.lock().unwrap();
1647 if !held.contains_key(&key) && held.len() >= ceiling {
1648 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1649 }
1650 }
1651 if let Some(ceiling) = *self.deferred_graph_byte_ceiling.lock().unwrap() {
1652 let held = self.deferred_graphs.lock().unwrap();
1653 let others: usize = held
1654 .iter()
1655 .filter(|(k, _)| **k != key)
1656 .map(|(_, r)| r.byte_size())
1657 .sum();
1658 if others + record.byte_size() > ceiling {
1659 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1660 }
1661 }
1662 self.fault_memos.lock().unwrap().remove(&key);
1663 *self.deferred_graph_writes.lock().unwrap() += 1;
1664 self.deferred_graphs.lock().unwrap().insert(
1665 (
1666 record.peer.clone(),
1667 record.topic.clone(),
1668 record.outpoint.clone(),
1669 ),
1670 record.clone(),
1671 );
1672 Ok(crate::gasp::DeferredGraphSave::Saved)
1673 }
1674
1675 async fn reserve_unanchorable_retry(
1676 &self,
1677 record: &crate::gasp::DeferredGraph,
1678 ) -> Result<bool, StorageError> {
1679 *self.deferred_graph_put_attempts.lock().unwrap() += 1;
1680 if *self.deferred_graph_put_fault.lock().unwrap() {
1681 return Err(StorageError::Database("retry reservation faulted".into()));
1682 }
1683 let key = (
1684 record.peer.clone(),
1685 record.topic.clone(),
1686 record.outpoint.clone(),
1687 );
1688 let mut held = self.fault_memos.lock().unwrap();
1689 let Some(prior) = held.get_mut(&key) else {
1690 return Ok(false);
1691 };
1692 let now = (crate::engine::current_timestamp_ms() / 1000.0) as u64;
1693 if prior.dead
1694 || !prior.unanchorable
1695 || record.fault_class() != Some(prior.class.as_str())
1696 || prior.score != record.score
1697 || prior.next_at > now
1698 || prior.retries.checked_add(1) != Some(record.unanchorable_retries)
1699 || record.unanchorable_retries > crate::gasp::UNANCHORABLE_RETRIES
1700 {
1701 return Ok(false);
1702 }
1703 prior.retries = record.unanchorable_retries;
1704 prior.next_at = record.retry_at;
1705 *self.deferred_graph_writes.lock().unwrap() += 1;
1706 Ok(true)
1707 }
1708
1709 async fn find_deferred_graphs(
1710 &self,
1711 host: &str,
1712 topic: &str,
1713 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
1714 if *self.deferred_graph_keys_fault.lock().unwrap() {
1715 return Err(StorageError::Database("deferred graph keys: fault".into()));
1716 }
1717 Ok(self
1718 .deferred_graphs()
1719 .into_iter()
1720 .filter(|r| r.peer == host && r.topic == topic)
1721 .map(|r| crate::gasp::DeferredGraphKey {
1722 outpoint: r.outpoint,
1723 score: r.score,
1724 })
1725 .collect())
1726 }
1727
1728 async fn get_deferred_graph(
1729 &self,
1730 host: &str,
1731 topic: &str,
1732 outpoint: &str,
1733 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
1734 *self.deferred_graph_reads.lock().unwrap() += 1;
1735 if let Some(memo) = self
1736 .fault_memos
1737 .lock()
1738 .unwrap()
1739 .get(&(host.into(), topic.into(), outpoint.into()))
1740 .cloned()
1741 {
1742 return Ok(Some(memo.into_record()));
1743 }
1744 Ok(self
1745 .deferred_graphs
1746 .lock()
1747 .unwrap()
1748 .get(&(host.to_string(), topic.to_string(), outpoint.to_string()))
1749 .cloned())
1750 }
1751
1752 async fn delete_deferred_graph(
1753 &self,
1754 host: &str,
1755 topic: &str,
1756 outpoint: &str,
1757 ) -> Result<(), StorageError> {
1758 self.fault_memos
1759 .lock()
1760 .unwrap()
1761 .remove(&(host.into(), topic.into(), outpoint.into()));
1762 self.deferred_graphs.lock().unwrap().remove(&(
1763 host.to_string(),
1764 topic.to_string(),
1765 outpoint.to_string(),
1766 ));
1767 Ok(())
1768 }
1769
1770 async fn remember_expired_outpoint(
1771 &self,
1772 topic: &str,
1773 outpoint: &str,
1774 expiry: u64,
1775 ) -> Result<(), StorageError> {
1776 let now = *self.clock_secs.lock().unwrap();
1777 let mut held = self.expired_outpoints.lock().unwrap();
1778 held.retain(|_, (keep, _)| *keep > now);
1779 held.insert(
1780 (topic.to_string(), outpoint.to_string()),
1781 (
1782 expiry.max(now) + crate::gasp::EXPIRED_OUTPOINT_MEMO_SECS,
1783 now,
1784 ),
1785 );
1786 let mut of_topic: Vec<(u64, String)> = held
1788 .iter()
1789 .filter(|((t, _), _)| t == topic)
1790 .map(|((_, o), (_, at))| (*at, o.clone()))
1791 .collect();
1792 let cap = crate::gasp::EXPIRED_OUTPOINT_MEMO_MAX_ROWS;
1793 if of_topic.len() > cap {
1794 of_topic.sort();
1795 for (_, o) in &of_topic[..of_topic.len() - cap] {
1796 held.remove(&(topic.to_string(), o.clone()));
1797 }
1798 }
1799 Ok(())
1800 }
1801
1802 async fn find_expired_outpoints(
1803 &self,
1804 topic: &str,
1805 outpoints: &[String],
1806 ) -> Result<Vec<String>, StorageError> {
1807 let now = *self.clock_secs.lock().unwrap();
1808 let held = self.expired_outpoints.lock().unwrap();
1809 Ok(outpoints
1810 .iter()
1811 .filter(|o| {
1812 held.get(&(topic.to_string(), (*o).clone()))
1813 .is_some_and(|(keep, _)| *keep > now)
1814 })
1815 .cloned()
1816 .collect())
1817 }
1818 }
1819}
1820
1821#[cfg(test)]
1826mod tests {
1827 use super::memory::MemoryStorage;
1828 use super::*;
1829 use crate::types::AppliedTransaction;
1830
1831 #[tokio::test]
1836 async fn the_rc_blanket_forwards_the_anchored_proven_latch() {
1837 let store = std::rc::Rc::new(MemoryStorage::new());
1838 let via_dyn: std::rc::Rc<dyn Storage> = store.clone();
1839 via_dyn
1840 .mark_transaction_proven_at("tx", Some(965_772))
1841 .await
1842 .unwrap();
1843 assert_eq!(
1844 store.proven_at_height("tx"),
1845 Some(Some(965_772)),
1846 "the Rc blanket forwarded the height to the inner store"
1847 );
1848 via_dyn
1850 .mark_transaction_proven_at("tx2", None)
1851 .await
1852 .unwrap();
1853 assert_eq!(store.proven_at_height("tx2"), Some(None));
1854 assert_eq!(
1855 store.proven_at_height("never"),
1856 None,
1857 "a txid never latched is absent"
1858 );
1859 }
1860
1861 fn make_output(txid: &str, index: u32, topic: &str, score: f64) -> Output {
1862 Output {
1863 txid: txid.to_string(),
1864 output_index: index,
1865 output_script: vec![0x76, 0xa9],
1866 satoshis: 1000,
1867 topic: topic.to_string(),
1868 spent: false,
1869 outputs_consumed: vec![],
1870 consumed_by: vec![],
1871 beef: Some(vec![0xBE, 0xEF]),
1872 block_height: None,
1873 score: Some(score),
1874 }
1875 }
1876
1877 #[tokio::test]
1878 async fn test_insert_and_find_output() {
1879 let store = MemoryStorage::new();
1880 let output = make_output("abc", 0, "tm_test", 1.0);
1881 store.insert_output(&output).await.unwrap();
1882
1883 let found = store
1885 .find_output("abc", 0, Some("tm_test"), None, false)
1886 .await
1887 .unwrap()
1888 .unwrap();
1889 assert_eq!(found.txid, "abc");
1890 assert_eq!(found.output_index, 0);
1891 assert!(found.beef.is_none());
1892
1893 let found = store
1895 .find_output("abc", 0, Some("tm_test"), None, true)
1896 .await
1897 .unwrap()
1898 .unwrap();
1899 assert_eq!(found.beef.unwrap(), vec![0xBE, 0xEF]);
1900 }
1901
1902 #[tokio::test]
1903 async fn test_insert_duplicate_is_noop() {
1904 let store = MemoryStorage::new();
1905 let output = make_output("abc", 0, "tm_test", 1.0);
1906 store.insert_output(&output).await.unwrap();
1907 store.insert_output(&output).await.unwrap();
1908 assert_eq!(store.output_count(), 1);
1909 }
1910
1911 #[tokio::test]
1912 async fn test_delete_output_cleans_up_beef() {
1913 let store = MemoryStorage::new();
1914 let output = make_output("abc", 0, "tm_test", 1.0);
1915 store.insert_output(&output).await.unwrap();
1916 assert_eq!(store.transaction_count(), 1);
1917
1918 store.delete_output("abc", 0, "tm_test").await.unwrap();
1919 assert_eq!(store.output_count(), 0);
1920 assert_eq!(store.transaction_count(), 0); }
1922
1923 #[tokio::test]
1924 async fn test_delete_output_keeps_beef_if_other_outputs_exist() {
1925 let store = MemoryStorage::new();
1926 store
1927 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1928 .await
1929 .unwrap();
1930 store
1931 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
1932 .await
1933 .unwrap();
1934 assert_eq!(store.output_count(), 2);
1935 assert_eq!(store.transaction_count(), 1);
1936
1937 store.delete_output("abc", 0, "tm_test").await.unwrap();
1938 assert_eq!(store.output_count(), 1);
1939 assert_eq!(store.transaction_count(), 1); }
1941
1942 #[tokio::test]
1943 async fn test_mark_utxo_as_spent() {
1944 let store = MemoryStorage::new();
1945 store
1946 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1947 .await
1948 .unwrap();
1949
1950 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
1951
1952 let found = store
1953 .find_output("abc", 0, Some("tm_test"), None, false)
1954 .await
1955 .unwrap()
1956 .unwrap();
1957 assert!(found.spent);
1958 }
1959
1960 #[tokio::test]
1961 async fn test_find_output_with_spent_filter() {
1962 let store = MemoryStorage::new();
1963 store
1964 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
1965 .await
1966 .unwrap();
1967
1968 assert!(store
1970 .find_output("abc", 0, Some("tm_test"), Some(false), false)
1971 .await
1972 .unwrap()
1973 .is_some());
1974 assert!(store
1975 .find_output("abc", 0, Some("tm_test"), Some(true), false)
1976 .await
1977 .unwrap()
1978 .is_none());
1979
1980 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
1981
1982 assert!(store
1984 .find_output("abc", 0, Some("tm_test"), Some(true), false)
1985 .await
1986 .unwrap()
1987 .is_some());
1988 assert!(store
1989 .find_output("abc", 0, Some("tm_test"), Some(false), false)
1990 .await
1991 .unwrap()
1992 .is_none());
1993 }
1994
1995 #[tokio::test]
1996 async fn test_update_consumed_by() {
1997 let store = MemoryStorage::new();
1998 store
1999 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2000 .await
2001 .unwrap();
2002
2003 let consumed = vec![Outpoint::new("def", 0), Outpoint::new("ghi", 1)];
2004 store
2005 .update_consumed_by("abc", 0, "tm_test", &consumed)
2006 .await
2007 .unwrap();
2008
2009 let found = store
2010 .find_output("abc", 0, Some("tm_test"), None, false)
2011 .await
2012 .unwrap()
2013 .unwrap();
2014 assert_eq!(found.consumed_by.len(), 2);
2015 assert_eq!(found.consumed_by[0].txid, "def");
2016 assert_eq!(found.consumed_by[1].output_index, 1);
2017 }
2018
2019 #[tokio::test]
2020 async fn test_update_transaction_beef() {
2021 let store = MemoryStorage::new();
2022 store
2023 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2024 .await
2025 .unwrap();
2026
2027 store
2028 .update_transaction_beef("abc", &[0xDE, 0xAD])
2029 .await
2030 .unwrap();
2031
2032 let found = store
2033 .find_output("abc", 0, Some("tm_test"), None, true)
2034 .await
2035 .unwrap()
2036 .unwrap();
2037 assert_eq!(found.beef.unwrap(), vec![0xDE, 0xAD]);
2038 }
2039
2040 #[tokio::test]
2041 async fn test_update_output_block_height() {
2042 let store = MemoryStorage::new();
2043 store
2044 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2045 .await
2046 .unwrap();
2047
2048 store
2049 .update_output_block_height("abc", 0, "tm_test", 850_000)
2050 .await
2051 .unwrap();
2052
2053 let found = store
2054 .find_output("abc", 0, Some("tm_test"), None, false)
2055 .await
2056 .unwrap()
2057 .unwrap();
2058 assert_eq!(found.block_height, Some(850_000));
2059 }
2060
2061 #[tokio::test]
2062 async fn test_find_outputs_for_transaction() {
2063 let store = MemoryStorage::new();
2064 store
2065 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2066 .await
2067 .unwrap();
2068 store
2069 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
2070 .await
2071 .unwrap();
2072 store
2073 .insert_output(&make_output("other", 0, "tm_test", 3.0))
2074 .await
2075 .unwrap();
2076
2077 let results = store
2078 .find_outputs_for_transaction("abc", false)
2079 .await
2080 .unwrap();
2081 assert_eq!(results.len(), 2);
2082 }
2083
2084 #[tokio::test]
2085 async fn test_find_utxos_for_topic() {
2086 let store = MemoryStorage::new();
2087 store
2088 .insert_output(&make_output("a", 0, "tm_test", 1.0))
2089 .await
2090 .unwrap();
2091 store
2092 .insert_output(&make_output("b", 0, "tm_test", 2.0))
2093 .await
2094 .unwrap();
2095 store
2096 .insert_output(&make_output("c", 0, "tm_test", 3.0))
2097 .await
2098 .unwrap();
2099 store
2100 .insert_output(&make_output("d", 0, "tm_other", 4.0))
2101 .await
2102 .unwrap();
2103
2104 store.mark_utxo_as_spent("b", 0, "tm_test").await.unwrap();
2106
2107 let results = store
2109 .find_utxos_for_topic("tm_test", None, None, false)
2110 .await
2111 .unwrap();
2112 assert_eq!(results.len(), 2); let results = store
2116 .find_utxos_for_topic("tm_test", Some(2.0), None, false)
2117 .await
2118 .unwrap();
2119 assert_eq!(results.len(), 1); let results = store
2123 .find_utxos_for_topic("tm_test", None, Some(1), false)
2124 .await
2125 .unwrap();
2126 assert_eq!(results.len(), 1);
2127 assert_eq!(results[0].txid, "a"); }
2129
2130 #[tokio::test]
2131 async fn test_find_utxos_for_topic_sorted_by_score() {
2132 let store = MemoryStorage::new();
2133 store
2134 .insert_output(&make_output("c", 0, "tm_test", 30.0))
2135 .await
2136 .unwrap();
2137 store
2138 .insert_output(&make_output("a", 0, "tm_test", 10.0))
2139 .await
2140 .unwrap();
2141 store
2142 .insert_output(&make_output("b", 0, "tm_test", 20.0))
2143 .await
2144 .unwrap();
2145
2146 let results = store
2147 .find_utxos_for_topic("tm_test", None, None, false)
2148 .await
2149 .unwrap();
2150 assert_eq!(results[0].txid, "a");
2151 assert_eq!(results[1].txid, "b");
2152 assert_eq!(results[2].txid, "c");
2153 }
2154
2155 #[tokio::test]
2156 async fn test_applied_transaction() {
2157 let store = MemoryStorage::new();
2158 let tx = AppliedTransaction {
2159 txid: "abc".to_string(),
2160 topic: "tm_test".to_string(),
2161 };
2162
2163 assert!(!store.does_applied_transaction_exist(&tx).await.unwrap());
2164 store.insert_applied_transaction(&tx).await.unwrap();
2165 assert!(store.does_applied_transaction_exist(&tx).await.unwrap());
2166
2167 let tx2 = AppliedTransaction {
2169 txid: "abc".to_string(),
2170 topic: "tm_other".to_string(),
2171 };
2172 assert!(!store.does_applied_transaction_exist(&tx2).await.unwrap());
2173 }
2174
2175 #[tokio::test]
2176 async fn test_sync_state() {
2177 let store = MemoryStorage::new();
2178
2179 assert_eq!(
2180 store
2181 .get_last_interaction("host1", "tm_test")
2182 .await
2183 .unwrap(),
2184 0
2185 );
2186
2187 store
2188 .update_last_interaction("host1", "tm_test", 100)
2189 .await
2190 .unwrap();
2191 assert_eq!(
2192 store
2193 .get_last_interaction("host1", "tm_test")
2194 .await
2195 .unwrap(),
2196 100
2197 );
2198
2199 store
2201 .update_last_interaction("host1", "tm_test", 200)
2202 .await
2203 .unwrap();
2204 assert_eq!(
2205 store
2206 .get_last_interaction("host1", "tm_test")
2207 .await
2208 .unwrap(),
2209 200
2210 );
2211
2212 assert_eq!(
2214 store
2215 .get_last_interaction("host2", "tm_test")
2216 .await
2217 .unwrap(),
2218 0
2219 );
2220 }
2221
2222 #[tokio::test]
2223 async fn test_find_outputs_by_outpoints_batch() {
2224 let store = MemoryStorage::new();
2225 store
2226 .insert_output(&make_output("a", 0, "tm_test", 1.0))
2227 .await
2228 .unwrap();
2229 store
2230 .insert_output(&make_output("b", 0, "tm_test", 2.0))
2231 .await
2232 .unwrap();
2233 store
2234 .insert_output(&make_output("c", 0, "tm_test", 3.0))
2235 .await
2236 .unwrap();
2237
2238 let outpoints = vec![
2239 Outpoint::new("a", 0),
2240 Outpoint::new("c", 0),
2241 Outpoint::new("missing", 0),
2242 ];
2243 let results = store
2244 .find_outputs_by_outpoints(&outpoints, false)
2245 .await
2246 .unwrap();
2247 assert_eq!(results.len(), 2); }
2249
2250 #[tokio::test]
2251 async fn test_find_output_without_topic() {
2252 let store = MemoryStorage::new();
2253 store
2254 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2255 .await
2256 .unwrap();
2257
2258 let found = store
2260 .find_output("abc", 0, None, None, false)
2261 .await
2262 .unwrap();
2263 assert!(found.is_some());
2264 assert_eq!(found.unwrap().topic, "tm_test");
2265 }
2266}