1use std::{num::NonZeroU16, str::FromStr};
5
6use linera_base::{data_types::Amount, listen_for_shutdown_signals, time::Duration};
7use linera_client::client_options::ResourceControlPolicyConfig;
8use linera_rpc::config::CrossChainConfig;
9#[cfg(feature = "storage-service")]
10use linera_storage_service::{
11 child::{StorageService, StorageServiceGuard},
12 common::get_service_storage_binary,
13};
14use tokio_util::sync::CancellationToken;
15use tracing::info;
16#[cfg(feature = "kubernetes")]
17use {
18 crate::cli_wrappers::local_kubernetes_net::{BuildMode, LocalKubernetesNetConfig},
19 std::path::PathBuf,
20};
21
22use crate::{
23 cli_wrappers::{
24 local_net::{
25 Database, ExportersSetup, InnerStorageConfigBuilder, LocalNetConfig, PathProvider,
26 },
27 ClientWrapper, FaucetService, LineraNet, LineraNetConfig, Network, NetworkConfig,
28 },
29 storage::{InnerStorageConfig, StorageConfig},
30};
31
32struct StorageConfigProvider {
33 config: StorageConfig,
35 #[cfg(feature = "storage-service")]
36 _service_guard: Option<StorageServiceGuard>,
37}
38
39impl StorageConfigProvider {
40 pub async fn new(storage: &Option<String>) -> anyhow::Result<StorageConfigProvider> {
41 match storage {
42 #[cfg(feature = "storage-service")]
43 None => {
44 let service_endpoint = linera_base::port::get_free_endpoint().await?;
45 let binary = get_service_storage_binary().await?.display().to_string();
46 let service = StorageService::new(&service_endpoint, binary);
47 let service_guard = Some(service.run().await?);
48 let inner_storage_config = InnerStorageConfig::Service {
49 endpoint: service_endpoint,
50 };
51 let namespace = "table_default".to_string();
52 let config = StorageConfig {
53 inner_storage_config,
54 namespace,
55 };
56 Ok(StorageConfigProvider {
57 config,
58 _service_guard: service_guard,
59 })
60 }
61 #[cfg(not(feature = "storage-service"))]
62 None => {
63 panic!("When storage is not selected, the storage-service needs to be enabled");
64 }
65 #[cfg(feature = "storage-service")]
66 Some(storage) => {
67 let config = StorageConfig::from_str(storage)?;
68 Ok(StorageConfigProvider {
69 config,
70 _service_guard: None,
71 })
72 }
73 #[cfg(not(feature = "storage-service"))]
74 Some(storage) => {
75 let config = StorageConfig::from_str(storage)?;
76 Ok(StorageConfigProvider { config })
77 }
78 }
79 }
80
81 pub fn inner_storage_config(&self) -> &InnerStorageConfig {
82 &self.config.inner_storage_config
83 }
84
85 pub fn namespace(&self) -> &str {
86 &self.config.namespace
87 }
88
89 pub fn database(&self) -> anyhow::Result<Database> {
90 match self.config.inner_storage_config {
91 InnerStorageConfig::Memory { .. } => anyhow::bail!("Not possible to work with memory"),
92 #[cfg(feature = "rocksdb")]
93 InnerStorageConfig::RocksDb { .. } => {
94 anyhow::bail!("Not possible to work with RocksDB")
95 }
96 #[cfg(feature = "storage-service")]
97 InnerStorageConfig::Service { .. } => Ok(Database::Service),
98 #[cfg(feature = "dynamodb")]
99 InnerStorageConfig::DynamoDb { .. } => Ok(Database::DynamoDb),
100 #[cfg(feature = "scylladb")]
101 InnerStorageConfig::ScyllaDb { .. } => Ok(Database::ScyllaDb),
102 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
103 InnerStorageConfig::DualRocksDbScyllaDb { .. } => Ok(Database::DualRocksDbScyllaDb),
104 }
105 }
106}
107
108#[expect(clippy::too_many_arguments)]
109#[cfg(feature = "kubernetes")]
110pub async fn handle_net_up_kubernetes(
111 num_other_initial_chains: u32,
112 initial_amount: u128,
113 num_initial_validators: usize,
114 num_proxies: usize,
115 num_shards: usize,
116 testing_prng_seed: Option<u64>,
117 binaries: &Option<Option<PathBuf>>,
118 no_build: bool,
119 docker_image_name: String,
120 build_mode: BuildMode,
121 policy_config: ResourceControlPolicyConfig,
122 with_faucet: bool,
123 faucet_port: NonZeroU16,
124 faucet_amount: Amount,
125 with_block_exporter: bool,
126 num_block_exporters: usize,
127 indexer_image_name: String,
128 explorer_image_name: String,
129 dual_store: bool,
130 path: &Option<String>,
131) -> anyhow::Result<()> {
132 assert!(
133 num_initial_validators >= 1,
134 "The local test network must have at least one validator."
135 );
136 assert!(
137 num_proxies >= 1,
138 "The local test network must have at least one proxy."
139 );
140 assert!(
141 num_shards >= 1,
142 "The local test network must have at least one shard per validator."
143 );
144
145 let shutdown_notifier = CancellationToken::new();
146 tokio::spawn(listen_for_shutdown_signals(shutdown_notifier.clone()));
147
148 let num_block_exporters = if with_block_exporter {
149 assert!(
150 num_block_exporters > 0,
151 "If --with-block-exporter is provided, --num-block-exporters must be greater than 0"
152 );
153 num_block_exporters
154 } else {
155 0
156 };
157
158 let initial_amount = Amount::from_tokens(initial_amount);
159 let config = LocalKubernetesNetConfig {
160 network: Network::Grpc,
161 testing_prng_seed,
162 num_other_initial_chains,
163 initial_amount,
164 num_initial_validators,
165 num_proxies,
166 num_shards,
167 binaries: binaries.clone().into(),
168 no_build,
169 docker_image_name,
170 build_mode,
171 policy_config,
172 num_block_exporters,
173 indexer_image_name,
174 explorer_image_name,
175 dual_store,
176 path_provider: PathProvider::from_path_option(path)?,
177 };
178 let (mut net, client) = config.instantiate().await?;
179 let faucet_service = print_messages_and_create_faucet(
180 client,
181 &mut net,
182 with_faucet,
183 faucet_port,
184 faucet_amount,
185 initial_amount,
186 )
187 .await?;
188 wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
189}
190
191#[expect(clippy::too_many_arguments)]
192pub async fn handle_net_up_service(
193 num_other_initial_chains: u32,
194 initial_amount: u128,
195 num_initial_validators: usize,
196 num_shards: usize,
197 testing_prng_seed: Option<u64>,
198 policy_config: ResourceControlPolicyConfig,
199 cross_chain_config: CrossChainConfig,
200 with_block_exporter: bool,
201 block_exporter_address: String,
202 block_exporter_port: NonZeroU16,
203 path: &Option<String>,
204 storage: &Option<String>,
205 external_protocol: String,
206 with_faucet: bool,
207 faucet_port: NonZeroU16,
208 faucet_amount: Amount,
209 http_request_allow_list: Option<Vec<String>>,
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,
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 "export LINERA_WALLET=\"{}\"",
313 client.wallet_path().display(),
314 );
315 println!(
316 "export LINERA_KEYSTORE=\"{}\"",
317 client.keystore_path().display(),
318 );
319 println!("export LINERA_STORAGE=\"{}\"", client.storage_path(),);
320
321 let faucet_service = if with_faucet {
324 let faucet_client = net.make_client().await;
325 faucet_client.wallet_init(None).await?;
326 let faucet_balance = Amount::from_attos(initial_amount.to_attos() / 2);
327 let faucet_chain = client
328 .open_and_assign(&faucet_client, faucet_balance)
329 .await?;
330
331 eprintln!("To connect to this network, you can use the following faucet URL:");
332 println!("export LINERA_FAUCET_URL=\"http://localhost:{faucet_port}\"");
333
334 let service = faucet_client
335 .run_faucet(Some(faucet_port.into()), Some(faucet_chain), faucet_amount)
336 .await?;
337 Some(service)
338 } else {
339 None
340 };
341
342 println!();
343
344 eprintln!(
345 "\nREADY!\nPress ^C to terminate the local test network and clean the temporary directory."
346 );
347
348 Ok(faucet_service)
349}