linera_service/cli_wrappers/
remote_net.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{env, sync::Arc};
5
6use anyhow::Result;
7use async_trait::async_trait;
8use linera_base::data_types::Amount;
9use linera_persistent::{self as persistent, Persist};
10use tempfile::{tempdir, TempDir};
11
12use super::{
13    local_net::PathProvider, ClientWrapper, Faucet, LineraNet, LineraNetConfig, Network,
14    OnClientDrop,
15};
16
17pub struct RemoteNetTestingConfig {
18    faucet: Faucet,
19}
20
21impl RemoteNetTestingConfig {
22    /// Creates a new [`RemoteNetTestingConfig`] for running tests with an external Linera
23    /// network.
24    ///
25    /// The `faucet_url` is used to connect to the network and obtain its configuration,
26    /// as well as to create microchains used for testing. If the parameter is [`None`],
27    /// then it falls back to the URL specified in the `LINERA_FAUCET_URL` environment
28    /// variable, or the default devnet faucet URL.
29    pub fn new(faucet_url: Option<String>) -> Self {
30        Self {
31            faucet: Faucet::new(
32                faucet_url
33                    .or_else(|| env::var("LINERA_FAUCET_URL").ok())
34                    .expect("Missing `LINERA_FAUCET_URL` environment variable"),
35            ),
36        }
37    }
38}
39
40#[async_trait]
41impl LineraNetConfig for RemoteNetTestingConfig {
42    type Net = RemoteNet;
43
44    async fn instantiate(self) -> Result<(Self::Net, ClientWrapper)> {
45        let seed = 37;
46        let mut net = RemoteNet::new(Some(seed), &self.faucet)
47            .await
48            .expect("Creating RemoteNet should not fail");
49
50        let client = net.make_client().await;
51        // The tests assume we've created a genesis config with 2
52        // chains with 10 tokens each. We create the first chain here
53        client.wallet_init(Some(&self.faucet)).await?;
54        client.request_chain(&self.faucet, true).await?;
55
56        // And the remaining 2 here
57        for _ in 0..2 {
58            client
59                .open_and_assign(&client, Amount::from_tokens(10))
60                .await
61                .unwrap();
62        }
63
64        Ok((net, client))
65    }
66}
67
68/// Remote net
69#[derive(Clone)]
70pub struct RemoteNet {
71    network: Network,
72    testing_prng_seed: Option<u64>,
73    next_client_id: usize,
74    tmp_dir: Arc<TempDir>,
75}
76
77#[async_trait]
78impl LineraNet for RemoteNet {
79    async fn ensure_is_running(&mut self) -> Result<()> {
80        // Leaving this just returning for now.
81        // We would have to connect to each validator in the remote net then run
82        // ensure_connected_cluster_is_running
83        Ok(())
84    }
85
86    async fn make_client(&mut self) -> ClientWrapper {
87        let path_provider = PathProvider::TemporaryDirectory {
88            tmp_dir: self.tmp_dir.clone(),
89        };
90        let client = ClientWrapper::new(
91            path_provider,
92            self.network,
93            self.testing_prng_seed,
94            self.next_client_id,
95            OnClientDrop::CloseChains,
96        );
97        if let Some(seed) = self.testing_prng_seed {
98            self.testing_prng_seed = Some(seed + 1);
99        }
100        self.next_client_id += 1;
101        client
102    }
103
104    async fn terminate(&mut self) -> Result<()> {
105        // We're not killing the remote net :)
106        Ok(())
107    }
108}
109
110impl RemoteNet {
111    async fn new(testing_prng_seed: Option<u64>, faucet: &Faucet) -> Result<Self> {
112        let tmp_dir = Arc::new(tempdir()?);
113        // Write json config to disk
114        persistent::File::new(
115            tmp_dir.path().join("genesis.json").as_path(),
116            faucet.genesis_config().await?,
117        )?
118        .persist()
119        .await?;
120        Ok(Self {
121            network: Network::Grpc,
122            testing_prng_seed,
123            next_client_id: 0,
124            tmp_dir,
125        })
126    }
127}