1use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use telltale_machine::composition::{CompositionCertificate, ReconfigurationPolicy};
9use telltale_machine::runtime::loader::CodeImage;
10use telltale_machine::ProtocolBundle as MachineProtocolBundle;
11use telltale_types::{FixedQ32, GlobalType, LocalTypeR};
12
13use crate::export::{global_to_json, local_to_json};
14use crate::import::{json_to_global, json_to_local};
15
16pub const PROTOCOL_BUNDLE_SCHEMA_VERSION: &str = "protocol_bundle.v1";
18
19#[must_use]
20pub fn canonical_protocol_bundle_schema_version() -> String {
21 PROTOCOL_BUNDLE_SCHEMA_VERSION.to_string()
22}
23
24fn deserialize_protocol_bundle_schema_version<'de, D>(deserializer: D) -> Result<String, D::Error>
25where
26 D: serde::Deserializer<'de>,
27{
28 let version = String::deserialize(deserializer)?;
29 if version == PROTOCOL_BUNDLE_SCHEMA_VERSION {
30 Ok(version)
31 } else {
32 Err(serde::de::Error::custom(format!(
33 "unsupported schema_version '{version}'; expected '{PROTOCOL_BUNDLE_SCHEMA_VERSION}'"
34 )))
35 }
36}
37
38#[derive(Clone, Debug, Default, Serialize, Deserialize)]
39pub enum SchedulerKind {
40 #[default]
41 Cooperative,
42 RoundRobin,
43 Priority,
44 ProgressAware,
45}
46
47#[derive(Clone, Debug, Default, Serialize, Deserialize)]
48pub enum FaultModel {
49 #[default]
50 Crash,
51 Byzantine,
52 Hybrid,
53}
54
55#[derive(Clone, Debug, Default, Serialize, Deserialize)]
56pub enum TimingModel {
57 #[default]
58 Asynchronous,
59 PartialSynchrony,
60 Synchronous,
61}
62
63#[derive(Clone, Debug, Default, Serialize, Deserialize)]
64pub enum ConsistencyLevel {
65 #[default]
66 Linearizable,
67 Sequential,
68 Eventual,
69}
70
71#[derive(Clone, Debug, Default, Serialize, Deserialize)]
72pub enum AvailabilityLevel {
73 #[default]
74 Total,
75 BoundedDegradation,
76 BestEffort,
77}
78
79#[derive(Clone, Debug, Default, Serialize, Deserialize)]
80pub enum PartitionModel {
81 #[default]
82 None,
83 CrashOnly,
84 NetworkSplit,
85}
86
87#[derive(Clone, Debug, Default, Serialize, Deserialize)]
88pub enum QuorumSystemKind {
89 #[default]
90 Majority,
91 Weighted,
92 Flexible,
93}
94
95#[derive(Clone, Debug, Default, Serialize, Deserialize)]
96pub struct LivenessConfig {
97 pub scheduler: SchedulerKind,
98 pub fairness_k: Option<usize>,
99 pub progress_required: bool,
100}
101
102#[derive(Clone, Debug, Default, Serialize, Deserialize)]
103pub struct FLPConfig {
104 pub crash_bound: usize,
105 pub requires_determinism: bool,
106}
107
108#[derive(Clone, Debug, Default, Serialize, Deserialize)]
109pub struct CAPConfig {
110 pub consistency: ConsistencyLevel,
111 pub availability: AvailabilityLevel,
112 pub partition_model: PartitionModel,
113}
114
115#[derive(Clone, Debug, Default, Serialize, Deserialize)]
116pub struct QuorumGeometryConfig {
117 pub quorum_system: QuorumSystemKind,
118 pub n: usize,
119 pub quorum_size: usize,
120 pub intersection_size: usize,
121}
122
123#[derive(Clone, Debug, Default, Serialize, Deserialize)]
124pub struct PartialSynchronyConfig {
125 pub timing: TimingModel,
126 pub delta_bound: Option<usize>,
127}
128
129#[derive(Clone, Debug, Default, Serialize, Deserialize)]
130pub struct ResponsivenessConfig {
131 pub leader_based: bool,
132 pub requires_stable_period: bool,
133}
134
135#[derive(Clone, Debug, Default, Serialize, Deserialize)]
136pub struct NakamotoConfig {
137 pub honest_fraction: FixedQ32,
138 pub finality_depth: usize,
139}
140
141#[derive(Clone, Debug, Default, Serialize, Deserialize)]
142pub struct ReconfigurationConfig {
143 pub dynamic_membership: bool,
144 pub overlap_required: bool,
145}
146
147#[derive(Clone, Debug, Default, Serialize, Deserialize)]
148pub struct AtomicBroadcastConfig {
149 pub total_order: bool,
150 pub valid_delivery: bool,
151}
152
153#[derive(Clone, Debug, Default, Serialize, Deserialize)]
154pub struct AccountableSafetyConfig {
155 pub enabled: bool,
156}
157
158#[derive(Clone, Debug, Default, Serialize, Deserialize)]
159pub struct FailureDetectorsConfig {
160 pub enabled: bool,
161}
162
163#[derive(Clone, Debug, Default, Serialize, Deserialize)]
164pub struct DataAvailabilityConfig {
165 pub enabled: bool,
166}
167
168#[derive(Clone, Debug, Default, Serialize, Deserialize)]
169pub struct CoordinationConfig {
170 pub enabled: bool,
171}
172
173#[derive(Clone, Debug, Default, Serialize, Deserialize)]
174pub struct CRDTConfig {
175 pub enabled: bool,
176}
177
178#[derive(Clone, Debug, Default, Serialize, Deserialize)]
179pub struct ByzantineSafetyConfig {
180 pub enabled: bool,
181}
182
183#[derive(Clone, Debug, Default, Serialize, Deserialize)]
184pub struct ConsensusEnvelopeConfig {
185 pub enabled: bool,
186}
187
188#[derive(Clone, Debug, Default, Serialize, Deserialize)]
189pub struct FailureEnvelopeConfig {
190 pub enabled: bool,
191}
192
193#[derive(Clone, Debug, Default, Serialize, Deserialize)]
194pub struct ProtocolMachineEnvelopeAdherenceConfig {
195 pub enabled: bool,
196}
197
198#[derive(Clone, Debug, Default, Serialize, Deserialize)]
199pub struct ProtocolMachineEnvelopeAdmissionConfig {
200 pub enabled: bool,
201}
202
203#[derive(Clone, Debug, Default, Serialize, Deserialize)]
204pub struct ProtocolEnvelopeBridgeConfig {
205 pub enabled: bool,
206}
207
208#[derive(Clone, Debug, Default, Serialize, Deserialize)]
209pub struct DistributedClaims {
210 #[serde(default)]
211 pub fault_model: FaultModel,
212 pub flp: Option<FLPConfig>,
213 pub cap: Option<CAPConfig>,
214 pub quorum_geometry: Option<QuorumGeometryConfig>,
215 pub partial_synchrony: Option<PartialSynchronyConfig>,
216 pub responsiveness: Option<ResponsivenessConfig>,
217 pub nakamoto: Option<NakamotoConfig>,
218 pub reconfiguration: Option<ReconfigurationConfig>,
219 pub atomic_broadcast: Option<AtomicBroadcastConfig>,
220 pub accountable_safety: Option<AccountableSafetyConfig>,
221 pub failure_detectors: Option<FailureDetectorsConfig>,
222 pub data_availability: Option<DataAvailabilityConfig>,
223 pub coordination: Option<CoordinationConfig>,
224 pub crdt: Option<CRDTConfig>,
225 pub byzantine_safety: Option<ByzantineSafetyConfig>,
226 pub consensus_envelope: Option<ConsensusEnvelopeConfig>,
227 pub failure_envelope: Option<FailureEnvelopeConfig>,
228 pub protocol_machine_envelope_adherence: Option<ProtocolMachineEnvelopeAdherenceConfig>,
229 pub protocol_machine_envelope_admission: Option<ProtocolMachineEnvelopeAdmissionConfig>,
230 pub protocol_envelope_bridge: Option<ProtocolEnvelopeBridgeConfig>,
231}
232
233#[derive(Clone, Debug, Default, Serialize, Deserialize)]
234pub struct FosterConfig {
235 pub enabled: bool,
236}
237
238#[derive(Clone, Debug, Default, Serialize, Deserialize)]
239pub struct MaxWeightConfig {
240 pub enabled: bool,
241 pub slack: Option<FixedQ32>,
242}
243
244#[derive(Clone, Debug, Default, Serialize, Deserialize)]
245pub struct LDPConfig {
246 pub enabled: bool,
247}
248
249#[derive(Clone, Debug, Default, Serialize, Deserialize)]
250pub struct MeanFieldConfig {
251 pub enabled: bool,
252 pub population_size: Option<usize>,
253}
254
255#[derive(Clone, Debug, Default, Serialize, Deserialize)]
256pub struct HeavyTrafficConfig {
257 pub enabled: bool,
258}
259
260#[derive(Clone, Debug, Default, Serialize, Deserialize)]
261pub struct MixingConfig {
262 pub enabled: bool,
263 pub mixing_time_bound: Option<usize>,
264}
265
266#[derive(Clone, Debug, Default, Serialize, Deserialize)]
267pub struct FluidConfig {
268 pub enabled: bool,
269}
270
271#[derive(Clone, Debug, Default, Serialize, Deserialize)]
272pub struct ConcentrationConfig {
273 pub enabled: bool,
274}
275
276#[derive(Clone, Debug, Default, Serialize, Deserialize)]
277pub struct LittlesLawConfig {
278 pub enabled: bool,
279}
280
281#[derive(Clone, Debug, Default, Serialize, Deserialize)]
282pub struct FunctionalCLTConfig {
283 pub enabled: bool,
284}
285
286#[derive(Clone, Debug, Default, Serialize, Deserialize)]
287pub struct SpectralGapTerminationConfig {
288 pub enabled: bool,
289}
290
291#[derive(Clone, Debug, Default, Serialize, Deserialize)]
292pub struct ClassicalClaims {
293 pub foster: Option<FosterConfig>,
294 pub max_weight: Option<MaxWeightConfig>,
295 pub ldp: Option<LDPConfig>,
296 pub mean_field: Option<MeanFieldConfig>,
297 pub heavy_traffic: Option<HeavyTrafficConfig>,
298 pub mixing: Option<MixingConfig>,
299 pub fluid: Option<FluidConfig>,
300 pub concentration: Option<ConcentrationConfig>,
301 pub littles_law: Option<LittlesLawConfig>,
302 pub functional_clt: Option<FunctionalCLTConfig>,
303 pub spectral_gap_termination: Option<SpectralGapTerminationConfig>,
304}
305
306#[derive(Clone, Debug, Serialize, Deserialize)]
307pub struct InvariantClaims {
308 #[serde(deserialize_with = "crate::schema::deserialize_schema_version")]
309 pub schema_version: String,
310 pub liveness: Option<LivenessConfig>,
311 #[serde(default)]
312 pub distributed: DistributedClaims,
313 #[serde(default)]
314 pub classical: ClassicalClaims,
315}
316
317impl Default for InvariantClaims {
318 fn default() -> Self {
319 Self {
320 schema_version: crate::schema::canonical_schema_version(),
321 liveness: None,
322 distributed: DistributedClaims::default(),
323 classical: ClassicalClaims::default(),
324 }
325 }
326}
327
328#[derive(Clone, Debug, Serialize, Deserialize)]
329pub struct ProtocolBundle {
330 #[serde(deserialize_with = "deserialize_protocol_bundle_schema_version")]
331 pub schema_version: String,
332 pub global_type: Value,
333 pub local_types: BTreeMap<String, Value>,
334 pub claims: InvariantClaims,
335}
336
337#[derive(Debug, thiserror::Error)]
339pub enum MachineBundleConversionError {
340 #[error("failed to import global type from bridge bundle: {0}")]
342 GlobalImport(#[from] crate::ImportError),
343 #[error("failed to import local type for role `{role}` from bridge bundle: {source}")]
345 LocalImport {
346 role: String,
348 source: crate::ImportError,
350 },
351}
352
353impl ProtocolBundle {
354 #[must_use]
356 pub fn reconfiguration_policy(&self) -> Option<ReconfigurationPolicy> {
357 self.claims
358 .distributed
359 .reconfiguration
360 .as_ref()
361 .map(|reconfiguration| ReconfigurationPolicy {
362 dynamic_membership: reconfiguration.dynamic_membership,
363 overlap_required: reconfiguration.overlap_required,
364 })
365 }
366
367 pub fn to_machine_bundle(
374 &self,
375 certificate: CompositionCertificate,
376 ) -> Result<MachineProtocolBundle, MachineBundleConversionError> {
377 let global = json_to_global(&self.global_type)?;
378 let mut local_types = BTreeMap::new();
379 for (role, local) in &self.local_types {
380 let local_type = json_to_local(local).map_err(|source| {
381 MachineBundleConversionError::LocalImport {
382 role: role.clone(),
383 source,
384 }
385 })?;
386 local_types.insert(role.clone(), local_type);
387 }
388 let code = Arc::new(CodeImage::from_local_types(&local_types, &global));
389 let bundle = MachineProtocolBundle::new(code, certificate);
390 Ok(match self.reconfiguration_policy() {
391 Some(policy) => bundle.with_reconfiguration_policy(policy),
392 None => bundle,
393 })
394 }
395}
396
397#[must_use]
399pub fn export_protocol_bundle(
400 global: &GlobalType,
401 local_types: &BTreeMap<String, LocalTypeR>,
402 claims: InvariantClaims,
403) -> ProtocolBundle {
404 let local_types = local_types
405 .iter()
406 .map(|(role, local)| (role.clone(), local_to_json(local)))
407 .collect();
408
409 ProtocolBundle {
410 schema_version: canonical_protocol_bundle_schema_version(),
411 global_type: global_to_json(global),
412 local_types,
413 claims,
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420 use telltale_types::{GlobalType, Label};
421
422 #[test]
423 fn test_export_protocol_bundle_includes_schema_and_claims() {
424 let global = GlobalType::send("A", "B", Label::new("msg"), GlobalType::End);
425 let mut locals = BTreeMap::new();
426 locals.insert(
427 "A".to_string(),
428 LocalTypeR::send("B", Label::new("msg"), LocalTypeR::End),
429 );
430 locals.insert(
431 "B".to_string(),
432 LocalTypeR::recv("A", Label::new("msg"), LocalTypeR::End),
433 );
434
435 let claims = InvariantClaims {
436 schema_version: crate::schema::canonical_schema_version(),
437 liveness: Some(LivenessConfig {
438 scheduler: SchedulerKind::RoundRobin,
439 fairness_k: Some(2),
440 progress_required: true,
441 }),
442 distributed: DistributedClaims {
443 flp: Some(FLPConfig {
444 crash_bound: 1,
445 requires_determinism: true,
446 }),
447 accountable_safety: Some(AccountableSafetyConfig { enabled: true }),
448 byzantine_safety: Some(ByzantineSafetyConfig { enabled: true }),
449 protocol_envelope_bridge: Some(ProtocolEnvelopeBridgeConfig { enabled: true }),
450 ..DistributedClaims::default()
451 },
452 classical: ClassicalClaims {
453 foster: Some(FosterConfig { enabled: true }),
454 ..ClassicalClaims::default()
455 },
456 };
457
458 let bundle = export_protocol_bundle(&global, &locals, claims);
459 assert_eq!(bundle.schema_version, PROTOCOL_BUNDLE_SCHEMA_VERSION);
460 assert_eq!(
461 bundle.claims.schema_version,
462 crate::schema::LEAN_BRIDGE_SCHEMA_VERSION
463 );
464 assert!(bundle.local_types.contains_key("A"));
465 assert!(bundle.local_types.contains_key("B"));
466 }
467}