Skip to main content

bsv_wallet_toolbox/monitor/tasks/
task_check_no_sends.rs

1//! TaskCheckNoSends -- processes nosend transactions by collecting proofs.
2//!
3//! Translated from wallet-toolbox/src/monitor/tasks/TaskCheckNoSends.ts.
4//!
5//! Unlike intentionally processed transactions, 'nosend' transactions are fully valid
6//! transactions which have not been processed by the wallet. This task periodically
7//! checks if any 'nosend' transaction has managed to get mined externally.
8
9use std::sync::atomic::{AtomicU32, Ordering};
10use std::sync::Arc;
11
12use async_trait::async_trait;
13
14use crate::error::WalletError;
15use crate::monitor::helpers::now_msecs;
16use crate::monitor::task_trait::WalletMonitorTask;
17use crate::monitor::ONE_DAY;
18use crate::services::traits::WalletServices;
19use crate::status::ProvenTxReqStatus;
20use crate::storage::find_args::{FindProvenTxReqsArgs, Paged, ProvenTxReqPartial};
21use crate::storage::manager::WalletStorageManager;
22use crate::storage::traits::reader::StorageReader;
23use crate::types::Chain;
24
25/// Task that retrieves merkle proofs for 'nosend' transactions.
26pub struct TaskCheckNoSends {
27    storage: WalletStorageManager,
28    services: Arc<dyn WalletServices>,
29    chain: Chain,
30    trigger_msecs: u64,
31    last_run_msecs: u64,
32    unproven_attempts_limit: u32,
33    /// Manual trigger flag.
34    pub check_now: bool,
35    /// Shared last header height (u32::MAX = unknown). Same as TaskCheckForProofs.
36    last_new_header_height: Arc<AtomicU32>,
37}
38
39impl TaskCheckNoSends {
40    /// Create a new no-sends checking task.
41    pub fn new(
42        storage: WalletStorageManager,
43        services: Arc<dyn WalletServices>,
44        chain: Chain,
45        unproven_attempts_limit: u32,
46        last_new_header_height: Arc<AtomicU32>,
47    ) -> Self {
48        Self {
49            storage,
50            services,
51            chain,
52            trigger_msecs: ONE_DAY,
53            last_run_msecs: 0,
54            unproven_attempts_limit,
55            check_now: false,
56            last_new_header_height,
57        }
58    }
59
60    /// Set the trigger interval in milliseconds.
61    pub fn with_trigger_msecs(mut self, msecs: u64) -> Self {
62        self.trigger_msecs = msecs;
63        self
64    }
65}
66
67#[async_trait]
68impl WalletMonitorTask for TaskCheckNoSends {
69    fn name(&self) -> &str {
70        "CheckNoSends"
71    }
72
73    fn trigger(&mut self, now_msecs: u64) -> bool {
74        self.check_now
75            || (self.trigger_msecs > 0 && now_msecs > self.last_run_msecs + self.trigger_msecs)
76    }
77
78    async fn run_task(&mut self) -> Result<String, WalletError> {
79        self.last_run_msecs = now_msecs();
80        let counts_as_attempt = self.check_now;
81        self.check_now = false;
82
83        let mut log = String::new();
84
85        // Match TS: skip proof checking when no header height is known.
86        let raw_height = self.last_new_header_height.load(Ordering::SeqCst);
87        let max_acceptable_height = if raw_height == u32::MAX {
88            return Ok(log);
89        } else {
90            Some(raw_height)
91        };
92
93        let limit = 100i64;
94        let mut offset = 0i64;
95        loop {
96            let reqs = self
97                .storage
98                .find_proven_tx_reqs(&FindProvenTxReqsArgs {
99                    partial: ProvenTxReqPartial::default(),
100                    since: None,
101                    paged: Some(Paged { limit, offset }),
102                    statuses: Some(vec![ProvenTxReqStatus::Nosend]),
103                })
104                .await?;
105
106            if reqs.is_empty() {
107                break;
108            }
109
110            log.push_str(&format!("{} reqs with status 'nosend'\n", reqs.len()));
111
112            // Call get_proofs helper to attempt proof collection
113            let r = crate::monitor::helpers::get_proofs(
114                &self.storage,
115                &*self.services,
116                &reqs,
117                &self.chain,
118                self.unproven_attempts_limit,
119                counts_as_attempt,
120                max_acceptable_height,
121            )
122            .await?;
123            log.push_str(&r.log);
124            log.push('\n');
125
126            if (reqs.len() as i64) < limit {
127                break;
128            }
129            offset += limit;
130        }
131
132        Ok(log)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use crate::monitor::ONE_DAY;
139
140    #[test]
141    fn test_check_no_sends_defaults() {
142        assert_eq!(ONE_DAY, 86_400_000);
143    }
144
145    #[test]
146    fn test_name() {
147        assert_eq!("CheckNoSends", "CheckNoSends");
148    }
149}