devimint/
recurringd.rs

1use std::collections::HashMap;
2use std::ops::ControlFlow;
3use std::path::PathBuf;
4
5use anyhow::Result;
6use fedimint_core::task::sleep;
7use fedimint_core::util::SafeUrl;
8use reqwest::get;
9use tracing::info;
10use {reqwest, serde_json};
11
12use crate::cmd;
13use crate::envs::{
14    FM_RECURRING_API_ADDRESS_ENV, FM_RECURRING_API_BEARER_TOKEN_ENV, FM_RECURRING_BIND_ADDRESS_ENV,
15    FM_RECURRING_DATA_DIR_ENV,
16};
17use crate::util::{ProcessHandle, ProcessManager, poll};
18
19#[derive(Clone)]
20pub struct Recurringd {
21    pub(crate) process: ProcessHandle,
22    pub addr: String,
23    pub api_url: SafeUrl,
24    pub log_path: PathBuf,
25}
26
27impl Recurringd {
28    pub async fn new(process_mgr: &ProcessManager) -> Result<Self> {
29        let test_dir = &process_mgr.globals.FM_TEST_DIR;
30        let port = process_mgr.globals.FM_PORT_RECURRINGD;
31        let bind_address = format!("127.0.0.1:{port}");
32        let api_url = SafeUrl::parse(&format!("http://{bind_address}/")).expect("Valid URL");
33
34        // Default bearer token for development
35        let bearer_token = "devimint-recurring-token";
36
37        let recurring_env: HashMap<String, String> = HashMap::from_iter([
38            (
39                FM_RECURRING_DATA_DIR_ENV.to_owned(),
40                format!("{}/recurringd", test_dir.display()),
41            ),
42            (
43                FM_RECURRING_BIND_ADDRESS_ENV.to_owned(),
44                bind_address.clone(),
45            ),
46            (FM_RECURRING_API_ADDRESS_ENV.to_owned(), api_url.to_string()),
47            (
48                FM_RECURRING_API_BEARER_TOKEN_ENV.to_owned(),
49                bearer_token.to_string(),
50            ),
51        ]);
52
53        let process = process_mgr
54            .spawn_daemon(
55                "recurringd",
56                cmd!("fedimint-recurringd")
57                    .arg(&"--encryption-key")
58                    .arg(&"01234567890123456789012345678901")
59                    .envs(recurring_env),
60            )
61            .await?;
62
63        let log_path = process_mgr.globals.FM_LOGS_DIR.join("recurringd.log");
64
65        let recurringd = Self {
66            process,
67            addr: bind_address,
68            api_url,
69            log_path,
70        };
71
72        // Poll to ensure the service is ready by checking the /federations endpoint
73        poll("waiting for recurringd to be ready", || async {
74            match get(format!("http://{}/lnv1/federations", recurringd.addr)).await {
75                Ok(response) if response.status().is_success() => Ok(()),
76                _ => {
77                    sleep(tokio::time::Duration::from_millis(100)).await;
78                    Err(ControlFlow::Continue(anyhow::anyhow!(
79                        "recurringd not ready yet"
80                    )))
81                }
82            }
83        })
84        .await?;
85
86        info!("Recurringd started at {}", recurringd.addr);
87        Ok(recurringd)
88    }
89
90    pub async fn terminate(self) -> Result<()> {
91        self.process.terminate().await
92    }
93
94    // Add a federation to recurringd
95    pub async fn add_federation(&self, invite_code: &str) -> Result<String> {
96        let url = format!("http://{}/lnv1/federations", self.addr);
97        let client = reqwest::Client::new();
98        let response = client
99            .put(&url)
100            .header("Authorization", "Bearer devimint-recurring-token")
101            .header("Content-Type", "application/json")
102            .json(&serde_json::json!({ "invite": invite_code }))
103            .send()
104            .await?;
105
106        Ok(response.text().await?)
107    }
108
109    // List federations registered with recurringd
110    pub async fn list_federations(&self) -> Result<String> {
111        let url = format!("http://{}/lnv1/federations", self.addr);
112        let client = reqwest::Client::new();
113        let response = client
114            .get(&url)
115            .header("Authorization", "Bearer devimint-recurring-token")
116            .send()
117            .await?;
118
119        Ok(response.text().await?)
120    }
121
122    pub fn api_url(&self) -> SafeUrl {
123        self.api_url.clone()
124    }
125}