meta_module_tests/
meta-module-tests.rs

1use std::future::Future;
2
3use anyhow::{Result, bail};
4use clap::Parser;
5use devimint::federation::Client;
6use devimint::util::poll_simple;
7use devimint::version_constants::VERSION_0_5_0_ALPHA;
8use devimint::{cmd, util};
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 fedimint_cli_version = util::FedimintCli::version_or_default().await;
31
32            let client = dev_fed
33                .fed()
34                .await?
35                .new_joined_client("meta-module-client")
36                .await?;
37
38            async fn get_consensus(client: &Client) -> anyhow::Result<serde_json::Value> {
39                cmd!(client, "module", "meta", "get").out_json().await
40            }
41
42            async fn get_submissions(
43                client: &Client,
44                peer_id: PeerId,
45            ) -> anyhow::Result<serde_json::Value> {
46                cmd!(
47                    client,
48                    "--our-id",
49                    &peer_id.to_string(),
50                    "--password",
51                    "notset",
52                    "module",
53                    "meta",
54                    "get-submissions"
55                )
56                .out_json()
57                .await
58            }
59
60            async fn submit(
61                client: &Client,
62                peer_id: PeerId,
63                value: &serde_json::Value,
64            ) -> anyhow::Result<serde_json::Value> {
65                info!(%peer_id, ?value, "Peer submitting value");
66
67                cmd!(
68                    client,
69                    "--our-id",
70                    &peer_id.to_string(),
71                    "--password",
72                    "notset",
73                    "module",
74                    "meta",
75                    "submit",
76                    &value.to_string(),
77                )
78                .out_json()
79                .await
80            }
81
82            pub async fn poll_value<Fut>(
83                name: &str,
84                f: impl Fn() -> Fut,
85                expected_value: serde_json::Value,
86            ) -> Result<serde_json::Value>
87            where
88                Fut: Future<Output = Result<serde_json::Value, anyhow::Error>>,
89            {
90                poll_simple(name, || async {
91                    let value = f().await?;
92                    if value == expected_value {
93                        Ok(value)
94                    } else {
95                        bail!("Incorrect value: {}, expected: {}", value, expected_value);
96                    }
97                })
98                .await
99            }
100
101            async fn get_meta_fields(client: &Client) -> anyhow::Result<serde_json::Value> {
102                cmd!(client, "dev", "meta-fields",).out_json().await
103            }
104
105            // check starting conditions
106            assert_eq!(get_consensus(&client).await?, serde_json::Value::Null);
107            assert_eq!(
108                get_submissions(&client, PeerId::from(1)).await?,
109                json! {
110                    {}
111                }
112            );
113
114            let submission_value = json! {
115                { "foo": "bar" }
116            };
117            let minority_submission_value = json! {
118                { "bar": "baz" }
119            };
120
121            // check submissions visible
122            submit(&client, PeerId::from(1), &submission_value).await?;
123
124            info!("Checking submission");
125            poll_value(
126                "submission visible on same peer",
127                || async { get_submissions(&client, PeerId::from(1)).await },
128                json! {
129                    {  "1": submission_value }
130                },
131            )
132            .await?;
133            poll_value(
134                "submission visible on a different peer",
135                || async { get_submissions(&client, PeerId::from(3)).await },
136                json! {
137                    {  "1": submission_value }
138                },
139            )
140            .await?;
141
142            // form a consensus with a minority vote
143            submit(&client, PeerId::from(0), &minority_submission_value).await?;
144            submit(&client, PeerId::from(2), &submission_value).await?;
145            assert_eq!(
146                submit(&client, PeerId::from(3), &submission_value).await?,
147                serde_json::Value::Bool(true)
148            );
149
150            info!(expected = %submission_value, "Checking consensus");
151            if let Err(e) = poll_value(
152                "consensus set",
153                || async { get_consensus(&client).await },
154                json! {
155                    {
156                        "revision": 0,
157                        "value":submission_value
158                    }
159                },
160            )
161            .await
162            {
163                let submissions = get_submissions(&client, PeerId::from(3)).await?;
164                warn!(%submissions, "Getting expected consensus value failed");
165                return Err(e);
166            }
167
168            // minority vote should be still visible
169            poll_value(
170                "minor submission visible",
171                || async { get_submissions(&client, PeerId::from(0)).await },
172                json! {
173                    {  "0": minority_submission_value}
174                },
175            )
176            .await?;
177
178            // If the peer with outstanding vote votes for the consensu value,
179            // their submission will clear.
180            submit(&client, PeerId::from(0), &submission_value).await?;
181            poll_value(
182                "submission cleared",
183                || async { get_submissions(&client, PeerId::from(1)).await },
184                json! { {} },
185            )
186            .await?;
187
188            // TODO(support:v0.4): meta fields were introduced in v0.5.0
189            // see: https://github.com/fedimint/fedimint/pull/5781
190            if fedimint_cli_version >= *VERSION_0_5_0_ALPHA {
191                let meta_fields = get_meta_fields(&client).await?;
192                assert_eq!(
193                    meta_fields,
194                    json! {
195                        {
196                            "revision": 0,
197                            "values": submission_value,
198                        }
199                    }
200                );
201            }
202
203            Ok(())
204        })
205        .await
206}