rusk_wallet/
rues.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7use std::time::Duration;
8
9use reqwest::{Body, Response};
10use rkyv::Archive;
11
12use crate::Error;
13
14/// Supported Rusk version
15const REQUIRED_RUSK_VERSION: &str = "1.0.0-rc.0";
16
17/// Target for contracts
18pub const CONTRACTS_TARGET: &str = "contracts";
19
20#[derive(Clone)]
21/// Rusk HTTP Binary Client
22pub struct RuesHttpClient {
23    client: reqwest::Client,
24    uri: String,
25}
26
27impl RuesHttpClient {
28    /// Create a new HTTP Client
29    pub fn new<S: Into<String>>(uri: S) -> Result<Self, Error> {
30        let client = reqwest::ClientBuilder::new()
31            .connect_timeout(Duration::from_secs(30))
32            .build();
33
34        match client {
35            Ok(client) => Ok(Self {
36                uri: uri.into(),
37                client,
38            }),
39            Err(_) => Err(Error::HttpClient),
40        }
41    }
42
43    /// Utility for querying the rusk VM
44    pub async fn contract_query<I, C, const N: usize>(
45        &self,
46        contract: C,
47        method: &str,
48        value: &I,
49    ) -> Result<Vec<u8>, Error>
50    where
51        I: Archive,
52        I: rkyv::Serialize<rkyv::ser::serializers::AllocSerializer<N>>,
53        C: Into<Option<&'static str>>,
54    {
55        let data = rkyv::to_bytes(value).map_err(|_| Error::Rkyv)?.to_vec();
56
57        let response = self
58            .call_raw(CONTRACTS_TARGET, contract.into(), method, &data, false)
59            .await?;
60
61        Ok(response.bytes().await?.to_vec())
62    }
63
64    /// Check rusk connection
65    pub async fn check_connection(&self) -> Result<(), reqwest::Error> {
66        self.client.post(&self.uri).send().await?;
67
68        Ok(())
69    }
70
71    /// Send a RuskRequest to a specific target.
72    ///
73    /// The response is interpreted as Binary
74    pub async fn call<E>(
75        &self,
76        target: &str,
77        entity: E,
78        topic: &str,
79        request: &[u8],
80    ) -> Result<Vec<u8>, Error>
81    where
82        E: Into<Option<&'static str>>,
83    {
84        let response =
85            self.call_raw(target, entity, topic, request, false).await?;
86        let data = response.bytes().await?;
87        Ok(data.to_vec())
88    }
89
90    /// Send a RuskRequest to a specific target without parsing the response
91    pub async fn call_raw<E>(
92        &self,
93        target: &str,
94        entity: E,
95        topic: &str,
96        data: &[u8],
97        feed: bool,
98    ) -> Result<Response, Error>
99    where
100        E: Into<Option<&'static str>>,
101    {
102        let uri = &self.uri;
103        let entity = entity.into().map(|e| format!(":{e}")).unwrap_or_default();
104
105        let rues_prefix = if uri.ends_with('/') { "on" } else { "/on" };
106        let mut request = self
107            .client
108            .post(format!("{uri}{rues_prefix}/{target}{entity}/{topic}"))
109            .body(Body::from(data.to_vec()))
110            .header("Content-Type", "application/octet-stream")
111            .header("rusk-version", REQUIRED_RUSK_VERSION);
112
113        if feed {
114            request = request.header("Rusk-Feeder", "1");
115        }
116        let response = request.send().await?;
117
118        let status = response.status();
119        if status.is_client_error() || status.is_server_error() {
120            let error = &response.bytes().await?;
121
122            let error = String::from_utf8(error.to_vec())
123                .unwrap_or("unparsable error".into());
124
125            let msg = format!("{status}: {error}");
126
127            Err(Error::Rusk(msg))
128        } else {
129            Ok(response)
130        }
131    }
132}