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 block_export_transport: crate::config::BlockExportTransport::Relay,
156 export_blocks_to_committee: false,
157 network,
158 database,
159 testing_prng_seed,
160 namespace,
161 num_other_initial_chains,
162 initial_amount,
163 num_initial_validators,
164 num_shards,
165 num_proxies,
166 policy_config,
167 http_request_allow_list,
168 cross_chain_config,
169 storage_config_builder,
170 path_provider,
171 block_exporters,
172 binary_dir: None,
173 };
174 let (mut net, client) = config.instantiate().await?;
175 let faucet_service = print_messages_and_create_faucet(
176 client,
177 &mut net,
178 with_faucet,
179 faucet_port,
180 faucet_amount,
181 initial_amount,
182 )
183 .await?;
184
185 wait_for_shutdown(shutdown_notifier, &mut net, faucet_service).await
186}
187
188async fn wait_for_shutdown(
189 shutdown_notifier: CancellationToken,
190 net: &mut impl LineraNet,
191 faucet_service: Option<FaucetService>,
192) -> anyhow::Result<()> {
193 shutdown_notifier.cancelled().await;
194 eprintln!();
195 if let Some(service) = faucet_service {
196 eprintln!("Terminating the faucet service");
197 service.terminate().await?;
198 }
199 eprintln!("Terminating the local test network");
200 net.terminate().await?;
201 eprintln!("Done.");
202
203 Ok(())
204}
205
206async fn print_messages_and_create_faucet(
207 client: ClientWrapper,
208 net: &mut impl LineraNet,
209 with_faucet: bool,
210 faucet_port: NonZeroU16,
211 faucet_amount: Amount,
212 initial_amount: Amount,
213) -> Result<Option<FaucetService>, anyhow::Error> {
214 linera_base::time::timer::sleep(Duration::from_secs(1)).await;
216
217 info!("Local test network successfully started.");
218
219 eprintln!(
220 "To use the admin wallet of this test network, you may set \
221 the environment variables LINERA_WALLET, LINERA_KEYSTORE, \
222 and LINERA_STORAGE as follows.\n"
223 );
224 println!(
225 "export LINERA_WALLET=\"{}\"",
226 client.wallet_path().display(),
227 );
228 println!(
229 "export LINERA_KEYSTORE=\"{}\"",
230 client.keystore_path().display(),
231 );
232 println!("export LINERA_STORAGE=\"{}\"", client.storage_path(),);
233
234 let faucet_service = if with_faucet {
237 let faucet_client = net.make_client().await;
238 faucet_client.wallet_init(None).await?;
239 let faucet_balance = Amount::from_attos(initial_amount.to_attos() / 2);
240 let faucet_chain = client
241 .open_and_assign(&faucet_client, faucet_balance)
242 .await?;
243
244 eprintln!("To connect to this network, you can use the following faucet URL:");
245 println!("export LINERA_FAUCET_URL=\"http://localhost:{faucet_port}\"");
246
247 let service = faucet_client
248 .run_faucet(Some(faucet_port.into()), Some(faucet_chain), faucet_amount)
249 .await?;
250 Some(service)
251 } else {
252 None
253 };
254
255 println!();
256
257 eprintln!(
258 "\nREADY!\nPress ^C to terminate the local test network and clean the temporary directory."
259 );
260
261 Ok(faucet_service)
262}