1use std::collections::BTreeMap;
2
3use anyhow::format_err;
4use fedimint_api_client::api::ApiVersionSet;
5use fedimint_core::PeerId;
6use fedimint_core::module::{
7 ApiVersion, SupportedApiVersionsSummary, SupportedCoreApiVersions, SupportedModuleApiVersions,
8};
9
10pub fn discover_common_core_api_version(
11 client_versions: &SupportedCoreApiVersions,
12 peer_versions: &BTreeMap<PeerId, SupportedCoreApiVersions>,
13) -> Option<ApiVersion> {
14 let mut best_major = None;
15 let mut best_major_peer_num = 0;
16
17 for client_api_version in &client_versions.api {
19 let peers_compatible_num = peer_versions
20 .values()
21 .filter_map(|supported_versions| {
22 supported_versions
23 .get_minor_api_version(client_versions.core_consensus, client_api_version.major)
24 })
25 .filter(|peer_minor| client_api_version.minor <= *peer_minor)
26 .count();
27
28 if best_major_peer_num < peers_compatible_num {
29 best_major = Some(client_api_version);
30 best_major_peer_num = peers_compatible_num;
31 }
32 }
33
34 best_major.map(
36 |ApiVersion {
37 major: best_major,
38 minor: best_major_minor,
39 }| ApiVersion {
40 major: best_major,
41 minor: peer_versions
42 .values()
43 .filter_map(|supported| {
44 supported.get_minor_api_version(client_versions.core_consensus, best_major)
45 })
46 .filter(|peer_minor| best_major_minor <= *peer_minor)
47 .min()
48 .expect("We must have at least one"),
49 },
50 )
51}
52
53#[test]
54fn discover_common_core_api_version_sanity() {
55 use fedimint_core::module::MultiApiVersion;
56
57 let core_consensus = fedimint_core::module::CoreConsensusVersion::new(0, 0);
58 let client_versions = SupportedCoreApiVersions {
59 core_consensus,
60 api: MultiApiVersion::try_from_iter([
61 ApiVersion { major: 2, minor: 3 },
62 ApiVersion { major: 3, minor: 1 },
63 ])
64 .unwrap(),
65 };
66
67 assert!(discover_common_core_api_version(&client_versions, &BTreeMap::from([])).is_none());
68 assert_eq!(
69 discover_common_core_api_version(
70 &client_versions,
71 &BTreeMap::from([(
72 PeerId::from(0),
73 SupportedCoreApiVersions {
74 core_consensus: fedimint_core::module::CoreConsensusVersion::new(0, 0),
75 api: MultiApiVersion::try_from_iter([ApiVersion { major: 2, minor: 3 }])
76 .unwrap(),
77 }
78 )])
79 ),
80 Some(ApiVersion { major: 2, minor: 3 })
81 );
82 assert_eq!(
83 discover_common_core_api_version(
84 &client_versions,
85 &BTreeMap::from([(
86 PeerId::from(0),
87 SupportedCoreApiVersions {
88 core_consensus: fedimint_core::module::CoreConsensusVersion::new(0, 1), api: MultiApiVersion::try_from_iter([ApiVersion { major: 2, minor: 3 }])
90 .unwrap(),
91 }
92 )])
93 ),
94 Some(ApiVersion { major: 2, minor: 3 })
95 );
96 assert_eq!(
97 discover_common_core_api_version(
98 &client_versions,
99 &BTreeMap::from([(
100 PeerId::from(0),
101 SupportedCoreApiVersions {
102 core_consensus: fedimint_core::module::CoreConsensusVersion::new(1, 0), api: MultiApiVersion::try_from_iter([ApiVersion { major: 2, minor: 4 }])
104 .unwrap(),
105 }
106 )])
107 ),
108 None
109 );
110 assert_eq!(
111 discover_common_core_api_version(
112 &client_versions,
113 &BTreeMap::from([
114 (
115 PeerId::from(0),
116 SupportedCoreApiVersions {
117 core_consensus,
118 api: MultiApiVersion::try_from_iter([ApiVersion { major: 2, minor: 2 }])
119 .unwrap(),
120 }
121 ),
122 (
123 PeerId::from(1),
124 SupportedCoreApiVersions {
125 core_consensus,
126 api: MultiApiVersion::try_from_iter([ApiVersion { major: 2, minor: 1 }])
127 .unwrap(),
128 }
129 ),
130 (
131 PeerId::from(1),
132 SupportedCoreApiVersions {
133 core_consensus,
134 api: MultiApiVersion::try_from_iter([ApiVersion { major: 3, minor: 1 }])
135 .unwrap(),
136 }
137 )
138 ])
139 ),
140 Some(ApiVersion { major: 3, minor: 1 })
141 );
142 assert_eq!(
143 discover_common_core_api_version(
144 &client_versions,
145 &BTreeMap::from([
146 (
147 PeerId::from(0),
148 SupportedCoreApiVersions {
149 core_consensus,
150 api: MultiApiVersion::try_from_iter([ApiVersion { major: 2, minor: 4 }])
151 .unwrap(),
152 }
153 ),
154 (
155 PeerId::from(1),
156 SupportedCoreApiVersions {
157 core_consensus,
158 api: MultiApiVersion::try_from_iter([ApiVersion { major: 2, minor: 5 }])
159 .unwrap(),
160 }
161 ),
162 ])
163 ),
164 Some(ApiVersion { major: 2, minor: 4 })
165 );
166}
167
168fn discover_common_module_api_version(
169 client_versions: &SupportedModuleApiVersions,
170 peer_versions: &BTreeMap<PeerId, SupportedModuleApiVersions>,
171) -> Option<ApiVersion> {
172 let mut best_major = None;
173 let mut best_major_peer_num = 0;
174
175 for client_api_version in &client_versions.api {
177 let peers_compatible_num = peer_versions
178 .values()
179 .filter_map(|supported_versions| {
180 supported_versions.get_minor_api_version(
181 client_versions.core_consensus,
182 client_versions.module_consensus,
183 client_api_version.major,
184 )
185 })
186 .filter(|peer_minor| client_api_version.minor <= *peer_minor)
187 .count();
188
189 if best_major_peer_num < peers_compatible_num {
190 best_major = Some(client_api_version);
191 best_major_peer_num = peers_compatible_num;
192 }
193 }
194
195 best_major.map(
197 |ApiVersion {
198 major: best_major,
199 minor: best_major_minor,
200 }| ApiVersion {
201 major: best_major,
202 minor: peer_versions
203 .values()
204 .filter_map(|supported| {
205 supported.get_minor_api_version(
206 client_versions.core_consensus,
207 client_versions.module_consensus,
208 best_major,
209 )
210 })
211 .filter(|peer_minor| best_major_minor <= *peer_minor)
212 .min()
213 .expect("We must have at least one"),
214 },
215 )
216}
217
218pub fn discover_common_api_versions_set(
219 client_versions: &SupportedApiVersionsSummary,
220 peer_versions: &BTreeMap<PeerId, SupportedApiVersionsSummary>,
221) -> anyhow::Result<ApiVersionSet> {
222 Ok(ApiVersionSet {
223 core: discover_common_core_api_version(
224 &client_versions.core,
225 &peer_versions
226 .iter()
227 .map(|(peer_id, peer_supported_api_versions)| {
228 (*peer_id, peer_supported_api_versions.core.clone())
229 })
230 .collect(),
231 )
232 .ok_or_else(|| format_err!("Could not find a common core API version"))?,
233 modules: client_versions
234 .modules
235 .iter()
236 .filter_map(
237 |(module_instance_id, client_supported_module_api_versions)| {
238 let discover_common_module_api_version = discover_common_module_api_version(
239 client_supported_module_api_versions,
240 &peer_versions
241 .iter()
242 .filter_map(|(peer_id, peer_supported_api_versions_summary)| {
243 peer_supported_api_versions_summary
244 .modules
245 .get(module_instance_id)
246 .map(|versions| (*peer_id, versions.clone()))
247 })
248 .collect(),
249 );
250 discover_common_module_api_version.map(|v| (*module_instance_id, v))
251 },
252 )
253 .collect(),
254 })
255}