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 async fn write_batch(
486 &self,
487 writes: &[crate::page_batch::PageWrite],
488 ) -> Result<u64, StorageError> {
489 use crate::page_batch::PageWrite;
490 for write in writes {
491 match write {
492 PageWrite::Output { output, gasp } => {
493 self.insert_output_with_origin(output, *gasp).await?;
494 }
495 PageWrite::Applied(tx) => self.insert_applied_transaction(tx).await?,
496 PageWrite::Backend(_) => {
497 return Err(StorageError::Other(
498 "a backend statement this storage did not defer".into(),
499 ))
500 }
501 }
502 }
503 Ok(writes.len() as u64)
504 }
505
506 async fn find_topic_outputs(
510 &self,
511 topic: &str,
512 outpoints: &[(String, u32)],
513 ) -> Result<Vec<Output>, StorageError> {
514 let mut held = Vec::new();
515 for (txid, vout) in outpoints {
516 if let Some(output) = self
517 .find_output(txid, *vout, Some(topic), None, false)
518 .await?
519 {
520 held.push(output);
521 }
522 }
523 Ok(held)
524 }
525
526 async fn find_applied_transactions(
529 &self,
530 topic: &str,
531 txids: &[String],
532 ) -> Result<Vec<String>, StorageError> {
533 let mut applied = Vec::new();
534 for txid in txids {
535 let tx = AppliedTransaction {
536 txid: txid.clone(),
537 topic: topic.to_string(),
538 };
539 if self.does_applied_transaction_exist(&tx).await? {
540 applied.push(txid.clone());
541 }
542 }
543 Ok(applied)
544 }
545}
546
547#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
550pub struct PeerYieldStreak {
551 pub yieldless_syncs: u64,
553 pub secs_since_first: Option<u64>,
556}
557
558#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
561pub struct PeerSyncHealth {
562 pub consecutive_failures: u64,
565 pub secs_since_last_attempt: Option<u64>,
568}
569
570#[async_trait(?Send)]
579impl<T: Storage + ?Sized> Storage for std::rc::Rc<T> {
580 async fn write_batch(
581 &self,
582 writes: &[crate::page_batch::PageWrite],
583 ) -> Result<u64, StorageError> {
584 (**self).write_batch(writes).await
585 }
586 async fn find_topic_outputs(
587 &self,
588 topic: &str,
589 outpoints: &[(String, u32)],
590 ) -> Result<Vec<Output>, StorageError> {
591 (**self).find_topic_outputs(topic, outpoints).await
592 }
593 async fn find_applied_transactions(
594 &self,
595 topic: &str,
596 txids: &[String],
597 ) -> Result<Vec<String>, StorageError> {
598 (**self).find_applied_transactions(topic, txids).await
599 }
600 async fn insert_output_with_origin(
601 &self,
602 output: &Output,
603 gasp: bool,
604 ) -> Result<(), StorageError> {
605 (**self).insert_output_with_origin(output, gasp).await
606 }
607 async fn read_fault_memos(
608 &self,
609 host: &str,
610 topic: &str,
611 outpoints: Option<&[String]>,
612 ) -> Result<crate::fault_memo::FaultMemoBatch, StorageError> {
613 (**self).read_fault_memos(host, topic, outpoints).await
614 }
615
616 async fn get_peer_cadence(
617 &self,
618 host: &str,
619 topic: &str,
620 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
621 (**self).get_peer_cadence(host, topic).await
622 }
623 async fn set_peer_cadence(
624 &self,
625 host: &str,
626 topic: &str,
627 state: &crate::sync_load::PeerCadence,
628 ) -> Result<(), StorageError> {
629 (**self).set_peer_cadence(host, topic, state).await
630 }
631 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
632 (**self).insert_output(output).await
633 }
634 async fn delete_output(
635 &self,
636 txid: &str,
637 output_index: u32,
638 topic: &str,
639 ) -> Result<(), StorageError> {
640 (**self).delete_output(txid, output_index, topic).await
641 }
642 async fn mark_utxo_as_spent(
643 &self,
644 txid: &str,
645 output_index: u32,
646 topic: &str,
647 ) -> Result<(), StorageError> {
648 (**self).mark_utxo_as_spent(txid, output_index, topic).await
649 }
650 async fn update_consumed_by(
651 &self,
652 txid: &str,
653 output_index: u32,
654 topic: &str,
655 consumed_by: &[Outpoint],
656 ) -> Result<(), StorageError> {
657 (**self)
658 .update_consumed_by(txid, output_index, topic, consumed_by)
659 .await
660 }
661 async fn update_transaction_beef(&self, txid: &str, beef: &[u8]) -> Result<(), StorageError> {
662 (**self).update_transaction_beef(txid, beef).await
663 }
664 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
665 (**self).mark_transaction_proven(txid).await
666 }
667 async fn mark_transaction_proven_at(
668 &self,
669 txid: &str,
670 height: Option<u64>,
671 ) -> Result<(), StorageError> {
672 (**self).mark_transaction_proven_at(txid, height).await
676 }
677 async fn update_output_block_height(
678 &self,
679 txid: &str,
680 output_index: u32,
681 topic: &str,
682 block_height: u32,
683 ) -> Result<(), StorageError> {
684 (**self)
685 .update_output_block_height(txid, output_index, topic, block_height)
686 .await
687 }
688 async fn insert_applied_transaction(
689 &self,
690 tx: &AppliedTransaction,
691 ) -> Result<(), StorageError> {
692 (**self).insert_applied_transaction(tx).await
693 }
694 async fn does_applied_transaction_exist(
695 &self,
696 tx: &AppliedTransaction,
697 ) -> Result<bool, StorageError> {
698 (**self).does_applied_transaction_exist(tx).await
699 }
700 async fn delete_applied_transaction(
701 &self,
702 tx: &AppliedTransaction,
703 ) -> Result<(), StorageError> {
704 (**self).delete_applied_transaction(tx).await
705 }
706 async fn find_output(
707 &self,
708 txid: &str,
709 output_index: u32,
710 topic: Option<&str>,
711 spent: Option<bool>,
712 include_beef: bool,
713 ) -> Result<Option<Output>, StorageError> {
714 (**self)
715 .find_output(txid, output_index, topic, spent, include_beef)
716 .await
717 }
718 async fn find_served_outputs(
719 &self,
720 outpoints: &[Outpoint],
721 ) -> Result<Vec<Output>, StorageError> {
722 (**self).find_served_outputs(outpoints).await
723 }
724
725 async fn find_outputs_for_transaction(
726 &self,
727 txid: &str,
728 include_beef: bool,
729 ) -> Result<Vec<Output>, StorageError> {
730 (**self)
731 .find_outputs_for_transaction(txid, include_beef)
732 .await
733 }
734 async fn find_utxos_for_topic(
735 &self,
736 topic: &str,
737 since: Option<f64>,
738 limit: Option<u64>,
739 include_beef: bool,
740 ) -> Result<Vec<Output>, StorageError> {
741 (**self)
742 .find_utxos_for_topic(topic, since, limit, include_beef)
743 .await
744 }
745 async fn find_transactions_for_proof_check(
746 &self,
747 limit: u64,
748 min_age_secs: u64,
749 ) -> Result<Vec<TransactionBeef>, StorageError> {
750 (**self)
751 .find_transactions_for_proof_check(limit, min_age_secs)
752 .await
753 }
754 async fn update_last_interaction(
755 &self,
756 host: &str,
757 topic: &str,
758 since: u64,
759 ) -> Result<(), StorageError> {
760 (**self).update_last_interaction(host, topic, since).await
761 }
762 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
763 (**self).get_last_interaction(host, topic).await
764 }
765 async fn record_peer_sync_outcome(
766 &self,
767 host: &str,
768 topic: &str,
769 success: bool,
770 ) -> Result<(), StorageError> {
771 (**self)
772 .record_peer_sync_outcome(host, topic, success)
773 .await
774 }
775 async fn get_peer_sync_health(
776 &self,
777 host: &str,
778 topic: &str,
779 ) -> Result<PeerSyncHealth, StorageError> {
780 (**self).get_peer_sync_health(host, topic).await
781 }
782 async fn record_peer_sync_yield(
783 &self,
784 host: &str,
785 topic: &str,
786 yielded: bool,
787 ) -> Result<PeerYieldStreak, StorageError> {
788 (**self).record_peer_sync_yield(host, topic, yielded).await
789 }
790 async fn put_deferred_graph(
791 &self,
792 record: &crate::gasp::DeferredGraph,
793 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
794 (**self).put_deferred_graph(record).await
795 }
796 async fn reserve_unanchorable_retry(
797 &self,
798 record: &crate::gasp::DeferredGraph,
799 ) -> Result<bool, StorageError> {
800 (**self).reserve_unanchorable_retry(record).await
801 }
802 async fn find_deferred_graphs(
803 &self,
804 host: &str,
805 topic: &str,
806 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
807 (**self).find_deferred_graphs(host, topic).await
808 }
809 async fn get_deferred_graph(
810 &self,
811 host: &str,
812 topic: &str,
813 outpoint: &str,
814 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
815 (**self).get_deferred_graph(host, topic, outpoint).await
816 }
817 async fn delete_deferred_graph(
818 &self,
819 host: &str,
820 topic: &str,
821 outpoint: &str,
822 ) -> Result<(), StorageError> {
823 (**self).delete_deferred_graph(host, topic, outpoint).await
824 }
825 async fn remember_expired_outpoint(
826 &self,
827 topic: &str,
828 outpoint: &str,
829 expiry: u64,
830 ) -> Result<(), StorageError> {
831 (**self)
832 .remember_expired_outpoint(topic, outpoint, expiry)
833 .await
834 }
835 async fn find_expired_outpoints(
836 &self,
837 topic: &str,
838 outpoints: &[String],
839 ) -> Result<Vec<String>, StorageError> {
840 (**self).find_expired_outpoints(topic, outpoints).await
841 }
842}
843
844#[derive(Debug, thiserror::Error)]
850pub enum StorageError {
851 #[error("not found: {0}")]
852 NotFound(String),
853
854 #[error("duplicate: {0}")]
855 Duplicate(String),
856
857 #[error("database error: {0}")]
858 Database(String),
859
860 #[error("serialization error: {0}")]
861 Serialization(String),
862
863 #[error("{0}")]
864 Other(String),
865}
866
867#[cfg(any(test, feature = "memory-storage"))]
872pub mod memory {
873 use super::*;
874 use std::collections::HashMap;
875 use std::sync::Mutex;
876
877 #[derive(Debug, Default)]
881 pub struct MemoryStorage {
882 fault_memos: Mutex<HashMap<(String, String, String), crate::fault_memo::FaultMemo>>,
883 fault_memo_ceiling: Mutex<Option<usize>>,
884 gasp_transactions: Mutex<std::collections::HashSet<String>>,
885 cadence: Mutex<HashMap<(String, String), (crate::sync_load::PeerCadence, u64)>>,
886 outputs: Mutex<HashMap<(String, u32, String), Output>>,
888 transactions: Mutex<HashMap<String, Vec<u8>>>,
890 proven: Mutex<std::collections::HashSet<String>>,
898 applied: Mutex<HashMap<(String, String), bool>>,
900 sync_state: Mutex<HashMap<(String, String), u64>>,
902 clock_secs: Mutex<u64>,
907 created_at: Mutex<HashMap<String, u64>>,
911 peer_health: Mutex<HashMap<(String, String), (u64, u64)>>,
916 proven_at: Mutex<HashMap<String, Option<u64>>>,
920 deferred_graphs: Mutex<HashMap<(String, String, String), crate::gasp::DeferredGraph>>,
923 deferred_graph_writes: Mutex<u64>,
926 deferred_graph_reads: Mutex<u64>,
929 deferred_graph_keys_fault: Mutex<bool>,
931 deferred_graph_ceiling: Mutex<Option<usize>>,
934 deferred_graph_byte_ceiling: Mutex<Option<usize>>,
938 deferred_graph_put_fault: Mutex<bool>,
940 expired_outpoints: Mutex<HashMap<(String, String), (u64, u64)>>,
944 deferred_graph_put_attempts: Mutex<u64>,
947 peer_yieldless: Mutex<HashMap<(String, String), YieldStreakRow>>,
953 }
954
955 type YieldStreakRow = (u64, u64, u64);
958
959 impl MemoryStorage {
960 pub fn fault_memos(&self) -> Vec<crate::fault_memo::FaultMemo> {
961 self.fault_memos.lock().unwrap().values().cloned().collect()
962 }
963
964 pub fn set_fault_memo_ceiling(&self, ceiling: Option<usize>) {
965 *self.fault_memo_ceiling.lock().unwrap() = ceiling;
966 }
967
968 pub fn seed_legacy_fault(&self, record: crate::gasp::DeferredGraph) {
969 self.deferred_graphs.lock().unwrap().insert(
970 (
971 record.peer.clone(),
972 record.topic.clone(),
973 record.outpoint.clone(),
974 ),
975 record,
976 );
977 }
978
979 fn cap_fault_memos(
980 held: &mut HashMap<(String, String, String), crate::fault_memo::FaultMemo>,
981 topic: &str,
982 ) {
983 if held.len() <= crate::fault_memo::FAULT_MEMO_MAX_ROWS {
984 return;
985 }
986 let mut keys: Vec<_> = held
987 .iter()
988 .filter(|(_, r)| r.topic == topic)
989 .map(|(k, r)| (r.first_at, k.clone()))
990 .collect();
991 let excess = keys
992 .len()
993 .saturating_sub(crate::fault_memo::FAULT_MEMO_MAX_ROWS);
994 if excess > 0 {
995 keys.sort();
996 for (_, key) in keys.into_iter().take(excess) {
997 held.remove(&key);
998 }
999 }
1000 }
1001
1002 pub fn new() -> Self {
1003 Self::default()
1004 }
1005
1006 pub fn proven_at_height(&self, txid: &str) -> Option<Option<u64>> {
1010 self.proven_at.lock().unwrap().get(txid).copied()
1011 }
1012
1013 pub fn expired_outpoints(&self, topic: &str) -> Vec<String> {
1018 let now = *self.clock_secs.lock().unwrap();
1019 let mut held: Vec<String> = self
1020 .expired_outpoints
1021 .lock()
1022 .unwrap()
1023 .iter()
1024 .filter(|((t, _), (keep, _))| t == topic && *keep > now)
1025 .map(|((_, o), _)| o.clone())
1026 .collect();
1027 held.sort();
1028 held
1029 }
1030
1031 pub fn advance_clock(&self, secs: u64) {
1032 *self.clock_secs.lock().unwrap() += secs;
1033 }
1034
1035 fn stamp_created(&self, txid: &str) {
1038 let now = *self.clock_secs.lock().unwrap();
1039 self.created_at
1040 .lock()
1041 .unwrap()
1042 .entry(txid.to_string())
1043 .or_insert(now);
1044 }
1045
1046 fn age_gate_open(&self, txid: &str, min_age_secs: u64) -> bool {
1049 if min_age_secs == 0 {
1050 return true;
1051 }
1052 let now = *self.clock_secs.lock().unwrap();
1053 match self.created_at.lock().unwrap().get(txid) {
1054 None => true, Some(created) => now.saturating_sub(*created) >= min_age_secs,
1056 }
1057 }
1058
1059 pub fn deferred_graphs(&self) -> Vec<crate::gasp::DeferredGraph> {
1061 let mut all: Vec<_> = self
1062 .deferred_graphs
1063 .lock()
1064 .unwrap()
1065 .values()
1066 .cloned()
1067 .collect();
1068 all.sort_by(|a, b| (a.score, &a.outpoint).cmp(&(b.score, &b.outpoint)));
1069 all
1070 }
1071
1072 pub fn deferred_graph_writes(&self) -> u64 {
1074 *self.deferred_graph_writes.lock().unwrap()
1075 }
1076
1077 pub fn deferred_graph_reads(&self) -> u64 {
1079 *self.deferred_graph_reads.lock().unwrap()
1080 }
1081
1082 pub fn set_deferred_graph_keys_fault(&self, fault: bool) {
1085 *self.deferred_graph_keys_fault.lock().unwrap() = fault;
1086 }
1087
1088 pub fn set_deferred_graph_ceiling(&self, ceiling: Option<usize>) {
1091 *self.deferred_graph_ceiling.lock().unwrap() = ceiling;
1092 }
1093
1094 pub fn set_deferred_graph_byte_ceiling(&self, ceiling: Option<usize>) {
1098 *self.deferred_graph_byte_ceiling.lock().unwrap() = ceiling;
1099 }
1100
1101 pub fn set_deferred_graph_put_fault(&self, fault: bool) {
1104 *self.deferred_graph_put_fault.lock().unwrap() = fault;
1105 }
1106
1107 pub fn deferred_graph_put_attempts(&self) -> u64 {
1110 *self.deferred_graph_put_attempts.lock().unwrap()
1111 }
1112
1113 pub fn peer_yieldless_syncs(&self, host: &str, topic: &str) -> u64 {
1118 self.peer_yieldless
1119 .lock()
1120 .unwrap()
1121 .get(&(crate::gasp::peer_origin(host), topic.to_string()))
1122 .map_or(0, |(count, _, _)| *count)
1123 }
1124
1125 pub fn output_count(&self) -> usize {
1127 self.outputs.lock().unwrap().len()
1128 }
1129
1130 pub fn transaction_count(&self) -> usize {
1132 self.transactions.lock().unwrap().len()
1133 }
1134 }
1135
1136 #[async_trait(?Send)]
1137 impl Storage for MemoryStorage {
1138 async fn insert_output_with_origin(
1139 &self,
1140 output: &Output,
1141 gasp: bool,
1142 ) -> Result<(), StorageError> {
1143 self.insert_output(output).await?;
1144 if gasp && output.beef.is_some() {
1145 self.gasp_transactions
1146 .lock()
1147 .unwrap()
1148 .insert(output.txid.clone());
1149 }
1150 Ok(())
1151 }
1152
1153 async fn read_fault_memos(
1154 &self,
1155 host: &str,
1156 topic: &str,
1157 outpoints: Option<&[String]>,
1158 ) -> Result<crate::fault_memo::FaultMemoBatch, StorageError> {
1159 use crate::fault_memo::{FaultMemo, FaultMemoBatch, FAULT_MEMO_DUE_LIMIT};
1160 let now = (crate::engine::current_timestamp_ms() / 1000.0) as u64;
1161 let mut batch = FaultMemoBatch::default();
1162 let mut held = self.fault_memos.lock().unwrap();
1163 if outpoints.is_none() {
1164 let mut legacy = self.deferred_graphs.lock().unwrap();
1165 legacy.retain(|key, record| {
1166 if record.peer != host || record.topic != topic {
1167 return true;
1168 }
1169 let Some(memo) = FaultMemo::from_record(record, now) else {
1170 return true;
1171 };
1172 held.entry(key.clone()).or_insert(memo);
1173 batch.folded += 1;
1174 false
1175 });
1176 Self::cap_fault_memos(&mut held, topic);
1177 let mut expired = self.expired_outpoints.lock().unwrap();
1178 let logical_now = *self.clock_secs.lock().unwrap();
1179 for r in held.values().filter(|r| {
1180 r.peer == host
1181 && r.topic == topic
1182 && (r.dead || r.unanchorable)
1183 && r.expiry.is_some_and(|t| t <= now)
1184 }) {
1185 expired.insert(
1186 (topic.into(), r.outpoint.clone()),
1187 (
1188 logical_now + crate::gasp::EXPIRED_OUTPOINT_MEMO_SECS,
1189 logical_now,
1190 ),
1191 );
1192 }
1193 held.retain(|_, r| {
1194 let remove = r.peer == host
1195 && r.topic == topic
1196 && (r.dead || r.unanchorable)
1197 && (r.expiry.is_some_and(|t| t <= now)
1198 || expired
1199 .get(&(topic.into(), r.outpoint.clone()))
1200 .is_some_and(|(until, _)| *until > logical_now));
1201 if remove {
1202 batch.swept += 1;
1203 }
1204 !remove
1205 });
1206 }
1207 batch.records = held
1208 .values()
1209 .filter(|r| r.peer == host && r.topic == topic)
1210 .filter(|r| {
1211 outpoints.map_or(!r.dead && r.next_at <= now, |keys| {
1212 keys.contains(&r.outpoint)
1213 })
1214 })
1215 .cloned()
1216 .collect();
1217 batch.records.sort_by(|a, b| {
1218 (a.next_at, a.score, &a.outpoint).cmp(&(b.next_at, b.score, &b.outpoint))
1219 });
1220 if outpoints.is_none() {
1221 batch.records.truncate(FAULT_MEMO_DUE_LIMIT);
1222 }
1223 Ok(batch)
1224 }
1225
1226 async fn insert_output(&self, output: &Output) -> Result<(), StorageError> {
1227 let key = (
1228 output.txid.clone(),
1229 output.output_index,
1230 output.topic.clone(),
1231 );
1232 let mut outputs = self.outputs.lock().unwrap();
1233
1234 if outputs.contains_key(&key) {
1236 if let Some(ref beef) = output.beef {
1238 self.transactions
1239 .lock()
1240 .unwrap()
1241 .entry(output.txid.clone())
1242 .or_insert_with(|| beef.clone());
1243 }
1244 return Ok(());
1245 }
1246
1247 let mut stored = output.clone();
1249 stored.beef = None;
1250 outputs.insert(key, stored);
1251
1252 if let Some(ref beef) = output.beef {
1254 self.transactions
1255 .lock()
1256 .unwrap()
1257 .entry(output.txid.clone())
1258 .or_insert_with(|| beef.clone());
1259 self.stamp_created(&output.txid);
1260 }
1261
1262 Ok(())
1263 }
1264
1265 async fn delete_output(
1266 &self,
1267 txid: &str,
1268 output_index: u32,
1269 topic: &str,
1270 ) -> Result<(), StorageError> {
1271 let key = (txid.to_string(), output_index, topic.to_string());
1272 let mut outputs = self.outputs.lock().unwrap();
1273 outputs.remove(&key);
1274
1275 let has_remaining = outputs.keys().any(|(t, _, _)| t == txid);
1277 if !has_remaining {
1278 self.transactions.lock().unwrap().remove(txid);
1279 self.proven.lock().unwrap().remove(txid);
1280 self.gasp_transactions.lock().unwrap().remove(txid);
1281 }
1282
1283 Ok(())
1284 }
1285
1286 async fn mark_utxo_as_spent(
1287 &self,
1288 txid: &str,
1289 output_index: u32,
1290 topic: &str,
1291 ) -> Result<(), StorageError> {
1292 let key = (txid.to_string(), output_index, topic.to_string());
1293 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1294 output.spent = true;
1295 }
1296 Ok(())
1297 }
1298
1299 async fn update_consumed_by(
1300 &self,
1301 txid: &str,
1302 output_index: u32,
1303 topic: &str,
1304 consumed_by: &[Outpoint],
1305 ) -> Result<(), StorageError> {
1306 let key = (txid.to_string(), output_index, topic.to_string());
1307 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1308 output.consumed_by = consumed_by.to_vec();
1309 }
1310 Ok(())
1311 }
1312
1313 async fn update_transaction_beef(
1314 &self,
1315 txid: &str,
1316 beef: &[u8],
1317 ) -> Result<(), StorageError> {
1318 let has_proof = beef_limits::has_proof(beef, txid);
1324 if has_proof {
1325 self.proven.lock().unwrap().insert(txid.to_string());
1326 }
1327 self.transactions
1328 .lock()
1329 .unwrap()
1330 .insert(txid.to_string(), beef.to_vec());
1331 self.stamp_created(txid);
1334 Ok(())
1335 }
1336
1337 async fn mark_transaction_proven(&self, txid: &str) -> Result<(), StorageError> {
1338 self.proven.lock().unwrap().insert(txid.to_string());
1341 Ok(())
1342 }
1343
1344 async fn mark_transaction_proven_at(
1345 &self,
1346 txid: &str,
1347 height: Option<u64>,
1348 ) -> Result<(), StorageError> {
1349 self.proven.lock().unwrap().insert(txid.to_string());
1353 self.proven_at
1354 .lock()
1355 .unwrap()
1356 .insert(txid.to_string(), height);
1357 Ok(())
1358 }
1359
1360 async fn update_output_block_height(
1361 &self,
1362 txid: &str,
1363 output_index: u32,
1364 topic: &str,
1365 block_height: u32,
1366 ) -> Result<(), StorageError> {
1367 let key = (txid.to_string(), output_index, topic.to_string());
1368 if let Some(output) = self.outputs.lock().unwrap().get_mut(&key) {
1369 output.block_height = Some(block_height);
1370 }
1371 Ok(())
1372 }
1373
1374 async fn insert_applied_transaction(
1375 &self,
1376 tx: &AppliedTransaction,
1377 ) -> Result<(), StorageError> {
1378 self.applied
1379 .lock()
1380 .unwrap()
1381 .insert((tx.txid.clone(), tx.topic.clone()), true);
1382 Ok(())
1383 }
1384
1385 async fn does_applied_transaction_exist(
1386 &self,
1387 tx: &AppliedTransaction,
1388 ) -> Result<bool, StorageError> {
1389 Ok(self
1390 .applied
1391 .lock()
1392 .unwrap()
1393 .contains_key(&(tx.txid.clone(), tx.topic.clone())))
1394 }
1395
1396 async fn delete_applied_transaction(
1397 &self,
1398 tx: &AppliedTransaction,
1399 ) -> Result<(), StorageError> {
1400 self.applied
1401 .lock()
1402 .unwrap()
1403 .remove(&(tx.txid.clone(), tx.topic.clone()));
1404 Ok(())
1405 }
1406
1407 async fn find_output(
1408 &self,
1409 txid: &str,
1410 output_index: u32,
1411 topic: Option<&str>,
1412 spent: Option<bool>,
1413 include_beef: bool,
1414 ) -> Result<Option<Output>, StorageError> {
1415 let outputs = self.outputs.lock().unwrap();
1416
1417 if let Some(topic) = topic {
1419 let key = (txid.to_string(), output_index, topic.to_string());
1420 if let Some(output) = outputs.get(&key) {
1421 if let Some(s) = spent {
1422 if output.spent != s {
1423 return Ok(None);
1424 }
1425 }
1426 let mut result = output.clone();
1427 if include_beef {
1428 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1429 }
1430 return Ok(Some(result));
1431 }
1432 return Ok(None);
1433 }
1434
1435 for ((t, oi, _), output) in outputs.iter() {
1437 if t == txid && *oi == output_index {
1438 if let Some(s) = spent {
1439 if output.spent != s {
1440 continue;
1441 }
1442 }
1443 let mut result = output.clone();
1444 if include_beef {
1445 result.beef = self.transactions.lock().unwrap().get(txid).cloned();
1446 }
1447 return Ok(Some(result));
1448 }
1449 }
1450 Ok(None)
1451 }
1452
1453 async fn find_served_outputs(
1454 &self,
1455 outpoints: &[Outpoint],
1456 ) -> Result<Vec<Output>, StorageError> {
1457 let outputs = self.find_outputs_by_outpoints(outpoints, true).await?;
1458 let now = i64::try_from(*self.clock_secs.lock().unwrap())
1459 .unwrap_or(i64::MAX)
1460 .saturating_mul(1000);
1461 let proven = self.proven.lock().unwrap();
1462 let stamps = self.created_at.lock().unwrap();
1463 Ok(outputs
1464 .into_iter()
1465 .filter(|output| {
1466 let seen = stamps
1467 .get(&output.txid)
1468 .and_then(|s| i64::try_from(*s).ok())
1469 .map(|s| s.saturating_mul(1000));
1470 crate::proof_window::verdict(proven.contains(&output.txid), seen, now)
1471 == crate::proof_window::Verdict::Served
1472 })
1473 .collect())
1474 }
1475
1476 async fn find_outputs_for_transaction(
1477 &self,
1478 txid: &str,
1479 include_beef: bool,
1480 ) -> Result<Vec<Output>, StorageError> {
1481 let outputs = self.outputs.lock().unwrap();
1482 let beef = if include_beef {
1483 self.transactions.lock().unwrap().get(txid).cloned()
1484 } else {
1485 None
1486 };
1487
1488 let results: Vec<Output> = outputs
1489 .iter()
1490 .filter(|((t, _, _), _)| t == txid)
1491 .map(|(_, output)| {
1492 let mut o = output.clone();
1493 if include_beef {
1494 o.beef.clone_from(&beef);
1495 }
1496 o
1497 })
1498 .collect();
1499
1500 Ok(results)
1501 }
1502
1503 async fn find_utxos_for_topic(
1504 &self,
1505 topic: &str,
1506 since: Option<f64>,
1507 limit: Option<u64>,
1508 include_beef: bool,
1509 ) -> Result<Vec<Output>, StorageError> {
1510 let outputs = self.outputs.lock().unwrap();
1511 let transactions = self.transactions.lock().unwrap();
1512
1513 let mut results: Vec<Output> = outputs
1514 .iter()
1515 .filter(|((_, _, t), o)| {
1516 t == topic && !o.spent && since.is_none_or(|s| o.score.unwrap_or(0.0) >= s)
1517 })
1518 .map(|(_, output)| {
1519 let mut o = output.clone();
1520 if include_beef {
1521 o.beef = transactions.get(&o.txid).cloned();
1522 }
1523 o
1524 })
1525 .collect();
1526
1527 results.sort_by(|a, b| {
1529 a.score
1530 .unwrap_or(0.0)
1531 .partial_cmp(&b.score.unwrap_or(0.0))
1532 .unwrap_or(std::cmp::Ordering::Equal)
1533 });
1534
1535 if let Some(limit) = limit {
1536 results.truncate(limit as usize);
1537 }
1538
1539 Ok(results)
1540 }
1541
1542 async fn find_transactions_for_proof_check(
1543 &self,
1544 limit: u64,
1545 min_age_secs: u64,
1546 ) -> Result<Vec<TransactionBeef>, StorageError> {
1547 let candidates: Vec<TransactionBeef> = {
1560 let transactions = self.transactions.lock().unwrap();
1561 let proven = self.proven.lock().unwrap();
1562 transactions
1563 .iter()
1564 .filter(|(txid, _)| !proven.contains(*txid))
1565 .map(|(txid, beef)| TransactionBeef {
1566 txid: txid.clone(),
1567 beef: beef.clone(),
1568 early_gasp: self.gasp_transactions.lock().unwrap().contains(txid)
1569 && !self.age_gate_open(txid, min_age_secs),
1570 })
1571 .collect()
1572 };
1573 Ok(candidates
1574 .into_iter()
1575 .filter(|c| c.early_gasp || self.age_gate_open(&c.txid, min_age_secs))
1576 .take(limit as usize)
1577 .collect())
1578 }
1579
1580 async fn update_last_interaction(
1581 &self,
1582 host: &str,
1583 topic: &str,
1584 since: u64,
1585 ) -> Result<(), StorageError> {
1586 self.sync_state
1587 .lock()
1588 .unwrap()
1589 .insert((host.to_string(), topic.to_string()), since);
1590 Ok(())
1591 }
1592
1593 async fn get_last_interaction(&self, host: &str, topic: &str) -> Result<u64, StorageError> {
1594 Ok(*self
1595 .sync_state
1596 .lock()
1597 .unwrap()
1598 .get(&(host.to_string(), topic.to_string()))
1599 .unwrap_or(&0))
1600 }
1601
1602 async fn get_peer_cadence(
1603 &self,
1604 host: &str,
1605 topic: &str,
1606 ) -> Result<crate::sync_load::PeerCadence, StorageError> {
1607 let now = *self.clock_secs.lock().unwrap();
1608 Ok(self
1609 .cadence
1610 .lock()
1611 .unwrap()
1612 .get(&(host.into(), topic.into()))
1613 .map_or_else(Default::default, |(s, at)| {
1614 let mut state = s.clone();
1615 state.retry_after_secs = at.saturating_sub(now);
1616 state
1617 }))
1618 }
1619 async fn set_peer_cadence(
1620 &self,
1621 host: &str,
1622 topic: &str,
1623 state: &crate::sync_load::PeerCadence,
1624 ) -> Result<(), StorageError> {
1625 let now = *self.clock_secs.lock().unwrap();
1626 self.cadence.lock().unwrap().insert(
1627 (host.into(), topic.into()),
1628 (state.clone(), now.saturating_add(state.retry_after_secs)),
1629 );
1630 Ok(())
1631 }
1632 async fn record_peer_sync_outcome(
1633 &self,
1634 host: &str,
1635 topic: &str,
1636 success: bool,
1637 ) -> Result<(), StorageError> {
1638 let now = *self.clock_secs.lock().unwrap();
1639 let mut health = self.peer_health.lock().unwrap();
1640 let entry = health
1641 .entry((host.to_string(), topic.to_string()))
1642 .or_insert((0, now));
1643 entry.0 = if success { 0 } else { entry.0 + 1 };
1644 entry.1 = now;
1645 Ok(())
1646 }
1647
1648 async fn get_peer_sync_health(
1649 &self,
1650 host: &str,
1651 topic: &str,
1652 ) -> Result<PeerSyncHealth, StorageError> {
1653 let now = *self.clock_secs.lock().unwrap();
1654 Ok(self
1655 .peer_health
1656 .lock()
1657 .unwrap()
1658 .get(&(host.to_string(), topic.to_string()))
1659 .map(|(fails, attempt_at)| PeerSyncHealth {
1660 consecutive_failures: *fails,
1661 secs_since_last_attempt: Some(now.saturating_sub(*attempt_at)),
1662 })
1663 .unwrap_or_default())
1664 }
1665
1666 async fn record_peer_sync_yield(
1667 &self,
1668 host: &str,
1669 topic: &str,
1670 yielded: bool,
1671 ) -> Result<PeerYieldStreak, StorageError> {
1672 let now = *self.clock_secs.lock().unwrap();
1673 let key = (host.to_string(), topic.to_string());
1674 let not_failed = self
1677 .peer_health
1678 .lock()
1679 .unwrap()
1680 .get(&key)
1681 .is_none_or(|(fails, _)| *fails == 0);
1682 let mut held = self.peer_yieldless.lock().unwrap();
1683 if yielded {
1684 held.remove(&key);
1685 return Ok(PeerYieldStreak::default());
1686 }
1687 let streak = held.entry(key).or_insert((0, now, now));
1688 if streak.0 == 0
1689 || (not_failed
1690 && now.saturating_sub(streak.2) > crate::gasp::PEER_YIELDLESS_DECAY_SECS)
1691 {
1692 *streak = (0, now, now);
1693 }
1694 streak.0 += 1;
1695 streak.2 = now;
1696 Ok(PeerYieldStreak {
1697 yieldless_syncs: streak.0,
1698 secs_since_first: Some(now.saturating_sub(streak.1)),
1699 })
1700 }
1701
1702 async fn put_deferred_graph(
1703 &self,
1704 record: &crate::gasp::DeferredGraph,
1705 ) -> Result<crate::gasp::DeferredGraphSave, StorageError> {
1706 let key = (
1707 record.peer.clone(),
1708 record.topic.clone(),
1709 record.outpoint.clone(),
1710 );
1711 *self.deferred_graph_put_attempts.lock().unwrap() += 1;
1712 if *self.deferred_graph_put_fault.lock().unwrap() {
1713 return Err(StorageError::Database("deferred graph save faulted".into()));
1714 }
1715 if let Some(mut memo) = crate::fault_memo::FaultMemo::from_record(
1716 record,
1717 (crate::engine::current_timestamp_ms() / 1000.0) as u64,
1718 ) {
1719 let mut held = self.fault_memos.lock().unwrap();
1720 if self
1721 .fault_memo_ceiling
1722 .lock()
1723 .unwrap()
1724 .is_some_and(|n| !held.contains_key(&key) && held.len() >= n)
1725 {
1726 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1727 }
1728 if let Some(prior) = held.get(&key).filter(|r| r.score == memo.score) {
1729 memo.first_at = prior.first_at;
1730 }
1731 held.insert(key.clone(), memo);
1732 Self::cap_fault_memos(&mut held, &record.topic);
1733 self.deferred_graphs.lock().unwrap().remove(&key);
1734 *self.deferred_graph_writes.lock().unwrap() += 1;
1735 return Ok(crate::gasp::DeferredGraphSave::Saved);
1736 }
1737 if let Some(ceiling) = *self.deferred_graph_ceiling.lock().unwrap() {
1738 let held = self.deferred_graphs.lock().unwrap();
1739 if !held.contains_key(&key) && held.len() >= ceiling {
1740 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1741 }
1742 }
1743 if let Some(ceiling) = *self.deferred_graph_byte_ceiling.lock().unwrap() {
1744 let held = self.deferred_graphs.lock().unwrap();
1745 let others: usize = held
1746 .iter()
1747 .filter(|(k, _)| **k != key)
1748 .map(|(_, r)| r.byte_size())
1749 .sum();
1750 if others + record.byte_size() > ceiling {
1751 return Ok(crate::gasp::DeferredGraphSave::AtCeiling);
1752 }
1753 }
1754 self.fault_memos.lock().unwrap().remove(&key);
1755 *self.deferred_graph_writes.lock().unwrap() += 1;
1756 self.deferred_graphs.lock().unwrap().insert(
1757 (
1758 record.peer.clone(),
1759 record.topic.clone(),
1760 record.outpoint.clone(),
1761 ),
1762 record.clone(),
1763 );
1764 Ok(crate::gasp::DeferredGraphSave::Saved)
1765 }
1766
1767 async fn reserve_unanchorable_retry(
1768 &self,
1769 record: &crate::gasp::DeferredGraph,
1770 ) -> Result<bool, StorageError> {
1771 *self.deferred_graph_put_attempts.lock().unwrap() += 1;
1772 if *self.deferred_graph_put_fault.lock().unwrap() {
1773 return Err(StorageError::Database("retry reservation faulted".into()));
1774 }
1775 let key = (
1776 record.peer.clone(),
1777 record.topic.clone(),
1778 record.outpoint.clone(),
1779 );
1780 let mut held = self.fault_memos.lock().unwrap();
1781 let Some(prior) = held.get_mut(&key) else {
1782 return Ok(false);
1783 };
1784 let now = (crate::engine::current_timestamp_ms() / 1000.0) as u64;
1785 if prior.dead
1786 || !prior.unanchorable
1787 || record.fault_class() != Some(prior.class.as_str())
1788 || prior.score != record.score
1789 || prior.next_at > now
1790 || prior.retries.checked_add(1) != Some(record.unanchorable_retries)
1791 || record.unanchorable_retries > crate::gasp::UNANCHORABLE_RETRIES
1792 {
1793 return Ok(false);
1794 }
1795 prior.retries = record.unanchorable_retries;
1796 prior.next_at = record.retry_at;
1797 *self.deferred_graph_writes.lock().unwrap() += 1;
1798 Ok(true)
1799 }
1800
1801 async fn find_deferred_graphs(
1802 &self,
1803 host: &str,
1804 topic: &str,
1805 ) -> Result<Vec<crate::gasp::DeferredGraphKey>, StorageError> {
1806 if *self.deferred_graph_keys_fault.lock().unwrap() {
1807 return Err(StorageError::Database("deferred graph keys: fault".into()));
1808 }
1809 Ok(self
1810 .deferred_graphs()
1811 .into_iter()
1812 .filter(|r| r.peer == host && r.topic == topic)
1813 .map(|r| crate::gasp::DeferredGraphKey {
1814 outpoint: r.outpoint,
1815 score: r.score,
1816 })
1817 .collect())
1818 }
1819
1820 async fn get_deferred_graph(
1821 &self,
1822 host: &str,
1823 topic: &str,
1824 outpoint: &str,
1825 ) -> Result<Option<crate::gasp::DeferredGraph>, StorageError> {
1826 *self.deferred_graph_reads.lock().unwrap() += 1;
1827 if let Some(memo) = self
1828 .fault_memos
1829 .lock()
1830 .unwrap()
1831 .get(&(host.into(), topic.into(), outpoint.into()))
1832 .cloned()
1833 {
1834 return Ok(Some(memo.into_record()));
1835 }
1836 Ok(self
1837 .deferred_graphs
1838 .lock()
1839 .unwrap()
1840 .get(&(host.to_string(), topic.to_string(), outpoint.to_string()))
1841 .cloned())
1842 }
1843
1844 async fn delete_deferred_graph(
1845 &self,
1846 host: &str,
1847 topic: &str,
1848 outpoint: &str,
1849 ) -> Result<(), StorageError> {
1850 self.fault_memos
1851 .lock()
1852 .unwrap()
1853 .remove(&(host.into(), topic.into(), outpoint.into()));
1854 self.deferred_graphs.lock().unwrap().remove(&(
1855 host.to_string(),
1856 topic.to_string(),
1857 outpoint.to_string(),
1858 ));
1859 Ok(())
1860 }
1861
1862 async fn remember_expired_outpoint(
1863 &self,
1864 topic: &str,
1865 outpoint: &str,
1866 expiry: u64,
1867 ) -> Result<(), StorageError> {
1868 let now = *self.clock_secs.lock().unwrap();
1869 let mut held = self.expired_outpoints.lock().unwrap();
1870 held.retain(|_, (keep, _)| *keep > now);
1871 held.insert(
1872 (topic.to_string(), outpoint.to_string()),
1873 (
1874 expiry.max(now) + crate::gasp::EXPIRED_OUTPOINT_MEMO_SECS,
1875 now,
1876 ),
1877 );
1878 let mut of_topic: Vec<(u64, String)> = held
1880 .iter()
1881 .filter(|((t, _), _)| t == topic)
1882 .map(|((_, o), (_, at))| (*at, o.clone()))
1883 .collect();
1884 let cap = crate::gasp::EXPIRED_OUTPOINT_MEMO_MAX_ROWS;
1885 if of_topic.len() > cap {
1886 of_topic.sort();
1887 for (_, o) in &of_topic[..of_topic.len() - cap] {
1888 held.remove(&(topic.to_string(), o.clone()));
1889 }
1890 }
1891 Ok(())
1892 }
1893
1894 async fn find_expired_outpoints(
1895 &self,
1896 topic: &str,
1897 outpoints: &[String],
1898 ) -> Result<Vec<String>, StorageError> {
1899 let now = *self.clock_secs.lock().unwrap();
1900 let held = self.expired_outpoints.lock().unwrap();
1901 Ok(outpoints
1902 .iter()
1903 .filter(|o| {
1904 held.get(&(topic.to_string(), (*o).clone()))
1905 .is_some_and(|(keep, _)| *keep > now)
1906 })
1907 .cloned()
1908 .collect())
1909 }
1910 }
1911}
1912
1913#[cfg(test)]
1918mod tests {
1919 use super::memory::MemoryStorage;
1920 use super::*;
1921 use crate::types::AppliedTransaction;
1922
1923 #[tokio::test]
1928 async fn the_rc_blanket_forwards_the_anchored_proven_latch() {
1929 let store = std::rc::Rc::new(MemoryStorage::new());
1930 let via_dyn: std::rc::Rc<dyn Storage> = store.clone();
1931 via_dyn
1932 .mark_transaction_proven_at("tx", Some(965_772))
1933 .await
1934 .unwrap();
1935 assert_eq!(
1936 store.proven_at_height("tx"),
1937 Some(Some(965_772)),
1938 "the Rc blanket forwarded the height to the inner store"
1939 );
1940 via_dyn
1942 .mark_transaction_proven_at("tx2", None)
1943 .await
1944 .unwrap();
1945 assert_eq!(store.proven_at_height("tx2"), Some(None));
1946 assert_eq!(
1947 store.proven_at_height("never"),
1948 None,
1949 "a txid never latched is absent"
1950 );
1951 }
1952
1953 fn make_output(txid: &str, index: u32, topic: &str, score: f64) -> Output {
1954 Output {
1955 txid: txid.to_string(),
1956 output_index: index,
1957 output_script: vec![0x76, 0xa9],
1958 satoshis: 1000,
1959 topic: topic.to_string(),
1960 spent: false,
1961 outputs_consumed: vec![],
1962 consumed_by: vec![],
1963 beef: Some(vec![0xBE, 0xEF]),
1964 block_height: None,
1965 score: Some(score),
1966 }
1967 }
1968
1969 #[tokio::test]
1970 async fn test_insert_and_find_output() {
1971 let store = MemoryStorage::new();
1972 let output = make_output("abc", 0, "tm_test", 1.0);
1973 store.insert_output(&output).await.unwrap();
1974
1975 let found = store
1977 .find_output("abc", 0, Some("tm_test"), None, false)
1978 .await
1979 .unwrap()
1980 .unwrap();
1981 assert_eq!(found.txid, "abc");
1982 assert_eq!(found.output_index, 0);
1983 assert!(found.beef.is_none());
1984
1985 let found = store
1987 .find_output("abc", 0, Some("tm_test"), None, true)
1988 .await
1989 .unwrap()
1990 .unwrap();
1991 assert_eq!(found.beef.unwrap(), vec![0xBE, 0xEF]);
1992 }
1993
1994 #[tokio::test]
1995 async fn test_insert_duplicate_is_noop() {
1996 let store = MemoryStorage::new();
1997 let output = make_output("abc", 0, "tm_test", 1.0);
1998 store.insert_output(&output).await.unwrap();
1999 store.insert_output(&output).await.unwrap();
2000 assert_eq!(store.output_count(), 1);
2001 }
2002
2003 #[tokio::test]
2004 async fn test_delete_output_cleans_up_beef() {
2005 let store = MemoryStorage::new();
2006 let output = make_output("abc", 0, "tm_test", 1.0);
2007 store.insert_output(&output).await.unwrap();
2008 assert_eq!(store.transaction_count(), 1);
2009
2010 store.delete_output("abc", 0, "tm_test").await.unwrap();
2011 assert_eq!(store.output_count(), 0);
2012 assert_eq!(store.transaction_count(), 0); }
2014
2015 #[tokio::test]
2016 async fn test_delete_output_keeps_beef_if_other_outputs_exist() {
2017 let store = MemoryStorage::new();
2018 store
2019 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2020 .await
2021 .unwrap();
2022 store
2023 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
2024 .await
2025 .unwrap();
2026 assert_eq!(store.output_count(), 2);
2027 assert_eq!(store.transaction_count(), 1);
2028
2029 store.delete_output("abc", 0, "tm_test").await.unwrap();
2030 assert_eq!(store.output_count(), 1);
2031 assert_eq!(store.transaction_count(), 1); }
2033
2034 #[tokio::test]
2035 async fn test_mark_utxo_as_spent() {
2036 let store = MemoryStorage::new();
2037 store
2038 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2039 .await
2040 .unwrap();
2041
2042 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
2043
2044 let found = store
2045 .find_output("abc", 0, Some("tm_test"), None, false)
2046 .await
2047 .unwrap()
2048 .unwrap();
2049 assert!(found.spent);
2050 }
2051
2052 #[tokio::test]
2053 async fn test_find_output_with_spent_filter() {
2054 let store = MemoryStorage::new();
2055 store
2056 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2057 .await
2058 .unwrap();
2059
2060 assert!(store
2062 .find_output("abc", 0, Some("tm_test"), Some(false), false)
2063 .await
2064 .unwrap()
2065 .is_some());
2066 assert!(store
2067 .find_output("abc", 0, Some("tm_test"), Some(true), false)
2068 .await
2069 .unwrap()
2070 .is_none());
2071
2072 store.mark_utxo_as_spent("abc", 0, "tm_test").await.unwrap();
2073
2074 assert!(store
2076 .find_output("abc", 0, Some("tm_test"), Some(true), false)
2077 .await
2078 .unwrap()
2079 .is_some());
2080 assert!(store
2081 .find_output("abc", 0, Some("tm_test"), Some(false), false)
2082 .await
2083 .unwrap()
2084 .is_none());
2085 }
2086
2087 #[tokio::test]
2088 async fn test_update_consumed_by() {
2089 let store = MemoryStorage::new();
2090 store
2091 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2092 .await
2093 .unwrap();
2094
2095 let consumed = vec![Outpoint::new("def", 0), Outpoint::new("ghi", 1)];
2096 store
2097 .update_consumed_by("abc", 0, "tm_test", &consumed)
2098 .await
2099 .unwrap();
2100
2101 let found = store
2102 .find_output("abc", 0, Some("tm_test"), None, false)
2103 .await
2104 .unwrap()
2105 .unwrap();
2106 assert_eq!(found.consumed_by.len(), 2);
2107 assert_eq!(found.consumed_by[0].txid, "def");
2108 assert_eq!(found.consumed_by[1].output_index, 1);
2109 }
2110
2111 #[tokio::test]
2112 async fn test_update_transaction_beef() {
2113 let store = MemoryStorage::new();
2114 store
2115 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2116 .await
2117 .unwrap();
2118
2119 store
2120 .update_transaction_beef("abc", &[0xDE, 0xAD])
2121 .await
2122 .unwrap();
2123
2124 let found = store
2125 .find_output("abc", 0, Some("tm_test"), None, true)
2126 .await
2127 .unwrap()
2128 .unwrap();
2129 assert_eq!(found.beef.unwrap(), vec![0xDE, 0xAD]);
2130 }
2131
2132 #[tokio::test]
2133 async fn test_update_output_block_height() {
2134 let store = MemoryStorage::new();
2135 store
2136 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2137 .await
2138 .unwrap();
2139
2140 store
2141 .update_output_block_height("abc", 0, "tm_test", 850_000)
2142 .await
2143 .unwrap();
2144
2145 let found = store
2146 .find_output("abc", 0, Some("tm_test"), None, false)
2147 .await
2148 .unwrap()
2149 .unwrap();
2150 assert_eq!(found.block_height, Some(850_000));
2151 }
2152
2153 #[tokio::test]
2154 async fn test_find_outputs_for_transaction() {
2155 let store = MemoryStorage::new();
2156 store
2157 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2158 .await
2159 .unwrap();
2160 store
2161 .insert_output(&make_output("abc", 1, "tm_test", 2.0))
2162 .await
2163 .unwrap();
2164 store
2165 .insert_output(&make_output("other", 0, "tm_test", 3.0))
2166 .await
2167 .unwrap();
2168
2169 let results = store
2170 .find_outputs_for_transaction("abc", false)
2171 .await
2172 .unwrap();
2173 assert_eq!(results.len(), 2);
2174 }
2175
2176 #[tokio::test]
2177 async fn test_find_utxos_for_topic() {
2178 let store = MemoryStorage::new();
2179 store
2180 .insert_output(&make_output("a", 0, "tm_test", 1.0))
2181 .await
2182 .unwrap();
2183 store
2184 .insert_output(&make_output("b", 0, "tm_test", 2.0))
2185 .await
2186 .unwrap();
2187 store
2188 .insert_output(&make_output("c", 0, "tm_test", 3.0))
2189 .await
2190 .unwrap();
2191 store
2192 .insert_output(&make_output("d", 0, "tm_other", 4.0))
2193 .await
2194 .unwrap();
2195
2196 store.mark_utxo_as_spent("b", 0, "tm_test").await.unwrap();
2198
2199 let results = store
2201 .find_utxos_for_topic("tm_test", None, None, false)
2202 .await
2203 .unwrap();
2204 assert_eq!(results.len(), 2); let results = store
2208 .find_utxos_for_topic("tm_test", Some(2.0), None, false)
2209 .await
2210 .unwrap();
2211 assert_eq!(results.len(), 1); let results = store
2215 .find_utxos_for_topic("tm_test", None, Some(1), false)
2216 .await
2217 .unwrap();
2218 assert_eq!(results.len(), 1);
2219 assert_eq!(results[0].txid, "a"); }
2221
2222 #[tokio::test]
2223 async fn test_find_utxos_for_topic_sorted_by_score() {
2224 let store = MemoryStorage::new();
2225 store
2226 .insert_output(&make_output("c", 0, "tm_test", 30.0))
2227 .await
2228 .unwrap();
2229 store
2230 .insert_output(&make_output("a", 0, "tm_test", 10.0))
2231 .await
2232 .unwrap();
2233 store
2234 .insert_output(&make_output("b", 0, "tm_test", 20.0))
2235 .await
2236 .unwrap();
2237
2238 let results = store
2239 .find_utxos_for_topic("tm_test", None, None, false)
2240 .await
2241 .unwrap();
2242 assert_eq!(results[0].txid, "a");
2243 assert_eq!(results[1].txid, "b");
2244 assert_eq!(results[2].txid, "c");
2245 }
2246
2247 #[tokio::test]
2248 async fn test_applied_transaction() {
2249 let store = MemoryStorage::new();
2250 let tx = AppliedTransaction {
2251 txid: "abc".to_string(),
2252 topic: "tm_test".to_string(),
2253 };
2254
2255 assert!(!store.does_applied_transaction_exist(&tx).await.unwrap());
2256 store.insert_applied_transaction(&tx).await.unwrap();
2257 assert!(store.does_applied_transaction_exist(&tx).await.unwrap());
2258
2259 let tx2 = AppliedTransaction {
2261 txid: "abc".to_string(),
2262 topic: "tm_other".to_string(),
2263 };
2264 assert!(!store.does_applied_transaction_exist(&tx2).await.unwrap());
2265 }
2266
2267 #[tokio::test]
2268 async fn test_sync_state() {
2269 let store = MemoryStorage::new();
2270
2271 assert_eq!(
2272 store
2273 .get_last_interaction("host1", "tm_test")
2274 .await
2275 .unwrap(),
2276 0
2277 );
2278
2279 store
2280 .update_last_interaction("host1", "tm_test", 100)
2281 .await
2282 .unwrap();
2283 assert_eq!(
2284 store
2285 .get_last_interaction("host1", "tm_test")
2286 .await
2287 .unwrap(),
2288 100
2289 );
2290
2291 store
2293 .update_last_interaction("host1", "tm_test", 200)
2294 .await
2295 .unwrap();
2296 assert_eq!(
2297 store
2298 .get_last_interaction("host1", "tm_test")
2299 .await
2300 .unwrap(),
2301 200
2302 );
2303
2304 assert_eq!(
2306 store
2307 .get_last_interaction("host2", "tm_test")
2308 .await
2309 .unwrap(),
2310 0
2311 );
2312 }
2313
2314 #[tokio::test]
2315 async fn test_find_outputs_by_outpoints_batch() {
2316 let store = MemoryStorage::new();
2317 store
2318 .insert_output(&make_output("a", 0, "tm_test", 1.0))
2319 .await
2320 .unwrap();
2321 store
2322 .insert_output(&make_output("b", 0, "tm_test", 2.0))
2323 .await
2324 .unwrap();
2325 store
2326 .insert_output(&make_output("c", 0, "tm_test", 3.0))
2327 .await
2328 .unwrap();
2329
2330 let outpoints = vec![
2331 Outpoint::new("a", 0),
2332 Outpoint::new("c", 0),
2333 Outpoint::new("missing", 0),
2334 ];
2335 let results = store
2336 .find_outputs_by_outpoints(&outpoints, false)
2337 .await
2338 .unwrap();
2339 assert_eq!(results.len(), 2); }
2341
2342 #[tokio::test]
2343 async fn test_find_output_without_topic() {
2344 let store = MemoryStorage::new();
2345 store
2346 .insert_output(&make_output("abc", 0, "tm_test", 1.0))
2347 .await
2348 .unwrap();
2349
2350 let found = store
2352 .find_output("abc", 0, None, None, false)
2353 .await
2354 .unwrap();
2355 assert!(found.is_some());
2356 assert_eq!(found.unwrap().topic, "tm_test");
2357 }
2358}