Skip to content

Commit

Permalink
feat(faucet): api endpoint to return distribution
Browse files Browse the repository at this point in the history
The server sends 100 tokens for the endpoint:
http://<ip>/<bls_hex_pubkey>
This adds a new (additional) endpoint to distribute to maid addresses:
http://<ip>/distribution?address=<address>&pkhex=<pkhex>
Calling the new endpoint will return an existing distribution for that
maidsafecoin address, or create a new distribution if needed.
  • Loading branch information
iancoleman authored and joshuef committed Feb 12, 2024
1 parent a5c22f4 commit 838407e
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 21 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions sn_faucet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ tiny_http = { version="0.12", features = ["ssl-rustls"] }
tokio = { version = "1.32.0", features = ["parking_lot", "rt"] }
tracing = { version = "~0.1.26" }
tracing-core = "0.1.30"
url = "2.5.0"

[lints]
workspace = true
98 changes: 80 additions & 18 deletions sn_faucet/src/faucet_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ use crate::{claim_genesis, send_tokens};
use color_eyre::eyre::{eyre, Result};
use sn_client::Client;
use sn_transfers::{HotWallet, NanoTokens};
use std::path::{self, Path, PathBuf};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tiny_http::{Response, Server};
use tracing::{debug, error, trace};
use url::Url;

/// Run the faucet server.
///
Expand Down Expand Up @@ -58,16 +60,18 @@ pub async fn restart_faucet_server(client: &Client) -> Result<()> {
}

async fn startup_server(client: &Client) -> Result<()> {
#[allow(unused)]
let mut balances = HashMap::<String, NanoTokens>::new();
#[cfg(feature = "distribution")]
{
let balances = token_distribution::load_maid_snapshot()?;
balances = token_distribution::load_maid_snapshot()?;
let keys = token_distribution::load_maid_pubkeys()?;
// Each distribution takes about 500ms to create, so for thousands of
// initial distributions this takes many minutes. This is run in the
// background instead of blocking the server from starting.
tokio::spawn(token_distribution::distribute_from_maid_to_tokens(
client.clone(),
balances,
balances.clone(),
keys,
));
}
Expand All @@ -90,26 +94,84 @@ async fn startup_server(client: &Client) -> Result<()> {
request.url(),
request.headers()
);
let key = request.url().trim_matches(path::is_separator);

match send_tokens(client, "100", key).await {
Ok(transfer) => {
println!("Sent tokens to {key}");
debug!("Sent tokens to {key}");
let response = Response::from_string(transfer);
let _ = request.respond(response).map_err(|err| {
eprintln!("Failed to send response: {err}");
error!("Failed to send response: {err}");
});
}
// There are two paths available in the faucet.
//
// http://<ip>/<wallet_hex_key>
// which sends a fixed amount to that key.
//
// http://<ip>/distribution?address=<addr>&publickey=<pkhex>
// which returns the distribution for that maid address
//
// tiny_http request.url() excludes host, ie is only the path.
// https://docs.rs/tiny_http/latest/tiny_http/struct.Request.html#method.url
// Returns the resource requested by the client.
let request_url = format!("http://127.0.0.1:8000{}", request.url());
let url = match Url::parse(&request_url) {
Ok(u) => u,
Err(err) => {
eprintln!("Failed to send tokens to {key}: {err}");
error!("Failed to send tokens to {key}: {err}");
let response = Response::from_string(format!("Failed to send tokens: {err}"));
let response = Response::from_string(format!("Invalid url: {err}"));
let _ = request
.respond(response.with_status_code(400))
.map_err(|err| eprintln!("Failed to get distribution: {err}"));
continue;
}
};
if url.path() == "/distribution" {
// if distribution feature is enabled, return the distribution for
// this address
#[cfg(feature = "distribution")]
{
match token_distribution::handle_distribution_req(client, url, balances.clone())
.await
{
Ok(distribution) => {
let response = Response::from_string(distribution);
let _ = request.respond(response).map_err(|err| {
eprintln!("Failed to send response: {err}");
error!("Failed to send response: {err}");
});
}
Err(err) => {
eprintln!("Failed to get distribution: {err}");
error!("Failed to get distribution: {err}");
let response =
Response::from_string(format!("Failed to get distribution: {err}"));
let _ = request
.respond(response.with_status_code(500))
.map_err(|err| eprintln!("Failed to get distribution: {err}"));
}
}
}
// if distribution feature not enabled then return an error
#[cfg(not(feature = "distribution"))]
{
let response = Response::from_string(format!("Distribution feature disabled"));
let _ = request
.respond(response.with_status_code(500))
.map_err(|err| eprintln!("Failed to send response: {err}"));
}
} else {
// issue a fixed amount of tokens to the wallet key
let key = url.path().trim_start_matches('/');
match send_tokens(client, "100", key).await {
Ok(transfer) => {
println!("Sent tokens to {key}");
debug!("Sent tokens to {key}");
let response = Response::from_string(transfer);
let _ = request.respond(response).map_err(|err| {
eprintln!("Failed to send response: {err}");
error!("Failed to send response: {err}");
});
}
Err(err) => {
eprintln!("Failed to send tokens to {key}: {err}");
error!("Failed to send tokens to {key}: {err}");
let response = Response::from_string(format!("Failed to send tokens: {err}"));
let _ = request
.respond(response.with_status_code(500))
.map_err(|err| eprintln!("Failed to send response: {err}"));
}
}
}
}
Ok(())
Expand Down
27 changes: 24 additions & 3 deletions sn_faucet/src/token_distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use sn_transfers::{MainPubkey, NanoTokens};
use std::str::FromStr;
use std::{collections::HashMap, path::PathBuf};
use tracing::info;
use url::Url;

const SNAPSHOT_FILENAME: &str = "snapshot.json";
const SNAPSHOT_URL: &str = "https://api.omniexplorer.info/ask.aspx?api=getpropertybalances&prop=3";
Expand Down Expand Up @@ -256,24 +257,44 @@ pub async fn distribute_from_maid_to_tokens(
continue;
}
let maid_pk = &pubkeys[&addr];
let _ = create_distribution(&client, &addr, maid_pk, amount).await;
let _ = create_distribution(&client, &addr, maid_pk, &amount).await;
}
}

pub async fn handle_distribution_req(
client: &Client,
url: Url,
balances: Snapshot,
) -> Result<String> {
let query: HashMap<String, String> = url.query_pairs().into_owned().collect();
let address = query
.get("address")
.ok_or(eyre!("Missing address in querystring"))?;
let pkhex = query
.get("pkhex")
.ok_or(eyre!("Missing pkhex in querystring"))?;
let amount = balances
.get(address)
.ok_or(eyre!("Address not in snapshot"))?;
create_distribution(client, address, pkhex, amount).await
}

async fn create_distribution(
client: &Client,
addr: &MaidAddress,
maid_pk: &MaidPubkey,
amount: NanoTokens,
amount: &NanoTokens,
) -> Result<String> {
// validate the pk and the address match
// because we can't be sure if this addr:pk pair has been pre-verified
// and we don't want to encrypt using the wrong pubkey for the address
if !maid_pk_matches_address(addr, maid_pk) {
let msg = format!("Not creating distribution for mismatched addr:pk {addr} {maid_pk}");
let msg = format!("Not creating distribution for mismatched addr/pk {addr} {maid_pk}");
info!(msg);
return Err(eyre!(msg));
}
// save this address and public key pair
save_address_pk(addr, maid_pk)?;
// check if this distribution has already been created
let root = get_distributions_data_dir_path()?;
let dist_path = root.join(addr);
Expand Down

0 comments on commit 838407e

Please sign in to comment.