casper_rust_wasm_sdk/sdk/rpcs/
speculative_exec.rs

1#[cfg(target_arch = "wasm32")]
2use crate::types::hash::block_hash::BlockHash;
3use crate::types::transaction::Transaction;
4use crate::{
5    types::{sdk_error::SdkError, verbosity::Verbosity},
6    SDK,
7};
8use casper_client::{
9    rpcs::results::SpeculativeExecTxnResult as _SpeculativeExecTxnResult,
10    speculative_exec_txn as speculative_exec_lib, JsonRpcId, SuccessResponse,
11};
12#[cfg(target_arch = "wasm32")]
13use gloo_utils::format::JsValueSerdeExt;
14use rand::Rng;
15#[cfg(target_arch = "wasm32")]
16use serde::{Deserialize, Serialize};
17#[cfg(target_arch = "wasm32")]
18use wasm_bindgen::prelude::*;
19
20// Define a struct to wrap the result of a speculative execution.
21#[cfg(target_arch = "wasm32")]
22#[derive(Debug, Deserialize, Clone, Serialize)]
23#[wasm_bindgen]
24pub struct SpeculativeExecTxnResult(_SpeculativeExecTxnResult);
25
26#[cfg(target_arch = "wasm32")]
27impl From<SpeculativeExecTxnResult> for _SpeculativeExecTxnResult {
28    fn from(result: SpeculativeExecTxnResult) -> Self {
29        result.0
30    }
31}
32
33#[cfg(target_arch = "wasm32")]
34impl From<_SpeculativeExecTxnResult> for SpeculativeExecTxnResult {
35    fn from(result: _SpeculativeExecTxnResult) -> Self {
36        SpeculativeExecTxnResult(result)
37    }
38}
39
40#[cfg(target_arch = "wasm32")]
41#[wasm_bindgen]
42impl SpeculativeExecTxnResult {
43    /// Get the API version of the result.
44    #[wasm_bindgen(getter)]
45    pub fn api_version(&self) -> JsValue {
46        JsValue::from_serde(&self.0.api_version).unwrap()
47    }
48
49    /// Get the block hash.
50    #[wasm_bindgen(getter)]
51    pub fn block_hash(&self) -> BlockHash {
52        self.0.execution_result.block_hash.into()
53    }
54
55    /// Get the execution result.
56    #[wasm_bindgen(getter)]
57    pub fn execution_result(&self) -> JsValue {
58        JsValue::from_serde(&self.0.execution_result).unwrap()
59    }
60
61    /// Convert the result to JSON format.
62    #[wasm_bindgen(js_name = "toJson")]
63    pub fn to_json(&self) -> JsValue {
64        JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
65    }
66}
67
68/// Options for speculative execution.
69#[derive(Debug, Deserialize, Clone, Default, Serialize)]
70#[cfg(target_arch = "wasm32")]
71#[wasm_bindgen(js_name = "getSpeculativeExecTxnOptions", getter_with_clone)]
72pub struct GetSpeculativeExecTxnOptions {
73    /// The transaction as a JSON string.
74    pub transaction_as_string: Option<String>,
75
76    /// The transaction to execute.
77    pub transaction: Option<Transaction>,
78
79    /// The rpc address.
80    pub rpc_address: Option<String>,
81
82    /// The verbosity level for logging.
83    pub verbosity: Option<Verbosity>,
84}
85
86#[cfg(target_arch = "wasm32")]
87#[wasm_bindgen]
88impl SDK {
89    /// Get options for speculative execution from a JavaScript value.
90    pub fn get_speculative_exec_options(
91        &self,
92        options: JsValue,
93    ) -> Result<GetSpeculativeExecTxnOptions, JsError> {
94        options
95            .into_serde::<GetSpeculativeExecTxnOptions>()
96            .map_err(|err| JsError::new(&format!("Error deserializing options: {:?}", err)))
97    }
98
99    /// JS function for speculative execution.
100    ///
101    /// # Arguments
102    ///
103    /// * `options` - The options for speculative execution.
104    ///
105    /// # Returns
106    ///
107    /// A `Result` containing the result of the speculative execution or a `JsError` in case of an error.
108    #[wasm_bindgen(js_name = "speculative_exec")]
109    pub async fn speculative_exec_js_alias(
110        &self,
111        options: Option<GetSpeculativeExecTxnOptions>,
112    ) -> Result<SpeculativeExecTxnResult, JsError> {
113        let GetSpeculativeExecTxnOptions {
114            transaction_as_string,
115            transaction,
116            verbosity,
117            rpc_address,
118        } = options.unwrap_or_default();
119
120        let transaction = if let Some(transaction_as_string) = transaction_as_string {
121            Transaction::new(transaction_as_string.into())
122        } else if let Some(transaction) = transaction {
123            transaction
124        } else {
125            let err = "Error: Missing transaction as json or transaction".to_string();
126            return Err(JsError::new(&err));
127        };
128
129        let result = self
130            .speculative_exec(transaction, verbosity, rpc_address)
131            .await;
132        match result {
133            Ok(data) => Ok(data.result.into()),
134            Err(err) => {
135                let err = &format!("Error occurred with {:?}", err);
136                Err(JsError::new(err))
137            }
138        }
139    }
140}
141
142impl SDK {
143    /// Perform speculative execution.
144    ///
145    /// # Arguments
146    ///
147    /// * `transaction` - The transaction to execute.
148    /// * `verbosity` - The verbosity level for logging.
149    /// * `rpc_address` - The address of the node to connect to.
150    ///
151    /// # Returns
152    ///
153    /// A `Result` containing the result of _SpeculativeExecTxnResult or a `SdkError` in case of an error.
154    pub async fn speculative_exec(
155        &self,
156        transaction: Transaction,
157        verbosity: Option<Verbosity>,
158        rpc_address: Option<String>,
159    ) -> Result<SuccessResponse<_SpeculativeExecTxnResult>, SdkError> {
160        //log("speculative_exec!");
161
162        speculative_exec_lib(
163            JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
164            &self.get_rpc_address(rpc_address),
165            self.get_verbosity(verbosity).into(),
166            transaction.into(),
167        )
168        .await
169        .map_err(SdkError::from)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::{
177        helpers::public_key_from_secret_key,
178        types::transaction_params::transaction_str_params::TransactionStrParams,
179    };
180    use sdk_tests::{
181        config::TRANSFER_AMOUNT,
182        tests::helpers::{get_network_constants, get_user_secret_key},
183    };
184    use tokio;
185
186    fn get_transaction() -> Transaction {
187        let secret_key = get_user_secret_key(None).unwrap();
188        let initiator_addr = public_key_from_secret_key(&secret_key).unwrap();
189        let (_, _, _, _, chain_name) = get_network_constants();
190
191        let transaction_params = TransactionStrParams::default();
192        transaction_params.set_secret_key(&secret_key);
193        transaction_params.set_chain_name(&chain_name);
194        transaction_params.set_payment_amount(TRANSFER_AMOUNT);
195
196        Transaction::new_transfer(
197            None,
198            &initiator_addr, // self transfer
199            TRANSFER_AMOUNT,
200            transaction_params,
201            None,
202        )
203        .unwrap()
204    }
205
206    #[tokio::test]
207    async fn test_speculative_exec_with_none_values() {
208        // Arrange
209        let sdk = SDK::new(None, None, None);
210        let transaction = get_transaction();
211        let error_message = "failed to parse node address as valid URL";
212
213        // Act
214        let result = sdk.speculative_exec(transaction, None, None).await;
215
216        // Assert
217        assert!(result.is_err());
218        let err_string = result.err().unwrap().to_string();
219        assert!(err_string.contains(error_message));
220    }
221
222    #[tokio::test]
223    #[ignore]
224    async fn _test_speculative_exec() {
225        // Arrange
226        let sdk = SDK::new(None, None, None);
227        let verbosity = Some(Verbosity::High);
228        let (_, _, default_speculative_address, _, _) = get_network_constants();
229        let transaction = get_transaction();
230
231        // Act
232        let result = sdk
233            .speculative_exec(transaction, verbosity, Some(default_speculative_address))
234            .await;
235
236        // Assert
237        // dbg!(result);
238        assert!(result.is_ok());
239    }
240
241    #[tokio::test]
242    async fn test_speculative_exec_with_error() {
243        // Arrange
244        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
245        let transaction = get_transaction();
246        let error_message = "error sending request for url (http://localhost/rpc)";
247
248        // Act
249        let result = sdk.speculative_exec(transaction, None, None).await;
250
251        // Assert
252        assert!(result.is_err());
253        let err_string = result.err().unwrap().to_string();
254        assert!(err_string.contains(error_message));
255    }
256}