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)]
103pub async fn handle_net_up_service(
104 num_other_initial_chains: u32,
105 initial_amount: u128,
106 num_initial_validators: usize,
107 num_shards: usize,
108 testing_prng_seed: Option<u64>,
109 policy_config: ResourceControlPolicyConfig,
110 cross_chain_config: CrossChainConfig,
111 with_block_exporter: bool,
112 block_exporter_address: String,
113 block_exporter_port: NonZeroU16,
114 path: &Option<String>,
115 storage: &Option<String>,
116 external_protocol: String,
117 with_faucet: bool,
118 faucet_port: NonZeroU16,
119 faucet_amount: Amount,
120 http_request_allow_list: Option<Vec<String>>,
121) -> anyhow::Result<()> {
122 assert!(
123 num_initial_validators >= 1,
124 "The local test network must have at least one validator."
125 );
126 assert!(
127 num_shards >= 1,
128 "The local test network must have at least one shard per validator."
129 );
130
131 let shutdown_notifier = CancellationToken::new();
132 tokio::spawn(listen_for_shutdown_signals(shutdown_notifier.clone()));
133
134 let storage = StorageConfigProvider::new(storage).await?;
135 let storage_config = storage.inner_storage_config().clone();
136 let namespace = storage.namespace().to_string();
137 let database = storage.database()?;
138 let storage_config_builder = InnerStorageConfigBuilder::ExistingConfig { storage_config };
139 let external = match external_protocol.as_str() {
140 "grpc" => Network::Grpc,
141 "grpcs" => Network::Grpcs,
142 _ => panic!("Only allowed options are grpc and grpcs"),
143 };
144 let internal = Network::Grpc;
145 let network = NetworkConfig { external, internal };
146 let path_provider = PathProvider::from_path_option(path)?;
147 let num_proxies = 1; let block_exporters = ExportersSetup::new(
149 with_block_exporter,
150 block_exporter_address,
151 block_exporter_port,
152 );
153 let initial_amount = Amount::from_tokens(initial_amount);
154 let config = LocalNetConfig {
155 network,
156 database,
157 testing_prng_seed,
158 namespace,
159 num_other_initial_chains,
160 initial_amount,
161 num_initial_validators,
162 num_shards,
163 num_proxies,
164 policy_config,
165 http_request_allow_list,
166 cross_chain_config,
167 storage_config_builder,
168 path_provider,
169 block_exporters,
170 binary_dir: None,
171 };
172 let (mut net, client) = config.instantiate().await?;
173 let faucet_service = print_messages_and_create_faucet(
174 client,
175 &mut net,
176 with_faucet,
177 faucet_port,
178 faucet_amount,
179 initial_amount,
180 )
181 .await?;
182
183 wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
184}
185
186async fn wait_for_shutdown(
187 shutdown_notifier: CancellationToken,
188 net: &mut impl LineraNet,
189 faucet_service: Option<FaucetService>,
190) -> anyhow::Result<()> {
191 shutdown_notifier.cancelled().await;
192 eprintln!();
193 if let Some(service) = faucet_service {
194 eprintln!("Terminating the faucet service");
195 service.terminate().await?;
196 }
197 eprintln!("Terminating the local test network");
198 net.terminate().await?;
199 eprintln!("Done.");
200
201 Ok(())
202}
203
204async fn print_messages_and_create_faucet(
205 client: ClientWrapper,
206 net: &mut impl LineraNet,
207 with_faucet: bool,
208 faucet_port: NonZeroU16,
209 faucet_amount: Amount,
210 initial_amount: Amount,
211) -> Result<Option<FaucetService>, anyhow::Error> {
212 linera_base::time::timer::sleep(Duration::from_secs(1)).await;
214
215 info!("Local test network successfully started.");
216
217 eprintln!(
218 "To use the admin wallet of this test network, you may set \
219 the environment variables LINERA_WALLET, LINERA_KEYSTORE, \
220 and LINERA_STORAGE as follows.\n"
221 );
222 println!(
223 "export LINERA_WALLET=\"{}\"",
224 client.wallet_path().display(),
225 );
226 println!(
227 "export LINERA_KEYSTORE=\"{}\"",
228 client.keystore_path().display(),
229 );
230 println!("export LINERA_STORAGE=\"{}\"", client.storage_path(),);
231
232 let faucet_service = if with_faucet {
235 let faucet_client = net.make_client().await;
236 faucet_client.wallet_init(None).await?;
237 let faucet_balance = Amount::from_attos(initial_amount.to_attos() / 2);
238 let faucet_chain = client
239 .open_and_assign(&faucet_client, faucet_balance)
240 .await?;
241
242 eprintln!("To connect to this network, you can use the following faucet URL:");
243 println!("export LINERA_FAUCET_URL=\"http://localhost:{faucet_port}\"");
244
245 let service = faucet_client
246 .run_faucet(Some(faucet_port.into()), Some(faucet_chain), faucet_amount)
247 .await?;
248 Some(service)
249 } else {
250 None
251 };
252
253 println!();
254
255 eprintln!(
256 "\nREADY!\nPress ^C to terminate the local test network and clean the temporary directory."
257 );
258
259 Ok(faucet_service)
260}