sui_cluster_test/
faucet.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::cluster::{Cluster, new_wallet_context_from_cluster};
5use async_trait::async_trait;
6use fastcrypto::encoding::{Encoding, Hex};
7use std::collections::HashMap;
8use std::env;
9use std::sync::Arc;
10use sui_faucet::{FaucetConfig, FaucetResponse, LocalFaucet, RequestStatus};
11use sui_types::base_types::SuiAddress;
12use sui_types::crypto::KeypairTraits;
13use tracing::{Instrument, debug, info, info_span};
14
15pub struct FaucetClientFactory;
16
17impl FaucetClientFactory {
18    pub async fn new_from_cluster(
19        cluster: &(dyn Cluster + Sync + Send),
20    ) -> Arc<dyn FaucetClient + Sync + Send> {
21        match cluster.remote_faucet_url() {
22            Some(url) => Arc::new(RemoteFaucetClient::new(url.into())),
23            // If faucet_url is none, it's a local cluster
24            None => {
25                let key = cluster
26                    .local_faucet_key()
27                    .expect("Expect local faucet key for local cluster")
28                    .copy();
29                let wallet_context = new_wallet_context_from_cluster(cluster, key)
30                    .await
31                    .instrument(info_span!("init_wallet_context_for_faucet"));
32
33                let config = FaucetConfig::default();
34                let simple_faucet = LocalFaucet::new(wallet_context.into_inner(), config)
35                    .await
36                    .unwrap();
37
38                Arc::new(LocalFaucetClient::new(simple_faucet))
39            }
40        }
41    }
42}
43
44/// Faucet Client abstraction
45#[async_trait]
46pub trait FaucetClient {
47    async fn request_sui_coins(&self, request_address: SuiAddress) -> FaucetResponse;
48}
49
50/// Client for a remote faucet that is accessible by POST requests
51pub struct RemoteFaucetClient {
52    remote_url: String,
53}
54
55impl RemoteFaucetClient {
56    fn new(url: String) -> Self {
57        info!("Use remote faucet: {}", url);
58        Self { remote_url: url }
59    }
60}
61
62#[async_trait]
63impl FaucetClient for RemoteFaucetClient {
64    /// Request test SUI coins from faucet.
65    /// It also verifies the effects are observed by fullnode.
66    async fn request_sui_coins(&self, request_address: SuiAddress) -> FaucetResponse {
67        let gas_url = format!("{}/v2/gas", self.remote_url);
68        debug!("Getting coin from remote faucet {}", gas_url);
69        let data = HashMap::from([("recipient", Hex::encode(request_address))]);
70        let map = HashMap::from([("FixedAmountRequest", data)]);
71
72        let auth_header = match env::var("FAUCET_AUTH_HEADER") {
73            Ok(val) => val,
74            _ => "".to_string(),
75        };
76
77        let response = reqwest::Client::new()
78            .post(&gas_url)
79            .header("Authorization", auth_header)
80            .json(&map)
81            .send()
82            .await
83            .unwrap_or_else(|e| panic!("Failed to talk to remote faucet {:?}: {:?}", gas_url, e));
84        let full_bytes = response.bytes().await.unwrap();
85        let faucet_response: FaucetResponse = serde_json::from_slice(&full_bytes)
86            .map_err(|e| anyhow::anyhow!("json deser failed with bytes {:?}: {e}", full_bytes))
87            .unwrap();
88
89        if let RequestStatus::Failure(error) = &faucet_response.status {
90            panic!("Failed to get gas tokens with error: {}", error)
91        };
92
93        faucet_response
94    }
95}
96
97/// A local faucet that holds some coins since genesis
98pub struct LocalFaucetClient {
99    simple_faucet: Arc<LocalFaucet>,
100}
101
102impl LocalFaucetClient {
103    fn new(simple_faucet: Arc<LocalFaucet>) -> Self {
104        info!("Use local faucet");
105        Self { simple_faucet }
106    }
107}
108#[async_trait]
109impl FaucetClient for LocalFaucetClient {
110    async fn request_sui_coins(&self, request_address: SuiAddress) -> FaucetResponse {
111        let coins = self
112            .simple_faucet
113            .local_request_execute_tx(request_address)
114            .await
115            .unwrap_or_else(|err| panic!("Failed to get gas tokens with error: {}", err));
116
117        FaucetResponse {
118            status: RequestStatus::Success,
119            coins_sent: Some(coins),
120        }
121    }
122}