1use clap::Parser;
20use color_eyre::eyre;
21
22pub(crate) mod interceptor;
23pub(crate) mod shared;
24
25mod variants;
26
27use variants::*;
28
29#[derive(Debug, Parser)]
31#[command(about = "Malus - the nemesis of polkadot.", version, rename_all = "kebab-case")]
32enum NemesisVariant {
33 SuggestGarbageCandidate(SuggestGarbageCandidateOptions),
35 SupportDisabled(SupportDisabledOptions),
37 BackGarbageCandidate(BackGarbageCandidateOptions),
39 DisputeAncestor(DisputeAncestorOptions),
41 DisputeFinalizedCandidates(DisputeFinalizedCandidatesOptions),
43 SpamStatementRequests(SpamStatementRequestsOptions),
45}
46
47#[derive(Debug, Parser)]
48#[allow(missing_docs)]
49struct MalusCli {
50 #[command(subcommand)]
51 pub variant: NemesisVariant,
52 pub finality_delay: Option<u32>,
54}
55
56impl MalusCli {
57 fn launch(self) -> eyre::Result<()> {
59 let finality_delay = self.finality_delay;
60 match self.variant {
61 NemesisVariant::BackGarbageCandidate(opts) => {
62 let BackGarbageCandidateOptions { percentage, cli } = opts;
63
64 polkadot_cli::run_node(cli, BackGarbageCandidates { percentage }, finality_delay)?
65 },
66 NemesisVariant::SuggestGarbageCandidate(opts) => {
67 let SuggestGarbageCandidateOptions { percentage, cli } = opts;
68
69 polkadot_cli::run_node(
70 cli,
71 SuggestGarbageCandidates { percentage },
72 finality_delay,
73 )?
74 },
75 NemesisVariant::SupportDisabled(opts) => {
76 let SupportDisabledOptions { cli } = opts;
77
78 polkadot_cli::run_node(cli, SupportDisabled, finality_delay)?
79 },
80 NemesisVariant::DisputeAncestor(opts) => {
81 let DisputeAncestorOptions {
82 fake_validation,
83 fake_validation_error,
84 percentage,
85 cli,
86 } = opts;
87
88 polkadot_cli::run_node(
89 cli,
90 DisputeValidCandidates { fake_validation, fake_validation_error, percentage },
91 finality_delay,
92 )?
93 },
94 NemesisVariant::DisputeFinalizedCandidates(opts) => {
95 let DisputeFinalizedCandidatesOptions { dispute_offset, cli } = opts;
96
97 polkadot_cli::run_node(
98 cli,
99 DisputeFinalizedCandidates { dispute_offset },
100 finality_delay,
101 )?
102 },
103 NemesisVariant::SpamStatementRequests(opts) => {
104 let SpamStatementRequestsOptions { spam_factor, cli } = opts;
105
106 polkadot_cli::run_node(cli, SpamStatementRequests { spam_factor }, finality_delay)?
107 },
108 }
109 Ok(())
110 }
111}
112
113fn main() -> eyre::Result<()> {
114 color_eyre::install()?;
115 let cli = MalusCli::parse();
116 cli.launch()?;
117 Ok(())
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn subcommand_works() {
126 let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
127 "malus",
128 "dispute-ancestor",
129 "--bob",
130 ]))
131 .unwrap();
132 assert_matches::assert_matches!(cli, MalusCli {
133 variant: NemesisVariant::DisputeAncestor(run),
134 ..
135 } => {
136 assert!(run.cli.run.base.bob);
137 });
138 }
139
140 #[test]
141 fn percentage_works_suggest_garbage() {
142 let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
143 "malus",
144 "suggest-garbage-candidate",
145 "--percentage",
146 "100",
147 "--bob",
148 ]))
149 .unwrap();
150 assert_matches::assert_matches!(cli, MalusCli {
151 variant: NemesisVariant::SuggestGarbageCandidate(run),
152 ..
153 } => {
154 assert!(run.cli.run.base.bob);
155 });
156 }
157
158 #[test]
159 fn percentage_works_dispute_ancestor() {
160 let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
161 "malus",
162 "dispute-ancestor",
163 "--percentage",
164 "100",
165 "--bob",
166 ]))
167 .unwrap();
168 assert_matches::assert_matches!(cli, MalusCli {
169 variant: NemesisVariant::DisputeAncestor(run),
170 ..
171 } => {
172 assert!(run.cli.run.base.bob);
173 });
174 }
175
176 #[test]
177 fn percentage_works_back_garbage() {
178 let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
179 "malus",
180 "back-garbage-candidate",
181 "--percentage",
182 "100",
183 "--bob",
184 ]))
185 .unwrap();
186 assert_matches::assert_matches!(cli, MalusCli {
187 variant: NemesisVariant::BackGarbageCandidate(run),
188 ..
189 } => {
190 assert!(run.cli.run.base.bob);
191 });
192 }
193
194 #[test]
195 #[should_panic]
196 fn validate_range_for_percentage() {
197 let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
198 "malus",
199 "suggest-garbage-candidate",
200 "--percentage",
201 "101",
202 "--bob",
203 ]))
204 .unwrap();
205 assert_matches::assert_matches!(cli, MalusCli {
206 variant: NemesisVariant::DisputeAncestor(run),
207 ..
208 } => {
209 assert!(run.cli.run.base.bob);
210 });
211 }
212
213 #[test]
214 fn dispute_finalized_candidates_works() {
215 let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
216 "malus",
217 "dispute-finalized-candidates",
218 "--bob",
219 ]))
220 .unwrap();
221 assert_matches::assert_matches!(cli, MalusCli {
222 variant: NemesisVariant::DisputeFinalizedCandidates(run),
223 ..
224 } => {
225 assert!(run.cli.run.base.bob);
226 });
227 }
228
229 #[test]
230 fn dispute_finalized_offset_value_works() {
231 let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
232 "malus",
233 "dispute-finalized-candidates",
234 "--dispute-offset",
235 "13",
236 "--bob",
237 ]))
238 .unwrap();
239 assert_matches::assert_matches!(cli, MalusCli {
240 variant: NemesisVariant::DisputeFinalizedCandidates(opts),
241 ..
242 } => {
243 assert_eq!(opts.dispute_offset, 13); assert!(opts.cli.run.base.bob);
245 });
246 }
247}