ariadne_simulator/
main.rs

1//! Executable crate for simulating committee selection with the Ariadne algorithm.
2//!
3//! This tool is meant for Partner Chain builders and governance authorities to run
4//! simulation of Ariadne commitee selection in order to select best-performing values
5//! of the D-Parameter for their actual set of permissioned and registered candidates,
6//! as well as predict general security of the algorithm.
7//!
8//! # Usage
9//!
10//! The tool exposes two commands that simulate Ariadne:
11//! - [simulate]: outputs selected committees as JSON arrays
12//! - [analyze]: calculates various statistics for each selected committee and outputs
13//!              them as CSV data
14//!
15//! See the documentation of each command for details.
16//!
17//! Both commands expect to receive as arguments JSON files containing information about
18//! committee member candidates. For registered candidates the format is a list of objects
19//! containing fields `key` and `stake`, eg.:
20//! ```json
21//! [
22//!   {
23//!      "key": "registered-1",
24//!      "stake": 134664494512628
25//!   },
26//!   {
27//!      "key": "registered-2",
28//!      "stake": 76499924001653
29//!   },
30//!   {
31//!      "key": "registered-3",
32//!      "stake": 75953756970290
33//!   }
34//! ]
35//! ```
36//! For permissioned candidates only the `key` field is expected, eg.:
37//! ```json
38//! [
39//!   { "key": "permissioned-0" },
40//!   { "key": "permissioned-1" },
41//!   { "key": "permissioned-2" }
42//! ]
43//! ```
44//!
45//! [simulate]: simple_sim::Command
46//! [analyze]: analyze::Command
47#![deny(missing_docs)]
48
49use clap::*;
50use rand::prelude::*;
51use rand_chacha::ChaCha20Rng;
52use rand_chacha::rand_core::SeedableRng;
53use serde::*;
54use std::fmt::Display;
55
56mod analyze;
57mod simple_sim;
58
59/// Top level command of the executable. Subcommands are various ways of simulating Ariadne selection.
60#[derive(clap::Parser, Debug)]
61pub enum TopCommand {
62	/// Simulates Ariadne selection and prints selected committees as JSON
63	Simulate(simple_sim::Command),
64	/// Simulates Ariadne selection and prints various statistics about the selected committees as CSV
65	Analyze(analyze::Command),
66}
67
68#[derive(Serialize, Deserialize, Debug)]
69struct SPO {
70	key: String,
71	stake: u64,
72}
73#[derive(Serialize, Deserialize, Debug)]
74struct Permissioned {
75	key: String,
76}
77
78#[derive(Serialize, Deserialize, Debug, Clone, clap::ValueEnum)]
79enum AriadneVersion {
80	V1,
81	V2,
82}
83
84impl Display for AriadneVersion {
85	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86		f.write_str(match self {
87			Self::V1 => "v1",
88			Self::V2 => "v2",
89		})
90	}
91}
92
93impl AriadneVersion {
94	pub fn select_authorities<SC>(
95		&self,
96		registered_seats: u16,
97		permissioned_seats: u16,
98		registered_candidates: Vec<(SC, selection::Weight)>,
99		permissioned_candidates: Vec<SC>,
100		rng: &mut ChaCha20Rng,
101	) -> Option<Vec<SC>>
102	where
103		SC: Ord + Clone,
104	{
105		let mut seed = [0u8; 32];
106		rng.fill(&mut seed);
107		match self {
108			Self::V1 => selection::ariadne::select_authorities(
109				registered_seats,
110				permissioned_seats,
111				registered_candidates,
112				permissioned_candidates,
113				seed,
114			),
115			Self::V2 => selection::ariadne_v2::select_authorities(
116				registered_seats,
117				permissioned_seats,
118				registered_candidates,
119				permissioned_candidates,
120				seed,
121			),
122		}
123	}
124}
125
126fn load_registered(file: String) -> Vec<(String, u128)> {
127	let file = std::fs::File::open(file).expect("Registered candidates file can't be opened");
128	serde_json::from_reader::<_, Vec<SPO>>(file)
129		.expect("Registered candidates file is invalid")
130		.into_iter()
131		.map(|spo| (spo.key, spo.stake.into()))
132		.collect()
133}
134
135fn load_permissioned(file: String) -> Vec<String> {
136	serde_json::from_reader::<_, Vec<Permissioned>>(
137		std::fs::File::open(file).expect("Permissioned candidates file can't be opened"),
138	)
139	.expect("Permissioned candidates file is invalid")
140	.into_iter()
141	.map(|p| p.key)
142	.collect()
143}
144
145fn main() {
146	env_logger::builder().filter_level(log::LevelFilter::Info).init();
147
148	let cmd = TopCommand::parse();
149
150	let rng = ChaCha20Rng::from_os_rng();
151
152	match cmd {
153		TopCommand::Simulate(cmd) => cmd.execute(rng),
154		TopCommand::Analyze(cmd) => cmd.execute(rng),
155	}
156}