Skip to main content

bsv_wallet_toolbox_rs/monitor/
config.rs

1//! Monitor configuration types.
2
3use std::sync::Arc;
4use std::time::Duration;
5
6use serde::{Deserialize, Serialize};
7
8/// Status update for a transaction event.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct TransactionStatusUpdate {
12    /// Transaction ID.
13    pub txid: String,
14    /// New status of the transaction.
15    pub status: String,
16    /// Merkle root if available.
17    pub merkle_root: Option<String>,
18    /// Encoded merkle path if available.
19    pub merkle_path: Option<String>,
20    /// Block height if mined.
21    pub block_height: Option<u32>,
22    /// Block hash if mined.
23    pub block_hash: Option<String>,
24}
25
26/// Configuration options for the Monitor daemon.
27#[derive(Clone)]
28pub struct MonitorOptions {
29    /// Configuration for each task.
30    pub tasks: TasksConfig,
31    /// Minimum age for transactions to be considered abandoned.
32    pub fail_abandoned_timeout: Duration,
33    /// Callback invoked when a transaction has been broadcast.
34    pub on_tx_broadcasted: Option<Arc<dyn Fn(TransactionStatusUpdate) + Send + Sync>>,
35    /// Callback invoked when a transaction proof has been obtained.
36    pub on_tx_proven: Option<Arc<dyn Fn(TransactionStatusUpdate) + Send + Sync>>,
37}
38
39impl std::fmt::Debug for MonitorOptions {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.debug_struct("MonitorOptions")
42            .field("tasks", &self.tasks)
43            .field("fail_abandoned_timeout", &self.fail_abandoned_timeout)
44            .field(
45                "on_tx_broadcasted",
46                &self.on_tx_broadcasted.as_ref().map(|_| "Some(<callback>)"),
47            )
48            .field(
49                "on_tx_proven",
50                &self.on_tx_proven.as_ref().map(|_| "Some(<callback>)"),
51            )
52            .finish()
53    }
54}
55
56impl Default for MonitorOptions {
57    fn default() -> Self {
58        Self {
59            tasks: TasksConfig::default(),
60            fail_abandoned_timeout: Duration::from_secs(5 * 60), // 5 minutes (matches TS/Go)
61            on_tx_broadcasted: None,
62            on_tx_proven: None,
63        }
64    }
65}
66
67/// Configuration for all monitor tasks.
68#[derive(Debug, Clone)]
69pub struct TasksConfig {
70    /// Check for proofs task configuration.
71    pub check_for_proofs: TaskConfig,
72    /// Send waiting transactions task configuration.
73    pub send_waiting: TaskConfig,
74    /// Fail abandoned transactions task configuration.
75    pub fail_abandoned: TaskConfig,
76    /// UnFail transactions task configuration.
77    pub unfail: TaskConfig,
78    /// Clock tick task configuration.
79    pub clock: TaskConfig,
80    /// New header polling task configuration.
81    pub new_header: TaskConfig,
82    /// Blockchain reorganization task configuration.
83    pub reorg: TaskConfig,
84    /// Check no-send transactions task configuration.
85    pub check_no_sends: TaskConfig,
86    /// Review transaction status task configuration.
87    pub review_status: TaskConfig,
88    /// Purge old data task configuration.
89    pub purge: TaskConfig,
90    /// Compact stored input_beef blobs task configuration.
91    pub compact_beef: TaskConfig,
92    /// Monitor service call history task configuration.
93    pub monitor_call_history: TaskConfig,
94    /// Sync-when-idle task configuration.
95    pub sync_when_idle: TaskConfig,
96}
97
98impl Default for TasksConfig {
99    fn default() -> Self {
100        Self {
101            check_for_proofs: TaskConfig {
102                enabled: true,
103                interval: Duration::from_secs(60), // 1 minute
104                start_immediately: false,
105            },
106            send_waiting: TaskConfig {
107                enabled: true,
108                interval: Duration::from_secs(5 * 60), // 5 minutes
109                start_immediately: true,
110            },
111            fail_abandoned: TaskConfig {
112                enabled: true,
113                interval: Duration::from_secs(5 * 60), // 5 minutes
114                start_immediately: false,
115            },
116            unfail: TaskConfig {
117                enabled: true,
118                interval: Duration::from_secs(10 * 60), // 10 minutes
119                start_immediately: false,
120            },
121            clock: TaskConfig {
122                enabled: true,
123                interval: Duration::from_secs(1), // 1 second
124                start_immediately: true,
125            },
126            new_header: TaskConfig {
127                enabled: true,
128                interval: Duration::from_secs(60), // 1 minute
129                start_immediately: false,
130            },
131            reorg: TaskConfig {
132                enabled: true,
133                interval: Duration::from_secs(60), // 1 minute
134                start_immediately: false,
135            },
136            check_no_sends: TaskConfig {
137                enabled: true,
138                interval: Duration::from_secs(86400), // 24 hours
139                start_immediately: false,
140            },
141            review_status: TaskConfig {
142                enabled: true,
143                interval: Duration::from_secs(900), // 15 minutes
144                start_immediately: false,
145            },
146            purge: TaskConfig {
147                enabled: true,
148                interval: Duration::from_secs(3600), // 1 hour
149                start_immediately: false,
150            },
151            compact_beef: TaskConfig {
152                enabled: true,
153                interval: Duration::from_secs(15 * 60), // 15 minutes
154                start_immediately: false,
155            },
156            monitor_call_history: TaskConfig {
157                enabled: true,
158                interval: Duration::from_secs(720), // 12 minutes
159                start_immediately: false,
160            },
161            sync_when_idle: TaskConfig {
162                enabled: true,
163                interval: Duration::from_secs(60), // 1 minute
164                start_immediately: false,
165            },
166        }
167    }
168}
169
170/// Configuration for a single monitor task.
171#[derive(Debug, Clone)]
172pub struct TaskConfig {
173    /// Whether this task is enabled.
174    pub enabled: bool,
175    /// How often to run this task.
176    pub interval: Duration,
177    /// Whether to run immediately on start.
178    pub start_immediately: bool,
179}
180
181impl TaskConfig {
182    /// Create a new enabled task config with the given interval.
183    pub fn new(interval: Duration) -> Self {
184        Self {
185            enabled: true,
186            interval,
187            start_immediately: false,
188        }
189    }
190
191    /// Create a disabled task config.
192    pub fn disabled() -> Self {
193        Self {
194            enabled: false,
195            interval: Duration::from_secs(60),
196            start_immediately: false,
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_default_options() {
207        let opts = MonitorOptions::default();
208        assert!(opts.tasks.check_for_proofs.enabled);
209        assert!(opts.tasks.send_waiting.enabled);
210        assert!(opts.tasks.fail_abandoned.enabled);
211        assert!(opts.tasks.unfail.enabled);
212        assert!(opts.tasks.clock.enabled);
213        assert!(opts.tasks.new_header.enabled);
214        assert!(opts.tasks.reorg.enabled);
215        assert!(opts.tasks.check_no_sends.enabled);
216        assert!(opts.tasks.review_status.enabled);
217        assert!(opts.tasks.purge.enabled);
218        assert!(opts.tasks.monitor_call_history.enabled);
219        assert!(opts.tasks.sync_when_idle.enabled);
220        assert_eq!(opts.fail_abandoned_timeout, Duration::from_secs(5 * 60));
221        assert!(opts.on_tx_broadcasted.is_none());
222        assert!(opts.on_tx_proven.is_none());
223    }
224
225    #[test]
226    fn test_default_task_intervals() {
227        let opts = MonitorOptions::default();
228        assert_eq!(opts.tasks.clock.interval, Duration::from_secs(1));
229        assert!(opts.tasks.clock.start_immediately);
230        assert_eq!(opts.tasks.new_header.interval, Duration::from_secs(60));
231        assert!(!opts.tasks.new_header.start_immediately);
232        assert_eq!(opts.tasks.reorg.interval, Duration::from_secs(60));
233        assert_eq!(
234            opts.tasks.check_no_sends.interval,
235            Duration::from_secs(86400)
236        );
237        assert_eq!(opts.tasks.review_status.interval, Duration::from_secs(900));
238        assert_eq!(opts.tasks.purge.interval, Duration::from_secs(3600));
239        assert_eq!(
240            opts.tasks.monitor_call_history.interval,
241            Duration::from_secs(720)
242        );
243        assert_eq!(opts.tasks.sync_when_idle.interval, Duration::from_secs(60));
244    }
245
246    #[test]
247    fn test_task_config_new() {
248        let config = TaskConfig::new(Duration::from_secs(30));
249        assert!(config.enabled);
250        assert_eq!(config.interval, Duration::from_secs(30));
251        assert!(!config.start_immediately);
252    }
253
254    #[test]
255    fn test_task_config_disabled() {
256        let config = TaskConfig::disabled();
257        assert!(!config.enabled);
258    }
259
260    #[test]
261    fn test_transaction_status_update() {
262        let update = TransactionStatusUpdate {
263            txid: "abc123".to_string(),
264            status: "completed".to_string(),
265            merkle_root: Some("root".to_string()),
266            merkle_path: Some("path".to_string()),
267            block_height: Some(800000),
268            block_hash: Some("hash".to_string()),
269        };
270        assert_eq!(update.txid, "abc123");
271        assert_eq!(update.status, "completed");
272        assert_eq!(update.block_height, Some(800000));
273    }
274
275    #[test]
276    fn test_monitor_options_with_callbacks() {
277        let opts = MonitorOptions {
278            on_tx_broadcasted: Some(Arc::new(|_update| {
279                // callback
280            })),
281            on_tx_proven: Some(Arc::new(|_update| {
282                // callback
283            })),
284            ..MonitorOptions::default()
285        };
286        assert!(opts.on_tx_broadcasted.is_some());
287        assert!(opts.on_tx_proven.is_some());
288    }
289}