Skip to content

Commit

Permalink
[faucet] updates to enable faucet web app (#20846)
Browse files Browse the repository at this point in the history
## Description 

This PR updates the faucet service to support requests from Faucet Web,
which for testnet are authenticated via a token. It keeps track of each
ip address and the number of requests, and limits to a predefined number
of requests per a time window. In authenticated mode, it expects that
requests go through `/v1/faucet_web_gas`. If requests go through the
original `/v1/gas`, they will be under strict rate limit.

In addition, it adds a new route `/health`, and moves the logic of `/`
to this new route. The old route `/` has a redirect logic to the
`faucet.sui.io` web app for requesting tokens.

Finally, the CLI is updated to error if `sui client faucet` is called on
the testnet network, and provides a message with the url to open to
request tokens.

## Test plan 

Added tests for the logic on cleaning up the list of banned IPs once the
reset time passes.
`cargo test -p sui-faucet -- server`

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [x] CLI: `sui client faucet` will now instruct users to use the Faucet
Web App (faucet.sui.io) to request testnet tokens. For devnet/localhost,
behaviour is unchanged.
- [ ] Rust SDK:
  • Loading branch information
stefan-mysten authored Jan 16, 2025
1 parent 75bb9d0 commit 8a5f697
Show file tree
Hide file tree
Showing 7 changed files with 598 additions and 44 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/sui-faucet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ async-trait.workspace = true
axum.workspace = true
bin-version.workspace = true
clap.workspace = true
dashmap.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["full"] }
tracing.workspace = true
Expand All @@ -29,6 +30,8 @@ eyre.workspace = true
tempfile.workspace = true
parking_lot.workspace = true
tonic.workspace = true
reqwest.workspace = true
once_cell.workspace = true
tower_governor = "0.4.3"

sui-json-rpc-types.workspace = true
Expand All @@ -45,6 +48,8 @@ mysten-network.workspace = true

[dev-dependencies]
test-cluster.workspace = true
wiremock.workspace = true
serde_json.workspace = true

[[bin]]
name = "sui-faucet"
Expand Down
9 changes: 9 additions & 0 deletions crates/sui-faucet/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ use thiserror::Error;

#[derive(Error, Debug, PartialEq, Eq)]
pub enum FaucetError {
#[error("Missing X-Turnstile-Token header. For testnet tokens, please use the Web UI: https://faucet.sui.io")]
MissingTurnstileTokenHeader,

#[error("Request limit exceeded. {0}")]
TooManyRequests(String),

#[error("Faucet cannot read objects from fullnode: {0}")]
FullnodeReadingError(String),

Expand Down Expand Up @@ -42,6 +48,9 @@ pub enum FaucetError {

#[error("Internal error: {0}")]
Internal(String),

#[error("Invalid user agent: {0}")]
InvalidUserAgent(String),
}

impl FaucetError {
Expand Down
31 changes: 31 additions & 0 deletions crates/sui-faucet/src/faucet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,32 @@ pub struct FaucetConfig {

#[clap(long, action = clap::ArgAction::Set, default_value_t = false)]
pub batch_enabled: bool,

/// Testnet faucet requires authentication via the Web UI at <https://faucet.sui.io>
/// This flag is used to indicate that authentication mode is enabled.
#[clap(long)]
pub authenticated: bool,

/// Maximum number of requests per IP address. This is used for the authenticated mode.
#[clap(long, default_value_t = 3)]
pub max_requests_per_ip: u64,

/// This is the amount of time to wait before adding one more quota to the rate limiter. Basically,
/// it ensures that we're not allowing too many requests all at once. This is very specific to
/// governor and tower-governor crates. This is used primarily for authenticated mode. A small
/// value will allow more requests to be processed in a short period of time.
#[clap(long, default_value_t = 10)]
pub replenish_quota_interval_ms: u64,

/// The amount of seconds to wait before resetting the request count for the IP addresses recorded
/// by the rate limit layer. Default is 12 hours. This is used for authenticated mode.
#[clap(long, default_value_t = 3600*12)]
pub reset_time_interval_secs: u64,

/// Interval time to run the task to clear the banned IP addresses by the rate limiter. This is
/// used for authenticated mode.
#[clap(long, default_value_t = 60)]
pub rate_limiter_cleanup_interval_secs: u64,
}

impl Default for FaucetConfig {
Expand All @@ -143,6 +169,11 @@ impl Default for FaucetConfig {
batch_request_size: 500,
ttl_expiration: 300,
batch_enabled: false,
authenticated: false,
max_requests_per_ip: 3,
replenish_quota_interval_ms: 10,
reset_time_interval_secs: 3600 * 12,
rate_limiter_cleanup_interval_secs: 60,
}
}
}
5 changes: 4 additions & 1 deletion crates/sui-faucet/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,8 @@ pub fn normalize_path(path: &str) -> &str {
/// Determines whether the given path should be tracked for metrics collection.
/// Only specified paths relevant to monitoring are included.
pub fn is_path_tracked(path: &str) -> bool {
matches!(path, "/v1/gas" | "/gas" | "/v1/status")
matches!(
path,
"/v1/gas" | "/gas" | "/v1/status" | "/v1/faucet_web_gas"
)
}
Loading

0 comments on commit 8a5f697

Please sign in to comment.