Skip to main content

casper_rust_wasm_sdk/sdk/rpcs/
speculative_exec.rs

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