use deno_core::error::AnyError; use hyper::http::response::Parts; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server, StatusCode}; use indoc::indoc; use routerify::prelude::RequestExt; use routerify::{RequestInfo, Router, RouterService}; use std::collections::HashMap; use std::convert::Infallible; use std::net::{IpAddr, SocketAddr}; use std::num::ParseIntError; use std::str::{FromStr, ParseBoolError}; use three_em_arweave::arweave::Arweave; use three_em_arweave::cache::{ArweaveCache, CacheExt}; use three_em_executor::execute_contract; use three_em_executor::executor::ExecuteResult; use url::Url; pub struct ServerConfiguration { pub port: u16, pub host: IpAddr, } pub fn build_error(message: &str) -> Response { Response::builder() .status(400) .body(Body::from( serde_json::json!({ "status": 400, "message": message}) .to_string(), )) .unwrap() } async fn echo(req: Request) -> Result, hyper::Error> { match (req.method(), req.uri().path()) { (&Method::GET, "/evaluate") => { let params: HashMap = req .uri() .query() .map(|v| { url::form_urlencoded::parse(v.as_bytes()) .into_owned() .collect() }) .unwrap_or_else(HashMap::new); let contract_id = params.get("contractId").map(|i| i.to_owned()); let height = params.get("height").map(|i| i.to_owned()); let gateway_host = params.get("gatewayHost").map(|i| i.to_owned()).unwrap_or(String::from("arweave.net")); let gateway_port = params.get("gatewayPort").map(|i| i.to_owned()).unwrap_or(String::from("443")); let gateway_protocol = params.get("gatewayProtocol").map(|i| i.to_owned()).unwrap_or(String::from("https")); let show_validity = params.get("showValidity").map(|i| i.to_owned()).unwrap_or(String::from("false")); let cache = params.get("cache").map(|i| i.to_owned()).unwrap_or(String::from("false")); let show_errors = params.get("showErrors").map(|i| i.to_owned()).unwrap_or(String::from("false")); let height = height.map(|h| h.parse::().unwrap_or(usize::MAX)); let show_validity = show_validity.parse::().unwrap_or(false); let cache = cache.parse::().unwrap_or(false); let show_errors = show_errors.parse::().unwrap_or(false); let port = gateway_port.parse::().unwrap_or(443); let mut response_result: Option> = None; if contract_id.is_none() { response_result = Some(build_error("contractId was not provided in query parameters. A contract id must be provided.")); } else { let arweave = Arweave::new(port, gateway_host.to_owned(), gateway_protocol.to_owned(), ArweaveCache::new()); let execute_result = execute_contract(&arweave, contract_id.unwrap().to_owned(), None, None, height, cache, show_errors).await; match execute_result { Ok(result) => { match result { ExecuteResult::V8(val, validity) => { if show_validity { response_result = Some(Response::new(Body::from( serde_json::json!({ "state": val, "validity": validity }).to_string() ))); } else { response_result = Some(Response::new(Body::from( serde_json::json!({ "state": val }).to_string() ))); } }, ExecuteResult::Evm(_, _, _) => { response_result = Some(build_error("EVM evaluation is disabled")); } } }, Err(e) => { response_result = Some(build_error(e.to_string().as_str())); } } } Ok(response_result.unwrap()) } _ => { Ok(Response::new(Body::from( "Try POSTing data to /echo such as: `curl localhost:3000/echo -XPOST -d 'hello world'`", ))) } } } pub async fn start_local_server(config: ServerConfiguration) { let addr = SocketAddr::from((config.host, config.port)); let service = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(echo)) }); println!("Serving {}", addr.to_string()); println!( "{}", indoc! { " Endpoints: GET /evaluate Evaluates a contract state given a contract id ?contractId Id of contract to be evaluated (Required) [string] ?height Height to be used during evaluation [number] ?gatewayHost Gateway to be used for and during evaluation (Default: arweave.net) [string] ?gatewayPort Port to be used for gateway communication (Default: 443) [number] ?gatewayProtocol Protocol to be used for gateway communication (Default: https) [string] ?showValidity Whether validity table should be included in the JSON response (Default: false) [boolean] ?cache Whether built-in cache system should be used during execution (Default: true) [boolean] ?showErrors Whether server console should print out execution exceptions (Default: false) [boolean] "} ); let server = Server::bind(&addr).executor(LocalExec).serve(service); server.await.unwrap(); } #[derive(Clone, Copy, Debug)] struct LocalExec; impl hyper::rt::Executor for LocalExec where F: std::future::Future + 'static, // not requiring `Send` { fn execute(&self, fut: F) { // This will spawn into the currently running `LocalSet`. tokio::task::spawn_local(fut); } }