diff --git a/Cargo.lock b/Cargo.lock index f537bfa4d..689bd88c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,6 +1348,25 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "cbindgen" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b922faaf31122819ec80c4047cc684c6979a087366c069611e33649bf98e18d" +dependencies = [ + "clap 3.2.25", + "heck 0.4.1", + "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml 0.5.11", +] + [[package]] name = "cc" version = "1.0.83" @@ -10128,6 +10147,26 @@ dependencies = [ "ts-rs", ] +[[package]] +name = "tari_wallet_sdk_ffi" +version = "0.4.1" +dependencies = [ + "axum", + "cbindgen", + "reqwest", + "tari_common", + "tari_dan_common_types", + "tari_dan_wallet_sdk", + "tari_dan_wallet_storage_sqlite", + "tari_engine_types", + "tari_indexer_client", + "tari_template_abi", + "tari_template_lib", + "tari_transaction", + "thiserror", + "url", +] + [[package]] name = "tariswap_bench" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 8ebee152e..098ef2de3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "clients/base_node_client", "clients/validator_node_client", "clients/wallet_daemon_client", + "clients/tari_wallet_sdk_ffi", "dan_layer/consensus", "dan_layer/consensus_tests", "dan_layer/epoch_manager", @@ -79,6 +80,7 @@ tari_dan_engine = { path = "dan_layer/engine" } tari_dan_storage = { path = "dan_layer/storage" } tari_dan_storage_sqlite = { path = "dan_layer/storage_sqlite" } tari_dan_wallet_daemon = { path = "applications/tari_dan_wallet_daemon" } +tari_wallet_sdk_ffi = { path = "clients/tari_wallet_sdk_ffi" } tari_dan_wallet_sdk = { path = "dan_layer/wallet/sdk" } tari_dan_wallet_crypto = { path = "dan_layer/wallet/crypto" } tari_dan_wallet_storage_sqlite = { path = "dan_layer/wallet/storage_sqlite" } diff --git a/clients/tari_wallet_sdk_ffi/Cargo.toml b/clients/tari_wallet_sdk_ffi/Cargo.toml new file mode 100644 index 000000000..448b509a0 --- /dev/null +++ b/clients/tari_wallet_sdk_ffi/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "tari_wallet_sdk_ffi" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tari_engine_types = { workspace = true } +tari_dan_common_types = { workspace = true } +tari_dan_wallet_sdk = { workspace = true } +tari_dan_wallet_storage_sqlite = { workspace = true } +tari_indexer_client = {workspace = true } +tari_template_abi = {workspace = true} +tari_template_lib = { workspace = true } +tari_transaction = {workspace = true } + + +axum = { workspace = true, features = ["headers"] } +reqwest = { workspace = true, features = ["json"] } +thiserror = { workspace = true } +url = { workspace = true } + + +[build-dependencies] +tari_common = {workspace = true, features = ["build"]} +cbindgen = "0.24.3" + +[lib] +crate-type = ["cdylib"] \ No newline at end of file diff --git a/clients/tari_wallet_sdk_ffi/build.rs b/clients/tari_wallet_sdk_ffi/build.rs new file mode 100644 index 000000000..e23f2b59c --- /dev/null +++ b/clients/tari_wallet_sdk_ffi/build.rs @@ -0,0 +1,58 @@ +// Copyright 2019, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{env, path::PathBuf}; + +use cbindgen::{Config, Language, LineEndingStyle, ParseConfig, Style}; +// use tari_common::build::StaticApplicationInfo; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + // generate version info + // let gen = StaticApplicationInfo::initialize().unwrap(); + // gen.write_consts_to_outdir("consts.rs").unwrap(); + + let output_file = PathBuf::from(&crate_dir) + .join("tari_wallet_sdk_ffi.h") + .display() + .to_string(); + + let config = Config { + language: Language::C, + header: Some("// Copyright 2024. The Tari Project\n// SPDX-License-Identifier: BSD-3-Clause".to_string()), + parse: ParseConfig { + //parse_deps: true, + // include: Some(vec!["tari_comms".to_string()]), + ..Default::default() + }, + autogen_warning: Some("// This file was generated by cargo-bindgen. Please do not edit manually.".to_string()), + style: Style::Tag, + cpp_compat: true, + line_endings: LineEndingStyle::CRLF, + ..Default::default() + }; + + cbindgen::generate_with_config(&crate_dir, config) + .unwrap() + .write_to_file(output_file); +} \ No newline at end of file diff --git a/clients/tari_wallet_sdk_ffi/src/indexer_jrpc_impl.rs b/clients/tari_wallet_sdk_ffi/src/indexer_jrpc_impl.rs new file mode 100644 index 000000000..7474aa6c9 --- /dev/null +++ b/clients/tari_wallet_sdk_ffi/src/indexer_jrpc_impl.rs @@ -0,0 +1,191 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::sync::{Arc, Mutex}; + +use axum::async_trait; +use reqwest::{IntoUrl, Url}; +use tari_dan_common_types::optional::IsNotFoundError; +use tari_dan_wallet_sdk::network::{ + SubstateQueryResult, + TransactionFinalizedResult, + TransactionQueryResult, + WalletNetworkInterface, +}; +use tari_engine_types::substate::SubstateId; +use tari_indexer_client::{ + error::IndexerClientError, + json_rpc_client::IndexerJsonRpcClient, + types::{ + GetSubstateRequest, + GetTransactionResultRequest, + IndexerTransactionFinalizedResult, + SubmitTransactionRequest, + }, +}; +use tari_template_lib::models::TemplateAddress; +use tari_transaction::{SubstateRequirement, Transaction, TransactionId}; +use url::ParseError; + +#[derive(Debug, Clone)] +pub struct IndexerJsonRpcNetworkInterface { + indexer_jrpc_address: Arc>, +} + +impl IndexerJsonRpcNetworkInterface { + pub fn new(indexer_jrpc_address: T) -> Self { + Self { + indexer_jrpc_address: Arc::new(Mutex::new( + indexer_jrpc_address + .into_url() + .expect("Malformed indexer JSON-RPC address"), + )), + } + } + + fn get_client(&self) -> Result { + let client = IndexerJsonRpcClient::connect((*self.indexer_jrpc_address.lock().unwrap()).clone())?; + Ok(client) + } + + pub fn set_endpoint(&mut self, endpoint: &str) -> Result<(), IndexerJrpcError> { + *self.indexer_jrpc_address.lock().unwrap() = Url::parse(endpoint)?; + Ok(()) + } + + pub fn get_endpoint(&self) -> Url { + (*self.indexer_jrpc_address.lock().unwrap()).clone() + } +} + +#[async_trait] +impl WalletNetworkInterface for IndexerJsonRpcNetworkInterface { + type Error = IndexerJrpcError; + + async fn query_substate( + &self, + address: &SubstateId, + version: Option, + local_search_only: bool, + ) -> Result { + let mut client = self.get_client()?; + let result = client + .get_substate(GetSubstateRequest { + address: address.clone(), + version, + local_search_only, + }) + .await?; + Ok(SubstateQueryResult { + address: result.address, + version: result.version, + substate: result.substate, + created_by_transaction: result.created_by_transaction, + }) + } + + async fn submit_transaction( + &self, + transaction: Transaction, + required_substates: Vec, + ) -> Result { + let mut client = self.get_client()?; + let result = client + .submit_transaction(SubmitTransactionRequest { + transaction, + required_substates, + is_dry_run: false, + }) + .await?; + Ok(result.transaction_id) + } + + async fn submit_dry_run_transaction( + &self, + transaction: Transaction, + required_substates: Vec, + ) -> Result { + let mut client = self.get_client()?; + let resp = client + .submit_transaction(SubmitTransactionRequest { + transaction, + required_substates, + is_dry_run: true, + }) + .await?; + + Ok(TransactionQueryResult { + transaction_id: resp.transaction_id, + result: convert_indexer_result_to_wallet_result(resp.result), + }) + } + + async fn query_transaction_result( + &self, + transaction_id: TransactionId, + ) -> Result { + let mut client = self.get_client()?; + let resp = client + .get_transaction_result(GetTransactionResultRequest { transaction_id }) + .await?; + + Ok(TransactionQueryResult { + transaction_id, + result: convert_indexer_result_to_wallet_result(resp.result), + }) + } + + async fn fetch_template_definition( + &self, + template_address: TemplateAddress, + ) -> Result { + let mut client = self.get_client()?; + let resp = client + .get_template_definition(tari_indexer_client::types::GetTemplateDefinitionRequest { template_address }) + .await?; + + Ok(resp.definition) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum IndexerJrpcError { + #[error("Indexer client error: {0}")] + IndexerClientError(#[from] IndexerClientError), + #[error("Indexer parse error : {0}")] + IndexerParseError(#[from] ParseError), +} + +impl IsNotFoundError for IndexerJrpcError { + fn is_not_found_error(&self) -> bool { + match self { + IndexerJrpcError::IndexerClientError(err) => err.is_not_found_error(), + _ => false, + } + } +} + +/// These types are identical, however in order to keep the wallet decoupled from the indexer, we define two types and +/// this conversion function. +// TODO: the common interface and types between the wallet and indexer could be made into a shared "view of the network" +// interface and we can avoid defining two types. +fn convert_indexer_result_to_wallet_result(result: IndexerTransactionFinalizedResult) -> TransactionFinalizedResult { + match result { + IndexerTransactionFinalizedResult::Pending => TransactionFinalizedResult::Pending, + IndexerTransactionFinalizedResult::Finalized { + final_decision, + execution_result, + finalized_time, + execution_time, + abort_details, + json_results, + } => TransactionFinalizedResult::Finalized { + final_decision, + execution_result, + execution_time, + finalized_time, + abort_details, + json_results, + }, + } +} diff --git a/clients/tari_wallet_sdk_ffi/src/lib.rs b/clients/tari_wallet_sdk_ffi/src/lib.rs new file mode 100644 index 000000000..e0858c1a1 --- /dev/null +++ b/clients/tari_wallet_sdk_ffi/src/lib.rs @@ -0,0 +1,66 @@ +use std::ffi::c_int; +use std::time::Duration; +use tari_dan_wallet_sdk::apis::config::{ConfigApi, ConfigApiError, ConfigKey}; +use tari_dan_wallet_sdk::{DanWalletSdk, WalletSdkConfig}; +use tari_dan_wallet_sdk::storage::WalletStorageError; +use tari_dan_wallet_storage_sqlite::SqliteWalletStore; +use std::path::Path; +use crate::indexer_jrpc_impl::IndexerJsonRpcNetworkInterface; +use tari_dan_common_types::optional::Optional; +use tari_dan_wallet_sdk::WalletSdkError; + + +mod indexer_jrpc_impl; + +#[derive(Debug, thiserror::Error)] +enum WalletFfiError { + #[error("Config API error: {0}")] + ConfigApiError(#[from] ConfigApiError), + #[error("Wallet storage error: {0}")] + WalletStorageError(#[from] WalletStorageError), + #[error("Wallet SDK error: {0}")] + WalletSdkError(#[from] WalletSdkError), +} + +impl WalletFfiError { + fn to_c_int(&self) -> c_int { + match self { + WalletFfiError::ConfigApiError(_) => 501, + WalletFfiError::WalletStorageError(_) => 502, + WalletFfiError::WalletSdkError(_) => 503, + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn initialize_wallet_sdk(error_out: *mut c_int) { + let result = initialize_wallet_sdk_inner(Path::new("data/wallet.sqlite"), "http://localhost:8080".to_string()); + match result { + Ok(_) => { + *error_out = 0; + }, + Err(e) => { + *error_out = e.to_c_int(); + } + } +} + +fn initialize_wallet_sdk_inner(db_path: &Path, indexer_url: String) -> Result<(), WalletFfiError> { + let store = SqliteWalletStore::try_open(db_path)?; + store.run_migrations()?; + let sdk_config = WalletSdkConfig { + // TODO: Configure + password: None, + jwt_expiry: Duration::from_millis(1), + jwt_secret_key: "secret".to_string(), + }; + let config_api = ConfigApi::new(&store); + let indexer_jrpc_endpoint = if let Some(config_url) = config_api.get(ConfigKey::IndexerUrl).optional()? { + config_url + } else { + indexer_url + }; + let indexer = IndexerJsonRpcNetworkInterface::new(indexer_jrpc_endpoint); + let wallet_sdk = DanWalletSdk::initialize(store, indexer, sdk_config)?; + Ok(()) +} \ No newline at end of file diff --git a/clients/tari_wallet_sdk_ffi/tari_wallet_sdk_ffi.h b/clients/tari_wallet_sdk_ffi/tari_wallet_sdk_ffi.h new file mode 100644 index 000000000..c922cad6b --- /dev/null +++ b/clients/tari_wallet_sdk_ffi/tari_wallet_sdk_ffi.h @@ -0,0 +1,19 @@ +// Copyright 2024. The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +// This file was generated by cargo-bindgen. Please do not edit manually. + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void initialize_wallet_sdk(int *error_out); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/dan_layer/wallet/sdk/src/lib.rs b/dan_layer/wallet/sdk/src/lib.rs index 75388525e..b6e4898f1 100644 --- a/dan_layer/wallet/sdk/src/lib.rs +++ b/dan_layer/wallet/sdk/src/lib.rs @@ -7,7 +7,7 @@ pub mod apis; pub mod models; mod sdk; -pub use sdk::{DanWalletSdk, WalletSdkConfig}; +pub use sdk::{DanWalletSdk, WalletSdkConfig, WalletSdkError}; pub mod network; pub use tari_key_manager::cipher_seed::CipherSeed;