referrerpolicy=no-referrer-when-downgrade

malus/
malus.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! A malus or nemesis node launch code.
18
19use clap::Parser;
20use color_eyre::eyre;
21
22pub(crate) mod interceptor;
23pub(crate) mod shared;
24
25mod variants;
26
27use variants::*;
28
29/// Define the different variants of behavior.
30#[derive(Debug, Parser)]
31#[command(about = "Malus - the nemesis of polkadot.", version, rename_all = "kebab-case")]
32enum NemesisVariant {
33	/// Suggest a candidate with an invalid proof of validity.
34	SuggestGarbageCandidate(SuggestGarbageCandidateOptions),
35	/// Support disabled validators in backing and statement distribution.
36	SupportDisabled(SupportDisabledOptions),
37	/// Back a candidate with a specifically crafted proof of validity.
38	BackGarbageCandidate(BackGarbageCandidateOptions),
39	/// Delayed disputing of ancestors that are perfectly fine.
40	DisputeAncestor(DisputeAncestorOptions),
41	/// Delayed disputing of finalized candidates.
42	DisputeFinalizedCandidates(DisputeFinalizedCandidatesOptions),
43	/// Spam many request statements instead of sending a single one.
44	SpamStatementRequests(SpamStatementRequestsOptions),
45}
46
47#[derive(Debug, Parser)]
48#[allow(missing_docs)]
49struct MalusCli {
50	#[command(subcommand)]
51	pub variant: NemesisVariant,
52	/// Sets the minimum delay between the best and finalized block.
53	pub finality_delay: Option<u32>,
54}
55
56impl MalusCli {
57	/// Launch a malus node.
58	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); // This line checks that dispute_offset is correctly set to 13
244			assert!(opts.cli.run.base.bob);
245		});
246	}
247}