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