diff --git a/.gitignore b/.gitignore index 835c118850f..8c3806b7365 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,13 @@ packages/wasm-drive-verify/analysis-results/ packages/wasm-drive-verify/size-analysis/ packages/wasm-drive-verify/test-tree-shaking/ +# wasm-sdk build artifacts +packages/wasm-sdk/target/ +packages/wasm-sdk/.cargo/ +packages/wasm-sdk/pkg/ +packages/wasm-sdk/dist/ +packages/wasm-sdk/*.bak + # gRPC coverage report grpc-coverage-report.txt diff --git a/Cargo.lock b/Cargo.lock index ae300b81587..11fc3ad8e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,7 +458,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.100", "which", @@ -479,7 +479,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.100", ] @@ -598,7 +598,7 @@ source = "git+https://github.com/dashpay/bls-signatures?tag=1.3.3#4e070243aed142 dependencies = [ "bls-dash-sys 1.2.5 (git+https://github.com/dashpay/bls-signatures?tag=1.3.3)", "hex", - "rand", + "rand 0.8.5", "serde", ] @@ -609,7 +609,7 @@ source = "git+https://github.com/dashpay/bls-signatures?rev=0bb5c5b03249c463debb dependencies = [ "bls-dash-sys 1.2.5 (git+https://github.com/dashpay/bls-signatures?rev=0bb5c5b03249c463debb5cef5f7e52ee66f3aaab)", "hex", - "rand", + "rand 0.8.5", "serde", ] @@ -625,9 +625,9 @@ dependencies = [ "hkdf", "merlin", "pairing", - "rand", - "rand_chacha", - "rand_core", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "serde", "serde_bare", "sha2", @@ -663,7 +663,7 @@ dependencies = [ "ff", "group", "pairing", - "rand_core", + "rand_core 0.6.4", "serde", "subtle", "zeroize", @@ -1158,7 +1158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", - "rand_core", + "rand_core 0.6.4", "serdect", "subtle", "zeroize", @@ -1263,6 +1263,18 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "dash-context-provider" +version = "2.0.0" +dependencies = [ + "dpp", + "drive", + "hex", + "serde", + "serde_json", + "thiserror 1.0.64", +] + [[package]] name = "dash-sdk" version = "2.0.0-rc.18" @@ -1278,6 +1290,7 @@ dependencies = [ "clap", "dapi-grpc", "dapi-grpc-macros", + "dash-context-provider", "dashcore-rpc", "data-contracts", "derive_more 1.0.0", @@ -1313,6 +1326,7 @@ dependencies = [ "anyhow", "base64-compat", "bech32", + "bincode", "bitflags 2.9.0", "blake3", "bls-signatures 1.2.5 (git+https://github.com/dashpay/bls-signatures?rev=0bb5c5b03249c463debb5cef5f7e52ee66f3aaab)", @@ -1365,6 +1379,7 @@ name = "dashcore_hashes" version = "0.39.6" source = "git+https://github.com/dashpay/rust-dashcore?tag=v0.39.6#51df58f5d5d499f5ee80ab17076ff70b5347c7db" dependencies = [ + "bincode", "dashcore-private", "secp256k1", "serde", @@ -1569,7 +1584,7 @@ dependencies = [ "platform-version", "platform-versioning", "pretty_assertions", - "rand", + "rand 0.8.5", "regex", "rust_decimal", "rust_decimal_macros", @@ -1615,7 +1630,7 @@ dependencies = [ "once_cell", "parking_lot", "platform-version", - "rand", + "rand 0.8.5", "serde", "serde_json", "sqlparser", @@ -1659,7 +1674,7 @@ dependencies = [ "mockall", "platform-version", "prost", - "rand", + "rand 0.8.5", "regex", "reopen", "rocksdb", @@ -1685,6 +1700,7 @@ version = "2.0.0-rc.18" dependencies = [ "bincode", "dapi-grpc", + "dash-context-provider", "derive_more 1.0.0", "dpp", "drive", @@ -1738,7 +1754,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core", + "rand_core 0.6.4", "serde", "sha2", "subtle", @@ -1765,7 +1781,7 @@ dependencies = [ "group", "hkdf", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "tap", @@ -1918,7 +1934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "bitvec", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2157,9 +2173,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -2193,8 +2211,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "rand_xorshift", "subtle", ] @@ -2277,7 +2295,7 @@ dependencies = [ "indexmap 2.7.0", "integer-encoding", "num_cpus", - "rand", + "rand 0.8.5", "thiserror 2.0.12", ] @@ -2616,6 +2634,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -3187,6 +3206,12 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lz4-sys" version = "1.10.0" @@ -3242,7 +3267,7 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core", + "rand_core 0.6.4", "zeroize", ] @@ -3492,7 +3517,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "rand", + "rand 0.8.5", "serde", ] @@ -3509,7 +3534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", "serde", ] @@ -3748,7 +3773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -3818,7 +3843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] @@ -3908,7 +3933,7 @@ dependencies = [ "indexmap 2.7.0", "platform-serialization", "platform-version", - "rand", + "rand 0.8.5", "serde", "serde_json", "thiserror 2.0.12", @@ -4163,6 +4188,61 @@ dependencies = [ "winapi", ] +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "lru-slab", + "rand 0.9.1", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.40" @@ -4191,8 +4271,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -4202,7 +4292,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -4214,13 +4314,22 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "rand_xorshift" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -4351,7 +4460,10 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", @@ -4359,11 +4471,13 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "windows-registry", ] @@ -4434,7 +4548,7 @@ dependencies = [ "http", "http-serde", "lru", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", @@ -4445,6 +4559,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "rs-sdk-trusted-context-provider" +version = "2.0.0" +dependencies = [ + "arc-swap", + "async-trait", + "dash-context-provider", + "dashcore", + "dpp", + "futures", + "hex", + "lru", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-test", + "tracing", + "url", +] + [[package]] name = "rust_decimal" version = "1.36.0" @@ -4455,7 +4591,7 @@ dependencies = [ "borsh", "bytes", "num-traits", - "rand", + "rand 0.8.5", "rkyv", "serde", "serde_json", @@ -4483,6 +4619,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.0" @@ -4561,6 +4703,9 @@ name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -4645,7 +4790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -4932,7 +5077,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -5050,7 +5195,7 @@ dependencies = [ "platform-serialization", "platform-serialization-derive", "platform-version", - "rand", + "rand 0.8.5", "rocksdb", "serde_json", "simple-signer", @@ -5699,7 +5844,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -5972,7 +6117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.15", - "rand", + "rand 0.8.5", ] [[package]] @@ -6028,7 +6173,7 @@ dependencies = [ "generic-array 1.1.0", "hex", "num", - "rand_core", + "rand_core 0.6.4", "serde", "sha3", "subtle", @@ -6256,6 +6401,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.3" diff --git a/Cargo.toml b/Cargo.toml index 8c598ea14c1..1cd3125fbf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ members = [ "packages/dpns-contract", "packages/data-contracts", "packages/rs-drive-proof-verifier", + "packages/rs-context-provider", + "packages/rs-sdk-trusted-context-provider", "packages/wasm-dpp", "packages/rs-dapi-client", "packages/rs-sdk", diff --git a/packages/rs-context-provider/Cargo.toml b/packages/rs-context-provider/Cargo.toml new file mode 100644 index 00000000000..7df11ad104c --- /dev/null +++ b/packages/rs-context-provider/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "dash-context-provider" +version = "2.0.0" +edition = "2021" +authors = ["sam@dash.org"] +license = "MIT" +description = "Context provider traits for Dash Platform SDK" + +[dependencies] +dpp = { path = "../rs-dpp", default-features = false } +drive = { path = "../rs-drive", default-features = false, features = ["verify"] } +thiserror = "1.0" +hex = { version = "0.4", optional = true } +serde = { version = "1.0", optional = true } +serde_json = { version = "1.0", optional = true } + +[features] +mocks = ["hex", "serde", "serde_json", "dpp/data-contract-serde-conversion"] \ No newline at end of file diff --git a/packages/rs-context-provider/src/error.rs b/packages/rs-context-provider/src/error.rs new file mode 100644 index 00000000000..f29545dd395 --- /dev/null +++ b/packages/rs-context-provider/src/error.rs @@ -0,0 +1,31 @@ +/// Errors returned by the context provider +#[derive(Debug, thiserror::Error)] +pub enum ContextProviderError { + /// Generic Context provider error + #[error("Context provider error: {0}")] + Generic(String), + + /// Configuration error + #[error("Configuration error: {0}")] + Config(String), + + /// Data contract is invalid or not found, or some error occurred during data contract retrieval + #[error("cannot get data contract: {0}")] + DataContractFailure(String), + + /// Token configuration is invalid or not found, or some error occurred during token configuration retrieval + #[error("cannot get token configuration: {0}")] + TokenConfigurationFailure(String), + + /// Provided quorum is invalid + #[error("invalid quorum: {0}")] + InvalidQuorum(String), + + /// Core Fork Error + #[error("activation fork error: {0}")] + ActivationForkError(String), + + /// Async error, eg. when tokio runtime fails + #[error("async error: {0}")] + AsyncError(String), +} diff --git a/packages/rs-context-provider/src/lib.rs b/packages/rs-context-provider/src/lib.rs new file mode 100644 index 00000000000..17da5907131 --- /dev/null +++ b/packages/rs-context-provider/src/lib.rs @@ -0,0 +1,13 @@ +//! Context provider traits for Dash Platform SDK +//! +//! This crate provides the core traits for context providers that are used +//! to fetch network state, data contracts, and quorum public keys. + +pub mod error; +pub mod provider; + +pub use error::ContextProviderError; +pub use provider::{ContextProvider, DataContractProvider}; + +#[cfg(feature = "mocks")] +pub use provider::MockContextProvider; diff --git a/packages/rs-drive-proof-verifier/src/provider.rs b/packages/rs-context-provider/src/provider.rs similarity index 98% rename from packages/rs-drive-proof-verifier/src/provider.rs rename to packages/rs-context-provider/src/provider.rs index 374aa2f3d27..d8843b48bee 100644 --- a/packages/rs-drive-proof-verifier/src/provider.rs +++ b/packages/rs-context-provider/src/provider.rs @@ -1,12 +1,15 @@ use crate::error::ContextProviderError; -use dpp::data_contract::serialized_version::DataContractInSerializationFormat; use dpp::data_contract::TokenConfiguration; use dpp::prelude::{CoreBlockHeight, DataContract, Identifier}; use dpp::version::PlatformVersion; use drive::{error::proof::ProofError, query::ContractLookupFn}; +use std::{ops::Deref, sync::Arc}; + #[cfg(feature = "mocks")] -use hex::ToHex; -use std::{io::ErrorKind, ops::Deref, sync::Arc}; +use { + dpp::data_contract::serialized_version::DataContractInSerializationFormat, hex::ToHex, + std::io::ErrorKind, +}; /// Interface between the Sdk and state of the application. /// diff --git a/packages/rs-drive-proof-verifier/Cargo.toml b/packages/rs-drive-proof-verifier/Cargo.toml index 2fe38480e03..31b34c72b21 100644 --- a/packages/rs-drive-proof-verifier/Cargo.toml +++ b/packages/rs-drive-proof-verifier/Cargo.toml @@ -30,6 +30,7 @@ dpp = { path = "../rs-dpp", features = [ "bls-signatures", "core-types-serialization", ], default-features = false } +dash-context-provider = { path = "../rs-context-provider", features = ["mocks"] } bincode = { version = "=2.0.0-rc.3", features = ["serde"] } platform-serialization-derive = { path = "../rs-platform-serialization-derive", optional = true } platform-serialization = { path = "../rs-platform-serialization" } diff --git a/packages/rs-drive-proof-verifier/src/error.rs b/packages/rs-drive-proof-verifier/src/error.rs index 3e5839483bb..b684f50bdb1 100644 --- a/packages/rs-drive-proof-verifier/src/error.rs +++ b/packages/rs-drive-proof-verifier/src/error.rs @@ -94,39 +94,7 @@ pub enum Error { /// Context provider error #[error("context provider error: {0}")] - ContextProviderError(#[from] ContextProviderError), -} - -/// Errors returned by the context provider -#[derive(Debug, thiserror::Error)] -pub enum ContextProviderError { - /// Generic Context provider error - #[error("Context provider error: {0}")] - Generic(String), - - /// Configuration error - #[error("Configuration error: {0}")] - Config(String), - - /// Data contract is invalid or not found, or some error occurred during data contract retrieval - #[error("cannot get data contract: {0}")] - DataContractFailure(String), - - /// Token configuration is invalid or not found, or some error occurred during token configuration retrieval - #[error("cannot get token configuration: {0}")] - TokenConfigurationFailure(String), - - /// Provided quorum is invalid - #[error("invalid quorum: {0}")] - InvalidQuorum(String), - - /// Core Fork Error - #[error("activation fork error: {0}")] - ActivationForkError(String), - - /// Async error, eg. when tokio runtime fails - #[error("async error: {0}")] - AsyncError(String), + ContextProviderError(#[from] dash_context_provider::ContextProviderError), } impl From for Error { diff --git a/packages/rs-drive-proof-verifier/src/lib.rs b/packages/rs-drive-proof-verifier/src/lib.rs index dfa6e414435..adc1fbdbd6b 100644 --- a/packages/rs-drive-proof-verifier/src/lib.rs +++ b/packages/rs-drive-proof-verifier/src/lib.rs @@ -5,14 +5,16 @@ pub mod error; /// Implementation of proof verification mod proof; -mod provider; pub mod types; mod verify; pub use error::Error; pub use proof::{FromProof, Length}; + +// Re-export context provider types from dash-context-provider #[cfg(feature = "mocks")] -pub use provider::MockContextProvider; -pub use provider::{ContextProvider, DataContractProvider}; +pub use dash_context_provider::MockContextProvider; +pub use dash_context_provider::{ContextProvider, ContextProviderError, DataContractProvider}; + /// From Request pub mod from_request; /// Implementation of unproved verification diff --git a/packages/rs-drive-proof-verifier/src/proof.rs b/packages/rs-drive-proof-verifier/src/proof.rs index 26df9deb900..72f09a0c366 100644 --- a/packages/rs-drive-proof-verifier/src/proof.rs +++ b/packages/rs-drive-proof-verifier/src/proof.rs @@ -8,9 +8,8 @@ pub mod token_status; pub mod token_total_supply; use crate::from_request::TryFromRequest; -use crate::provider::DataContractProvider; use crate::verify::verify_tenderdash_proof; -use crate::{types::*, ContextProvider, Error}; +use crate::{types::*, ContextProvider, DataContractProvider, Error}; use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start; use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0; use dapi_grpc::platform::v0::get_path_elements_request::GetPathElementsRequestV0; diff --git a/packages/rs-sdk-trusted-context-provider/Cargo.toml b/packages/rs-sdk-trusted-context-provider/Cargo.toml new file mode 100644 index 00000000000..35669b1619e --- /dev/null +++ b/packages/rs-sdk-trusted-context-provider/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "rs-sdk-trusted-context-provider" +version = "2.0.0" +edition = "2021" +authors = ["sam@dash.org"] +license = "MIT" +description = "Trusted HTTP-based context provider for Dash Platform SDK" + +[dependencies] +dash-context-provider = { path = "../rs-context-provider" } +dpp = { path = "../rs-dpp", default-features = false, features = ["dash-sdk-features"] } +tokio = { version = "1.40", features = ["macros", "rt-multi-thread", "time"] } +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "2.0" +tracing = "0.1.40" +lru = "0.12.5" +arc-swap = "1.7.1" +async-trait = "0.1.83" +hex = "0.4.3" +dashcore = { git = "https://github.com/dashpay/rust-dashcore", features = ["bls-signatures"], tag = "v0.39.6" } +futures = "0.3" +url = "2.5" + +[dev-dependencies] +tokio-test = "0.4.4" \ No newline at end of file diff --git a/packages/rs-sdk-trusted-context-provider/README.md b/packages/rs-sdk-trusted-context-provider/README.md new file mode 100644 index 00000000000..e63b63697b7 --- /dev/null +++ b/packages/rs-sdk-trusted-context-provider/README.md @@ -0,0 +1,134 @@ +# Trusted Context Provider for Dash SDK + +This crate provides a trusted HTTP-based context provider for the Dash SDK that fetches quorum information from trusted HTTP endpoints instead of requiring Core RPC access. + +## Features + +- Fetches quorum public keys from trusted HTTP endpoints +- Supports mainnet, testnet, and devnet networks +- Allows custom URLs for your own trusted endpoints +- LRU caching for quorum data +- Optional fallback provider for data contracts and token configurations +- Domain resolution verification during initialization + +## Networks Supported + +- **Mainnet**: Uses `https://quorums.mainnet.networks.dash.org/` +- **Testnet**: Uses `https://quorums.testnet.networks.dash.org/` +- **Devnet**: Uses `https://quorums.devnet..networks.dash.org/` + +## Usage + +### Basic Usage + +```rust +use dash_sdk::{Sdk, SdkBuilder}; +use dash_sdk::dapi_client::AddressList; +use dpp::dashcore::Network; +use rs_sdk_trusted_context_provider::TrustedHttpContextProvider; +use std::num::NonZeroUsize; + +// Create the trusted context provider +let context_provider = TrustedHttpContextProvider::new( + Network::Testnet, + None, // devnet_name - only needed for devnet + NonZeroUsize::new(100).expect("cache size"), +)?; + +// Build SDK with the trusted context provider +let sdk = SdkBuilder::new(AddressList::default()) + .with_context_provider(context_provider) + .build()?; +``` + +### With Custom URL + +If you want to use your own trusted HTTP endpoint instead of the default ones: + +```rust +// Create provider with custom URL +let custom_provider = TrustedHttpContextProvider::new_with_url( + Network::Testnet, + "https://my-trusted-server.com".to_string(), + NonZeroUsize::new(100).unwrap(), +)?; + +// Build SDK with the custom provider +let sdk = SdkBuilder::new(AddressList::default()) + .with_context_provider(custom_provider) + .build()?; +``` + +### With Fallback Provider + +Since the trusted HTTP provider only provides quorum public keys, you may need to set a fallback provider for data contracts and token configurations: + +```rust +use dash_sdk::mock::provider::GrpcContextProvider; + +// Create a fallback provider that can fetch data contracts +let grpc_provider = GrpcContextProvider::new( + None, + "core.example.com", + 19998, + "dashrpc", + "password", + NonZeroUsize::new(100).unwrap(), + NonZeroUsize::new(100).unwrap(), + NonZeroUsize::new(100).unwrap(), +)?; + +// Create the trusted provider with fallback +let trusted_provider = TrustedHttpContextProvider::new( + Network::Testnet, + None, + NonZeroUsize::new(100).unwrap(), +)? +.with_fallback_provider(grpc_provider); + +// Build SDK with the trusted provider +let sdk = SdkBuilder::new(AddressList::default()) + .with_context_provider(trusted_provider) + .build()?; +``` + +### With Pre-loaded Known Contracts + +You can also pre-load known contracts that will be served immediately without requiring a fallback provider: + +```rust +use dpp::data_contract::DataContract; + +// Load your known contracts +let dpns_contract = DataContract::from_json(...)?; +let dashpay_contract = DataContract::from_json(...)?; + +// Create the trusted provider with known contracts +let trusted_provider = TrustedHttpContextProvider::new( + Network::Testnet, + None, + NonZeroUsize::new(100).unwrap(), +)? +.with_known_contracts(vec![dpns_contract, dashpay_contract]); + +// Build SDK with the trusted provider +let sdk = SdkBuilder::new(AddressList::default()) + .with_context_provider(trusted_provider) + .build()?; +``` + +## Implementation Details + +The `TrustedHttpContextProvider` implements the `ContextProvider` trait and provides: + +1. **Quorum Public Keys**: Fetched from trusted HTTP endpoints with LRU caching +2. **Data Contracts**: + - First checks pre-loaded known contracts (if any) + - Then delegates to the fallback provider if set + - Otherwise returns `None` +3. **Token Configurations**: Delegated to the fallback provider if set, otherwise returns `None` +4. **Platform Activation Height**: Returns hardcoded values for each network + +## License + +MIT \ No newline at end of file diff --git a/packages/rs-sdk-trusted-context-provider/src/error.rs b/packages/rs-sdk-trusted-context-provider/src/error.rs new file mode 100644 index 00000000000..91fe0f98211 --- /dev/null +++ b/packages/rs-sdk-trusted-context-provider/src/error.rs @@ -0,0 +1,31 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TrustedContextProviderError { + #[error("HTTP request error: {0}")] + HttpError(#[from] reqwest::Error), + + #[error("JSON parsing error: {0}")] + JsonError(#[from] serde_json::Error), + + #[error("Quorum not found for type {quorum_type} and hash {quorum_hash}")] + QuorumNotFound { + quorum_type: u32, + quorum_hash: String, + }, + + #[error("Invalid quorum public key format")] + InvalidPublicKeyFormat, + + #[error("Network error: {0}")] + NetworkError(String), + + #[error("Cache error: {0}")] + CacheError(String), + + #[error("Invalid devnet name: {0}")] + InvalidDevnetName(String), + + #[error("Unsupported network: {0}")] + UnsupportedNetwork(String), +} diff --git a/packages/rs-sdk-trusted-context-provider/src/lib.rs b/packages/rs-sdk-trusted-context-provider/src/lib.rs new file mode 100644 index 00000000000..ba067951109 --- /dev/null +++ b/packages/rs-sdk-trusted-context-provider/src/lib.rs @@ -0,0 +1,62 @@ +//! # Trusted Context Provider for Dash Platform SDK +//! +//! This crate provides a trusted HTTP-based context provider that fetches quorum +//! information from trusted HTTP endpoints instead of requiring Core RPC access. +//! +//! ## Networks Supported +//! - **Mainnet**: Uses `https://quorums.mainnet.networks.dash.org/` +//! - **Testnet**: Uses `https://quorums.testnet.networks.dash.org/` +//! - **Devnet**: Uses `https://quorums.devnet..networks.dash.org/` + +pub mod error; +pub mod provider; +pub mod types; + +pub use error::TrustedContextProviderError; +pub use provider::TrustedHttpContextProvider; + +use dpp::dashcore::Network; + +/// Get the base URL for quorum endpoints based on the network +pub fn get_quorum_base_url( + network: Network, + devnet_name: Option<&str>, +) -> Result { + match network { + Network::Dash => Ok("https://quorums.mainnet.networks.dash.org".to_string()), + Network::Testnet => Ok("https://quorums.testnet.networks.dash.org".to_string()), + Network::Devnet => { + if let Some(name) = devnet_name { + // Validate devnet name format: must be alphanumeric with hyphens allowed + if name.is_empty() { + return Err(TrustedContextProviderError::InvalidDevnetName( + "Devnet name cannot be empty".to_string(), + )); + } + if !name.chars().all(|c| c.is_alphanumeric() || c == '-') { + return Err(TrustedContextProviderError::InvalidDevnetName( + "Devnet name must contain only alphanumeric characters and hyphens" + .to_string(), + )); + } + if name.starts_with('-') || name.ends_with('-') { + return Err(TrustedContextProviderError::InvalidDevnetName( + "Devnet name cannot start or end with a hyphen".to_string(), + )); + } + Ok(format!("https://quorums.devnet.{}.networks.dash.org", name)) + } else { + Err(TrustedContextProviderError::InvalidDevnetName( + "Devnet name must be provided for devnet network".to_string(), + )) + } + } + Network::Regtest => Err(TrustedContextProviderError::UnsupportedNetwork( + "Regtest network is not supported by trusted context provider".to_string(), + )), + _ => Err(TrustedContextProviderError::UnsupportedNetwork(format!( + "Unknown network type: {:?}", + network + ))), + } +} diff --git a/packages/rs-sdk-trusted-context-provider/src/provider.rs b/packages/rs-sdk-trusted-context-provider/src/provider.rs new file mode 100644 index 00000000000..16c8edb0ca0 --- /dev/null +++ b/packages/rs-sdk-trusted-context-provider/src/provider.rs @@ -0,0 +1,532 @@ +use crate::error::TrustedContextProviderError; +use crate::get_quorum_base_url; +use crate::types::{PreviousQuorumsResponse, QuorumData, QuorumsResponse}; + +use arc_swap::ArcSwap; +use async_trait::async_trait; +use dash_context_provider::{ContextProvider, ContextProviderError}; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::prelude::{CoreBlockHeight, DataContract, Identifier}; +// QuorumHash is just [u8; 32] +type QuorumHash = [u8; 32]; +use dpp::dashcore::Network; +use dpp::data_contract::TokenConfiguration; +use dpp::version::PlatformVersion; + +/// Get the LLMQ type for the network +fn get_llmq_type_for_network(network: Network) -> u32 { + match network { + Network::Dash => 4, // Mainnet uses LLMQ type 4 + Network::Testnet => 6, // Testnet uses LLMQ type 6 + Network::Devnet => 107, // Devnet uses LLMQ type 107 + _ => 6, // Default to testnet type + } +} +use lru::LruCache; +use reqwest::Client; +use std::collections::HashMap; +use std::net::ToSocketAddrs; +use std::num::NonZeroUsize; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use tracing::{debug, info}; +use url::Url; + +/// A trusted HTTP-based context provider that fetches quorum information +/// from trusted HTTP endpoints instead of requiring Core RPC access. +pub struct TrustedHttpContextProvider { + network: Network, + client: Client, + base_url: String, + + /// Cache for current quorums + current_quorums_cache: Arc>>, + + /// Cache for previous quorums + previous_quorums_cache: Arc>>, + + /// Last fetched current quorums data + last_current_quorums: Arc>>, + + /// Last fetched previous quorums data + last_previous_quorums: Arc>>, + + /// Optional fallback provider for data contracts and token configurations + fallback_provider: Option>, + + /// Known contracts cache - contracts that are pre-loaded and can be served immediately + known_contracts: HashMap>, +} + +impl TrustedHttpContextProvider { + /// Verify that a URL's domain resolves + fn verify_domain_resolves(url: &str) -> Result<(), TrustedContextProviderError> { + let parsed_url = Url::parse(url).map_err(|e| { + TrustedContextProviderError::NetworkError(format!("Invalid URL: {}", e)) + })?; + + let host = parsed_url.host_str().ok_or_else(|| { + TrustedContextProviderError::NetworkError("URL has no host".to_string()) + })?; + + let port = parsed_url.port_or_known_default().ok_or_else(|| { + TrustedContextProviderError::NetworkError( + "Unknown URL scheme and no port specified".to_string(), + ) + })?; + + // Try to resolve the domain + let addr = format!("{}:{}", host, port); + match addr.to_socket_addrs() { + Ok(mut addrs) => { + if addrs.next().is_none() { + return Err(TrustedContextProviderError::NetworkError(format!( + "Domain '{}' does not resolve to any IP addresses", + host + ))); + } + debug!("Domain '{}' resolves successfully", host); + Ok(()) + } + Err(e) => Err(TrustedContextProviderError::NetworkError(format!( + "Failed to resolve domain '{}': {}", + host, e + ))), + } + } + + /// Create a new trusted HTTP context provider with default URLs + pub fn new( + network: Network, + devnet_name: Option, + cache_size: NonZeroUsize, + ) -> Result { + let base_url = get_quorum_base_url(network, devnet_name.as_deref())?; + Self::new_with_url(network, base_url, cache_size) + } + + /// Create a new trusted HTTP context provider with a custom URL + pub fn new_with_url( + network: Network, + base_url: String, + cache_size: NonZeroUsize, + ) -> Result { + // Verify the domain resolves before proceeding + Self::verify_domain_resolves(&base_url)?; + + let client = Client::builder().timeout(Duration::from_secs(30)).build()?; + + Ok(Self { + network, + client, + base_url, + current_quorums_cache: Arc::new(Mutex::new(LruCache::new(cache_size))), + previous_quorums_cache: Arc::new(Mutex::new(LruCache::new(cache_size))), + last_current_quorums: Arc::new(ArcSwap::new(Arc::new(None))), + last_previous_quorums: Arc::new(ArcSwap::new(Arc::new(None))), + fallback_provider: None, + known_contracts: HashMap::new(), + }) + } + + /// Set a fallback provider for data contracts and token configurations + pub fn with_fallback_provider(mut self, provider: P) -> Self { + self.fallback_provider = Some(Box::new(provider)); + self + } + + /// Set known contracts that will be served immediately without fallback + pub fn with_known_contracts(mut self, contracts: Vec) -> Self { + for contract in contracts { + let id = contract.id(); + self.known_contracts.insert(id, Arc::new(contract)); + } + self + } + + /// Fetch current quorums from the HTTP endpoint + async fn fetch_current_quorums(&self) -> Result { + let llmq_type = get_llmq_type_for_network(self.network); + let url = format!("{}/quorums?quorumType={}", self.base_url, llmq_type); + debug!("Fetching current quorums from: {}", url); + + let response = self.client.get(&url).send().await?; + + if !response.status().is_success() { + return Err(TrustedContextProviderError::NetworkError(format!( + "HTTP {} from {}", + response.status(), + url + ))); + } + + let quorums: QuorumsResponse = response.json().await?; + + // Update cache + self.last_current_quorums + .store(Arc::new(Some(quorums.clone()))); + + // Cache individual quorums + if let Ok(mut cache) = self.current_quorums_cache.lock() { + for quorum in &quorums.data { + match hex::decode(&quorum.quorum_hash) + .ok() + .and_then(|bytes| bytes.try_into().ok()) + { + Some(hash) => { + cache.put(hash, quorum.clone()); + } + None => { + debug!( + "Skipping invalid quorum hash '{}' for current quorums", + quorum.quorum_hash + ); + } + } + } + } + + Ok(quorums) + } + + /// Fetch previous quorums from the HTTP endpoint + async fn fetch_previous_quorums( + &self, + ) -> Result { + let llmq_type = get_llmq_type_for_network(self.network); + let url = format!("{}/previous?quorumType={}", self.base_url, llmq_type); + debug!("Fetching previous quorums from: {}", url); + + let response = self.client.get(&url).send().await?; + + if !response.status().is_success() { + return Err(TrustedContextProviderError::NetworkError(format!( + "HTTP {} from {}", + response.status(), + url + ))); + } + + let quorums: PreviousQuorumsResponse = response.json().await?; + + // Update cache + self.last_previous_quorums + .store(Arc::new(Some(quorums.clone()))); + + // Cache individual quorums + if let Ok(mut cache) = self.previous_quorums_cache.lock() { + for quorum in &quorums.data.quorums { + match hex::decode(&quorum.quorum_hash) + .ok() + .and_then(|bytes| bytes.try_into().ok()) + { + Some(hash) => { + cache.put(hash, quorum.clone()); + } + None => { + debug!( + "Skipping invalid quorum hash '{}' for previous quorums", + quorum.quorum_hash + ); + } + } + } + } + + Ok(quorums) + } + + /// Find a quorum by type and hash + async fn find_quorum( + &self, + quorum_type: u32, + quorum_hash: QuorumHash, + ) -> Result { + let expected_type = get_llmq_type_for_network(self.network); + if quorum_type != expected_type { + debug!( + "Quorum type {} doesn't match network type {}", + quorum_type, expected_type + ); + } + + // Check current cache first + if let Ok(mut cache) = self.current_quorums_cache.lock() { + if let Some(quorum) = cache.get(&quorum_hash) { + debug!("Found quorum in current cache"); + return Ok(quorum.clone()); + } + } + + // Check previous cache + if let Ok(mut cache) = self.previous_quorums_cache.lock() { + if let Some(quorum) = cache.get(&quorum_hash) { + debug!("Found quorum in previous cache"); + return Ok(quorum.clone()); + } + } + + // Fetch fresh data + info!("Quorum not in cache, fetching fresh data"); + + // Try current quorums first + if let Ok(current) = self.fetch_current_quorums().await { + for quorum in ¤t.data { + let hash_bytes: Option<[u8; 32]> = hex::decode(&quorum.quorum_hash) + .ok() + .and_then(|bytes| bytes.try_into().ok()); + + if let Some(hash_bytes) = hash_bytes { + if hash_bytes == quorum_hash { + return Ok(quorum.clone()); + } + } + } + } + + // Try previous quorums + if let Ok(previous) = self.fetch_previous_quorums().await { + for quorum in &previous.data.quorums { + let hash_bytes: Option<[u8; 32]> = hex::decode(&quorum.quorum_hash) + .ok() + .and_then(|bytes| bytes.try_into().ok()); + + if let Some(hash_bytes) = hash_bytes { + if hash_bytes == quorum_hash { + return Ok(quorum.clone()); + } + } + } + } + + Err(TrustedContextProviderError::QuorumNotFound { + quorum_type, + quorum_hash: hex::encode(quorum_hash), + }) + } +} + +#[async_trait] +impl ContextProvider for TrustedHttpContextProvider { + fn get_quorum_public_key( + &self, + quorum_type: u32, + quorum_hash: QuorumHash, + _core_chain_locked_height: CoreBlockHeight, + ) -> Result<[u8; 48], ContextProviderError> { + // Use blocking to run async code in sync context + let quorum = futures::executor::block_on(self.find_quorum(quorum_type, quorum_hash)) + .map_err(|e| ContextProviderError::Generic(e.to_string()))?; + + // Parse the public key from the 'key' field + let pubkey_hex = quorum.key.trim_start_matches("0x"); + let pubkey_bytes = hex::decode(pubkey_hex).map_err(|e| { + ContextProviderError::Generic(format!("Invalid hex in public key: {}", e)) + })?; + + if pubkey_bytes.len() != 48 { + return Err(ContextProviderError::Generic(format!( + "Invalid public key length: {} bytes, expected 48", + pubkey_bytes.len() + ))); + } + + pubkey_bytes.try_into().map_err(|_| { + ContextProviderError::Generic("Failed to convert public key to array".to_string()) + }) + } + + fn get_data_contract( + &self, + id: &Identifier, + platform_version: &PlatformVersion, + ) -> Result>, ContextProviderError> { + // First check known contracts cache + if let Some(contract) = self.known_contracts.get(id) { + return Ok(Some(contract.clone())); + } + + // If not found in known contracts, delegate to fallback provider if available + if let Some(ref provider) = self.fallback_provider { + provider.get_data_contract(id, platform_version) + } else { + // No fallback provider, return None + Ok(None) + } + } + + fn get_token_configuration( + &self, + token_id: &Identifier, + ) -> Result, ContextProviderError> { + // Delegate to fallback provider if available + if let Some(ref provider) = self.fallback_provider { + provider.get_token_configuration(token_id) + } else { + // No fallback provider, return None + Ok(None) + } + } + + fn get_platform_activation_height(&self) -> Result { + // Return the L1 locked height for each network + match self.network { + Network::Dash => Ok(2132092), // Mainnet L1 locked height + Network::Testnet => Ok(1090319), // Testnet L1 locked height + Network::Devnet => Ok(1), // Devnet activation height + _ => Err(ContextProviderError::Generic( + "Unsupported network".to_string(), + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_quorum_base_url() { + assert_eq!( + get_quorum_base_url(Network::Dash, None).unwrap(), + "https://quorums.mainnet.networks.dash.org" + ); + + assert_eq!( + get_quorum_base_url(Network::Testnet, None).unwrap(), + "https://quorums.testnet.networks.dash.org" + ); + + assert_eq!( + get_quorum_base_url(Network::Devnet, Some("example")).unwrap(), + "https://quorums.devnet.example.networks.dash.org" + ); + } + + #[test] + fn test_devnet_without_name_returns_error() { + let result = get_quorum_base_url(Network::Devnet, None); + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + TrustedContextProviderError::InvalidDevnetName(_) + )); + } + + #[test] + fn test_regtest_returns_error() { + let result = get_quorum_base_url(Network::Regtest, None); + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + TrustedContextProviderError::UnsupportedNetwork(_) + )); + } + + #[test] + fn test_invalid_devnet_names() { + // Empty name + let result = get_quorum_base_url(Network::Devnet, Some("")); + assert!(result.is_err()); + + // Name with special characters + let result = get_quorum_base_url(Network::Devnet, Some("test@name")); + assert!(result.is_err()); + + // Name starting with hyphen + let result = get_quorum_base_url(Network::Devnet, Some("-test")); + assert!(result.is_err()); + + // Name ending with hyphen + let result = get_quorum_base_url(Network::Devnet, Some("test-")); + assert!(result.is_err()); + + // Valid names should work + assert!(get_quorum_base_url(Network::Devnet, Some("test")).is_ok()); + assert!(get_quorum_base_url(Network::Devnet, Some("test-123")).is_ok()); + assert!(get_quorum_base_url(Network::Devnet, Some("TEST123")).is_ok()); + } + + #[test] + fn test_known_contracts() { + use dpp::version::PlatformVersion; + + // Create a provider + let provider = TrustedHttpContextProvider::new( + Network::Testnet, + None, + NonZeroUsize::new(100).unwrap(), + ) + .unwrap(); + + // Test that initially there are no known contracts + let contract_id = Identifier::from([1u8; 32]); + let retrieved = provider + .get_data_contract(&contract_id, PlatformVersion::latest()) + .unwrap(); + assert!(retrieved.is_none()); + + // Test that we can use the builder pattern to add known contracts + // The builder pattern is more appropriate since contracts are only added during initialization + } + + #[test] + fn test_domain_resolution_check() { + // Test with a domain that should resolve (using localhost) + let result = TrustedHttpContextProvider::verify_domain_resolves("https://localhost"); + assert!(result.is_ok()); + + // Test with HTTP URL (should use port 80 by default) + let result = TrustedHttpContextProvider::verify_domain_resolves("http://localhost"); + assert!(result.is_ok()); + + // Test with an invalid domain that won't resolve + let result = TrustedHttpContextProvider::verify_domain_resolves( + "https://this-domain-definitely-does-not-exist-12345.com", + ); + assert!(result.is_err()); + + // Test with an invalid URL + let result = TrustedHttpContextProvider::verify_domain_resolves("not-a-valid-url"); + assert!(result.is_err()); + + // Test with unknown scheme - should fail due to port_or_known_default returning None + let result = TrustedHttpContextProvider::verify_domain_resolves("unknown://localhost"); + assert!(result.is_err()); + } + + #[test] + fn test_provider_creation_with_invalid_domain() { + // This test will fail if we try to create a provider with an invalid devnet name + // that results in a non-resolving domain + let result = TrustedHttpContextProvider::new( + Network::Devnet, + Some("nonexistent-devnet-12345".to_string()), + NonZeroUsize::new(100).unwrap(), + ); + + assert!(result.is_err()); + } + + #[test] + fn test_provider_with_custom_url() { + // Test with a valid custom URL (localhost should resolve) + let result = TrustedHttpContextProvider::new_with_url( + Network::Testnet, + "https://localhost:8080".to_string(), + NonZeroUsize::new(100).unwrap(), + ); + assert!(result.is_ok()); + + let provider = result.unwrap(); + assert_eq!(provider.base_url, "https://localhost:8080"); + + // Test with an invalid custom URL + let result = TrustedHttpContextProvider::new_with_url( + Network::Testnet, + "https://this-domain-definitely-does-not-exist-12345.com".to_string(), + NonZeroUsize::new(100).unwrap(), + ); + assert!(result.is_err()); + } +} diff --git a/packages/rs-sdk-trusted-context-provider/src/types.rs b/packages/rs-sdk-trusted-context-provider/src/types.rs new file mode 100644 index 00000000000..01ea7c6de19 --- /dev/null +++ b/packages/rs-sdk-trusted-context-provider/src/types.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; + +/// Response from the quorums endpoint +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QuorumsResponse { + pub success: bool, + pub data: Vec, +} + +/// Data about a specific quorum +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QuorumData { + pub quorum_hash: String, + pub key: String, + pub height: u64, + pub valid_members_count: u32, +} + +/// Information about a specific quorum (internal format) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QuorumInfo { + pub version: u16, + pub llmq_type: u32, + pub quorum_hash: String, + pub quorum_public_key: String, + #[serde(rename = "signersCount")] + pub signers_count: u32, + pub signers: String, + #[serde(rename = "validMembersCount")] + pub valid_members_count: u32, + #[serde(rename = "validMembers")] + pub valid_members: String, + #[serde(rename = "quorumIndex")] + pub quorum_index: Option, +} + +/// Response from the previous quorums endpoint +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PreviousQuorumsResponse { + pub success: bool, + pub data: PreviousQuorumsData, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PreviousQuorumsData { + pub height: u64, + pub quorums: Vec, +} diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index bb9a8c0161f..0a48b3e7cda 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -19,6 +19,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } +dash-context-provider = { path = "../rs-context-provider", default-features = false } dapi-grpc-macros = { path = "../rs-dapi-grpc-macros" } http = { version = "1.1" } rustls-pemfile = { version = "2.0.0" } diff --git a/packages/rs-sdk/src/core/dash_core_client.rs b/packages/rs-sdk/src/core/dash_core_client.rs index 67d1dea28ce..1b586e62c2b 100644 --- a/packages/rs-sdk/src/core/dash_core_client.rs +++ b/packages/rs-sdk/src/core/dash_core_client.rs @@ -4,6 +4,7 @@ //! into dash-platform-sdk. use crate::error::Error; +use dash_context_provider::ContextProviderError; use dashcore_rpc::{ dashcore::{hashes::Hash, Amount, QuorumHash}, dashcore_rpc_json as json, @@ -12,7 +13,6 @@ use dashcore_rpc::{ }; use dpp::dashcore::ProTxHash; use dpp::prelude::CoreBlockHeight; -use drive_proof_verifier::error::ContextProviderError; use std::{fmt::Debug, sync::Mutex}; use zeroize::Zeroizing; diff --git a/packages/rs-sdk/src/error.rs b/packages/rs-sdk/src/error.rs index 4412a580e36..fafb95649e5 100644 --- a/packages/rs-sdk/src/error.rs +++ b/packages/rs-sdk/src/error.rs @@ -1,12 +1,12 @@ //! Definitions of errors use dapi_grpc::platform::v0::StateTransitionBroadcastError as StateTransitionBroadcastErrorProto; use dapi_grpc::tonic::Code; +pub use dash_context_provider::ContextProviderError; use dpp::block::block_info::BlockInfo; use dpp::consensus::ConsensusError; use dpp::serialization::PlatformDeserializable; use dpp::version::PlatformVersionError; use dpp::ProtocolError; -pub use drive_proof_verifier::error::ContextProviderError; use rs_dapi_client::transport::TransportError; use rs_dapi_client::{CanRetry, DapiClientError, ExecutionError}; use std::fmt::Debug; diff --git a/packages/rs-sdk/src/mock/provider.rs b/packages/rs-sdk/src/mock/provider.rs index 16225dfc082..88327ff5564 100644 --- a/packages/rs-sdk/src/mock/provider.rs +++ b/packages/rs-sdk/src/mock/provider.rs @@ -5,11 +5,10 @@ use crate::platform::Fetch; use crate::sync::block_on; use crate::{Error, Sdk}; use arc_swap::ArcSwapAny; +use dash_context_provider::{ContextProvider, ContextProviderError}; use dpp::data_contract::TokenConfiguration; use dpp::prelude::{CoreBlockHeight, DataContract, Identifier}; use dpp::version::PlatformVersion; -use drive_proof_verifier::error::ContextProviderError; -use drive_proof_verifier::ContextProvider; use std::hash::Hash; use std::num::NonZeroUsize; use std::sync::Arc; diff --git a/packages/rs-sdk/src/mock/sdk.rs b/packages/rs-sdk/src/mock/sdk.rs index bcb2372220c..d3d336fcce2 100644 --- a/packages/rs-sdk/src/mock/sdk.rs +++ b/packages/rs-sdk/src/mock/sdk.rs @@ -16,9 +16,10 @@ use dapi_grpc::{ mock::Mockable, platform::v0::{self as proto}, }; +use dash_context_provider::{ContextProvider, ContextProviderError}; use dpp::dashcore::Network; use dpp::version::PlatformVersion; -use drive_proof_verifier::{error::ContextProviderError, ContextProvider, FromProof}; +use drive_proof_verifier::FromProof; use rs_dapi_client::mock::MockError; use rs_dapi_client::{ mock::{Key, MockDapiClient}, diff --git a/packages/rs-sdk/src/platform.rs b/packages/rs-sdk/src/platform.rs index 233242abd09..782c6b7d602 100644 --- a/packages/rs-sdk/src/platform.rs +++ b/packages/rs-sdk/src/platform.rs @@ -21,6 +21,9 @@ pub mod group_actions; pub mod tokens; pub use dapi_grpc::platform::v0 as proto; +pub use dash_context_provider::ContextProvider; +#[cfg(feature = "mocks")] +pub use dash_context_provider::MockContextProvider; pub use documents::document_query::DocumentQuery; pub use dpp::{ self as dpp, @@ -28,9 +31,6 @@ pub use dpp::{ prelude::{DataContract, Identifier, Identity, IdentityPublicKey, Revision}, }; pub use drive::query::DriveDocumentQuery; -pub use drive_proof_verifier::ContextProvider; -#[cfg(feature = "mocks")] -pub use drive_proof_verifier::MockContextProvider; pub use rs_dapi_client as dapi; pub use { fetch::Fetch, diff --git a/packages/rs-sdk/src/platform/delegate.rs b/packages/rs-sdk/src/platform/delegate.rs index 63f250e59e2..67dbddb8c68 100644 --- a/packages/rs-sdk/src/platform/delegate.rs +++ b/packages/rs-sdk/src/platform/delegate.rs @@ -83,7 +83,7 @@ macro_rules! delegate_from_proof_variant { response: O, network: dpp::dashcore::Network, version: &dpp::version::PlatformVersion, - provider: &'a dyn drive_proof_verifier::ContextProvider, + provider: &'a dyn dash_context_provider::ContextProvider, ) -> Result<(Option, ResponseMetadata, dapi_grpc::platform::v0::Proof), drive_proof_verifier::Error> where Self: Sized + 'a, diff --git a/packages/rs-sdk/src/platform/documents/document_query.rs b/packages/rs-sdk/src/platform/documents/document_query.rs index 491d0cdf3b6..49b0e722735 100644 --- a/packages/rs-sdk/src/platform/documents/document_query.rs +++ b/packages/rs-sdk/src/platform/documents/document_query.rs @@ -10,6 +10,7 @@ use dapi_grpc::platform::v0::{ get_documents_request::{get_documents_request_v0::Start, GetDocumentsRequestV0}, GetDocumentsRequest, Proof, ResponseMetadata, }; +use dash_context_provider::ContextProvider; use dpp::dashcore::Network; use dpp::version::PlatformVersion; use dpp::{ @@ -22,7 +23,7 @@ use dpp::{ InvalidVectorSizeError, ProtocolError, }; use drive::query::{DriveDocumentQuery, InternalClauses, OrderClause, WhereClause, WhereOperator}; -use drive_proof_verifier::{types::Documents, ContextProvider, FromProof}; +use drive_proof_verifier::{types::Documents, FromProof}; use rs_dapi_client::transport::{ AppliedRequestSettings, BoxFuture, TransportError, TransportRequest, }; diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index 54610845000..a70786d87b3 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -9,10 +9,10 @@ use dapi_grpc::platform::v0::{ wait_for_state_transition_result_response, Proof, WaitForStateTransitionResultResponse, }; use dapi_grpc::platform::VersionedGrpcResponse; +use dash_context_provider::ContextProviderError; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; use drive::drive::Drive; -use drive_proof_verifier::error::ContextProviderError; use drive_proof_verifier::DataContractProvider; use rs_dapi_client::{DapiRequest, ExecutionError, InnerInto, IntoInner, RequestSettings}; use rs_dapi_client::{ExecutionResponse, WrapToExecutionResult}; diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 849e62d8e04..790db1f70a0 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -12,6 +12,9 @@ use dapi_grpc::mock::Mockable; use dapi_grpc::platform::v0::{Proof, ResponseMetadata}; #[cfg(not(target_arch = "wasm32"))] use dapi_grpc::tonic::transport::Certificate; +use dash_context_provider::ContextProvider; +#[cfg(feature = "mocks")] +use dash_context_provider::MockContextProvider; use dpp::bincode; use dpp::bincode::error::DecodeError; use dpp::dashcore::Network; @@ -20,9 +23,7 @@ use dpp::prelude::IdentityNonce; use dpp::version::{PlatformVersion, PlatformVersionCurrentVersion}; use drive::grovedb::operations::proof::GroveDBProof; use drive_proof_verifier::types::{IdentityContractNonceFetcher, IdentityNonceFetcher}; -#[cfg(feature = "mocks")] -use drive_proof_verifier::MockContextProvider; -use drive_proof_verifier::{ContextProvider, FromProof}; +use drive_proof_verifier::FromProof; pub use http::Uri; #[cfg(feature = "mocks")] use rs_dapi_client::mock::MockDapiClient; diff --git a/packages/rs-sdk/src/sync.rs b/packages/rs-sdk/src/sync.rs index 14a74acadd8..b494115e45c 100644 --- a/packages/rs-sdk/src/sync.rs +++ b/packages/rs-sdk/src/sync.rs @@ -5,7 +5,7 @@ //! using a channel. use arc_swap::ArcSwap; -use drive_proof_verifier::error::ContextProviderError; +use dash_context_provider::ContextProviderError; use rs_dapi_client::{ update_address_ban_status, AddressList, CanRetry, ExecutionResult, RequestSettings, }; diff --git a/packages/wasm-drive-verify/src/lib.rs b/packages/wasm-drive-verify/src/lib.rs index 7667da7aee5..c68e22da416 100644 --- a/packages/wasm-drive-verify/src/lib.rs +++ b/packages/wasm-drive-verify/src/lib.rs @@ -34,11 +34,13 @@ //! All identifiers (identity IDs, contract IDs, document IDs, etc.) are returned as base58-encoded //! strings for consistency and compatibility with the Dash ecosystem. - // Core utilities module (always available) mod utils; pub use utils::serialization::*; +// Native Rust API (for use by other Rust/WASM projects) +pub mod native; + // Conditional compilation for modules #[cfg(any(feature = "identity", feature = "full"))] mod identity; diff --git a/packages/wasm-drive-verify/src/native.rs b/packages/wasm-drive-verify/src/native.rs new file mode 100644 index 00000000000..149a5abc73f --- /dev/null +++ b/packages/wasm-drive-verify/src/native.rs @@ -0,0 +1,54 @@ +//! Native Rust API for proof verification +//! +//! This module provides Rust-native functions for proof verification, +//! allowing other Rust/WASM projects to use wasm-drive-verify as a library. + +use dpp::data_contract::DataContract; +use dpp::document::Document; +use dpp::identity::Identity; +use dpp::version::PlatformVersion; +use drive::drive::Drive; +use drive::query::DriveDocumentQuery; + +/// Verify a full identity by identity ID +pub fn verify_full_identity_by_identity_id( + proof: &[u8], + is_proof_subset: bool, + identity_id: [u8; 32], + platform_version: &PlatformVersion, +) -> Result<([u8; 32], Option), drive::error::Error> { + Drive::verify_full_identity_by_identity_id( + proof, + is_proof_subset, + identity_id, + platform_version, + ) +} + +/// Verify a data contract by contract ID +pub fn verify_contract( + proof: &[u8], + contract_known_keeps_history: Option, + is_proof_subset: bool, + in_multiple_contract_proof_form: bool, + contract_id: [u8; 32], + platform_version: &PlatformVersion, +) -> Result<([u8; 32], Option), drive::error::Error> { + Drive::verify_contract( + proof, + contract_known_keeps_history, + is_proof_subset, + in_multiple_contract_proof_form, + contract_id, + platform_version, + ) +} + +/// Verify documents using a query +pub fn verify_documents_with_query( + proof: &[u8], + query: &DriveDocumentQuery, + platform_version: &PlatformVersion, +) -> Result<([u8; 32], Vec), drive::error::Error> { + query.verify_proof(proof, platform_version) +} diff --git a/packages/wasm-drive-verify/src/utils/getters.rs b/packages/wasm-drive-verify/src/utils/getters.rs index a1068114aae..6e0abcf46b7 100644 --- a/packages/wasm-drive-verify/src/utils/getters.rs +++ b/packages/wasm-drive-verify/src/utils/getters.rs @@ -16,4 +16,3 @@ impl VecU8ToUint8Array for [u8] { js_sys::Uint8Array::from(self) } } - diff --git a/packages/wasm-drive-verify/src/utils/platform_version.rs b/packages/wasm-drive-verify/src/utils/platform_version.rs index 1b15fdb7ebb..75d7206bf26 100644 --- a/packages/wasm-drive-verify/src/utils/platform_version.rs +++ b/packages/wasm-drive-verify/src/utils/platform_version.rs @@ -45,7 +45,7 @@ pub fn get_platform_version_with_validation( }) } -#[cfg(test)] +#[cfg(all(test, target_arch = "wasm32"))] mod tests { use super::*; diff --git a/packages/wasm-drive-verify/tests/contract_tests.rs b/packages/wasm-drive-verify/tests/contract_tests.rs index d8c3ee48370..688ac0cd2fd 100644 --- a/packages/wasm-drive-verify/tests/contract_tests.rs +++ b/packages/wasm-drive-verify/tests/contract_tests.rs @@ -16,7 +16,14 @@ fn test_verify_contract_invalid_id_length() { let invalid_contract_id = Uint8Array::from(&[0u8; 31][..]); // One byte short let platform_version = test_platform_version(); - let result = verify_contract(&proof, None, false, false, &invalid_contract_id, platform_version); + let result = verify_contract( + &proof, + None, + false, + false, + &invalid_contract_id, + platform_version, + ); assert_error_contains( &result.map(|_| ()), "Invalid contract_id length. Expected 32 bytes", @@ -30,8 +37,7 @@ fn test_verify_contract_history_invalid_parameters() { let platform_version = test_platform_version(); // Test with start_at_date of 0 - let result = - verify_contract_history(&proof, &contract_id, 0, None, None, platform_version); + let result = verify_contract_history(&proof, &contract_id, 0, None, None, platform_version); // Should not panic, actual verification will fail due to mock proof assert!(result.is_err()); } @@ -46,9 +52,9 @@ fn test_verify_contract_history_large_limit() { let result = verify_contract_history( &proof, &contract_id, - 0, // start_at_date + 0, // start_at_date Some(50000), // large limit within u16 range - None, // offset + None, // offset platform_version, ); // Should not panic, actual verification will fail due to mock proof diff --git a/packages/wasm-drive-verify/tests/document_tests.rs b/packages/wasm-drive-verify/tests/document_tests.rs index 6d03221d94d..a5176845cc8 100644 --- a/packages/wasm-drive-verify/tests/document_tests.rs +++ b/packages/wasm-drive-verify/tests/document_tests.rs @@ -4,8 +4,8 @@ use js_sys::{Object, Uint8Array}; use wasm_bindgen::JsValue; use wasm_bindgen_test::*; use wasm_drive_verify::document_verification::verify_document_proof; -use wasm_drive_verify::document_verification::SingleDocumentDriveQueryWasm; use wasm_drive_verify::document_verification::verify_start_at_document_in_proof; +use wasm_drive_verify::document_verification::SingleDocumentDriveQueryWasm; mod common; use common::*; @@ -24,7 +24,7 @@ fn test_verify_proof_invalid_contract_id() { let contract_js = JsValue::from(Uint8Array::from(&mock_identifier()[..])); let where_clauses = JsValue::from(&query); let order_by = JsValue::NULL; - + let result = verify_document_proof( &proof, &contract_js, @@ -56,7 +56,7 @@ fn test_verify_proof_empty_document_type() { let contract_js = JsValue::from(Uint8Array::from(&mock_identifier()[..])); let where_clauses = JsValue::from(&query); let order_by = JsValue::NULL; - + let result = verify_document_proof( &proof, &contract_js, @@ -88,9 +88,9 @@ fn test_verify_single_document_invalid_document_id() { false, // document_type_keeps_history invalid_document_id, None, // block_time_ms - 0, // contested_status (NotContested) + 0, // contested_status (NotContested) ); - + assert!(query_result.is_err()); assert_error_contains( &query_result.map(|_| ()), @@ -122,7 +122,7 @@ fn test_verify_start_at_document_bounds_check() { let contract_js = JsValue::from(Uint8Array::from(&mock_identifier()[..])); let order_by = JsValue::NULL; let document_id = Uint8Array::from(&mock_identifier()[..]); - + // Should handle large nested structures gracefully let result = verify_start_at_document_in_proof( &proof, diff --git a/packages/wasm-drive-verify/tests/fuzz_tests.rs b/packages/wasm-drive-verify/tests/fuzz_tests.rs index 9fff8f31322..84a55917928 100644 --- a/packages/wasm-drive-verify/tests/fuzz_tests.rs +++ b/packages/wasm-drive-verify/tests/fuzz_tests.rs @@ -88,9 +88,21 @@ fn fuzz_document_query_with_nested_structures() { let contract_js = JsValue::from(contract_id.clone()); let where_clauses = JsValue::from(&query); let order_by = JsValue::NULL; - + // Should handle without panic (may error due to bounds) - let _ = verify_document_proof(&proof, &contract_js, "test_doc", &where_clauses, &order_by, None, None, None, false, None, 1); + let _ = verify_document_proof( + &proof, + &contract_js, + "test_doc", + &where_clauses, + &order_by, + None, + None, + None, + false, + None, + 1, + ); } } @@ -176,8 +188,20 @@ fn fuzz_unicode_and_special_characters() { let contract_js = JsValue::from(contract_id.clone()); let where_clauses = JsValue::from(&query); let order_by = JsValue::NULL; - + // Should handle special characters without panic - let _ = verify_document_proof(&proof, &contract_js, doc_type, &where_clauses, &order_by, None, None, None, false, None, 1); + let _ = verify_document_proof( + &proof, + &contract_js, + doc_type, + &where_clauses, + &order_by, + None, + None, + None, + false, + None, + 1, + ); } } diff --git a/packages/wasm-drive-verify/tests/identity_tests.rs b/packages/wasm-drive-verify/tests/identity_tests.rs index 10c8f09436c..41c4099a7e5 100644 --- a/packages/wasm-drive-verify/tests/identity_tests.rs +++ b/packages/wasm-drive-verify/tests/identity_tests.rs @@ -52,7 +52,8 @@ fn test_verify_identity_balance_invalid_id() { let invalid_id = Uint8Array::from(&[0u8; 31][..]); // One byte short let platform_version = test_platform_version(); - let result = verify_identity_balance_for_identity_id(&proof, &invalid_id, false, platform_version); + let result = + verify_identity_balance_for_identity_id(&proof, &invalid_id, false, platform_version); assert_error_contains( &result.map(|_| ()), "Invalid identity_id length. Expected 32 bytes", @@ -97,8 +98,7 @@ fn test_verify_identity_nonce_invalid_identity_id() { let invalid_identity_id = Uint8Array::from(&[0u8; 16][..]); // Too short let platform_version = test_platform_version(); - let result = - verify_identity_nonce(&proof, &invalid_identity_id, false, platform_version); + let result = verify_identity_nonce(&proof, &invalid_identity_id, false, platform_version); assert_error_contains( &result.map(|_| ()), "Invalid identity_id length. Expected 32 bytes", diff --git a/packages/wasm-drive-verify/tests/token_tests.rs b/packages/wasm-drive-verify/tests/token_tests.rs index 0148415e56f..303f432325e 100644 --- a/packages/wasm-drive-verify/tests/token_tests.rs +++ b/packages/wasm-drive-verify/tests/token_tests.rs @@ -4,8 +4,8 @@ use js_sys::{Array, Uint8Array}; use wasm_bindgen_test::*; use wasm_drive_verify::token_verification::verify_token_balance_for_identity_id::verify_token_balance_for_identity_id; use wasm_drive_verify::token_verification::verify_token_balances_for_identity_ids::verify_token_balances_for_identity_ids_vec; -use wasm_drive_verify::token_verification::verify_token_statuses::verify_token_statuses_vec; use wasm_drive_verify::token_verification::verify_token_direct_selling_prices::verify_token_direct_selling_prices_vec; +use wasm_drive_verify::token_verification::verify_token_statuses::verify_token_statuses_vec; mod common; use common::*; @@ -96,6 +96,7 @@ fn test_verify_token_direct_selling_prices_mixed_valid_invalid() { let platform_version = test_platform_version(); - let result = verify_token_direct_selling_prices_vec(&proof, &contract_ids, false, platform_version); + let result = + verify_token_direct_selling_prices_vec(&proof, &contract_ids, false, platform_version); assert_error_contains(&result.map(|_| ()), "Invalid contract_id at index 1"); }