aleo_development_server/
routes.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
17use super::*;
18
19impl<N: Network> Rest<N> {
20    /// Initializes the routes for the development server REST API
21    pub fn routes(&self) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
22        // POST /deploy
23        let deploy = warp::post()
24            .and(warp::path!("testnet3" / "deploy"))
25            .and(warp::body::content_length_limit(16 * 1024 * 1024))
26            .and(warp::body::json())
27            .and(with(self.record_finder.clone()))
28            .and(with(self.private_key_ciphertext.clone()))
29            .and(with(self.api_client.clone()))
30            .and_then(Self::deploy_program);
31
32        // POST /execute
33        let execute = warp::post()
34            .and(warp::path!("testnet3" / "execute"))
35            .and(warp::body::content_length_limit(16 * 1024 * 1024))
36            .and(warp::body::json())
37            .and(with(self.record_finder.clone()))
38            .and(with(self.private_key_ciphertext.clone()))
39            .and(with(self.api_client.clone()))
40            .and_then(Self::execute_program);
41
42        // POST /transfer
43        let transfer = warp::post()
44            .and(warp::path!("testnet3" / "transfer"))
45            .and(warp::body::content_length_limit(16 * 1024 * 1024))
46            .and(warp::body::json())
47            .and(with(self.record_finder.clone()))
48            .and(with(self.private_key_ciphertext.clone()))
49            .and(with(self.api_client.clone()))
50            .and_then(Self::transfer);
51
52        // GET /health
53        let health = warp::get().and(warp::path!("health")).map(reply::reply);
54
55        deploy.or(execute).or(transfer).or(health)
56    }
57}
58
59impl<N: Network> Rest<N> {
60    // Get the private key if one is specified in the request or decrypt the local request
61    fn get_private_key(
62        private_key_ciphertext: Option<Ciphertext<N>>,
63        private_key: Option<PrivateKey<N>>,
64        password: Option<String>,
65    ) -> Result<PrivateKey<N>, Rejection> {
66        if let Some(private_key) = private_key {
67            Ok(private_key)
68        } else if let Some(password) = private_key_ciphertext.as_ref().and(password) {
69            let private_key_ciphertext = private_key_ciphertext.as_ref().unwrap();
70            Encryptor::decrypt_private_key_with_secret(private_key_ciphertext, &password).or_reject()
71        } else {
72            if private_key_ciphertext.is_none() {
73                return Err(reject::custom(RestError::Request(
74                    "No private key was provided, and no encrypted keys were found in the server's configuration."
75                        .to_string(),
76                )));
77            }
78            Err(reject::custom(RestError::Request("No private key or decryption passwords were provided.".to_string())))
79        }
80    }
81
82    // If a separate peer url is provided in the request, use that instead of the one in the config
83    fn get_api_client(api_client: AleoAPIClient<N>, peer_url: &Option<String>) -> Result<AleoAPIClient<N>, Rejection> {
84        if let Some(peer_url) = peer_url {
85            AleoAPIClient::new(peer_url, "testnet3").or_reject()
86        } else {
87            Ok(api_client)
88        }
89    }
90
91    // Deploy a program to the network specified
92    async fn deploy_program(
93        request: DeployRequest<N>,
94        record_finder: RecordFinder<N>,
95        private_key_ciphertext: Option<Ciphertext<N>>,
96        api_client: AleoAPIClient<N>,
97    ) -> Result<impl Reply, Rejection> {
98        // Error if fee == 0
99        if request.fee == 0 {
100            return Err(reject::custom(RestError::Request(
101                "Fee must be greater than zero in order to deploy a program to the Aleo Network".to_string(),
102            )));
103        }
104        // Get API client and private key and create a program manager
105        let api_client = Self::get_api_client(api_client, &request.peer_url)?;
106        let private_key = Self::get_private_key(private_key_ciphertext, request.private_key, request.password.clone())?;
107        let mut program_manager = ProgramManager::new(Some(private_key), None, Some(api_client), None).or_reject()?;
108        program_manager.add_program(&request.program).or_reject()?;
109
110        // Get the fee record if it is not provided in the request
111        let fee_record = if request.fee_record.is_none() {
112            spawn_blocking!(record_finder.find_one_record(&private_key, request.fee))?
113        } else {
114            request.fee_record.unwrap()
115        };
116
117        // Deploy the program and return the resulting transaction id
118        let transaction_id =
119            spawn_blocking!(program_manager.deploy_program(request.program.id(), request.fee, fee_record, None))?;
120
121        Ok(reply::json(&transaction_id))
122    }
123
124    // Execute a program on the network specified
125    async fn execute_program(
126        mut request: ExecuteRequest<N>,
127        record_finder: RecordFinder<N>,
128        private_key_ciphertext: Option<Ciphertext<N>>,
129        api_client: AleoAPIClient<N>,
130    ) -> Result<impl Reply, Rejection> {
131        if request.fee == 0 {
132            return Err(reject::custom(RestError::Request(
133                "Fee must be greater than zero in order to execute a program on the Aleo Network".to_string(),
134            )));
135        }
136        // Get API client and private key and create a program manager
137        let api_client = Self::get_api_client(api_client, &request.peer_url)?;
138        let private_key = Self::get_private_key(private_key_ciphertext, request.private_key, request.password.clone())?;
139        let mut program_manager = ProgramManager::new(Some(private_key), None, Some(api_client), None).or_reject()?;
140
141        // Find a fee record if a fee is specified and a fee record is not provided
142        let fee_record = if request.fee_record.is_none() {
143            spawn_blocking!(record_finder.find_one_record(&private_key, request.fee))?
144        } else {
145            request.fee_record.take().unwrap()
146        };
147
148        // Execute the program and return the resulting transaction id
149        let transaction_id = spawn_blocking!(program_manager.execute_program(
150            request.program_id,
151            request.program_function,
152            request.inputs.iter(),
153            request.fee,
154            fee_record,
155            None,
156        ))?;
157
158        Ok(reply::json(&transaction_id))
159    }
160
161    // Create a value transfer on the network specified
162    async fn transfer(
163        request: TransferRequest<N>,
164        record_finder: RecordFinder<N>,
165        private_key_ciphertext: Option<Ciphertext<N>>,
166        api_client: AleoAPIClient<N>,
167    ) -> Result<impl Reply, Rejection> {
168        // Get API client and private key and create a program manager
169        if request.fee == 0 {
170            return Err(reject::custom(RestError::Request(
171                "Fee must be greater than zero in order to transfer funds on the Aleo Network".to_string(),
172            )));
173        }
174        let api_client = Self::get_api_client(api_client, &request.peer_url)?;
175        let private_key = Self::get_private_key(private_key_ciphertext, request.private_key, request.password.clone())?;
176        let program_manager = ProgramManager::new(Some(private_key), None, Some(api_client), None).or_reject()?;
177
178        let specified_transfer_type = request.transfer_type.as_str();
179        info!("Transfer type specified: {specified_transfer_type}");
180        let transfer_type = match specified_transfer_type {
181            "private" => { TransferType::Private },
182            "public" => { TransferType::Public },
183            "private_to_public" => { TransferType::PrivateToPublic },
184            "public_to_private" => { TransferType::PublicToPrivate },
185            _ => Err(anyhow!("Invalid transfer type specified, type must be one of the following: private, public, private-to-public, public-to-private")).or_reject()?,
186        };
187
188        let (amount_record, fee_record) =
189            match transfer_type {
190                TransferType::Public => {
191                    if let Some(fee_record) = request.fee_record {
192                        (None, fee_record)
193                    } else {
194                        (None, spawn_blocking!(record_finder.find_one_record(&private_key, request.fee))?)
195                    }
196                }
197                TransferType::PublicToPrivate => {
198                    if let Some(fee_record) = request.fee_record {
199                        (None, fee_record)
200                    } else {
201                        (None, spawn_blocking!(record_finder.find_one_record(&private_key, request.fee))?)
202                    }
203                }
204                _ => {
205                    match (request.amount_record, request.fee_record) {
206                        (Some(amount_record), Some(fee_record)) => (Some(amount_record), fee_record),
207                        (Some(amount_record), None) => {
208                            // Find a fee record if a fee is specified and a fee record is not provided
209                            (
210                                Some(amount_record),
211                                spawn_blocking!(record_finder.find_one_record(&private_key, request.fee))?,
212                            )
213                        }
214                        (None, Some(fee_record)) => (
215                            Some(spawn_blocking!(record_finder.find_one_record(&private_key, request.amount))?),
216                            fee_record,
217                        ),
218                        (None, None) => {
219                            let (amount_record, fee_record) = spawn_blocking!(
220                                record_finder.find_amount_and_fee_records(request.amount, request.fee, &private_key)
221                            )?;
222                            (Some(amount_record), fee_record)
223                        }
224                    }
225                }
226            };
227
228        // Run the transfer program within credits.aleo and return the resulting transaction id
229        let transaction_id = spawn_blocking!(program_manager.transfer(
230            request.amount,
231            request.fee,
232            request.recipient,
233            transfer_type,
234            None,
235            amount_record,
236            fee_record
237        ))?;
238        Ok(reply::json(&transaction_id))
239    }
240}