1use std::{num::NonZeroU16, str::FromStr};
5
6use colored::Colorize as _;
7use linera_base::{data_types::Amount, listen_for_shutdown_signals, time::Duration};
8use linera_client::client_options::ResourceControlPolicyConfig;
9use linera_rpc::config::CrossChainConfig;
10#[cfg(feature = "storage-service")]
11use linera_storage_service::{
12 child::{StorageService, StorageServiceGuard},
13 common::get_service_storage_binary,
14};
15use tokio_util::sync::CancellationToken;
16use tracing::info;
17#[cfg(feature = "kubernetes")]
18use {
19 crate::cli_wrappers::local_kubernetes_net::{BuildMode, LocalKubernetesNetConfig},
20 std::path::PathBuf,
21};
22
23use crate::{
24 cli_wrappers::{
25 local_net::{
26 Database, ExportersSetup, InnerStorageConfigBuilder, LocalNetConfig, PathProvider,
27 },
28 ClientWrapper, FaucetService, LineraNet, LineraNetConfig, Network, NetworkConfig,
29 },
30 storage::{InnerStorageConfig, StorageConfig},
31};
32
33struct StorageConfigProvider {
34 config: StorageConfig,
36 #[cfg(feature = "storage-service")]
37 _service_guard: Option<StorageServiceGuard>,
38}
39
40impl StorageConfigProvider {
41 pub async fn new(storage: &Option<String>) -> anyhow::Result<StorageConfigProvider> {
42 match storage {
43 #[cfg(feature = "storage-service")]
44 None => {
45 let service_endpoint = linera_base::port::get_free_endpoint().await?;
46 let binary = get_service_storage_binary().await?.display().to_string();
47 let service = StorageService::new(&service_endpoint, binary);
48 let service_guard = Some(service.run().await?);
49 let inner_storage_config = InnerStorageConfig::Service {
50 endpoint: service_endpoint,
51 };
52 let namespace = "table_default".to_string();
53 let config = StorageConfig {
54 inner_storage_config,
55 namespace,
56 };
57 Ok(StorageConfigProvider {
58 config,
59 _service_guard: service_guard,
60 })
61 }
62 #[cfg(not(feature = "storage-service"))]
63 None => {
64 panic!("When storage is not selected, the storage-service needs to be enabled");
65 }
66 #[cfg(feature = "storage-service")]
67 Some(storage) => {
68 let config = StorageConfig::from_str(storage)?;
69 Ok(StorageConfigProvider {
70 config,
71 _service_guard: None,
72 })
73 }
74 #[cfg(not(feature = "storage-service"))]
75 Some(storage) => {
76 let config = StorageConfig::from_str(storage)?;
77 Ok(StorageConfigProvider { config })
78 }
79 }
80 }
81
82 pub fn inner_storage_config(&self) -> &InnerStorageConfig {
83 &self.config.inner_storage_config
84 }
85
86 pub fn namespace(&self) -> &str {
87 &self.config.namespace
88 }
89
90 pub fn database(&self) -> anyhow::Result<Database> {
91 match self.config.inner_storage_config {
92 InnerStorageConfig::Memory { .. } => anyhow::bail!("Not possible to work with memory"),
93 #[cfg(feature = "rocksdb")]
94 InnerStorageConfig::RocksDb { .. } => {
95 anyhow::bail!("Not possible to work with RocksDB")
96 }
97 #[cfg(feature = "storage-service")]
98 InnerStorageConfig::Service { .. } => Ok(Database::Service),
99 #[cfg(feature = "dynamodb")]
100 InnerStorageConfig::DynamoDb { .. } => Ok(Database::DynamoDb),
101 #[cfg(feature = "scylladb")]
102 InnerStorageConfig::ScyllaDb { .. } => Ok(Database::ScyllaDb),
103 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
104 InnerStorageConfig::DualRocksDbScyllaDb { .. } => Ok(Database::DualRocksDbScyllaDb),
105 }
106 }
107}
108
109#[expect(clippy::too_many_arguments)]
110#[cfg(feature = "kubernetes")]
111pub async fn handle_net_up_kubernetes(
112 num_other_initial_chains: u32,
113 initial_amount: u128,
114 num_initial_validators: usize,
115 num_proxies: usize,
116 num_shards: usize,
117 testing_prng_seed: Option<u64>,
118 binaries: &Option<Option<PathBuf>>,
119 no_build: bool,
120 docker_image_name: String,
121 build_mode: BuildMode,
122 policy_config: ResourceControlPolicyConfig,
123 with_faucet: bool,
124 faucet_port: NonZeroU16,
125 faucet_amount: Amount,
126 with_block_exporter: bool,
127 num_block_exporters: usize,
128 indexer_image_name: String,
129 explorer_image_name: String,
130 dual_store: bool,
131 path: &Option<String>,
132) -> anyhow::Result<()> {
133 assert!(
134 num_initial_validators >= 1,
135 "The local test network must have at least one validator."
136 );
137 assert!(
138 num_proxies >= 1,
139 "The local test network must have at least one proxy."
140 );
141 assert!(
142 num_shards >= 1,
143 "The local test network must have at least one shard per validator."
144 );
145
146 let shutdown_notifier = CancellationToken::new();
147 tokio::spawn(listen_for_shutdown_signals(shutdown_notifier.clone()));
148
149 let num_block_exporters = if with_block_exporter {
150 assert!(
151 num_block_exporters > 0,
152 "If --with-block-exporter is provided, --num-block-exporters must be greater than 0"
153 );
154 num_block_exporters
155 } else {
156 0
157 };
158
159 let initial_amount = Amount::from_tokens(initial_amount);
160 let config = LocalKubernetesNetConfig {
161 network: Network::Grpc,
162 testing_prng_seed,
163 num_other_initial_chains,
164 initial_amount,
165 num_initial_validators,
166 num_proxies,
167 num_shards,
168 binaries: binaries.clone().into(),
169 no_build,
170 docker_image_name,
171 build_mode,
172 policy_config,
173 num_block_exporters,
174 indexer_image_name,
175 explorer_image_name,
176 dual_store,
177 path_provider: PathProvider::from_path_option(path)?,
178 };
179 let (mut net, client) = config.instantiate().await?;
180 let faucet_service = print_messages_and_create_faucet(
181 client,
182 &mut net,
183 with_faucet,
184 faucet_port,
185 faucet_amount,
186 initial_amount,
187 )
188 .await?;
189 wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
190}
191
192#[expect(clippy::too_many_arguments)]
193pub async fn handle_net_up_service(
194 num_other_initial_chains: u32,
195 initial_amount: u128,
196 num_initial_validators: usize,
197 num_shards: usize,
198 testing_prng_seed: Option<u64>,
199 policy_config: ResourceControlPolicyConfig,
200 cross_chain_config: CrossChainConfig,
201 with_block_exporter: bool,
202 block_exporter_address: String,
203 block_exporter_port: NonZeroU16,
204 path: &Option<String>,
205 storage: &Option<String>,
206 external_protocol: String,
207 with_faucet: bool,
208 faucet_port: NonZeroU16,
209 faucet_amount: Amount,
210) -> anyhow::Result<()> {
211 assert!(
212 num_initial_validators >= 1,
213 "The local test network must have at least one validator."
214 );
215 assert!(
216 num_shards >= 1,
217 "The local test network must have at least one shard per validator."
218 );
219
220 let shutdown_notifier = CancellationToken::new();
221 tokio::spawn(listen_for_shutdown_signals(shutdown_notifier.clone()));
222
223 let storage = StorageConfigProvider::new(storage).await?;
224 let storage_config = storage.inner_storage_config().clone();
225 let namespace = storage.namespace().to_string();
226 let database = storage.database()?;
227 let storage_config_builder = InnerStorageConfigBuilder::ExistingConfig { storage_config };
228 let external = match external_protocol.as_str() {
229 "grpc" => Network::Grpc,
230 "grpcs" => Network::Grpcs,
231 _ => panic!("Only allowed options are grpc and grpcs"),
232 };
233 let internal = Network::Grpc;
234 let network = NetworkConfig { external, internal };
235 let path_provider = PathProvider::from_path_option(path)?;
236 let num_proxies = 1; let block_exporters = ExportersSetup::new(
238 with_block_exporter,
239 block_exporter_address,
240 block_exporter_port,
241 );
242 let initial_amount = Amount::from_tokens(initial_amount);
243 let config = LocalNetConfig {
244 network,
245 database,
246 testing_prng_seed,
247 namespace,
248 num_other_initial_chains,
249 initial_amount,
250 num_initial_validators,
251 num_shards,
252 num_proxies,
253 policy_config,
254 http_request_allow_list: None,
255 cross_chain_config,
256 storage_config_builder,
257 path_provider,
258 block_exporters,
259 binary_dir: None,
260 };
261 let (mut net, client) = config.instantiate().await?;
262 let faucet_service = print_messages_and_create_faucet(
263 client,
264 &mut net,
265 with_faucet,
266 faucet_port,
267 faucet_amount,
268 initial_amount,
269 )
270 .await?;
271
272 wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
273}
274
275async fn wait_for_shutdown(
276 shutdown_notifier: CancellationToken,
277 net: &mut impl LineraNet,
278 faucet_service: Option<FaucetService>,
279) -> anyhow::Result<()> {
280 shutdown_notifier.cancelled().await;
281 eprintln!();
282 if let Some(service) = faucet_service {
283 eprintln!("Terminating the faucet service");
284 service.terminate().await?;
285 }
286 eprintln!("Terminating the local test network");
287 net.terminate().await?;
288 eprintln!("Done.");
289
290 Ok(())
291}
292
293async fn print_messages_and_create_faucet(
294 client: ClientWrapper,
295 net: &mut impl LineraNet,
296 with_faucet: bool,
297 faucet_port: NonZeroU16,
298 faucet_amount: Amount,
299 initial_amount: Amount,
300) -> Result<Option<FaucetService>, anyhow::Error> {
301 linera_base::time::timer::sleep(Duration::from_secs(1)).await;
303
304 info!("Local test network successfully started.");
305
306 eprintln!(
307 "To use the admin wallet of this test network, you may set \
308 the environment variables LINERA_WALLET, LINERA_KEYSTORE, \
309 and LINERA_STORAGE as follows.\n"
310 );
311 println!(
312 "{}",
313 format!(
314 "export LINERA_WALLET=\"{}\"",
315 client.wallet_path().display(),
316 )
317 .bold(),
318 );
319 println!(
320 "{}",
321 format!(
322 "export LINERA_KEYSTORE=\"{}\"",
323 client.keystore_path().display(),
324 )
325 .bold()
326 );
327 println!(
328 "{}",
329 format!("export LINERA_STORAGE=\"{}\"", client.storage_path()).bold(),
330 );
331
332 let faucet_service = if with_faucet {
335 let faucet_client = net.make_client().await;
336 faucet_client.wallet_init(None).await?;
337 let faucet_balance = Amount::from_attos(initial_amount.to_attos() / 2);
338 let faucet_chain = client
339 .open_and_assign(&faucet_client, faucet_balance)
340 .await?;
341
342 eprintln!("To connect to this network, you can use the following faucet URL:");
343 println!(
344 "{}",
345 format!("export LINERA_FAUCET_URL=\"http://localhost:{faucet_port}\"").bold(),
346 );
347
348 let service = faucet_client
349 .run_faucet(Some(faucet_port.into()), Some(faucet_chain), faucet_amount)
350 .await?;
351 Some(service)
352 } else {
353 None
354 };
355
356 println!();
357
358 eprintln!(
359 "\nREADY!\nPress ^C to terminate the local test network and clean the temporary directory."
360 );
361
362 Ok(faucet_service)
363}