mint_module_tests/
mint-module-tests.rs1use anyhow::{Context as _, Result};
2use clap::Parser;
3use devimint::cmd;
4use devimint::federation::Federation;
5use devimint::util::FedimintCli;
6use devimint::version_constants::VERSION_0_5_0_ALPHA;
7use fedimint_logging::LOG_DEVIMINT;
8use rand::Rng;
9use tracing::info;
10
11#[derive(Debug, Parser)]
12enum Cmd {
13 Restore,
14 Sanity,
15}
16
17#[tokio::main]
18async fn main() -> anyhow::Result<()> {
19 match Cmd::parse() {
20 Cmd::Restore => restore().await,
21 Cmd::Sanity => sanity().await,
22 }
23}
24
25async fn restore() -> anyhow::Result<()> {
26 devimint::run_devfed_test(|fed, _process_mgr| async move {
27 let fed = fed.fed().await?;
28
29 test_restore_gap_test(fed).await?;
30 Ok(())
31 })
32 .await
33}
34
35pub async fn test_restore_gap_test(fed: &Federation) -> Result<()> {
36 let client = fed.new_joined_client("restore-gap-test").await?;
37 let fedimint_cli_version = FedimintCli::version_or_default().await;
38
39 if fedimint_cli_version < *VERSION_0_5_0_ALPHA {
40 return Ok(());
41 }
42
43 const PEGIN_SATS: u64 = 300000;
44 fed.pegin_client(PEGIN_SATS, &client).await?;
45
46 for i in 0..20 {
47 let gap = rand::thread_rng().gen_range(0..20);
48 info!(target: LOG_DEVIMINT, gap, "Gap");
49 cmd!(
50 client,
51 "dev",
52 "advance-note-idx",
53 "--amount",
54 "1024msat",
55 "--count",
56 &gap.to_string()
60 )
61 .run()
62 .await?;
63
64 let reissure_amount_sats = if i % 2 == 0 {
65 PEGIN_SATS
67 } else {
68 rand::thread_rng().gen_range(10..PEGIN_SATS)
70 };
71 info!(target: LOG_DEVIMINT, i, reissure_amount_sats, "Reissue");
72
73 let notes = cmd!(client, "spend", reissure_amount_sats * 1000)
74 .out_json()
75 .await?
76 .get("notes")
77 .expect("Output didn't contain e-cash notes")
78 .as_str()
79 .unwrap()
80 .to_owned();
81
82 cmd!(client, "reissue", notes).out_json().await?;
84 }
85
86 let secret = cmd!(client, "print-secret").out_json().await?["secret"]
87 .as_str()
88 .map(ToOwned::to_owned)
89 .unwrap();
90
91 let pre_notes = cmd!(client, "info").out_json().await?;
92
93 let pre_balance = pre_notes["total_amount_msat"].as_u64().unwrap();
94
95 info!(target: LOG_DEVIMINT, %pre_notes, pre_balance, "State before backup");
96
97 assert!(0 < pre_balance);
99
100 {
102 let client =
103 devimint::federation::Client::create("restore-gap-test-without-backup").await?;
104 let _ = cmd!(
105 client,
106 "restore",
107 "--mnemonic",
108 &secret,
109 "--invite-code",
110 fed.invite_code()?
111 )
112 .out_json()
113 .await?;
114
115 let _ = cmd!(client, "dev", "wait-complete").out_json().await?;
117 let post_notes = cmd!(client, "info").out_json().await?;
118 let post_balance = post_notes["total_amount_msat"].as_u64().unwrap();
119 info!(target: LOG_DEVIMINT, %post_notes, post_balance, "State after backup");
120 assert_eq!(pre_balance, post_balance);
121 assert_eq!(pre_notes, post_notes);
122 }
123
124 Ok(())
125}
126
127async fn sanity() -> anyhow::Result<()> {
128 devimint::run_devfed_test(|fed, _process_mgr| async move {
129 let fed = fed.fed().await?;
130
131 test_note_consoliation(fed).await?;
132 Ok(())
133 })
134 .await
135}
136
137async fn test_note_consoliation(fed: &devimint::federation::Federation) -> anyhow::Result<()> {
144 let sender = fed.new_joined_client("sender").await?;
145 let receiver = fed.new_joined_client("receiver").await?;
146
147 let can_no_wait = cmd!(sender, "reissue", "--help")
148 .out_string()
149 .await?
150 .contains("no-wait");
151
152 if !can_no_wait {
153 info!("Version before `--no-wait` didn't have consolidation implemented");
154 return Ok(());
155 }
156 fed.pegin_client(10_000, &sender).await?;
157
158 let mut all_notes = vec![];
159 for i in 0..20 {
160 let info = cmd!(sender, "info").out_json().await?;
161 info!(%info, "sender info");
162 if i % 2 == 1 {
164 let notes = cmd!(sender, "spend", "1sat",).out_json().await?["notes"]
165 .as_str()
166 .context("invoice must be string")?
167 .to_owned();
168
169 cmd!(sender, "reissue", notes).run().await?;
170 }
171
172 let notes = cmd!(sender, "spend", "1msat",).out_json().await?["notes"]
173 .as_str()
174 .context("invoice must be string")?
175 .to_owned();
176
177 all_notes.push(notes);
178 }
179
180 for notes in &all_notes[..all_notes.len() - 1] {
181 cmd!(receiver, "reissue", "--no-wait", notes).run().await?;
182 }
183
184 cmd!(receiver, "dev", "wait-complete").run().await?;
186
187 cmd!(receiver, "reissue")
189 .args(&all_notes[all_notes.len() - 1..])
190 .run()
191 .await?;
192
193 let info = cmd!(receiver, "info").out_json().await?;
194 info!(%info, "receiver info");
195 assert_eq!(info["total_amount_msat"].as_i64().unwrap(), 20);
197 assert!(info["denominations_msat"]["1"].as_i64().unwrap() < 20);
199
200 Ok(())
201}