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
17use crate::{
18 cli_wrappers::{
19 local_net::{
20 Database, ExportersSetup, InnerStorageConfigBuilder, LocalNetConfig, PathProvider,
21 },
22 ClientWrapper, FaucetService, LineraNet, LineraNetConfig, Network, NetworkConfig,
23 },
24 storage::{InnerStorageConfig, StorageConfig},
25};
26
27struct StorageConfigProvider {
28 config: StorageConfig,
30 #[cfg(feature = "storage-service")]
31 _service_guard: Option<StorageServiceGuard>,
32}
33
34impl StorageConfigProvider {
35 pub async fn new(storage: &Option<String>) -> anyhow::Result<StorageConfigProvider> {
36 match storage {
37 #[cfg(feature = "storage-service")]
38 None => {
39 let service_endpoint = linera_base::port::get_free_endpoint().await?;
40 let binary = get_service_storage_binary().await?.display().to_string();
41 let service = StorageService::new(&service_endpoint, binary);
42 let service_guard = Some(service.run().await?);
43 let inner_storage_config = InnerStorageConfig::Service {
44 endpoint: service_endpoint,
45 };
46 let namespace = "table_default".to_string();
47 let config = StorageConfig {
48 inner_storage_config,
49 namespace,
50 };
51 Ok(StorageConfigProvider {
52 config,
53 _service_guard: service_guard,
54 })
55 }
56 #[cfg(not(feature = "storage-service"))]
57 None => {
58 panic!("When storage is not selected, the storage-service needs to be enabled");
59 }
60 #[cfg(feature = "storage-service")]
61 Some(storage) => {
62 let config = StorageConfig::from_str(storage)?;
63 Ok(StorageConfigProvider {
64 config,
65 _service_guard: None,
66 })
67 }
68 #[cfg(not(feature = "storage-service"))]
69 Some(storage) => {
70 let config = StorageConfig::from_str(storage)?;
71 Ok(StorageConfigProvider { config })
72 }
73 }
74 }
75
76 pub fn inner_storage_config(&self) -> &InnerStorageConfig {
77 &self.config.inner_storage_config
78 }
79
80 pub fn namespace(&self) -> &str {
81 &self.config.namespace
82 }
83
84 pub fn database(&self) -> anyhow::Result<Database> {
85 match self.config.inner_storage_config {
86 InnerStorageConfig::Memory { .. } => anyhow::bail!("Not possible to work with memory"),
87 #[cfg(feature = "rocksdb")]
88 InnerStorageConfig::RocksDb { .. } => {
89 anyhow::bail!("Not possible to work with RocksDB")
90 }
91 #[cfg(feature = "storage-service")]
92 InnerStorageConfig::Service { .. } => Ok(Database::Service),
93 #[cfg(feature = "scylladb")]
94 InnerStorageConfig::ScyllaDb { .. } => Ok(Database::ScyllaDb),
95 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
96 InnerStorageConfig::DualRocksDbScyllaDb { .. } => Ok(Database::DualRocksDbScyllaDb),
97 }
98 }
99}
100
101#[expect(clippy::too_many_arguments)]
102pub async fn handle_net_up_service(
103 num_other_initial_chains: u32,
104 initial_amount: u128,
105 num_initial_validators: usize,
106 num_shards: usize,
107 testing_prng_seed: Option<u64>,
108 policy_config: ResourceControlPolicyConfig,
109 cross_chain_config: CrossChainConfig,
110 with_block_exporter: bool,
111 block_exporter_address: String,
112 block_exporter_port: NonZeroU16,
113 path: &Option<String>,
114 storage: &Option<String>,
115 external_protocol: String,
116 with_faucet: bool,
117 faucet_port: NonZeroU16,
118 faucet_amount: Amount,
119 http_request_allow_list: Option<Vec<String>>,
120) -> anyhow::Result<()> {
121 assert!(
122 num_initial_validators >= 1,
123 "The local test network must have at least one validator."
124 );
125 assert!(
126 num_shards >= 1,
127 "The local test network must have at least one shard per validator."
128 );
129
130 let shutdown_notifier = CancellationToken::new();
131 tokio::spawn(listen_for_shutdown_signals(shutdown_notifier.clone()));
132
133 let storage = StorageConfigProvider::new(storage).await?;
134 let storage_config = storage.inner_storage_config().clone();
135 let namespace = storage.namespace().to_string();
136 let database = storage.database()?;
137 let storage_config_builder = InnerStorageConfigBuilder::ExistingConfig { storage_config };
138 let external = match external_protocol.as_str() {
139 "grpc" => Network::Grpc,
140 "grpcs" => Network::Grpcs,
141 _ => panic!("Only allowed options are grpc and grpcs"),
142 };
143 let internal = Network::Grpc;
144 let network = NetworkConfig { external, internal };
145 let path_provider = PathProvider::from_path_option(path)?;
146 let num_proxies = 1; let block_exporters = ExportersSetup::new(
148 with_block_exporter,
149 block_exporter_address,
150 block_exporter_port,
151 );
152 let initial_amount = Amount::from_tokens(initial_amount);
153 let config = LocalNetConfig {
154 network,
155 database,
156 testing_prng_seed,
157 namespace,
158 num_other_initial_chains,
159 initial_amount,
160 num_initial_validators,
161 num_shards,
162 num_proxies,
163 policy_config,
164 http_request_allow_list,
165 cross_chain_config,
166 storage_config_builder,
167 path_provider,
168 block_exporters,
169 binary_dir: None,
170 };
171 let (mut net, client) = config.instantiate().await?;
172 let faucet_service = print_messages_and_create_faucet(
173 client,
174 &mut net,
175 with_faucet,
176 faucet_port,
177 faucet_amount,
178 initial_amount,
179 )
180 .await?;
181
182 wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
183}
184
185async fn wait_for_shutdown(
186 shutdown_notifier: CancellationToken,
187 net: &mut impl LineraNet,
188 faucet_service: Option<FaucetService>,
189) -> anyhow::Result<()> {
190 shutdown_notifier.cancelled().await;
191 eprintln!();
192 if let Some(service) = faucet_service {
193 eprintln!("Terminating the faucet service");
194 service.terminate().await?;
195 }
196 eprintln!("Terminating the local test network");
197 net.terminate().await?;
198 eprintln!("Done.");
199
200 Ok(())
201}
202
203async fn print_messages_and_create_faucet(
204 client: ClientWrapper,
205 net: &mut impl LineraNet,
206 with_faucet: bool,
207 faucet_port: NonZeroU16,
208 faucet_amount: Amount,
209 initial_amount: Amount,
210) -> Result<Option<FaucetService>, anyhow::Error> {
211 linera_base::time::timer::sleep(Duration::from_secs(1)).await;
213
214 info!("Local test network successfully started.");
215
216 eprintln!(
217 "To use the admin wallet of this test network, you may set \
218 the environment variables LINERA_WALLET, LINERA_KEYSTORE, \
219 and LINERA_STORAGE as follows.\n"
220 );
221 println!(
222 "export LINERA_WALLET=\"{}\"",
223 client.wallet_path().display(),
224 );
225 println!(
226 "export LINERA_KEYSTORE=\"{}\"",
227 client.keystore_path().display(),
228 );
229 println!("export LINERA_STORAGE=\"{}\"", client.storage_path(),);
230
231 let faucet_service = if with_faucet {
234 let faucet_client = net.make_client().await;
235 faucet_client.wallet_init(None).await?;
236 let faucet_balance = Amount::from_attos(initial_amount.to_attos() / 2);
237 let faucet_chain = client
238 .open_and_assign(&faucet_client, faucet_balance)
239 .await?;
240
241 eprintln!("To connect to this network, you can use the following faucet URL:");
242 println!("export LINERA_FAUCET_URL=\"http://localhost:{faucet_port}\"");
243
244 let service = faucet_client
245 .run_faucet(Some(faucet_port.into()), Some(faucet_chain), faucet_amount)
246 .await?;
247 Some(service)
248 } else {
249 None
250 };
251
252 println!();
253
254 eprintln!(
255 "\nREADY!\nPress ^C to terminate the local test network and clean the temporary directory."
256 );
257
258 Ok(faucet_service)
259}