iota_aws_orchestrator/protocol/
iota.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    fmt::{Debug, Display},
7    path::PathBuf,
8    str::FromStr,
9};
10
11use iota_swarm_config::genesis_config::GenesisConfig;
12use iota_types::{base_types::IotaAddress, multiaddr::Multiaddr};
13use serde::{Deserialize, Serialize};
14
15use super::{ProtocolCommands, ProtocolMetrics};
16use crate::{
17    benchmark::{BenchmarkParameters, BenchmarkType},
18    client::Instance,
19    display,
20    settings::Settings,
21};
22
23#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct IotaBenchmarkType {
25    /// Percentage of shared vs owned objects; 0 means only owned objects and
26    /// 100 means only shared objects.
27    shared_objects_ratio: u16,
28}
29
30impl Debug for IotaBenchmarkType {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        write!(f, "{}", self.shared_objects_ratio)
33    }
34}
35
36impl Display for IotaBenchmarkType {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        write!(f, "{}% shared objects", self.shared_objects_ratio)
39    }
40}
41
42impl FromStr for IotaBenchmarkType {
43    type Err = std::num::ParseIntError;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        Ok(Self {
47            shared_objects_ratio: s.parse::<u16>()?.min(100),
48        })
49    }
50}
51
52impl BenchmarkType for IotaBenchmarkType {}
53
54/// All configurations information to run an IOTA client or validator.
55pub struct IotaProtocol {
56    working_dir: PathBuf,
57}
58
59impl ProtocolCommands<IotaBenchmarkType> for IotaProtocol {
60    fn protocol_dependencies(&self) -> Vec<&'static str> {
61        vec![
62            // Install typical iota dependencies.
63            "sudo apt-get -y install curl git-all clang cmake gcc libssl-dev pkg-config libclang-dev",
64            // This dependency is missing from the IOTA docs.
65            "sudo apt-get -y install libpq-dev",
66        ]
67    }
68
69    fn db_directories(&self) -> Vec<PathBuf> {
70        let authorities_db = [&self.working_dir, &iota_config::AUTHORITIES_DB_NAME.into()]
71            .iter()
72            .collect();
73        let consensus_db = [&self.working_dir, &iota_config::CONSENSUS_DB_NAME.into()]
74            .iter()
75            .collect();
76        vec![authorities_db, consensus_db]
77    }
78
79    fn genesis_command<'a, I>(
80        &self,
81        instances: I,
82        parameters: &BenchmarkParameters<IotaBenchmarkType>,
83    ) -> String
84    where
85        I: Iterator<Item = &'a Instance>,
86    {
87        let working_dir = self.working_dir.display();
88        let ips = instances
89            .map(|x| {
90                match parameters.use_internal_ip_address {
91                    true => x.private_ip,
92                    false => x.main_ip,
93                }
94                .to_string()
95            })
96            .collect::<Vec<_>>()
97            .join(" ");
98        let genesis = [
99            "cargo run --release --bin iota --",
100            "genesis",
101            &format!("-f --working-dir {working_dir} --benchmark-ips {ips}"),
102        ]
103        .join(" ");
104
105        [
106            &format!("mkdir -p {working_dir}"),
107            "source $HOME/.cargo/env",
108            &genesis,
109        ]
110        .join(" && ")
111    }
112
113    fn monitor_command<I>(&self, _instances: I) -> Vec<(Instance, String)>
114    where
115        I: IntoIterator<Item = Instance>,
116    {
117        // instances
118        //     .into_iter()
119        //     .map(|i| {
120        //         (
121        //             i,
122        //             "tail -f --pid=$(pidof iota) -f /dev/null; tail -100
123        // node.log".to_string(),         )
124        //     })
125        //     .collect()
126        vec![]
127    }
128
129    fn node_command<I>(
130        &self,
131        instances: I,
132        _parameters: &BenchmarkParameters<IotaBenchmarkType>,
133    ) -> Vec<(Instance, String)>
134    where
135        I: IntoIterator<Item = Instance>,
136    {
137        let working_dir = self.working_dir.clone();
138        let network_addresses = Self::resolve_network_addresses(instances);
139
140        network_addresses
141            .into_iter()
142            .enumerate()
143            .map(|(i, (instance, network_address))| {
144                let validator_config =
145                    iota_config::validator_config_file(network_address.clone(), i);
146                let config_path: PathBuf = working_dir.join(validator_config);
147
148                let run = [
149                    "cargo run --release --bin iota-node --",
150                    &format!(
151                        "--config-path {} --listen-address {}",
152                        config_path.display(),
153                        network_address.with_zero_ip()
154                    ),
155                ]
156                .join(" ");
157                let command = ["source $HOME/.cargo/env", &run].join(" && ");
158
159                display::action(format!("\n Command ({i}): {command}"));
160
161                (instance, command)
162            })
163            .collect()
164    }
165
166    fn client_command<I>(
167        &self,
168        instances: I,
169        parameters: &BenchmarkParameters<IotaBenchmarkType>,
170    ) -> Vec<(Instance, String)>
171    where
172        I: IntoIterator<Item = Instance>,
173    {
174        let genesis_path: PathBuf = [
175            &self.working_dir,
176            &iota_config::IOTA_GENESIS_FILENAME.into(),
177        ]
178        .iter()
179        .collect();
180        let keystore_path: PathBuf = [
181            &self.working_dir,
182            &iota_config::IOTA_BENCHMARK_GENESIS_GAS_KEYSTORE_FILENAME.into(),
183        ]
184        .iter()
185        .collect();
186
187        let committee_size = parameters.nodes;
188        let clients: Vec<_> = instances.into_iter().collect();
189        let load_share = parameters.load / clients.len();
190        let shared_counter = parameters.benchmark_type.shared_objects_ratio;
191        let transfer_objects = 100 - shared_counter;
192        let metrics_port = Self::CLIENT_METRICS_PORT;
193        let gas_keys = GenesisConfig::benchmark_gas_keys(committee_size);
194
195        clients
196            .into_iter()
197            .enumerate()
198            .map(|(i, instance)| {
199                let genesis = genesis_path.display();
200                let keystore = keystore_path.display();
201                let gas_key = &gas_keys[i % committee_size];
202                let gas_address = IotaAddress::from(&gas_key.public());
203
204                let run = [
205                    "cargo run --release --bin stress --",
206                    "--num-client-threads 24 --num-server-threads 1",
207                    "--local false --num-transfer-accounts 2",
208                    &format!("--genesis-blob-path {genesis} --keystore-path {keystore}",),
209                    &format!("--primary-gas-owner-id {gas_address}"),
210                    "bench",
211                    &format!("--in-flight-ratio 30 --num-workers 24 --target-qps {load_share}"),
212                    &format!(
213                        "--shared-counter {shared_counter} --transfer-object {transfer_objects}"
214                    ),
215                    "--shared-counter-hotness-factor 50",
216                    &format!("--client-metric-host 0.0.0.0 --client-metric-port {metrics_port}"),
217                ]
218                .join(" ");
219                let command = ["source $HOME/.cargo/env", &run].join(" && ");
220
221                (instance, command)
222            })
223            .collect()
224    }
225}
226
227impl IotaProtocol {
228    const CLIENT_METRICS_PORT: u16 = GenesisConfig::BENCHMARKS_PORT_OFFSET + 2000;
229
230    /// Make a new instance of the IOTA protocol commands generator.
231    pub fn new(settings: &Settings) -> Self {
232        Self {
233            working_dir: [&settings.working_dir, &iota_config::IOTA_CONFIG_DIR.into()]
234                .iter()
235                .collect(),
236        }
237    }
238
239    /// Creates the network addresses in multi address format for the instances.
240    /// It returns the Instance and the corresponding address.
241    pub fn resolve_network_addresses(
242        instances: impl IntoIterator<Item = Instance>,
243    ) -> Vec<(Instance, Multiaddr)> {
244        let instances: Vec<Instance> = instances.into_iter().collect();
245        let ips: Vec<_> = instances.iter().map(|x| x.main_ip.to_string()).collect();
246        let genesis_config = GenesisConfig::new_for_benchmarks(&ips);
247        let mut addresses = Vec::new();
248        if let Some(validator_configs) = genesis_config.validator_config_info.as_ref() {
249            for (i, validator_info) in validator_configs.iter().enumerate() {
250                let address = &validator_info.network_address;
251                addresses.push((instances[i].clone(), address.clone()));
252            }
253        }
254        addresses
255    }
256}
257
258impl ProtocolMetrics for IotaProtocol {
259    const BENCHMARK_DURATION: &'static str = "benchmark_duration";
260    const TOTAL_TRANSACTIONS: &'static str = "latency_s_count";
261    const LATENCY_BUCKETS: &'static str = "latency_s";
262    const LATENCY_SUM: &'static str = "latency_s_sum";
263    const LATENCY_SQUARED_SUM: &'static str = "latency_squared_s";
264
265    fn nodes_metrics_path<I, T>(
266        &self,
267        instances: I,
268        parameters: &BenchmarkParameters<T>,
269    ) -> Vec<(Instance, String)>
270    where
271        I: IntoIterator<Item = Instance>,
272        T: BenchmarkType,
273    {
274        let (ips, instances): (Vec<_>, Vec<_>) = instances
275            .into_iter()
276            .map(|x| {
277                (
278                    match parameters.use_internal_ip_address {
279                        true => x.private_ip,
280                        false => x.main_ip,
281                    }
282                    .to_string(),
283                    x,
284                )
285            })
286            .unzip();
287
288        GenesisConfig::new_for_benchmarks(&ips)
289            .validator_config_info
290            .expect("No validator in genesis")
291            .iter()
292            .zip(instances)
293            .map(|(config, instance)| {
294                let path = format!(
295                    "{}:{}{}",
296                    instance.main_ip,
297                    config.metrics_address.port(),
298                    iota_metrics::METRICS_ROUTE
299                );
300                (instance, path)
301            })
302            .collect()
303    }
304
305    fn clients_metrics_path<I>(&self, instances: I) -> Vec<(Instance, String)>
306    where
307        I: IntoIterator<Item = Instance>,
308    {
309        instances
310            .into_iter()
311            .map(|instance| {
312                let path = format!(
313                    "{}:{}{}",
314                    instance.main_ip,
315                    Self::CLIENT_METRICS_PORT,
316                    iota_metrics::METRICS_ROUTE
317                );
318                (instance, path)
319            })
320            .collect()
321    }
322}