Skip to main content

meta_module_tests/
meta-module-tests.rs

1use std::future::Future;
2
3use anyhow::{Result, bail};
4use clap::Parser;
5use devimint::cmd;
6use devimint::federation::Client;
7use devimint::util::{FedimintdCmd, poll_simple};
8use devimint::version_constants::VERSION_0_13_0_ALPHA;
9use fedimint_core::PeerId;
10use serde_json::json;
11use tracing::{info, warn};
12
13#[derive(Parser, Debug)]
14pub enum TestCli {
15    Sanity,
16}
17
18#[tokio::main]
19async fn main() -> anyhow::Result<()> {
20    let opts = TestCli::parse();
21
22    match opts {
23        TestCli::Sanity => sanity_tests().await,
24    }
25}
26
27async fn sanity_tests() -> anyhow::Result<()> {
28    devimint::run_devfed_test()
29        .call(|dev_fed, _process_mgr| async move {
30            let client = dev_fed
31                .fed()
32                .await?
33                .new_joined_client("meta-module-client")
34                .await?;
35
36            async fn get_consensus(client: &Client) -> anyhow::Result<serde_json::Value> {
37                cmd!(client, "module", "meta", "get").out_json().await
38            }
39
40            // Older fedimintd versions do not enforce authentication for these
41            // endpoints, so only assert the new behavior when testing a current server.
42            if *VERSION_0_13_0_ALPHA <= FedimintdCmd::version_or_default().await {
43                cmd!(
44                    client,
45                    "--our-id",
46                    "0",
47                    "--password",
48                    "incorrect",
49                    "module",
50                    "meta",
51                    "get-submissions"
52                )
53                .assert_error_contains("Invalid authorization")
54                .await?;
55
56                cmd!(
57                    client,
58                    "--our-id",
59                    "0",
60                    "--password",
61                    "incorrect",
62                    "module",
63                    "meta",
64                    "submit",
65                    "\"unauthorized\""
66                )
67                .assert_error_contains("Invalid authorization")
68                .await?;
69            }
70
71            async fn get_submissions(
72                client: &Client,
73                peer_id: PeerId,
74            ) -> anyhow::Result<serde_json::Value> {
75                cmd!(
76                    client,
77                    "--our-id",
78                    &peer_id.to_string(),
79                    "--password",
80                    "pass",
81                    "module",
82                    "meta",
83                    "get-submissions"
84                )
85                .out_json()
86                .await
87            }
88
89            async fn submit(
90                client: &Client,
91                peer_id: PeerId,
92                value: &serde_json::Value,
93            ) -> anyhow::Result<serde_json::Value> {
94                info!(%peer_id, ?value, "Peer submitting value");
95
96                cmd!(
97                    client,
98                    "--our-id",
99                    &peer_id.to_string(),
100                    "--password",
101                    "pass",
102                    "module",
103                    "meta",
104                    "submit",
105                    &value.to_string(),
106                )
107                .out_json()
108                .await
109            }
110
111            pub async fn poll_value<Fut>(
112                name: &str,
113                f: impl Fn() -> Fut,
114                expected_value: serde_json::Value,
115            ) -> Result<serde_json::Value>
116            where
117                Fut: Future<Output = Result<serde_json::Value, anyhow::Error>>,
118            {
119                poll_simple(name, || async {
120                    let value = f().await?;
121                    if value == expected_value {
122                        Ok(value)
123                    } else {
124                        bail!("Incorrect value: {}, expected: {}", value, expected_value);
125                    }
126                })
127                .await
128            }
129
130            async fn get_meta_fields(client: &Client) -> anyhow::Result<serde_json::Value> {
131                cmd!(client, "dev", "meta-fields",).out_json().await
132            }
133
134            // check starting conditions
135            assert_eq!(get_consensus(&client).await?, serde_json::Value::Null);
136            assert_eq!(
137                get_submissions(&client, PeerId::from(1)).await?,
138                json! {
139                    {}
140                }
141            );
142
143            let submission_value = json! {
144                { "foo": "bar" }
145            };
146            let minority_submission_value = json! {
147                { "bar": "baz" }
148            };
149
150            // check submissions visible
151            submit(&client, PeerId::from(1), &submission_value).await?;
152
153            info!("Checking submission");
154            poll_value(
155                "submission visible on same peer",
156                || async { get_submissions(&client, PeerId::from(1)).await },
157                json! {
158                    {  "1": submission_value }
159                },
160            )
161            .await?;
162            poll_value(
163                "submission visible on a different peer",
164                || async { get_submissions(&client, PeerId::from(3)).await },
165                json! {
166                    {  "1": submission_value }
167                },
168            )
169            .await?;
170
171            // form a consensus with a minority vote
172            submit(&client, PeerId::from(0), &minority_submission_value).await?;
173            submit(&client, PeerId::from(2), &submission_value).await?;
174            assert_eq!(
175                submit(&client, PeerId::from(3), &submission_value).await?,
176                serde_json::Value::Bool(true)
177            );
178
179            info!(expected = %submission_value, "Checking consensus");
180            if let Err(e) = poll_value(
181                "consensus set",
182                || async { get_consensus(&client).await },
183                json! {
184                    {
185                        "revision": 0,
186                        "value":submission_value
187                    }
188                },
189            )
190            .await
191            {
192                let submissions = get_submissions(&client, PeerId::from(3)).await?;
193                warn!(%submissions, "Getting expected consensus value failed");
194                return Err(e);
195            }
196
197            // minority vote should be still visible
198            poll_value(
199                "minor submission visible",
200                || async { get_submissions(&client, PeerId::from(0)).await },
201                json! {
202                    {  "0": minority_submission_value}
203                },
204            )
205            .await?;
206
207            // If the peer with outstanding vote votes for the consensu value,
208            // their submission will clear.
209            submit(&client, PeerId::from(0), &submission_value).await?;
210            poll_value(
211                "submission cleared",
212                || async { get_submissions(&client, PeerId::from(1)).await },
213                json! { {} },
214            )
215            .await?;
216
217            let meta_fields = get_meta_fields(&client).await?;
218            assert_eq!(
219                meta_fields,
220                json! {
221                    {
222                        "revision": 0,
223                        "values": submission_value,
224                    }
225                }
226            );
227
228            Ok(())
229        })
230        .await
231}