Skip to content

Commit

Permalink
feat: add pow difficulty to settings + README
Browse files Browse the repository at this point in the history
  • Loading branch information
Zk2u committed Sep 17, 2024
1 parent ae140bd commit 5f02635
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 13 deletions.
25 changes: 22 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ alloy = { version = "0.3.5", default-features = false, features = [
arrayvec = "0.7.6"
axum = { version = "0.7.5", features = ["http2"] }
axum-client-ip = "0.6.0"
# axum-macros = "0.4.1"
axum-macros = "0.4.1"
bdk_esplora = { version = "0.18.0", features = [
"async-https",
"async-https-rustls",
Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
# faucet-api
# Alpen Labs faucet API

## how to claim

First, call `GET /get_pow_challenge` which will return something like this:

```json
{
nonce: "<16 byte hex string>",
difficulty: <0 to 255>
}
```

This will only fail if you call it over IPv6, where it will respond with a `503 SERVICE UNAVAILABLE` code.

As the client, you are challenged to then find a solution where:

```rs
// b"alpen labs faucet 2024"
let salt = 0x616c70656e206c616273206661756365742032303234;
// nonce is the 16 decoded bytes from the API
// solution is a 8 byte array
// `|` is representing concatenation
count_leading_zeros(sha256(salt | nonce | solution)) >= difficulty
```

Once you find a solution, hex encode it and use it in a claim for either L1 or L2 funds:

### L1

`GET /claim_l1/<solution_as_hex>/<l1_address>`

Where `l1_address` is the address that you want to receive funds on.

If successful, this will return a `200 OK` with an empty body.
If not, it will return a status code and a raw error message string in the body.

### L2

`todo`
1 change: 1 addition & 0 deletions faucet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ sqlite_file = "faucet.sqlite"
network = "signet"
esplora = "https://explorer.bc-2.jp/api"
sats_per_claim = 10_000_000
pow_difficulty = 17
11 changes: 10 additions & 1 deletion src/hex.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! A module for encoding and decoding hexadecimal strings.
use std::ops::{Deref, DerefMut};
use std::{
fmt::Debug,
ops::{Deref, DerefMut},
};

use axum::{body::Body, http::Response, response::IntoResponse};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
Expand Down Expand Up @@ -51,6 +54,12 @@ impl<'de, const N: usize> Deserialize<'de> for Hex<[u8; N]> {
}
}

impl<T: AsRef<[u8]>> std::fmt::Debug for Hex<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&encode(self.0.as_ref()))
}
}

impl<T: AsRef<[u8]>> Deref for Hex<T> {
type Target = T;

Expand Down
23 changes: 18 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ use axum::{
extract::{Path, State},
http::StatusCode,
routing::get,
Router,
Json, Router,
};
use axum_client_ip::SecureClientIp;
use bdk_wallet::bitcoin::{address::NetworkUnchecked, Address};
use hex::Hex;
use l1::{fee_rate, L1Wallet, ESPLORA_CLIENT};
use parking_lot::{RwLock, RwLockWriteGuard};
use pow::{Challenge, Nonce, Solution};
use serde::{Deserialize, Serialize};
use settings::{settings, Settings};
use tokio::net::TcpListener;
use tracing::{error, info};
Expand Down Expand Up @@ -80,11 +81,23 @@ async fn main() {
.unwrap();
}

async fn get_pow_challenge(SecureClientIp(ip): SecureClientIp) -> Result<Hex<Nonce>, &'static str> {
#[derive(Debug, Serialize, Deserialize)]
pub struct PowChallenge {
nonce: Hex<Nonce>,
difficulty: u8,
}

async fn get_pow_challenge(
SecureClientIp(ip): SecureClientIp,
State(state): State<Arc<AppState>>,
) -> Result<Json<PowChallenge>, (StatusCode, &'static str)> {
if let IpAddr::V4(ip) = ip {
Ok(Hex(Challenge::get(&ip).nonce()))
Ok(Json(PowChallenge {
nonce: Hex(Challenge::get(&ip).nonce()),
difficulty: state.settings.pow_difficulty,
}))
} else {
Err("IPV6 is not unavailable")
Err((StatusCode::SERVICE_UNAVAILABLE, "IPV6 is not unavailable"))
}
}

Expand All @@ -101,7 +114,7 @@ async fn claim_l1(
};

// num hashes on average to solve challenge: 2^15
if !Challenge::valid(&ip, 15, solution.0) {
if !Challenge::valid(&ip, state.settings.pow_difficulty, solution.0) {
return Err((StatusCode::BAD_REQUEST, "Bad solution".to_string()));
}

Expand Down
4 changes: 2 additions & 2 deletions src/pow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Challenge {
}

/// Validates the proof of work solution by the client.
pub fn valid(ip: &Ipv4Addr, target_prefixed_zeros: u8, solution: Solution) -> bool {
pub fn valid(ip: &Ipv4Addr, difficulty: u8, solution: Solution) -> bool {
let ns = nonce_set();
let raw_ip = ip.to_bits();
let nonce = match ns.get(&raw_ip) {
Expand All @@ -59,7 +59,7 @@ impl Challenge {
hasher.update(b"alpen labs faucet 2024");
hasher.update(nonce);
hasher.update(solution);
let pow_valid = count_leading_zeros(&hasher.finalize()) >= target_prefixed_zeros;
let pow_valid = count_leading_zeros(&hasher.finalize()) >= difficulty;
if pow_valid {
ns.insert(raw_ip, (nonce, true));
}
Expand Down
3 changes: 3 additions & 0 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct InternalSettings {
pub network: Option<Network>,
pub esplora: Option<String>,
pub sats_per_claim: Option<Amount>,
pub pow_difficulty: Option<u8>,
}

#[derive(Serialize, Deserialize, Debug)]
Expand All @@ -31,6 +32,7 @@ pub struct Settings {
pub network: Network,
pub esplora: String,
pub sats_per_claim: Amount,
pub pow_difficulty: u8,
}

impl From<InternalSettings> for Settings {
Expand All @@ -48,6 +50,7 @@ impl From<InternalSettings> for Settings {
sats_per_claim: internal
.sats_per_claim
.unwrap_or(Amount::from_sat(10_000_000)),
pow_difficulty: internal.pow_difficulty.unwrap_or(17),
}
}
}
Expand Down

0 comments on commit 5f02635

Please sign in to comment.