Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add faucet for miden-node #224

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
miden-client = { git = "https://github.com/0xPolygonMiden/miden-client", branch = "main", features = [
"testing",
"concurrent",
] }
actix-web = "4"
actix-files = "0.6.5"
actix-cors = "0.7.0"
derive_more = "0.99.17"

[dev-dependencies]
figment = { version = "0.10", features = ["toml", "env", "test"] }
Expand Down
13 changes: 12 additions & 1 deletion node/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use serde::{Deserialize, Serialize};
use tokio::task::JoinSet;

// use crate::faucet;

// Top-level config
// ================================================================================================

Expand All @@ -22,7 +24,10 @@
// START
// ===================================================================================================

pub async fn start_node(config_filepath: &Path) -> Result<()> {
pub async fn start_node(
config_filepath: &Path,
with_faucet: &bool,

Check failure on line 29 in node/src/commands/start.rs

View workflow job for this annotation

GitHub Actions / clippy nightly on ubuntu

unused variable: `with_faucet`
) -> Result<()> {
let config: StartCommandConfig = load_config(config_filepath).extract().map_err(|err| {
anyhow!("failed to load config file `{}`: {err}", config_filepath.display())
})?;
Expand All @@ -39,6 +44,12 @@
tokio::time::sleep(Duration::from_secs(1)).await;
join_set.spawn(rpc_server::serve(config.rpc));

// if with_faucet {
// // start miden-faucet
// tokio::time::sleep(Duration::from_secs(1)).await;
// join_set.spawn(faucet::serve());
// }

// block on all tasks
while let Some(res) = join_set.join_next().await {
// For now, if one of the components fails, crash the node
Expand Down
190 changes: 190 additions & 0 deletions node/src/faucet/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use std::sync::Arc;

use actix_cors::Cors;
use actix_files::{self};

Check failure on line 4 in node/src/faucet/mod.rs

View workflow job for this annotation

GitHub Actions / clippy nightly on ubuntu

the item `actix_files` is imported redundantly
use actix_web::{http::header, post, web, App, HttpResponse, HttpServer, ResponseError};
use anyhow::Result;
use derive_more::Display;
use miden_client::{
client::{transactions::TransactionTemplate, Client},
config::ClientConfig,
};
use miden_objects::{accounts::AccountId, assets::FungibleAsset, utils::serde::Serializable};

Check failure on line 12 in node/src/faucet/mod.rs

View workflow job for this annotation

GitHub Actions / clippy nightly on ubuntu

unused import: `utils::serde::Serializable`
use serde::Deserialize;
use tokio::sync::Mutex;

mod utils;

#[derive(Debug, Display)]
enum FaucetError {
#[display(fmt = "Internal server error")]
InternalError(String),

#[display(fmt = "Bad client request data")]
BadClientData(String),
}

impl ResponseError for FaucetError {}

#[derive(Deserialize)]
struct UserId {
account_id: String,
}

struct FaucetState {
client: Arc<Mutex<Client>>,

Check failure on line 35 in node/src/faucet/mod.rs

View workflow job for this annotation

GitHub Actions / docs stable on ubuntu with --all-features --release

missing generics for struct `miden_client::client::Client`

Check failure on line 35 in node/src/faucet/mod.rs

View workflow job for this annotation

GitHub Actions / clippy nightly on ubuntu

missing generics for struct `miden_client::client::Client`

Check failure on line 35 in node/src/faucet/mod.rs

View workflow job for this annotation

GitHub Actions / test stable on ubuntu with --all-features --workspace

missing generics for struct `Client`
asset: FungibleAsset,
}

#[post("/get_tokens")]
async fn get_tokens(
state: web::Data<FaucetState>,
req: web::Json<UserId>,
) -> Result<HttpResponse, FaucetError> {
println!("Received request from account_id: {}", req.account_id);

// get account id from user
let target_account_id = AccountId::from_hex(&req.account_id)
.map_err(|e| FaucetError::BadClientData(e.to_string()))?;

// Sync client and drop the lock before await
let block = {
let mut client = state.client.lock().await;
client.sync_state().await.map_err(|e| {
eprintln!("Failed to sync");
FaucetError::InternalError(e.to_string())
})?
};

println!("Synced to block: {block}");

// create transaction template from data
let template = TransactionTemplate::MintFungibleAsset {
asset: state.asset,
target_account_id,
};

// Execute, prove and submit tx
let transaction = {
let mut client = state.client.lock().await;
client.new_transaction(template).map_err(|e| {
eprintln!("Error: {}", e);
FaucetError::InternalError(e.to_string())
})?
};

println!("Transaction has been executed");

let note_id = transaction
.created_notes()
.first()
.ok_or_else(|| {
FaucetError::InternalError("Transaction has not created a note.".to_string())
})?
.id();

{
let mut client = state.client.lock().await;
client.send_transaction(transaction).await.map_err(|e| {
println!("error {e}");
FaucetError::InternalError(e.to_string())
})?;
}

println!("Transaction has been proven and sent!");

// let mut is_input_note = false;

// for _ in 0..10 {
// // sync client after submitting tx to get input_note
// let block = state.client.lock().unwrap().sync_state().await.map_err(|e| {
// eprintln!("Failed to sync");
// FaucetError::InternalError(e.to_string())
// })?;

// println!("Synced to block: {block}");

// if let Ok(note) = state.client.lock().unwrap().get_input_note(note_id) {
// let input_note_result: Result<InputNote, _> = note.try_into();

// if let Ok(_input_note) = input_note_result {
// is_input_note = true;
// break;
// }
// }
// sleep(Duration::from_secs(1)).await;
// }

let note = state
.client
.lock()
.await
.get_input_note(note_id)
.map_err(|e| FaucetError::InternalError(e.to_string()))?;

// if is_input_note {
let bytes = note.to_bytes();
println!("Transaction has been turned to bytes");
Ok(HttpResponse::Ok()
.content_type("application/octet-stream")
.append_header(header::ContentDisposition {
disposition: actix_web::http::header::DispositionType::Attachment,
parameters: vec![actix_web::http::header::DispositionParam::Filename(
"note.mno".to_string(),
)],
})
.body(bytes))
// } else {
// Err(FaucetError::InternalError("Failed to return note".to_string()))
// }
}

pub async fn serve() -> Result<()> {
// import faucet
let faucet = match utils::import_account_from_args() {
Ok(account_data) => account_data,
Err(e) => panic!("Failed importing faucet account: {e}"),
};

// init asset
let asset = FungibleAsset::new(faucet.account.id(), 100)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;

// init client & Arc<Mutex<Client>> to enable safe thread passing and mutability
let config = ClientConfig::default();
let client = Arc::new(Mutex::new(
Client::new(config)

Check failure on line 156 in node/src/faucet/mod.rs

View workflow job for this annotation

GitHub Actions / clippy nightly on ubuntu

this function takes 3 arguments but 1 argument was supplied
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?,
));

// load faucet into client
client
.lock()
.await
.import_account(faucet.clone())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;

println!("Faucet: {} has been loaded into client", faucet.account.id());

let server = Arc::new(Mutex::new(
HttpServer::new(move || {
let cors = Cors::default().allow_any_origin().allow_any_method().allow_any_header();
App::new()
.app_data(web::Data::new(FaucetState {
client: client.clone(),
asset,
}))
.wrap(cors)
.service(get_tokens)
.service(
actix_files::Files::new("/", "faucet/src/static/").index_file("index.html"),
)
})
.bind("127.0.0.1:8080")?
.run(),
));

let _ = server.lock().await;

Ok(())
}
Binary file added node/src/faucet/static/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions node/src/faucet/static/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
input:focus {
outline: none;
}

*,
*::before,
*::after {
box-sizing: border-box;
}


body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-image: url(./background.png);
background-repeat: repeat;
}

#error-message {
display: none;
color: red;
text-align: center;
margin-bottom: 5px;
}

#navbar {
position: fixed;
top: 0;
left: 0;
background-color: rgb(17, 24, 39);
width: 100%;
padding: 20px;
}

#title {
font-size: x-large;
text-align: center;
font-weight: bold;
color: white;
margin: 0;
}

#center-container {
background-color: rgb(17, 24, 39);
border-radius: 10px;
display: flex;
flex-direction: column;
padding: 30px;
}

#subtitle {
font-size: large;
text-align: center;
font-weight: bold;
color: white;
margin: 0;
margin-bottom: 20px;
}

#account-id {
padding: 10px;
border-radius: 10px;
border: 1px solid #ccc;
margin-bottom: 30px;
width: 300px;
}

#button {
color: white;
border-radius: 10px;
font-weight: bold;
padding: 10px 20px;
background-color: rgb(124, 58, 237);
width: 300px;
}
24 changes: 24 additions & 0 deletions node/src/faucet/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Miden Faucet</title>
<link rel="stylesheet" href="./index.css">
</head>

<body>
<div id="navbar">
<h1 id="title">Miden Faucet</h1>
</div>
<div id="center-container">
<h2 id="subtitle">Request test POL tokens</h2>
<span id="error-message"></span>
<input type="text" id="account-id" placeholder="Hex encoded Account id" required>
<button id="button">Send me tokens!</button>
</div>
<script src="./index.js"></script>
</body>

</html>
Loading
Loading