diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f14b6ad --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "grin-finalize" +version = "0.1.0" +edition = "2024" + +[dependencies] +warp = { version = "0.3", features = ["tls"] } +tokio = { version = "1", features = ["full"] } +tokio-rustls = "0.22" +serde = { version = "1.0", features = ["derive"] } +chrono = "0.4" +simplelog = "0.11" +log = "0.4" \ No newline at end of file diff --git a/finalize.py b/finalize.py new file mode 100644 index 0000000..5884d2a --- /dev/null +++ b/finalize.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Script Name: finalize.py +Author: transatoshi +Date Created: 2025-05-11 +Description: This script takes care of finalizing a Grin transaction via slatepack +""" + + +import pexpect +import time + +def run_grin_wallet(): + # Start the grin-wallet finalize command + command = "/usr/local/bin/grin-wallet finalize" + child = pexpect.spawn(command) + + # Expect the password prompt and send the password + child.expect("Password:") + child.sendline("") + + # Expect the slatepack message prompt + child.expect("Please paste your encoded slatepack message:") + + # Read the contents of the slatepack file + with open("/home/grin/grin-finalize/slatepack.tmp", "r") as file: + slatepack_content = file.read() + + # Send the contents of the slatepack file + child.sendline(slatepack_content) + + # Finalize by hitting return + child.sendline() + + # Wait for the command to complete + child.expect(pexpect.EOF) + + # Print the output + print(child.before.decode('utf-8')) + +if __name__ == "__main__": + run_grin_wallet() diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e6546ab --- /dev/null +++ b/src/main.rs @@ -0,0 +1,103 @@ +// ... (other imports remain unchanged) +use std::fs::File; +use std::io::Write; +use std::process::Command; +use log::{info, error}; +use serde::{Deserialize, Serialize}; +use simplelog::{Config, LevelFilter, ColorChoice, TermLogger, WriteLogger, CombinedLogger, TerminalMode}; +use warp::Filter; + +#[derive(Deserialize)] +struct SendRequest { + slatepack: String, +} + +#[derive(Serialize)] +struct Response { + message: String, +} + +#[tokio::main] +async fn main() { + // Initialize logging + let log_file = File::create("finalize.log").unwrap(); + CombinedLogger::init(vec![ + TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto), + WriteLogger::new(LevelFilter::Info, Config::default(), log_file), + ]).unwrap(); + + // Define the POST route + let send_finalize = warp::post() + .and(warp::path("finalize")) + .and(warp::body::json()) + .map(|request: SendRequest| { + // Extract the slatepack message from the request + let slatepack = request.slatepack; + + // Write slatepack to a file + let file_path = "/home/grin/grin-finalize/slatepack.tmp"; + if let Err(e) = write_to_file(file_path, &slatepack) { + error!("Failed to write slatepack to file: {:?}", e); + return warp::reply::json(&Response { + message: "Failed to write slatepack to file".to_string(), + }); + } + + // Execute the command + let output = Command::new("bash") + .arg("-c") + .arg(format!( + "python /home/grin/grin-finalize/finalize.py" + )) + .output() + .expect("Failed to execute command"); + + // Process the command output + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + // Check for specific error message + if stderr.contains("Wallet command failed: LibWallet Error: Couldn't decrypt Slatepack: Could not decrypt slatepack with any provided index on the address derivation path") { + return warp::reply::json(&Response { + message: "Please receive the slatepack in your wallet then finalize that slatepack".to_string(), + }); + } + + if stderr.is_empty() { + info!("Transaction finalized: {}", stdout); + return warp::reply::json(&Response { + message: "Grin transaction successfully finalized ツ".to_string(), + }); + } else { + error!("Failed to finalize transaction: {}", stderr); + return warp::reply::json(&Response { + message: format!("Error: {}", stderr), + }); + } + }); + + // Load SSL keys and certs + let cert_path = "/etc/ssl/cert.pem"; + let key_path = "/etc/ssl/cert.key"; + + // Enable CORS + let cors = warp::cors() + .allow_origin("https://spigot.grinminer.net") + .allow_methods(vec!["POST"]) + .allow_headers(vec!["Content-Type"]); + + // Start the Warp server with CORS and TLS + warp::serve(send_finalize.with(cors)) + .tls() + .cert_path(cert_path) + .key_path(key_path) + .run(([0, 0, 0, 0], 3032)) // Listen on all interfaces + .await; +} + +// Function to write slatepack to a file +fn write_to_file(file_path: &str, content: &str) -> std::io::Result<()> { + let mut file = File::create(file_path)?; + file.write_all(content.as_bytes())?; + Ok(()) +}