aleo_development_server/
lib.rs

1// Copyright (C) 2019-2023 Aleo Systems Inc.
2// This file is part of the Aleo SDK library.
3
4// The Aleo SDK library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Aleo SDK library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>.
16
17//! [![github]](https://github.com/AleoHQ/sdk)&ensp;[![crates-io]](https://crates.io/crates/aleo-development-server)&ensp;[![docs-rs]](https://docs.rs/aleo-rust/latest/aleo-development-server/)
18//!
19//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
20//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
21//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
22//!
23//! <br/>
24//! The Aleo Development Server is a REST server that can perform the proving and verification
25//! operations necessary to create Aleo program deployments/executions and broadcast them to the
26//! Aleo network. It is intended to be used within a trusted context such as a local development
27//! environment, CI/CD pipeline, or a private network within a cloud.
28//! <br/>
29//! <br/>
30//! It *SHOULD NOT* be used to create a public API.
31//!
32//! # RESTFUL Program Execution & Deployment
33//!
34//! The Aleo Development Server provides REST endpoints that allow developers to send the necessary
35//! data needed to create program deployments and executions to the Aleo network. Currently there
36//! are three endpoints:
37//! - `/deploy` - Create a program deployment
38//! - `/execute` - Create a program execution
39//! - `/transfer` - Create a transfer of Aleo credits
40//!
41//! ## Installation & Configuration
42//! The development server can be installed with:
43//!
44//! `cargo install aleo-develop` - Install from crates.io
45//!
46//! `cargo install --path . --locked` - Install from source
47//!
48//! Once installed the following command can be used to start the server:
49//!
50//! `aleo-develop start` - Start the server at `0.0.0.0:4040` (by default the server sends transactions to the Aleo testnet3 network)
51//!
52//! `aleo-develop start --help` - Show all available options for configuring the server
53//!
54//! #### Configuring Encrypted Private Keys
55//! The server can be started with an encrypted private key. If this option is used, the server
56//! will look for a password field in the body of incoming requests. If a password is provided
57//! (and the password is correct), the server will decrypt the private key and use it to build
58//! and send the transaction to the network. This mode of operation can be invoked with:
59//!
60//! `aleo-develop start --key-ciphertext <encrypted_private_key>`
61//!
62//! ## Usage
63//! Once started, the endpoints have the following options. All requests should be sent as a POST request with a json body.
64//!
65//! #### Javascript API
66//! A javascript client for this server is available in the [Aleo SDK](https://www.npmjs.com/package/@aleohq/sdk)
67//!
68//! #### Endpoints
69//! `\develop`
70//! * `program`: Text representation of the program to be deployed
71//! * `fee`: Required fee to be paid for the program deployment
72//! * `private_key`: Optional private key of the user who is deploying the program
73//! * `password`: If the development server is started with an encrypted private key, the password will decrypt the private key
74//! * `fee_record`: Optional record in text format to be used for the fee. If not provided, the server will search the network for a suitable record to pay the fee.
75//! * `returns`: The transaction ID of the deployment transaction if successful
76//!
77//! `\execute`
78//! * `program_id` The program ID of the program to be executed (e.g. hello.aleo)
79//! * `program_function` The function to execute within the program (e.g. hello)
80//! * `fee` Optional fee to be paid for the transfer, specify 0 for no fee
81//! * `inputs` Array of inputs to be passed to the program
82//! * `private_key` Optional private key of the user who is executing the program
83//! * `password`: If the development server is started with an encrypted private key, the password will decrypt the private key
84//! * `fee_record`: Optional record in text format to be used for the fee. If not provided, the server will search the network for a suitable record to pay the fee
85//! * `returns`: The transaction ID of the execution transaction if successful
86//!
87//! `\transfer`
88//! * `amount` The amount of credits to be sent (e.g. 1.5)
89//! * `fee` Optional fee to be paid for the transfer, specify 0 for no fee
90//! * `recipient` The recipient of the transfer
91//! * `privateKey` Optional private key of the user who is sending the transfer
92//! * `password`: If the development server is started with an encrypted private key, the password will decrypt the private key
93//! * `amount_record` Optional record in text format to be used to fund the transfer. If not provided, the server will search the network for a suitable record to fund the amount
94//! * `fee_record` Optional record in text format to be used for the fee. If not provided, the server will search the network for a suitable record to pay the fee
95//! @returns {string | Error} The transaction ID of the execution transaction if successful
96//!
97//! #### Curl Examples
98//! Example curl requests for the above endpoints:
99//! ```bash
100//! ## Deploy a program
101//! curl -X POST -H "Content-Type: application/json" \
102//! -d '{
103//!     "program": "program hello.aleo;\n\nfunction hello:\n    input r0 as u32.public;\n    input r1 as u32.private;\n    add r0 r1 into r2;\n    output r2 as u32.private;\n",
104//!     "fee": 100000,
105//!     "private_key": "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"
106//! }' \
107//! http://0.0.0.0:4040/testnet3/deploy
108//!
109//! ## Execute a program
110//! curl -X POST -H "Content-Type: application/json" \
111//! -d '{
112//!     "program_id": "hello.aleo",
113//!     "program_function": "hello",
114//!     "inputs": ["5u32", "5u32"],
115//!     "private_key": "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH",
116//!     "fee": 0
117//! }' \
118//! http://0.0.0.0:4040/testnet3/execute
119//!
120//! ## Create a value transfer
121//! curl -X POST -H "Content-Type: application/json" \
122//! -d '{
123//!     "amount": 1000,
124//!     "fee": 0,
125//!     "recipient": "aleo1trtljxr7rw6cn368v2pslnxgl2vzk9pgfunev59k53x645hvrygs5v4f2e",
126//!     "private_key": "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"
127//! }' \
128//! http://0.0.0.0:4040/testnet3/transfer
129//! ```
130//!
131//! This API of this server is currently under active development and is expected to change in the
132//! future in order to provide a more streamlined experience for program execution and deployment.
133//!
134
135#![forbid(unsafe_code)]
136
137#[macro_use]
138extern crate tracing;
139
140mod cli;
141pub use cli::*;
142
143mod helpers;
144pub use helpers::*;
145
146mod requests;
147use requests::*;
148
149mod routes;
150pub use routes::*;
151
152use aleo_rust::{AleoAPIClient, Encryptor, ProgramManager, RecordFinder, TransferType};
153use snarkvm::{
154    console::{
155        account::{Address, PrivateKey},
156        program::{Ciphertext, Identifier, Plaintext, ProgramID, Record},
157    },
158    prelude::{Network, Testnet3},
159    synthesizer::Program,
160};
161use tracing_subscriber::fmt;
162
163use anyhow::{anyhow, Result};
164use colored::*;
165use serde::{Deserialize, Serialize};
166use std::net::SocketAddr;
167use warp::{reject, reply, Filter, Rejection, Reply};
168
169/// Server object for the Aleo Development Server
170#[derive(Clone)]
171pub struct Rest<N: Network> {
172    api_client: AleoAPIClient<N>,
173    /// Private key ciphertext for the account being used with the server
174    private_key_ciphertext: Option<Ciphertext<N>>,
175    /// Record finder for finding records
176    record_finder: RecordFinder<N>,
177    /// Socket address for the server
178    socket_address: SocketAddr,
179    /// Debug mode flag
180    debug: bool,
181}
182
183impl<N: Network> Rest<N> {
184    /// Initializes a new instance of the server.
185    pub fn initialize(
186        socket_address: Option<SocketAddr>,
187        private_key_ciphertext: Option<Ciphertext<N>>,
188        peer_url: Option<String>,
189        debug: bool,
190    ) -> Result<Self> {
191        // If no socket address was specified, use the default of 0.0.0.0:4040
192        let socket_address = socket_address.unwrap_or_else(|| SocketAddr::from(([0, 0, 0, 0], 4040)));
193
194        // If no peer url was specified, use the default of https://vm.aleo.org/api
195        let peer = peer_url.unwrap_or("https://vm.aleo.org/api".to_string());
196
197        // Initialize an API client configured for the specified network.
198        let api_client = AleoAPIClient::new(&peer, "testnet3")?;
199        let record_finder = RecordFinder::new(api_client.clone());
200
201        let key_warning = if private_key_ciphertext.is_some() {
202            format!("{}", "Using configured private key ciphertext for main development account, authentication will be required for all requests\n".bright_blue())
203        } else {
204            "".to_string()
205        };
206
207        // Initialize the server.
208        let server = Self { api_client, private_key_ciphertext, record_finder, socket_address, debug };
209
210        // Print an initialization message and return the server
211        println!("{}", "\nStarting Aleo development server...".bright_blue());
212        println!(
213            "{}{}{}{}",
214            "Listening on ".bright_blue(),
215            socket_address.to_string().bright_green().bold(),
216            " with remote peer ".bright_blue(),
217            peer
218        );
219        println!("{}", key_warning);
220        Ok(server)
221    }
222}
223
224impl<N: Network> Rest<N> {
225    /// Initializes the development server
226    pub async fn start(&mut self) {
227        let cors = warp::cors().allow_any_origin().allow_methods(vec!["GET", "POST", "OPTIONS"]).allow_headers(vec![
228            "access-control-allow-origin",
229            "access-control-request-method",
230            "access-control-request-headers",
231            "referer",
232            "referrer-policy",
233            "user-agent",
234            "origin",
235            "accept",
236            "content-type",
237            "authorization",
238        ]);
239
240        // Initialize the routes
241        let routes = self.routes();
242
243        // Initialize the logger with configured level
244        let level = if self.debug { "debug" } else { "info" };
245        fmt::Subscriber::builder().with_env_filter(level).init();
246
247        // Add custom logging for each request.
248        let custom_log = warp::log::custom(|info| match info.remote_addr() {
249            Some(addr) => info!("Received '{} {}' from '{addr}' ({})", info.method(), info.path(), info.status()),
250            None => info!("Received '{} {}' ({})", info.method(), info.path(), info.status()),
251        });
252
253        // Start the server
254        println!("{}", "✅ Launching Aleo Development Server!".bright_blue().bold());
255        warp::serve(routes.with(cors).with(custom_log)).run(self.socket_address).await;
256    }
257}