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 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").envs(recurring_env),
57 )
58 .await?;
59
60 let log_path = process_mgr.globals.FM_LOGS_DIR.join("recurringd.log");
61
62 let recurringd = Self {
63 process,
64 addr: bind_address,
65 api_url,
66 log_path,
67 };
68
69 poll("waiting for recurringd to be ready", || async {
71 match get(format!("http://{}/lnv1/federations", recurringd.addr)).await {
72 Ok(response) if response.status().is_success() => Ok(()),
73 _ => {
74 sleep(tokio::time::Duration::from_millis(100)).await;
75 Err(ControlFlow::Continue(anyhow::anyhow!(
76 "recurringd not ready yet"
77 )))
78 }
79 }
80 })
81 .await?;
82
83 info!("Recurringd started at {}", recurringd.addr);
84 Ok(recurringd)
85 }
86
87 pub async fn terminate(self) -> Result<()> {
88 self.process.terminate().await
89 }
90
91 pub async fn add_federation(&self, invite_code: &str) -> Result<String> {
93 let url = format!("http://{}/lnv1/federations", self.addr);
94 let client = reqwest::Client::new();
95 let response = client
96 .put(&url)
97 .header("Authorization", "Bearer devimint-recurring-token")
98 .header("Content-Type", "application/json")
99 .json(&serde_json::json!({ "invite": invite_code }))
100 .send()
101 .await?;
102
103 Ok(response.text().await?)
104 }
105
106 pub async fn list_federations(&self) -> Result<String> {
108 let url = format!("http://{}/lnv1/federations", self.addr);
109 let client = reqwest::Client::new();
110 let response = client
111 .get(&url)
112 .header("Authorization", "Bearer devimint-recurring-token")
113 .send()
114 .await?;
115
116 Ok(response.text().await?)
117 }
118
119 pub fn api_url(&self) -> SafeUrl {
120 self.api_url.clone()
121 }
122}