diff --git a/.gitignore b/.gitignore index cd8b6c88..d1deeeb1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.DS_Store +.vscode/ + # Generated by Cargo # will have compiled files and executables /target/ @@ -11,4 +14,4 @@ # Generated by Intellij Idea /.idea/ *.iml -/DashSharedCore \ No newline at end of file +/DashSharedCore diff --git a/Cargo.lock b/Cargo.lock index 47146aaa..aeddb8ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -77,23 +77,23 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -146,7 +146,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.75", + "syn 2.0.87", "which", ] @@ -219,7 +219,7 @@ dependencies = [ "arrayvec 0.7.6", "cc", "cfg-if 1.0.0", - "constant_time_eq 0.3.0", + "constant_time_eq 0.3.1", ] [[package]] @@ -278,9 +278,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cbindgen" @@ -322,9 +322,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.13" +version = "1.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70" dependencies = [ "shlex", ] @@ -424,9 +424,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" @@ -455,13 +455,28 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-common" version = "0.1.6" @@ -506,7 +521,18 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", +] + +[[package]] +name = "dash-spv-coinjoin" +version = "0.1.0" +dependencies = [ + "byte", + "cbindgen 0.24.5", + "dash-spv-masternode-processor", + "logging", + "tracing", ] [[package]] @@ -530,23 +556,26 @@ dependencies = [ "dirs-next", "ed25519-dalek", "libc", - "log", + "logging", "reqwest", "rs-x11-hash", "secp256k1", "serde", "serde_json", - "simplelog", + "tracing", "zeroize", ] [[package]] name = "dash_spv_apple_bindings" -version = "0.4.18" +version = "0.4.19" dependencies = [ "cbindgen 0.24.5", + "dash-spv-coinjoin", "dash-spv-masternode-processor", + "logging", "rs-merk-verify-c-binding", + "tracing", ] [[package]] @@ -588,6 +617,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -598,6 +636,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -609,6 +658,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "ed" version = "0.2.2" @@ -662,9 +722,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if 1.0.0", ] @@ -704,14 +764,14 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] name = "fastrand" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fiat-crypto" @@ -751,42 +811,42 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", @@ -820,9 +880,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -842,7 +902,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -857,9 +917,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "heck" @@ -939,9 +999,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -951,9 +1011,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -986,14 +1046,143 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1008,12 +1197,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] @@ -1027,9 +1216,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itoa" @@ -1039,9 +1228,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1060,9 +1249,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "libloading" @@ -1090,6 +1279,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -1106,6 +1301,26 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "logging" +version = "0.1.0" +dependencies = [ + "dirs", + "dirs-next", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1136,11 +1351,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1183,40 +1398,41 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] [[package]] -name = "num_threads" -version = "0.1.7" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", @@ -1235,7 +1451,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -1246,9 +1462,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -1262,6 +1478,12 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1309,9 +1531,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1331,9 +1553,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "powerfmt" @@ -1352,28 +1574,28 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1413,15 +1635,17 @@ name = "reachability" version = "0.1.0" dependencies = [ "libc", + "logging", "system-configuration", "tokio", + "tracing", ] [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -1439,32 +1663,47 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -1555,18 +1794,18 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" dependencies = [ "bitflags 2.6.0", "errno", @@ -1601,11 +1840,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1660,9 +1899,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -1676,29 +1915,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -1729,6 +1968,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1753,17 +2001,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "simplelog" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" -dependencies = [ - "log", - "termcolor", - "time", -] - [[package]] name = "slab" version = "0.4.9" @@ -1799,6 +2036,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.8.0" @@ -1830,9 +2073,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -1857,6 +2100,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -1880,9 +2134,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if 1.0.0", "fastrand", @@ -1917,35 +2171,43 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", @@ -1960,14 +2222,24 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -1985,9 +2257,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -2009,7 +2281,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", ] [[package]] @@ -2024,9 +2296,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2057,9 +2329,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -2067,6 +2363,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -2081,56 +2407,68 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2172,9 +2510,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if 1.0.0", "once_cell", @@ -2183,24 +2521,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2210,9 +2548,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2220,28 +2558,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2448,6 +2786,42 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2466,7 +2840,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", ] [[package]] @@ -2474,3 +2869,25 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] diff --git a/Cargo.toml b/Cargo.toml index cc945f3a..15d21312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "dash-spv-apple-bindings", "dash-spv-example-app", "dash-spv-masternode-processor", + "logging", "reachability", ] diff --git a/dash-spv-apple-bindings/Cargo.toml b/dash-spv-apple-bindings/Cargo.toml index 83402660..589f1a1d 100644 --- a/dash-spv-apple-bindings/Cargo.toml +++ b/dash-spv-apple-bindings/Cargo.toml @@ -8,10 +8,16 @@ build = "build.rs" publish = true [dependencies] +logging = { path = "../logging" } +tracing = "0.1.40" [dependencies.dash-spv-masternode-processor] path = "../dash-spv-masternode-processor" -features = ["default", "use_serde"] +features = ["default", "generate-dashj-tests", "use_serde"] + +[dependencies.dash-spv-coinjoin] +path = "../dash-spv-coinjoin" +features = [] [dependencies.rs-merk-verify-c-binding] git = "https://github.com/dashpay/rs-merk-verify-c-binding" diff --git a/dash-spv-apple-bindings/build.rs b/dash-spv-apple-bindings/build.rs index d9b93aad..e5d8526e 100644 --- a/dash-spv-apple-bindings/build.rs +++ b/dash-spv-apple-bindings/build.rs @@ -7,6 +7,7 @@ fn main() { // Here we write down crate names (!) where we want to retrieve C-bindings let includes = vec![ "dash-spv-masternode-processor".to_string(), + "dash-spv-coinjoin".to_string(), "rs-merk-verify-c-binding".to_string() ]; let config = cbindgen::Config { diff --git a/dash-spv-apple-bindings/src/coinjoin.rs b/dash-spv-apple-bindings/src/coinjoin.rs new file mode 100644 index 00000000..7e6d8ad0 --- /dev/null +++ b/dash-spv-apple-bindings/src/coinjoin.rs @@ -0,0 +1,528 @@ +use core::slice; +use std::cell::RefCell; +use std::ffi::{c_void, CStr}; +use std::io::Cursor; +use std::os::raw::c_char; +use std::rc::Rc; + +use dash_spv_coinjoin::coinjoin_client_manager::CoinJoinClientManager; +use dash_spv_coinjoin::coinjoin_client_queue_manager::CoinJoinClientQueueManager; + +use dash_spv_coinjoin::coinjoin::CoinJoin; +use dash_spv_coinjoin::ffi::callbacks::{AddPendingMasternode, AvailableCoins, CommitTransaction, DestroyCoinJoinKeys, DestroyGatheredOutputs, DestroyInputValue, DestroyMasternode, DestroyMasternodeList, DestroySelectedCoins, DestroyWalletTransaction, DisconnectMasternode, FreshCoinJoinAddress, GetCoinJoinKeys, GetInputValueByPrevoutHash, GetMasternodeList, GetWalletTransaction, HasChainLock, InputsWithAmount, IsBlockchainSynced, IsMasternodeOrDisconnectRequested, IsMineInput, IsWaitingForNewBlock, MasternodeByHash, MixingLivecycleListener, SelectCoinsGroupedByAddresses, SendMessage, SessionLifecycleListener, SignTransaction, StartManagerAsync, UpdateSuccessBlock, ValidMasternodeCount}; +use dash_spv_coinjoin::ffi::coinjoin_denominations::CoinJoinDenominations; +use dash_spv_coinjoin::ffi::coinjoin_keys::CoinJoinKeys; +use dash_spv_coinjoin::ffi::coinjoin_session_statuses::CoinJoinSessionStatuses; +use dash_spv_coinjoin::masternode_meta_data_manager::MasternodeMetadataManager; +use dash_spv_coinjoin::messages; +use dash_spv_coinjoin::messages::coinjoin_broadcast_tx::CoinJoinBroadcastTx; +use dash_spv_coinjoin::messages::coinjoin_message::CoinJoinMessage; +use dash_spv_coinjoin::models::coinjoin_tx_type::CoinJoinTransactionType; +use dash_spv_coinjoin::models::tx_outpoint::TxOutPoint; +use dash_spv_coinjoin::models::{Balance, CoinJoinClientOptions}; +use dash_spv_coinjoin::wallet_ex::WalletEx; +use dash_spv_masternode_processor::common::{self, SocketAddress}; +use dash_spv_masternode_processor::consensus::Decodable; +use dash_spv_masternode_processor::crypto::byte_util::ConstDecodable; +use dash_spv_masternode_processor::crypto::{UInt128, UInt256}; +use dash_spv_masternode_processor::ffi::boxer::boxed; +use dash_spv_masternode_processor::ffi::from::FromFFI; +use dash_spv_masternode_processor::ffi::unboxer::unbox_any; +use dash_spv_masternode_processor::ffi::ByteArray; +use dash_spv_masternode_processor::types; +use logging::*; +use tracing::*; + +#[no_mangle] +pub unsafe extern "C" fn register_client_manager( + context: *const c_void, + options_ptr: *mut CoinJoinClientOptions, + get_masternode_list: GetMasternodeList, + destroy_mn_list: DestroyMasternodeList, + get_input_value_by_prevout_hash: GetInputValueByPrevoutHash, + has_chain_lock: HasChainLock, + destroy_input_value: DestroyInputValue, + update_success_block: UpdateSuccessBlock, + is_waiting_for_new_block: IsWaitingForNewBlock, + get_wallet_transaction: GetWalletTransaction, + sign_transaction: SignTransaction, + destroy_transaction: DestroyWalletTransaction, + is_mine: IsMineInput, + commit_transaction: CommitTransaction, + is_synced: IsBlockchainSynced, + fresh_coinjoin_key: FreshCoinJoinAddress, + count_inputs_with_amount: InputsWithAmount, + available_coins: AvailableCoins, + destroy_gathered_outputs: DestroyGatheredOutputs, + selected_coins: SelectCoinsGroupedByAddresses, + destroy_selected_coins: DestroySelectedCoins, + is_masternode_or_disconnect_requested: IsMasternodeOrDisconnectRequested, + disconnect_masternode: DisconnectMasternode, + send_message: SendMessage, + add_pending_masternode: AddPendingMasternode, + start_manager_async: StartManagerAsync, + session_lifecycle_listener: SessionLifecycleListener, + mixing_lifecycle_listener: MixingLivecycleListener, + get_coinjoin_keys: GetCoinJoinKeys, + destroy_coinjoin_keys: DestroyCoinJoinKeys +) -> *mut CoinJoinClientManager { + logging::init_logging(); + let coinjoin = CoinJoin::new( + get_input_value_by_prevout_hash, + has_chain_lock, + destroy_input_value, + context + ); + + let options = Rc::new(RefCell::new(std::ptr::read(options_ptr))); + + let wallet_ex: WalletEx = WalletEx::new( + context, + options.clone(), + get_wallet_transaction, + sign_transaction, + destroy_transaction, + is_mine, + available_coins, + destroy_gathered_outputs, + selected_coins, + destroy_selected_coins, + count_inputs_with_amount, + fresh_coinjoin_key, + commit_transaction, + is_synced, + is_masternode_or_disconnect_requested, + disconnect_masternode, + send_message, + add_pending_masternode, + start_manager_async, + get_coinjoin_keys, + destroy_coinjoin_keys + ); + + let client_manager = CoinJoinClientManager::new( + Rc::new(RefCell::new(wallet_ex)), + Rc::new(RefCell::new(coinjoin)), + options, + get_masternode_list, + destroy_mn_list, + update_success_block, + is_waiting_for_new_block, + session_lifecycle_listener, + mixing_lifecycle_listener, + context + ); + log_info!(target: "CoinJoin", "register_client_manager"); + boxed(client_manager) +} + +#[no_mangle] +pub unsafe extern "C" fn add_client_queue_manager( + client_manager_ptr: *mut CoinJoinClientManager, + masternode_by_hash: MasternodeByHash, + destroy_masternode: DestroyMasternode, + valid_mns_count: ValidMasternodeCount, + context: *const c_void +) { + let client_queue_manager = CoinJoinClientQueueManager::new( + client_manager_ptr, + MasternodeMetadataManager::new(), + masternode_by_hash, + destroy_masternode, + valid_mns_count, + context + ); + + (*client_manager_ptr).set_client_queue_manager(Rc::new(RefCell::new(client_queue_manager))); + log_info!(target: "CoinJoin", "add_client_queue_manager"); +} + +#[no_mangle] +pub unsafe extern "C" fn start_mixing( + client_manager: *mut CoinJoinClientManager +) -> bool { + return (*client_manager).start_mixing(); +} + +#[no_mangle] +pub unsafe extern "C" fn set_stop_on_nothing_to_do( + client_manager: *mut CoinJoinClientManager, + stop_on_nothing_to_do: bool +) { + (*client_manager).set_stop_on_nothing_to_do(stop_on_nothing_to_do); +} + +#[no_mangle] +pub unsafe extern "C" fn do_maintenance( + client_manager: *mut CoinJoinClientManager, + balance_info: Balance +) { + (*client_manager).do_maintenance(balance_info); +} + +#[no_mangle] +pub unsafe extern "C" fn do_automatic_denominating( + client_manager: *mut CoinJoinClientManager, + balance_info: Balance, + dry_run: bool +) -> bool { + return (*client_manager).do_automatic_denominating(balance_info, dry_run); +} + +#[no_mangle] +pub unsafe extern "C" fn finish_automatic_denominating( + manager: *mut CoinJoinClientManager, + client_session_id: *mut [u8; 32] +) -> bool { + println!("[RUST] CoinJoin: session.finish_automatic_denominating"); + return (*manager).finish_automatic_denominating(UInt256(*(client_session_id))); +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_transaction( + tx: *mut types::Transaction +) { + let unboxed = unbox_any(tx); +} + +#[no_mangle] +pub unsafe extern "C" fn unregister_client_manager(client_manager: *mut CoinJoinClientManager) { + println!("[RUST] CoinJoin 💀 unregister_client_manager"); + let unboxed = unbox_any(client_manager); +} + +#[no_mangle] +pub unsafe extern "C" fn is_denominated_amount( + amount: u64, +) -> bool { + return CoinJoin::is_denominated_amount(amount); +} + +#[no_mangle] +pub unsafe extern "C" fn is_collateral_amount( + amount: u64, +) -> bool { + return CoinJoin::is_collateral_amount(amount); +} + +#[no_mangle] +pub unsafe extern "C" fn has_collateral_inputs( + client_manager: *mut CoinJoinClientManager, + only_confirmed: bool +) -> bool { + return (*client_manager).wallet_ex.borrow_mut().has_collateral_inputs(only_confirmed); +} + +#[no_mangle] +pub unsafe extern "C" fn is_fully_mixed_with_manager( + client_manager: *mut CoinJoinClientManager, + prevout_hash: *mut [u8; 32], + index: u32, +) -> bool { + return (*client_manager).wallet_ex.borrow_mut().is_fully_mixed(TxOutPoint::new(UInt256(*(prevout_hash)), index)); +} + +#[no_mangle] +pub unsafe extern "C" fn is_fully_mixed( + wallet_ex: *mut WalletEx, + prevout_hash: *mut [u8; 32], + index: u32, +) -> bool { + return (*wallet_ex).is_fully_mixed(TxOutPoint::new(UInt256(*(prevout_hash)), index)); +} + +#[no_mangle] +pub unsafe extern "C" fn is_locked_coin( + wallet_ex: *mut WalletEx, + prevout_hash: *mut [u8; 32], + index: u32, +) -> bool { + return (*wallet_ex).locked_coins_set.contains(&TxOutPoint::new(UInt256(*(prevout_hash)), index)); +} + +#[no_mangle] +pub unsafe extern "C" fn has_locked_coins( + client_manager: *mut CoinJoinClientManager, +) -> u32 { + return (*client_manager).wallet_ex.borrow().locked_coins_set.len() as u32; +} + +#[no_mangle] +pub unsafe extern "C" fn print_locked_coins( + client_manager: *mut CoinJoinClientManager +) { + println!("[RUST] CoinJoin: print_locked_coins"); + for outpoint in (*client_manager).wallet_ex.borrow().locked_coins_set.iter() { + println!("[RUST] CoinJoin: {:?}", outpoint); + } +} + +#[no_mangle] +pub unsafe extern "C" fn is_locked_coin_with_manager( + client_manager: *mut CoinJoinClientManager, + prevout_hash: *mut [u8; 32], + index: u32, +) -> bool { + return (*client_manager).wallet_ex.borrow().locked_coins_set.contains(&TxOutPoint::new(UInt256(*(prevout_hash)), index)); +} + +#[no_mangle] +pub unsafe extern "C" fn coinjoin_get_smallest_denomination() -> u64 { + return CoinJoin::get_smallest_denomination(); +} + +#[no_mangle] +pub unsafe extern "C" fn process_coinjoin_message( + client_manager: *mut CoinJoinClientManager, + peer_address: *const u8, + peer_port: u16, + message: *mut ByteArray, + message_type: *const c_char +) { + let c_str = unsafe { CStr::from_ptr(message_type) }; + let message = match c_str.to_str().unwrap() { + "dssu" => CoinJoinMessage::StatusUpdate(process_coinjoin_status_update((*message).ptr, (*message).len)), + "dsf" => CoinJoinMessage::FinalTransaction(process_coinjoin_final_transaction((*message).ptr, (*message).len)), + "dsc" => CoinJoinMessage::Complete(process_coinjoin_complete_message((*message).ptr, (*message).len)), + "dstx" => CoinJoinMessage::BroadcastTx(process_coinjoin_broadcast_tx((*message).ptr, (*message).len)), + _ => panic!("CoinJoin: Unsupported message type") + }; + + let from_peer = SocketAddress { + ip_address: UInt128::from_const(peer_address).unwrap_or(UInt128::MIN), + port: peer_port + }; + + (*client_manager).process_message(from_peer, message); +} + +#[no_mangle] +pub unsafe extern "C" fn notify_new_best_block( + client_manager: *mut CoinJoinClientManager, + block_hash: *mut [u8; 32], + block_height: u32 +) { + let block = common::Block::new( + block_height, + UInt256(*(block_hash)) + ); + (*client_manager).update_block_tip(block); +} + +unsafe fn process_coinjoin_accept_message( + message: *const u8, + message_length: usize +) -> messages::CoinJoinAcceptMessage { + let message: &[u8] = slice::from_raw_parts(message, message_length); + let mut cursor = Cursor::new(message); + + return messages::CoinJoinAcceptMessage::consensus_decode(&mut cursor).unwrap(); +} + +unsafe fn process_coinjoin_broadcast_tx( + message: *const u8, + message_length: usize +) -> CoinJoinBroadcastTx { + let message: &[u8] = slice::from_raw_parts(message, message_length); + let mut cursor = Cursor::new(message); + + return CoinJoinBroadcastTx::consensus_decode(&mut cursor).unwrap(); +} + +unsafe fn process_coinjoin_complete_message( + message: *const u8, + message_length: usize +) -> messages::CoinJoinCompleteMessage { + let message: &[u8] = slice::from_raw_parts(message, message_length); + let mut cursor = Cursor::new(message); + + return messages::CoinJoinCompleteMessage::consensus_decode(&mut cursor).unwrap(); +} + +unsafe fn process_coinjoin_entry( + message: *const u8, + message_length: usize +) -> messages::CoinJoinEntry { + let message: &[u8] = slice::from_raw_parts(message, message_length); + let mut cursor = Cursor::new(message); + return messages::CoinJoinEntry::consensus_decode(&mut cursor).unwrap(); +} + +unsafe fn process_coinjoin_final_transaction( + message: *const u8, + message_length: usize +) -> messages::CoinJoinFinalTransaction { + let message: &[u8] = slice::from_raw_parts(message, message_length); + let mut cursor = Cursor::new(message); + + return messages::CoinJoinFinalTransaction::consensus_decode(&mut cursor).unwrap(); +} + +unsafe fn process_coinjoin_queue_message( + message: *const u8, + message_length: usize +) -> messages::CoinJoinQueueMessage { + let message: &[u8] = slice::from_raw_parts(message, message_length); + let mut cursor = Cursor::new(message); + + return messages::CoinJoinQueueMessage::consensus_decode(&mut cursor).unwrap(); +} + +unsafe fn process_coinjoin_status_update( + message: *const u8, + message_length: usize +) -> messages::CoinJoinStatusUpdate { + let message: &[u8] = slice::from_raw_parts(message, message_length); + let mut cursor = Cursor::new(message); + + return messages::CoinJoinStatusUpdate::consensus_decode(&mut cursor).unwrap(); +} + +#[no_mangle] +pub unsafe extern "C" fn process_ds_queue( + client_manager: *mut CoinJoinClientManager, + peer_address: *const u8, + peer_port: u16, + message: *mut ByteArray +) { + let from_peer = SocketAddress { + ip_address: UInt128::from_const(peer_address).unwrap_or(UInt128::MIN), + port: peer_port + }; + let message = process_coinjoin_queue_message((*message).ptr, (*message).len); + (*client_manager).queue_queue_manager.as_ref().unwrap().borrow_mut().process_ds_queue(from_peer, message); +} + +#[no_mangle] +pub unsafe extern "C" fn is_mixing_fee_tx( + client_manager: *mut CoinJoinClientManager, + tx_id: *mut [u8; 32] +) -> bool { + return (*client_manager).is_mixing_fee_tx(UInt256(*(tx_id))); +} + +#[no_mangle] +pub unsafe extern "C" fn get_coinjoin_tx_type( + tx: *mut types::Transaction, + input_values: *const u64, + input_values_len: usize +) -> CoinJoinTransactionType { + let inputs: Vec = (0..input_values_len).map(|i| *(input_values.add(i))).collect(); + return CoinJoinTransactionType::from_tx(&(*tx).decode(), &inputs) +} + +#[no_mangle] +pub unsafe extern "C" fn process_used_scripts( + client_manager: *mut CoinJoinClientManager, + coinjoin_keys: *mut CoinJoinKeys +) { + let mut scripts: Vec> = Vec::new(); + + for i in 0..(*coinjoin_keys).item_count { + let byte_array = *(*coinjoin_keys).items.add(i); + let script = slice::from_raw_parts((*byte_array).ptr, (*byte_array).len); + scripts.push(script.to_vec()); + } + + (*client_manager).process_used_scripts(&scripts) +} + +#[no_mangle] +pub unsafe extern "C" fn refresh_unused_keys( + client_manager: *mut CoinJoinClientManager +) { + (*client_manager).refresh_unused_keys(); +} + +#[no_mangle] +pub unsafe extern "C" fn get_anonymizable_balance( + client_manager: *mut CoinJoinClientManager, + skip_denominated: bool, + skip_unconfirmed: bool +) -> u64 { + return (*client_manager).wallet_ex.borrow_mut().get_anonymizable_balance(skip_denominated, skip_unconfirmed); +} + +#[no_mangle] +pub unsafe extern "C" fn change_coinjoin_options( + client_manager: *mut CoinJoinClientManager, + new_options_ptr: *mut CoinJoinClientOptions +) { + (*client_manager).change_options(std::ptr::read(new_options_ptr)); +} + +#[no_mangle] +pub unsafe extern "C" fn get_standard_denominations() -> *mut CoinJoinDenominations { + let denominations = CoinJoin::get_standard_denominations(); + return boxed(CoinJoinDenominations { + denoms: denominations.as_ptr(), + length: denominations.len(), + }) +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_coinjoin_denomination( + denomination: *mut CoinJoinDenominations +) { + let _ = unbox_any(denomination); +} + +#[no_mangle] +pub unsafe extern "C" fn amount_to_denomination( + input_amount: u64 +) -> u32 { + return CoinJoin::amount_to_denomination(input_amount); +} + +#[no_mangle] +pub unsafe extern "C" fn get_real_outpoint_coinjoin_rounds( + client_manager: *mut CoinJoinClientManager, + prevout_hash: *mut [u8; 32], + index: u32, + rounds: i32 +) -> i32 { + let outpoint = TxOutPoint::new(UInt256(*(prevout_hash)), index); + return (*client_manager).get_real_outpoint_coinjoin_rounds(outpoint, rounds); +} + +#[no_mangle] +pub unsafe extern "C" fn get_collateral_amount() -> u64 { + return CoinJoin::get_collateral_amount(); +} + +#[no_mangle] +pub unsafe extern "C" fn get_max_collateral_amount() -> u64 { + return CoinJoin::get_max_collateral_amount(); +} + +#[no_mangle] +pub unsafe extern "C" fn get_sessions_status( + client_manager: *mut CoinJoinClientManager +) -> *mut CoinJoinSessionStatuses { + let statuses = (*client_manager).get_sessions_status(); + let statuses_ptr = statuses.as_ptr(); + let length = statuses.len(); + + return boxed(CoinJoinSessionStatuses { + statuses: statuses_ptr, + length: length, + }); +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_coinjoin_session_statuses( + statuses: *mut CoinJoinSessionStatuses +) { + let _ = unbox_any(statuses); +} + +#[no_mangle] +pub unsafe extern "C" fn stop_and_reset_coinjoin( + client_manager: *mut CoinJoinClientManager +) { + if (*client_manager).is_mixing { + (*client_manager).reset_pool(); + (*client_manager).stop_mixing(); + } +} + diff --git a/dash-spv-apple-bindings/src/fermented.rs b/dash-spv-apple-bindings/src/fermented.rs new file mode 100644 index 00000000..ba7b8d52 --- /dev/null +++ b/dash-spv-apple-bindings/src/fermented.rs @@ -0,0 +1 @@ +# [allow (clippy :: let_and_return , clippy :: suspicious_else_formatting , clippy :: redundant_field_names , dead_code , redundant_semicolons , unused_braces , unused_imports , unused_unsafe , unused_variables , unused_qualifications)] pub mod types { pub mod address { pub mod addresses { # [doc = "FFI-representation of the address_with_script_pubkey"] # [doc = r" # Safety"] # [no_mangle] pub unsafe extern "C" fn ffi_address_with_script_pubkey (script : * mut crate :: fermented :: generics :: Vec_u8 , chain_type : * mut dash_spv_masternode_processor :: fermented :: types :: chain :: common :: chain_type :: ChainType ,) -> * mut std :: os :: raw :: c_char { let obj = crate :: address :: addresses :: address_with_script_pubkey (ferment_interfaces :: FFIConversion :: ffi_from (script) , ferment_interfaces :: FFIConversion :: ffi_from (chain_type) ,) ; ferment_interfaces :: FFIConversion :: ffi_to_opt (obj) } # [doc = "FFI-representation of the address_from_hash160"] # [doc = r" # Safety"] # [no_mangle] pub unsafe extern "C" fn ffi_address_from_hash160 (hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt160 , chain_type : * mut dash_spv_masternode_processor :: fermented :: types :: chain :: common :: chain_type :: ChainType ,) -> * mut std :: os :: raw :: c_char { let obj = crate :: address :: addresses :: address_from_hash160 (ferment_interfaces :: FFIConversion :: ffi_from (hash) , ferment_interfaces :: FFIConversion :: ffi_from (chain_type) ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } } } pub mod ffi { pub mod callbacks { # [doc = "FFI-representation of the GetMerkleRootCallback"] # [allow (non_camel_case_types)] pub type GetMerkleRootCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 ; # [doc = "FFI-representation of the GetBlockHashByHeightCallback"] # [allow (non_camel_case_types)] pub type GetBlockHashByHeightCallback = unsafe extern "C" fn (block_height : u32 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 ; # [doc = "FFI-representation of the ShouldProcessDiffWithRangeCallback"] # [allow (non_camel_case_types)] pub type ShouldProcessDiffWithRangeCallback = unsafe extern "C" fn (base_block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> * mut dash_spv_masternode_processor :: fermented :: types :: processing :: processing_error :: ProcessingError ; # [doc = "FFI-representation of the GetLLMQSnapshotByBlockHashCallback"] # [allow (non_camel_case_types)] pub type GetLLMQSnapshotByBlockHashCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> * mut dash_spv_masternode_processor :: fermented :: types :: models :: snapshot :: LLMQSnapshot ; # [doc = "FFI-representation of the SaveMasternodeListCallback"] # [allow (non_camel_case_types)] pub type SaveMasternodeListCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , masternode_list : * mut dash_spv_masternode_processor :: fermented :: types :: models :: masternode_list :: MasternodeList , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> bool ; # [doc = "FFI-representation of the SaveLLMQSnapshotCallback"] # [allow (non_camel_case_types)] pub type SaveLLMQSnapshotCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , snapshot : * mut dash_spv_masternode_processor :: fermented :: types :: models :: snapshot :: LLMQSnapshot , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> bool ; # [doc = "FFI-representation of the GetBlockHeightByHashCallback"] # [allow (non_camel_case_types)] pub type GetBlockHeightByHashCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> u32 ; # [doc = "FFI-representation of the AddInsightCallback"] # [allow (non_camel_case_types)] pub type AddInsightCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) ; # [doc = "FFI-representation of the DestroyMasternodeListCallback"] # [allow (non_camel_case_types)] pub type DestroyMasternodeListCallback = unsafe extern "C" fn (masternode_list : * mut dash_spv_masternode_processor :: fermented :: types :: models :: masternode_list :: MasternodeList) ; # [doc = "FFI-representation of the DestroyHashCallback"] # [allow (non_camel_case_types)] pub type DestroyHashCallback = unsafe extern "C" fn (hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256) ; # [doc = "FFI-representation of the GetMasternodeListCallback"] # [allow (non_camel_case_types)] pub type GetMasternodeListCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> * mut dash_spv_masternode_processor :: fermented :: types :: models :: masternode_list :: MasternodeList ; # [doc = "FFI-representation of the GetCLSignatureByBlockHashCallback"] # [allow (non_camel_case_types)] pub type GetCLSignatureByBlockHashCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt768 ; # [doc = "FFI-representation of the DestroyLLMQSnapshotCallback"] # [allow (non_camel_case_types)] pub type DestroyLLMQSnapshotCallback = unsafe extern "C" fn (snapshot : * mut dash_spv_masternode_processor :: fermented :: types :: models :: snapshot :: LLMQSnapshot) ; # [doc = "FFI-representation of the SaveCLSignatureCallback"] # [allow (non_camel_case_types)] pub type SaveCLSignatureCallback = unsafe extern "C" fn (block_hash : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt256 , cl_signature : * mut dash_spv_masternode_processor :: fermented :: types :: crypto :: byte_util :: UInt768 , context : * mut ferment_interfaces :: fermented :: types :: OpaqueContext_FFI) -> bool ; } } } # [allow (clippy :: let_and_return , clippy :: suspicious_else_formatting , clippy :: redundant_field_names , dead_code , redundant_semicolons , unused_braces , unused_imports , unused_unsafe , unused_variables , unused_qualifications)] pub mod generics { # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_u8 { pub count : usize , pub values : * mut u8 , } impl ferment_interfaces :: FFIConversion < Vec < u8 > > for Vec_u8 { unsafe fn ffi_from_const (ffi : * const Vec_u8) -> Vec < u8 > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < u8 >) -> * const Vec_u8 { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_u8) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_u8 { type Value = Vec < u8 > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_primitive_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: boxed_vec (obj) }) } } impl Drop for Vec_u8 { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_vec_ptr (self . values , self . count) ; } } } } \ No newline at end of file diff --git a/dash-spv-apple-bindings/src/lib.rs b/dash-spv-apple-bindings/src/lib.rs index 85fc7363..1025a28a 100644 --- a/dash-spv-apple-bindings/src/lib.rs +++ b/dash-spv-apple-bindings/src/lib.rs @@ -1,4 +1,7 @@ #![allow(dead_code)] #![allow(unused_variables)] pub extern crate dash_spv_masternode_processor; +pub extern crate dash_spv_coinjoin; pub extern crate merk; + +pub mod coinjoin; \ No newline at end of file diff --git a/dash-spv-coinjoin/Cargo.toml b/dash-spv-coinjoin/Cargo.toml new file mode 100644 index 00000000..b86d1b0f --- /dev/null +++ b/dash-spv-coinjoin/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "dash-spv-coinjoin" +version = "0.1.0" +description = "Library for processing coinjoin messages" + +edition = "2021" +build = "build.rs" +publish = false + +[features] +unstable = [] +test-helpers = [] + +[dependencies] +dash-spv-masternode-processor = { path = "../dash-spv-masternode-processor" } +logging = { path = "../logging" } +byte = "0.2" +tracing = "0.1.40" + +[build-dependencies] +cbindgen = "0.24.3" + +[lib] +name = "dash_spv_coinjoin" diff --git a/dash-spv-coinjoin/LICENSE b/dash-spv-coinjoin/LICENSE new file mode 100644 index 00000000..a449489c --- /dev/null +++ b/dash-spv-coinjoin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Dash Core Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dash-spv-coinjoin/README.md b/dash-spv-coinjoin/README.md new file mode 100644 index 00000000..ae84cd40 --- /dev/null +++ b/dash-spv-coinjoin/README.md @@ -0,0 +1,25 @@ +# dash-spv-coinjoin + +Library for processing CoinJoin messages. + +Run tests: +cargo test --package dash-spv-coinjoin --lib tests +Run c test-like functions: + +./build.sh && clang c/main.c target/universal/release/libdash_spv_coinjoin_macos.a -o test && ./test + +For fast local testing: +In 'dash-shared-core' +1) Create custom branch +2) Modify DashSharedCore.podspec so 'source' points to branch from previous step +3) Modify Cargo.toml so needed dependency points to desired branch + +In 'masternodes-coinjoin': +1) Don't forget to push the changes into the branch that 'dash-shared-core' is looking at + +In 'DashSync' when building example app: +1) In Podfile put 'DashSharedCore' pod which is 'dash-shared-core' looking at right above 'DashSync' pod import +2) Perform 'pod cache clean DashSharedCore' if neccessary +3) Run 'pod update' + + diff --git a/dash-spv-coinjoin/build.rs b/dash-spv-coinjoin/build.rs new file mode 100644 index 00000000..d99f9cad --- /dev/null +++ b/dash-spv-coinjoin/build.rs @@ -0,0 +1,19 @@ +extern crate cbindgen; + +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let mut config = cbindgen::Config::from_file("./cbindgen.toml").expect("Error config"); + let includes = vec![]; + config.language = cbindgen::Language::C; + config.parse = cbindgen::ParseConfig { + parse_deps: true, + include: Some(includes.clone()), + extra_bindings: includes, + ..Default::default() + }; + cbindgen::generate_with_config(&crate_dir, config) + .unwrap() + .write_to_file("target/dash_spv_coinjoin.h"); +} diff --git a/dash-spv-coinjoin/build.sh b/dash-spv-coinjoin/build.sh new file mode 100755 index 00000000..e1af08f2 --- /dev/null +++ b/dash-spv-coinjoin/build.sh @@ -0,0 +1,5 @@ +set -e +cargo build --verbose --release +cargo expand | sed '/#!/d' > target/expanded.rs +sed -n '/#\[prelude_import\]/,$p' target/expanded.rs > target/expanded_reduced.rs +cbindgen --config cbindgen.toml -o target/example.h target/expanded_reduced.rs \ No newline at end of file diff --git a/dash-spv-coinjoin/cbindgen.toml b/dash-spv-coinjoin/cbindgen.toml new file mode 100644 index 00000000..1badfe6f --- /dev/null +++ b/dash-spv-coinjoin/cbindgen.toml @@ -0,0 +1,16 @@ +language = "C" +autogen_warning = "/* This file is autogenerated by cbindgen. Don't modify this manually. */" +braces = "SameLine" +line_length = 80 +tab_width = 4 +documentation_style = "c" +style = "tag" +include_guard = "example_h" + +[parse] +parse_deps = true +include = [] +extra_bindings = [] + +[enum] +prefix_with_name = true diff --git a/dash-spv-coinjoin/rustfmt.toml b/dash-spv-coinjoin/rustfmt.toml new file mode 100644 index 00000000..746f841e --- /dev/null +++ b/dash-spv-coinjoin/rustfmt.toml @@ -0,0 +1,8 @@ +binop_separator = "Back" +brace_style = "PreferSameLine" +edition = "2021" +imports_indent = "Visual" +match_arm_blocks = false +match_block_trailing_comma = true +reorder_imports = true +use_field_init_shorthand = true diff --git a/dash-spv-coinjoin/src/coin_selection/compact_tally_item.rs b/dash-spv-coinjoin/src/coin_selection/compact_tally_item.rs new file mode 100644 index 00000000..8bb9e071 --- /dev/null +++ b/dash-spv-coinjoin/src/coin_selection/compact_tally_item.rs @@ -0,0 +1,19 @@ +use crate::models::tx_destination::TxDestination; +use crate::coin_selection::input_coin::InputCoin; + +#[derive(Clone, Debug)] +pub struct CompactTallyItem { + pub tx_destination: TxDestination, + pub amount: u64, + pub input_coins: Vec, +} + +impl CompactTallyItem { + pub fn new(tx_destination: TxDestination) -> Self { + CompactTallyItem { + tx_destination, + amount: 0, + input_coins: Vec::new(), + } + } +} diff --git a/dash-spv-coinjoin/src/coin_selection/input_coin.rs b/dash-spv-coinjoin/src/coin_selection/input_coin.rs new file mode 100644 index 00000000..60f60cd6 --- /dev/null +++ b/dash-spv-coinjoin/src/coin_selection/input_coin.rs @@ -0,0 +1,9 @@ +use dash_spv_masternode_processor::tx::TransactionOutput; +use crate::models::tx_outpoint::TxOutPoint; + +#[derive(Clone, Debug)] +pub struct InputCoin { + pub tx_outpoint: TxOutPoint, + pub output: TransactionOutput, + pub effective_value: u64 +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/coin_selection/mod.rs b/dash-spv-coinjoin/src/coin_selection/mod.rs new file mode 100644 index 00000000..2cbefc68 --- /dev/null +++ b/dash-spv-coinjoin/src/coin_selection/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod compact_tally_item; +pub(crate) mod input_coin; \ No newline at end of file diff --git a/dash-spv-coinjoin/src/coinjoin.rs b/dash-spv-coinjoin/src/coinjoin.rs new file mode 100644 index 00000000..a82ed4ea --- /dev/null +++ b/dash-spv-coinjoin/src/coinjoin.rs @@ -0,0 +1,353 @@ +use std::collections::HashMap; + +use dash_spv_masternode_processor::chain::common::ChainType; +use dash_spv_masternode_processor::chain::params::DUFFS; +use dash_spv_masternode_processor::common::Block; +use dash_spv_masternode_processor::crypto::byte_util::UInt256; +use dash_spv_masternode_processor::ffi::boxer::boxed; +use dash_spv_masternode_processor::ffi::unboxer::unbox_any; +use dash_spv_masternode_processor::tx::transaction::Transaction; +use dash_spv_masternode_processor::util::script::ScriptType; +use logging::*; +use tracing::{info, warn, debug}; +use crate::ffi::callbacks::{GetInputValueByPrevoutHash, HasChainLock, DestroyInputValue}; +use crate::ffi::input_value::InputValue; +use crate::messages::pool_message::PoolMessage; +use crate::messages::pool_status::PoolStatus; +use crate::messages::coinjoin_broadcast_tx::CoinJoinBroadcastTx; +use crate::constants::COINJOIN_ENTRY_MAX_SIZE; +use crate::utils::coin_format::CoinFormat; + +#[derive(Debug)] +pub struct CoinJoin { + pub opaque_context: *const std::ffi::c_void, + pub get_input_value_by_prevout_hash: GetInputValueByPrevoutHash, + pub has_chain_lock: HasChainLock, + pub destroy_input_value: DestroyInputValue, + map_dstx: HashMap, // TODO: thread safety? +} + +impl CoinJoin { + // this list of standard denominations cannot be modified and must remain the same as + // CoinJoin::vecStandardDenominations in coinjoin.cpp + const STANDARD_DENOMINATIONS: [u64; 5] = [ + (10 * DUFFS) + 10000, + (1 * DUFFS) + 1000, + (DUFFS / 10) + 100, + (DUFFS / 100) + 10, + (DUFFS / 1000) + 1, + ]; + + pub fn new( + get_input_value_by_prevout_hash: GetInputValueByPrevoutHash, + has_chain_lock: HasChainLock, + destroy_input_value: DestroyInputValue, + context: *const std::ffi::c_void + ) -> Self { + Self { + opaque_context: context, + get_input_value_by_prevout_hash, + has_chain_lock, + destroy_input_value, + map_dstx: HashMap::new() + } + } + + pub fn get_standard_denominations() -> &'static [u64] { + &Self::STANDARD_DENOMINATIONS + } + + pub fn get_smallest_denomination() -> u64 { + Self::STANDARD_DENOMINATIONS[Self::STANDARD_DENOMINATIONS.len() - 1] + } + + pub fn is_denominated_amount(input_amount: u64) -> bool { + Self::amount_to_denomination(input_amount) > 0 + } + + pub fn is_valid_denomination(n_denom: u32) -> bool { + Self::denomination_to_amount(n_denom) > 0 + } + + /// Return a bitshifted integer representing a denomination in STANDARD_DENOMINATIONS + /// or 0 if none was found + pub fn amount_to_denomination(input_amount: u64) -> u32 { + for (i, &denom) in Self::STANDARD_DENOMINATIONS.iter().enumerate() { + if input_amount == denom { + return 1 << i; + } + } + + return 0; + } + + /// # Returns + /// - one of standard denominations from STANDARD_DENOMINATIONS based on the provided bitshifted integer + /// - 0 for non-initialized sessions (nDenom = 0) + /// - a value below 0 if an error occurred while converting from one to another + pub fn denomination_to_amount(n_denom: u32) -> i64 { + if n_denom == 0 { + // not initialized + return 0; + } + + let n_max_denoms = Self::STANDARD_DENOMINATIONS.len(); + + if n_denom >= (1 << n_max_denoms) { + // out of bounds + return -1; + } + + if n_denom & (n_denom - 1) != 0 { + // non-denom + return -2; + } + + let mut n_denom_amount = -3; + + for i in 0..n_max_denoms { + if n_denom & (1 << i) != 0 { + n_denom_amount = Self::STANDARD_DENOMINATIONS[i] as i64; + break; + } + } + + n_denom_amount + } + + pub fn max_pool_amount() -> u64 { + Self::STANDARD_DENOMINATIONS[0] * COINJOIN_ENTRY_MAX_SIZE + } + + pub fn denomination_to_string(denom: u32) -> String { + match Self::denomination_to_amount(denom) { + 0 => "N/A".to_string(), + -1 => "out-of-bounds".to_string(), + -2 => "non-denom".to_string(), + -3 => "to-amount-error".to_string(), + n => n.to_friendly_string() + } + } + + // check to make sure the collateral provided by the client is valid + pub fn is_collateral_valid(&self, tx_collateral: &Transaction, check_inputs: bool) -> bool { + if tx_collateral.outputs.is_empty() { + log_warn!(target: "CoinJoin", "Collateral invalid due to no outputs: {}", tx_collateral.tx_hash.unwrap_or_default()); + return false; + } + + if tx_collateral.lock_time != 0 { + log_warn!(target: "CoinJoin", "Collateral invalid due to lock time != 0: {}", tx_collateral.tx_hash.unwrap_or_default()); + return false; + } + + let mut n_value_in: i64 = 0; + let mut n_value_out: i64 = 0; + + for txout in &tx_collateral.outputs { + n_value_out = n_value_out + txout.amount as i64; + + if txout.script_pub_key_type() != ScriptType::PayToPubkeyHash && !txout.is_script_unspendable() { + log_warn!(target: "CoinJoin", "Invalid Script, txCollateral={}", tx_collateral.tx_hash().unwrap_or_default()); + return false; + } + } + + if check_inputs { + for txin in &tx_collateral.inputs { + let result = self.get_input_value_by_prevout_hash(txin.input_hash, txin.index); + + if let Some(input_value) = result { + if !input_value.is_valid { + log_warn!(target: "CoinJoin", "spent or non-locked mempool input!"); + log_debug!(target: "CoinJoin", "txin={:?}", txin); + return false; + } + + n_value_in = n_value_in + input_value.value as i64; + } else { + log_warn!(target: "CoinJoin", "Unknown inputs in collateral transaction, txCollateral={}", tx_collateral.tx_hash().unwrap_or_default()); + return false; + } + } + + log_debug!(target: "CoinJoin", "is_collateral_valid, values: n_value_out={}, n_value_in={}", n_value_out, n_value_in); + + if n_value_in - n_value_out < CoinJoin::get_collateral_amount() as i64 { + log_warn!(target: "CoinJoin", "did not include enough fees in transaction: fees: {}", n_value_out - n_value_in); + log_debug!(target: "CoinJoin", "txCollateral={:?}", tx_collateral.tx_hash().unwrap_or_default()); + return false; + } + } + + true + } + + pub fn get_collateral_amount() -> u64 { Self::get_smallest_denomination() / 10 } + pub fn get_max_collateral_amount() -> u64 { Self::get_collateral_amount() * 4 } + + pub fn is_collateral_amount(input_amount: u64) -> bool { + input_amount >= Self::get_collateral_amount() && input_amount <= Self::get_max_collateral_amount() + } + + pub fn calculate_amount_priority(input_amount: u64) -> i64 { + let mut opt_denom = 0; + + for denom in Self::get_standard_denominations() { + if input_amount == *denom { + opt_denom = *denom; + } + } + + if opt_denom > 0 { + return (DUFFS as f64 / opt_denom as f64 * 10000.0) as i64; + } + + if input_amount < DUFFS { + return 20000; + } + + // nondenom return largest first + -1 * (input_amount / DUFFS) as i64 + } + + pub fn add_dstx(&mut self, dstx: CoinJoinBroadcastTx) { + if let Some(tx_hash) = dstx.tx.tx_hash { + self.map_dstx.insert(tx_hash, dstx); + } + } + + pub fn has_dstx(&self, tx_hash: UInt256) -> bool { + self.map_dstx.contains_key(&tx_hash) + } + + pub fn get_dstx(&self, tx_hash: UInt256) -> Option<&CoinJoinBroadcastTx> { + self.map_dstx.get(&tx_hash) + } + + pub fn update_block_tip(&mut self, block: Block) { + self.check_dstxs(block); + } + + pub fn notify_chain_lock(&mut self, block: Block) { + self.check_dstxs(block); + } + + pub fn update_dstx_confirmed_height(&mut self, tx_hash: UInt256, n_height: i32) { + if let Some(broadcast_tx) = self.map_dstx.get_mut(&tx_hash) { + broadcast_tx.set_confirmed_height(n_height); + } + } + + pub fn transaction_added_to_mempool(&mut self, tx_hash: UInt256) { + self.update_dstx_confirmed_height(tx_hash, -1); + } + + pub fn block_connected(&mut self, block_height: u32, block_transactions: Vec, vtx_conflicted: Vec) { + for tx_hash in vtx_conflicted { + self.update_dstx_confirmed_height(tx_hash, -1); + } + + for tx_hash in block_transactions { + self.update_dstx_confirmed_height(tx_hash, block_height as i32); + } + } + + pub fn block_disconnected(&mut self, block_transactions: Vec,) { + for tx_hash in block_transactions { + self.update_dstx_confirmed_height(tx_hash, -1); + } + } + + pub fn get_message_by_id(message_id: PoolMessage) -> &'static str { + match message_id { + PoolMessage::ErrAlreadyHave => "Already have that input.", + PoolMessage::ErrDenom => "No matching denominations found for mixing.", + PoolMessage::ErrEntriesFull => "Entries are full.", + PoolMessage::ErrExistingTx => "Not compatible with existing transactions.", + PoolMessage::ErrFees => "Transaction fees are too high.", + PoolMessage::ErrInvalidCollateral => "Collateral not valid.", + PoolMessage::ErrInvalidInput => "Input is not valid.", + PoolMessage::ErrInvalidScript => "Invalid script detected.", + PoolMessage::ErrInvalidTx => "Transaction not valid.", + PoolMessage::ErrMaximum => "Entry exceeds maximum size.", + PoolMessage::ErrMnList => "Not in the Masternode list.", + PoolMessage::ErrMode => "Incompatible mode.", + PoolMessage::ErrQueueFull => "Masternode queue is full.", + PoolMessage::ErrRecent => "Last queue was created too recently.", + PoolMessage::ErrSession => "Session not complete!", + PoolMessage::ErrMissingTx => "Missing input transaction information.", + PoolMessage::ErrVersion => "Incompatible version.", + PoolMessage::MsgNoErr => "No errors detected.", + PoolMessage::MsgSuccess => "Transaction created successfully.", + PoolMessage::MsgEntriesAdded => "Your entries added successfully.", + PoolMessage::ErrSizeMismatch => "Inputs vs outputs size mismatch.", + PoolMessage::ErrTimeout => "Session has timed out.", + PoolMessage::ErrConnectionTimeout => "Connection attempt has timed out (15 ms).", // PendingDsaRequest.TIMEOUT + } + } + + pub fn get_status_message(status: PoolStatus) -> &'static str { + match status { + PoolStatus::Warmup => "Warming up...", + PoolStatus::Connecting => "Trying to connect...", + PoolStatus::Mixing => "Mixing in progress...", + PoolStatus::Finished => "Mixing Finished", + PoolStatus::ErrNoMasternodesDetected => "No masternodes detected", + PoolStatus::ErrMasternodeNotFound => "Can't find random Masternode", + PoolStatus::ErrWalletLocked => "Wallet is locked", + PoolStatus::ErrNotEnoughFunds => "Not enough funds", + PoolStatus::ErrNoInputs => "Can't mix: no compatible inputs found!", + PoolStatus::WarnNoMixingQueues => "Failed to find mixing queue to join", + PoolStatus::WarnNoCompatibleMasternode => "No compatible Masternode found", + _ => "", + } + } + + pub fn pool_min_participants(chain_type: &ChainType) -> u32 { + match chain_type { + ChainType::MainNet => 3, + ChainType::TestNet => 2, + ChainType::DevNet(_) => 2, + } + } + + pub fn pool_max_participants(chain_type: &ChainType) -> u32 { + match chain_type { + ChainType::MainNet => 20, + ChainType::TestNet => 20, + ChainType::DevNet(_) => 20, + } + } + + pub fn get_rounds_string(rounds: i32) -> &'static str { + match rounds { + -4 => "bad index", + -3 => "collateral", + -2 => "non-denominated", + -1 => "no such tx", + _ => "coinjoin", + } + } + + fn get_input_value_by_prevout_hash(&self, prevout_hash: UInt256, index: u32) -> Option { + unsafe { + let boxed_prevout_hash = boxed(prevout_hash.0); + let input_ptr = (self.get_input_value_by_prevout_hash)(boxed_prevout_hash, index, self.opaque_context); + + if input_ptr.is_null() { + return None + } + + let input_value: InputValue = std::ptr::read(input_ptr); + (self.destroy_input_value)(input_ptr); + unbox_any(boxed_prevout_hash); + + return Some(input_value); + } + } + + fn check_dstxs(&mut self, block: Block) { + self.map_dstx.retain(|_, tx| !tx.is_expired(block, self.has_chain_lock, self.opaque_context)); + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/coinjoin_base_session.rs b/dash-spv-coinjoin/src/coinjoin_base_session.rs new file mode 100644 index 00000000..24df65c4 --- /dev/null +++ b/dash-spv-coinjoin/src/coinjoin_base_session.rs @@ -0,0 +1,138 @@ +use std::collections::HashSet; +use std::fmt::Debug; +use std::time::{SystemTime, UNIX_EPOCH}; + +use dash_spv_masternode_processor::hashes::hex::ToHex; +use dash_spv_masternode_processor::tx::transaction::{Transaction, TransactionOutput, TransactionInput}; +use dash_spv_masternode_processor::util::script::ScriptType; +use logging::*; +use tracing::{error, warn, info}; +use crate::coinjoin::CoinJoin; +use crate::messages::{coinjoin_entry::CoinJoinEntry, pool_state::PoolState, pool_status::PoolStatus, pool_message::PoolMessage}; +use crate::models::valid_in_outs::ValidInOuts; + +#[repr(C)] +#[derive(Debug)] +pub struct CoinJoinBaseSession { + pub entries: Vec, + pub final_mutable_transaction: Option, + pub state: PoolState, + pub status: PoolStatus, + pub time_last_successful_step: u64, + pub session_id: i32, + pub session_denom: u32, // Users must submit a denom matching this, +} + +impl CoinJoinBaseSession { + pub fn new() -> Self { + Self { + entries: Vec::new(), + final_mutable_transaction: None, + state: PoolState::Idle, + status: PoolStatus::Warmup, + time_last_successful_step: 0, + session_id: 0, + session_denom: 0 + } + } + + pub fn set_null(&mut self) { + self.state = PoolState::Idle; + self.session_id = 0; + self.entries.clear(); + self.final_mutable_transaction = None; + self.time_last_successful_step = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(n) => n.as_secs(), + Err(_) => { println!("Failed to get time since epoch"); 0 }, + } + } + + pub fn is_valid_in_outs(&self, vin: &Vec, vout: &Vec) -> ValidInOuts { + let mut set_scrip_pub_keys = HashSet::new(); + let mut result = ValidInOuts::new(); + + if vin.len() != vout.len() { + log_error!(target: "CoinJoin", "ERROR: inputs vs outputs size mismatch! {} vs {}", vin.len(), vout.len()); + result.message_id = PoolMessage::ErrSizeMismatch; + result.consume_collateral = true; + result.result = false; + + return result; + } + + let mut check_tx_out = |tx_out: &TransactionOutput| -> ValidInOuts { + let denom = CoinJoin::amount_to_denomination(tx_out.amount); + let mut result = ValidInOuts::new(); + + if denom != self.session_denom { + log_error!(target: "CoinJoin", "ERROR: incompatible denom {} ({}) != sessionDenom {} ({})", + denom, CoinJoin::denomination_to_string(denom), self.session_denom, CoinJoin::denomination_to_string(self.session_denom)); + result.message_id = PoolMessage::ErrDenom; + result.consume_collateral = true; + result.result = false; + + return result; + } + + let hex = tx_out.script.as_ref().unwrap_or(&vec![]).to_hex(); + + if tx_out.script_pub_key_type() != ScriptType::PayToPubkeyHash { + log_error!(target: "CoinJoin", "ERROR: invalid scriptPubKey={}", hex); + result.message_id = PoolMessage::ErrInvalidScript; + result.consume_collateral = true; + result.result = false; + + return result; + } + + if !set_scrip_pub_keys.insert(hex.clone()) { + log_error!(target: "CoinJoin", "ERROR: already have this script! scriptPubKey={}", hex); + result.message_id = PoolMessage::ErrAlreadyHave; + result.consume_collateral = true; + result.result = false; + + return result; + } + + // IsPayToPublicKeyHash() above already checks for scriptPubKey size, + // no need to double-check, hence no usage of ERR_NON_STANDARD_PUBKEY + result.result = true; + + return result; + }; + + // Note: here, Dash Core checks that the fee's are zero, but we cannot since we don't have access to all of the inputs + + for tx_out in vout { + let output_result = &check_tx_out(&tx_out); + + if !output_result.result { + result.result = false; + return result; + } + } + + for tx_in in vin { + if tx_in.input_hash.0.is_empty() { + log_error!(target: "CoinJoin", "ERROR: invalid input! {:?}", tx_in); + result.message_id = PoolMessage::ErrInvalidInput; + result.consume_collateral = true; + result.result = false; + + return result; + } + } + + return result; + } + + pub fn get_state_string(&self) -> &'static str { + match self.state { + PoolState::Idle => "IDLE", + PoolState::Queue => "QUEUE", + PoolState::AcceptingEntries => "ACCEPTING_ENTRIES", + PoolState::Signing => "SIGNING", + PoolState::Error => "ERROR" + } + } +} diff --git a/dash-spv-coinjoin/src/coinjoin_client_manager.rs b/dash-spv-coinjoin/src/coinjoin_client_manager.rs new file mode 100644 index 00000000..786049bb --- /dev/null +++ b/dash-spv-coinjoin/src/coinjoin_client_manager.rs @@ -0,0 +1,451 @@ +use dash_spv_masternode_processor::{common::{Block, SocketAddress}, crypto::{byte_util::Reversable, UInt256}, ffi::{boxer::boxed_vec, from::FromFFI, unboxer::unbox_vec_ptr}, models::{MasternodeEntry, MasternodeList}, secp256k1::rand::{self, seq::SliceRandom, thread_rng, Rng}}; +use std::{cell::RefCell, collections::VecDeque, ffi::c_void, rc::Rc, time::{SystemTime, UNIX_EPOCH}}; +use tracing::{info, warn, debug, error}; +use logging::*; +use crate::{coinjoin::CoinJoin, coinjoin_client_queue_manager::CoinJoinClientQueueManager, coinjoin_client_session::CoinJoinClientSession, constants::{COINJOIN_AUTO_TIMEOUT_MAX, COINJOIN_AUTO_TIMEOUT_MIN}, ffi::callbacks::{DestroyMasternodeList, GetMasternodeList, IsWaitingForNewBlock, MixingLivecycleListener, SessionLifecycleListener, UpdateSuccessBlock}, messages::{coinjoin_message::CoinJoinMessage, CoinJoinQueueMessage, PoolState, PoolStatus}, models::{tx_outpoint::TxOutPoint, Balance, CoinJoinClientOptions}, wallet_ex::WalletEx}; + +pub struct CoinJoinClientManager { + pub wallet_ex: Rc>, + coinjoin: Rc>, + pub queue_queue_manager: Option>>, + options: Rc>, + masternodes_used: Vec, + last_masternode_used: usize, + last_time_report_too_recent: u64, + tick: i32, + do_auto_next_run: i32, + pub is_mixing: bool, + deq_sessions: VecDeque, + continue_mixing_on_status: Vec, + str_auto_denom_result: String, + stop_on_nothing_to_do: bool, + mixing_finished: bool, + get_masternode_list: GetMasternodeList, + destroy_mn_list: DestroyMasternodeList, + update_success_block: UpdateSuccessBlock, + is_waiting_for_new_block: IsWaitingForNewBlock, + session_lifecycle_listener: SessionLifecycleListener, + mixing_lifecycle_listener: MixingLivecycleListener, + context: *const c_void +} + +impl CoinJoinClientManager { + pub fn new( + wallet_ex: Rc>, + coinjoin: Rc>, + options: Rc>, + get_masternode_list: GetMasternodeList, + destroy_mn_list: DestroyMasternodeList, + update_success_block: UpdateSuccessBlock, + is_waiting_for_new_block: IsWaitingForNewBlock, + session_lifecycle_listener: SessionLifecycleListener, + mixing_lifecycle_listener: MixingLivecycleListener, + context: *const c_void + ) -> Self { + Self { + wallet_ex, + coinjoin, + queue_queue_manager: None, + options, + masternodes_used: vec![], + last_masternode_used: 0, + last_time_report_too_recent: 0, + tick: 0, + do_auto_next_run: COINJOIN_AUTO_TIMEOUT_MIN, + is_mixing: false, + deq_sessions: VecDeque::new(), + continue_mixing_on_status: vec![], + str_auto_denom_result: String::new(), + stop_on_nothing_to_do: false, + mixing_finished: false, + get_masternode_list, + destroy_mn_list, + update_success_block, + is_waiting_for_new_block, + session_lifecycle_listener, + mixing_lifecycle_listener, + context + } + } + + pub fn set_client_queue_manager(&mut self, queue_queue_manager: Rc>) { + self.queue_queue_manager = Some(queue_queue_manager); + } + + pub fn process_message(&mut self, from: SocketAddress, message: CoinJoinMessage) { + if !self.options.borrow().enable_coinjoin { + return; + } + + if !self.wallet_ex.borrow().is_synced() { + return; + } + + if let CoinJoinMessage::BroadcastTx(broadcast_tx) = message { + self.coinjoin.borrow_mut().add_dstx(broadcast_tx.clone()); + } else { + let mut update_success_block = false; + + for session in &mut self.deq_sessions { + update_success_block = session.process_message(&from, &message); + } + + if update_success_block { + self.updated_success_block(); + } + } + } + + pub fn start_mixing(&mut self) -> bool { + self.queue_mixing_lifecycle_listeners(false, false); + + if !self.is_mixing { + self.is_mixing = true; + return true; + } + + return false; + } + + pub fn set_stop_on_nothing_to_do(&mut self, stop: bool) { + self.stop_on_nothing_to_do = stop; + } + + pub fn stop_mixing(&mut self) { + self.is_mixing = false; + self.queue_mixing_lifecycle_listeners(self.mixing_finished, true); + } + + pub fn do_maintenance(&mut self, balance_info: Balance) { + if !self.options.borrow().enable_coinjoin { + return; + } + + if !self.wallet_ex.borrow().is_synced() { + log_debug!(target: "CoinJoin", "not synced"); + return; + } + + if let Some(queue_manager) = &self.queue_queue_manager { + queue_manager.borrow_mut().do_maintenance(); + } + + self.tick += 1; + self.check_timeout(); + self.process_pending_dsa_request(); + + if self.do_auto_next_run >= self.tick { + self.do_automatic_denominating(balance_info, false); + let mut rng = rand::thread_rng(); + self.do_auto_next_run = self.tick + COINJOIN_AUTO_TIMEOUT_MIN + rng.gen_range(0..COINJOIN_AUTO_TIMEOUT_MAX - COINJOIN_AUTO_TIMEOUT_MIN); + } + + // are all sessions idle? + let mut is_idle = !self.deq_sessions.is_empty(); // false if no sessions created yet + + for session in &self.deq_sessions { + if !session.has_nothing_to_do { + is_idle = false; + break; + } + } + + // if all sessions idle, then trigger stop mixing + if is_idle { + let statuses = self.get_sessions_status(); + + for status in statuses { + if status == PoolStatus::Finished || (status.is_error() && !self.continue_mixing_on_status.contains(&status)) { + self.trigger_mixing_finished(); + } + } + } + } + + pub fn do_automatic_denominating(&mut self, balance_info: Balance, dry_run: bool) -> bool { + if !self.options.borrow().enable_coinjoin || (!dry_run && !self.is_mixing) { + return false; + } + + if !dry_run && !self.wallet_ex.borrow().is_synced() { + log_info!(target: "CoinJoin", "wallet is not synced."); + self.str_auto_denom_result = "Wallet is not synced.".to_string(); + return false; + } + + // TODO: recheck + // if (!dryRun && mixingWallet.isEncrypted() && context.coinJoinManager.requestKeyParameter(mixingWallet) == null) { + // strAutoDenomResult = "Wallet is locked."; + // return false; + // } + + let mn_count_enabled = if dry_run { 0 } else { self.get_valid_mns_count(&self.get_mn_list()) }; + + // If we've used 90% of the Masternode list then drop the oldest first ~30% + let threshold_high = (mn_count_enabled as f64 * 0.9) as usize; + let threshold_low = (threshold_high as f64 * 0.7) as usize; + + if !self.is_waiting_for_new_block() { + if self.masternodes_used.len() != self.last_masternode_used { + self.last_masternode_used = self.masternodes_used.len(); + } + } + + if self.masternodes_used.len() > threshold_high { + // remove the first masternodesUsed.size() - thresholdLow masternodes + // this might be a problem for SPV + self.masternodes_used.drain(0..(self.masternodes_used.len() - threshold_low)); + log_warn!(target: "CoinJoin", "masternodesUsed: new size: {}, threshold: {}", self.masternodes_used.len(), threshold_high); + } + + if let Some(queue_manager) = &self.queue_queue_manager { + let mut result = true; + + if self.deq_sessions.len() < self.options.borrow().coinjoin_sessions as usize { + let new_session = CoinJoinClientSession::new( + self.coinjoin.clone(), + self.options.clone(), + self.wallet_ex.clone(), + queue_manager.clone(), + self.session_lifecycle_listener, + self.context + ); + + log_info!(target: "CoinJoin", "creating new session, current session amount: {}", self.deq_sessions.len()); + self.deq_sessions.push_back(new_session); + } + + let mut sessions_to_process: Vec<_> = self.deq_sessions.drain(..).collect(); + + for session in sessions_to_process.iter_mut() { + // (DashJ) we may not need this + if !dry_run && self.is_waiting_for_new_block() { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + if current_time - self.last_time_report_too_recent > 15_000 { + self.str_auto_denom_result = "Last successful action was too recent.".to_string(); + log_info!(target: "CoinJoin", "do_automatic_denominating {}", self.str_auto_denom_result); + self.last_time_report_too_recent = current_time; + } + + result = false; + break; + } + + result &= session.do_automatic_denominating(self, dry_run, balance_info.clone()); + } + + self.deq_sessions.extend(sessions_to_process); + + return result; + } + + return false; + } + + pub fn finish_automatic_denominating(&mut self, client_session_id: UInt256) -> bool { + let mut sessions: Vec<_> = self.deq_sessions.drain(..).collect(); + let mut finished = false; + + for session in &mut sessions { + if session.id == client_session_id { + finished = session.finish_automatic_denominating(self); + break; + } + } + + self.deq_sessions.extend(sessions); + + return finished; + } + + pub fn add_used_masternode(&mut self, pro_tx_hash: UInt256) { + self.masternodes_used.push(pro_tx_hash); + } + + pub fn get_random_not_used_masternode(&self) -> Option { + let mn_list = self.get_mn_list(); + let count_enabled = self.get_valid_mns_count(&mn_list); + let count_not_excluded = count_enabled - self.masternodes_used.len(); + + if count_not_excluded < 1 { + return None; + } + + // fill a vector + let mut vp_masternodes_shuffled: Vec<&MasternodeEntry> = Vec::with_capacity(count_enabled); + mn_list.masternodes.values().filter(|mn| mn.is_valid).for_each(|mn| { + vp_masternodes_shuffled.push(mn); + }); + + // shuffle pointers + let mut rng = thread_rng(); + vp_masternodes_shuffled.shuffle(&mut rng); + + // loop through + for dmn in vp_masternodes_shuffled { + if self.masternodes_used.contains(&dmn.provider_registration_transaction_hash) { + continue; + } + + log_info!(target: "CoinJoin", "mn found, proTxHash={}", dmn.provider_registration_transaction_hash); + return Some(dmn.clone()); + } + + log_error!(target: "CoinJoin", "failed get_random_not_used_masternode"); + return None; + } + + pub fn updated_success_block(&mut self) { + unsafe { + (self.update_success_block)(self.context); + } + } + + pub fn get_mn_list(&self) -> MasternodeList { + unsafe { + let raw_mn_list = (self.get_masternode_list)(self.context); + let mn_list = (*raw_mn_list).decode(); + (self.destroy_mn_list)(raw_mn_list); + + mn_list + } + } + + pub fn process_pending_dsa_request(&mut self) { + for session in self.deq_sessions.iter_mut() { + if session.process_pending_dsa_request() { + self.str_auto_denom_result = "Mixing in progress...".to_string(); + } + } + } + + pub fn get_sessions_status(&self) -> Vec { + let mut sessions_status = Vec::new(); + + for session in self.deq_sessions.iter() { + sessions_status.push(session.base_session.status); + } + + return sessions_status; + } + + pub fn is_waiting_for_new_block(&self) -> bool { + return unsafe { (self.is_waiting_for_new_block)(self.context) }; + } + + pub fn try_submit_denominate(&mut self, mn_addr: SocketAddress) -> bool { + for session in self.deq_sessions.iter_mut() { + if let Some(mn_mixing) = &session.mixing_masternode { + if mn_mixing.socket_address == mn_addr && session.base_session.state == PoolState::Queue { + session.submit_denominate(); + return true; + } else { + log_debug!(target: "CoinJoin", "mixingMasternode {} != mnAddr {} or {:?} != {:?}", mn_mixing.socket_address, mn_addr, session.base_session.state, PoolState::Queue); + } + } else { + log_debug!(target: "CoinJoin", "mixingMasternode is None"); + } + } + + return false; + } + + pub fn mark_already_joined_queue_as_tried(&mut self, dsq: &mut CoinJoinQueueMessage) -> bool { + for session in self.deq_sessions.iter_mut() { + if let Some(mn_mixing) = &session.mixing_masternode { + if mn_mixing.provider_registration_transaction_hash == dsq.pro_tx_hash { + dsq.tried = true; + return true; + } + } + } + + return false; + } + + pub fn update_block_tip(&mut self, block: Block) { + self.coinjoin.borrow_mut().update_block_tip(block) + } + + pub fn is_mixing_fee_tx(&mut self, tx_id: UInt256) -> bool { + for session in &mut self.deq_sessions { + if session.is_mixing_fee_tx(tx_id) { + return true; + } + } + + return false; + } + + pub fn refresh_unused_keys(&mut self) { + self.wallet_ex.borrow_mut().refresh_unused_keys(); + } + + pub fn process_used_scripts(&mut self, scripts: &Vec>) { + self.wallet_ex.borrow_mut().process_used_scripts(scripts); + } + + pub fn change_options(&mut self, new_options: CoinJoinClientOptions) { + if new_options != *self.options.borrow() { + if self.options.borrow().enable_coinjoin || new_options.enable_coinjoin { + log_info!(target: "CoinJoin", "updating client options: {:?}", new_options); + } + *self.options.borrow_mut() = new_options.clone(); + } + } + + pub fn get_real_outpoint_coinjoin_rounds(&mut self, outpoint: TxOutPoint, rounds: i32) -> i32 { + return self.wallet_ex.borrow_mut().get_real_outpoint_coinjoin_rounds(outpoint, rounds); + } + + pub fn reset_pool(&mut self) { + println!("[RUST] CoinJoin: reset_pool, self.deq_sessions.len(): {}", self.deq_sessions.len()); + self.masternodes_used.clear(); + + for session in &mut self.deq_sessions { + session.reset_pool(); + } + self.deq_sessions.clear(); + } + + fn get_valid_mns_count(&self, mn_list: &MasternodeList) -> usize { + mn_list.masternodes.values().filter(|mn| mn.is_valid).count() + } + + fn check_timeout(&mut self) { + if !self.options.borrow().enable_coinjoin || !self.is_mixing { + return; + } + + for session in self.deq_sessions.iter_mut() { + if session.check_timeout() { + self.str_auto_denom_result = "Session timed out.".to_string(); + } + } + } + + fn trigger_mixing_finished(&mut self) { + if self.stop_on_nothing_to_do { + self.mixing_finished = true; + self.queue_mixing_lifecycle_listeners(true, false); + } + } + + fn queue_mixing_lifecycle_listeners(&self, is_complete: bool, is_interrupted: bool) { + println!("[RUST] CoinJoin: queue_mixing_lifecycle_listeners, self.deq_sessions.len(): {}", self.deq_sessions.len()); + for session in &self.deq_sessions { + println!("[RUST] CoinJoin: session {:?} status {:?}", session.id, session.base_session.status); + println!("[RUST] CoinJoin: session outpoints_locked: {:?}", session.outpoints_locked.iter().map(|x| x.hash.reversed().to_string()).collect::>()); + } + + let statuses: Vec = self.deq_sessions.iter().map(|x| x.base_session.status).collect(); + let length = statuses.len(); + + unsafe { + let boxed_statuses = boxed_vec(statuses); + (self.mixing_lifecycle_listener)(is_complete, is_interrupted, boxed_statuses, length, self.context); + unbox_vec_ptr(boxed_statuses, length); + } + } +} diff --git a/dash-spv-coinjoin/src/coinjoin_client_queue_manager.rs b/dash-spv-coinjoin/src/coinjoin_client_queue_manager.rs new file mode 100644 index 00000000..d5570b79 --- /dev/null +++ b/dash-spv-coinjoin/src/coinjoin_client_queue_manager.rs @@ -0,0 +1,163 @@ +use std::collections::HashMap; +use dash_spv_masternode_processor::{common::SocketAddress, crypto::UInt256, ffi::{boxer::boxed, from::FromFFI, unboxer::unbox_any}, models::MasternodeEntry}; +use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::{debug, info, warn}; +use logging::*; +use crate::{coinjoin_client_manager::CoinJoinClientManager, constants::COINJOIN_QUEUE_TIMEOUT, ffi::callbacks::{DestroyMasternode, MasternodeByHash, ValidMasternodeCount}, masternode_meta_data_manager::MasternodeMetadataManager, messages::CoinJoinQueueMessage}; + +pub struct CoinJoinClientQueueManager { + client_manager_ptr: *mut CoinJoinClientManager, + coinjoin_queue: Vec, + spamming_masternodes: HashMap, + pub masternode_metadata_manager: MasternodeMetadataManager, + masternode_by_hash: MasternodeByHash, + destroy_masternode: DestroyMasternode, + valid_mns_count: ValidMasternodeCount, + context: *const std::ffi::c_void +} + +impl CoinJoinClientQueueManager { + pub fn new( + client_manager_ptr: *mut CoinJoinClientManager, + masternode_metadata_manager: MasternodeMetadataManager, + masternode_by_hash: MasternodeByHash, + destroy_masternode: DestroyMasternode, + valid_mns_count: ValidMasternodeCount, + context: *const std::ffi::c_void + ) -> Self { + Self { + client_manager_ptr, + coinjoin_queue: Vec::new(), + spamming_masternodes: HashMap::new(), + masternode_by_hash, + destroy_masternode, + masternode_metadata_manager, + valid_mns_count, + context + } + } + + pub fn set_null(&mut self) { + self.coinjoin_queue.clear(); + } + + pub fn check_queue(&mut self) { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.coinjoin_queue.retain(|q| !q.is_time_out_of_bounds(current_time)); + } + + pub fn get_queue_item_and_try(&mut self) -> Option { + for dsq in self.coinjoin_queue.iter_mut() { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + if !dsq.tried && !dsq.is_time_out_of_bounds(current_time) { + dsq.tried = true; + return Some(dsq.clone()); + } + } + None + } + + pub fn process_ds_queue(&mut self, from_peer: SocketAddress, mut dsq: CoinJoinQueueMessage) { + // let mut client_manager = (*self.client_manager_ptr); + + // process every dsq only once + for q in self.coinjoin_queue.iter() { + if q == &dsq { + return; + } + + if q.ready == dsq.ready && q.pro_tx_hash == dsq.pro_tx_hash { + // no way the same mn can send another dsq with the same readiness this soon + if !self.spamming_masternodes.contains_key(&dsq.pro_tx_hash) { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.spamming_masternodes.insert(dsq.pro_tx_hash, current_time); + log_debug!(target: "CoinJoin", "Peer {:?} is sending WAY too many dsq messages for a masternode {:?}", from_peer.ip_address, dsq.pro_tx_hash); + } + return; + } + } + + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + if dsq.is_time_out_of_bounds(current_time) { + return; + } + + if let Some(dmn) = self.get_mn(dsq.pro_tx_hash) { + if !dsq.check_signature(dmn.operator_public_key) { + // add 10 points to ban score + return; + } + + // if the queue is ready, submit if we can + if dsq.ready && self.try_submit_denominate(dmn.socket_address.clone()) { + log_info!(target: "CoinJoin", "CoinJoin queue ({}) is ready on masternode {}", dsq, dmn.socket_address); + } else { + if let Some(meta_info) = self.masternode_metadata_manager.get_meta_info(dmn.provider_registration_transaction_hash, true) { + let last_dsq = meta_info.last_dsq; + let dsq_threshold = self.masternode_metadata_manager.get_dsq_threshold(dmn.provider_registration_transaction_hash, self.valid_mns_count()); + + // don't allow a few nodes to dominate the queuing process + if last_dsq != 0 && dsq_threshold > self.masternode_metadata_manager.dsq_count { + if !self.spamming_masternodes.contains_key(&dsq.pro_tx_hash) { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.spamming_masternodes.insert(dsq.pro_tx_hash, current_time); + log_info!(target: "CoinJoin", "Masternode {} is sending too many dsq messages", dmn.provider_registration_transaction_hash); + } + return; + } + } else { + return; + } + + self.masternode_metadata_manager.allow_mixing(dmn.provider_registration_transaction_hash); + let log_msg = format!("new CoinJoin queue ({}) from masternode {}", dsq, dmn.socket_address); + + if dsq.ready { + log_info!(target: "CoinJoin", "{}", log_msg); + } else { + log_debug!(target: "CoinJoin", "{}", log_msg); + } + + self.mark_already_joined_queue_as_tried(&mut dsq); + self.coinjoin_queue.push(dsq); + } + } + } + + pub fn do_maintenance(&mut self) { + self.check_queue(); + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.spamming_masternodes.retain(|_, v| (*v + COINJOIN_QUEUE_TIMEOUT as u64) > current_time); + } + + fn get_mn(&self, pro_tx_hash: UInt256) -> Option { + unsafe { + let boxed_hash = boxed(pro_tx_hash.0); + let mn = (self.masternode_by_hash)(boxed_hash, self.context); + + if mn.is_null() { + return None; + } + + let masternode = (*mn).decode(); + (self.destroy_masternode)(mn); + unbox_any(boxed_hash); + + return Some(masternode); + } + } + + fn valid_mns_count(&self) -> u64 { + unsafe { return (self.valid_mns_count)(self.context); } + } + + fn try_submit_denominate(&mut self, mn_addr: SocketAddress) -> bool { + unsafe { (*self.client_manager_ptr).try_submit_denominate(mn_addr) } + } + + fn mark_already_joined_queue_as_tried(&mut self, dsq: &mut CoinJoinQueueMessage) -> bool { + unsafe { (*self.client_manager_ptr).mark_already_joined_queue_as_tried(dsq) } + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/coinjoin_client_session.rs b/dash-spv-coinjoin/src/coinjoin_client_session.rs new file mode 100644 index 00000000..f6a2fd27 --- /dev/null +++ b/dash-spv-coinjoin/src/coinjoin_client_session.rs @@ -0,0 +1,1640 @@ +use std::cell::RefCell; +use std::cmp::Ordering; +use std::collections::{HashMap, HashSet}; +use std::ffi::c_void; +use std::rc::Rc; +use std::time::{SystemTime, UNIX_EPOCH}; +use logging::*; +use tracing::{info, warn, error, debug}; +use dash_spv_masternode_processor::blockdata::opcodes::all::OP_RETURN; +use dash_spv_masternode_processor::chain::params::DUFFS; +use dash_spv_masternode_processor::chain::tx::protocol::{TXIN_SEQUENCE, TX_UNCONFIRMED}; +use dash_spv_masternode_processor::common::SocketAddress; +use dash_spv_masternode_processor::consensus::Encodable; +use dash_spv_masternode_processor::crypto::byte_util::{Random, Reversable}; +use dash_spv_masternode_processor::crypto::{UInt128, UInt256}; +use dash_spv_masternode_processor::ffi::boxer::boxed; +use dash_spv_masternode_processor::ffi::unboxer::unbox_any; +use dash_spv_masternode_processor::models::MasternodeEntry; +use dash_spv_masternode_processor::secp256k1::rand::{self, Rng}; +use dash_spv_masternode_processor::tx::{Transaction, TransactionInput, TransactionOutput, TransactionType}; +use dash_spv_masternode_processor::util::script::ScriptType; + +use crate::coinjoin::CoinJoin; +use crate::coinjoin_client_manager::CoinJoinClientManager; +use crate::coinjoin_client_queue_manager::CoinJoinClientQueueManager; +use crate::constants::{COINJOIN_DENOM_OUTPUTS_THRESHOLD, COINJOIN_ENTRY_MAX_SIZE, COINJOIN_QUEUE_TIMEOUT, COINJOIN_SIGNING_TIMEOUT, DEFAULT_COINJOIN_DENOMS_GOAL, DEFAULT_COINJOIN_DENOMS_HARDCAP}; +use crate::ffi::callbacks::SessionLifecycleListener; +use crate::messages::{CoinJoinAcceptMessage, CoinJoinCompleteMessage, CoinJoinEntry, CoinJoinFinalTransaction, CoinJoinSignedInputs, CoinJoinStatusUpdate, PoolMessage, PoolStatusUpdate}; +use crate::models::coin_control::{CoinControl, CoinType}; +use crate::models::coinjoin_transaction_input::CoinJoinTransactionInput; +use crate::models::reserve_destination::ReserveDestination; +use crate::models::tx_outpoint::TxOutPoint; +use crate::messages::{pool_state::PoolState, pool_status::PoolStatus}; +use crate::models::pending_dsa_request::PendingDsaRequest; +use crate::models::{CoinJoinClientOptions, Balance}; +use crate::coinjoin_base_session::CoinJoinBaseSession; +use crate::utils::coin_format::CoinFormat; +use crate::utils::key_holder_storage::KeyHolderStorage; +use crate::coin_selection::compact_tally_item::CompactTallyItem; +use crate::utils::transaction_builder::TransactionBuilder; +use crate::wallet_ex::WalletEx; +use crate::messages::coinjoin_message::{CoinJoinMessageType, CoinJoinMessage}; + +pub struct CoinJoinClientSession { + pub id: UInt256, + coinjoin: Rc>, + mixing_wallet: Rc>, + queue_manager: Rc>, + pub base_session: CoinJoinBaseSession, + options: Rc>, + key_holder_storage: KeyHolderStorage, + last_create_denominated_result: bool, + pub outpoints_locked: Vec, + pub mixing_masternode: Option, + pending_dsa_request: Option, + tx_my_collateral: Option, + is_my_collateral_valid: bool, + collateral_session_map: HashMap, + balance_needs_anonymized: u64, + joined: bool, // did we join a session (true), or start a session (false) + pub has_nothing_to_do: bool, + str_auto_denom_result: String, + str_last_message: String, + session_lifecycle_listener: SessionLifecycleListener, + context: *const c_void +} + +impl CoinJoinClientSession { + pub fn new( + coinjoin: Rc>, + options: Rc>, + wallet_ex: Rc>, + client_queue_manager: Rc>, + session_lifecycle_listener: SessionLifecycleListener, + context: *const c_void + ) -> Self { + Self { + id: UInt256::random(), + coinjoin: coinjoin, + mixing_wallet: wallet_ex, + queue_manager: client_queue_manager, + base_session: CoinJoinBaseSession::new(), + key_holder_storage: KeyHolderStorage::new(), + options: options, + last_create_denominated_result: true, + outpoints_locked: Vec::new(), + mixing_masternode: None, + pending_dsa_request: None, + tx_my_collateral: None, + is_my_collateral_valid: false, + collateral_session_map: HashMap::new(), + balance_needs_anonymized: 0, + joined: false, + has_nothing_to_do: false, + str_auto_denom_result: String::new(), + str_last_message: String::new(), + session_lifecycle_listener, + context + } + } + + pub fn do_automatic_denominating(&mut self, client_manager: &mut CoinJoinClientManager, dry_run: bool, balance_info: Balance) -> bool { + if self.outpoints_locked.len() > 0 { + println!("[RUST] do_automatic_denominating, session: {}, outpoints_locked.len(): {}", self.id, self.outpoints_locked.len()); + } + + if self.base_session.state != PoolState::Idle || !self.options.borrow().enable_coinjoin { + return false; + } + + if self.base_session.entries.len() > 0 { + self.set_status(PoolStatus::Mixing); + return false; + } + + if self.options.borrow().coinjoin_amount == 0 || balance_info.my_trusted == 0 { + log_debug!(target: "CoinJoin", "CoinJoinClientSession::do_automatic_denominating -- skipping"); + return false; + } + + log_info!(target: "CoinJoin", "balance: {}, coinjoin_amount: {}", balance_info, self.options.borrow().coinjoin_amount); + + let balance_anonymized = balance_info.anonymized; + let sub_res = self.options.borrow().coinjoin_amount.checked_sub(balance_anonymized); + + if sub_res.is_none() || sub_res.unwrap() == 0 { + log_info!(target: "CoinJoin", "CoinJoinClientSession::do_automatic_denominating -- Nothing to do"); + // nothing to do, just keep it in idle mode + self.set_status(PoolStatus::Finished); + return false; + } + + self.has_nothing_to_do = false; + let mut balance_needs_anonymized = sub_res.unwrap(); + let mut value_min = CoinJoin::get_smallest_denomination(); + + // if there are no confirmed DS collateral inputs yet + if !self.mixing_wallet.borrow_mut().has_collateral_inputs(true) { + // should have some additional amount for them + value_min = value_min + CoinJoin::get_max_collateral_amount(); + } + + // including denoms but applying some restrictions + let balance_anonymizable = self.mixing_wallet.borrow_mut().get_anonymizable_balance(false, true); + + // mixable balance is way too small + if balance_anonymizable < value_min { + let balance_left_to_mix = self.mixing_wallet.borrow_mut().get_anonymizable_balance(false, false); + + if !dry_run && balance_left_to_mix < value_min { + self.set_status(PoolStatus::ErrNotEnoughFunds); + self.queue_session_lifecycle_listeners(true, self.base_session.state, PoolMessage::ErrSession, PoolStatus::ErrNotEnoughFunds); + } + + log_info!(target: "CoinJoin", "skipping, balance_anonymizable: {}, balance_left_to_mix: {}, value_min: {}, dry_run: {}", balance_anonymizable, balance_left_to_mix, value_min, dry_run); + return false; + } + + let balance_anonimizable_non_denom = self.mixing_wallet.borrow_mut().get_anonymizable_balance(true, true); + let balance_denominated_conf = balance_info.denominated_trusted; + let balance_denominated_unconf = balance_info.denominated_untrusted_pending; + let balance_denominated = balance_denominated_conf + balance_denominated_unconf; + let balance_to_denominate = self.options.borrow().coinjoin_amount.saturating_sub(balance_denominated); + log_info!(target: "CoinJoin", "balance_to_denominate: {}, dry_run: {}", balance_to_denominate, dry_run); + + // Adjust balance_needs_anonymized to consume final denom + if balance_denominated.saturating_sub(balance_anonymized) > balance_needs_anonymized as u64 { + log_info!(target: "CoinJoin", "adjusting balance_needs_anonymized to consume final denom"); + let denoms = CoinJoin::get_standard_denominations(); + let mut additional_denom: u64 = 0; + + for denom in denoms { + if (balance_needs_anonymized as u64) < *denom { + additional_denom = *denom; + } else { + break; + } + } + balance_needs_anonymized += additional_denom; + } + + log_info!(target: "CoinJoin", "current stats: value_min: {}, my_trusted: {}, balance_anonymizable: {}, balance_anonymized: {}, balance_needs_anonymized: {}, balance_anonimizable_non_denom: {}, balance_denominated_conf: {}, balance_denominated_unconf: {}, balance_denominated: {}, balance_to_denominate: {}", + value_min.to_friendly_string(), + balance_info.my_trusted.to_friendly_string(), + balance_anonymizable.to_friendly_string(), + balance_anonymized.to_friendly_string(), + balance_needs_anonymized.to_friendly_string(), + balance_anonimizable_non_denom.to_friendly_string(), + balance_denominated_conf.to_friendly_string(), + balance_denominated_unconf.to_friendly_string(), + balance_denominated.to_friendly_string(), + balance_to_denominate.to_friendly_string() + ); + + // Check if we have should create more denominated inputs i.e. + // there are funds to denominate and denominated balance does not exceed + // max amount to mix yet. + self.last_create_denominated_result = false; + + if balance_anonimizable_non_denom >= value_min + CoinJoin::get_max_collateral_amount() && balance_to_denominate >= CoinJoin::get_smallest_denomination() { + self.last_create_denominated_result = self.create_denominated(client_manager, balance_to_denominate, dry_run); + } + + if dry_run { + if self.last_create_denominated_result { + self.balance_needs_anonymized = balance_needs_anonymized; + return true; + } + + return false; + } + + self.balance_needs_anonymized = balance_needs_anonymized; + + if self.last_create_denominated_result { + log_debug!(target: "CoinJoin", "auto_denom: wait for finish callback"); + // If transaction was commited, return and wait for obj-c to call finish_automatic_denominating + return true; + } else { + log_debug!(target: "CoinJoin", "auto_denom: proceed immediately"); + // If no transactions were commited, call finish_automatic_denominating directly + self.finish_automatic_denominating(client_manager); + + return true; + } + } + + pub fn finish_automatic_denominating(&mut self, client_manager: &mut CoinJoinClientManager) -> bool { + log_debug!(target: "CoinJoin", "finish_automatic_denominating: {}", self.balance_needs_anonymized.to_friendly_string()); + + if self.balance_needs_anonymized == 0 { + return false; + } + + let balance_needs_anonymized = self.balance_needs_anonymized; + self.balance_needs_anonymized = 0; + + // check if we have the collateral sized inputs + if !self.mixing_wallet.borrow_mut().has_collateral_inputs(true) { + let result = !self.mixing_wallet.borrow_mut().has_collateral_inputs(false) && self.make_collateral_amounts(client_manager); + + if !result && !self.last_create_denominated_result { + self.set_status(PoolStatus::ErrNoInputs); + } + + return result; + } + + if self.base_session.session_id != 0 { + self.set_status(PoolStatus::Mixing); + log_info!(target: "CoinJoin", "base_session.session_id != 0"); + return false; + } + + // Initial phase, find a Masternode + // Clean if there is anything left from previous session + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + + // should be no unconfirmed denoms in non-multi-session mode + // TODO: support denominatedUntrustedPending + + // if !self.options.coinjoin_multi_session && balance_denominated_unconf > 0 { + // self.str_auto_denom_result = "Found unconfirmed denominated outputs, will wait till they confirm to continue.".to_string(); + // println!("[RUST] CoinJoin: {}", self.str_auto_denom_result); + // return false; + // } + + let mut reason = String::new(); + match self.tx_my_collateral.clone() { + None => { + if !self.create_collateral_transaction(&mut reason) { + log_error!(target: "CoinJoin", "create collateral error: {}", reason); + return false; + } + }, + Some(collateral) => { + if !self.is_my_collateral_valid || !self.coinjoin.borrow().is_collateral_valid(&collateral, true) { + log_info!(target: "CoinJoin", "invalid collateral, recreating... [id: {}] ", self.id); + let output = &collateral.outputs[0]; + + if output.script_pub_key_type() == ScriptType::PayToPubkeyHash { + self.mixing_wallet.borrow_mut().add_unused_key(&output.script); + } + + if !self.create_collateral_transaction(&mut reason) { + log_error!(target: "CoinJoin", "create collateral error: {}", reason); + return false; + } + } + + // lock the funds we're going to use for our collateral + for txin in &collateral.inputs { + let outpoint = TxOutPoint::new(txin.input_hash, txin.index); + self.mixing_wallet.borrow_mut().lock_coin(outpoint.clone()); + println!("[RUST] CoinJoin: locked coin by session {:?}: {:?}", self.id, outpoint); + self.outpoints_locked.push(outpoint); + println!("[RUST] CoinJoin: session outpoints_locked: {:?}", self.outpoints_locked.iter().map(|x| x.hash.reversed().to_string()).collect::>()); + } + } + } + + if self.options.borrow().denom_only { + return true; + } + + // Always attempt to join an existing queue + if self.join_existing_queue(client_manager, balance_needs_anonymized) { + return true; + } + + // If we were unable to find/join an existing queue then start a new one. + if self.start_new_queue(client_manager, balance_needs_anonymized) { + return true; + } + + self.set_status(PoolStatus::WarnNoCompatibleMasternode); + + return false; + } + + pub fn process_pending_dsa_request(&mut self) -> bool { + if let Some(pending_request) = &self.pending_dsa_request { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.base_session.time_last_successful_step = current_time; + let mut buffer = vec![]; + pending_request.dsa.consensus_encode(&mut buffer).unwrap(); + let message_sent = self.mixing_wallet.borrow_mut().send_message(buffer, pending_request.dsa.get_message_type(), &pending_request.addr, false); + + if message_sent { + log_info!(target: "CoinJoin", "sent dsa to {}", pending_request.addr); + log_debug!(target: "CoinJoin", "dsa={}", pending_request.dsa); + self.pending_dsa_request = None; + } else if pending_request.is_expired() { + log_warn!(target: "CoinJoin", "failed to connect to {}; reason: cannot find peer", pending_request.addr); + self.set_status(PoolStatus::ConnectionTimeout); + self.queue_session_lifecycle_listeners(true, self.base_session.state, PoolMessage::ErrConnectionTimeout, PoolStatus::ConnectionTimeout); + self.set_null(); + } + + return message_sent; + } + + return false; + } + + fn create_denominated(&mut self, client_manager: &mut CoinJoinClientManager, balance_to_denominate: u64, dry_run: bool) -> bool { + if !self.options.borrow().enable_coinjoin { + return false; + } + + // NOTE: We do not allow txes larger than 100 kB, so we have to limit number of inputs here. + // We still want to consume a lot of inputs to avoid creating only smaller denoms though. + // Knowing that each CTxIn is at least 148 B big, 400 inputs should take 400 x ~148 B = ~60 kB. + // This still leaves more than enough room for another data of typical CreateDenominated tx. + let mut vec_tally: Vec = self.mixing_wallet.borrow_mut().select_coins_grouped_by_addresses(true, true, true, 400); + + if vec_tally.is_empty() { + log_info!(target: "CoinJoin", "CoinJoinClientSession::CreateDenominated -- SelectCoinsGroupedByAddresses can't find any inputs!"); + return false; + } + + // Start from the largest balances first to speed things up by creating txes with larger/largest denoms included + vec_tally.sort_by(|a, b| b.amount.cmp(&a.amount)); + let create_mixing_collaterals = !self.mixing_wallet.borrow_mut().has_collateral_inputs(true); + log_info!(target: "CoinJoin", "has_collateral_inputs: {}", !create_mixing_collaterals); + + for item in vec_tally { + if !self.create_denominated_with_item(client_manager, &item, balance_to_denominate, create_mixing_collaterals, dry_run) { + continue; + } + + return true; + } + + log_info!(target: "CoinJoin", "CoinJoinClientSession: createDenominated({}) -- failed!", balance_to_denominate.to_friendly_string()); + false + } + + fn create_denominated_with_item( + &mut self, + client_manager: &mut CoinJoinClientManager, + tally_item: &CompactTallyItem, + balance_to_denominate: u64, + create_mixing_collaterals: bool, + dry_run: bool + ) -> bool { + if !self.options.borrow().enable_coinjoin { + return false; + } + + // denominated input is always a single one, so we can check its amount directly and return early + if tally_item.input_coins.len() == 1 && CoinJoin::is_denominated_amount(tally_item.amount) { + return false; + } + + let mut tx_builder = TransactionBuilder::new( + self.mixing_wallet.clone(), + self.options.borrow().chain_type, + tally_item.clone(), + dry_run + ); + + log_info!(target: "CoinJoin", "create_denominated_with_item. Start tx_builder: {}", tx_builder); + + // ****** Add an output for mixing collaterals ************ / + + if create_mixing_collaterals && !tx_builder.add_output(CoinJoin::get_max_collateral_amount()) { + log_warn!(target: "CoinJoin", "CoinJoinClientSession::CreateDenominatedWithItem -- Failed to add collateral output"); + return false; + } + + // ****** Add outputs for denoms ************ / + + let mut add_final = true; + let denoms = CoinJoin::get_standard_denominations(); + let mut map_denom_count = HashMap::new(); + + for denom_value in denoms { + map_denom_count.insert(*denom_value, self.mixing_wallet.borrow_mut().count_inputs_with_amount(*denom_value)); + } + + // Will generate outputs for the createdenoms up to coinjoinmaxdenoms per denom + + // This works in the way creating PS denoms has traditionally worked, assuming enough funds, + // it will start with the smallest denom then create 11 of those, then go up to the next biggest denom create 11 + // and repeat. Previously, once the largest denom was reached, as many would be created were created as possible and + // then any remaining was put into a change address and denominations were created in the same manner a block later. + // Now, in this system, so long as we don't reach COINJOIN_DENOM_OUTPUTS_THRESHOLD outputs the process repeats in + // the same transaction, creating up to nCoinJoinDenomsHardCap per denomination in a single transaction. + // let tx_builder = self.tx_builder.borrow_mut(); + + let mut balance_to_denominate = balance_to_denominate; + + while tx_builder.could_add_output(CoinJoin::get_smallest_denomination()) && (tx_builder.outputs.len() as i32) < COINJOIN_DENOM_OUTPUTS_THRESHOLD { + for denom_value in denoms.iter().rev() { + let mut outputs = 0; + + let mut need_more_outputs = |tx_builder: &TransactionBuilder, balance_to_denom: u64, outputs: i32| { + if tx_builder.could_add_output(*denom_value) { + if add_final && balance_to_denom > 0 && balance_to_denom < *denom_value { + add_final = false; // add final denom only once, only the smallest possible one + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 1 - FINAL - nDenomValue: {}, nBalanceToDenominate: {}, nOutputs: {}, {}", + denom_value.to_friendly_string(), balance_to_denom.to_friendly_string(), outputs, tx_builder.to_string()); + return true; + } else if balance_to_denom >= *denom_value { + return true; + } + } + + return false; + }; + + // add each output up to 11 times or until it can't be added again or until we reach nCoinJoinDenomsGoal + while need_more_outputs(&tx_builder, balance_to_denominate, outputs) && outputs <= 10 && map_denom_count[denom_value] < DEFAULT_COINJOIN_DENOMS_GOAL { + // Add output and subtract denomination amount + if tx_builder.add_output(*denom_value) { + outputs += 1; + *map_denom_count.entry(*denom_value).or_insert(0) += 1; + balance_to_denominate = balance_to_denominate.saturating_sub(*denom_value); + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 2 - nDenomValue: {}, nBalanceToDenominate: {}, nOutputs: {}, {}", + denom_value.to_friendly_string(), balance_to_denominate.to_friendly_string(), outputs, tx_builder.to_string()); + } else { + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 2 - Error: AddOutput failed for nDenomValue: {}, nBalanceToDenominate: {}, nOutputs: {}, {}", + denom_value.to_friendly_string(), balance_to_denominate.to_friendly_string(), outputs, tx_builder.to_string()); + return false; + } + } + + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 2 - tx_builder.amount_left: {}, balance_to_denominate: {}", tx_builder.amount_left(), balance_to_denominate); + if tx_builder.amount_left() == 0 || balance_to_denominate == 0 { + break; + } + } + + let mut finished = true; + + for (denom, count) in &map_denom_count { + // Check if this specific denom could use another loop, check that there aren't nCoinJoinDenomsGoal of this + // denom and that our nValueLeft/nBalanceToDenominate is enough to create one of these denoms, if so, loop again. + if *count < DEFAULT_COINJOIN_DENOMS_GOAL && tx_builder.could_add_output(*denom) && balance_to_denominate >= CoinJoin::get_smallest_denomination() { + finished = false; + log_info!(target: "CoinJoin", "CoinJoinClientSession -- 1 - NOT finished - nDenomValue: {}, count: {}, nBalanceToDenominate: {}, {}", + denom.to_friendly_string(), count, balance_to_denominate.to_friendly_string(), tx_builder.to_string()); + break; + } + log_info!(target: "CoinJoin", "CoinJoinClientSession -- 1 - FINISHED - nDenomValue: {}, count: {}, nBalanceToDenominate: {}, {}", + denom.to_friendly_string(), count, balance_to_denominate.to_friendly_string(), tx_builder.to_string()); + } + + if finished { + break; + } + } + + // Now that nCoinJoinDenomsGoal worth of each denom have been created or the max number of denoms given the value of the input, do something with the remainder. + // if (txBuilder.CouldAddOutput(CCoinJoin::GetSmallestDenomination()) && nBalanceToDenominate >= CCoinJoin::GetSmallestDenomination() && txBuilder.CountOutputs() < COINJOIN_DENOM_OUTPUTS_THRESHOLD) { + if tx_builder.could_add_output(CoinJoin::get_smallest_denomination()) && balance_to_denominate >= CoinJoin::get_smallest_denomination() && (tx_builder.outputs.len() as i32) < COINJOIN_DENOM_OUTPUTS_THRESHOLD { + let largest_denom_value = denoms[0]; + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 2 - Process remainder: {}\n", tx_builder.to_string()); + + let count_possible_outputs = |amount: u64, tx_builder: &TransactionBuilder| -> u64 { + let mut vec_outputs: Vec = Vec::new(); + loop { + // Create a potential output + vec_outputs.push(amount); + if !tx_builder.could_add_outputs(&vec_outputs) || + tx_builder.outputs.len() + vec_outputs.len() > COINJOIN_DENOM_OUTPUTS_THRESHOLD as usize { + // If it's not possible to add it due to insufficient amount left or total number of outputs exceeds + // COINJOIN_DENOM_OUTPUTS_THRESHOLD, drop the output again and stop trying. + vec_outputs.pop(); + break; + } + } + vec_outputs.len() as u64 + }; + + // Go big to small + for denom_value in denoms { + if balance_to_denominate <= 0 { + break; + } + + let mut outputs = 0; + // Number of denoms we can create given our denom and the amount of funds we have left + let denoms_to_create_value = count_possible_outputs(*denom_value, &tx_builder); + // Prefer overshooting the target balance by larger denoms (hence `+1`) instead of a more + // accurate approximation by many smaller denoms. This is ok because when we get here we + // should have nCoinJoinDenomsGoal of each smaller denom already. Also, without `+1` + // we can end up in a situation when there is already nCoinJoinDenomsHardCap of smaller + // denoms, yet we can't mix the remaining nBalanceToDenominate because it's smaller than + // nDenomValue (and thus denomsToCreateBal == 0), so the target would never get reached + // even when there is enough funds for that. + let denoms_to_create_bal = (balance_to_denominate / *denom_value as u64) + 1; + // Use the smaller value + let denoms_to_create = denoms_to_create_value.min(denoms_to_create_bal); + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 2 - nBalanceToDenominate: {}, nDenomValue: {}, denomsToCreateValue: {}, denomsToCreateBal: {}\n", + balance_to_denominate.to_friendly_string(), denom_value.to_friendly_string(), denoms_to_create_value.to_friendly_string(), denoms_to_create_bal.to_friendly_string()); + + let mut it = map_denom_count[denom_value]; + + for i in 0..denoms_to_create { + // Never go above the cap unless it's the largest denom + if *denom_value != largest_denom_value && it >= DEFAULT_COINJOIN_DENOMS_HARDCAP { + break; + } + + // Increment helpers, add output and subtract denomination amount + if tx_builder.add_output(*denom_value) { + outputs += 1; + it += 1; + map_denom_count.insert(*denom_value, it); + balance_to_denominate = balance_to_denominate.saturating_sub(*denom_value); + } else { + log_error!(target: "CoinJoin", "CoinJoinClientSession -- 2 - Error: AddOutput failed at {}/{}, {}\n", i + 1, denoms_to_create, tx_builder.to_string()); + break; + } + + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 2 - denomValue: {}, balanceToDenominate: {}, nOutputs: {}, {}\n", + denom_value.to_friendly_string(), balance_to_denominate.to_friendly_string(), outputs, tx_builder.to_string()); + + if (tx_builder.outputs.len() as i32) >= COINJOIN_DENOM_OUTPUTS_THRESHOLD { + break; + } + } + + if (tx_builder.outputs.len() as i32) >= COINJOIN_DENOM_OUTPUTS_THRESHOLD { + break; + } + } + } + + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 3 - nBalanceToDenominate: {}, {}", balance_to_denominate.to_friendly_string(), tx_builder.to_string()); + + for (denom, count) in &map_denom_count { + log_debug!(target: "CoinJoin", "CoinJoinClientSession -- 3 - DONE - nDenomValue: {}, count: {}", denom.to_friendly_string(), count); + } + + // No reasons to create mixing collaterals if we can't create denoms to mix + if (create_mixing_collaterals && tx_builder.outputs.len() == 1) || tx_builder.outputs.len() == 0 { + return false; + } + + if !dry_run { + let mut str_result = String::new(); + + if !tx_builder.commit(&mut str_result, true, self.id) { + log_error!(target: "CoinJoin", "CoinJoinClientSession -- 4 - Commit failed: {}\n", str_result); + return false; + } + + // use the same nCachedLastSuccessBlock as for DS mixing to prevent race + client_manager.updated_success_block(); + log_info!(target: "CoinJoin", "CoinJoinClientSession -- 4: commited CreateDenomination tx, str_result: {}\n", str_result); + } + + return true; + } + + fn make_collateral_amounts(&mut self, client_manager: &mut CoinJoinClientManager) -> bool { + if !self.options.borrow().enable_coinjoin { + return false; + } + + log_info!(target: "CoinJoin", "make_collateral_amounts"); + + // NOTE: We do not allow txes larger than 100 kB, so we have to limit number of inputs here. + // We still want to consume a lot of inputs to avoid creating only smaller denoms though. + // Knowing that each CTxIn is at least 148 B big, 400 inputs should take 400 x ~148 B = ~60 kB. + // This still leaves more than enough room for another data of typical MakeCollateralAmounts tx. + let mut vec_tally = self.mixing_wallet.borrow_mut().select_coins_grouped_by_addresses(false, false, true, 400); + + if vec_tally.is_empty() { + log_warn!(target: "CoinJoin", "CoinJoinClientSession::MakeCollateralAmounts -- SelectCoinsGroupedByAddresses can't find any inputs!\n"); + return false; + } + + // Start from the smallest balances first to consume tiny amounts and cleanup UTXO a bit + vec_tally.sort_by(|a, b| a.amount.cmp(&b.amount)); + + // First try to use only non-denominated funds + for item in &vec_tally { + if !self.make_collateral_amounts_with_item(client_manager, item, false) { + continue; + } + + return true; + } + + // There should be at least some denominated funds we should be able to break in pieces to continue mixing + for item in &vec_tally { + if !self.make_collateral_amounts_with_item(client_manager, item, true) { + continue; + } + + return true; + } + + // If we got here then something is terribly broken actually + log_error!(target: "CoinJoin", "CoinJoinClientSession::MakeCollateralAmounts -- ERROR: Can't make collaterals!"); + return false; + } + + fn make_collateral_amounts_with_item(&mut self, client_manager: &mut CoinJoinClientManager, tally_item: &CompactTallyItem, try_denominated: bool) -> bool { + if !self.options.borrow().enable_coinjoin { + return false; + } + + log_debug!(target: "CoinJoin", "make_collateral_amounts_with_item: {:?}", tally_item); + + // Denominated input is always a single one, so we can check its amount directly and return early + if !try_denominated && tally_item.input_coins.len() == 1 && CoinJoin::is_denominated_amount(tally_item.amount) { + return false; + } + + // Skip single inputs that can be used as collaterals already + if tally_item.input_coins.len() == 1 && CoinJoin::is_collateral_amount(tally_item.amount) { + return false; + } + + let mut tx_builder = TransactionBuilder::new( + self.mixing_wallet.clone(), + self.options.borrow().chain_type, + tally_item.clone(), + false + ); + + // Skip way too tiny amounts. Smallest we want is minimum collateral amount in a one output tx + if !tx_builder.could_add_output(CoinJoin::get_collateral_amount()) { + return false; + } + + let case; // Just for debug logs + + if tx_builder.could_add_outputs(&[CoinJoin::get_max_collateral_amount(), CoinJoin::get_collateral_amount()]) { + case = 1; + // , see TransactionRecord::decomposeTransaction + // Out1 == CoinJoin.GetMaxCollateralAmount() + // Out2 >= CoinJoin.GetCollateralAmount() + + tx_builder.add_output(CoinJoin::get_max_collateral_amount()); + // Note, here we first add a zero amount output to get the remainder after all fees and then assign it + if tx_builder.add_zero_output() { + let amount_left = tx_builder.amount_left(); + // If remainder is denominated add one duff to the fee + if let Some(out) = tx_builder.outputs.last_mut() { + out.update_amount( + if CoinJoin::is_denominated_amount(amount_left) { amount_left - DUFFS } else { amount_left }, + amount_left + ); + } + } + } else if tx_builder.could_add_outputs(&[CoinJoin::get_collateral_amount(), CoinJoin::get_collateral_amount()]) { + case = 2; + // , see TransactionRecord::decomposeTransaction + // Out1 CoinJoin.isCollateralAmount() + // Out2 CoinJoin.isCollateralAmount() + + // First add two outputs to get the available value after all fees + tx_builder.add_zero_output(); + tx_builder.add_zero_output(); + + // Create two equal outputs from the available value. This adds one duff to the fee if txBuilder.GetAmountLeft() is odd. + let amount_outputs = tx_builder.amount_left() / 2; + + assert!(CoinJoin::is_collateral_amount(amount_outputs)); + + let amount_left = tx_builder.amount_left(); + let last_index = tx_builder.outputs.len() - 1; + + if let Some(out1) = tx_builder.outputs.get_mut(last_index) { + out1.update_amount(amount_outputs, amount_left); + } + + if let Some(out2) = tx_builder.outputs.get_mut(last_index - 1) { + out2.update_amount(amount_outputs, amount_left); + } + } else { // still at least possible to add one CoinJoin.GetCollateralAmount() output + case = 3; + // , see TransactionRecord::decomposeTransaction + // Out1 CoinJoin.isCollateralAmount() + // Out2 Skipped + + tx_builder.add_zero_output(); + let amount_left = tx_builder.amount_left(); + + if let Some(out) = tx_builder.outputs.last_mut() { + out.update_amount(amount_left, amount_left); + assert!(CoinJoin::is_collateral_amount(out.amount)); + } + } + + log_info!(target: "CoinJoin", "Done with case {}: {}", case, tx_builder); + assert!(TransactionBuilder::is_dust(tx_builder.amount_left())); + + let mut str_result = String::new(); + + if !tx_builder.commit(&mut str_result, false, self.id) { + log_error!(target: "CoinJoin", "Commit failed: {}", str_result); + return false; + } + + client_manager.updated_success_block(); + log_info!(target: "CoinJoin", "commited MakeCollateralInputs tx, str_result: {}", str_result); + + return true; + } + + + pub fn set_status(&mut self, pool_status: PoolStatus) { + self.str_auto_denom_result = CoinJoin::get_status_message(pool_status).to_string(); + + if pool_status.is_error() { + log_error!(target: "CoinJoin", "Session {:?} has an error: {}", self.id, self.str_auto_denom_result); + } else if pool_status.is_warning() { + log_warn!(target: "CoinJoin", "Session {:?} has a warning: {}", self.id, self.str_auto_denom_result); + } + + self.base_session.status = pool_status; + + if pool_status.should_stop() { + log_debug!(target: "CoinJoin", "Session has nothing to do: {:?}", pool_status); + self.has_nothing_to_do = true; + } + } + + pub fn check_timeout(&mut self) -> bool { + match self.base_session.state { + PoolState::Idle => return false, + PoolState::Error => { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + if self.base_session.time_last_successful_step + 10 >= current_time { + // reset after being in POOL_STATE_ERROR for 10 or more seconds + self.set_null(); + } + return false; + }, + _ => {} + } + + let lag_time = 10; // give the server a few extra seconds before resetting. + let timeout = match self.base_session.state { + PoolState::Signing => COINJOIN_SIGNING_TIMEOUT, + _ => COINJOIN_QUEUE_TIMEOUT, + }; + + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + let is_timeout = current_time >= self.base_session.time_last_successful_step + timeout + lag_time; + + if !is_timeout { + return false; + } + + log_warn!(target: "CoinJoin", "connect: {} {} timed out ({})", + if self.base_session.state == PoolState::Signing { "Signing at session" } else { "Session" }, + self.id, timeout); + + self.queue_session_lifecycle_listeners(true, self.base_session.state, PoolMessage::ErrTimeout, PoolStatus::Timeout); + self.base_session.state = PoolState::Error; + self.set_status(PoolStatus::Timeout); + self.unlock_coins(); + self.key_holder_storage.return_all(); + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.base_session.time_last_successful_step = current_time; + self.str_last_message = CoinJoin::get_message_by_id(PoolMessage::ErrSession).to_string(); + + return true; + } + + pub fn is_mixing_fee_tx(&self, tx_id: UInt256) -> bool { + return self.collateral_session_map.contains_key(&tx_id); + } + + fn unlock_coins(&mut self) { + if !self.options.borrow().enable_coinjoin { + return; + } + + // TODO (DashJ): should we wait here? check Dash Core code + + for outpoint in &self.outpoints_locked { + println!("[RUST] CoinJoin: unlock_coin by session: {:?}: {:?}", self.id, outpoint); + self.mixing_wallet.borrow_mut().unlock_coin(outpoint); + } + + self.outpoints_locked.clear(); + println!("[RUST] CoinJoin: session outpoints_locked: {:?}", self.outpoints_locked.iter().map(|x| x.hash.reversed().to_string()).collect::>()); + } + + fn set_null(&mut self) { + if let Some(dmn) = self.mixing_masternode.clone() { + if self.mixing_wallet.borrow().is_masternode_or_disconnect_requested(dmn.socket_address) { + if !self.mixing_wallet.borrow().disconnect_masternode(dmn.socket_address) { + log_info!(target: "CoinJoin", "not closing existing masternode: {}", dmn.socket_address); + } + } else { + log_info!(target: "CoinJoin", "not closing masternode since it is not found: {}", dmn.socket_address); + } + } + + self.mixing_masternode = None; + self.pending_dsa_request = None; + self.base_session.set_null(); + } + + fn create_collateral_transaction(&mut self, str_reason: &mut String) -> bool { + let mut coin_control = CoinControl::new(); + coin_control.coin_type = CoinType::OnlyCoinJoinCollateral; + let coins = self.mixing_wallet.borrow_mut().available_coins(true, coin_control); + + if coins.is_empty() { + str_reason.push_str("CoinJoin requires a collateral transaction and could not locate an acceptable input!"); + return false; + } + + let mut rng = rand::thread_rng(); + let txout = &coins[rng.gen_range(0..coins.len())]; + let inputs = vec![TransactionInput { + input_hash: txout.tx_outpoint.hash, + index: txout.tx_outpoint.index, + script: None, + signature: Some(Vec::new()), + sequence: TXIN_SEQUENCE + }]; + let mut tx_collateral = Transaction { + inputs: inputs, + outputs: Vec::new(), + lock_time: 0, + version: 1, + tx_hash: None, + tx_type: TransactionType::Classic, + payload_offset: 0, + block_height: TX_UNCONFIRMED as u32 + }; + + // pay collateral charge in fees + // NOTE: no need for protobump patch here, + // CCoinJoin::IsCollateralAmount in GetCollateralTxDSIn should already take care of this + if txout.output.amount >= CoinJoin::get_collateral_amount() * 2 { + // make our change address + let mut reserve_destination = ReserveDestination::new(self.mixing_wallet.clone()); + + if let Some(dest) = reserve_destination.get_reserved_destination(true) { + reserve_destination.keep_destination(); + // return change + tx_collateral.outputs.push( + TransactionOutput { + amount: txout.output.amount.saturating_sub(CoinJoin::get_collateral_amount()), + script: Some(dest), + address: None + } + ); + } + } else { // txout.nValue < CCoinJoin::GetCollateralAmount() * 2 + // create dummy data output only and pay everything as a fee + tx_collateral.outputs.push( + TransactionOutput { + amount: 0, + script: Some(vec![OP_RETURN.into_u8()]), + address: None + } + ); + } + + log_info!(target: "CoinJoin", "sign collateral"); + log_debug!(target: "CoinJoin", "tx_collateral={:?}", tx_collateral); + if let Some(signed_tx) = self.mixing_wallet.borrow().sign_transaction(&tx_collateral, false) { + if let Some(tx_id) = signed_tx.tx_hash { + self.tx_my_collateral = Some(signed_tx); + self.is_my_collateral_valid = true; + + if !self.collateral_session_map.contains_key(&tx_id) { + self.collateral_session_map.insert(tx_id, 0); + } + } + + return true; + } + + log_warn!(target: "CoinJoin", "Unable to sign collateral transaction!"); + str_reason.push_str("Unable to sign collateral transaction!"); + + return false; + } + + fn join_existing_queue(&mut self, client_manager: &mut CoinJoinClientManager, balance_needs_anonymized: u64) -> bool { + if !self.options.borrow().enable_coinjoin { + return false; + } + + let mn_list = client_manager.get_mn_list();// TODO: at_chain_tip ? + let queue_manager_rc = self.queue_manager.clone(); + let mut queue_manager = queue_manager_rc.borrow_mut(); + let mut dsq_option = queue_manager.get_queue_item_and_try(); + + while let Some(dsq) = dsq_option.clone() { + let dmn = mn_list.masternode_for(dsq.pro_tx_hash.reversed()); + + match (dmn, self.tx_my_collateral.clone()) { + (None, _) => { + log_info!(target: "CoinJoin", "masternode is not in masternode list, masternode={}", dsq.pro_tx_hash); + dsq_option = queue_manager.get_queue_item_and_try(); + continue; + }, + (Some(dmn), Some(tx)) => { + log_debug!(target: "CoinJoin", "trying existing queue: {}", dsq); + let mut vec_tx_dsin_tmp = Vec::new(); + + if !self.mixing_wallet.borrow_mut().select_tx_dsins_by_denomination(dsq.denomination, balance_needs_anonymized, &mut vec_tx_dsin_tmp) { + log_debug!(target: "CoinJoin", "couldn't match denomination {} ({})", dsq.denomination, CoinJoin::denomination_to_string(dsq.denomination)); + dsq_option = queue_manager.get_queue_item_and_try(); + continue; + } + + client_manager.add_used_masternode(dsq.pro_tx_hash); + + if self.mixing_wallet.borrow().is_masternode_or_disconnect_requested(dmn.socket_address) { + log_debug!(target: "CoinJoin", "skipping masternode connection, addr={}", dmn.socket_address); + dsq_option = queue_manager.get_queue_item_and_try(); + continue; + } + + self.base_session.session_denom = dsq.denomination; + self.mixing_masternode = Some(dmn.clone()); + self.pending_dsa_request = Some(PendingDsaRequest::new( + dmn.socket_address, + CoinJoinAcceptMessage::new( + self.base_session.session_denom, + tx + ) + )); + self.mixing_wallet.borrow_mut().add_pending_masternode(dmn.provider_registration_transaction_hash, self.id); + self.base_session.state = PoolState::Queue; + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.base_session.time_last_successful_step = current_time; + log_info!(target: "CoinJoin", "join existing queue -> pending connection, sessionDenom: {} ({}), addr={}", + self.base_session.session_denom, CoinJoin::denomination_to_string(self.base_session.session_denom), dmn.socket_address); + self.set_status(PoolStatus::Connecting); + self.joined = true; + return true; + } + (Some(_), None) => { + log_warn!(target: "CoinJoin", "tx_collateral is missing"); + } + } + } + + self.set_status(PoolStatus::WarnNoMixingQueues); + return false; + } + + fn start_new_queue(&mut self, client_manager: &mut CoinJoinClientManager, balance_needs_anonymized: u64) -> bool { + if !self.options.borrow().enable_coinjoin { + return false; + } + if balance_needs_anonymized <= 0 { + return false; + } + + let mut tries = 0; + let mn_list = client_manager.get_mn_list(); + let mn_count = mn_list.masternodes.values().filter(|x| x.is_valid).count(); + let mut set_amounts = HashSet::new(); + + if !self.mixing_wallet.borrow_mut().select_denominated_amounts(balance_needs_anonymized, &mut set_amounts) { + if !self.last_create_denominated_result { + self.set_status(PoolStatus::ErrNoInputs); + } + return false; + } + + while tries < 10 { + let dmn = client_manager.get_random_not_used_masternode(); + + match dmn { + None => { + self.set_status(PoolStatus::ErrMasternodeNotFound); + return false; + }, + Some(dmn) => { + client_manager.add_used_masternode(dmn.provider_registration_transaction_hash); + + { + let metadata_manager = &mut self.queue_manager.borrow_mut().masternode_metadata_manager; + let last_dsq = metadata_manager.get_meta_info(dmn.provider_registration_transaction_hash, true).unwrap().last_dsq; + let dsq_threshold = metadata_manager.get_dsq_threshold(dmn.provider_registration_transaction_hash, mn_count as u64); + + if last_dsq != 0 && dsq_threshold > metadata_manager.dsq_count { + log_warn!(target: "CoinJoin", "warning: Too early to mix on this masternode! masternode={} addr={} nLastDsq={} nDsqThreshold={} nDsqCount={}", + dmn.provider_registration_transaction_hash, dmn.socket_address, last_dsq, dsq_threshold, metadata_manager.dsq_count); + tries += 1; + continue; + } + + if self.mixing_wallet.borrow_mut().is_masternode_or_disconnect_requested(dmn.socket_address) { + log_warn!(target: "CoinJoin", "warning: skipping masternode connection, addr={}", dmn.socket_address); + tries += 1; + continue; + } + } + + log_info!(target: "CoinJoin", "attempt {} connection to masternode {}, protx: {}", tries + 1, dmn.socket_address, dmn.provider_registration_transaction_hash); + + while self.base_session.session_denom == 0 { + for amount in &set_amounts { + if set_amounts.len() > 1 && rand::random::() { + continue; + } + self.base_session.session_denom = CoinJoin::amount_to_denomination(*amount); + break; + } + } + + self.mixing_masternode = Some(dmn.clone()); + self.mixing_wallet.borrow_mut().add_pending_masternode(dmn.provider_registration_transaction_hash, self.id); + self.pending_dsa_request = Some(PendingDsaRequest::new( + dmn.socket_address, + CoinJoinAcceptMessage::new( + self.base_session.session_denom, + self.tx_my_collateral.clone().unwrap() + ) + )); + self.base_session.state = PoolState::Queue; + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.base_session.time_last_successful_step = current_time; + log_info!(target: "CoinJoin", "start new queue -> pending connection, sessionDenom: {} ({}), addr={}", + self.base_session.session_denom, CoinJoin::denomination_to_string(self.base_session.session_denom), dmn.socket_address); + self.mixing_wallet.borrow_mut().start_manager_async(); + self.set_status(PoolStatus::Connecting); + self.joined = false; + + return true; + } + } + } + self.str_auto_denom_result = "Failed to start a new mixing queue".to_string(); + return false; + } + + /// As a client, submit part of a future mixing transaction to a Masternode to start the process + pub fn submit_denominate(&mut self) -> bool { + let mut str_error = String::new(); + let mut vec_tx_dsin = Vec::new(); + let mut vec_psin_out_pairs_tmp = Vec::new(); + + if !self.select_denominate(&mut str_error, &mut vec_tx_dsin) { + log_error!(target: "CoinJoin", "SelectDenominate failed, error: {}", str_error); + return false; + } + + let mut vec_inputs_by_rounds = Vec::new(); + let rounds = self.options.borrow().coinjoin_rounds; + let coinjoin_rounds = self.options.borrow().coinjoin_rounds; + let random_rounds = self.options.borrow().coinjoin_random_rounds; + + for i in 0..(rounds + random_rounds) { + if self.prepare_denominate(i, i, &mut str_error, &vec_tx_dsin, &mut vec_psin_out_pairs_tmp, true) { + log_debug!(target: "CoinJoin", "Running CoinJoin denominate for {} rounds, success", i); + vec_inputs_by_rounds.push((i, vec_psin_out_pairs_tmp.len())); + } + } + + // more inputs first, for equal input count prefer the one with fewer rounds + vec_inputs_by_rounds.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0))); + let rounds = vec_inputs_by_rounds[0].0; + + if self.prepare_denominate(rounds, rounds, &mut str_error, &vec_tx_dsin, &mut vec_psin_out_pairs_tmp, false) { + log_debug!(target: "CoinJoin", "Running CoinJoin denominate for {} rounds, success", rounds); + return self.send_denominate(vec_psin_out_pairs_tmp); + } + + // We failed? That's strange but let's just make final attempt and try to mix everything + if self.prepare_denominate(0, coinjoin_rounds - 1, &mut str_error, &vec_tx_dsin, &mut vec_psin_out_pairs_tmp, false) { + log_debug!(target: "CoinJoin", "Running CoinJoin denominate for all rounds, success"); + return self.send_denominate(vec_psin_out_pairs_tmp); + } + + // Should never actually get here but just in case + log_debug!(target: "CoinJoin", "Running CoinJoin denominate for all rounds, error: {}", str_error); + self.str_auto_denom_result = str_error; + + return false; + } + + /// step 0: select denominated inputs and txouts + fn select_denominate(&mut self, str_error_ret: &mut String, vec_tx_dsin_ret: &mut Vec) -> bool { + if !self.options.borrow().enable_coinjoin { + return false; + } + + // if sm_wallet.IsLocked(true) { TODO: recheck + // str_error_ret.push_str("Wallet locked, unable to create transaction!"); + // return false; + // } + + if self.base_session.entries.len() > 0 { + str_error_ret.push_str("Already have pending entries in the CoinJoin pool "); + return false; + } + + vec_tx_dsin_ret.clear(); + let selected = self.mixing_wallet.borrow_mut().select_tx_dsins_by_denomination( + self.base_session.session_denom, + CoinJoin::max_pool_amount(), + vec_tx_dsin_ret + ); + + if !selected { + str_error_ret.push_str( + &format!("Can't select current denominated inputs: {} for session {} ", + CoinJoin::denomination_to_amount(self.base_session.session_denom).to_friendly_string(), self.base_session.session_id) + ); + + for input in vec_tx_dsin_ret.iter() { + str_error_ret.push_str(&format!("\n{:?} ", input)); + } + + return false; + } + + return true; + } + + /// step 1: prepare denominated inputs and outputs + fn prepare_denominate( + &mut self, + min_rounds: i32, + max_rounds: i32, + str_error_ret: &mut String, + vec_tx_dsin: &Vec, + vec_psin_out_pairs_ret: &mut Vec<(CoinJoinTransactionInput, TransactionOutput)>, + dry_run: bool + ) -> bool { + if !CoinJoin::is_valid_denomination(self.base_session.session_denom) { + str_error_ret.push_str("Incorrect session denom"); + return false; + } + let denom_amount = CoinJoin::denomination_to_amount(self.base_session.session_denom); + + let mut steps = 0; + vec_psin_out_pairs_ret.clear(); + + for entry in vec_tx_dsin.iter() { + if steps >= COINJOIN_ENTRY_MAX_SIZE { + break; + } + + if entry.rounds < min_rounds || entry.rounds > max_rounds { + continue; + } + + let script_denom; + if dry_run { + script_denom = Some(vec![]); + } else { + // randomly skip some inputs when we have at least one of the same denom already + if steps >= 1 && rand::thread_rng().gen_range(0..5) == 0 { + steps += 1; + continue; + } + + script_denom = self.key_holder_storage.add_key(self.mixing_wallet.clone()); + } + vec_psin_out_pairs_ret.push((entry.clone(), TransactionOutput { amount: denom_amount as u64, script: script_denom, address: None } )); + steps += 1; + } + + if vec_psin_out_pairs_ret.is_empty() { + self.key_holder_storage.return_all(); + str_error_ret.push_str("Can't prepare current denominated outputs"); + return false; + } + + if !dry_run { + for pair in vec_psin_out_pairs_ret.iter() { + self.mixing_wallet.borrow_mut().lock_coin(pair.0.outpoint()); + self.outpoints_locked.push(pair.0.outpoint()); + println!("[RUST] CoinJoin: locked coin by session {:?}: {:?}", self.id, pair.0.outpoint()); + println!("[RUST] CoinJoin: session outpoints_locked: {:?}", self.outpoints_locked.iter().map(|x| x.hash.reversed().to_string()).collect::>()); + } + } + + true + } + + /// step 2: send denominated inputs and outputs prepared in step 1 + fn send_denominate(&mut self, vec_psin_out_pairs: Vec<(CoinJoinTransactionInput, TransactionOutput)>) -> bool { + if self.tx_my_collateral.is_none() || self.tx_my_collateral.as_ref().unwrap().inputs.is_empty() { + log_warn!(target: "CoinJoin", "CoinJoin collateral not set"); + return false; + } + + // we should already be connected to a Masternode + if self.base_session.session_id == 0 { + log_warn!(target: "CoinJoin", " No Masternode has been selected yet."); + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + + return false; + } + + self.base_session.state = PoolState::AcceptingEntries; + self.str_auto_denom_result = String::new(); + + let mut tx = Transaction { // for debug purposes only + inputs: vec![], + outputs: vec![], + lock_time: 0, + version: 0, + tx_hash: None, + tx_type: TransactionType::Classic, + payload_offset: 0, + block_height: 0, + }; + let mut vec_tx_dsin_tmp = Vec::new(); + let mut vec_tx_out_tmp = Vec::new(); + + for pair in vec_psin_out_pairs { + vec_tx_dsin_tmp.push(pair.0.txin.clone()); + vec_tx_out_tmp.push(pair.1.clone()); + tx.inputs.push(pair.0.txin); + tx.outputs.push(pair.1); + } + + log_info!(target: "CoinJoin", "Submitting partial tx to session {}", self.base_session.session_id); + log_debug!(target: "CoinJoin", "tx={:?}", tx); + + // Store our entry for later use + let entry = CoinJoinEntry { + mixing_inputs: vec_tx_dsin_tmp, + mixing_outputs: vec_tx_out_tmp, + tx_collateral: self.tx_my_collateral.as_ref().unwrap().clone() + }; + self.base_session.entries.push(entry.clone()); + self.relay(&entry); + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.base_session.time_last_successful_step = current_time; + + return true; + } + + fn relay(&self, entry: &CoinJoinEntry) { + if let Some(mn) = self.mixing_masternode.as_ref() { + let mut buffer = vec![]; + entry.consensus_encode(&mut buffer).unwrap(); + + if !self.mixing_wallet.borrow_mut().send_message(buffer, entry.get_message_type(), &mn.socket_address, true) { + log_info!(target: "CoinJoin", "failed to send dsi to {}", mn.socket_address); + } + } + } + + pub fn process_message(&mut self, peer: &SocketAddress, message: &CoinJoinMessage) -> bool { + match message { + CoinJoinMessage::StatusUpdate(status_update) => { + self.process_status_update(peer, status_update); + return false; + }, + CoinJoinMessage::FinalTransaction(final_tx) => { + self.process_final_transaction(peer, final_tx); + return false; + }, + CoinJoinMessage::Complete(complete) => { + return self.process_complete(peer, complete); + }, + _ => { return false } + } + } + + pub fn reset_pool(&mut self) { + self.tx_my_collateral = None; + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + } + + fn process_status_update(&mut self, peer: &SocketAddress, status_update: &CoinJoinStatusUpdate) { + if self.mixing_masternode.is_none() { + return; + } + + if self.mixing_masternode.as_ref().unwrap().socket_address != *peer { + return; + } + + self.process_pool_state_update(peer, status_update); + } + + /// Process Masternode updates about the progress of mixing + fn process_pool_state_update(&mut self, peer: &SocketAddress, status_update: &CoinJoinStatusUpdate) { + log_debug!(target: "CoinJoin", "status update received: {:?} from {}", status_update, peer); + + // do not update state when mixing client state is one of these + if self.base_session.state == PoolState::Idle || self.base_session.state == PoolState::Error { + return; + } + + if status_update.pool_state < PoolState::pool_state_min() || status_update.pool_state > PoolState::pool_state_max() { + return; + } + + if status_update.message_id < PoolMessage::msg_pool_min() || status_update.message_id > PoolMessage::msg_pool_max() { + return; + } + + let mut str_message_tmp = CoinJoin::get_message_by_id(status_update.message_id).to_string(); + self.str_auto_denom_result = format!("Masternode: {}", str_message_tmp); + + match status_update.status_update { + PoolStatusUpdate::Rejected => { + log_info!(target: "CoinJoin", "session: rejected by Masternode {}: {}", peer, str_message_tmp); + self.base_session.state = PoolState::Error; + self.unlock_coins(); + self.key_holder_storage.return_all(); + + match status_update.message_id { + PoolMessage::ErrInvalidCollateral => { + if let Some(collateral) = &self.tx_my_collateral { + log_warn!(target: "CoinJoin", "is collateral valid: {}", self.coinjoin.borrow().is_collateral_valid(collateral, true)); + } else { + log_warn!(target: "CoinJoin", "collateral invalid, tx_my_collateral is None"); + } + + self.is_my_collateral_valid = false; + self.set_null(); // for now lets disconnect. TODO(DashJ): Why is the collateral invalid? + }, + _ => { + log_warn!(target: "CoinJoin", "rejected for other reasons"); + } + } + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + self.base_session.time_last_successful_step = current_time; + self.str_last_message = str_message_tmp; + }, + PoolStatusUpdate::Accepted => { + if let Some(collateral) = &self.tx_my_collateral { + if self.base_session.state == status_update.pool_state && + status_update.pool_state == PoolState::Queue && + self.base_session.session_id == 0 && + status_update.session_id != 0 + { + // new session id should be set only in POOL_STATE_QUEUE state + self.base_session.session_id = status_update.session_id; + self.collateral_session_map.insert(collateral.tx_hash.unwrap(), status_update.session_id); + self.base_session.time_last_successful_step = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + str_message_tmp = format!("{} Set SID to {}", str_message_tmp, status_update.session_id); + self.queue_session_lifecycle_listeners(false, self.base_session.state, PoolMessage::MsgSuccess, PoolStatus::Mixing); + } + + log_debug!(target: "CoinJoin", "session: accepted by Masternode: {}", str_message_tmp); + } else { + log_warn!(target: "CoinJoin", "collateral accepted but tx_my_collateral is None"); + } + } + } + } + + fn process_complete(& mut self, peer: &SocketAddress, complete_message: &CoinJoinCompleteMessage) -> bool { + if self.mixing_masternode.is_none() { + return false; + } + + if self.mixing_masternode.as_ref().unwrap().socket_address != *peer { + return false; + } + + if complete_message.msg_message_id < PoolMessage::msg_pool_min() || complete_message.msg_message_id > PoolMessage::msg_pool_max() { + log_warn!(target: "CoinJoin", "msgID is out of bounds: {:?}", complete_message.msg_message_id); + return false; + } + + if self.base_session.session_id != complete_message.msg_session_id { + log_warn!(target: "CoinJoin", "message doesn't match current CoinJoin session: SID: {} msgID: {} ({})", + self.base_session.session_id, complete_message.msg_session_id, CoinJoin::get_message_by_id(complete_message.msg_message_id)); + return false; + } + + log_info!(target: "CoinJoin", "msgSID {} msg {:?} ({})", complete_message.msg_session_id, + complete_message.msg_message_id, CoinJoin::get_message_by_id(complete_message.msg_message_id)); + + return self.completed_transaction(complete_message.msg_message_id); + } + + fn completed_transaction(&mut self, message_id: PoolMessage) -> bool { + let mut update_success_block = false; + + if message_id == PoolMessage::MsgSuccess { + log_info!(target: "CoinJoin", "completedTransaction -- success"); + self.queue_session_lifecycle_listeners(true, self.base_session.state, PoolMessage::MsgSuccess, PoolStatus::Complete); + self.key_holder_storage.keep_all(); + update_success_block = true; + } else { + log_error!(target: "CoinJoin", "completedTransaction -- error"); + self.key_holder_storage.return_all(); + } + + self.unlock_coins(); + self.set_null(); + self.set_status(PoolStatus::Complete); + self.str_last_message = CoinJoin::get_message_by_id(message_id).to_string(); + + return update_success_block; + } + + fn process_final_transaction(&mut self, peer: &SocketAddress, final_tx: &CoinJoinFinalTransaction) { + if self.mixing_masternode.is_none() { + return; + } + + if self.mixing_masternode.as_ref().unwrap().socket_address != *peer { + return; + } + + if self.base_session.session_id != final_tx.msg_session_id { + log_warn!(target: "CoinJoin", "DSFINALTX: message doesn't match current CoinJoin session: sessionID: {} msgSessionID: {}", + self.base_session.session_id, final_tx.msg_session_id); + return; + } + + log_debug!(target: "CoinJoin", "DSFINALTX: txNew {:?}", final_tx.tx); + + // check to see if input is spent already? (and probably not confirmed) + self.sign_final_transaction(&final_tx.tx, peer); + } + + /// As a client, check and sign the final transaction + fn sign_final_transaction(&mut self, final_transaction_new: &Transaction, peer: &SocketAddress) { + if !self.options.borrow().enable_coinjoin { + return; + } + + if self.mixing_masternode.is_none() { + return; + } + + let mut final_mutable_transaction = final_transaction_new.clone(); + + // STEP 1: check final transaction general rules + + // Make sure it's BIP69 compliant + final_mutable_transaction.inputs.sort_by(Self::compare_input_bip69); + final_mutable_transaction.outputs.sort_by(Self::compare_output_bip69); + + if UInt256::sha256d(final_mutable_transaction.to_data()) != UInt256::sha256d(final_transaction_new.to_data()) { + log_error!(target: "CoinJoin", "Masternode {} is not BIP69 compliant!", self.mixing_masternode.as_ref().unwrap().provider_registration_transaction_hash); + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + return; + } + + // Make sure all inputs/outputs are valid + let is_valid_ins_outs = self.base_session.is_valid_in_outs(&final_mutable_transaction.inputs, &final_mutable_transaction.outputs); + + if !is_valid_ins_outs.result { + log_error!(target: "CoinJoin", "IsValidInOuts() failed: {}", CoinJoin::get_message_by_id(is_valid_ins_outs.message_id)); + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + return; + } + + // STEP 2: make sure our own inputs/outputs are present, otherwise refuse to sign + let mut coins = vec![]; + + for entry in &self.base_session.entries { + // Check that the final transaction has all our outputs + for txout in &entry.mixing_outputs { + let found = final_mutable_transaction.outputs.iter().any(|txout_final| + txout_final.amount == txout.amount && txout_final.script == txout.script + ); + + if !found { + // Something went wrong and we'll refuse to sign. It's possible we'll be charged collateral. But that's + // better than signing if the transaction doesn't look like what we wanted. + log_warn!(target: "CoinJoin", "an output is missing, refusing to sign!"); + log_debug!(target: "CoinJoin", "txout={:?}", txout); + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + return; + } + } + + for txin in &entry.mixing_inputs { + /* Sign my transaction and all outputs */ + let mut my_input_index: Option = None; + + for (i, txin_final) in final_mutable_transaction.inputs.iter().enumerate() { + if txin_final.input_hash == txin.input_hash && txin_final.index == txin.index { + my_input_index = Some(i); + break; + } + } + + if let Some(index) = my_input_index { + let input = final_mutable_transaction.inputs[index].clone(); + // add a pair with an empty value + coins.push(input); + } else { + // Can't find one of my own inputs, refuse to sign. It's possible we'll be charged collateral. But that's + // better than signing if the transaction doesn't look like what we wanted. + log_warn!(target: "CoinJoin", "missing input!"); + log_debug!(target: "CoinJoin", "txdsin={:?}", txin); + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + return; + } + } + } + + let signed_tx = self.mixing_wallet.borrow_mut().sign_transaction(&final_mutable_transaction, true); + + if let Some(tx) = signed_tx { + let mut signed_inputs = vec![]; + + for txin in &tx.inputs { + if coins.iter().any(|coin| coin.input_hash == txin.input_hash && coin.index == txin.index) { + signed_inputs.push(txin.clone()); + } + } + + if signed_inputs.is_empty() { + log_warn!(target: "CoinJoin", "can't sign anything!"); + self.unlock_coins(); + self.key_holder_storage.return_all(); + self.set_null(); + return; + } + + // push all of our signatures to the Masternode + let message = CoinJoinSignedInputs { inputs: signed_inputs }; + log_info!(target: "CoinJoin", "pushing signed inputs to the masternode"); + log_debug!(target: "CoinJoin", "{}", message); + let mut buffer = vec![]; + message.consensus_encode(&mut buffer).unwrap(); + self.mixing_wallet.borrow_mut().send_message(buffer, message.get_message_type(), peer, true); + self.base_session.state = PoolState::Signing; + self.base_session.time_last_successful_step = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + } else { + log_warn!(target: "CoinJoin", "sign_transaction returned false for the tx"); + } + } + + fn compare_input_bip69(a: &TransactionInput, b: &TransactionInput) -> Ordering { + if a.input_hash == b.input_hash { + return a.index.cmp(&b.index); + } + + let rev_hash_a: Vec = a.input_hash.reversed().0.to_vec(); + let rev_hash_b: Vec = b.input_hash.reversed().0.to_vec(); + + rev_hash_a.cmp(&rev_hash_b) + } + + fn compare_output_bip69(a: &TransactionOutput, b: &TransactionOutput) -> Ordering { + match a.amount.cmp(&b.amount) { + Ordering::Equal => a.script.cmp(&b.script), + other => other, + } + } + + fn queue_session_lifecycle_listeners(&self, is_complete: bool, state: PoolState, pool_message: PoolMessage, pool_status: PoolStatus) { + if is_complete { + log_debug!(target: "CoinJoin", "session completed: {}, pool_status: {:?}", self.id, pool_status); + log_debug!(target: "CoinJoin", "outpoints_locked"); + for coin in self.outpoints_locked.iter() { + log_debug!(target: "CoinJoin", "outpoint_locked: {:?}", coin); + } + } + + unsafe { + let boxed_session_id = boxed(self.id.0); + let boxed_mixing_mn_ip = if let Some(mn) = self.mixing_masternode.as_ref() { + boxed(mn.socket_address.ip_address.0) + } else { + boxed(UInt128::ip_address_from_u32(0).0) + }; + + (self.session_lifecycle_listener)( + is_complete, + self.base_session.session_id, + boxed_session_id, + self.base_session.session_denom, + state, + pool_message, + pool_status, + boxed_mixing_mn_ip, + self.joined, + self.context + ); + + unbox_any(boxed_session_id); + unbox_any(boxed_mixing_mn_ip); + } + } +} diff --git a/dash-spv-coinjoin/src/constants.rs b/dash-spv-coinjoin/src/constants.rs new file mode 100644 index 00000000..2872fbbb --- /dev/null +++ b/dash-spv-coinjoin/src/constants.rs @@ -0,0 +1,46 @@ +use dash_spv_masternode_processor::chain::params::{MAX_MONEY, DUFFS}; + +pub const COINJOIN_AUTO_TIMEOUT_MIN: i32 = 5; +pub const COINJOIN_AUTO_TIMEOUT_MAX: i32 = 15; + +pub const COINJOIN_QUEUE_TIMEOUT: u64 = 30; +pub const COINJOIN_SIGNING_TIMEOUT: u64 = 15; + +pub const COINJOIN_ENTRY_MAX_SIZE: u64 = 9; + +pub const MIN_COINJOIN_SESSIONS: i32 = 1; +pub const MIN_COINJOIN_ROUNDS: i32 = 2; +pub const MIN_COINJOIN_AMOUNT: i32 = 2; +pub const MIN_COINJOIN_DENOMS_GOAL: i32 = 10; +pub const MIN_COINJOIN_DENOMS_HARDCAP: i32 = 10; +pub const MAX_COINJOIN_SESSIONS: i32 = 10; +pub const MAX_COINJOIN_ROUNDS: i32 = 16; +pub const MAX_COINJOIN_DENOMS_GOAL: i32 = 100000; +pub const MAX_COINJOIN_DENOMS_HARDCAP: i32 = 100000; +pub const MAX_COINJOIN_AMOUNT: u64 = MAX_MONEY / DUFFS; +pub const DEFAULT_COINJOIN_SESSIONS: i32 = 4; +pub const DEFAULT_COINJOIN_ROUNDS: i32 = 4; +pub const DEFAULT_COINJOIN_AMOUNT: i32 = 1000; +pub const DEFAULT_COINJOIN_DENOMS_GOAL: u32 = 50; +pub const DEFAULT_COINJOIN_DENOMS_HARDCAP: u32 = 300; + +// How many new denom outputs to create before we consider the "goal" loop in CreateDenominated +// a final one and start creating an actual tx. Same limit applies for the "hard cap" part of the algo. +// NOTE: We do not allow txes larger than 100kB, so we have to limit the number of outputs here. +// We still want to create a lot of outputs though. +// Knowing that each CTxOut is ~35b big, 400 outputs should take 400 x ~35b = ~17.5kb. +// More than 500 outputs starts to make qt quite laggy. +// Additionally to need all 500 outputs (assuming a max per denom of 50) you'd need to be trying to +// create denominations for over 3000 dash! +pub const COINJOIN_DENOM_OUTPUTS_THRESHOLD: i32 = 500; + +// Warn user if mixing in gui or try to create backup if mixing in daemon mode +// when we have only this many keys left +pub const COINJOIN_KEYS_THRESHOLD_WARNING: i32 = 100; +// Stop mixing completely, it's too dangerous to continue when we have only this many keys left +pub const COINJOIN_KEYS_THRESHOLD_STOP: i32 = 50; +// Pseudorandomly mix up to this many times in addition to base round count +pub const COINJOIN_RANDOM_ROUNDS: i32 = 3; + +// If feePerKb is lower than this, Dash Core will treat it as if there were no fee. +pub const REFERENCE_DEFAULT_MIN_TX_FEE: u64 = 1000; // 0.01 mDASH diff --git a/dash-spv-coinjoin/src/fermented.rs b/dash-spv-coinjoin/src/fermented.rs new file mode 100644 index 00000000..21e83d88 --- /dev/null +++ b/dash-spv-coinjoin/src/fermented.rs @@ -0,0 +1 @@ +# [allow (clippy :: let_and_return , clippy :: suspicious_else_formatting , clippy :: redundant_field_names , dead_code , redundant_semicolons , unused_braces , unused_imports , unused_unsafe , unused_variables , unused_qualifications)] pub mod types { pub mod messages { pub mod coinjoin_status_update { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: messages :: coinjoin_status_update :: CoinJoinStatusUpdate\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct CoinJoinStatusUpdate { pub session_id : i32 , pub pool_state : * mut crate :: fermented :: types :: messages :: pool_state :: PoolState , pub status_update : * mut crate :: fermented :: types :: messages :: pool_status_update :: PoolStatusUpdate , pub message_id : * mut crate :: fermented :: types :: messages :: pool_message :: PoolMessage , } impl ferment_interfaces :: FFIConversion < crate :: messages :: coinjoin_status_update :: CoinJoinStatusUpdate > for CoinJoinStatusUpdate { unsafe fn ffi_from_const (ffi : * const CoinJoinStatusUpdate) -> crate :: messages :: coinjoin_status_update :: CoinJoinStatusUpdate { let ffi_ref = & * ffi ; crate :: messages :: coinjoin_status_update :: CoinJoinStatusUpdate { session_id : ffi_ref . session_id , pool_state : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . pool_state) , status_update : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . status_update) , message_id : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . message_id) , } } unsafe fn ffi_to_const (obj : crate :: messages :: coinjoin_status_update :: CoinJoinStatusUpdate) -> * const CoinJoinStatusUpdate { ferment_interfaces :: boxed (CoinJoinStatusUpdate { session_id : obj . session_id , pool_state : ferment_interfaces :: FFIConversion :: ffi_to (obj . pool_state) , status_update : ferment_interfaces :: FFIConversion :: ffi_to (obj . status_update) , message_id : ferment_interfaces :: FFIConversion :: ffi_to (obj . message_id) , }) } unsafe fn destroy (ffi : * mut CoinJoinStatusUpdate) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for CoinJoinStatusUpdate { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ferment_interfaces :: unbox_any (ffi_ref . pool_state) ; ; ferment_interfaces :: unbox_any (ffi_ref . status_update) ; ; ferment_interfaces :: unbox_any (ffi_ref . message_id) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinStatusUpdate_ctor (session_id : i32 , pool_state : * mut crate :: fermented :: types :: messages :: pool_state :: PoolState , status_update : * mut crate :: fermented :: types :: messages :: pool_status_update :: PoolStatusUpdate , message_id : * mut crate :: fermented :: types :: messages :: pool_message :: PoolMessage) -> * mut CoinJoinStatusUpdate { ferment_interfaces :: boxed (CoinJoinStatusUpdate { session_id , pool_state , status_update , message_id , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinStatusUpdate_destroy (ffi : * mut CoinJoinStatusUpdate) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod coinjoin_final_transaction { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: messages :: coinjoin_final_transaction :: CoinJoinFinalTransaction\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct CoinJoinFinalTransaction { pub msg_session_id : i32 , pub tx : * mut dash_spv_masternode_processor :: tx :: transaction :: Transaction , } impl ferment_interfaces :: FFIConversion < crate :: messages :: coinjoin_final_transaction :: CoinJoinFinalTransaction > for CoinJoinFinalTransaction { unsafe fn ffi_from_const (ffi : * const CoinJoinFinalTransaction) -> crate :: messages :: coinjoin_final_transaction :: CoinJoinFinalTransaction { let ffi_ref = & * ffi ; crate :: messages :: coinjoin_final_transaction :: CoinJoinFinalTransaction { msg_session_id : ffi_ref . msg_session_id , tx : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . tx) , } } unsafe fn ffi_to_const (obj : crate :: messages :: coinjoin_final_transaction :: CoinJoinFinalTransaction) -> * const CoinJoinFinalTransaction { ferment_interfaces :: boxed (CoinJoinFinalTransaction { msg_session_id : obj . msg_session_id , tx : ferment_interfaces :: FFIConversion :: ffi_to (obj . tx) , }) } unsafe fn destroy (ffi : * mut CoinJoinFinalTransaction) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for CoinJoinFinalTransaction { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ferment_interfaces :: unbox_any (ffi_ref . tx) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinFinalTransaction_ctor (msg_session_id : i32 , tx : * mut dash_spv_masternode_processor :: tx :: transaction :: Transaction) -> * mut CoinJoinFinalTransaction { ferment_interfaces :: boxed (CoinJoinFinalTransaction { msg_session_id , tx , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinFinalTransaction_destroy (ffi : * mut CoinJoinFinalTransaction) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod send_coinjoin_queue { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: messages :: send_coinjoin_queue :: SendCoinJoinQueue\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct SendCoinJoinQueue { pub send : bool , } impl ferment_interfaces :: FFIConversion < crate :: messages :: send_coinjoin_queue :: SendCoinJoinQueue > for SendCoinJoinQueue { unsafe fn ffi_from_const (ffi : * const SendCoinJoinQueue) -> crate :: messages :: send_coinjoin_queue :: SendCoinJoinQueue { let ffi_ref = & * ffi ; crate :: messages :: send_coinjoin_queue :: SendCoinJoinQueue { send : ffi_ref . send , } } unsafe fn ffi_to_const (obj : crate :: messages :: send_coinjoin_queue :: SendCoinJoinQueue) -> * const SendCoinJoinQueue { ferment_interfaces :: boxed (SendCoinJoinQueue { send : obj . send , }) } unsafe fn destroy (ffi : * mut SendCoinJoinQueue) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for SendCoinJoinQueue { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn SendCoinJoinQueue_ctor (send : bool) -> * mut SendCoinJoinQueue { ferment_interfaces :: boxed (SendCoinJoinQueue { send , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn SendCoinJoinQueue_destroy (ffi : * mut SendCoinJoinQueue) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod coinjoin_accept_message { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: messages :: coinjoin_accept_message :: CoinJoinAcceptMessage\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct CoinJoinAcceptMessage { pub denomination : u32 , pub tx_collateral : * mut dash_spv_masternode_processor :: tx :: transaction :: Transaction , } impl ferment_interfaces :: FFIConversion < crate :: messages :: coinjoin_accept_message :: CoinJoinAcceptMessage > for CoinJoinAcceptMessage { unsafe fn ffi_from_const (ffi : * const CoinJoinAcceptMessage) -> crate :: messages :: coinjoin_accept_message :: CoinJoinAcceptMessage { let ffi_ref = & * ffi ; crate :: messages :: coinjoin_accept_message :: CoinJoinAcceptMessage { denomination : ffi_ref . denomination , tx_collateral : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . tx_collateral) , } } unsafe fn ffi_to_const (obj : crate :: messages :: coinjoin_accept_message :: CoinJoinAcceptMessage) -> * const CoinJoinAcceptMessage { ferment_interfaces :: boxed (CoinJoinAcceptMessage { denomination : obj . denomination , tx_collateral : ferment_interfaces :: FFIConversion :: ffi_to (obj . tx_collateral) , }) } unsafe fn destroy (ffi : * mut CoinJoinAcceptMessage) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for CoinJoinAcceptMessage { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ferment_interfaces :: unbox_any (ffi_ref . tx_collateral) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinAcceptMessage_ctor (denomination : u32 , tx_collateral : * mut dash_spv_masternode_processor :: tx :: transaction :: Transaction) -> * mut CoinJoinAcceptMessage { ferment_interfaces :: boxed (CoinJoinAcceptMessage { denomination , tx_collateral , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinAcceptMessage_destroy (ffi : * mut CoinJoinAcceptMessage) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod coinjoin_complete_message { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: messages :: coinjoin_complete_message :: CoinJoinCompleteMessage\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct CoinJoinCompleteMessage { pub msg_session_id : i32 , pub msg_message_id : * mut crate :: fermented :: types :: messages :: pool_message :: PoolMessage , } impl ferment_interfaces :: FFIConversion < crate :: messages :: coinjoin_complete_message :: CoinJoinCompleteMessage > for CoinJoinCompleteMessage { unsafe fn ffi_from_const (ffi : * const CoinJoinCompleteMessage) -> crate :: messages :: coinjoin_complete_message :: CoinJoinCompleteMessage { let ffi_ref = & * ffi ; crate :: messages :: coinjoin_complete_message :: CoinJoinCompleteMessage { msg_session_id : ffi_ref . msg_session_id , msg_message_id : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . msg_message_id) , } } unsafe fn ffi_to_const (obj : crate :: messages :: coinjoin_complete_message :: CoinJoinCompleteMessage) -> * const CoinJoinCompleteMessage { ferment_interfaces :: boxed (CoinJoinCompleteMessage { msg_session_id : obj . msg_session_id , msg_message_id : ferment_interfaces :: FFIConversion :: ffi_to (obj . msg_message_id) , }) } unsafe fn destroy (ffi : * mut CoinJoinCompleteMessage) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for CoinJoinCompleteMessage { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ferment_interfaces :: unbox_any (ffi_ref . msg_message_id) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinCompleteMessage_ctor (msg_session_id : i32 , msg_message_id : * mut crate :: fermented :: types :: messages :: pool_message :: PoolMessage) -> * mut CoinJoinCompleteMessage { ferment_interfaces :: boxed (CoinJoinCompleteMessage { msg_session_id , msg_message_id , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinJoinCompleteMessage_destroy (ffi : * mut CoinJoinCompleteMessage) { ferment_interfaces :: unbox_any (ffi) ; } } } pub mod ffi { pub mod input_value { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: ffi :: input_value :: InputValue\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct InputValue { pub is_valid : bool , pub value : u64 , } impl ferment_interfaces :: FFIConversion < crate :: ffi :: input_value :: InputValue > for InputValue { unsafe fn ffi_from_const (ffi : * const InputValue) -> crate :: ffi :: input_value :: InputValue { let ffi_ref = & * ffi ; crate :: ffi :: input_value :: InputValue { is_valid : ffi_ref . is_valid , value : ffi_ref . value , } } unsafe fn ffi_to_const (obj : crate :: ffi :: input_value :: InputValue) -> * const InputValue { ferment_interfaces :: boxed (InputValue { is_valid : obj . is_valid , value : obj . value , }) } unsafe fn destroy (ffi : * mut InputValue) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for InputValue { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn InputValue_ctor (is_valid : bool , value : u64) -> * mut InputValue { ferment_interfaces :: boxed (InputValue { is_valid , value , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn InputValue_destroy (ffi : * mut InputValue) { ferment_interfaces :: unbox_any (ffi) ; } } } } # [allow (clippy :: let_and_return , clippy :: suspicious_else_formatting , clippy :: redundant_field_names , dead_code , redundant_semicolons , unused_braces , unused_imports , unused_unsafe , unused_variables , unused_qualifications)] pub mod generics { } \ No newline at end of file diff --git a/dash-spv-coinjoin/src/ffi/callbacks.rs b/dash-spv-coinjoin/src/ffi/callbacks.rs new file mode 100644 index 00000000..b73af63f --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/callbacks.rs @@ -0,0 +1,185 @@ +use std::ffi::{c_char, c_void}; +use dash_spv_masternode_processor::ffi::ByteArray; +use dash_spv_masternode_processor::types::{self, MasternodeEntry}; +use crate::ffi::selected_coins::SelectedCoins; +use crate::messages::{PoolMessage, PoolState, PoolStatus}; +use crate::wallet_ex::WalletEx; + +use super::coin_control::CoinControl; +use super::coinjoin_keys::CoinJoinKeys; +use super::gathered_outputs::GatheredOutputs; +use super::input_value::InputValue; +use super::recepient::Recipient; + +pub type GetInputValueByPrevoutHash = unsafe extern "C" fn( + prevout_hash: *mut [u8; 32], + index: u32, + context: *const c_void, +) -> *mut InputValue; + +pub type HasChainLock = unsafe extern "C" fn( + block: *mut types::Block, + context: *const c_void, +) -> bool; + +pub type DestroyInputValue = unsafe extern "C" fn( + input_value: *mut InputValue, +); + +pub type GetWalletTransaction = unsafe extern "C" fn( + hash: *mut [u8; 32], + context: *const c_void, +) -> *mut types::Transaction; + +pub type DestroyWalletTransaction = unsafe extern "C" fn( + input_value: *mut types::Transaction, +); + +pub type SignTransaction = unsafe extern "C" fn( + transaction: *mut types::Transaction, + anyone_can_pay: bool, + context: *const c_void +) -> *mut types::Transaction; + +pub type IsMineInput = unsafe extern "C" fn( + prevout_hash: *mut [u8; 32], + index: u32, + context: *const c_void, +) -> bool; + +pub type AvailableCoins = unsafe extern "C" fn( + only_safe: bool, + coin_control: *mut CoinControl, + wallet_ex: &mut WalletEx, + context: *const c_void, +) -> *mut GatheredOutputs; + +pub type DestroyGatheredOutputs = unsafe extern "C" fn( + gathered_outputs: *mut GatheredOutputs, +); + +pub type SelectCoinsGroupedByAddresses = unsafe extern "C" fn( + skip_denominated: bool, + anonymizable: bool, + skip_unconfirmed: bool, + max_oupoints_per_address: i32, + wallet_ex: &mut WalletEx, + context: *const c_void, +) -> *mut SelectedCoins; + +pub type DestroySelectedCoins = unsafe extern "C" fn( + selected_coins: *mut SelectedCoins, +); + +pub type InputsWithAmount = unsafe extern "C" fn( + amount: u64, + context: *const c_void, +) -> u32; + +pub type FreshCoinJoinAddress = unsafe extern "C" fn( + internal: bool, + context: *const c_void, +) -> ByteArray; + +pub type CommitTransaction = unsafe extern "C" fn( + items: *mut *mut Recipient, + item_count: usize, + coin_control: *mut CoinControl, + is_denominating: bool, + client_session_id: *mut [u8; 32], + context: *const c_void +) -> bool; + +pub type MasternodeByHash = unsafe extern "C" fn( + address: *mut [u8; 32], + context: *const c_void, +) -> *mut MasternodeEntry; + +pub type DestroyMasternode = unsafe extern "C" fn( + selected_coins: *mut MasternodeEntry, +); + +pub type ValidMasternodeCount = unsafe extern "C" fn( + context: *const c_void, +) -> u64; + +pub type IsBlockchainSynced = unsafe extern "C" fn( + context: *const c_void, +) -> bool; + +pub type GetMasternodeList = unsafe extern "C" fn( + context: *const c_void, +) -> *mut types::MasternodeList; + +pub type DestroyMasternodeList = unsafe extern "C" fn( + mn_list: *mut types::MasternodeList, +); + +pub type IsMasternodeOrDisconnectRequested = unsafe extern "C" fn( + ip_address: *mut [u8; 16], + port: u16, + context: *const c_void +) -> bool; + +pub type DisconnectMasternode = unsafe extern "C" fn( + ip_address: *mut [u8; 16], + port: u16, + context: *const c_void +) -> bool; + +pub type SendMessage = unsafe extern "C" fn( + message_type: *mut c_char, + message: *mut ByteArray, + ip_address: *mut [u8; 16], + port: u16, + warn: bool, + context: *const c_void +) -> bool; + +pub type AddPendingMasternode = unsafe extern "C" fn( + pro_tx_hash: *mut [u8; 32], + session_id: *mut [u8; 32], + context: *const c_void +) -> bool; + +pub type StartManagerAsync = unsafe extern "C" fn( + context: *const c_void +); + +pub type UpdateSuccessBlock = unsafe extern "C" fn( + context: *const c_void +); + +pub type IsWaitingForNewBlock = unsafe extern "C" fn( + context: *const c_void +) -> bool; + +pub type SessionLifecycleListener = unsafe extern "C" fn( + is_complete: bool, + base_session_id: i32, + client_session_id: *mut [u8; 32], + denomination: u32, + state: PoolState, + message: PoolMessage, + status: PoolStatus, + ip_address: *mut [u8; 16], + joined: bool, + context: *const c_void +); + +pub type MixingLivecycleListener = unsafe extern "C" fn( + is_complete: bool, + is_interrupted: bool, + pool_statuses: *const PoolStatus, + pool_statuses_len: usize, + context: *const c_void +); + +pub type GetCoinJoinKeys = unsafe extern "C" fn( + used: bool, + context: *const c_void +) -> *mut CoinJoinKeys; + +pub type DestroyCoinJoinKeys = unsafe extern "C" fn( + coinjoin_keys: *mut CoinJoinKeys +); diff --git a/dash-spv-coinjoin/src/ffi/coin_control.rs b/dash-spv-coinjoin/src/ffi/coin_control.rs new file mode 100644 index 00000000..0a948060 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/coin_control.rs @@ -0,0 +1,15 @@ +use dash_spv_masternode_processor::ffi::ByteArray; +use crate::models::coin_control::CoinType; +use super::tx_outpoint::TxOutPoint; + +#[repr(C)] +pub struct CoinControl { + pub coin_type: CoinType, + pub min_depth: i32, + pub max_depth: i32, + pub avoid_address_reuse: bool, + pub allow_other_inputs: bool, + pub set_selected: *mut *mut TxOutPoint, + pub set_selected_size: usize, + pub dest_change: *mut ByteArray +} diff --git a/dash-spv-coinjoin/src/ffi/coinjoin_denominations.rs b/dash-spv-coinjoin/src/ffi/coinjoin_denominations.rs new file mode 100644 index 00000000..bc313e11 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/coinjoin_denominations.rs @@ -0,0 +1,5 @@ +#[repr(C)] +pub struct CoinJoinDenominations { + pub denoms: *const u64, + pub length: usize, +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/ffi/coinjoin_keys.rs b/dash-spv-coinjoin/src/ffi/coinjoin_keys.rs new file mode 100644 index 00000000..cce52b87 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/coinjoin_keys.rs @@ -0,0 +1,8 @@ +use dash_spv_masternode_processor::ffi::ByteArray; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinKeys { + pub items: *mut *mut ByteArray, + pub item_count: usize, +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/ffi/coinjoin_session_statuses.rs b/dash-spv-coinjoin/src/ffi/coinjoin_session_statuses.rs new file mode 100644 index 00000000..7d55ae89 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/coinjoin_session_statuses.rs @@ -0,0 +1,7 @@ +use crate::messages::PoolStatus; + +#[repr(C)] +pub struct CoinJoinSessionStatuses { + pub statuses: *const PoolStatus, + pub length: usize, +} diff --git a/dash-spv-coinjoin/src/ffi/compact_tally_item.rs b/dash-spv-coinjoin/src/ffi/compact_tally_item.rs new file mode 100644 index 00000000..80beace6 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/compact_tally_item.rs @@ -0,0 +1,29 @@ +use crate::ffi::input_coin::InputCoin; +use crate::coin_selection::compact_tally_item; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct CompactTallyItem { + pub tx_destination: *mut u8, + pub tx_destination_length: usize, + pub amount: u64, + pub input_coins: *mut *mut InputCoin, + pub input_coins_size: usize +} + +impl CompactTallyItem { + pub unsafe fn decode(&self) -> compact_tally_item::CompactTallyItem { + compact_tally_item::CompactTallyItem { + tx_destination: if self.tx_destination_length == 0 || self.tx_destination.is_null() { + None + } else { + Some(std::slice::from_raw_parts(self.tx_destination, self.tx_destination_length).to_vec()) + }, + input_coins: (0..self.input_coins_size) + .into_iter() + .map(|i| (*(*self.input_coins.add(i))).decode()) + .collect(), + amount: self.amount + } + } +} diff --git a/dash-spv-coinjoin/src/ffi/gathered_outputs.rs b/dash-spv-coinjoin/src/ffi/gathered_outputs.rs new file mode 100644 index 00000000..a9171d94 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/gathered_outputs.rs @@ -0,0 +1,8 @@ +use super::input_coin::InputCoin; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct GatheredOutputs { + pub items: *mut *mut InputCoin, + pub item_count: usize, +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/ffi/input_coin.rs b/dash-spv-coinjoin/src/ffi/input_coin.rs new file mode 100644 index 00000000..fe092afe --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/input_coin.rs @@ -0,0 +1,27 @@ +use dash_spv_masternode_processor::crypto::UInt256; +use dash_spv_masternode_processor::ffi::from::FromFFI; +use dash_spv_masternode_processor::types::TransactionOutput; +use crate::coin_selection::input_coin; +use crate::models::tx_outpoint::TxOutPoint; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct InputCoin { + pub outpoint_hash: *mut [u8; 32], + pub outpoint_index: u32, + pub output: *mut TransactionOutput, + pub effective_value: u64 +} + +impl InputCoin { + pub unsafe fn decode(&self) -> input_coin::InputCoin { + input_coin::InputCoin { + tx_outpoint: TxOutPoint { + hash: UInt256(*self.outpoint_hash), + index: self.outpoint_index + }, + output: (*self.output).decode(), + effective_value: self.effective_value + } + } +} diff --git a/dash-spv-coinjoin/src/ffi/input_value.rs b/dash-spv-coinjoin/src/ffi/input_value.rs new file mode 100644 index 00000000..f12e5342 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/input_value.rs @@ -0,0 +1,6 @@ +#[repr(C)] +#[derive(Clone, Debug)] +pub struct InputValue { + pub is_valid: bool, + pub value: u64, +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/ffi/mod.rs b/dash-spv-coinjoin/src/ffi/mod.rs new file mode 100644 index 00000000..635d8ff6 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/mod.rs @@ -0,0 +1,13 @@ +pub mod compact_tally_item; +pub mod input_coin; +pub mod selected_coins; +pub mod callbacks; +pub mod recepient; +pub mod coin_control; +pub mod gathered_outputs; +pub mod input_value; +pub mod socket_addres; +pub mod coinjoin_keys; +pub mod coinjoin_denominations; +pub mod coinjoin_session_statuses; +pub mod tx_outpoint; \ No newline at end of file diff --git a/dash-spv-coinjoin/src/ffi/recepient.rs b/dash-spv-coinjoin/src/ffi/recepient.rs new file mode 100644 index 00000000..9f1d6bd1 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/recepient.rs @@ -0,0 +1,8 @@ +use dash_spv_masternode_processor::ffi::ByteArray; + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct Recipient { + pub script_pub_key: ByteArray, + pub amount: u64 +} diff --git a/dash-spv-coinjoin/src/ffi/selected_coins.rs b/dash-spv-coinjoin/src/ffi/selected_coins.rs new file mode 100644 index 00000000..648b0359 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/selected_coins.rs @@ -0,0 +1,8 @@ +use crate::ffi::compact_tally_item::CompactTallyItem; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct SelectedCoins { + pub items: *mut *mut CompactTallyItem, + pub item_count: usize, +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/ffi/socket_addres.rs b/dash-spv-coinjoin/src/ffi/socket_addres.rs new file mode 100644 index 00000000..9bdcd03b --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/socket_addres.rs @@ -0,0 +1,6 @@ +#[repr(C)] +#[derive(Clone, Debug)] +pub struct SocketAddress { + pub ip_address: *mut [u8; 16], + pub port: u16 +} diff --git a/dash-spv-coinjoin/src/ffi/tx_outpoint.rs b/dash-spv-coinjoin/src/ffi/tx_outpoint.rs new file mode 100644 index 00000000..a7de4199 --- /dev/null +++ b/dash-spv-coinjoin/src/ffi/tx_outpoint.rs @@ -0,0 +1,16 @@ +use crate::models::tx_outpoint as tx_outpoint; + +#[repr(C)] +pub struct TxOutPoint { + pub hash: [u8; 32], + pub index: u32, +} + +impl From for TxOutPoint { + fn from(outpoint: tx_outpoint::TxOutPoint) -> Self { + TxOutPoint { + hash: outpoint.hash.0, + index: outpoint.index, + } + } +} diff --git a/dash-spv-coinjoin/src/lib.rs b/dash-spv-coinjoin/src/lib.rs new file mode 100644 index 00000000..9a42e51c --- /dev/null +++ b/dash-spv-coinjoin/src/lib.rs @@ -0,0 +1,17 @@ +pub mod messages; +pub mod models; +pub mod coinjoin; +pub mod constants; +pub mod coinjoin_base_session; +pub mod coinjoin_client_session; +pub mod coinjoin_client_manager; +pub mod coinjoin_client_queue_manager; +pub mod masternode_meta_data_manager; +pub mod wallet_ex; +pub mod ffi; + +pub(crate) mod coin_selection; +pub(crate) mod utils; + +#[cfg(test)] +pub mod tests; \ No newline at end of file diff --git a/dash-spv-coinjoin/src/masternode_meta_data_manager.rs b/dash-spv-coinjoin/src/masternode_meta_data_manager.rs new file mode 100644 index 00000000..e6f6de22 --- /dev/null +++ b/dash-spv-coinjoin/src/masternode_meta_data_manager.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; +use dash_spv_masternode_processor::crypto::UInt256; +use crate::models::masternode_meta_info::MasternodeMetaInfo; + +#[derive(Debug)] +pub struct MasternodeMetadataManager { + meta_infos: HashMap, + pub dsq_count: i64 +} + +impl MasternodeMetadataManager { + pub fn new() -> MasternodeMetadataManager { + return Self { + meta_infos: HashMap::new(), + dsq_count: 0 + }; + } + + pub fn get_meta_info(&mut self, pro_tx_hash: UInt256, create: bool) -> Option { + if let Some(info) = self.meta_infos.get(&pro_tx_hash) { + return Some(info.clone()); + } + + if !create { + return None; + } + + let info = MasternodeMetaInfo::new(pro_tx_hash); + self.meta_infos.insert(pro_tx_hash, info.clone()); + + return Some(info); + } + + pub fn get_dsq_threshold(&mut self, pro_tx_hash: UInt256, mn_count: u64) -> i64 { + if let Some(meta_info) = self.get_meta_info(pro_tx_hash, true) { + return meta_info.last_dsq + (mn_count / 5) as i64; + } else { + // return a threshold which is slightly above nDsqCount i.e. a no-go + return mn_count as i64 / 5; + } + } + + pub fn allow_mixing(&mut self, pro_tx_hash: UInt256) { + if let Some(mut mm) = self.get_meta_info(pro_tx_hash, true) { + self.dsq_count += 1; + mm.last_dsq = self.dsq_count; + mm.mixing_tx_count = 0; + } + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/messages/coinjoin_accept_message.rs b/dash-spv-coinjoin/src/messages/coinjoin_accept_message.rs new file mode 100644 index 00000000..b0ce18cf --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_accept_message.rs @@ -0,0 +1,65 @@ +use std::io::{Read, Write, Error}; +use dash_spv_masternode_processor::consensus::encode; +use dash_spv_masternode_processor::tx::transaction::{Transaction, TransactionType}; +use crate::coinjoin::CoinJoin; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// dsa +#[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinAcceptMessage { + pub denomination: u32, + pub tx_collateral: Transaction, +} + +impl std::fmt::Display for CoinJoinAcceptMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + + write!(f, "CoinJoinAccept(denom={}[{}], txCol={:?})", + CoinJoin::denomination_to_string(self.denomination), + self.denomination, + self.tx_collateral.tx_hash + )?; + Ok(()) + } +} + + +impl CoinJoinAcceptMessage { + pub fn new(denomination: u32, tx_collateral: Transaction) -> Self { + return Self { + denomination, + tx_collateral + }; + } +} + +impl CoinJoinMessageType for CoinJoinAcceptMessage { + fn get_message_type(&self) -> String { + return "dsa".to_string(); + } +} + +impl encode::Encodable for CoinJoinAcceptMessage { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += self.denomination.consensus_encode(&mut writer)?; + let tx_data = self.tx_collateral.to_data(); // TODO: consensus_encode + writer.write_all(&tx_data)?; + offset += tx_data.len(); + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinAcceptMessage { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let denomination = u32::consensus_decode(&mut d)?; + let mut tx_collateral = Transaction::consensus_decode(&mut d)?; + tx_collateral.tx_type = TransactionType::Classic; + + Ok(CoinJoinAcceptMessage { denomination, tx_collateral }) + } +} diff --git a/dash-spv-coinjoin/src/messages/coinjoin_broadcast_tx.rs b/dash-spv-coinjoin/src/messages/coinjoin_broadcast_tx.rs new file mode 100644 index 00000000..87015b7b --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_broadcast_tx.rs @@ -0,0 +1,96 @@ +use std::ffi::c_void; +use std::io::{Read, Write, Error}; +use dash_spv_masternode_processor::consensus::encode; +use dash_spv_masternode_processor::common::block::Block; +use dash_spv_masternode_processor::crypto::byte_util::UInt256; +use dash_spv_masternode_processor::ffi::boxer::boxed; +use dash_spv_masternode_processor::ffi::to::ToFFI; +use dash_spv_masternode_processor::ffi::unboxer::unbox_any; +use dash_spv_masternode_processor::tx::transaction::Transaction; + +use crate::ffi::callbacks::HasChainLock; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// dstx +// #[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinBroadcastTx { + pub tx: Transaction, + pub pro_tx_hash: UInt256, + pub signature: Option>, + pub signature_time: i64, + // memory only + // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1 + confirmed_height: i32, +} + +impl CoinJoinBroadcastTx { + pub fn new(tx: Transaction, pro_tx_hash: UInt256, signature: Option>, signature_time: i64) -> Self { + Self { + tx, + pro_tx_hash, + signature, + signature_time, + confirmed_height: -1, + } + } + + pub fn set_confirmed_height(&mut self, confirmed_height: i32) { + self.confirmed_height = confirmed_height; + } + + pub fn is_expired(&self, block: Block, has_chain_lock: HasChainLock, context: *const c_void) -> bool { + // expire confirmed DSTXes after ~1h since confirmation or chainlocked confirmation + if self.confirmed_height == -1 || (block.height as i32) < self.confirmed_height { + return false; // not mined yet + } + + if block.height as i32 - self.confirmed_height > 24 { + return true; // mined more than an hour ago + } + + return unsafe { + let boxed_block = boxed(block.encode()); + let is_chain_locked = has_chain_lock(boxed_block, context); + unbox_any(boxed_block); + + is_chain_locked + }; + } +} + +impl CoinJoinMessageType for CoinJoinBroadcastTx { + fn get_message_type(&self) -> String { + return "dstx".to_string(); + } +} + +impl encode::Encodable for CoinJoinBroadcastTx { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + let tx_data = self.tx.to_data(); // TODO: consensus_encode + writer.write_all(&tx_data)?; + offset += tx_data.len(); + offset += self.pro_tx_hash.consensus_encode(&mut writer)?; + offset += match self.signature { + Some(ref signature) => signature.consensus_encode(&mut writer)?, + None => 0 + }; + offset += self.signature_time.consensus_encode(&mut writer)?; + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinBroadcastTx { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let tx = Transaction::consensus_decode(&mut d)?; + let pro_tx_hash = UInt256::consensus_decode(&mut d)?; + let signature: Option> = Vec::consensus_decode(&mut d).ok(); + let signature_time = i64::consensus_decode(&mut d)?; + + Ok(CoinJoinBroadcastTx::new(tx, pro_tx_hash, signature, signature_time) ) + } +} diff --git a/dash-spv-coinjoin/src/messages/coinjoin_complete_message.rs b/dash-spv-coinjoin/src/messages/coinjoin_complete_message.rs new file mode 100644 index 00000000..7b755d28 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_complete_message.rs @@ -0,0 +1,42 @@ +use std::io::{Read, Write, Error}; +use dash_spv_masternode_processor::consensus::encode; +use crate::messages::pool_message::PoolMessage; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// dsc +#[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinCompleteMessage { + pub msg_session_id: i32, + pub msg_message_id: PoolMessage, +} + +impl CoinJoinMessageType for CoinJoinCompleteMessage { + fn get_message_type(&self) -> String { + return "dsc".to_string(); + } +} + +impl encode::Encodable for CoinJoinCompleteMessage { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += self.msg_session_id.consensus_encode(&mut writer)?; + offset += self.msg_message_id.value().consensus_encode(&mut writer)?; + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinCompleteMessage { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let msg_session_id = u32::consensus_decode(&mut d)? as i32; + let message_id = u32::consensus_decode(&mut d)? as i32; + + Ok(CoinJoinCompleteMessage { + msg_session_id, + msg_message_id: PoolMessage::from_value(message_id) + }) + } +} diff --git a/dash-spv-coinjoin/src/messages/coinjoin_entry.rs b/dash-spv-coinjoin/src/messages/coinjoin_entry.rs new file mode 100644 index 00000000..0a713b0a --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_entry.rs @@ -0,0 +1,77 @@ +use std::io::{Read, Write, Error}; +use dash_spv_masternode_processor::consensus::encode; +use dash_spv_masternode_processor::tx::transaction::{TransactionInput, TransactionOutput, Transaction, TransactionType}; +use dash_spv_masternode_processor::consensus::encode::VarInt; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// dsi +// A client's transaction in the mixing pool +// #[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinEntry { + pub mixing_inputs: Vec, + pub mixing_outputs: Vec, + pub tx_collateral: Transaction, +} + +impl CoinJoinMessageType for CoinJoinEntry { + fn get_message_type(&self) -> String { + return "dsi".to_string(); + } +} + +impl encode::Encodable for CoinJoinEntry { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + let inputs_amount = VarInt(self.mixing_inputs.len() as u64); + offset += inputs_amount.consensus_encode(&mut writer)?; + + for i in 0..self.mixing_inputs.len() { + offset += self.mixing_inputs[i].consensus_encode(&mut writer)?; + } + + let tx_data = self.tx_collateral.to_data(); // TODO: consensus_encode + writer.write_all(&tx_data)?; + offset += tx_data.len(); + + let outputs_amount = VarInt(self.mixing_outputs.len() as u64); + offset += outputs_amount.consensus_encode(&mut writer)?; + + for i in 0..self.mixing_outputs.len() { + offset += self.mixing_outputs[i].consensus_encode(&mut writer)?; + } + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinEntry { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let mut mixing_inputs = vec![]; + let input_amount = encode::VarInt::consensus_decode(&mut d)?.0; + + for _ in 0..input_amount { + let input = TransactionInput::consensus_decode(&mut d)?; + mixing_inputs.push(input); + } + + let mut tx_collateral = Transaction::consensus_decode(&mut d)?; + tx_collateral.tx_type = TransactionType::Classic; + + let mut mixing_outputs = vec![]; + let output_amount = encode::VarInt::consensus_decode(&mut d)?.0; + + for _ in 0..output_amount { + let output = TransactionOutput::consensus_decode(&mut d)?; + mixing_outputs.push(output); + } + + Ok(CoinJoinEntry { + mixing_inputs, + tx_collateral, + mixing_outputs + }) + } +} diff --git a/dash-spv-coinjoin/src/messages/coinjoin_final_transaction.rs b/dash-spv-coinjoin/src/messages/coinjoin_final_transaction.rs new file mode 100644 index 00000000..537c7ae5 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_final_transaction.rs @@ -0,0 +1,42 @@ +use std::io::{Error, Read, Write}; +use dash_spv_masternode_processor::tx::transaction::{Transaction, TransactionType}; +use dash_spv_masternode_processor::consensus::encode; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// dsf +#[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinFinalTransaction { + pub msg_session_id: i32, + pub tx: Transaction, +} + +impl CoinJoinMessageType for CoinJoinFinalTransaction { + fn get_message_type(&self) -> String { + return "dsf".to_string(); + } +} + +impl encode::Encodable for CoinJoinFinalTransaction { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += (self.msg_session_id as u32).consensus_encode(&mut writer)?; + let tx_data = self.tx.to_data(); // TODO: consensus_encode + writer.write_all(&tx_data)?; + offset += tx_data.len(); + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinFinalTransaction { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let msg_session_id = u32::consensus_decode(&mut d)? as i32; + let mut tx = Transaction::consensus_decode(&mut d)?; + tx.tx_type = TransactionType::Classic; + + Ok(CoinJoinFinalTransaction { msg_session_id, tx }) + } +} diff --git a/dash-spv-coinjoin/src/messages/coinjoin_message.rs b/dash-spv-coinjoin/src/messages/coinjoin_message.rs new file mode 100644 index 00000000..0d58ce36 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_message.rs @@ -0,0 +1,12 @@ +use super::{coinjoin_broadcast_tx::CoinJoinBroadcastTx, CoinJoinCompleteMessage, CoinJoinFinalTransaction, CoinJoinStatusUpdate}; + +pub trait CoinJoinMessageType { + fn get_message_type(&self) -> String; +} + +pub enum CoinJoinMessage { + StatusUpdate(CoinJoinStatusUpdate), + BroadcastTx(CoinJoinBroadcastTx), + FinalTransaction(CoinJoinFinalTransaction), + Complete(CoinJoinCompleteMessage) +} diff --git a/dash-spv-coinjoin/src/messages/coinjoin_queue_message.rs b/dash-spv-coinjoin/src/messages/coinjoin_queue_message.rs new file mode 100644 index 00000000..80a4e829 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_queue_message.rs @@ -0,0 +1,120 @@ +use std::io::{Error, Read, Write}; +use std::time::{SystemTime, UNIX_EPOCH}; +use dash_spv_masternode_processor::consensus::encode::VarInt; +use dash_spv_masternode_processor::crypto::byte_util::{Reversable, UInt256}; +use dash_spv_masternode_processor::consensus::{encode, Encodable}; +use dash_spv_masternode_processor::crypto::UInt768; +use dash_spv_masternode_processor::hashes::hex::ToHex; +use dash_spv_masternode_processor::hashes::{sha256d, Hash}; +use dash_spv_masternode_processor::keys::BLSKey; +use dash_spv_masternode_processor::models::OperatorPublicKey; +use logging::*; +use tracing::warn; +use crate::coinjoin::CoinJoin; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +use crate::constants::COINJOIN_QUEUE_TIMEOUT; + +// dsq +// A currently in progress mixing merge and denomination information +// #[repr(C)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CoinJoinQueueMessage { + pub denomination: u32, + pub pro_tx_hash: UInt256, + pub time: i64, + pub ready: bool, // ready to submit + pub signature: Option, + // Memory only + pub tried: bool +} + +impl std::fmt::Display for CoinJoinQueueMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + write!(f, "CoinJoinQueue(denom={}[{}], t={}[exp={}], ready={}, proTxHash={})", + CoinJoin::denomination_to_string(self.denomination), + self.denomination, + self.time, + self.is_time_out_of_bounds(current_time), + self.ready, + self.pro_tx_hash.reversed().0.to_hex().chars().take(16).collect::() + )?; + Ok(()) + } +} + +impl CoinJoinQueueMessage { + pub fn is_time_out_of_bounds(&self, current_time: u64) -> bool { + return current_time.saturating_sub(self.time as u64) > COINJOIN_QUEUE_TIMEOUT || + (self.time as u64).saturating_sub(current_time) > COINJOIN_QUEUE_TIMEOUT; + } + + pub fn check_signature(&self, key: OperatorPublicKey) -> bool { + if let Some(ref signature) = self.signature { + let hash = self.get_signature_hash(); + let verified = BLSKey::key_with_public_key( + key.data, + key.is_legacy() + ).verify_insecure(&hash, *signature); + + if !verified { + log_warn!(target: "CoinJoinQueue", "verifySignature failed"); + } + + return verified; + } + + return false; + } + + pub fn get_signature_hash(&self) -> Vec { + let mut writer = Vec::::new(); + self.denomination.consensus_encode(&mut writer).unwrap(); + self.pro_tx_hash.consensus_encode(&mut writer).unwrap(); + self.time.consensus_encode(&mut writer).unwrap(); + self.ready.consensus_encode(&mut writer).unwrap(); + sha256d::Hash::hash(&writer).into_inner().to_vec() + } +} + +impl CoinJoinMessageType for CoinJoinQueueMessage { + fn get_message_type(&self) -> String { + return "dsq".to_string(); + } +} + +impl encode::Encodable for CoinJoinQueueMessage { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += self.denomination.consensus_encode(&mut writer)?; + offset += self.pro_tx_hash.consensus_encode(&mut writer)?; + offset += self.time.consensus_encode(&mut writer)?; + offset += self.ready.consensus_encode(&mut writer)?; + offset += match self.signature { + Some(ref signature) => { + let len_offset = VarInt(signature.0.len() as u64).consensus_encode(&mut writer)?; + let sig_offset = signature.consensus_encode(&mut writer)?; + len_offset + sig_offset + } + None => 0 + }; + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinQueueMessage { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let denomination = u32::consensus_decode(&mut d)?; + let pro_tx_hash = UInt256::consensus_decode(&mut d)?; + let time = i64::consensus_decode(&mut d)?; + let ready: bool = bool::consensus_decode(&mut d)?; + let _signature_len = VarInt::consensus_decode(&mut d)?; + let signature: Option = UInt768::consensus_decode(&mut d).ok(); + let message = CoinJoinQueueMessage { denomination, pro_tx_hash, time, ready, signature, tried: false }; + Ok(message) + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/messages/coinjoin_signed_inputs.rs b/dash-spv-coinjoin/src/messages/coinjoin_signed_inputs.rs new file mode 100644 index 00000000..2abbd16c --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_signed_inputs.rs @@ -0,0 +1,60 @@ +use std::io::{Error, Read, Write}; +use dash_spv_masternode_processor::tx::transaction::TransactionInput; +use dash_spv_masternode_processor::consensus::encode; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// dss +// #[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinSignedInputs { + pub inputs: Vec, +} + +impl CoinJoinMessageType for CoinJoinSignedInputs { + fn get_message_type(&self) -> String { + return "dss".to_string(); + } +} + +impl std::fmt::Display for CoinJoinSignedInputs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "CoinJoinSignedInputs {{ inputs: [")?; + for (i, input) in self.inputs.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{{ input_hash: {}, index: {} }}", input.input_hash, input.index)?; + } + write!(f, "] }}") + } +} + +impl encode::Encodable for CoinJoinSignedInputs { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + let amount = encode::VarInt(self.inputs.len() as u64); + offset += amount.consensus_encode(&mut writer)?; + + for i in 0..self.inputs.len() { + offset += self.inputs[i].consensus_encode(&mut writer)?; + } + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinSignedInputs { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let mut inputs = vec![]; + let amount = encode::VarInt::consensus_decode(&mut d)?.0; + + for _ in 0..amount { + let input = TransactionInput::consensus_decode(&mut d)?; + inputs.push(input); + } + + Ok(CoinJoinSignedInputs { inputs }) + } +} diff --git a/dash-spv-coinjoin/src/messages/coinjoin_status_update.rs b/dash-spv-coinjoin/src/messages/coinjoin_status_update.rs new file mode 100644 index 00000000..73daff56 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/coinjoin_status_update.rs @@ -0,0 +1,53 @@ +use std::io::{Error, Read, Write}; +use dash_spv_masternode_processor::consensus::encode; + +use crate::messages::pool_message::PoolMessage; +use crate::messages::pool_state::PoolState; +use crate::messages::pool_status_update::PoolStatusUpdate; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// dssu +#[repr(C)] +#[derive(Clone, Debug)] +pub struct CoinJoinStatusUpdate { + pub session_id: i32, + pub pool_state: PoolState, + pub status_update: PoolStatusUpdate, + pub message_id: PoolMessage, +} + +impl CoinJoinMessageType for CoinJoinStatusUpdate { + fn get_message_type(&self) -> String { + return "dssu".to_string(); + } +} + +impl encode::Encodable for CoinJoinStatusUpdate { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += (self.session_id as u32).consensus_encode(&mut writer)?; + offset += (self.pool_state.value() as u32).consensus_encode(&mut writer)?; + offset += (self.status_update.value() as u32).consensus_encode(&mut writer)?; + offset += (self.message_id.value() as u32).consensus_encode(&mut writer)?; + + Ok(offset) + } +} + +impl encode::Decodable for CoinJoinStatusUpdate { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let session_id = u32::consensus_decode(&mut d)? as i32; + let pool_state = u32::consensus_decode(&mut d)? as i32; + let status_update = u32::consensus_decode(&mut d)? as i32; + let message_id = u32::consensus_decode(&mut d)? as i32; + + Ok(CoinJoinStatusUpdate { + session_id, + pool_state: PoolState::from_value(pool_state), + status_update: PoolStatusUpdate::from_value(status_update), + message_id: PoolMessage::from_value(message_id) + }) + } +} diff --git a/dash-spv-coinjoin/src/messages/mod.rs b/dash-spv-coinjoin/src/messages/mod.rs new file mode 100644 index 00000000..00fcd402 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/mod.rs @@ -0,0 +1,28 @@ +pub mod coinjoin_accept_message; +pub mod pool_message; +pub mod coinjoin_complete_message; +pub mod coinjoin_final_transaction; +pub mod pool_state; +pub mod coinjoin_status_update; +pub mod pool_status_update; +pub mod coinjoin_signed_inputs; +pub mod coinjoin_entry; +pub mod coinjoin_message; +pub mod send_coinjoin_queue; +pub mod coinjoin_broadcast_tx; +pub mod coinjoin_queue_message; +pub mod pool_status; + +pub use self::coinjoin_accept_message::CoinJoinAcceptMessage; +pub use self::coinjoin_complete_message::CoinJoinCompleteMessage; +pub use self::coinjoin_final_transaction::CoinJoinFinalTransaction; +pub use self::coinjoin_status_update::CoinJoinStatusUpdate; +pub use self::coinjoin_signed_inputs::CoinJoinSignedInputs; +pub use self::coinjoin_entry::CoinJoinEntry; +pub use self::coinjoin_message::CoinJoinMessageType; +pub use self::send_coinjoin_queue::SendCoinJoinQueue; +pub use self::coinjoin_queue_message::CoinJoinQueueMessage; +pub use self::pool_message::PoolMessage; +pub use self::pool_state::PoolState; +pub use self::pool_status::PoolStatus; +pub use self::pool_status_update::PoolStatusUpdate; diff --git a/dash-spv-coinjoin/src/messages/pool_message.rs b/dash-spv-coinjoin/src/messages/pool_message.rs new file mode 100644 index 00000000..c79904bb --- /dev/null +++ b/dash-spv-coinjoin/src/messages/pool_message.rs @@ -0,0 +1,72 @@ +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum PoolMessage { + ErrAlreadyHave = 0, + ErrDenom = 1, + ErrEntriesFull = 2, + ErrExistingTx = 3, + ErrFees = 4, + ErrInvalidCollateral = 5, + ErrInvalidInput = 6, + ErrInvalidScript = 7, + ErrInvalidTx = 8, + ErrMaximum = 9, + ErrMnList = 10, + ErrMode = 11, + ErrQueueFull = 14, + ErrRecent = 15, + ErrSession = 16, + ErrMissingTx = 17, + ErrVersion = 18, + MsgNoErr = 19, + MsgSuccess = 20, + MsgEntriesAdded = 21, + ErrSizeMismatch = 22, + + // extra values for DashSync Reporting + ErrTimeout = 23, + ErrConnectionTimeout = 24, +} + +impl PoolMessage { + pub fn value(&self) -> i32 { + *self as i32 + } + + pub fn from_value(value: i32) -> Self { + match value { + 0 => PoolMessage::ErrAlreadyHave, + 1 => PoolMessage::ErrDenom, + 2 => PoolMessage::ErrEntriesFull, + 3 => PoolMessage::ErrExistingTx, + 4 => PoolMessage::ErrFees, + 5 => PoolMessage::ErrInvalidCollateral, + 6 => PoolMessage::ErrInvalidInput, + 7 => PoolMessage::ErrInvalidScript, + 8 => PoolMessage::ErrInvalidTx, + 9 => PoolMessage::ErrMaximum, + 10 => PoolMessage::ErrMnList, + 11 => PoolMessage::ErrMode, + 14 => PoolMessage::ErrQueueFull, + 15 => PoolMessage::ErrRecent, + 16 => PoolMessage::ErrSession, + 17 => PoolMessage::ErrMissingTx, + 18 => PoolMessage::ErrVersion, + 19 => PoolMessage::MsgNoErr, + 20 => PoolMessage::MsgSuccess, + 21 => PoolMessage::MsgEntriesAdded, + 22 => PoolMessage::ErrSizeMismatch, + 23 => PoolMessage::ErrTimeout, + 24 => PoolMessage::ErrConnectionTimeout, + _ => PoolMessage::MsgNoErr, // Default case + } + } + + pub fn msg_pool_min() -> Self { + PoolMessage::ErrAlreadyHave + } + + pub fn msg_pool_max() -> Self { + PoolMessage::ErrSizeMismatch + } +} diff --git a/dash-spv-coinjoin/src/messages/pool_state.rs b/dash-spv-coinjoin/src/messages/pool_state.rs new file mode 100644 index 00000000..0e0e2128 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/pool_state.rs @@ -0,0 +1,34 @@ +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum PoolState { + Idle = 0, + Queue = 1, + AcceptingEntries = 2, + Signing = 3, + Error = 4, +} + +impl PoolState { + pub fn value(&self) -> i32 { + *self as i32 + } + + pub fn from_value(value: i32) -> Self { + match value { + 0 => PoolState::Idle, + 1 => PoolState::Queue, + 2 => PoolState::AcceptingEntries, + 3 => PoolState::Signing, + 4 => PoolState::Error, + _ => PoolState::Idle, // Default case + } + } + + pub fn pool_state_min() -> Self { + PoolState::Idle + } + + pub fn pool_state_max() -> Self { + PoolState::Error + } +} diff --git a/dash-spv-coinjoin/src/messages/pool_status.rs b/dash-spv-coinjoin/src/messages/pool_status.rs new file mode 100644 index 00000000..96d648be --- /dev/null +++ b/dash-spv-coinjoin/src/messages/pool_status.rs @@ -0,0 +1,51 @@ +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PoolStatus { + Warmup = 0x0001, + Idle = 0x0002, + Connecting = 0x0003, + Connected = 0x0004, + Mixing = 0x0005, + Complete = 0x0106, + Finished = 0x1007, + Timeout = 0x0107, + ConnectionTimeout = 0x0108, + // Errors + ErrNoInputs = 0x2100, + ErrMasternodeNotFound = 0x2101, + ErrNoMasternodesDetected = 0x2102, + ErrWalletLocked = 0x3103, + ErrNotEnoughFunds = 0x3104, + // Warnings + WarnNoMixingQueues = 0x4200, + WarnNoCompatibleMasternode = 0x4201 +} + +impl PoolStatus { + const STOP: i32 = 0x1000; + const ERROR: i32 = 0x2000; + const WARNING: i32 = 0x4000; + const COMPLETED: i32 = 0x0100; + + pub fn value(&self) -> i32 { + *self as i32 + } + + pub fn is_error(&self) -> bool { + (self.value() & Self::ERROR) != 0 + } + + pub fn is_warning(&self) -> bool { + (self.value() & Self::WARNING) != 0 + } + + pub fn should_stop(&self) -> bool { + (self.value() & Self::STOP) != 0 || + self == &PoolStatus::ErrNoInputs || + self == &PoolStatus::ErrNotEnoughFunds + } + + pub fn session_completed(&self) -> bool { + (self.value() & Self::COMPLETED) != 0 + } +} diff --git a/dash-spv-coinjoin/src/messages/pool_status_update.rs b/dash-spv-coinjoin/src/messages/pool_status_update.rs new file mode 100644 index 00000000..9d1e5bc9 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/pool_status_update.rs @@ -0,0 +1,19 @@ +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PoolStatusUpdate { + Rejected = 0, + Accepted = 1, +} + +impl PoolStatusUpdate { + pub fn value(&self) -> i32 { + *self as i32 + } + + pub fn from_value(value: i32) -> Self { + match value { + 0 => PoolStatusUpdate::Rejected, + _ => PoolStatusUpdate::Accepted, + } + } +} diff --git a/dash-spv-coinjoin/src/messages/send_coinjoin_queue.rs b/dash-spv-coinjoin/src/messages/send_coinjoin_queue.rs new file mode 100644 index 00000000..40db59f0 --- /dev/null +++ b/dash-spv-coinjoin/src/messages/send_coinjoin_queue.rs @@ -0,0 +1,35 @@ +use std::io::{Error, Read, Write}; +use dash_spv_masternode_processor::consensus::encode; +use crate::messages::coinjoin_message::CoinJoinMessageType; + +// senddsq +#[repr(C)] +#[derive(Clone, Debug)] +pub struct SendCoinJoinQueue { + pub send: bool, +} + +impl CoinJoinMessageType for SendCoinJoinQueue { + fn get_message_type(&self) -> String { + return "senddsq".to_string(); + } +} + +impl encode::Encodable for SendCoinJoinQueue { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += self.send.consensus_encode(&mut writer)?; + + Ok(offset) + } +} + +impl encode::Decodable for SendCoinJoinQueue { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let send = bool::consensus_decode(&mut d)?; + + Ok(SendCoinJoinQueue { send }) + } +} diff --git a/dash-spv-coinjoin/src/models/balance.rs b/dash-spv-coinjoin/src/models/balance.rs new file mode 100644 index 00000000..f61a8d8f --- /dev/null +++ b/dash-spv-coinjoin/src/models/balance.rs @@ -0,0 +1,41 @@ +use std::fmt; + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct Balance { + pub my_trusted: u64, // Trusted, at depth=GetBalance.min_depth or more + pub my_untrusted_pending: u64, // Untrusted, but in mempool (pending) + pub my_immature: u64, // Immature coinbases in the main chain + pub watch_only_trusted: u64, + pub watch_only_untrusted_pending: u64, + pub watch_only_immature: u64, + pub anonymized: u64, + pub denominated_trusted: u64, + pub denominated_untrusted_pending: u64 +} + +impl Balance { + pub fn new() -> Self{ + Self { + my_trusted: 0, + my_untrusted_pending: 0, + my_immature: 0, + watch_only_trusted: 0, + watch_only_untrusted_pending: 0, + watch_only_immature: 0, + anonymized: 0, + denominated_trusted: 0, + denominated_untrusted_pending: 0 + } + } +} + +impl fmt::Display for Balance { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Balance(my_trusted: {}, anonymized: {}, denominated_trusted: {})", + self.my_trusted, + self.anonymized, + self.denominated_trusted + ) + } +} diff --git a/dash-spv-coinjoin/src/models/coin_control.rs b/dash-spv-coinjoin/src/models/coin_control.rs new file mode 100644 index 00000000..7dd08f06 --- /dev/null +++ b/dash-spv-coinjoin/src/models/coin_control.rs @@ -0,0 +1,83 @@ +use std::collections::HashSet; +use dash_spv_masternode_processor::ffi::{boxer::{boxed, boxed_vec}, ByteArray}; + +use crate::ffi as ffi; + +use crate::constants::REFERENCE_DEFAULT_MIN_TX_FEE; +use super::{tx_destination::TxDestination, tx_outpoint::TxOutPoint}; + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(C)] +pub enum CoinType { + AllCoins = 0, + OnlyFullyMixed = 1, + OnlyReadyToMix = 2, + OnlyNonDenominated = 3, + OnlyMasternodeCollateral = 4, + OnlyCoinJoinCollateral = 5, +} + +// CoinControl comes from Dash Core. Not all functions fields and functions are supported within the Wallet class +#[derive(Clone, Debug)] + +pub struct CoinControl { + pub dest_change: TxDestination, + pub allow_other_inputs: bool, + pub fee_rate: u64, + pub discard_fee_rate: u64, + avoid_address_reuse: bool, + min_depth: i32, + max_depth: i32, + pub coin_type: CoinType, + pub set_selected: HashSet +} + +impl CoinControl { + pub fn new() -> Self { + Self { + dest_change: None, + allow_other_inputs: false, + fee_rate: REFERENCE_DEFAULT_MIN_TX_FEE / 1000, + discard_fee_rate: REFERENCE_DEFAULT_MIN_TX_FEE / 1000, + avoid_address_reuse: false, + min_depth: 0, + max_depth: 9999999, + coin_type: CoinType::AllCoins, + set_selected: HashSet::new() + } + } + + pub fn select(&mut self, out_point: TxOutPoint) { + self.set_selected.insert(out_point); + } + + pub fn unselect(&mut self, out_point: TxOutPoint) { + self.set_selected.remove(&out_point); + } + + pub fn encode(&self) -> ffi::coin_control::CoinControl { + ffi::coin_control::CoinControl { + coin_type: self.coin_type, + min_depth: self.min_depth, + max_depth: self.max_depth, + avoid_address_reuse: self.avoid_address_reuse, + allow_other_inputs: self.allow_other_inputs, + set_selected: if self.set_selected.is_empty() { + std::ptr::null_mut() + } else { + boxed_vec( + self.set_selected + .iter() + .map(|outpoint| boxed(ffi::tx_outpoint::TxOutPoint::from(outpoint.clone()))) + .collect() + ) + }, + set_selected_size: self.set_selected.len(), + dest_change: if self.dest_change.is_none() { + std::ptr::null_mut() + } else { + boxed(ByteArray::from(self.dest_change.as_ref().unwrap().to_vec())) + } + } + } +} diff --git a/dash-spv-coinjoin/src/models/coinjoin_client_options.rs b/dash-spv-coinjoin/src/models/coinjoin_client_options.rs new file mode 100644 index 00000000..375c5e7d --- /dev/null +++ b/dash-spv-coinjoin/src/models/coinjoin_client_options.rs @@ -0,0 +1,16 @@ +use dash_spv_masternode_processor::chain::common::ChainType; + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CoinJoinClientOptions { + pub enable_coinjoin: bool, + pub coinjoin_amount: u64, + pub coinjoin_sessions: i32, + pub coinjoin_rounds: i32, + pub coinjoin_random_rounds: i32, + pub coinjoin_denoms_goal: i32, + pub coinjoin_denoms_hardcap: i32, + pub coinjoin_multi_session: bool, + pub chain_type: ChainType, + pub denom_only: bool +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/models/coinjoin_transaction_input.rs b/dash-spv-coinjoin/src/models/coinjoin_transaction_input.rs new file mode 100644 index 00000000..d158dac5 --- /dev/null +++ b/dash-spv-coinjoin/src/models/coinjoin_transaction_input.rs @@ -0,0 +1,24 @@ +use dash_spv_masternode_processor::tx::TransactionInput; + +use super::tx_outpoint::TxOutPoint; + +// Holds a mixing input +#[derive(Debug, Clone)] +pub struct CoinJoinTransactionInput { + pub txin: TransactionInput, + pub rounds: i32 +} + +impl CoinJoinTransactionInput { + pub fn new(txin: TransactionInput, rounds: i32) -> Self { + CoinJoinTransactionInput { + txin, + rounds + } + } + + pub fn outpoint(&self) -> TxOutPoint { + return TxOutPoint::new(self.txin.input_hash, self.txin.index); + } +} + diff --git a/dash-spv-coinjoin/src/models/coinjoin_tx_type.rs b/dash-spv-coinjoin/src/models/coinjoin_tx_type.rs new file mode 100644 index 00000000..2b2672cf --- /dev/null +++ b/dash-spv-coinjoin/src/models/coinjoin_tx_type.rs @@ -0,0 +1,101 @@ +use dash_spv_masternode_processor::{crypto::byte_util::Reversable, tx::Transaction}; + +use crate::coinjoin::CoinJoin; + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub enum CoinJoinTransactionType { + None, + CreateDenomination, + MakeCollateralInputs, + MixingFee, + Mixing, + Send, +} + +impl CoinJoinTransactionType { + pub fn from_tx(tx: &Transaction, input_values: &Vec) -> Self { + let input_sum: u64 = input_values.iter().sum(); + + if tx.inputs.len() == tx.outputs.len() && tx.outputs.iter().all(|output| CoinJoin::is_denominated_amount(output.amount)) { + return Self::Mixing; + } else if Self::is_mixing_fee(tx, input_sum) { + return Self::MixingFee; + } else { + let mut make_collateral = false; + if tx.outputs.len() == 2 { + let amount_0 = tx.outputs[0].amount; + let amount_1 = tx.outputs[1].amount; + // , see CCoinJoinClientSession.makeCollateralAmounts + make_collateral = (amount_0 == CoinJoin::get_max_collateral_amount() && !CoinJoin::is_denominated_amount(amount_1) && amount_1 >= CoinJoin::get_collateral_amount()) || + (amount_1 == CoinJoin::get_max_collateral_amount() && !CoinJoin::is_denominated_amount(amount_0) && amount_0 >= CoinJoin::get_collateral_amount()) || + // , see CCoinJoinClientSession.makeCollateralAmounts + (amount_0 == amount_1 && CoinJoin::is_collateral_amount(amount_0)); + } else if tx.outputs.len() == 1 { + // , see CCoinJoinClientSession.makeCollateralAmounts + make_collateral = CoinJoin::is_collateral_amount(tx.outputs[0].amount); + } + if make_collateral { + return Self::MakeCollateralInputs; + } else { + for output in &tx.outputs { + if CoinJoin::is_denominated_amount(output.amount) { + return Self::CreateDenomination; // Done, it's definitely a tx creating mixing denoms, no need to look any further + } + } + } + } + + // is this a coinjoin send transaction + if CoinJoinTransactionType::is_coinjoin_send(tx, &input_values) { + return Self::Send; + } + + return Self::None; + } + + fn is_coinjoin_send(tx: &Transaction, input_values: &Vec) -> bool { + let inputs_are_denominated = input_values.iter().all(|input| CoinJoin::is_denominated_amount(*input)); + let fee = CoinJoinTransactionType::get_fee(tx, input_values); + + return inputs_are_denominated && fee.map_or(false, |f| f != 0); + } + + fn is_mixing_fee(tx: &Transaction, inputs_value: u64) -> bool { + let outputs_value = tx.outputs.iter().map(|output| output.amount).sum(); + + if inputs_value < outputs_value { + return false; + } + + let net_value = inputs_value - outputs_value; + + // check for the tx with OP_RETURN + if outputs_value == 0 && tx.inputs.len() == 1 && tx.outputs.len() == 1 && tx.outputs[0].is_op_return() { + return true; + } + + return tx.inputs.len() == 1 && tx.outputs.len() == 1 + && CoinJoin::is_collateral_amount(inputs_value) + && CoinJoin::is_collateral_amount(outputs_value) + && CoinJoin::is_collateral_amount(net_value); + } + + fn get_fee(tx: &Transaction, inputs_values: &Vec) -> Option { + let mut fee: u64 = 0; + + if inputs_values.is_empty() || tx.outputs.is_empty() { + return None; + } + + for input_value in inputs_values { + fee = fee.saturating_add(*input_value); + } + + for output in &tx.outputs { + fee = fee.saturating_sub(output.amount); + } + + return Some(fee); + } +} diff --git a/dash-spv-coinjoin/src/models/masternode_meta_info.rs b/dash-spv-coinjoin/src/models/masternode_meta_info.rs new file mode 100644 index 00000000..88d7b4a7 --- /dev/null +++ b/dash-spv-coinjoin/src/models/masternode_meta_info.rs @@ -0,0 +1,19 @@ +use dash_spv_masternode_processor::crypto::UInt256; + +#[derive(Debug, Clone)] +pub struct MasternodeMetaInfo { + pub pro_tx_hash: UInt256, + // the dsq count from the last dsq broadcast of this node + pub last_dsq: i64, + pub mixing_tx_count: i32 +} + +impl MasternodeMetaInfo { + pub fn new(pro_tx_hash: UInt256) -> MasternodeMetaInfo { + Self { + pro_tx_hash, + last_dsq: 0, + mixing_tx_count: 0 + } + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/models/mod.rs b/dash-spv-coinjoin/src/models/mod.rs new file mode 100644 index 00000000..96996bf9 --- /dev/null +++ b/dash-spv-coinjoin/src/models/mod.rs @@ -0,0 +1,15 @@ +pub mod coinjoin_client_options; +pub mod balance; +pub mod tx_outpoint; +pub mod coinjoin_transaction_input; +pub mod coinjoin_tx_type; +pub(crate) mod tx_destination; +pub(crate) mod valid_in_outs; +pub(crate) mod pending_dsa_request; +pub(crate) mod coin_control; +pub(crate) mod reserve_destination; +pub(crate) mod transaction_builder_output; +pub(crate) mod masternode_meta_info; + +pub use self::coinjoin_client_options::CoinJoinClientOptions; +pub use self::balance::Balance; diff --git a/dash-spv-coinjoin/src/models/pending_dsa_request.rs b/dash-spv-coinjoin/src/models/pending_dsa_request.rs new file mode 100644 index 00000000..548922f4 --- /dev/null +++ b/dash-spv-coinjoin/src/models/pending_dsa_request.rs @@ -0,0 +1,31 @@ +use std::time::{SystemTime, Duration}; + +use dash_spv_masternode_processor::common::SocketAddress; + +use crate::messages::CoinJoinAcceptMessage; + +#[derive(Debug)] +pub(crate) struct PendingDsaRequest { + pub addr: SocketAddress, + pub dsa: CoinJoinAcceptMessage, + time_created: SystemTime, +} + +const TIMEOUT: Duration = Duration::from_secs(15); + +impl PendingDsaRequest { + pub fn new(addr: SocketAddress, dsa: CoinJoinAcceptMessage) -> Self { + Self { addr, dsa, time_created: SystemTime::now() } + } + + pub fn is_expired(&self) -> bool { + self.time_created.elapsed().unwrap() > TIMEOUT + } +} + +impl std::fmt::Display for PendingDsaRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "PendingDsaRequest {{ addr: {}, dsa: {}, n_time_created: {:?} }}", + self.addr, self.dsa, self.time_created) + } +} diff --git a/dash-spv-coinjoin/src/models/reserve_destination.rs b/dash-spv-coinjoin/src/models/reserve_destination.rs new file mode 100644 index 00000000..5a4adf40 --- /dev/null +++ b/dash-spv-coinjoin/src/models/reserve_destination.rs @@ -0,0 +1,72 @@ +use std::{cell::RefCell, rc::Rc}; +use tracing::debug; +use logging::*; +use crate::wallet_ex::WalletEx; +use super::tx_destination::TxDestination; + +/** A wrapper to reserve an address from a wallet + * + * ReserveDestination is used to reserve an address. It is passed around + * during the CreateTransaction/CommitTransaction procedure. + * + * Instantiating a ReserveDestination does not reserve an address. To do so, + * GetReservedDestination() needs to be called on the object. Once an address has been + * reserved, call KeepDestination() on the ReserveDestination object to make sure it is not + * returned. Call ReturnDestination() to return the address so it can be re-used (for + * example, if the address was used in a new transaction + * and that transaction was not completed and needed to be aborted). + * + * If an address is reserved and KeepDestination() is not called, then the address will be + * returned when the ReserveDestination goes out of scope. + */ +#[derive(Clone)] +pub struct ReserveDestination { + wallet_ex: Rc>, + pub key: TxDestination, + pub internal: bool, +} + +impl<'a> ReserveDestination { + pub fn new(wallet_ex: Rc>) -> Self { + Self { + wallet_ex, + key: None, + internal: false, + } + } + + pub fn get_reserved_destination(&mut self, internal: bool) -> TxDestination { + if self.key.is_none() { + let mut wallet = self.wallet_ex.borrow_mut(); + + if let Some(key) = wallet.get_unused_key(internal) { + self.key = Some(key); + self.internal = true; + } else { + return None; + } + } + + return self.key.clone(); + } + + pub fn keep_destination(&mut self) { + if self.key.is_some() { + self.wallet_ex.borrow_mut().remove_unused_key(&self.key); + } else { + log_debug!(target: "CoinJoin", "cannot keep key"); + } + + self.key = None; + } + + pub fn return_destination(&mut self) { + if self.key.is_some() { + self.wallet_ex.borrow_mut().add_unused_key(&self.key); + } else { + log_debug!(target: "CoinJoin", "cannot return key"); + } + + self.key = None; + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/models/transaction_builder_output.rs b/dash-spv-coinjoin/src/models/transaction_builder_output.rs new file mode 100644 index 00000000..0946e3f6 --- /dev/null +++ b/dash-spv-coinjoin/src/models/transaction_builder_output.rs @@ -0,0 +1,42 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::wallet_ex::WalletEx; +use super::reserve_destination::ReserveDestination; + +#[derive(Clone)] +pub struct TransactionBuilderOutput { + reserve_destination: ReserveDestination, + pub amount: u64, + pub script: Option> +} + +impl<'a> TransactionBuilderOutput { + pub fn new(wallet: Rc>, amount: u64, dry_run: bool) -> Self { + let mut reserve_destination = ReserveDestination::new(wallet); + Self { + script: if dry_run { Some(vec![0;20]) } else { reserve_destination.get_reserved_destination(false) }, + reserve_destination, + amount, + } + } + + /// Tell the wallet to remove the key used by this output from the keypool + pub fn keep_key(&mut self) { + self.reserve_destination.keep_destination(); + } + + /// Tell the wallet to return the key used by this output to the keypool + pub fn return_key(&mut self) { + self.reserve_destination.return_destination(); + } + + /// Try update the amount of this output. Returns true if it was successful and false if not (e.g. insufficient amount left). + pub fn update_amount(&mut self, new_amount: u64, amount_left: u64) -> bool { + if new_amount - self.amount > amount_left { + return false; + } + + self.amount = new_amount; + return true; + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/models/tx_destination.rs b/dash-spv-coinjoin/src/models/tx_destination.rs new file mode 100644 index 00000000..92b226fe --- /dev/null +++ b/dash-spv-coinjoin/src/models/tx_destination.rs @@ -0,0 +1,8 @@ +/** + * A txout script template with a specific destination. It is either: + * * CNoDestination: no destination set + * * CKeyID: TxoutType::PUBKEYHASH destination + * * CScriptID: TxoutType::SCRIPTHASH destination + * A CTxDestination is the internal data type encoded in a bitcoin address + */ +pub type TxDestination = Option>; diff --git a/dash-spv-coinjoin/src/models/tx_outpoint.rs b/dash-spv-coinjoin/src/models/tx_outpoint.rs new file mode 100644 index 00000000..c5e135a0 --- /dev/null +++ b/dash-spv-coinjoin/src/models/tx_outpoint.rs @@ -0,0 +1,45 @@ +use std::io::{Error, Read, Write}; +use dash_spv_masternode_processor::crypto::byte_util::{Reversable, UInt256}; +use dash_spv_masternode_processor::consensus::encode; + +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct TxOutPoint { + pub hash: UInt256, + pub index: u32, +} + +impl std::fmt::Debug for TxOutPoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TxOutPoint") + .field("hash", &self.hash.reversed()) + .field("index", &self.index) + .finish() + } +} + +impl TxOutPoint { + pub fn new(hash: UInt256, index: u32) -> Self { + TxOutPoint { hash, index } + } +} + +impl encode::Encodable for TxOutPoint { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += self.hash.consensus_encode(&mut writer)?; + offset += self.index.consensus_encode(&mut writer)?; + + Ok(offset) + } +} + +impl encode::Decodable for TxOutPoint { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let hash = UInt256::consensus_decode(&mut d)?; + let index = u32::consensus_decode(&mut d)?; + + Ok(TxOutPoint { hash, index }) + } +} diff --git a/dash-spv-coinjoin/src/models/valid_in_outs.rs b/dash-spv-coinjoin/src/models/valid_in_outs.rs new file mode 100644 index 00000000..c1829405 --- /dev/null +++ b/dash-spv-coinjoin/src/models/valid_in_outs.rs @@ -0,0 +1,17 @@ +use crate::messages::pool_message::PoolMessage; + +pub struct ValidInOuts { + pub result: bool, + pub message_id: PoolMessage, + pub consume_collateral: bool +} + +impl ValidInOuts { + pub fn new() -> Self { + Self { + result: true, + message_id: PoolMessage::MsgNoErr, + consume_collateral: false + } + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/tests/coinjoin.rs b/dash-spv-coinjoin/src/tests/coinjoin.rs new file mode 100644 index 00000000..00d6c706 --- /dev/null +++ b/dash-spv-coinjoin/src/tests/coinjoin.rs @@ -0,0 +1,129 @@ +use std::ffi::c_void; +use std::{collections::HashMap, io::Cursor}; +use dash_spv_masternode_processor::ffi::unboxer::unbox_any; +use dash_spv_masternode_processor::types; +use dash_spv_masternode_processor::{hashes::hex::FromHex, consensus::Decodable}; +use dash_spv_masternode_processor::tx::transaction::Transaction; + +use crate::ffi::input_value::InputValue; +use crate::coinjoin::CoinJoin; + +#[test] +pub fn test_coinjoin() { + let smallest = CoinJoin::get_smallest_denomination(); + assert_eq!(100001, smallest); +} + +struct DenomTest { + amount: u64, + is_denomination: bool, + string_value: String, +} + +#[test] +fn standard_denomination_test() { + let tests = vec![ + DenomTest { amount: 1000010000, is_denomination: true, string_value: "10.0001".to_string() }, + DenomTest { amount: 100001000, is_denomination: true, string_value: "1.00001".to_string() }, + DenomTest { amount: 10000100, is_denomination: true, string_value: "0.100001".to_string() }, + DenomTest { amount: 1000010, is_denomination: true, string_value: "0.0100001".to_string() }, + DenomTest { amount: 100001, is_denomination: true, string_value: "0.00100001".to_string() }, + DenomTest { amount: 10000, is_denomination: false, string_value: "N/A".to_string() }, + DenomTest { amount: 20000, is_denomination: false, string_value: "N/A".to_string() }, + DenomTest { amount: 1000, is_denomination: false, string_value: "N/A".to_string() }, + DenomTest { amount: 546, is_denomination: false, string_value: "N/A".to_string() }, + DenomTest { amount: 1000000000, is_denomination: false, string_value: "N/A".to_string() }, + ]; + + for test in tests.iter() { + assert_eq!(test.is_denomination, CoinJoin::is_denominated_amount(test.amount)); + assert_eq!(test.string_value, CoinJoin::denomination_to_string(CoinJoin::amount_to_denomination(test.amount))); + } + + assert_eq!(100001, CoinJoin::get_smallest_denomination()); + + for value in CoinJoin::get_standard_denominations().iter() { + assert_eq!(*value as i64, CoinJoin::denomination_to_amount(CoinJoin::amount_to_denomination(*value))); + } +} + +#[test] +fn test_collateral() { + let good_collateral_values = vec![10000, 12345, 32123, 19000]; + let bad_collateral_values = vec![9999, 40001, 100000, 100001]; + + for value in good_collateral_values.iter() { + assert!(CoinJoin::is_collateral_amount(*value)); + } + + for value in bad_collateral_values.iter() { + assert!(!CoinJoin::is_collateral_amount(*value)); + } +} + +#[test] +fn rounds_string_test() { + let mut map = HashMap::new(); + map.insert(0, "coinjoin"); + map.insert(16, "coinjoin"); + map.insert(-4, "bad index"); + map.insert(-3, "collateral"); + map.insert(-2, "non-denominated"); + map.insert(-1, "no such tx"); + + for (rounds, expected_str) in map { + assert_eq!(expected_str, CoinJoin::get_rounds_string(rounds)); + } +} + +#[test] +fn is_collateral_valid_test() { + let payload = Vec::from_hex("0100000001cb1768cae4d44860a6ae18fec6d81f14fa84de48f0027a83107889671c1f1d54000000006a47304402202edab2fb737f7672bd9898e00855a86ca3bdc60a676a16766edb505370e9e0d50220139fd47f674e2ccee32139cf7a82e441f6f2c7d79d7135ac900a3a836591ae9301210262ffa9b2c936262abd869ead9cfde301d29adbe3d4b18d8cd6a150d45e61d656ffffffff0130750000000000001976a914d1a0b93ec28bba201c03fb01a934727782c7b9e288ac00000000").unwrap(); + let mut cursor = Cursor::new(&payload); + let tx = Transaction::consensus_decode(&mut cursor).unwrap(); + + let coinjoin = CoinJoin::new( + good_input_value, + has_chain_lock, + destroy_input_value, + std::ptr::null() + ); + + assert!(coinjoin.is_collateral_valid(&tx, true)); + + let coinjoin = CoinJoin::new( + bad_input_value, + has_chain_lock, + destroy_input_value, + std::ptr::null() + ); + + assert!(!coinjoin.is_collateral_valid(&tx, true)); +} + +extern "C" fn good_input_value( + _prevout_hash: *mut [u8; 32], + _index: u32, + _context: *const c_void, +) -> *mut InputValue { + Box::into_raw(Box::new(InputValue { is_valid: true, value: 40000 })) +} + +extern "C" fn bad_input_value( + _prevout_hash: *mut [u8; 32], + _index: u32, + _context: *const c_void, +) -> *mut InputValue { + Box::into_raw(Box::new(InputValue { is_valid: true, value: 10000 })) +} + +extern "C" fn has_chain_lock( + _block: *mut types::Block, + _context: *const c_void, +) -> bool { + true +} + +unsafe extern "C" fn destroy_input_value(input_value: *mut InputValue) { + let _res = unbox_any(input_value); +} diff --git a/dash-spv-coinjoin/src/tests/messages.rs b/dash-spv-coinjoin/src/tests/messages.rs new file mode 100644 index 00000000..62d6741a --- /dev/null +++ b/dash-spv-coinjoin/src/tests/messages.rs @@ -0,0 +1,242 @@ +use std::io::Cursor; +use dash_spv_masternode_processor::consensus::{Decodable, Encodable}; +use dash_spv_masternode_processor::hashes::hex::{FromHex, ToHex}; +use dash_spv_masternode_processor::crypto::byte_util::{AsBytes, Reversable}; +use dash_spv_masternode_processor::crypto::{UInt256, UInt384, UInt768}; +use dash_spv_masternode_processor::models::OperatorPublicKey; +use dash_spv_masternode_processor::tx::Transaction; +use crate::messages::{self, CoinJoinEntry}; +use crate::messages::coinjoin_broadcast_tx::CoinJoinBroadcastTx; + +#[test] +pub fn test_coinjoin_accept_message() { + // CoinJoinAcceptMessage(denomination=16, txCollateral=956685418a5c4fecaedf74d2ac56862e22830658b6b256e4e2dea0b665e1f756) + let payload = Vec::from_hex("100000000100000001c493c126391c84670115d1d5ed81b27a741a99a8942289bc82396da0b490795c000000006b483045022100d9899056ed6aceee4411b965a07a4a624fc43faca0112fae49129101942ae42f02205b92eefecf87975a356fd80a918978da49594baf8bb1082f527542e56f4f191d012102bc626099759860a95d13a6cdeb754efa5deec2178cc65142b92bbd90ab4d1364ffffffff01204e0000000000001976a914b2705af1d15ee81e96fce5ec49cd9ed6639362f188ac00000000").unwrap(); + let tx_id = UInt256::from_hex("956685418a5c4fecaedf74d2ac56862e22830658b6b256e4e2dea0b665e1f756").unwrap().reversed(); + + let mut cursor = Cursor::new(&payload); + let dca = messages::CoinJoinAcceptMessage::consensus_decode(&mut cursor).unwrap(); + + let mut buffer = Vec::new(); + dca.consensus_encode(&mut buffer).unwrap(); + + assert_eq!(16, dca.denomination); + assert_eq!(buffer.to_hex(), payload.to_hex()); + assert_eq!(Some(tx_id), dca.tx_collateral.tx_hash); +} + + +#[test] +pub fn test_coinjoin_complete_message() { + // CoinJoinComplete(msgSessionID=549379, msgMessageID=20) + let payload = Vec::from_hex("0362080014000000").unwrap(); + + let mut cursor = Cursor::new(&payload); + let dsc = messages::CoinJoinCompleteMessage::consensus_decode(&mut cursor).unwrap(); + + let from_ctor = messages::CoinJoinCompleteMessage { + msg_session_id: 549379, + msg_message_id: messages::PoolMessage::MsgSuccess + }; + let mut buffer = Vec::new(); + from_ctor.consensus_encode(&mut buffer).unwrap(); + + assert_eq!(20, dsc.msg_message_id.value()); + assert_eq!(549379, dsc.msg_session_id); + assert_eq!(buffer.to_hex(), payload.to_hex()); +} + +#[test] +pub fn test_coinjoin_final_transaction() { + // CoinJoinFinalTransaction(msgSessionID=512727, transaction=a57015f0f8c85cdee8ab47d9bb7792c23c21bfbad1a19b7b4368915ba970fae1) + let payload = Vec::from_hex("d7d20700020000000424e55da190e79da9540b0fc87e859261e4a2a33530a005cab68f5cd3ece0234a0200000000fffffffff7b9fdaf651dc308b6f538ac9dae090268f08f424fd91c705fe5d12174999f5d0400000000ffffffff20d63b2ca93309e3526cac6fab31545bd932ba705e7011e720ba69af59baaba10000000000ffffffff20d63b2ca93309e3526cac6fab31545bd932ba705e7011e720ba69af59baaba10300000000ffffffff044a420f00000000001976a914257b0482306f29fe7d97fb8b847746ba4a1606e588ac4a420f00000000001976a9146662e6130b7b1f06778ae1e24b687db752e1a83d88ac4a420f00000000001976a914ac2faba75d50cdd8dff86b8a457c45201aefb96f88ac4a420f00000000001976a914b89ab9894c767e22e85e2164b9f65fc0a4c3dc7e88ac00000000").unwrap(); + let tx_id = UInt256::from_hex("a57015f0f8c85cdee8ab47d9bb7792c23c21bfbad1a19b7b4368915ba970fae1").unwrap().reversed(); + + let mut cursor = Cursor::new(&payload); + let dsf = messages::CoinJoinFinalTransaction::consensus_decode(&mut cursor).unwrap(); + + let tx_hash = dsf.tx.tx_hash; + let from_ctor = messages::CoinJoinFinalTransaction { + msg_session_id: 512727, + tx: dsf.tx + }; + let mut buffer = Vec::new(); + from_ctor.consensus_encode(&mut buffer).unwrap(); + + assert_eq!(512727, dsf.msg_session_id); + assert_eq!(Some(tx_id), tx_hash); + assert_eq!(buffer.to_hex(), payload.to_hex()); +} + +#[test] +pub fn test_coinjoin_status_update_from_payload() { + // CoinJoinStatusUpdate(sessionID=783283, state=POOL_STATE_QUEUE, statusUpdate=STATUS_REJECTED, messageID=ERR_DENOM) + let mut payload = Vec::from_hex("b3f30b00010000000000000001000000").unwrap(); + let mut cursor = Cursor::new(&payload); + let mut dssu = messages::CoinJoinStatusUpdate::consensus_decode(&mut cursor).unwrap(); + + assert_eq!(783283, dssu.session_id); + assert_eq!(messages::PoolState::Queue, dssu.pool_state); + assert_eq!(messages::PoolStatusUpdate::Rejected, dssu.status_update); + assert_eq!(messages::PoolMessage::ErrDenom, dssu.message_id); + + payload = Vec::from_hex("d7d20700030000000100000013000000").unwrap(); + cursor = Cursor::new(&payload); + dssu = messages::CoinJoinStatusUpdate::consensus_decode(&mut cursor).unwrap(); + + assert_eq!(512727, dssu.session_id); + assert_eq!(messages::PoolState::Signing, dssu.pool_state); + assert_eq!(messages::PoolStatusUpdate::Accepted, dssu.status_update); + assert_eq!(messages::PoolMessage::MsgNoErr, dssu.message_id); +} + +#[test] +pub fn test_coinjoin_status_update_from_ctor() { + let payload = Vec::from_hex("5faa0c00010000000100000013000000").unwrap(); + let mut cursor = Cursor::new(&payload); + let dssu = messages::CoinJoinStatusUpdate::consensus_decode(&mut cursor).unwrap(); + + assert_eq!(830047, dssu.session_id); + assert_eq!(messages::PoolState::Queue, dssu.pool_state); + assert_eq!(messages::PoolStatusUpdate::Accepted, dssu.status_update); + assert_eq!(messages::PoolMessage::MsgNoErr, dssu.message_id); + + let from_ctor = messages::CoinJoinStatusUpdate { + session_id: 830047, + pool_state: messages::PoolState::Queue, + status_update: messages::PoolStatusUpdate::Accepted, + message_id: messages::PoolMessage::MsgNoErr + }; + + let mut buffer = Vec::new(); + from_ctor.consensus_encode(&mut buffer).unwrap(); + + assert_eq!(buffer.to_hex(), payload.to_hex()); +} + +#[test] +pub fn coinjoin_signed_inputs_round_test() { + let tx_data = Vec::from_hex("02000000042607763cf6eceb2478060ead38fbb3151b7676b6a243e78b58c420a4ad99cb05010000006a47304402201f95f3a194bd51c521adcd46173d3d5c9bd2dd148004dd1da72e686fd6d946e4022020e34d85cd817aff0663b133915ca2eda5ecd5d5a93fba33f2e9644f1d1513a3012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffe27ecbb210e98a5d2dba6e3bfa0732b8f6371155c3f8bd0420027d2eb3d24a7d010000006b483045022100c7d5c710ebdf8a2526389347823c3de83b3da498eeac5d1e9001e2e86f4cd0d002200e91ee98abc4f5fb5a78e8e80ed6fd17697a706e7118f87e545d8fdad65a845b012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff70a65da4b8d4438058c2e8f36811577cdb244d33c7973644386259135e3635a3010000006b483045022100d1c279574bdb0a4c72b6a11247f2945746b50f3a847c9c6925f0badfa8f5827a0220059884f1e9099fcfbb4966cced355e764ddf18bc60a3e03a3804c0c9b20618a4012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff4605e08cc9758029e89705c41872f063854684b5abf2020e56aca53f161b3fea000000006b483045022100f5afc8c1e722b25532b0a3561f0c37cf80bcd288a40fa0ced53d9a137f06dbc8022067c8ad28484b4a504f74cc7ad754ab4b87f0fbb46a4725e915b625eb000be8fd012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914b889fb3449a36530c85d9689c4773c5cd1ba223388ac51844c8c060000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb1f80a00").unwrap(); + let mut cursor = Cursor::new(&tx_data); + let tx = Transaction::consensus_decode(&mut cursor).unwrap(); + + let dss = messages::CoinJoinSignedInputs { inputs: tx.inputs }; // using inputs from the transaction above + let mut buffer = Vec::new(); + dss.consensus_encode(&mut buffer).unwrap(); + + cursor = Cursor::new(&buffer); + let from_bytes = messages::CoinJoinSignedInputs::consensus_decode(&mut cursor).unwrap(); + + assert_eq!(4, from_bytes.inputs.len()); + assert_eq!(4, dss.inputs.len()); + assert_eq!(from_bytes.inputs[0].input_hash, dss.inputs[0].input_hash); + assert_eq!(from_bytes.inputs[2].input_hash, dss.inputs[2].input_hash); +} + +#[test] +pub fn coinjoin_entry_round_test() { + let mut tx_data = Vec::from_hex("02000000042607763cf6eceb2478060ead38fbb3151b7676b6a243e78b58c420a4ad99cb05010000006a47304402201f95f3a194bd51c521adcd46173d3d5c9bd2dd148004dd1da72e686fd6d946e4022020e34d85cd817aff0663b133915ca2eda5ecd5d5a93fba33f2e9644f1d1513a3012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffe27ecbb210e98a5d2dba6e3bfa0732b8f6371155c3f8bd0420027d2eb3d24a7d010000006b483045022100c7d5c710ebdf8a2526389347823c3de83b3da498eeac5d1e9001e2e86f4cd0d002200e91ee98abc4f5fb5a78e8e80ed6fd17697a706e7118f87e545d8fdad65a845b012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff70a65da4b8d4438058c2e8f36811577cdb244d33c7973644386259135e3635a3010000006b483045022100d1c279574bdb0a4c72b6a11247f2945746b50f3a847c9c6925f0badfa8f5827a0220059884f1e9099fcfbb4966cced355e764ddf18bc60a3e03a3804c0c9b20618a4012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff4605e08cc9758029e89705c41872f063854684b5abf2020e56aca53f161b3fea000000006b483045022100f5afc8c1e722b25532b0a3561f0c37cf80bcd288a40fa0ced53d9a137f06dbc8022067c8ad28484b4a504f74cc7ad754ab4b87f0fbb46a4725e915b625eb000be8fd012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914b889fb3449a36530c85d9689c4773c5cd1ba223388ac51844c8c060000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb1f80a00").unwrap(); + let mut cursor = Cursor::new(&tx_data); + let tx1 = Transaction::consensus_decode(&mut cursor).unwrap(); + + tx_data = Vec::from_hex("01000000033f90cbc2d751c77358b3ff37efd72936b389a17b9ec72bdec4678394814cfe2d000000006a473044022050d2f3b6f097f1973b29bb5a0e98f307f6fc338bb8d29e4a7eb257eebd147ccd022055f88aa06cf90aec97991db9c351fd622fa60fe2cb6bbe6df2ecfef03ca047fa012102d336120a91d7d3497056715f6078e36c56e84c41038cf630260ef3245f6ba39effffffff94cae0fa480e004218a66ea7eae8c0a1a39dbd8ebba966004ddfdcac1e11f089000000006b483045022100ed1fbe54b90c8d69e616b79ba5e03e192bdee6b26f66d40d9da14ae7c7e64a9c022062c54fb1635937a38f3b43b504777c9faf357734cad6f53130870f7e980a3be60121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff3e2611f35c7a2fefadce6b115ce8e14b31b627667af9c04909c0ddcceb8294a3000000006a473044022036bed2e8600ed1a715618ca398553254c14fcea824b77ed784cee5f5b23b84df022041c4821e6e639169ddc891e4d6b4e146e5f4684e5687daf5fcce2fd1f73392230121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff0260182300000000001976a9140205411ec940f9139ea72e3a999d21fceff671e688ac4dc27200000000001976a91425b2b9126bf32e6115a813d019e72b7b9106211b88ac00000000").unwrap(); + cursor = Cursor::new(&tx_data); + let tx2 = Transaction::consensus_decode(&mut cursor).unwrap(); + + let dsi = messages::CoinJoinEntry { + mixing_inputs: tx1.inputs, + mixing_outputs: tx1.outputs, + tx_collateral: tx2 + }; + + let mut buffer = Vec::new(); + dsi.consensus_encode(&mut buffer).unwrap(); + + cursor = Cursor::new(&buffer); + let from_bytes = messages::CoinJoinEntry::consensus_decode(&mut cursor).unwrap(); + + assert_eq!(4, from_bytes.mixing_inputs.len()); + assert_eq!(4, dsi.mixing_inputs.len()); + assert_eq!(2, from_bytes.mixing_outputs.len()); + assert_eq!(2, dsi.mixing_outputs.len()); + + assert_eq!(from_bytes.mixing_inputs[0].input_hash, dsi.mixing_inputs[0].input_hash); + assert_eq!(from_bytes.mixing_inputs[2].input_hash, dsi.mixing_inputs[2].input_hash); + + assert_eq!(from_bytes.mixing_outputs[0].amount, dsi.mixing_outputs[0].amount); + assert_eq!(from_bytes.mixing_outputs[1].amount, dsi.mixing_outputs[1].amount); + + assert_eq!(from_bytes.tx_collateral.tx_hash, dsi.tx_collateral.tx_hash); +} + +#[test] +pub fn coinjoin_entry_test() { + let dsi_message = Vec::from_hex("05a57b11f764c258688b0dd9557147aafac15165729b65a09180436f56a4c0709d0100000000ffffffff6eb1d7c1ba2dd0bab9c158e9aa9535614c518e700c7155ac95f5f8db70dd235a0000000000ffffffff2126f88ff1a1d77012083cf291ce395d37079ecff1c3ab30c3869d5493b387800000000000ffffffffc692fbe122b3934d1926b1f19b14137b24a9a7182937d35e99ad7da24aae4dab0200000000ffffffffac65973a3c7c45ad2ddb033ca2c224b7a151f847716d5449ba912b3868ff71c40000000000ffffffff0100000001dd86755f2cb8d67c52da2fc42c9a97a2b08e6de01f94037390d46fae68e8ed99000000006b483045022100954aa1d666906e78bcc42806874f153d31d5e1e4ae18dcadcbf8dbfc1123e3900220732c82db96098bbee7a69f284464d5233de0e2adf23401552bf2eba1623509fb0121036bfa0b828ff9b020750e765158495979bea2134b13386e6655e00ce1b9cbe980ffffffff0110270000000000001976a9146f6293e33e78ae0f2d74ba1922b86794b9efb08288ac0000000005a1860100000000001976a914b0a180dfd44ba7066f5f42f3eaf431f0cd668fe388aca1860100000000001976a914799bcbf0ff47c4e83fbf1db27a337cc89ed8407088aca1860100000000001976a914060ae9e82d52804b66911e8aa4e94d88665ec19f88aca1860100000000001976a914a48fd783b7a6aad534a91f941a9665c8ecf0964388aca1860100000000001976a91466dd8d620782a0d89e63f0ff2d7935c46e053a4288ac").unwrap(); + let mut cursor = Cursor::new(&dsi_message); + let entry = CoinJoinEntry::consensus_decode(&mut cursor).unwrap(); + + let tx_collateral_bytes = Vec::from_hex("0100000001dd86755f2cb8d67c52da2fc42c9a97a2b08e6de01f94037390d46fae68e8ed99000000006b483045022100954aa1d666906e78bcc42806874f153d31d5e1e4ae18dcadcbf8dbfc1123e3900220732c82db96098bbee7a69f284464d5233de0e2adf23401552bf2eba1623509fb0121036bfa0b828ff9b020750e765158495979bea2134b13386e6655e00ce1b9cbe980ffffffff0110270000000000001976a9146f6293e33e78ae0f2d74ba1922b86794b9efb08288ac00000000").unwrap(); + cursor = Cursor::new(&tx_collateral_bytes); + let collateral_tx = Transaction::consensus_decode(&mut cursor).unwrap(); + assert_eq!(collateral_tx, entry.tx_collateral); + assert_eq!(5, entry.mixing_inputs.len()); + assert_eq!(5, entry.mixing_outputs.len()); +} + +#[test] +pub fn coinjoin_broadcast_tx_round_test() { + let tx_data = Vec::from_hex("02000000042607763cf6eceb2478060ead38fbb3151b7676b6a243e78b58c420a4ad99cb05010000006a47304402201f95f3a194bd51c521adcd46173d3d5c9bd2dd148004dd1da72e686fd6d946e4022020e34d85cd817aff0663b133915ca2eda5ecd5d5a93fba33f2e9644f1d1513a3012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffe27ecbb210e98a5d2dba6e3bfa0732b8f6371155c3f8bd0420027d2eb3d24a7d010000006b483045022100c7d5c710ebdf8a2526389347823c3de83b3da498eeac5d1e9001e2e86f4cd0d002200e91ee98abc4f5fb5a78e8e80ed6fd17697a706e7118f87e545d8fdad65a845b012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff70a65da4b8d4438058c2e8f36811577cdb244d33c7973644386259135e3635a3010000006b483045022100d1c279574bdb0a4c72b6a11247f2945746b50f3a847c9c6925f0badfa8f5827a0220059884f1e9099fcfbb4966cced355e764ddf18bc60a3e03a3804c0c9b20618a4012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff4605e08cc9758029e89705c41872f063854684b5abf2020e56aca53f161b3fea000000006b483045022100f5afc8c1e722b25532b0a3561f0c37cf80bcd288a40fa0ced53d9a137f06dbc8022067c8ad28484b4a504f74cc7ad754ab4b87f0fbb46a4725e915b625eb000be8fd012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914b889fb3449a36530c85d9689c4773c5cd1ba223388ac51844c8c060000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb1f80a00").unwrap(); + let mut cursor = Cursor::new(&tx_data); + let tx = Transaction::consensus_decode(&mut cursor).unwrap(); + let pro_tx_hash = UInt256::from_hex("3fc39b657385a7d2e824ca2644bdcddcef0bc25775c30c4f747345ef4f1c7503").unwrap().reversed(); + let signature = Vec::from_hex("998c5118eef9a89bfe5c6b961a8cc5af52cb00d0394688e78b23194699f7356cece6f8af63fdb0c28c2728c05325a6fe").unwrap(); + let signature_time: i64 = 1702813411; + + let dstx = CoinJoinBroadcastTx::new(tx, pro_tx_hash, Some(signature), signature_time); + let mut buffer = Vec::new(); + dstx.consensus_encode(&mut buffer).unwrap(); + + cursor = Cursor::new(&buffer); + let from_bytes = CoinJoinBroadcastTx::consensus_decode(&mut cursor).unwrap(); + + assert_eq!(dstx.tx.tx_hash, from_bytes.tx.tx_hash); + assert_eq!(dstx.pro_tx_hash, from_bytes.pro_tx_hash); + assert_eq!(dstx.signature.unwrap().to_hex(), from_bytes.signature.unwrap().to_hex()); + assert_eq!(dstx.signature_time, from_bytes.signature_time); +} + +#[test] +pub fn coinjoin_queue_message_test() { + let payload = Vec::from_hex("0800000036c6298f595939395ec930f936452726f33a311a79b2abe290ae01aad020011652498465000000000060a4f1ebf98b3b2df98c6375d391c4aba667edbaccb31610a8ded1eaba92c87ce59d2dcbea67fd59d212edd87553fbbeac0041bc514782b3ae5184f6d194c3dbdd8f94b5ce5e0e358aed3557b18188d51cbbcda80fba2ff7dabb808029ba255431").unwrap(); + let mut cursor = Cursor::new(&payload); + let queue_from_hex = messages::CoinJoinQueueMessage::consensus_decode(&mut cursor).unwrap(); + + assert_eq!(8, queue_from_hex.denomination); + assert_eq!(UInt256::from_hex("160120d0aa01ae90e2abb2791a313af326274536f930c95e393959598f29c636").unwrap().reversed(), queue_from_hex.pro_tx_hash); + assert_eq!(1703168338, queue_from_hex.time); + assert_eq!(false, queue_from_hex.ready); + assert_eq!(UInt768::from_hex("a4f1ebf98b3b2df98c6375d391c4aba667edbaccb31610a8ded1eaba92c87ce59d2dcbea67fd59d212edd87553fbbeac0041bc514782b3ae5184f6d194c3dbdd8f94b5ce5e0e358aed3557b18188d51cbbcda80fba2ff7dabb808029ba255431").unwrap(), queue_from_hex.signature.unwrap()); + + let queue_from_ctor = messages::CoinJoinQueueMessage { + denomination: 8, + pro_tx_hash: UInt256::from_hex("160120d0aa01ae90e2abb2791a313af326274536f930c95e393959598f29c636").unwrap().reversed(), + time: 1703168338, + ready: false, + signature: UInt768::from_hex("a4f1ebf98b3b2df98c6375d391c4aba667edbaccb31610a8ded1eaba92c87ce59d2dcbea67fd59d212edd87553fbbeac0041bc514782b3ae5184f6d194c3dbdd8f94b5ce5e0e358aed3557b18188d51cbbcda80fba2ff7dabb808029ba255431").ok(), + tried: false + }; + + let mut buffer = Vec::new(); + queue_from_ctor.consensus_encode(&mut buffer).unwrap(); + assert_eq!(queue_from_hex, queue_from_ctor); + assert_eq!(buffer.to_hex(), payload.to_hex()); + assert_eq!(false, queue_from_ctor.tried); + + let masternode_operator_key = OperatorPublicKey { + data: UInt384::from_hex("066d57a6451b7800c1c2a6c6e04fe73ec2e1c95e492bacae760ad2f79ca3c30727ec9bf0daea43c08ff1ad6c2cf07612").unwrap(), + version: 1 + }; + println!("op_key raw data: {}", masternode_operator_key.data.0.to_hex()); + + assert!(queue_from_ctor.check_signature(masternode_operator_key)); +} diff --git a/dash-spv-coinjoin/src/tests/mod.rs b/dash-spv-coinjoin/src/tests/mod.rs new file mode 100644 index 00000000..3ef32bb7 --- /dev/null +++ b/dash-spv-coinjoin/src/tests/mod.rs @@ -0,0 +1,3 @@ +pub mod messages; +pub mod coinjoin; +pub mod models; \ No newline at end of file diff --git a/dash-spv-coinjoin/src/tests/models.rs b/dash-spv-coinjoin/src/tests/models.rs new file mode 100644 index 00000000..08ca207a --- /dev/null +++ b/dash-spv-coinjoin/src/tests/models.rs @@ -0,0 +1,25 @@ +use std::io::Cursor; +use dash_spv_masternode_processor::hashes::hex::{FromHex, ToHex}; +use dash_spv_masternode_processor::crypto::{byte_util::Reversable, UInt256}; +use dash_spv_masternode_processor::consensus::{Decodable, Encodable}; +use crate::models::tx_outpoint::TxOutPoint; + +#[test] +pub fn test_transaction_outpoint_payload() { + let hex = "e2f910eb47e2dde768b9f89e1a84607ac559c0f9628ff0b44b49de0a92e5b0ce00000000"; + let outpoint_data = Vec::from_hex(hex).unwrap(); + let mut cursor = Cursor::new(&outpoint_data); + let outpoint = TxOutPoint::consensus_decode(&mut cursor).unwrap(); + + let hash = UInt256::from_hex("ceb0e5920ade494bb4f08f62f9c059c57a60841a9ef8b968e7dde247eb10f9e2").unwrap().reversed(); + + assert_eq!(hash, outpoint.hash); + assert_eq!(0, outpoint.index); + + let from_ctor = TxOutPoint { hash, index: 0 }; + let mut buffer = Vec::new(); + from_ctor.consensus_encode(&mut buffer).unwrap(); + + assert_eq!(hash, outpoint.hash); + assert_eq!(hex, buffer.to_hex()); +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/utils/coin_format.rs b/dash-spv-coinjoin/src/utils/coin_format.rs new file mode 100644 index 00000000..0ea257b0 --- /dev/null +++ b/dash-spv-coinjoin/src/utils/coin_format.rs @@ -0,0 +1,35 @@ +use dash_spv_masternode_processor::chain::params::DUFFS; + +pub trait CoinFormat { + fn to_friendly_string(self) -> String; +} + +impl CoinFormat for i64 { + fn to_friendly_string(self) -> String { + let sign = if self < 0 { "-" } else { "" }; + let abs_amount = self.abs(); + let quotient = abs_amount / DUFFS as i64; + let remainder = abs_amount % DUFFS as i64; + + if remainder == 0 { + format!("{}{}", sign, quotient) + } else { + let decimal_part = format!("{:08}", remainder).trim_end_matches('0').to_string(); + format!("{}{}.{}", sign, quotient, decimal_part) + } + } +} + +impl CoinFormat for u64 { + fn to_friendly_string(self) -> String { + let quotient = self / DUFFS; + let remainder = self % DUFFS; + + if remainder == 0 { + format!("{}", quotient) + } else { + let decimal_part = format!("{:08}", remainder).trim_end_matches('0').to_string(); + format!("{}.{}", quotient, decimal_part) + } + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/utils/key_holder.rs b/dash-spv-coinjoin/src/utils/key_holder.rs new file mode 100644 index 00000000..413e3214 --- /dev/null +++ b/dash-spv-coinjoin/src/utils/key_holder.rs @@ -0,0 +1,29 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::{wallet_ex::WalletEx, models::{reserve_destination::ReserveDestination, tx_destination::TxDestination}}; + +#[derive(Clone)] +pub(crate) struct KeyHolder { + reserve_destination: ReserveDestination, // TODO(dashj): use ReserveKey + pub destination: TxDestination, +} + +impl KeyHolder { + pub fn new(wallet: Rc>) -> Self { + // Get the next CoinJoinKey? + let mut reserve_destination = ReserveDestination::new(wallet); + let destination = reserve_destination.get_reserved_destination(false); + Self { + reserve_destination, + destination, + } + } + + pub fn keep_key(&mut self) { + self.reserve_destination.keep_destination(); + } + + pub fn return_key(&mut self) { + self.reserve_destination.return_destination(); + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/utils/key_holder_storage.rs b/dash-spv-coinjoin/src/utils/key_holder_storage.rs new file mode 100644 index 00000000..5b3cef89 --- /dev/null +++ b/dash-spv-coinjoin/src/utils/key_holder_storage.rs @@ -0,0 +1,43 @@ +use std::{cell::RefCell, rc::Rc}; +use tracing::info; +use logging::*; +use crate::{utils::key_holder::KeyHolder, wallet_ex::WalletEx}; + +pub(crate) struct KeyHolderStorage { + storage: Vec, +} + +impl KeyHolderStorage { + pub fn new() -> Self { + Self { + storage: Vec::new(), + } + } + + pub fn add_key(&mut self, wallet: Rc>) -> Option> { + let key_holder = KeyHolder::new(wallet); + let script = key_holder.destination.clone(); + self.storage.push(key_holder); + + return script; + } + + pub fn keep_all(&mut self) { + let mut tmp = self.storage.clone(); + self.storage.clear(); + + if !tmp.is_empty() { + for key_holder in &mut tmp { + key_holder.keep_key(); + } + + log_info!(target: "CoinJoin", "keepAll: {} keys kept", tmp.len()); + } + } + + pub fn return_all(&mut self) { + for key_holder in &mut self.storage { + key_holder.return_key(); + } + } +} \ No newline at end of file diff --git a/dash-spv-coinjoin/src/utils/mod.rs b/dash-spv-coinjoin/src/utils/mod.rs new file mode 100644 index 00000000..ed3d8c28 --- /dev/null +++ b/dash-spv-coinjoin/src/utils/mod.rs @@ -0,0 +1,4 @@ +pub mod coin_format; +pub mod key_holder; +pub mod key_holder_storage; +pub mod transaction_builder; \ No newline at end of file diff --git a/dash-spv-coinjoin/src/utils/transaction_builder.rs b/dash-spv-coinjoin/src/utils/transaction_builder.rs new file mode 100644 index 00000000..e30a944e --- /dev/null +++ b/dash-spv-coinjoin/src/utils/transaction_builder.rs @@ -0,0 +1,307 @@ +use std::cell::RefCell; +use std::fmt; +use std::rc::Rc; +use tracing::{info, debug}; +use logging::*; +use dash_spv_masternode_processor::chain::common::ChainType; +use dash_spv_masternode_processor::chain::params::TX_MIN_OUTPUT_AMOUNT; +use dash_spv_masternode_processor::chain::tx::protocol::TXIN_SEQUENCE; +use dash_spv_masternode_processor::consensus::Encodable; +use dash_spv_masternode_processor::crypto::UInt256; +use dash_spv_masternode_processor::ffi::ByteArray; +use dash_spv_masternode_processor::tx::{Transaction, TransactionInput, TransactionOutput, TransactionType}; +use dash_spv_masternode_processor::util::data_append::DataAppend; + +use crate::coin_selection::compact_tally_item::CompactTallyItem; +use crate::constants::REFERENCE_DEFAULT_MIN_TX_FEE; +use crate::ffi::recepient::Recipient; +use crate::utils::coin_format::CoinFormat; +use crate::wallet_ex::WalletEx; +use crate::models::coin_control::CoinControl; +use crate::models::reserve_destination::ReserveDestination; +use crate::models::transaction_builder_output::TransactionBuilderOutput; + +pub struct TransactionBuilder { + /// Wallet the transaction will be build for + wallet_ex: Rc>, + /// See CTransactionBuilder() for initialization + coin_control: CoinControl, + /// Dummy since we anyway use tallyItem's destination as change destination in coincontrol. + /// Its a member just to make sure ReturnKey can be called in destructor just in case it gets generated/kept + /// somewhere in CWallet code. + dummy_reserve_destination: ReserveDestination, + /// Contains all utxos available to generate this transactions. They are all from the same address. + tally_item: CompactTallyItem, + /// Contains the number of bytes required for a transaction with only the inputs of tallyItems, no outputs + pub bytes_base: i32, + /// Contains the number of bytes required to add one output + bytes_output: i32, + /// Call KeepKey for all keys in destructor if fKeepKeys is true, call ReturnKey for all key if its false. + keep_keys: bool, + /// Contains all outputs already added to the transaction + pub outputs: Vec, + dry_run: bool +} + +impl Drop for TransactionBuilder { + fn drop(&mut self) { + self.clear(); + } +} + +impl<'a> TransactionBuilder { + pub fn new( + wallet_ex: Rc>, + chain_type: ChainType, + tally_item: CompactTallyItem, + dry_run: bool + ) -> Self { + let mut coin_control = CoinControl::new(); + // Generate a feerate which will be used to consider if the remainder is dust and will go into fees or not + coin_control.discard_fee_rate = REFERENCE_DEFAULT_MIN_TX_FEE; + // Generate a feerate which will be used by calculations of this class and also by CWallet::CreateTransaction + coin_control.fee_rate = REFERENCE_DEFAULT_MIN_TX_FEE / 1000; + // Change always goes back to origin + coin_control.dest_change = tally_item.tx_destination.clone(); + // Only allow tallyItems inputs for tx creation + coin_control.allow_other_inputs = false; + + let mut dummy_tx = Transaction { + inputs: Vec::new(), + outputs: Vec::new(), + lock_time: 0, + version: 0, + tx_hash: None, + tx_type: TransactionType::Classic, + payload_offset: 0, + block_height: 0 + }; + + // Select all tallyItem outputs in the coinControl so that CreateTransaction knows what to use + for coin in &tally_item.input_coins { + coin_control.select(coin.tx_outpoint.clone()); + let input = TransactionInput { + input_hash: coin.tx_outpoint.hash, + index: coin.tx_outpoint.index, + script: None, + signature: Some(Vec::new()), + sequence: TXIN_SEQUENCE + }; + dummy_tx.inputs.push(input); + } + + let bytes_base = TransactionBuilder::calculate_maximum_signed_tx_size(wallet_ex.clone(), &mut dummy_tx); + let bytes_output: i32 = TransactionBuilder::calculate_bytes_output(chain_type); + + let mut tx_builder = Self { + wallet_ex: wallet_ex.clone(), + coin_control, + dummy_reserve_destination: ReserveDestination::new(wallet_ex), + tally_item, + bytes_base, + bytes_output, + keep_keys: false, + outputs: Vec::new(), + dry_run + }; + tx_builder.clear(); + tx_builder + } + + /// Check it would be possible to add a single output with the amount amount. Returns true if its possible and false if not. + pub fn could_add_output(&self, amount_output: u64) -> bool { + // Adding another output can change the serialized size of the vout size hence + GetSizeOfCompactSizeDiff() + let bytes = self.get_bytes_total() + self.bytes_output + self.get_size_of_compact_size_diff(1); + return TransactionBuilder::get_amount_left(self.get_amount_initial() as i64, (self.get_amount_used() + amount_output) as i64, self.get_fee(bytes as u64) as i64) >= 0; + } + + /// Check if it's possible to add multiple outputs as vector of amounts. Returns true if its possible to add all of them and false if not. + pub fn could_add_outputs(&self, vec_output_amounts: &[u64]) -> bool { + let mut amount_additional = 0; + let bytes_additional = self.bytes_output * vec_output_amounts.len() as i32; + let vec_len = vec_output_amounts.len(); + + for amount_output in vec_output_amounts { + amount_additional += *amount_output; + } + // Adding other outputs can change the serialized size of the vout size hence + GetSizeOfCompactSizeDiff() + let bytes = self.get_bytes_total() + bytes_additional + self.get_size_of_compact_size_diff(vec_len); + return TransactionBuilder::get_amount_left(self.get_amount_initial() as i64, self.get_amount_used() as i64 + amount_additional as i64, self.get_fee(bytes as u64) as i64) >= 0; + } + + /// Get amount we had available when we started + pub fn get_amount_initial(&self) -> u64 { + return self.tally_item.amount; + } + + /// Helper to calculate static amount left by simply subtracting an used amount and a fee from a provided initial amount. + pub fn get_amount_left(amount_initial: i64, amount_used: i64, fee: i64) -> i64{ + return amount_initial - amount_used - fee; + } + + /// Get the amount currently left to add more outputs. Does respect fees. + pub fn amount_left(&self) -> u64 { + let initial = self.get_amount_initial(); + let used = self.get_amount_used(); + let bytes_total = self.get_bytes_total(); + let fee = self.get_fee(bytes_total as u64); + + return initial.saturating_sub(used).saturating_sub(fee); + } + + /// Check if an amounts should be considered as dust + pub fn is_dust(amount: u64) -> bool { + return TX_MIN_OUTPUT_AMOUNT > amount; + } + + /// Add an output with the amount. Returns a pointer to the output if it could be added and nullptr if not due to insufficient amount left. + pub fn add_zero_output(&mut self) -> bool { + return self.add_output(0) + } + + pub fn add_output(&mut self, amount_output: u64) -> bool { + if self.could_add_output(amount_output) { + self.outputs.push(TransactionBuilderOutput::new(self.wallet_ex.clone(), amount_output as u64, self.dry_run)); + return true; + } + return false; + } + + pub fn commit(&mut self, str_result: &mut String, is_denominating: bool, client_session_id: UInt256) -> bool { + let vec_send: Vec = self.outputs + .iter() + .filter(|x| x.script.is_some()) + .map(|out| Recipient { + script_pub_key: ByteArray::from(out.script.clone().unwrap()), + amount: out.amount + }) + .collect(); + + log_debug!(target: "CoinJoin", "tx_builder.commit: {:?}", vec_send.iter().map(|f| f.amount).collect::>()); + + if !self.wallet_ex.borrow().commit_transaction(&vec_send, self.coin_control.clone(), is_denominating, client_session_id) { + log_debug!(target: "CoinJoin", "tx_builder.commit: Failed to commit transaction"); + str_result.push_str("Failed to commit transaction"); + return false; + } + + log_debug!(target: "CoinJoin", "tx_builder.commit: Transaction committed"); + str_result.push_str("Transaction committed"); + self.keep_keys = true; + return true; + } + + fn calculate_maximum_signed_tx_size(wallet_ex: Rc>, tx: &mut Transaction) -> i32 { + for input in tx.inputs.iter_mut() { + match wallet_ex.borrow().get_wallet_transaction(input.input_hash) { + Some(transaction) => { + assert!(input.index < transaction.outputs.len() as u32, "Index out of bounds"); + input.script = transaction.outputs[input.index as usize].script.clone(); + }, + None => { + // Cannot estimate size without knowing the input details + return -1; + } + } + } + + if let Some(signed_tx) = wallet_ex.borrow().sign_transaction(&tx, false) { + return signed_tx.to_data().len() as i32; + } + + log_info!(target: "CoinJoin", "TxBuilder: Could not sign transaction"); + return -1; + } + + fn calculate_bytes_output(chain_type: ChainType) -> i32 { + let script_map = chain_type.script_map(); + let pub_key = Vec::::script_pub_key_for_address(&TransactionBuilder::get_dummy_address(chain_type), &script_map); + let tx_output = TransactionOutput { amount: 0, script: Some(pub_key), address: None }; + let mut buffer = Vec::new(); + tx_output.consensus_encode(&mut buffer).unwrap(); + + return buffer.len() as i32; + } + + fn get_dummy_address(chain_type: ChainType) -> String { + return if chain_type == ChainType::MainNet { "XqQHMfqiEbmswPk7Ruhfq3WKrgANDunDgG".to_string() } else { "yVkt3e49pAj11jSj4HAnzVAWmy4VD1MwZd".to_string() }; + } + + fn get_bytes_total(&self) -> i32 { + return self.bytes_base + self.outputs.len() as i32 * self.bytes_output + self.get_size_of_compact_size_diff(self.outputs.len()); + } + + fn get_size_of_compact_size_diff(&self, add: usize) -> i32 { + let size = self.outputs.len(); + + return self.get_compact_size_diff(size, size + add); + } + + fn get_compact_size_diff(&self, old_size: usize, new_size: usize) -> i32 { + let mut buffer = Vec::new(); + let old_var_int_size = (old_size as i32).consensus_encode(&mut buffer).unwrap() as i32; + let new_var_int_size = (new_size as i32).consensus_encode(&mut buffer).unwrap() as i32; + + return old_var_int_size - new_var_int_size; + } + + fn get_amount_used(&self) -> u64 { + let mut amount = 0; + for output in &self.outputs { + amount += output.amount; + } + return amount; + } + + /// Get fees based on the number of bytes and the feerate set in CoinControl. + /// NOTE: To get the total transaction fee this should only be called once with the total number of bytes for the transaction to avoid + /// calling CFeeRate::GetFee multiple times with subtotals as this may add rounding errors with each further call. + fn get_fee(&self, bytes: u64) -> u64 { + let mut fee_calc = self.coin_control.fee_rate * bytes; + let required_fee = REFERENCE_DEFAULT_MIN_TX_FEE * bytes / 1000; + + if required_fee > fee_calc { + fee_calc = required_fee; + } + + if fee_calc > REFERENCE_DEFAULT_MIN_TX_FEE * 10 { + fee_calc = REFERENCE_DEFAULT_MIN_TX_FEE; + } + + return fee_calc; + } + + /// Clear the output vector and keep/return the included keys depending on the value of fKeepKeys + pub fn clear(&mut self) { + let mut vec_outputs_tmp = self.outputs.clone(); + self.outputs.clear(); + + for output in &mut vec_outputs_tmp { + if self.keep_keys { + output.keep_key(); + } else { + output.return_key(); + } + } + + // Always return this key + self.dummy_reserve_destination.return_destination(); + } +} + +impl fmt::Display for TransactionBuilder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TransactionBuilder(Amount initial: {}, Amount left: {}, Bytes base: {}, Bytes output: {}, Bytes total: {}, Amount used: {}, Outputs: {}, Fee rate: {}, Discard fee rate: {}, Fee: {})", + self.get_amount_initial().to_friendly_string(), + self.amount_left().to_friendly_string(), + self.bytes_base, + self.bytes_output, + self.get_bytes_total(), + self.get_amount_initial().to_friendly_string(), + self.outputs.len(), + self.coin_control.fee_rate.to_friendly_string(), + self.coin_control.discard_fee_rate.to_friendly_string(), + self.get_fee(self.get_bytes_total() as u64).to_friendly_string() + ) + } +} diff --git a/dash-spv-coinjoin/src/wallet_ex.rs b/dash-spv-coinjoin/src/wallet_ex.rs new file mode 100644 index 00000000..0f0b6379 --- /dev/null +++ b/dash-spv-coinjoin/src/wallet_ex.rs @@ -0,0 +1,730 @@ +use std::cell::RefCell; +use std::collections::{HashSet, HashMap}; +use std::ffi::CString; +use std::rc::Rc; +use std::slice; +use byte::{BytesExt, LE}; +use dash_spv_masternode_processor::chain::tx::protocol::TXIN_SEQUENCE; +use dash_spv_masternode_processor::common::SocketAddress; +use dash_spv_masternode_processor::ffi::unboxer::{unbox_any, unbox_any_vec, unbox_vec, unbox_vec_ptr}; +use dash_spv_masternode_processor::ffi::ByteArray; +use dash_spv_masternode_processor::types::opaque_key::AsCStringPtr; +use rand::seq::SliceRandom; +use dash_spv_masternode_processor::consensus::Encodable; +use dash_spv_masternode_processor::crypto::UInt256; +use dash_spv_masternode_processor::crypto::byte_util::Reversable; +use dash_spv_masternode_processor::ffi::boxer::{boxed, boxed_vec}; +use dash_spv_masternode_processor::ffi::from::FromFFI; +use dash_spv_masternode_processor::ffi::to::ToFFI; +use dash_spv_masternode_processor::secp256k1::rand; +use dash_spv_masternode_processor::tx::{Transaction, TransactionInput}; +use dash_spv_masternode_processor::util::address::address; +use logging::*; +use tracing::{info, error, debug}; +use crate::coin_selection::compact_tally_item::CompactTallyItem; +use crate::coin_selection::input_coin::InputCoin; +use crate::ffi::callbacks::{AddPendingMasternode, AvailableCoins, CommitTransaction, DestroyCoinJoinKeys, DestroyGatheredOutputs, DestroySelectedCoins, DestroyWalletTransaction, DisconnectMasternode, FreshCoinJoinAddress, GetCoinJoinKeys, GetWalletTransaction, InputsWithAmount, IsBlockchainSynced, IsMasternodeOrDisconnectRequested, IsMineInput, SelectCoinsGroupedByAddresses, SendMessage, SignTransaction, StartManagerAsync}; +use crate::coinjoin::CoinJoin; +use crate::constants::MAX_COINJOIN_ROUNDS; +use crate::ffi::recepient::Recipient; +use crate::models::coin_control::{CoinControl, CoinType}; +use crate::models::coinjoin_transaction_input::CoinJoinTransactionInput; +use crate::models::tx_destination::TxDestination; +use crate::models::tx_outpoint::TxOutPoint; +use crate::models::CoinJoinClientOptions; + +pub struct WalletEx { + context: *const std::ffi::c_void, + options: Rc>, + pub locked_coins_set: HashSet, + anonymizable_tally_cached_non_denom: bool, + vec_anonymizable_tally_cached_non_denom: Vec, + anonymizable_tally_cached: bool, + vec_anonymizable_tally_cached: Vec, + map_outpoint_rounds_cache: HashMap, + unused_keys: HashMap>, + // TODO (DashJ): we may not need keyUsage, it is used as a way to audit unusedKeys + key_usage: HashMap, + coinjoin_salt: UInt256, + loaded_keys: bool, + get_wallet_transaction: GetWalletTransaction, + sign_transaction: SignTransaction, + destroy_transaction: DestroyWalletTransaction, + is_mine_input: IsMineInput, + available_coins: AvailableCoins, + destroy_gathered_outputs: DestroyGatheredOutputs, + select_coins: SelectCoinsGroupedByAddresses, + destroy_selected_coins: DestroySelectedCoins, + inputs_with_amount: InputsWithAmount, + fresh_coinjoin_address: FreshCoinJoinAddress, + commit_transaction: CommitTransaction, + is_masternode_or_disconnect_requested: IsMasternodeOrDisconnectRequested, + disconnect_masternode: DisconnectMasternode, + is_synced: IsBlockchainSynced, + send_message: SendMessage, + add_pending_masternode: AddPendingMasternode, + start_manager_async: StartManagerAsync, + get_coinjoin_keys: GetCoinJoinKeys, + destroy_coinjoin_keys: DestroyCoinJoinKeys +} + +impl WalletEx { + pub fn new( + context: *const std::ffi::c_void, + options: Rc>, + get_wallet_transaction: GetWalletTransaction, + sign_transaction: SignTransaction, + destroy_transaction: DestroyWalletTransaction, + is_mine_input: IsMineInput, + available_coins: AvailableCoins, + destroy_gathered_outputs: DestroyGatheredOutputs, + select_coins: SelectCoinsGroupedByAddresses, + destroy_selected_coins: DestroySelectedCoins, + inputs_with_amount: InputsWithAmount, + fresh_coinjoin_address: FreshCoinJoinAddress, + commit_transaction: CommitTransaction, + is_synced: IsBlockchainSynced, + is_masternode_or_disconnect_requested: IsMasternodeOrDisconnectRequested, + disconnect_masternode: DisconnectMasternode, + send_message: SendMessage, + add_pending_masternode: AddPendingMasternode, + start_manager_async: StartManagerAsync, + get_coinjoin_keys: GetCoinJoinKeys, + destroy_coinjoin_keys: DestroyCoinJoinKeys + ) -> Self { + WalletEx { + context, + options: options, + locked_coins_set: HashSet::new(), + anonymizable_tally_cached_non_denom: false, + vec_anonymizable_tally_cached_non_denom: Vec::new(), + anonymizable_tally_cached: false, + vec_anonymizable_tally_cached: Vec::new(), + map_outpoint_rounds_cache: HashMap::new(), + coinjoin_salt: UInt256([0;32]), // TODO: InitCoinJoinSalt ? + loaded_keys: false, + unused_keys: HashMap::with_capacity(1024), + key_usage: HashMap::new(), + get_wallet_transaction, + sign_transaction, + destroy_transaction, + is_mine_input, + available_coins, + destroy_gathered_outputs, + select_coins, + destroy_selected_coins, + inputs_with_amount, + fresh_coinjoin_address, + commit_transaction, + is_masternode_or_disconnect_requested, + disconnect_masternode, + is_synced, + send_message, + add_pending_masternode, + start_manager_async, + get_coinjoin_keys, + destroy_coinjoin_keys + } + } + + pub fn lock_coin(&mut self, outpoint: TxOutPoint) { + let joined_hashes = self.locked_coins_set.iter().map(|coin| coin.hash.reversed().to_string()).collect::>(); + println!("[RUST] CoinJoin: WalletEx: lock_coin, before: {}: {:?}", self.locked_coins_set.len(), joined_hashes); + self.locked_coins_set.insert(outpoint); + let joined_hashes_after = self.locked_coins_set.iter().map(|coin| coin.hash.reversed().to_string()).collect::>(); + println!("[RUST] CoinJoin: WalletEx: lock_coin, after: {}: {:?}", self.locked_coins_set.len(), joined_hashes_after); + self.clear_anonymizable_caches(); + } + + pub fn unlock_coin(&mut self, outpoint: &TxOutPoint) { + let joined_hashes = self.locked_coins_set.iter().map(|coin| coin.hash.reversed().to_string()).collect::>(); + println!("[RUST] CoinJoin: WalletEx: unlock_coin, before: {}: {:?}", self.locked_coins_set.len(), joined_hashes); + let removed = self.locked_coins_set.remove(outpoint); + let joined_hashes_after = self.locked_coins_set.iter().map(|coin| coin.hash.reversed().to_string()).collect::>(); + println!("[RUST] CoinJoin: WalletEx: unlock_coin, removed: {}, after: {}: {:?}", removed, self.locked_coins_set.len(), joined_hashes_after); + self.clear_anonymizable_caches(); + } + + pub fn is_fully_mixed(&mut self, outpoint: TxOutPoint) -> bool { + let rounds = self.get_real_outpoint_coinjoin_rounds(outpoint.clone(), 0); + + // Mix again if we don't have N rounds yet + if rounds < self.options.borrow().coinjoin_rounds { + return false; + } + + // Try to mix a "random" number of rounds more than minimum. + // If we have already mixed N + MaxOffset rounds, don't mix again. + // Otherwise, we should mix again 50% of the time, this results in an exponential decay + // N rounds 50% N+1 25% N+2 12.5%... until we reach N + GetRandomRounds() rounds where we stop. + if rounds < self.options.borrow().coinjoin_rounds + self.options.borrow().coinjoin_random_rounds { + let mut buffer = Vec::new(); + outpoint.consensus_encode(&mut buffer).unwrap(); + buffer.extend_from_slice(&self.coinjoin_salt.reversed().0); + let hash = UInt256::sha256(&buffer); + + if &hash.0.read_with::(&mut 0, LE).unwrap() % 2 == 0 { + return false; + } + } + + true + } + + pub fn get_real_outpoint_coinjoin_rounds(&mut self, outpoint: TxOutPoint, rounds: i32) -> i32 { + let rounds_max = MAX_COINJOIN_ROUNDS + self.options.borrow().coinjoin_random_rounds; + + if rounds >= rounds_max { + // there can only be rounds_max rounds max + return rounds_max - 1; + } + + let mut rounds_ref = *self.map_outpoint_rounds_cache.entry(outpoint.clone()).or_insert(-10); + + if rounds_ref != -10 { + return rounds_ref; + } + + let wtx: Option = self.get_wallet_transaction(outpoint.hash); + + if wtx.is_none() { + // no such tx in this wallet + rounds_ref = -1; + log_debug!(target: "CoinJoin", "FAILED {:?} {} (no such tx)", outpoint, rounds_ref); + self.map_outpoint_rounds_cache.insert(outpoint, rounds_ref); + return rounds_ref; + } + + let transaction = wtx.unwrap(); + // bounds check + if outpoint.index >= transaction.outputs.len() as u32 { + // should never actually hit this + rounds_ref = -4; + log_debug!(target: "CoinJoin", "FAILED {:?} {} (bad index)", outpoint, rounds_ref); + self.map_outpoint_rounds_cache.insert(outpoint, rounds_ref); + return rounds_ref; + } + + let tx_out = &transaction.outputs[outpoint.index as usize]; + + if CoinJoin::is_collateral_amount(tx_out.amount) { + rounds_ref = -3; + log_debug!(target: "CoinJoin", "UPDATED {:?} {} (collateral)", outpoint, rounds_ref); + self.map_outpoint_rounds_cache.insert(outpoint, rounds_ref); + return rounds_ref; + } + + // make sure the final output is non-denominate + if !CoinJoin::is_denominated_amount(tx_out.amount) { + rounds_ref = -2; + log_debug!(target: "CoinJoin", "UPDATED {:?} {} (non-denominated)", outpoint, rounds_ref); + self.map_outpoint_rounds_cache.insert(outpoint, rounds_ref); + return rounds_ref; + } + + for out in &transaction.outputs { + if !CoinJoin::is_denominated_amount(out.amount) { + // this one is denominated but there is another non-denominated output found in the same tx + rounds_ref = 0; + log_debug!(target: "CoinJoin", "UPDATED {:?} {} (non-denominated)", outpoint, rounds_ref); + self.map_outpoint_rounds_cache.insert(outpoint, rounds_ref); + return rounds_ref; + } + } + + let mut n_shortest = -10; // an initial value, should be no way to get this by calculations + let mut denom_found = false; + + // only denoms here so let's look up + for txin_next in &transaction.inputs { + if self.is_mine_input(&txin_next) { + let outpoint = TxOutPoint::new(txin_next.input_hash, txin_next.index); + let n = self.get_real_outpoint_coinjoin_rounds(outpoint, rounds + 1); + + // denom found, find the shortest chain or initially assign nShortest with the first found value + if n >= 0 && (n < n_shortest || n_shortest == -10) { + n_shortest = n; + denom_found = true; + } + } + } + + rounds_ref = if denom_found { + if n_shortest >= rounds_max - 1 { rounds_max } else { n_shortest + 1 } + } else { + 0 + }; + + log_debug!(target: "CoinJoin", "UPDATED {:?} {} (coinjoin)", outpoint, rounds_ref); + self.map_outpoint_rounds_cache.insert(outpoint, rounds_ref); + rounds_ref + } + + pub fn has_collateral_inputs(&mut self, only_confirmed: bool) -> bool { + let mut coin_control = CoinControl::new(); + coin_control.coin_type = CoinType::OnlyCoinJoinCollateral; + let result = self.available_coins(only_confirmed, coin_control); + + return !result.is_empty(); + } + + pub fn available_coins(&mut self, only_safe: bool, coin_control: CoinControl) -> Vec { + let mut vec_gathered_outputs: Vec = Vec::new(); + + unsafe { + let encoded_coin_control = boxed(coin_control.encode()); + let gathered_outputs = (self.available_coins)(only_safe, encoded_coin_control, self, self.context); + (0..(*gathered_outputs).item_count) + .into_iter() + .map(|i| (**(*gathered_outputs).items.add(i)).decode()) + .for_each( + |item| vec_gathered_outputs.push(item) + ); + + (self.destroy_gathered_outputs)(gathered_outputs); + let unboxed_coin_control = unbox_any(encoded_coin_control); + + if !unboxed_coin_control.set_selected.is_null() && unboxed_coin_control.set_selected_size > 0 { + let unboxed = unbox_vec_ptr(unboxed_coin_control.set_selected, unboxed_coin_control.set_selected_size); + unbox_vec(unboxed); + } + } + + vec_gathered_outputs + } + + pub fn select_coins_grouped_by_addresses( + &mut self, + skip_denominated: bool, + anonymizable: bool, + skip_unconfirmed: bool, + max_outpoints_per_address: i32 + ) -> Vec { + // Try using the cache for already confirmed mixable inputs. + // This should only be used if maxOupointsPerAddress was NOT specified. + if max_outpoints_per_address == -1 && anonymizable && skip_unconfirmed { + if skip_denominated && self.anonymizable_tally_cached_non_denom { + log_debug!(target: "CoinJoin", "SelectCoinsGroupedByAddresses - using cache for non-denom inputs {}", self.vec_anonymizable_tally_cached_non_denom.len()); + return self.vec_anonymizable_tally_cached_non_denom.clone(); + } + + if !skip_denominated && self.anonymizable_tally_cached { + log_debug!(target: "CoinJoin", "SelectCoinsGroupedByAddresses - using cache for all inputs {}", self.vec_anonymizable_tally_cached.len()); + return self.vec_anonymizable_tally_cached.clone(); + } + } + + let mut vec_tally_ret: Vec = Vec::new(); + + unsafe { + let selected_coins = (self.select_coins)(skip_denominated, anonymizable, skip_unconfirmed, max_outpoints_per_address, self, self.context); + (0..(*selected_coins).item_count) + .into_iter() + .map(|i| (**(*selected_coins).items.add(i)).decode()) + .for_each( + |item| vec_tally_ret.push(item) + ); + + (self.destroy_selected_coins)(selected_coins); + } + + // Cache already confirmed mixable entries for later use. + // This should only be used if nMaxOupointsPerAddress was NOT specified. + if max_outpoints_per_address == -1 && anonymizable && skip_unconfirmed && !vec_tally_ret.is_empty() { + if skip_denominated { + log_debug!(target: "CoinJoin", "SelectCoinsGroupedByAddresses - set cache for non-denom inputs, len: {}", vec_tally_ret.len()); + self.vec_anonymizable_tally_cached_non_denom = vec_tally_ret.clone(); + self.anonymizable_tally_cached_non_denom = true; + } else { + log_debug!(target: "CoinJoin", "SelectCoinsGroupedByAddresses - set cache for all inputs, len: {}", vec_tally_ret.len()); + self.vec_anonymizable_tally_cached = vec_tally_ret.clone(); + self.anonymizable_tally_cached = true; + } + } + + return vec_tally_ret; + } + + pub fn get_anonymizable_balance(&mut self, skip_denominated: bool, skip_unconfirmed: bool) -> u64 { + if !self.options.borrow().enable_coinjoin { + return 0; + } + + let tally_items = self.select_coins_grouped_by_addresses(skip_denominated, true, skip_unconfirmed, -1); + + if tally_items.is_empty() { + return 0; + } + + let mut total = 0; + let smallest_denom = CoinJoin::get_smallest_denomination(); + let mixing_collateral = CoinJoin::get_collateral_amount(); + + for item in tally_items { + let is_denominated = CoinJoin::is_denominated_amount(item.amount); + + if skip_denominated && is_denominated { + continue; + } + + // assume that the fee to create denoms should be mixing collateral at max + if item.amount >= smallest_denom + if is_denominated { 0 } else { mixing_collateral } { + total = total + item.amount; + } + } + + return total; + } + + pub fn get_wallet_transaction(&self, hash: UInt256) -> Option { + unsafe { + let boxed_hash = boxed(hash.0); + let wtx = (self.get_wallet_transaction)(boxed_hash, self.context); + + if wtx.is_null() { + return None; + } + + let transaction = (*wtx).decode(); + (self.destroy_transaction)(wtx); + unbox_any(boxed_hash); + Some(transaction) + } + } + + /** + * Count the number of unspent outputs that have a certain value + */ + pub fn count_inputs_with_amount(&self, value: u64) -> u32 { + return unsafe { (self.inputs_with_amount)(value, self.context) }; + } + + pub fn get_unused_key(&mut self, internal: bool) -> TxDestination { + if self.unused_keys.is_empty() { + if !self.key_usage.is_empty() && self.key_usage.values().all(|used| !used) { + log_info!(target: "CoinJoin", "WalletEx - keyUsage map has unused keys, unused key count: {}", self.unused_keys.len()); + } + + return Some(self.fresh_receive_key(internal)); + } + + let key: UInt256; + let item: Vec; + + if let Some(pair) = self.unused_keys.iter().next() { + key = *pair.0; + item = pair.1.clone(); + log_info!(target: "CoinJoin", "WalletEx - reusing key - is this key used: {}, unused key count: {}", self.key_usage.get(&key).unwrap(), self.unused_keys.len()); + } else { + return None; + } + + // remove the key + self.unused_keys.remove(&key); + self.key_usage.insert(key, true); + + return Some(item); + } + + pub fn add_unused_key(&mut self, destination: &TxDestination) { + if let Some(key) = destination { + let key_id = UInt256::sha256(key); + self.unused_keys.insert(key_id, key.clone()); + self.key_usage.insert(key_id, false); + log_debug!(target: "CoinJoin", "WalletEx - add unused key: {:?}", address::with_script_sig(&key, &self.options.borrow().chain_type.script_map())); + } + } + + pub fn remove_unused_key(&mut self, destination: &TxDestination) { + if let Some(key) = destination { + let key_id = UInt256::sha256(key); + self.unused_keys.remove(&key_id); + self.key_usage.insert(key_id, true); + log_debug!(target: "CoinJoin", "WalletEx - remove unused key: {:?}", address::with_script_sig(&key, &self.options.borrow().chain_type.script_map())); + } + } + + pub fn refresh_unused_keys(&mut self) { + self.unused_keys.clear(); + let issued_keys = self.get_issued_receive_keys(); + + for key in &issued_keys { + let key_id = UInt256::sha256(key); + self.unused_keys.insert(key_id, key.clone()); + self.key_usage.insert(key_id, false); + } + + let used_keys = self.get_used_receive_keys(); + + for used_key in &used_keys { + let key_id = UInt256::sha256(used_key); + self.unused_keys.remove(&key_id); + self.key_usage.insert(key_id, true); + } + + for (_, key) in &self.unused_keys { + log_debug!(target: "CoinJoin", "WalletEx - unused key: {:?}", address::with_script_sig(key, &self.options.borrow().chain_type.script_map())); + } + + for (key_id, used) in &self.key_usage { + if !used { + if let Some(key) = self.unused_keys.get(key_id) { + log_debug!(target: "CoinJoin", "WalletEx - unused key: {:?}", address::with_script_sig(key, &self.options.borrow().chain_type.script_map())); + } + } + } + + self.loaded_keys = true; + + } + + pub fn process_used_scripts(&mut self, scripts: &Vec>) { + for script in scripts { + let key_id = UInt256::sha256(script); + + if self.loaded_keys { + self.key_usage.insert(key_id, true); + self.unused_keys.remove(&key_id); + } + + if let Some(key) = self.unused_keys.get(&key_id) { + log_debug!(target: "CoinJoin", "WalletEx - key used: {:?}", address::with_script_pub_key(key, &self.options.borrow().chain_type.script_map())); + } + } + } + + pub fn commit_transaction(&self, vec_send: &Vec, coin_control: CoinControl, is_denominating: bool, client_session_id: UInt256) -> bool { + let result: bool; + + unsafe { + let encoded_coin_control = boxed(coin_control.encode()); + let boxed_vec = boxed_vec( + vec_send + .iter() + .map(|input| boxed((*input).clone())) + .collect() + ); + + let boxed_client_session_id = boxed(client_session_id.0); // Released in DashSync + result = (self.commit_transaction)(boxed_vec, vec_send.len(), encoded_coin_control, is_denominating, boxed_client_session_id, self.context); + let vec = unbox_vec_ptr(boxed_vec, vec_send.len()); + unbox_any_vec(vec); + let unboxed_coin_control = unbox_any(encoded_coin_control); + + if !unboxed_coin_control.set_selected.is_null() && unboxed_coin_control.set_selected_size > 0 { + let unboxed = unbox_vec_ptr(unboxed_coin_control.set_selected, unboxed_coin_control.set_selected_size); + unbox_vec(unboxed); + } + } + + return result; + } + + pub fn sign_transaction(&self, tx: &Transaction, anyone_can_pay: bool) -> Option { + unsafe { + let boxed_tx = boxed(tx.encode()); + let raw_tx = (self.sign_transaction)(boxed_tx, anyone_can_pay, self.context); + + if raw_tx.is_null() { + return None; + } + + let signed_tx = (*raw_tx).decode(); + (self.destroy_transaction)(raw_tx); + unbox_any(boxed_tx); + + return Some(signed_tx); + } + } + + pub fn select_tx_dsins_by_denomination(&mut self, denom: u32, value_max: u64, vec_tx_dsin_ret: &mut Vec) -> bool { + let mut value_total: u64 = 0; + let mut set_recent_tx_ids = HashSet::new(); + vec_tx_dsin_ret.clear(); + + if !CoinJoin::is_valid_denomination(denom) { + return false; + } + + let denom_amount = CoinJoin::denomination_to_amount(denom); + let mut coin_control = CoinControl::new(); + coin_control.coin_type = CoinType::OnlyReadyToMix; + + let mut coins = self.available_coins(true, coin_control); + coins.shuffle(&mut rand::thread_rng()); + + for out in coins.iter() { + let tx_hash = out.tx_outpoint.hash; + let value = out.output.amount; + + if set_recent_tx_ids.contains(&tx_hash) || value_total + value > value_max || value as i64 != denom_amount { + continue; + } + + let txin = TransactionInput { + input_hash: tx_hash, + index: out.tx_outpoint.index, + script: None, + signature: Some(Vec::new()), + sequence: TXIN_SEQUENCE + }; + let rounds = self.get_real_outpoint_coinjoin_rounds(out.tx_outpoint.clone(), 0); + + value_total += value; + vec_tx_dsin_ret.push(CoinJoinTransactionInput::new(txin, rounds)); + set_recent_tx_ids.insert(tx_hash); + } + + return value_total > 0; + } + + pub fn select_denominated_amounts(&mut self, value_max: u64, set_amounts_ret: &mut HashSet) -> bool { + let mut value_total: u64 = 0; + set_amounts_ret.clear(); + + let mut coin_control = CoinControl::new(); + coin_control.coin_type = CoinType::OnlyReadyToMix; + let mut coins = self.available_coins(true, coin_control); + + // larger denoms first + coins.sort_by(|a, b| b.output.amount.cmp(&a.output.amount)); + + for out in coins.iter() { + let value = out.output.amount; + if value_total + value <= value_max { + value_total += value; + set_amounts_ret.insert(value); + } + } + + return value_total >= CoinJoin::get_smallest_denomination() + } + + pub fn is_masternode_or_disconnect_requested(&self, address: SocketAddress) -> bool { + return unsafe { + let boxed_address = boxed(address.ip_address.0); + let result = (self.is_masternode_or_disconnect_requested)(boxed_address, address.port, self.context); + unbox_any(boxed_address); + result + }; + } + + pub fn disconnect_masternode(&self, address: SocketAddress) -> bool { + return unsafe { + let boxed_address = boxed(address.ip_address.0); + let result = (self.disconnect_masternode)(boxed_address, address.port, self.context); + unbox_any(boxed_address); + result + }; + } + + pub fn is_synced(&self) -> bool { + unsafe { return (self.is_synced)(self.context); } + } + + pub fn send_message(&mut self, message: Vec, msg_type: String, address: &SocketAddress, warn: bool) -> bool { + return unsafe { + let message_type = msg_type.to_c_string_ptr(); + let boxed_ip = boxed(address.ip_address.0); + let boxed_message = boxed(ByteArray::from(message)); + let result = (self.send_message)( + message_type, + boxed_message, + boxed_ip, + address.port, + warn, + self.context + ); + + let _ = CString::from_raw(message_type); + unbox_any(boxed_ip); + let msg = unbox_any(boxed_message); + let _ = Vec::from_raw_parts(msg.ptr as *mut u8, msg.len, msg.len); + + result + }; + } + + pub fn add_pending_masternode(&mut self, pro_tx_hash: UInt256, session_id: UInt256) -> bool { + return unsafe { + let pro_tx_hash_ptr = boxed(pro_tx_hash.0); + let session_id_ptr = boxed(session_id.0); + + let result = (self.add_pending_masternode)(pro_tx_hash_ptr, session_id_ptr, self.context); + + unbox_any(pro_tx_hash_ptr); + unbox_any(session_id_ptr); + + result + }; + } + + pub fn start_manager_async(&self) { + unsafe { (self.start_manager_async)(self.context); } + } + + fn clear_anonymizable_caches(&mut self) { + self.anonymizable_tally_cached_non_denom = false; + self.anonymizable_tally_cached = false; + } + + fn is_mine_input(&self, txin: &TransactionInput) -> bool { + unsafe { + let boxed_txin_hash = boxed(txin.input_hash.0); + let is_mine = (self.is_mine_input)(boxed_txin_hash, txin.index, self.context); + unbox_any(boxed_txin_hash); + + return is_mine; + } + } + + fn fresh_receive_key(&mut self, internal: bool) -> Vec { + let fresh_key = unsafe { + let data = (self.fresh_coinjoin_address)(internal, self.context); + let result = slice::from_raw_parts(data.ptr, data.len).to_vec(); + unbox_vec_ptr(data.ptr as *mut u8, data.len); + result + }; + + log_debug!(target: "CoinJoin", "WalletEx - fresh key: {:?}", address::with_script_pub_key(&fresh_key, &self.options.borrow().chain_type.script_map())); + self.key_usage.insert(UInt256::sha256(&fresh_key), true); + + return fresh_key; + } + + fn get_issued_receive_keys(&self) -> Vec> { + unsafe { + let data = (self.get_coinjoin_keys)(false, self.context); + let keys = &*data; + let mut result = Vec::with_capacity(keys.item_count); + + for i in 0..keys.item_count { + let item = *keys.items.add(i); + let bytes = slice::from_raw_parts((*item).ptr, (*item).len).to_vec(); + result.push(bytes); + } + + (self.destroy_coinjoin_keys)(data); + + result + } + } + + fn get_used_receive_keys(&self) -> Vec> { + unsafe { + let data = (self.get_coinjoin_keys)(true, self.context); + let keys = &*data; + let mut result = Vec::with_capacity(keys.item_count); + + for i in 0..keys.item_count { + let item = *keys.items.add(i); + let bytes = slice::from_raw_parts((*item).ptr, (*item).len).to_vec(); + result.push(bytes); + } + + (self.destroy_coinjoin_keys)(data); + + result + } + } +} diff --git a/dash-spv-coinjoin/target/dash_spv_coinjoin.h b/dash-spv-coinjoin/target/dash_spv_coinjoin.h new file mode 100644 index 00000000..6c2d20ce --- /dev/null +++ b/dash-spv-coinjoin/target/dash_spv_coinjoin.h @@ -0,0 +1,61 @@ +#ifndef example_h +#define example_h + +/* This file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include + +#define COINJOIN_AUTO_TIMEOUT_MIN 5 + +#define COINJOIN_AUTO_TIMEOUT_MAX 15 + +#define COINJOIN_QUEUE_TIMEOUT 30 + +#define COINJOIN_SIGNING_TIMEOUT 15 + +#define COINJOIN_ENTRY_MAX_SIZE 9 + +#define MIN_COINJOIN_SESSIONS 1 + +#define MIN_COINJOIN_ROUNDS 2 + +#define MIN_COINJOIN_AMOUNT 2 + +#define MIN_COINJOIN_DENOMS_GOAL 10 + +#define MIN_COINJOIN_DENOMS_HARDCAP 10 + +#define MAX_COINJOIN_SESSIONS 10 + +#define MAX_COINJOIN_ROUNDS 16 + +#define MAX_COINJOIN_DENOMS_GOAL 100000 + +#define MAX_COINJOIN_DENOMS_HARDCAP 100000 + +#define MAX_COINJOIN_AMOUNT (MAX_MONEY / DUFFS) + +#define DEFAULT_COINJOIN_SESSIONS 4 + +#define DEFAULT_COINJOIN_ROUNDS 4 + +#define DEFAULT_COINJOIN_AMOUNT 1000 + +#define DEFAULT_COINJOIN_DENOMS_GOAL 50 + +#define DEFAULT_COINJOIN_DENOMS_HARDCAP 300 + +#define COINJOIN_DENOM_OUTPUTS_THRESHOLD 500 + +#define COINJOIN_KEYS_THRESHOLD_WARNING 100 + +#define COINJOIN_KEYS_THRESHOLD_STOP 50 + +#define COINJOIN_RANDOM_ROUNDS 3 + +#define REFERENCE_DEFAULT_MIN_TX_FEE 1000 + +#endif /* example_h */ diff --git a/dash-spv-coinjoin/target/example.h b/dash-spv-coinjoin/target/example.h new file mode 100644 index 00000000..6c2d20ce --- /dev/null +++ b/dash-spv-coinjoin/target/example.h @@ -0,0 +1,61 @@ +#ifndef example_h +#define example_h + +/* This file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include + +#define COINJOIN_AUTO_TIMEOUT_MIN 5 + +#define COINJOIN_AUTO_TIMEOUT_MAX 15 + +#define COINJOIN_QUEUE_TIMEOUT 30 + +#define COINJOIN_SIGNING_TIMEOUT 15 + +#define COINJOIN_ENTRY_MAX_SIZE 9 + +#define MIN_COINJOIN_SESSIONS 1 + +#define MIN_COINJOIN_ROUNDS 2 + +#define MIN_COINJOIN_AMOUNT 2 + +#define MIN_COINJOIN_DENOMS_GOAL 10 + +#define MIN_COINJOIN_DENOMS_HARDCAP 10 + +#define MAX_COINJOIN_SESSIONS 10 + +#define MAX_COINJOIN_ROUNDS 16 + +#define MAX_COINJOIN_DENOMS_GOAL 100000 + +#define MAX_COINJOIN_DENOMS_HARDCAP 100000 + +#define MAX_COINJOIN_AMOUNT (MAX_MONEY / DUFFS) + +#define DEFAULT_COINJOIN_SESSIONS 4 + +#define DEFAULT_COINJOIN_ROUNDS 4 + +#define DEFAULT_COINJOIN_AMOUNT 1000 + +#define DEFAULT_COINJOIN_DENOMS_GOAL 50 + +#define DEFAULT_COINJOIN_DENOMS_HARDCAP 300 + +#define COINJOIN_DENOM_OUTPUTS_THRESHOLD 500 + +#define COINJOIN_KEYS_THRESHOLD_WARNING 100 + +#define COINJOIN_KEYS_THRESHOLD_STOP 50 + +#define COINJOIN_RANDOM_ROUNDS 3 + +#define REFERENCE_DEFAULT_MIN_TX_FEE 1000 + +#endif /* example_h */ diff --git a/dash-spv-masternode-processor/Cargo.toml b/dash-spv-masternode-processor/Cargo.toml index 46a5b2e8..4021880d 100644 --- a/dash-spv-masternode-processor/Cargo.toml +++ b/dash-spv-masternode-processor/Cargo.toml @@ -35,12 +35,12 @@ core2 = { version = "0.4.0", optional = true, default-features = false } dirs-next = "2.0.0" ed25519-dalek = "2.0.0-rc.3" libc = "0.2.158" -log = { version = "0.4.*", features = ["std"] } +logging = { path = "../logging" } rs-x11-hash = "0.1.8" secp256k1 = { version = "0.26.0", features = [ "recovery", "rand-std", "bitcoin_hashes" ] } serde = { version = "1.0.209", features = ["derive"], optional = true } serde_json = "1.0.85" -simplelog = "0.12.2" +tracing = "0.1.40" zeroize = "1.5.7" bincode = "2.0.0-rc.3" diff --git a/dash-spv-masternode-processor/src/bindings/common.rs b/dash-spv-masternode-processor/src/bindings/common.rs index d367a799..73feb8d2 100644 --- a/dash-spv-masternode-processor/src/bindings/common.rs +++ b/dash-spv-masternode-processor/src/bindings/common.rs @@ -1,7 +1,7 @@ use std::ffi::CString; -use std::fs::File; use std::os::raw::c_char; -use simplelog::{ColorChoice, CombinedLogger, ConfigBuilder, LevelFilter, TerminalMode, TermLogger, WriteLogger}; +use logging::*; +use tracing::*; use crate::crypto::byte_util::ConstDecodable; use crate::crypto::UInt256; use crate::ffi::boxer::boxed; @@ -14,37 +14,8 @@ use crate::types; /// Initializes logger (it could be initialize only once) #[no_mangle] pub unsafe extern "C" fn register_rust_logger() { - // Get the path to the cache directory. - let cache_path = match dirs_next::cache_dir() { - Some(path) => path, - None => panic!("Failed to find the cache directory"), - }; - - // Create the log directory if it doesn't exist. - let log_dir = cache_path.join("Logs"); - if !log_dir.exists() { - std::fs::create_dir_all(&log_dir).expect("Failed to create log directory"); - } - - // Create the log file inside the cache directory. - let log_file_path = log_dir.join("processor.log"); - println!("Log file create at: {:?}", log_file_path); - let log_file = File::create(log_file_path) - .expect("Failed to create log file"); - let config = ConfigBuilder::new().build(); - //let config = ConfigBuilder::new().set_time_level(LevelFilter::Off).set_max_level(LevelFilter::Off).build(); - match CombinedLogger::init( - vec![ - TermLogger::new(LevelFilter::Error, config.clone(), TerminalMode::Mixed, ColorChoice::Auto), - TermLogger::new(LevelFilter::Warn, config.clone(), TerminalMode::Mixed, ColorChoice::Auto), - WriteLogger::new(LevelFilter::Error, config.clone(), log_file.try_clone().unwrap()), - WriteLogger::new(LevelFilter::Warn, config.clone(), log_file.try_clone().unwrap()), - WriteLogger::new(LevelFilter::Info, config.clone(), log_file.try_clone().unwrap()), - ] - ) { - Ok(()) => println!("Logger initialized"), - Err(err) => println!("Failed to init logger: {}", err) - } + logging::init_logging(); + log_info!(target: "masternode-processor", "register_rust_logger"); } /// Register all the callbacks for use across FFI @@ -66,6 +37,7 @@ pub unsafe extern "C" fn register_processor( destroy_snapshot: LLMQSnapshotDestroy, should_process_diff_with_range: ShouldProcessDiffWithRange, ) -> *mut MasternodeProcessor { + logging::init_logging(); let processor = MasternodeProcessor::new( get_merkle_root_by_hash, get_block_height_by_hash, @@ -82,7 +54,7 @@ pub unsafe extern "C" fn register_processor( destroy_snapshot, should_process_diff_with_range, ); - println!("register_processor: {:?}", processor); + log_info!(target: "masternode-processor", "register_processor: {:?}", processor); boxed(processor) } @@ -90,7 +62,7 @@ pub unsafe extern "C" fn register_processor( /// # Safety #[no_mangle] pub unsafe extern "C" fn unregister_processor(processor: *mut MasternodeProcessor) { - println!("unregister_processor: {:?}", processor); + log_info!(target: "masternode-processor", "unregister_processor: {:?}", processor); let unboxed = unbox_any(processor); } @@ -99,7 +71,7 @@ pub unsafe extern "C" fn unregister_processor(processor: *mut MasternodeProcesso #[no_mangle] pub unsafe extern "C" fn processor_create_cache() -> *mut MasternodeProcessorCache { let cache = MasternodeProcessorCache::default(); - println!("processor_create_cache: {:?}", cache); + log_info!(target: "masternode-processor", "processor_create_cache: {:?}", cache); boxed(cache) } @@ -107,7 +79,7 @@ pub unsafe extern "C" fn processor_create_cache() -> *mut MasternodeProcessorCac /// # Safety #[no_mangle] pub unsafe extern "C" fn processor_destroy_cache(cache: *mut MasternodeProcessorCache) { - println!("processor_destroy_cache: {:?}", cache); + log_debug!(target: "masternode-processor", "processor_destroy_cache: {:?}", cache); let cache = unbox_any(cache); } @@ -115,7 +87,7 @@ pub unsafe extern "C" fn processor_destroy_cache(cache: *mut MasternodeProcessor /// # Safety #[no_mangle] pub unsafe extern "C" fn processor_remove_masternode_list_from_cache_for_block_hash(block_hash: *const u8, cache: *mut MasternodeProcessorCache) { - println!("processor_remove_masternode_list_from_cache_for_block_hash: {:?} {:p}", block_hash, cache); + log_debug!(target: "masternode-processor", "processor_remove_masternode_list_from_cache_for_block_hash: {:?} {:p}", block_hash, cache); if let Some(hash) = UInt256::from_const(block_hash) { (*cache).remove_masternode_list(&hash); } @@ -125,7 +97,7 @@ pub unsafe extern "C" fn processor_remove_masternode_list_from_cache_for_block_h /// # Safety #[no_mangle] pub unsafe extern "C" fn processor_remove_llmq_snapshot_from_cache_for_block_hash(block_hash: *const u8, cache: *mut MasternodeProcessorCache) { - println!("processor_remove_llmq_snapshot_from_cache_for_block_hash: {:?} {:p}", block_hash, cache); + log_info!(target: "masternode-processor", "processor_remove_llmq_snapshot_from_cache_for_block_hash: {:?} {:p}", block_hash, cache); if let Some(hash) = UInt256::from_const(block_hash) { (*cache).remove_snapshot(&hash); } @@ -135,7 +107,7 @@ pub unsafe extern "C" fn processor_remove_llmq_snapshot_from_cache_for_block_has /// # Safety #[no_mangle] pub unsafe extern "C" fn processor_remove_llmq_members_from_cache_for_block_hash(block_hash: *const u8, cache: *mut MasternodeProcessorCache) { - println!("processor_remove_llmq_members_from_cache_for_block_hash: {:?} {:p}", block_hash, cache); + log_info!(target: "masternode-processor", "processor_remove_llmq_members_from_cache_for_block_hash: {:?} {:p}", block_hash, cache); if let Some(hash) = UInt256::from_const(block_hash) { (*cache).remove_quorum_members(&hash); } @@ -145,7 +117,7 @@ pub unsafe extern "C" fn processor_remove_llmq_members_from_cache_for_block_hash /// # Safety #[no_mangle] pub unsafe extern "C" fn processor_clear_cache(cache: *mut MasternodeProcessorCache) { - println!("processor_clear_cache: {:p}", cache); + log_info!(target: "masternode-processor", "processor_clear_cache: {:p}", cache); (*cache).clear(); } diff --git a/dash-spv-masternode-processor/src/bindings/keys.rs b/dash-spv-masternode-processor/src/bindings/keys.rs index 9041b547..713bc01b 100644 --- a/dash-spv-masternode-processor/src/bindings/keys.rs +++ b/dash-spv-masternode-processor/src/bindings/keys.rs @@ -4,6 +4,8 @@ use std::ptr::null_mut; use std::slice; use byte::BytesExt; use secp256k1::Scalar; +use logging::*; +use tracing::*; use crate::chain::bip::bip32; use crate::chain::bip::bip38::BIP38; use crate::chain::common::{ChainType, IHaveChainSettings}; @@ -21,6 +23,8 @@ use crate::processing::keys_cache::KeysCache; use crate::types::opaque_key::{AsCStringPtr, AsOpaqueKey, OpaqueKey, KeyWithUniqueId, OpaqueKeys, OpaqueSerializedKeys}; use crate::util::address::address; use crate::util::sec_vec::SecVec; +#[cfg(feature = "use_serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Destroys /// # Safety @@ -44,7 +48,7 @@ pub unsafe extern "C" fn processor_destroy_serialized_opaque_keys(data: *mut Opa #[no_mangle] pub unsafe extern "C" fn keys_create_cache() -> *mut KeysCache { let cache = KeysCache::default(); - println!("keys_create_cache: {:?}", cache); + log_info!(target: "masternode-processor", "keys_create_cache: {:?}", cache); boxed(cache) } @@ -52,7 +56,7 @@ pub unsafe extern "C" fn keys_create_cache() -> *mut KeysCache { /// # Safety #[no_mangle] pub unsafe extern "C" fn keys_clear_cache(cache: *mut KeysCache) { - println!("keys_clear_cache: {:p}", cache); + log_info!(target: "masternode-processor", "keys_clear_cache: {:p}", cache); (*cache).clear(); } @@ -60,7 +64,7 @@ pub unsafe extern "C" fn keys_clear_cache(cache: *mut KeysCache) { /// # Safety #[no_mangle] pub unsafe extern "C" fn keys_destroy_cache(cache: *mut KeysCache) { - println!("keys_destroy_cache: {:?}", cache); + log_info!(target: "masternode-processor", "keys_destroy_cache: {:?}", cache); let cache = unbox_any(cache); } diff --git a/dash-spv-masternode-processor/src/bindings/masternode.rs b/dash-spv-masternode-processor/src/bindings/masternode.rs index c01ad9fc..9e28d011 100644 --- a/dash-spv-masternode-processor/src/bindings/masternode.rs +++ b/dash-spv-masternode-processor/src/bindings/masternode.rs @@ -1,6 +1,8 @@ use std::ptr::null_mut; use std::slice; use byte::BytesExt; +use logging::*; +use tracing::*; use crate::{models, types}; use crate::chain::common::{ChainType, IHaveChainSettings, LLMQType}; use crate::consensus::encode; @@ -28,7 +30,7 @@ pub unsafe extern "C" fn process_mnlistdiff_from_message( let instant = std::time::Instant::now(); let processor = &mut *processor; let cache = &mut *cache; - println!("process_mnlistdiff_from_message -> {:?} {:p} {:p} {:p}", instant, processor, cache, context); + log_debug!(target: "masternode-processor", "process_mnlistdiff_from_message -> {:?} {:p} {:p} {:p}", instant, processor, cache, context); processor.opaque_context = context; processor.use_insight_as_backup = use_insight_as_backup; processor.chain_type = chain_type; @@ -39,12 +41,12 @@ pub unsafe extern "C" fn process_mnlistdiff_from_message( let error = processor .should_process_diff_with_range(list_diff.base_block_hash, list_diff.block_hash); if error != ProcessingError::None { - println!("process_mnlistdiff_from_message <- {:?} ms [{:?}]", instant.elapsed().as_millis(), error); + log_debug!(target: "masternode-processor", "process_mnlistdiff_from_message <- {:?} ms [{:?}]", instant.elapsed().as_millis(), error); return boxed(types::MNListDiffResult::default_with_error(error)); } } let result = processor.get_list_diff_result_with_base_lookup(list_diff, LLMQVerificationContext::MNListDiff, cache); - println!("process_mnlistdiff_from_message <- {:?} ms", instant.elapsed().as_millis()); + log_debug!(target: "masternode-processor", "process_mnlistdiff_from_message <- {:?} ms", instant.elapsed().as_millis()); boxed(result) } @@ -73,7 +75,7 @@ pub unsafe extern "C" fn process_qrinfo_from_message( processor.opaque_context = context; processor.use_insight_as_backup = use_insight_as_backup; processor.chain_type = chain_type; - println!( "process_qrinfo_from_message -> {:?} {:p} {:p} {:p}", instant, processor, cache, context); + log_debug!(target: "masternode-processor", "process_qrinfo_from_message -> {:?} {:p} {:p} {:p}", instant, processor, cache, context); let offset = &mut 0; let mut process_list_diff = |list_diff: models::MNListDiff, verification_context: LLMQVerificationContext| { processor.get_list_diff_result_with_base_lookup(list_diff, verification_context, cache) @@ -92,7 +94,7 @@ pub unsafe extern "C" fn process_qrinfo_from_message( let error = processor.should_process_diff_with_range(diff_tip.base_block_hash, diff_tip.block_hash); if error != ProcessingError::None { - println!("process_qrinfo_from_message <- {:?} ms [{:#?}]", instant.elapsed().as_millis(), error); + log_debug!(target: "masternode-processor", "process_qrinfo_from_message <- {:?} ms [{:#?}]", instant.elapsed().as_millis(), error); return boxed(types::QRInfoResult::default_with_error(error)); } } @@ -178,7 +180,7 @@ pub unsafe extern "C" fn process_qrinfo_from_message( #[cfg(feature = "generate-dashj-tests")] crate::util::java::generate_qr_state_test_file_json(chain_type, &result); - println!("process_qrinfo_from_message <- {:?} ms", instant.elapsed().as_millis()); + log_debug!(target: "masternode-processor", "process_qrinfo_from_message <- {:?} ms", instant.elapsed().as_millis()); boxed(result) } diff --git a/dash-spv-masternode-processor/src/chain/params.rs b/dash-spv-masternode-processor/src/chain/params.rs index 851ce562..cae07bb4 100644 --- a/dash-spv-masternode-processor/src/chain/params.rs +++ b/dash-spv-masternode-processor/src/chain/params.rs @@ -4,8 +4,8 @@ use crate::chain::common::ChainType; use crate::crypto::byte_util::Reversable; use crate::crypto::UInt256; -pub(crate) const DUFFS: u64 = 100000000; -pub(crate) const MAX_MONEY: u64 = 21000000 * DUFFS; +pub const DUFFS: u64 = 100000000; +pub const MAX_MONEY: u64 = 21000000 * DUFFS; /// standard tx fee per b of tx size pub(crate) const TX_FEE_PER_B: u64 = 1; /// standard ix fee per input @@ -15,7 +15,7 @@ pub(crate) const TX_OUTPUT_SIZE: u64 = 34; /// estimated size for a typical compact pubkey transaction input pub(crate) const TX_INPUT_SIZE: u64 = 148; /// no txout can be below this amount -pub(crate) const TX_MIN_OUTPUT_AMOUNT: u64 = TX_FEE_PER_B * 3 * (TX_OUTPUT_SIZE + TX_INPUT_SIZE); +pub const TX_MIN_OUTPUT_AMOUNT: u64 = TX_FEE_PER_B * 3 * (TX_OUTPUT_SIZE + TX_INPUT_SIZE); /// no tx can be larger than this size in bytes pub(crate) const TX_MAX_SIZE: u64 = 100000; /// block height indicating transaction is unconfirmed diff --git a/dash-spv-masternode-processor/src/common/socket_address.rs b/dash-spv-masternode-processor/src/common/socket_address.rs index 598754d0..633fb72a 100644 --- a/dash-spv-masternode-processor/src/common/socket_address.rs +++ b/dash-spv-masternode-processor/src/common/socket_address.rs @@ -3,6 +3,7 @@ use byte::{BytesExt, TryRead}; use crate::consensus::Encodable; use crate::crypto::UInt128; + #[repr(C)] #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)] pub struct SocketAddress { @@ -12,7 +13,7 @@ pub struct SocketAddress { impl std::fmt::Display for SocketAddress { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}:{}", self.ip_address, self.port)?; + write!(f, "{}:{}", self.ip_address.to_ip_addr(), self.port)?; Ok(()) } } diff --git a/dash-spv-masternode-processor/src/consensus/encode.rs b/dash-spv-masternode-processor/src/consensus/encode.rs index 2158be6f..41e884c5 100644 --- a/dash-spv-masternode-processor/src/consensus/encode.rs +++ b/dash-spv-masternode-processor/src/consensus/encode.rs @@ -36,11 +36,13 @@ use std::borrow::Cow; use std::io::{self, Cursor, Read}; use std::{rc, sync}; use hashes::{Hash, sha256, sha256d}; +use crate::crypto::UInt256; use crate::hash_types::{BlockHash, FilterHash, FilterHeader, TxMerkleNode}; use crate::hashes::hex::ToHex; // use hashes::hex::ToHex; +use crate::tx::{TransactionInput, TransactionOutput}; // use blockdata::transaction::{TxOut, Transaction, TxIn}; // #[cfg(feature = "std")] // use network::{message_blockdata::Inventory, address::{Address, AddrV2Message}}; @@ -611,6 +613,13 @@ impl_vec!(TxMerkleNode); impl_vec!(Vec); impl_vec!(u64); +impl_vec!(bool); +impl_vec!(UInt256); +impl_vec!(TransactionInput); +impl_vec!(TransactionOutput); +// impl_vec!(MasternodeEntry); +// impl_vec!(crate::models::LLMQEntry); + // #[cfg(feature = "std")] impl_vec!(Inventory); // #[cfg(feature = "std")] impl_vec!((u32, Address)); // #[cfg(feature = "std")] impl_vec!(AddrV2Message); diff --git a/dash-spv-masternode-processor/src/fermented.rs b/dash-spv-masternode-processor/src/fermented.rs new file mode 100644 index 00000000..f3d18f6c --- /dev/null +++ b/dash-spv-masternode-processor/src/fermented.rs @@ -0,0 +1 @@ +# [allow (clippy :: let_and_return , clippy :: suspicious_else_formatting , clippy :: redundant_field_names , dead_code , redundant_semicolons , unused_braces , unused_imports , unused_unsafe , unused_variables , unused_qualifications)] pub mod types { pub mod crypto { pub mod byte_util { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: crypto :: byte_util :: UInt128\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct UInt128 (* mut [u8 ; 16] ,) ; impl ferment_interfaces :: FFIConversion < crate :: crypto :: byte_util :: UInt128 > for UInt128 { unsafe fn ffi_from_const (ffi : * const UInt128) -> crate :: crypto :: byte_util :: UInt128 { let ffi_ref = & * ffi ; crate :: crypto :: byte_util :: UInt128 (* ffi_ref . 0 ,) } unsafe fn ffi_to_const (obj : crate :: crypto :: byte_util :: UInt128) -> * const UInt128 { ferment_interfaces :: boxed (UInt128 (ferment_interfaces :: boxed (obj . 0) ,)) } unsafe fn destroy (ffi : * mut UInt128) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for UInt128 { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . 0) ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt128_ctor (o_0 : * mut [u8 ; 16]) -> * mut UInt128 { ferment_interfaces :: boxed (UInt128 (o_0 ,)) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt128_destroy (ffi : * mut UInt128) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: crypto :: byte_util :: UInt160\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct UInt160 (* mut [u8 ; 20] ,) ; impl ferment_interfaces :: FFIConversion < crate :: crypto :: byte_util :: UInt160 > for UInt160 { unsafe fn ffi_from_const (ffi : * const UInt160) -> crate :: crypto :: byte_util :: UInt160 { let ffi_ref = & * ffi ; crate :: crypto :: byte_util :: UInt160 (* ffi_ref . 0 ,) } unsafe fn ffi_to_const (obj : crate :: crypto :: byte_util :: UInt160) -> * const UInt160 { ferment_interfaces :: boxed (UInt160 (ferment_interfaces :: boxed (obj . 0) ,)) } unsafe fn destroy (ffi : * mut UInt160) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for UInt160 { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . 0) ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt160_ctor (o_0 : * mut [u8 ; 20]) -> * mut UInt160 { ferment_interfaces :: boxed (UInt160 (o_0 ,)) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt160_destroy (ffi : * mut UInt160) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: crypto :: byte_util :: UInt256\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct UInt256 (* mut [u8 ; 32] ,) ; impl ferment_interfaces :: FFIConversion < crate :: crypto :: byte_util :: UInt256 > for UInt256 { unsafe fn ffi_from_const (ffi : * const UInt256) -> crate :: crypto :: byte_util :: UInt256 { let ffi_ref = & * ffi ; crate :: crypto :: byte_util :: UInt256 (* ffi_ref . 0 ,) } unsafe fn ffi_to_const (obj : crate :: crypto :: byte_util :: UInt256) -> * const UInt256 { ferment_interfaces :: boxed (UInt256 (ferment_interfaces :: boxed (obj . 0) ,)) } unsafe fn destroy (ffi : * mut UInt256) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for UInt256 { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . 0) ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt256_ctor (o_0 : * mut [u8 ; 32]) -> * mut UInt256 { ferment_interfaces :: boxed (UInt256 (o_0 ,)) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt256_destroy (ffi : * mut UInt256) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: crypto :: byte_util :: UInt768\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct UInt768 (* mut [u8 ; 96] ,) ; impl ferment_interfaces :: FFIConversion < crate :: crypto :: byte_util :: UInt768 > for UInt768 { unsafe fn ffi_from_const (ffi : * const UInt768) -> crate :: crypto :: byte_util :: UInt768 { let ffi_ref = & * ffi ; crate :: crypto :: byte_util :: UInt768 (* ffi_ref . 0 ,) } unsafe fn ffi_to_const (obj : crate :: crypto :: byte_util :: UInt768) -> * const UInt768 { ferment_interfaces :: boxed (UInt768 (ferment_interfaces :: boxed (obj . 0) ,)) } unsafe fn destroy (ffi : * mut UInt768) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for UInt768 { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . 0) ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt768_ctor (o_0 : * mut [u8 ; 96]) -> * mut UInt768 { ferment_interfaces :: boxed (UInt768 (o_0 ,)) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt768_destroy (ffi : * mut UInt768) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: crypto :: byte_util :: UInt384\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct UInt384 (* mut [u8 ; 48] ,) ; impl ferment_interfaces :: FFIConversion < crate :: crypto :: byte_util :: UInt384 > for UInt384 { unsafe fn ffi_from_const (ffi : * const UInt384) -> crate :: crypto :: byte_util :: UInt384 { let ffi_ref = & * ffi ; crate :: crypto :: byte_util :: UInt384 (* ffi_ref . 0 ,) } unsafe fn ffi_to_const (obj : crate :: crypto :: byte_util :: UInt384) -> * const UInt384 { ferment_interfaces :: boxed (UInt384 (ferment_interfaces :: boxed (obj . 0) ,)) } unsafe fn destroy (ffi : * mut UInt384) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for UInt384 { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . 0) ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt384_ctor (o_0 : * mut [u8 ; 48]) -> * mut UInt384 { ferment_interfaces :: boxed (UInt384 (o_0 ,)) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt384_destroy (ffi : * mut UInt384) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: crypto :: byte_util :: UInt512\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct UInt512 (* mut [u8 ; 64] ,) ; impl ferment_interfaces :: FFIConversion < crate :: crypto :: byte_util :: UInt512 > for UInt512 { unsafe fn ffi_from_const (ffi : * const UInt512) -> crate :: crypto :: byte_util :: UInt512 { let ffi_ref = & * ffi ; crate :: crypto :: byte_util :: UInt512 (* ffi_ref . 0 ,) } unsafe fn ffi_to_const (obj : crate :: crypto :: byte_util :: UInt512) -> * const UInt512 { ferment_interfaces :: boxed (UInt512 (ferment_interfaces :: boxed (obj . 0) ,)) } unsafe fn destroy (ffi : * mut UInt512) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for UInt512 { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . 0) ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt512_ctor (o_0 : * mut [u8 ; 64]) -> * mut UInt512 { ferment_interfaces :: boxed (UInt512 (o_0 ,)) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn UInt512_destroy (ffi : * mut UInt512) { ferment_interfaces :: unbox_any (ffi) ; } } } pub mod tx { pub mod coinbase_transaction { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: tx :: coinbase_transaction :: CoinbaseTransaction\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct CoinbaseTransaction { pub base : * mut crate :: fermented :: types :: tx :: transaction :: Transaction , pub coinbase_transaction_version : u16 , pub height : u32 , pub merkle_root_mn_list : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub merkle_root_llmq_list : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub best_cl_height_diff : u64 , pub best_cl_signature : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt768 , pub credit_pool_balance : i64 , } impl ferment_interfaces :: FFIConversion < crate :: tx :: coinbase_transaction :: CoinbaseTransaction > for CoinbaseTransaction { unsafe fn ffi_from_const (ffi : * const CoinbaseTransaction) -> crate :: tx :: coinbase_transaction :: CoinbaseTransaction { let ffi_ref = & * ffi ; crate :: tx :: coinbase_transaction :: CoinbaseTransaction { base : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . base) , coinbase_transaction_version : ffi_ref . coinbase_transaction_version , height : ffi_ref . height , merkle_root_mn_list : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . merkle_root_mn_list) , merkle_root_llmq_list : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . merkle_root_llmq_list) , best_cl_height_diff : ffi_ref . best_cl_height_diff , best_cl_signature : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . best_cl_signature) , credit_pool_balance : (ffi_ref . credit_pool_balance > 0) . then_some (ffi_ref . credit_pool_balance) , } } unsafe fn ffi_to_const (obj : crate :: tx :: coinbase_transaction :: CoinbaseTransaction) -> * const CoinbaseTransaction { ferment_interfaces :: boxed (CoinbaseTransaction { base : ferment_interfaces :: FFIConversion :: ffi_to (obj . base) , coinbase_transaction_version : obj . coinbase_transaction_version , height : obj . height , merkle_root_mn_list : ferment_interfaces :: FFIConversion :: ffi_to (obj . merkle_root_mn_list) , merkle_root_llmq_list : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . merkle_root_llmq_list) , best_cl_height_diff : obj . best_cl_height_diff , best_cl_signature : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . best_cl_signature) , credit_pool_balance : obj . credit_pool_balance . unwrap_or (0) , }) } unsafe fn destroy (ffi : * mut CoinbaseTransaction) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for CoinbaseTransaction { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . base) ; ; ; ; ferment_interfaces :: unbox_any (ffi_ref . merkle_root_mn_list) ; ; if ! ffi_ref . merkle_root_llmq_list . is_null () { ferment_interfaces :: unbox_any (ffi_ref . merkle_root_llmq_list) ; } ; ; if ! ffi_ref . best_cl_signature . is_null () { ferment_interfaces :: unbox_any (ffi_ref . best_cl_signature) ; } ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinbaseTransaction_ctor (base : * mut crate :: fermented :: types :: tx :: transaction :: Transaction , coinbase_transaction_version : u16 , height : u32 , merkle_root_mn_list : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , merkle_root_llmq_list : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , best_cl_height_diff : u64 , best_cl_signature : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt768 , credit_pool_balance : i64) -> * mut CoinbaseTransaction { ferment_interfaces :: boxed (CoinbaseTransaction { base , coinbase_transaction_version , height , merkle_root_mn_list , merkle_root_llmq_list , best_cl_height_diff , best_cl_signature , credit_pool_balance , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn CoinbaseTransaction_destroy (ffi : * mut CoinbaseTransaction) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod transaction { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: tx :: transaction :: Transaction\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Transaction { pub inputs : * mut crate :: fermented :: generics :: Vec_crate_tx_transaction_TransactionInput , pub outputs : * mut crate :: fermented :: generics :: Vec_crate_tx_transaction_TransactionOutput , pub lock_time : u32 , pub version : u16 , pub tx_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub tx_type : * mut crate :: fermented :: types :: tx :: transaction :: TransactionType , pub payload_offset : usize , pub block_height : u32 , } impl ferment_interfaces :: FFIConversion < crate :: tx :: transaction :: Transaction > for Transaction { unsafe fn ffi_from_const (ffi : * const Transaction) -> crate :: tx :: transaction :: Transaction { let ffi_ref = & * ffi ; crate :: tx :: transaction :: Transaction { inputs : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . inputs) , outputs : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . outputs) , lock_time : ffi_ref . lock_time , version : ffi_ref . version , tx_hash : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . tx_hash) , tx_type : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . tx_type) , payload_offset : ffi_ref . payload_offset , block_height : ffi_ref . block_height , } } unsafe fn ffi_to_const (obj : crate :: tx :: transaction :: Transaction) -> * const Transaction { ferment_interfaces :: boxed (Transaction { inputs : ferment_interfaces :: FFIConversion :: ffi_to (obj . inputs) , outputs : ferment_interfaces :: FFIConversion :: ffi_to (obj . outputs) , lock_time : obj . lock_time , version : obj . version , tx_hash : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . tx_hash) , tx_type : ferment_interfaces :: FFIConversion :: ffi_to (obj . tx_type) , payload_offset : obj . payload_offset , block_height : obj . block_height , }) } unsafe fn destroy (ffi : * mut Transaction) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for Transaction { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . inputs) ; ; ferment_interfaces :: unbox_any (ffi_ref . outputs) ; ; ; ; if ! ffi_ref . tx_hash . is_null () { ferment_interfaces :: unbox_any (ffi_ref . tx_hash) ; } ; ferment_interfaces :: unbox_any (ffi_ref . tx_type) ; ; ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn Transaction_ctor (inputs : * mut crate :: fermented :: generics :: Vec_crate_tx_transaction_TransactionInput , outputs : * mut crate :: fermented :: generics :: Vec_crate_tx_transaction_TransactionOutput , lock_time : u32 , version : u16 , tx_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , tx_type : * mut crate :: fermented :: types :: tx :: transaction :: TransactionType , payload_offset : usize , block_height : u32) -> * mut Transaction { ferment_interfaces :: boxed (Transaction { inputs , outputs , lock_time , version , tx_hash , tx_type , payload_offset , block_height , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn Transaction_destroy (ffi : * mut Transaction) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: tx :: transaction :: TransactionOutput\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct TransactionOutput { pub amount : u64 , pub script : * mut crate :: fermented :: generics :: Vec_u8 , pub address : * mut crate :: fermented :: generics :: Vec_u8 , } impl ferment_interfaces :: FFIConversion < crate :: tx :: transaction :: TransactionOutput > for TransactionOutput { unsafe fn ffi_from_const (ffi : * const TransactionOutput) -> crate :: tx :: transaction :: TransactionOutput { let ffi_ref = & * ffi ; crate :: tx :: transaction :: TransactionOutput { amount : ffi_ref . amount , script : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . script) , address : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . address) , } } unsafe fn ffi_to_const (obj : crate :: tx :: transaction :: TransactionOutput) -> * const TransactionOutput { ferment_interfaces :: boxed (TransactionOutput { amount : obj . amount , script : match obj . script { Some (vec) => ferment_interfaces :: FFIConversion :: ffi_to (vec) , None => std :: ptr :: null_mut () , } , address : match obj . address { Some (vec) => ferment_interfaces :: FFIConversion :: ffi_to (vec) , None => std :: ptr :: null_mut () , } , }) } unsafe fn destroy (ffi : * mut TransactionOutput) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for TransactionOutput { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; if ! ffi_ref . script . is_null () { ferment_interfaces :: unbox_any (ffi_ref . script) ; } ; if ! ffi_ref . address . is_null () { ferment_interfaces :: unbox_any (ffi_ref . address) ; } ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionOutput_ctor (amount : u64 , script : * mut crate :: fermented :: generics :: Vec_u8 , address : * mut crate :: fermented :: generics :: Vec_u8) -> * mut TransactionOutput { ferment_interfaces :: boxed (TransactionOutput { amount , script , address , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionOutput_destroy (ffi : * mut TransactionOutput) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: tx :: transaction :: TransactionInput\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct TransactionInput { pub input_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub index : u32 , pub script : * mut crate :: fermented :: generics :: Vec_u8 , pub signature : * mut crate :: fermented :: generics :: Vec_u8 , pub sequence : u32 , } impl ferment_interfaces :: FFIConversion < crate :: tx :: transaction :: TransactionInput > for TransactionInput { unsafe fn ffi_from_const (ffi : * const TransactionInput) -> crate :: tx :: transaction :: TransactionInput { let ffi_ref = & * ffi ; crate :: tx :: transaction :: TransactionInput { input_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . input_hash) , index : ffi_ref . index , script : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . script) , signature : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . signature) , sequence : ffi_ref . sequence , } } unsafe fn ffi_to_const (obj : crate :: tx :: transaction :: TransactionInput) -> * const TransactionInput { ferment_interfaces :: boxed (TransactionInput { input_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . input_hash) , index : obj . index , script : match obj . script { Some (vec) => ferment_interfaces :: FFIConversion :: ffi_to (vec) , None => std :: ptr :: null_mut () , } , signature : match obj . signature { Some (vec) => ferment_interfaces :: FFIConversion :: ffi_to (vec) , None => std :: ptr :: null_mut () , } , sequence : obj . sequence , }) } unsafe fn destroy (ffi : * mut TransactionInput) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for TransactionInput { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . input_hash) ; ; ; if ! ffi_ref . script . is_null () { ferment_interfaces :: unbox_any (ffi_ref . script) ; } ; if ! ffi_ref . signature . is_null () { ferment_interfaces :: unbox_any (ffi_ref . signature) ; } ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionInput_ctor (input_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , index : u32 , script : * mut crate :: fermented :: generics :: Vec_u8 , signature : * mut crate :: fermented :: generics :: Vec_u8 , sequence : u32) -> * mut TransactionInput { ferment_interfaces :: boxed (TransactionInput { input_hash , index , script , signature , sequence , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionInput_destroy (ffi : * mut TransactionInput) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the TransactionType"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum TransactionType { Classic = 0 , ProviderRegistration = 1 , ProviderUpdateService = 2 , ProviderUpdateRegistrar = 3 , ProviderUpdateRevocation = 4 , Coinbase = 5 , QuorumCommitment = 6 , AssetLock = 8 , AssetUnlock = 9 , TypeMax = 10 , SubscriptionCloseAccount = 11 , Transition = 12 , CreditFunding = 255 , } impl ferment_interfaces :: FFIConversion < crate :: tx :: transaction :: TransactionType > for TransactionType { unsafe fn ffi_from_const (ffi : * const TransactionType) -> crate :: tx :: transaction :: TransactionType { let ffi_ref = & * ffi ; match ffi_ref { TransactionType :: Classic => crate :: tx :: transaction :: TransactionType :: Classic , TransactionType :: ProviderRegistration => crate :: tx :: transaction :: TransactionType :: ProviderRegistration , TransactionType :: ProviderUpdateService => crate :: tx :: transaction :: TransactionType :: ProviderUpdateService , TransactionType :: ProviderUpdateRegistrar => crate :: tx :: transaction :: TransactionType :: ProviderUpdateRegistrar , TransactionType :: ProviderUpdateRevocation => crate :: tx :: transaction :: TransactionType :: ProviderUpdateRevocation , TransactionType :: Coinbase => crate :: tx :: transaction :: TransactionType :: Coinbase , TransactionType :: QuorumCommitment => crate :: tx :: transaction :: TransactionType :: QuorumCommitment , TransactionType :: AssetLock => crate :: tx :: transaction :: TransactionType :: AssetLock , TransactionType :: AssetUnlock => crate :: tx :: transaction :: TransactionType :: AssetUnlock , TransactionType :: TypeMax => crate :: tx :: transaction :: TransactionType :: TypeMax , TransactionType :: SubscriptionCloseAccount => crate :: tx :: transaction :: TransactionType :: SubscriptionCloseAccount , TransactionType :: Transition => crate :: tx :: transaction :: TransactionType :: Transition , TransactionType :: CreditFunding => crate :: tx :: transaction :: TransactionType :: CreditFunding , } } unsafe fn ffi_to_const (obj : crate :: tx :: transaction :: TransactionType) -> * const TransactionType { ferment_interfaces :: boxed (match obj { crate :: tx :: transaction :: TransactionType :: Classic => TransactionType :: Classic , crate :: tx :: transaction :: TransactionType :: ProviderRegistration => TransactionType :: ProviderRegistration , crate :: tx :: transaction :: TransactionType :: ProviderUpdateService => TransactionType :: ProviderUpdateService , crate :: tx :: transaction :: TransactionType :: ProviderUpdateRegistrar => TransactionType :: ProviderUpdateRegistrar , crate :: tx :: transaction :: TransactionType :: ProviderUpdateRevocation => TransactionType :: ProviderUpdateRevocation , crate :: tx :: transaction :: TransactionType :: Coinbase => TransactionType :: Coinbase , crate :: tx :: transaction :: TransactionType :: QuorumCommitment => TransactionType :: QuorumCommitment , crate :: tx :: transaction :: TransactionType :: AssetLock => TransactionType :: AssetLock , crate :: tx :: transaction :: TransactionType :: AssetUnlock => TransactionType :: AssetUnlock , crate :: tx :: transaction :: TransactionType :: TypeMax => TransactionType :: TypeMax , crate :: tx :: transaction :: TransactionType :: SubscriptionCloseAccount => TransactionType :: SubscriptionCloseAccount , crate :: tx :: transaction :: TransactionType :: Transition => TransactionType :: Transition , crate :: tx :: transaction :: TransactionType :: CreditFunding => TransactionType :: CreditFunding , }) } unsafe fn destroy (ffi : * mut TransactionType) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for TransactionType { fn drop (& mut self) { unsafe { match self { TransactionType :: Classic => { } , TransactionType :: ProviderRegistration => { } , TransactionType :: ProviderUpdateService => { } , TransactionType :: ProviderUpdateRegistrar => { } , TransactionType :: ProviderUpdateRevocation => { } , TransactionType :: Coinbase => { } , TransactionType :: QuorumCommitment => { } , TransactionType :: AssetLock => { } , TransactionType :: AssetUnlock => { } , TransactionType :: TypeMax => { } , TransactionType :: SubscriptionCloseAccount => { } , TransactionType :: Transition => { } , TransactionType :: CreditFunding => { } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_Classic_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: Classic) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_ProviderRegistration_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: ProviderRegistration) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_ProviderUpdateService_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: ProviderUpdateService) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_ProviderUpdateRegistrar_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: ProviderUpdateRegistrar) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_ProviderUpdateRevocation_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: ProviderUpdateRevocation) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_Coinbase_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: Coinbase) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_QuorumCommitment_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: QuorumCommitment) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_AssetLock_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: AssetLock) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_AssetUnlock_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: AssetUnlock) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_TypeMax_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: TypeMax) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_SubscriptionCloseAccount_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: SubscriptionCloseAccount) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_Transition_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: Transition) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_CreditFunding_ctor () -> * mut TransactionType { ferment_interfaces :: boxed (TransactionType :: CreditFunding) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn TransactionType_destroy (ffi : * mut TransactionType) { ferment_interfaces :: unbox_any (ffi) ; } } } pub mod processing { pub mod mn_listdiff_result { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: processing :: mn_listdiff_result :: MNListDiffResult\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct MNListDiffResult { pub base_block_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub block_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub has_found_coinbase : bool , pub has_valid_coinbase : bool , pub has_valid_mn_list_root : bool , pub has_valid_llmq_list_root : bool , pub has_valid_quorums : bool , pub masternode_list : * mut crate :: fermented :: types :: models :: masternode_list :: MasternodeList , pub added_masternodes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry , pub modified_masternodes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry , pub added_quorums : * mut crate :: fermented :: generics :: Vec_crate_models_llmq_entry_LLMQEntry , pub needed_masternode_lists : * mut crate :: fermented :: generics :: Vec_crate_crypto_byte_util_UInt256 , } impl ferment_interfaces :: FFIConversion < crate :: processing :: mn_listdiff_result :: MNListDiffResult > for MNListDiffResult { unsafe fn ffi_from_const (ffi : * const MNListDiffResult) -> crate :: processing :: mn_listdiff_result :: MNListDiffResult { let ffi_ref = & * ffi ; crate :: processing :: mn_listdiff_result :: MNListDiffResult { base_block_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . base_block_hash) , block_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . block_hash) , has_found_coinbase : ffi_ref . has_found_coinbase , has_valid_coinbase : ffi_ref . has_valid_coinbase , has_valid_mn_list_root : ffi_ref . has_valid_mn_list_root , has_valid_llmq_list_root : ffi_ref . has_valid_llmq_list_root , has_valid_quorums : ffi_ref . has_valid_quorums , masternode_list : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . masternode_list) , added_masternodes : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . added_masternodes) , modified_masternodes : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . modified_masternodes) , added_quorums : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . added_quorums) , needed_masternode_lists : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . needed_masternode_lists) , } } unsafe fn ffi_to_const (obj : crate :: processing :: mn_listdiff_result :: MNListDiffResult) -> * const MNListDiffResult { ferment_interfaces :: boxed (MNListDiffResult { base_block_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . base_block_hash) , block_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . block_hash) , has_found_coinbase : obj . has_found_coinbase , has_valid_coinbase : obj . has_valid_coinbase , has_valid_mn_list_root : obj . has_valid_mn_list_root , has_valid_llmq_list_root : obj . has_valid_llmq_list_root , has_valid_quorums : obj . has_valid_quorums , masternode_list : ferment_interfaces :: FFIConversion :: ffi_to (obj . masternode_list) , added_masternodes : ferment_interfaces :: FFIConversion :: ffi_to (obj . added_masternodes) , modified_masternodes : ferment_interfaces :: FFIConversion :: ffi_to (obj . modified_masternodes) , added_quorums : ferment_interfaces :: FFIConversion :: ffi_to (obj . added_quorums) , needed_masternode_lists : ferment_interfaces :: FFIConversion :: ffi_to (obj . needed_masternode_lists) , }) } unsafe fn destroy (ffi : * mut MNListDiffResult) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for MNListDiffResult { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . base_block_hash) ; ; ferment_interfaces :: unbox_any (ffi_ref . block_hash) ; ; ; ; ; ; ; ferment_interfaces :: unbox_any (ffi_ref . masternode_list) ; ; ferment_interfaces :: unbox_any (ffi_ref . added_masternodes) ; ; ferment_interfaces :: unbox_any (ffi_ref . modified_masternodes) ; ; ferment_interfaces :: unbox_any (ffi_ref . added_quorums) ; ; ferment_interfaces :: unbox_any (ffi_ref . needed_masternode_lists) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MNListDiffResult_ctor (base_block_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , block_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , has_found_coinbase : bool , has_valid_coinbase : bool , has_valid_mn_list_root : bool , has_valid_llmq_list_root : bool , has_valid_quorums : bool , masternode_list : * mut crate :: fermented :: types :: models :: masternode_list :: MasternodeList , added_masternodes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry , modified_masternodes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry , added_quorums : * mut crate :: fermented :: generics :: Vec_crate_models_llmq_entry_LLMQEntry , needed_masternode_lists : * mut crate :: fermented :: generics :: Vec_crate_crypto_byte_util_UInt256) -> * mut MNListDiffResult { ferment_interfaces :: boxed (MNListDiffResult { base_block_hash , block_hash , has_found_coinbase , has_valid_coinbase , has_valid_mn_list_root , has_valid_llmq_list_root , has_valid_quorums , masternode_list , added_masternodes , modified_masternodes , added_quorums , needed_masternode_lists , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MNListDiffResult_destroy (ffi : * mut MNListDiffResult) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod qr_info_result { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: processing :: qr_info_result :: QRInfoResult\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct QRInfoResult { pub result_at_tip : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , pub result_at_h : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , pub result_at_h_c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , pub result_at_h_2c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , pub result_at_h_3c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , pub result_at_h_4c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , pub snapshot_at_h_c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , pub snapshot_at_h_2c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , pub snapshot_at_h_3c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , pub snapshot_at_h_4c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , pub extra_share : bool , pub last_quorum_per_index : * mut crate :: fermented :: generics :: Vec_crate_models_llmq_entry_LLMQEntry , pub quorum_snapshot_list : * mut crate :: fermented :: generics :: Vec_crate_models_snapshot_LLMQSnapshot , pub mn_list_diff_list : * mut crate :: fermented :: generics :: Vec_crate_processing_mn_listdiff_result_MNListDiffResult , } impl ferment_interfaces :: FFIConversion < crate :: processing :: qr_info_result :: QRInfoResult > for QRInfoResult { unsafe fn ffi_from_const (ffi : * const QRInfoResult) -> crate :: processing :: qr_info_result :: QRInfoResult { let ffi_ref = & * ffi ; crate :: processing :: qr_info_result :: QRInfoResult { result_at_tip : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . result_at_tip) , result_at_h : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . result_at_h) , result_at_h_c : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . result_at_h_c) , result_at_h_2c : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . result_at_h_2c) , result_at_h_3c : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . result_at_h_3c) , result_at_h_4c : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . result_at_h_4c) , snapshot_at_h_c : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . snapshot_at_h_c) , snapshot_at_h_2c : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . snapshot_at_h_2c) , snapshot_at_h_3c : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . snapshot_at_h_3c) , snapshot_at_h_4c : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . snapshot_at_h_4c) , extra_share : ffi_ref . extra_share , last_quorum_per_index : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . last_quorum_per_index) , quorum_snapshot_list : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . quorum_snapshot_list) , mn_list_diff_list : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . mn_list_diff_list) , } } unsafe fn ffi_to_const (obj : crate :: processing :: qr_info_result :: QRInfoResult) -> * const QRInfoResult { ferment_interfaces :: boxed (QRInfoResult { result_at_tip : ferment_interfaces :: FFIConversion :: ffi_to (obj . result_at_tip) , result_at_h : ferment_interfaces :: FFIConversion :: ffi_to (obj . result_at_h) , result_at_h_c : ferment_interfaces :: FFIConversion :: ffi_to (obj . result_at_h_c) , result_at_h_2c : ferment_interfaces :: FFIConversion :: ffi_to (obj . result_at_h_2c) , result_at_h_3c : ferment_interfaces :: FFIConversion :: ffi_to (obj . result_at_h_3c) , result_at_h_4c : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . result_at_h_4c) , snapshot_at_h_c : ferment_interfaces :: FFIConversion :: ffi_to (obj . snapshot_at_h_c) , snapshot_at_h_2c : ferment_interfaces :: FFIConversion :: ffi_to (obj . snapshot_at_h_2c) , snapshot_at_h_3c : ferment_interfaces :: FFIConversion :: ffi_to (obj . snapshot_at_h_3c) , snapshot_at_h_4c : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . snapshot_at_h_4c) , extra_share : obj . extra_share , last_quorum_per_index : ferment_interfaces :: FFIConversion :: ffi_to (obj . last_quorum_per_index) , quorum_snapshot_list : ferment_interfaces :: FFIConversion :: ffi_to (obj . quorum_snapshot_list) , mn_list_diff_list : ferment_interfaces :: FFIConversion :: ffi_to (obj . mn_list_diff_list) , }) } unsafe fn destroy (ffi : * mut QRInfoResult) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for QRInfoResult { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . result_at_tip) ; ; ferment_interfaces :: unbox_any (ffi_ref . result_at_h) ; ; ferment_interfaces :: unbox_any (ffi_ref . result_at_h_c) ; ; ferment_interfaces :: unbox_any (ffi_ref . result_at_h_2c) ; ; ferment_interfaces :: unbox_any (ffi_ref . result_at_h_3c) ; ; if ! ffi_ref . result_at_h_4c . is_null () { ferment_interfaces :: unbox_any (ffi_ref . result_at_h_4c) ; } ; ferment_interfaces :: unbox_any (ffi_ref . snapshot_at_h_c) ; ; ferment_interfaces :: unbox_any (ffi_ref . snapshot_at_h_2c) ; ; ferment_interfaces :: unbox_any (ffi_ref . snapshot_at_h_3c) ; ; if ! ffi_ref . snapshot_at_h_4c . is_null () { ferment_interfaces :: unbox_any (ffi_ref . snapshot_at_h_4c) ; } ; ; ferment_interfaces :: unbox_any (ffi_ref . last_quorum_per_index) ; ; ferment_interfaces :: unbox_any (ffi_ref . quorum_snapshot_list) ; ; ferment_interfaces :: unbox_any (ffi_ref . mn_list_diff_list) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn QRInfoResult_ctor (result_at_tip : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , result_at_h : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , result_at_h_c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , result_at_h_2c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , result_at_h_3c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , result_at_h_4c : * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , snapshot_at_h_c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , snapshot_at_h_2c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , snapshot_at_h_3c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , snapshot_at_h_4c : * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , extra_share : bool , last_quorum_per_index : * mut crate :: fermented :: generics :: Vec_crate_models_llmq_entry_LLMQEntry , quorum_snapshot_list : * mut crate :: fermented :: generics :: Vec_crate_models_snapshot_LLMQSnapshot , mn_list_diff_list : * mut crate :: fermented :: generics :: Vec_crate_processing_mn_listdiff_result_MNListDiffResult) -> * mut QRInfoResult { ferment_interfaces :: boxed (QRInfoResult { result_at_tip , result_at_h , result_at_h_c , result_at_h_2c , result_at_h_3c , result_at_h_4c , snapshot_at_h_c , snapshot_at_h_2c , snapshot_at_h_3c , snapshot_at_h_4c , extra_share , last_quorum_per_index , quorum_snapshot_list , mn_list_diff_list , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn QRInfoResult_destroy (ffi : * mut QRInfoResult) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod processing_error { # [doc = "FFI-representation of the ProcessingError"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum ProcessingError { None = 0 , PersistInRetrieval = 1 , LocallyStored = 2 , ParseError = 3 , HasNoBaseBlockHash = 4 , UnknownBlockHash = 5 , } impl ferment_interfaces :: FFIConversion < crate :: processing :: processing_error :: ProcessingError > for ProcessingError { unsafe fn ffi_from_const (ffi : * const ProcessingError) -> crate :: processing :: processing_error :: ProcessingError { let ffi_ref = & * ffi ; match ffi_ref { ProcessingError :: None => crate :: processing :: processing_error :: ProcessingError :: None , ProcessingError :: PersistInRetrieval => crate :: processing :: processing_error :: ProcessingError :: PersistInRetrieval , ProcessingError :: LocallyStored => crate :: processing :: processing_error :: ProcessingError :: LocallyStored , ProcessingError :: ParseError => crate :: processing :: processing_error :: ProcessingError :: ParseError , ProcessingError :: HasNoBaseBlockHash => crate :: processing :: processing_error :: ProcessingError :: HasNoBaseBlockHash , ProcessingError :: UnknownBlockHash => crate :: processing :: processing_error :: ProcessingError :: UnknownBlockHash , } } unsafe fn ffi_to_const (obj : crate :: processing :: processing_error :: ProcessingError) -> * const ProcessingError { ferment_interfaces :: boxed (match obj { crate :: processing :: processing_error :: ProcessingError :: None => ProcessingError :: None , crate :: processing :: processing_error :: ProcessingError :: PersistInRetrieval => ProcessingError :: PersistInRetrieval , crate :: processing :: processing_error :: ProcessingError :: LocallyStored => ProcessingError :: LocallyStored , crate :: processing :: processing_error :: ProcessingError :: ParseError => ProcessingError :: ParseError , crate :: processing :: processing_error :: ProcessingError :: HasNoBaseBlockHash => ProcessingError :: HasNoBaseBlockHash , crate :: processing :: processing_error :: ProcessingError :: UnknownBlockHash => ProcessingError :: UnknownBlockHash , }) } unsafe fn destroy (ffi : * mut ProcessingError) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for ProcessingError { fn drop (& mut self) { unsafe { match self { ProcessingError :: None => { } , ProcessingError :: PersistInRetrieval => { } , ProcessingError :: LocallyStored => { } , ProcessingError :: ParseError => { } , ProcessingError :: HasNoBaseBlockHash => { } , ProcessingError :: UnknownBlockHash => { } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ProcessingError_None_ctor () -> * mut ProcessingError { ferment_interfaces :: boxed (ProcessingError :: None) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ProcessingError_PersistInRetrieval_ctor () -> * mut ProcessingError { ferment_interfaces :: boxed (ProcessingError :: PersistInRetrieval) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ProcessingError_LocallyStored_ctor () -> * mut ProcessingError { ferment_interfaces :: boxed (ProcessingError :: LocallyStored) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ProcessingError_ParseError_ctor () -> * mut ProcessingError { ferment_interfaces :: boxed (ProcessingError :: ParseError) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ProcessingError_HasNoBaseBlockHash_ctor () -> * mut ProcessingError { ferment_interfaces :: boxed (ProcessingError :: HasNoBaseBlockHash) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ProcessingError_UnknownBlockHash_ctor () -> * mut ProcessingError { ferment_interfaces :: boxed (ProcessingError :: UnknownBlockHash) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ProcessingError_destroy (ffi : * mut ProcessingError) { ferment_interfaces :: unbox_any (ffi) ; } } } pub mod common { pub mod llmq_snapshot_skip_mode { # [doc = "FFI-representation of the LLMQSnapshotSkipMode"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum LLMQSnapshotSkipMode { NoSkipping = 0 , SkipFirst = 1 , SkipExcept = 2 , SkipAll = 3 , } impl ferment_interfaces :: FFIConversion < crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode > for LLMQSnapshotSkipMode { unsafe fn ffi_from_const (ffi : * const LLMQSnapshotSkipMode) -> crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode { let ffi_ref = & * ffi ; match ffi_ref { LLMQSnapshotSkipMode :: NoSkipping => crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: NoSkipping , LLMQSnapshotSkipMode :: SkipFirst => crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: SkipFirst , LLMQSnapshotSkipMode :: SkipExcept => crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: SkipExcept , LLMQSnapshotSkipMode :: SkipAll => crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: SkipAll , } } unsafe fn ffi_to_const (obj : crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode) -> * const LLMQSnapshotSkipMode { ferment_interfaces :: boxed (match obj { crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: NoSkipping => LLMQSnapshotSkipMode :: NoSkipping , crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: SkipFirst => LLMQSnapshotSkipMode :: SkipFirst , crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: SkipExcept => LLMQSnapshotSkipMode :: SkipExcept , crate :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode :: SkipAll => LLMQSnapshotSkipMode :: SkipAll , }) } unsafe fn destroy (ffi : * mut LLMQSnapshotSkipMode) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQSnapshotSkipMode { fn drop (& mut self) { unsafe { match self { LLMQSnapshotSkipMode :: NoSkipping => { } , LLMQSnapshotSkipMode :: SkipFirst => { } , LLMQSnapshotSkipMode :: SkipExcept => { } , LLMQSnapshotSkipMode :: SkipAll => { } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQSnapshotSkipMode_NoSkipping_ctor () -> * mut LLMQSnapshotSkipMode { ferment_interfaces :: boxed (LLMQSnapshotSkipMode :: NoSkipping) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQSnapshotSkipMode_SkipFirst_ctor () -> * mut LLMQSnapshotSkipMode { ferment_interfaces :: boxed (LLMQSnapshotSkipMode :: SkipFirst) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQSnapshotSkipMode_SkipExcept_ctor () -> * mut LLMQSnapshotSkipMode { ferment_interfaces :: boxed (LLMQSnapshotSkipMode :: SkipExcept) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQSnapshotSkipMode_SkipAll_ctor () -> * mut LLMQSnapshotSkipMode { ferment_interfaces :: boxed (LLMQSnapshotSkipMode :: SkipAll) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQSnapshotSkipMode_destroy (ffi : * mut LLMQSnapshotSkipMode) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod socket_address { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: common :: socket_address :: SocketAddress\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct SocketAddress { pub ip_address : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt128 , pub port : u16 , } impl ferment_interfaces :: FFIConversion < crate :: common :: socket_address :: SocketAddress > for SocketAddress { unsafe fn ffi_from_const (ffi : * const SocketAddress) -> crate :: common :: socket_address :: SocketAddress { let ffi_ref = & * ffi ; crate :: common :: socket_address :: SocketAddress { ip_address : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . ip_address) , port : ffi_ref . port , } } unsafe fn ffi_to_const (obj : crate :: common :: socket_address :: SocketAddress) -> * const SocketAddress { ferment_interfaces :: boxed (SocketAddress { ip_address : ferment_interfaces :: FFIConversion :: ffi_to (obj . ip_address) , port : obj . port , }) } unsafe fn destroy (ffi : * mut SocketAddress) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for SocketAddress { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . ip_address) ; ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn SocketAddress_ctor (ip_address : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt128 , port : u16) -> * mut SocketAddress { ferment_interfaces :: boxed (SocketAddress { ip_address , port , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn SocketAddress_destroy (ffi : * mut SocketAddress) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod masternode_type { # [doc = "FFI-representation of the MasternodeType"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum MasternodeType { Regular = 0 , HighPerformance = 1 , } impl ferment_interfaces :: FFIConversion < crate :: common :: masternode_type :: MasternodeType > for MasternodeType { unsafe fn ffi_from_const (ffi : * const MasternodeType) -> crate :: common :: masternode_type :: MasternodeType { let ffi_ref = & * ffi ; match ffi_ref { MasternodeType :: Regular => crate :: common :: masternode_type :: MasternodeType :: Regular , MasternodeType :: HighPerformance => crate :: common :: masternode_type :: MasternodeType :: HighPerformance , } } unsafe fn ffi_to_const (obj : crate :: common :: masternode_type :: MasternodeType) -> * const MasternodeType { ferment_interfaces :: boxed (match obj { crate :: common :: masternode_type :: MasternodeType :: Regular => MasternodeType :: Regular , crate :: common :: masternode_type :: MasternodeType :: HighPerformance => MasternodeType :: HighPerformance , }) } unsafe fn destroy (ffi : * mut MasternodeType) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for MasternodeType { fn drop (& mut self) { unsafe { match self { MasternodeType :: Regular => { } , MasternodeType :: HighPerformance => { } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MasternodeType_Regular_ctor () -> * mut MasternodeType { ferment_interfaces :: boxed (MasternodeType :: Regular) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MasternodeType_HighPerformance_ctor () -> * mut MasternodeType { ferment_interfaces :: boxed (MasternodeType :: HighPerformance) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MasternodeType_destroy (ffi : * mut MasternodeType) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod bitset { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: common :: bitset :: Bitset\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Bitset { pub count : usize , pub bitset : * mut crate :: fermented :: generics :: Vec_u8 , } impl ferment_interfaces :: FFIConversion < crate :: common :: bitset :: Bitset > for Bitset { unsafe fn ffi_from_const (ffi : * const Bitset) -> crate :: common :: bitset :: Bitset { let ffi_ref = & * ffi ; crate :: common :: bitset :: Bitset { count : ffi_ref . count , bitset : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . bitset) , } } unsafe fn ffi_to_const (obj : crate :: common :: bitset :: Bitset) -> * const Bitset { ferment_interfaces :: boxed (Bitset { count : obj . count , bitset : ferment_interfaces :: FFIConversion :: ffi_to (obj . bitset) , }) } unsafe fn destroy (ffi : * mut Bitset) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for Bitset { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ferment_interfaces :: unbox_any (ffi_ref . bitset) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn Bitset_ctor (count : usize , bitset : * mut crate :: fermented :: generics :: Vec_u8) -> * mut Bitset { ferment_interfaces :: boxed (Bitset { count , bitset , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn Bitset_destroy (ffi : * mut Bitset) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod block { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: common :: block :: Block\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Block { pub height : u32 , pub hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , } impl ferment_interfaces :: FFIConversion < crate :: common :: block :: Block > for Block { unsafe fn ffi_from_const (ffi : * const Block) -> crate :: common :: block :: Block { let ffi_ref = & * ffi ; crate :: common :: block :: Block { height : ffi_ref . height , hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . hash) , } } unsafe fn ffi_to_const (obj : crate :: common :: block :: Block) -> * const Block { ferment_interfaces :: boxed (Block { height : obj . height , hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . hash) , }) } unsafe fn destroy (ffi : * mut Block) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for Block { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ferment_interfaces :: unbox_any (ffi_ref . hash) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn Block_ctor (height : u32 , hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256) -> * mut Block { ferment_interfaces :: boxed (Block { height , hash , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn Block_destroy (ffi : * mut Block) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod llmq_version { # [doc = "FFI-representation of the LLMQVersion"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum LLMQVersion { Default = 1 , Indexed = 2 , BLSBasicDefault = 3 , BLSBasicIndexed = 4 , } impl ferment_interfaces :: FFIConversion < crate :: common :: llmq_version :: LLMQVersion > for LLMQVersion { unsafe fn ffi_from_const (ffi : * const LLMQVersion) -> crate :: common :: llmq_version :: LLMQVersion { let ffi_ref = & * ffi ; match ffi_ref { LLMQVersion :: Default => crate :: common :: llmq_version :: LLMQVersion :: Default , LLMQVersion :: Indexed => crate :: common :: llmq_version :: LLMQVersion :: Indexed , LLMQVersion :: BLSBasicDefault => crate :: common :: llmq_version :: LLMQVersion :: BLSBasicDefault , LLMQVersion :: BLSBasicIndexed => crate :: common :: llmq_version :: LLMQVersion :: BLSBasicIndexed , } } unsafe fn ffi_to_const (obj : crate :: common :: llmq_version :: LLMQVersion) -> * const LLMQVersion { ferment_interfaces :: boxed (match obj { crate :: common :: llmq_version :: LLMQVersion :: Default => LLMQVersion :: Default , crate :: common :: llmq_version :: LLMQVersion :: Indexed => LLMQVersion :: Indexed , crate :: common :: llmq_version :: LLMQVersion :: BLSBasicDefault => LLMQVersion :: BLSBasicDefault , crate :: common :: llmq_version :: LLMQVersion :: BLSBasicIndexed => LLMQVersion :: BLSBasicIndexed , }) } unsafe fn destroy (ffi : * mut LLMQVersion) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQVersion { fn drop (& mut self) { unsafe { match self { LLMQVersion :: Default => { } , LLMQVersion :: Indexed => { } , LLMQVersion :: BLSBasicDefault => { } , LLMQVersion :: BLSBasicIndexed => { } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQVersion_Default_ctor () -> * mut LLMQVersion { ferment_interfaces :: boxed (LLMQVersion :: Default) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQVersion_Indexed_ctor () -> * mut LLMQVersion { ferment_interfaces :: boxed (LLMQVersion :: Indexed) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQVersion_BLSBasicDefault_ctor () -> * mut LLMQVersion { ferment_interfaces :: boxed (LLMQVersion :: BLSBasicDefault) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQVersion_BLSBasicIndexed_ctor () -> * mut LLMQVersion { ferment_interfaces :: boxed (LLMQVersion :: BLSBasicIndexed) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQVersion_destroy (ffi : * mut LLMQVersion) { ferment_interfaces :: unbox_any (ffi) ; } } } pub mod models { pub mod snapshot { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: models :: snapshot :: LLMQSnapshot\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct LLMQSnapshot { pub member_list : * mut crate :: fermented :: generics :: Vec_u8 , pub skip_list : * mut crate :: fermented :: generics :: Vec_i32 , pub skip_list_mode : * mut crate :: fermented :: types :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode , } impl ferment_interfaces :: FFIConversion < crate :: models :: snapshot :: LLMQSnapshot > for LLMQSnapshot { unsafe fn ffi_from_const (ffi : * const LLMQSnapshot) -> crate :: models :: snapshot :: LLMQSnapshot { let ffi_ref = & * ffi ; crate :: models :: snapshot :: LLMQSnapshot { member_list : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . member_list) , skip_list : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . skip_list) , skip_list_mode : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . skip_list_mode) , } } unsafe fn ffi_to_const (obj : crate :: models :: snapshot :: LLMQSnapshot) -> * const LLMQSnapshot { ferment_interfaces :: boxed (LLMQSnapshot { member_list : ferment_interfaces :: FFIConversion :: ffi_to (obj . member_list) , skip_list : ferment_interfaces :: FFIConversion :: ffi_to (obj . skip_list) , skip_list_mode : ferment_interfaces :: FFIConversion :: ffi_to (obj . skip_list_mode) , }) } unsafe fn destroy (ffi : * mut LLMQSnapshot) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQSnapshot { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . member_list) ; ; ferment_interfaces :: unbox_any (ffi_ref . skip_list) ; ; ferment_interfaces :: unbox_any (ffi_ref . skip_list_mode) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQSnapshot_ctor (member_list : * mut crate :: fermented :: generics :: Vec_u8 , skip_list : * mut crate :: fermented :: generics :: Vec_i32 , skip_list_mode : * mut crate :: fermented :: types :: common :: llmq_snapshot_skip_mode :: LLMQSnapshotSkipMode) -> * mut LLMQSnapshot { ferment_interfaces :: boxed (LLMQSnapshot { member_list , skip_list , skip_list_mode , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQSnapshot_destroy (ffi : * mut LLMQSnapshot) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod llmq_indexed_hash { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: models :: llmq_indexed_hash :: LLMQIndexedHash\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct LLMQIndexedHash { pub index : u32 , pub hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , } impl ferment_interfaces :: FFIConversion < crate :: models :: llmq_indexed_hash :: LLMQIndexedHash > for LLMQIndexedHash { unsafe fn ffi_from_const (ffi : * const LLMQIndexedHash) -> crate :: models :: llmq_indexed_hash :: LLMQIndexedHash { let ffi_ref = & * ffi ; crate :: models :: llmq_indexed_hash :: LLMQIndexedHash { index : ffi_ref . index , hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . hash) , } } unsafe fn ffi_to_const (obj : crate :: models :: llmq_indexed_hash :: LLMQIndexedHash) -> * const LLMQIndexedHash { ferment_interfaces :: boxed (LLMQIndexedHash { index : obj . index , hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . hash) , }) } unsafe fn destroy (ffi : * mut LLMQIndexedHash) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQIndexedHash { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ferment_interfaces :: unbox_any (ffi_ref . hash) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQIndexedHash_ctor (index : u32 , hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256) -> * mut LLMQIndexedHash { ferment_interfaces :: boxed (LLMQIndexedHash { index , hash , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQIndexedHash_destroy (ffi : * mut LLMQIndexedHash) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod llmq_entry { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: models :: llmq_entry :: LLMQEntry\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct LLMQEntry { pub version : * mut crate :: fermented :: types :: common :: llmq_version :: LLMQVersion , pub llmq_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub index : u16 , pub public_key : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt384 , pub threshold_signature : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt768 , pub verification_vector_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub all_commitment_aggregated_signature : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt768 , pub llmq_type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub signers : * mut crate :: fermented :: types :: common :: bitset :: Bitset , pub valid_members : * mut crate :: fermented :: types :: common :: bitset :: Bitset , pub entry_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub verified : bool , pub saved : bool , pub commitment_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , } impl ferment_interfaces :: FFIConversion < crate :: models :: llmq_entry :: LLMQEntry > for LLMQEntry { unsafe fn ffi_from_const (ffi : * const LLMQEntry) -> crate :: models :: llmq_entry :: LLMQEntry { let ffi_ref = & * ffi ; crate :: models :: llmq_entry :: LLMQEntry { version : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . version) , llmq_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . llmq_hash) , index : (ffi_ref . index > 0) . then_some (ffi_ref . index) , public_key : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . public_key) , threshold_signature : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . threshold_signature) , verification_vector_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . verification_vector_hash) , all_commitment_aggregated_signature : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . all_commitment_aggregated_signature) , llmq_type : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . llmq_type) , signers : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . signers) , valid_members : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . valid_members) , entry_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . entry_hash) , verified : ffi_ref . verified , saved : ffi_ref . saved , commitment_hash : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . commitment_hash) , } } unsafe fn ffi_to_const (obj : crate :: models :: llmq_entry :: LLMQEntry) -> * const LLMQEntry { ferment_interfaces :: boxed (LLMQEntry { version : ferment_interfaces :: FFIConversion :: ffi_to (obj . version) , llmq_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . llmq_hash) , index : obj . index . unwrap_or (0) , public_key : ferment_interfaces :: FFIConversion :: ffi_to (obj . public_key) , threshold_signature : ferment_interfaces :: FFIConversion :: ffi_to (obj . threshold_signature) , verification_vector_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . verification_vector_hash) , all_commitment_aggregated_signature : ferment_interfaces :: FFIConversion :: ffi_to (obj . all_commitment_aggregated_signature) , llmq_type : ferment_interfaces :: FFIConversion :: ffi_to (obj . llmq_type) , signers : ferment_interfaces :: FFIConversion :: ffi_to (obj . signers) , valid_members : ferment_interfaces :: FFIConversion :: ffi_to (obj . valid_members) , entry_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . entry_hash) , verified : obj . verified , saved : obj . saved , commitment_hash : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . commitment_hash) , }) } unsafe fn destroy (ffi : * mut LLMQEntry) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQEntry { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . version) ; ; ferment_interfaces :: unbox_any (ffi_ref . llmq_hash) ; ; ; ferment_interfaces :: unbox_any (ffi_ref . public_key) ; ; ferment_interfaces :: unbox_any (ffi_ref . threshold_signature) ; ; ferment_interfaces :: unbox_any (ffi_ref . verification_vector_hash) ; ; ferment_interfaces :: unbox_any (ffi_ref . all_commitment_aggregated_signature) ; ; ferment_interfaces :: unbox_any (ffi_ref . llmq_type) ; ; ferment_interfaces :: unbox_any (ffi_ref . signers) ; ; ferment_interfaces :: unbox_any (ffi_ref . valid_members) ; ; ferment_interfaces :: unbox_any (ffi_ref . entry_hash) ; ; ; ; if ! ffi_ref . commitment_hash . is_null () { ferment_interfaces :: unbox_any (ffi_ref . commitment_hash) ; } ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQEntry_ctor (version : * mut crate :: fermented :: types :: common :: llmq_version :: LLMQVersion , llmq_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , index : u16 , public_key : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt384 , threshold_signature : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt768 , verification_vector_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , all_commitment_aggregated_signature : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt768 , llmq_type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , signers : * mut crate :: fermented :: types :: common :: bitset :: Bitset , valid_members : * mut crate :: fermented :: types :: common :: bitset :: Bitset , entry_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , verified : bool , saved : bool , commitment_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256) -> * mut LLMQEntry { ferment_interfaces :: boxed (LLMQEntry { version , llmq_hash , index , public_key , threshold_signature , verification_vector_hash , all_commitment_aggregated_signature , llmq_type , signers , valid_members , entry_hash , verified , saved , commitment_hash , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQEntry_destroy (ffi : * mut LLMQEntry) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod masternode_list { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: models :: masternode_list :: MasternodeList\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct MasternodeList { pub block_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub known_height : u32 , pub masternode_merkle_root : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub llmq_merkle_root : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub masternodes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry , pub quorums : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry , } impl ferment_interfaces :: FFIConversion < crate :: models :: masternode_list :: MasternodeList > for MasternodeList { unsafe fn ffi_from_const (ffi : * const MasternodeList) -> crate :: models :: masternode_list :: MasternodeList { let ffi_ref = & * ffi ; crate :: models :: masternode_list :: MasternodeList { block_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . block_hash) , known_height : ffi_ref . known_height , masternode_merkle_root : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . masternode_merkle_root) , llmq_merkle_root : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . llmq_merkle_root) , masternodes : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . masternodes) , quorums : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . quorums) , } } unsafe fn ffi_to_const (obj : crate :: models :: masternode_list :: MasternodeList) -> * const MasternodeList { ferment_interfaces :: boxed (MasternodeList { block_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . block_hash) , known_height : obj . known_height , masternode_merkle_root : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . masternode_merkle_root) , llmq_merkle_root : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . llmq_merkle_root) , masternodes : ferment_interfaces :: FFIConversion :: ffi_to (obj . masternodes) , quorums : ferment_interfaces :: FFIConversion :: ffi_to (obj . quorums) , }) } unsafe fn destroy (ffi : * mut MasternodeList) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for MasternodeList { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . block_hash) ; ; ; if ! ffi_ref . masternode_merkle_root . is_null () { ferment_interfaces :: unbox_any (ffi_ref . masternode_merkle_root) ; } ; if ! ffi_ref . llmq_merkle_root . is_null () { ferment_interfaces :: unbox_any (ffi_ref . llmq_merkle_root) ; } ; ferment_interfaces :: unbox_any (ffi_ref . masternodes) ; ; ferment_interfaces :: unbox_any (ffi_ref . quorums) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MasternodeList_ctor (block_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , known_height : u32 , masternode_merkle_root : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , llmq_merkle_root : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , masternodes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry , quorums : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry) -> * mut MasternodeList { ferment_interfaces :: boxed (MasternodeList { block_hash , known_height , masternode_merkle_root , llmq_merkle_root , masternodes , quorums , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MasternodeList_destroy (ffi : * mut MasternodeList) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod masternode_entry { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: models :: masternode_entry :: MasternodeEntry\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct MasternodeEntry { pub provider_registration_transaction_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub confirmed_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub confirmed_hash_hashed_with_provider_registration_transaction_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub socket_address : * mut crate :: fermented :: types :: common :: socket_address :: SocketAddress , pub operator_public_key : * mut crate :: fermented :: types :: models :: operator_public_key :: OperatorPublicKey , pub previous_operator_public_keys : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey , pub previous_entry_hashes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256 , pub previous_validity : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_common_block_Block_values_bool , pub known_confirmed_at_height : u32 , pub update_height : u32 , pub key_id_voting : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt160 , pub is_valid : bool , pub mn_type : * mut crate :: fermented :: types :: common :: masternode_type :: MasternodeType , pub platform_http_port : u16 , pub platform_node_id : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt160 , pub entry_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , } impl ferment_interfaces :: FFIConversion < crate :: models :: masternode_entry :: MasternodeEntry > for MasternodeEntry { unsafe fn ffi_from_const (ffi : * const MasternodeEntry) -> crate :: models :: masternode_entry :: MasternodeEntry { let ffi_ref = & * ffi ; crate :: models :: masternode_entry :: MasternodeEntry { provider_registration_transaction_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . provider_registration_transaction_hash) , confirmed_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . confirmed_hash) , confirmed_hash_hashed_with_provider_registration_transaction_hash : ferment_interfaces :: FFIConversion :: ffi_from_opt (ffi_ref . confirmed_hash_hashed_with_provider_registration_transaction_hash) , socket_address : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . socket_address) , operator_public_key : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . operator_public_key) , previous_operator_public_keys : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . previous_operator_public_keys) , previous_entry_hashes : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . previous_entry_hashes) , previous_validity : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . previous_validity) , known_confirmed_at_height : (ffi_ref . known_confirmed_at_height > 0) . then_some (ffi_ref . known_confirmed_at_height) , update_height : ffi_ref . update_height , key_id_voting : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . key_id_voting) , is_valid : ffi_ref . is_valid , mn_type : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . mn_type) , platform_http_port : ffi_ref . platform_http_port , platform_node_id : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . platform_node_id) , entry_hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . entry_hash) , } } unsafe fn ffi_to_const (obj : crate :: models :: masternode_entry :: MasternodeEntry) -> * const MasternodeEntry { ferment_interfaces :: boxed (MasternodeEntry { provider_registration_transaction_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . provider_registration_transaction_hash) , confirmed_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . confirmed_hash) , confirmed_hash_hashed_with_provider_registration_transaction_hash : ferment_interfaces :: FFIConversion :: ffi_to_opt (obj . confirmed_hash_hashed_with_provider_registration_transaction_hash) , socket_address : ferment_interfaces :: FFIConversion :: ffi_to (obj . socket_address) , operator_public_key : ferment_interfaces :: FFIConversion :: ffi_to (obj . operator_public_key) , previous_operator_public_keys : ferment_interfaces :: FFIConversion :: ffi_to (obj . previous_operator_public_keys) , previous_entry_hashes : ferment_interfaces :: FFIConversion :: ffi_to (obj . previous_entry_hashes) , previous_validity : ferment_interfaces :: FFIConversion :: ffi_to (obj . previous_validity) , known_confirmed_at_height : obj . known_confirmed_at_height . unwrap_or (0) , update_height : obj . update_height , key_id_voting : ferment_interfaces :: FFIConversion :: ffi_to (obj . key_id_voting) , is_valid : obj . is_valid , mn_type : ferment_interfaces :: FFIConversion :: ffi_to (obj . mn_type) , platform_http_port : obj . platform_http_port , platform_node_id : ferment_interfaces :: FFIConversion :: ffi_to (obj . platform_node_id) , entry_hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . entry_hash) , }) } unsafe fn destroy (ffi : * mut MasternodeEntry) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for MasternodeEntry { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . provider_registration_transaction_hash) ; ; ferment_interfaces :: unbox_any (ffi_ref . confirmed_hash) ; ; if ! ffi_ref . confirmed_hash_hashed_with_provider_registration_transaction_hash . is_null () { ferment_interfaces :: unbox_any (ffi_ref . confirmed_hash_hashed_with_provider_registration_transaction_hash) ; } ; ferment_interfaces :: unbox_any (ffi_ref . socket_address) ; ; ferment_interfaces :: unbox_any (ffi_ref . operator_public_key) ; ; ferment_interfaces :: unbox_any (ffi_ref . previous_operator_public_keys) ; ; ferment_interfaces :: unbox_any (ffi_ref . previous_entry_hashes) ; ; ferment_interfaces :: unbox_any (ffi_ref . previous_validity) ; ; ; ; ferment_interfaces :: unbox_any (ffi_ref . key_id_voting) ; ; ; ferment_interfaces :: unbox_any (ffi_ref . mn_type) ; ; ; ferment_interfaces :: unbox_any (ffi_ref . platform_node_id) ; ; ferment_interfaces :: unbox_any (ffi_ref . entry_hash) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MasternodeEntry_ctor (provider_registration_transaction_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , confirmed_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , confirmed_hash_hashed_with_provider_registration_transaction_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , socket_address : * mut crate :: fermented :: types :: common :: socket_address :: SocketAddress , operator_public_key : * mut crate :: fermented :: types :: models :: operator_public_key :: OperatorPublicKey , previous_operator_public_keys : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey , previous_entry_hashes : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256 , previous_validity : * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_common_block_Block_values_bool , known_confirmed_at_height : u32 , update_height : u32 , key_id_voting : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt160 , is_valid : bool , mn_type : * mut crate :: fermented :: types :: common :: masternode_type :: MasternodeType , platform_http_port : u16 , platform_node_id : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt160 , entry_hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256) -> * mut MasternodeEntry { ferment_interfaces :: boxed (MasternodeEntry { provider_registration_transaction_hash , confirmed_hash , confirmed_hash_hashed_with_provider_registration_transaction_hash , socket_address , operator_public_key , previous_operator_public_keys , previous_entry_hashes , previous_validity , known_confirmed_at_height , update_height , key_id_voting , is_valid , mn_type , platform_http_port , platform_node_id , entry_hash , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn MasternodeEntry_destroy (ffi : * mut MasternodeEntry) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod operator_public_key { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: models :: operator_public_key :: OperatorPublicKey\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct OperatorPublicKey { pub data : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt384 , pub version : u16 , } impl ferment_interfaces :: FFIConversion < crate :: models :: operator_public_key :: OperatorPublicKey > for OperatorPublicKey { unsafe fn ffi_from_const (ffi : * const OperatorPublicKey) -> crate :: models :: operator_public_key :: OperatorPublicKey { let ffi_ref = & * ffi ; crate :: models :: operator_public_key :: OperatorPublicKey { data : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . data) , version : ffi_ref . version , } } unsafe fn ffi_to_const (obj : crate :: models :: operator_public_key :: OperatorPublicKey) -> * const OperatorPublicKey { ferment_interfaces :: boxed (OperatorPublicKey { data : ferment_interfaces :: FFIConversion :: ffi_to (obj . data) , version : obj . version , }) } unsafe fn destroy (ffi : * mut OperatorPublicKey) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for OperatorPublicKey { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . data) ; ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn OperatorPublicKey_ctor (data : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt384 , version : u16) -> * mut OperatorPublicKey { ferment_interfaces :: boxed (OperatorPublicKey { data , version , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn OperatorPublicKey_destroy (ffi : * mut OperatorPublicKey) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod llmq_typed_hash { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: models :: llmq_typed_hash :: LLMQTypedHash\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct LLMQTypedHash { pub llmq_type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , } impl ferment_interfaces :: FFIConversion < crate :: models :: llmq_typed_hash :: LLMQTypedHash > for LLMQTypedHash { unsafe fn ffi_from_const (ffi : * const LLMQTypedHash) -> crate :: models :: llmq_typed_hash :: LLMQTypedHash { let ffi_ref = & * ffi ; crate :: models :: llmq_typed_hash :: LLMQTypedHash { llmq_type : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . llmq_type) , hash : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . hash) , } } unsafe fn ffi_to_const (obj : crate :: models :: llmq_typed_hash :: LLMQTypedHash) -> * const LLMQTypedHash { ferment_interfaces :: boxed (LLMQTypedHash { llmq_type : ferment_interfaces :: FFIConversion :: ffi_to (obj . llmq_type) , hash : ferment_interfaces :: FFIConversion :: ffi_to (obj . hash) , }) } unsafe fn destroy (ffi : * mut LLMQTypedHash) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQTypedHash { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . llmq_type) ; ; ferment_interfaces :: unbox_any (ffi_ref . hash) ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQTypedHash_ctor (llmq_type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , hash : * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256) -> * mut LLMQTypedHash { ferment_interfaces :: boxed (LLMQTypedHash { llmq_type , hash , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQTypedHash_destroy (ffi : * mut LLMQTypedHash) { ferment_interfaces :: unbox_any (ffi) ; } } } pub mod chain { pub mod common { pub mod llmq_type { # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: chain :: common :: llmq_type :: LLMQParams\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct LLMQParams { pub r#type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub name : * mut std :: os :: raw :: c_char , pub size : u32 , pub min_size : u32 , pub threshold : u32 , pub dkg_params : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: DKGParams , pub signing_active_quorum_count : u32 , pub keep_old_connections : u32 , pub recovery_members : u32 , } impl ferment_interfaces :: FFIConversion < crate :: chain :: common :: llmq_type :: LLMQParams > for LLMQParams { unsafe fn ffi_from_const (ffi : * const LLMQParams) -> crate :: chain :: common :: llmq_type :: LLMQParams { let ffi_ref = & * ffi ; crate :: chain :: common :: llmq_type :: LLMQParams { r#type : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . r#type) , name : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . name) , size : ffi_ref . size , min_size : ffi_ref . min_size , threshold : ffi_ref . threshold , dkg_params : ferment_interfaces :: FFIConversion :: ffi_from (ffi_ref . dkg_params) , signing_active_quorum_count : ffi_ref . signing_active_quorum_count , keep_old_connections : ffi_ref . keep_old_connections , recovery_members : ffi_ref . recovery_members , } } unsafe fn ffi_to_const (obj : crate :: chain :: common :: llmq_type :: LLMQParams) -> * const LLMQParams { ferment_interfaces :: boxed (LLMQParams { r#type : ferment_interfaces :: FFIConversion :: ffi_to (obj . r#type) , name : ferment_interfaces :: FFIConversion :: ffi_to (obj . name) , size : obj . size , min_size : obj . min_size , threshold : obj . threshold , dkg_params : ferment_interfaces :: FFIConversion :: ffi_to (obj . dkg_params) , signing_active_quorum_count : obj . signing_active_quorum_count , keep_old_connections : obj . keep_old_connections , recovery_members : obj . recovery_members , }) } unsafe fn destroy (ffi : * mut LLMQParams) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQParams { fn drop (& mut self) { unsafe { let ffi_ref = self ; ferment_interfaces :: unbox_any (ffi_ref . r#type) ; ; < std :: os :: raw :: c_char as ferment_interfaces :: FFIConversion < & str >> :: destroy (ffi_ref . name) ; ; ; ; ferment_interfaces :: unbox_any (ffi_ref . dkg_params) ; ; ; ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQParams_ctor (r#type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , name : * mut std :: os :: raw :: c_char , size : u32 , min_size : u32 , threshold : u32 , dkg_params : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: DKGParams , signing_active_quorum_count : u32 , keep_old_connections : u32 , recovery_members : u32) -> * mut LLMQParams { ferment_interfaces :: boxed (LLMQParams { r#type , name , size , min_size , threshold , dkg_params , signing_active_quorum_count , keep_old_connections , recovery_members , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQParams_destroy (ffi : * mut LLMQParams) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the # [doc = \"FFI-representation of the crate :: chain :: common :: llmq_type :: DKGParams\"]"] # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct DKGParams { pub interval : u32 , pub phase_blocks : u32 , pub mining_window_start : u32 , pub mining_window_end : u32 , pub bad_votes_threshold : u32 , } impl ferment_interfaces :: FFIConversion < crate :: chain :: common :: llmq_type :: DKGParams > for DKGParams { unsafe fn ffi_from_const (ffi : * const DKGParams) -> crate :: chain :: common :: llmq_type :: DKGParams { let ffi_ref = & * ffi ; crate :: chain :: common :: llmq_type :: DKGParams { interval : ffi_ref . interval , phase_blocks : ffi_ref . phase_blocks , mining_window_start : ffi_ref . mining_window_start , mining_window_end : ffi_ref . mining_window_end , bad_votes_threshold : ffi_ref . bad_votes_threshold , } } unsafe fn ffi_to_const (obj : crate :: chain :: common :: llmq_type :: DKGParams) -> * const DKGParams { ferment_interfaces :: boxed (DKGParams { interval : obj . interval , phase_blocks : obj . phase_blocks , mining_window_start : obj . mining_window_start , mining_window_end : obj . mining_window_end , bad_votes_threshold : obj . bad_votes_threshold , }) } unsafe fn destroy (ffi : * mut DKGParams) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for DKGParams { fn drop (& mut self) { unsafe { let ffi_ref = self ; ; ; ; ; ; } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DKGParams_ctor (interval : u32 , phase_blocks : u32 , mining_window_start : u32 , mining_window_end : u32 , bad_votes_threshold : u32) -> * mut DKGParams { ferment_interfaces :: boxed (DKGParams { interval , phase_blocks , mining_window_start , mining_window_end , bad_votes_threshold , }) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DKGParams_destroy (ffi : * mut DKGParams) { ferment_interfaces :: unbox_any (ffi) ; } # [doc = "FFI-representation of the LLMQType"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum LLMQType { LlmqtypeUnknown = 0 , Llmqtype50_60 = 1 , Llmqtype400_60 = 2 , Llmqtype400_85 = 3 , Llmqtype100_67 = 4 , Llmqtype60_75 = 5 , Llmqtype25_67 = 6 , LlmqtypeTest = 100 , LlmqtypeDevnet = 101 , LlmqtypeTestV17 = 102 , LlmqtypeTestDIP0024 = 103 , LlmqtypeTestInstantSend = 104 , LlmqtypeDevnetDIP0024 = 105 , LlmqtypeTestnetPlatform = 106 , LlmqtypeDevnetPlatform = 107 , } impl ferment_interfaces :: FFIConversion < crate :: chain :: common :: llmq_type :: LLMQType > for LLMQType { unsafe fn ffi_from_const (ffi : * const LLMQType) -> crate :: chain :: common :: llmq_type :: LLMQType { let ffi_ref = & * ffi ; match ffi_ref { LLMQType :: LlmqtypeUnknown => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeUnknown , LLMQType :: Llmqtype50_60 => crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype50_60 , LLMQType :: Llmqtype400_60 => crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype400_60 , LLMQType :: Llmqtype400_85 => crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype400_85 , LLMQType :: Llmqtype100_67 => crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype100_67 , LLMQType :: Llmqtype60_75 => crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype60_75 , LLMQType :: Llmqtype25_67 => crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype25_67 , LLMQType :: LlmqtypeTest => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTest , LLMQType :: LlmqtypeDevnet => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeDevnet , LLMQType :: LlmqtypeTestV17 => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestV17 , LLMQType :: LlmqtypeTestDIP0024 => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestDIP0024 , LLMQType :: LlmqtypeTestInstantSend => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestInstantSend , LLMQType :: LlmqtypeDevnetDIP0024 => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeDevnetDIP0024 , LLMQType :: LlmqtypeTestnetPlatform => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestnetPlatform , LLMQType :: LlmqtypeDevnetPlatform => crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeDevnetPlatform , } } unsafe fn ffi_to_const (obj : crate :: chain :: common :: llmq_type :: LLMQType) -> * const LLMQType { ferment_interfaces :: boxed (match obj { crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeUnknown => LLMQType :: LlmqtypeUnknown , crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype50_60 => LLMQType :: Llmqtype50_60 , crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype400_60 => LLMQType :: Llmqtype400_60 , crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype400_85 => LLMQType :: Llmqtype400_85 , crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype100_67 => LLMQType :: Llmqtype100_67 , crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype60_75 => LLMQType :: Llmqtype60_75 , crate :: chain :: common :: llmq_type :: LLMQType :: Llmqtype25_67 => LLMQType :: Llmqtype25_67 , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTest => LLMQType :: LlmqtypeTest , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeDevnet => LLMQType :: LlmqtypeDevnet , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestV17 => LLMQType :: LlmqtypeTestV17 , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestDIP0024 => LLMQType :: LlmqtypeTestDIP0024 , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestInstantSend => LLMQType :: LlmqtypeTestInstantSend , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeDevnetDIP0024 => LLMQType :: LlmqtypeDevnetDIP0024 , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeTestnetPlatform => LLMQType :: LlmqtypeTestnetPlatform , crate :: chain :: common :: llmq_type :: LLMQType :: LlmqtypeDevnetPlatform => LLMQType :: LlmqtypeDevnetPlatform , }) } unsafe fn destroy (ffi : * mut LLMQType) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for LLMQType { fn drop (& mut self) { unsafe { match self { LLMQType :: LlmqtypeUnknown => { } , LLMQType :: Llmqtype50_60 => { } , LLMQType :: Llmqtype400_60 => { } , LLMQType :: Llmqtype400_85 => { } , LLMQType :: Llmqtype100_67 => { } , LLMQType :: Llmqtype60_75 => { } , LLMQType :: Llmqtype25_67 => { } , LLMQType :: LlmqtypeTest => { } , LLMQType :: LlmqtypeDevnet => { } , LLMQType :: LlmqtypeTestV17 => { } , LLMQType :: LlmqtypeTestDIP0024 => { } , LLMQType :: LlmqtypeTestInstantSend => { } , LLMQType :: LlmqtypeDevnetDIP0024 => { } , LLMQType :: LlmqtypeTestnetPlatform => { } , LLMQType :: LlmqtypeDevnetPlatform => { } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeUnknown_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeUnknown) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_Llmqtype50_60_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: Llmqtype50_60) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_Llmqtype400_60_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: Llmqtype400_60) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_Llmqtype400_85_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: Llmqtype400_85) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_Llmqtype100_67_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: Llmqtype100_67) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_Llmqtype60_75_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: Llmqtype60_75) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_Llmqtype25_67_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: Llmqtype25_67) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeTest_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeTest) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeDevnet_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeDevnet) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeTestV17_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeTestV17) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeTestDIP0024_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeTestDIP0024) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeTestInstantSend_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeTestInstantSend) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeDevnetDIP0024_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeDevnetDIP0024) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeTestnetPlatform_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeTestnetPlatform) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_LlmqtypeDevnetPlatform_ctor () -> * mut LLMQType { ferment_interfaces :: boxed (LLMQType :: LlmqtypeDevnetPlatform) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn LLMQType_destroy (ffi : * mut LLMQType) { ferment_interfaces :: unbox_any (ffi) ; } } pub mod chain_type { # [doc = "FFI-representation of the ChainType"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum ChainType { MainNet , TestNet , DevNet (* mut crate :: fermented :: types :: chain :: common :: chain_type :: DevnetType ,) , } impl ferment_interfaces :: FFIConversion < crate :: chain :: common :: chain_type :: ChainType > for ChainType { unsafe fn ffi_from_const (ffi : * const ChainType) -> crate :: chain :: common :: chain_type :: ChainType { let ffi_ref = & * ffi ; match ffi_ref { ChainType :: MainNet => crate :: chain :: common :: chain_type :: ChainType :: MainNet , ChainType :: TestNet => crate :: chain :: common :: chain_type :: ChainType :: TestNet , ChainType :: DevNet (o_0 ,) => crate :: chain :: common :: chain_type :: ChainType :: DevNet (ferment_interfaces :: FFIConversion :: ffi_from (* o_0) ,) , } } unsafe fn ffi_to_const (obj : crate :: chain :: common :: chain_type :: ChainType) -> * const ChainType { ferment_interfaces :: boxed (match obj { crate :: chain :: common :: chain_type :: ChainType :: MainNet => ChainType :: MainNet , crate :: chain :: common :: chain_type :: ChainType :: TestNet => ChainType :: TestNet , crate :: chain :: common :: chain_type :: ChainType :: DevNet (o_0 ,) => ChainType :: DevNet (ferment_interfaces :: FFIConversion :: ffi_to (o_0) ,) , }) } unsafe fn destroy (ffi : * mut ChainType) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for ChainType { fn drop (& mut self) { unsafe { match self { ChainType :: MainNet => { } , ChainType :: TestNet => { } , ChainType :: DevNet (o_0 ,) => { ferment_interfaces :: unbox_any (o_0 . to_owned ()) ; ; } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ChainType_MainNet_ctor () -> * mut ChainType { ferment_interfaces :: boxed (ChainType :: MainNet) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ChainType_TestNet_ctor () -> * mut ChainType { ferment_interfaces :: boxed (ChainType :: TestNet) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ChainType_DevNet_ctor (o_o_0 : * mut crate :: fermented :: types :: chain :: common :: chain_type :: DevnetType) -> * mut ChainType { ferment_interfaces :: boxed (ChainType :: DevNet (o_o_0 ,)) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ChainType_destroy (ffi : * mut ChainType) { ferment_interfaces :: unbox_any (ffi) ; } # [allow (non_snake_case , non_upper_case_globals)] static ChainType_IHaveChainSettings_VTable : IHaveChainSettings_VTable = { unsafe extern "C" fn ChainType_genesis_hash (obj : * const () ,) -> * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: genesis_hash (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn ChainType_genesis_height (obj : * const () ,) -> u32 { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: genesis_height (cast_obj ,) ; obj } unsafe extern "C" fn ChainType_is_llmq_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: is_llmq_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn ChainType_isd_llmq_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: isd_llmq_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn ChainType_chain_locks_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: chain_locks_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn ChainType_platform_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: platform_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn ChainType_should_process_llmq_of_type (obj : * const () , llmq_type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType ,) -> bool { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: should_process_llmq_of_type (cast_obj , ferment_interfaces :: FFIConversion :: ffi_from (llmq_type) ,) ; obj } unsafe extern "C" fn ChainType_is_evolution_enabled (obj : * const () ,) -> bool { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: is_evolution_enabled (cast_obj ,) ; obj } unsafe extern "C" fn ChainType_name (obj : * const () ,) -> * mut std :: os :: raw :: c_char { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: ChainType)) ; let obj = < crate :: chain :: common :: chain_type :: ChainType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: name (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } IHaveChainSettings_VTable { genesis_hash : ChainType_genesis_hash , genesis_height : ChainType_genesis_height , is_llmq_type : ChainType_is_llmq_type , isd_llmq_type : ChainType_isd_llmq_type , chain_locks_type : ChainType_chain_locks_type , platform_type : ChainType_platform_type , should_process_llmq_of_type : ChainType_should_process_llmq_of_type , is_evolution_enabled : ChainType_is_evolution_enabled , name : ChainType_name , } } ; # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub extern "C" fn ChainType_as_IHaveChainSettings_TraitObject (obj : * const crate :: chain :: common :: chain_type :: ChainType) -> IHaveChainSettings_TraitObject { IHaveChainSettings_TraitObject { object : obj as * const () , vtable : & ChainType_IHaveChainSettings_VTable , } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn ChainType_as_IHaveChainSettings_TraitObject_destroy (obj : IHaveChainSettings_TraitObject) { ferment_interfaces :: unbox_any (obj . object as * mut crate :: chain :: common :: chain_type :: ChainType) ; } # [doc = "FFI-representation of the DevnetType"] # [repr (C)] # [allow (non_camel_case_types)] # [derive (Clone)] pub enum DevnetType { JackDaniels = 0 , Devnet333 = 1 , Chacha = 2 , Mojito = 3 , WhiteRussian = 4 , MiningTest = 5 , Mobile2 = 6 , Zero = 7 , Screwdriver = 8 , Absinthe = 9 , Bintang = 10 , } impl ferment_interfaces :: FFIConversion < crate :: chain :: common :: chain_type :: DevnetType > for DevnetType { unsafe fn ffi_from_const (ffi : * const DevnetType) -> crate :: chain :: common :: chain_type :: DevnetType { let ffi_ref = & * ffi ; match ffi_ref { DevnetType :: JackDaniels => crate :: chain :: common :: chain_type :: DevnetType :: JackDaniels , DevnetType :: Devnet333 => crate :: chain :: common :: chain_type :: DevnetType :: Devnet333 , DevnetType :: Chacha => crate :: chain :: common :: chain_type :: DevnetType :: Chacha , DevnetType :: Mojito => crate :: chain :: common :: chain_type :: DevnetType :: Mojito , DevnetType :: WhiteRussian => crate :: chain :: common :: chain_type :: DevnetType :: WhiteRussian , DevnetType :: MiningTest => crate :: chain :: common :: chain_type :: DevnetType :: MiningTest , DevnetType :: Mobile2 => crate :: chain :: common :: chain_type :: DevnetType :: Mobile2 , DevnetType :: Zero => crate :: chain :: common :: chain_type :: DevnetType :: Zero , DevnetType :: Screwdriver => crate :: chain :: common :: chain_type :: DevnetType :: Screwdriver , DevnetType :: Absinthe => crate :: chain :: common :: chain_type :: DevnetType :: Absinthe , DevnetType :: Bintang => crate :: chain :: common :: chain_type :: DevnetType :: Bintang , } } unsafe fn ffi_to_const (obj : crate :: chain :: common :: chain_type :: DevnetType) -> * const DevnetType { ferment_interfaces :: boxed (match obj { crate :: chain :: common :: chain_type :: DevnetType :: JackDaniels => DevnetType :: JackDaniels , crate :: chain :: common :: chain_type :: DevnetType :: Devnet333 => DevnetType :: Devnet333 , crate :: chain :: common :: chain_type :: DevnetType :: Chacha => DevnetType :: Chacha , crate :: chain :: common :: chain_type :: DevnetType :: Mojito => DevnetType :: Mojito , crate :: chain :: common :: chain_type :: DevnetType :: WhiteRussian => DevnetType :: WhiteRussian , crate :: chain :: common :: chain_type :: DevnetType :: MiningTest => DevnetType :: MiningTest , crate :: chain :: common :: chain_type :: DevnetType :: Mobile2 => DevnetType :: Mobile2 , crate :: chain :: common :: chain_type :: DevnetType :: Zero => DevnetType :: Zero , crate :: chain :: common :: chain_type :: DevnetType :: Screwdriver => DevnetType :: Screwdriver , crate :: chain :: common :: chain_type :: DevnetType :: Absinthe => DevnetType :: Absinthe , crate :: chain :: common :: chain_type :: DevnetType :: Bintang => DevnetType :: Bintang , }) } unsafe fn destroy (ffi : * mut DevnetType) { ferment_interfaces :: unbox_any (ffi) ; } } impl Drop for DevnetType { fn drop (& mut self) { unsafe { match self { DevnetType :: JackDaniels => { } , DevnetType :: Devnet333 => { } , DevnetType :: Chacha => { } , DevnetType :: Mojito => { } , DevnetType :: WhiteRussian => { } , DevnetType :: MiningTest => { } , DevnetType :: Mobile2 => { } , DevnetType :: Zero => { } , DevnetType :: Screwdriver => { } , DevnetType :: Absinthe => { } , DevnetType :: Bintang => { } , } } } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_JackDaniels_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: JackDaniels) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Devnet333_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Devnet333) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Chacha_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Chacha) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Mojito_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Mojito) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_WhiteRussian_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: WhiteRussian) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_MiningTest_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: MiningTest) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Mobile2_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Mobile2) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Zero_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Zero) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Screwdriver_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Screwdriver) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Absinthe_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Absinthe) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_Bintang_ctor () -> * mut DevnetType { ferment_interfaces :: boxed (DevnetType :: Bintang) } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_destroy (ffi : * mut DevnetType) { ferment_interfaces :: unbox_any (ffi) ; } # [allow (non_snake_case , non_upper_case_globals)] static DevnetType_IHaveChainSettings_VTable : IHaveChainSettings_VTable = { unsafe extern "C" fn DevnetType_genesis_hash (obj : * const () ,) -> * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: genesis_hash (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn DevnetType_genesis_height (obj : * const () ,) -> u32 { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: genesis_height (cast_obj ,) ; obj } unsafe extern "C" fn DevnetType_is_llmq_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: is_llmq_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn DevnetType_isd_llmq_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: isd_llmq_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn DevnetType_chain_locks_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: chain_locks_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn DevnetType_platform_type (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: platform_type (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } unsafe extern "C" fn DevnetType_should_process_llmq_of_type (obj : * const () , llmq_type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType ,) -> bool { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: should_process_llmq_of_type (cast_obj , ferment_interfaces :: FFIConversion :: ffi_from (llmq_type) ,) ; obj } unsafe extern "C" fn DevnetType_is_evolution_enabled (obj : * const () ,) -> bool { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: is_evolution_enabled (cast_obj ,) ; obj } unsafe extern "C" fn DevnetType_name (obj : * const () ,) -> * mut std :: os :: raw :: c_char { let cast_obj = & (* (obj as * const crate :: chain :: common :: chain_type :: DevnetType)) ; let obj = < crate :: chain :: common :: chain_type :: DevnetType as crate :: chain :: common :: chain_type :: IHaveChainSettings > :: name (cast_obj ,) ; ferment_interfaces :: FFIConversion :: ffi_to (obj) } IHaveChainSettings_VTable { genesis_hash : DevnetType_genesis_hash , genesis_height : DevnetType_genesis_height , is_llmq_type : DevnetType_is_llmq_type , isd_llmq_type : DevnetType_isd_llmq_type , chain_locks_type : DevnetType_chain_locks_type , platform_type : DevnetType_platform_type , should_process_llmq_of_type : DevnetType_should_process_llmq_of_type , is_evolution_enabled : DevnetType_is_evolution_enabled , name : DevnetType_name , } } ; # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub extern "C" fn DevnetType_as_IHaveChainSettings_TraitObject (obj : * const crate :: chain :: common :: chain_type :: DevnetType) -> IHaveChainSettings_TraitObject { IHaveChainSettings_TraitObject { object : obj as * const () , vtable : & DevnetType_IHaveChainSettings_VTable , } } # [doc = r" # Safety"] # [allow (non_snake_case)] # [no_mangle] pub unsafe extern "C" fn DevnetType_as_IHaveChainSettings_TraitObject_destroy (obj : IHaveChainSettings_TraitObject) { ferment_interfaces :: unbox_any (obj . object as * mut crate :: chain :: common :: chain_type :: DevnetType) ; } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct IHaveChainSettings_VTable { pub genesis_hash : unsafe extern "C" fn (obj : * const () ,) -> * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub genesis_height : unsafe extern "C" fn (obj : * const () ,) -> u32 , pub is_llmq_type : unsafe extern "C" fn (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub isd_llmq_type : unsafe extern "C" fn (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub chain_locks_type : unsafe extern "C" fn (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub platform_type : unsafe extern "C" fn (obj : * const () ,) -> * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub should_process_llmq_of_type : unsafe extern "C" fn (obj : * const () , llmq_type : * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType ,) -> bool , pub is_evolution_enabled : unsafe extern "C" fn (obj : * const () ,) -> bool , pub name : unsafe extern "C" fn (obj : * const () ,) -> * mut std :: os :: raw :: c_char , } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct IHaveChainSettings_TraitObject { pub object : * const () , pub vtable : * const IHaveChainSettings_VTable } } } } } # [allow (clippy :: let_and_return , clippy :: suspicious_else_formatting , clippy :: redundant_field_names , dead_code , redundant_semicolons , unused_braces , unused_imports , unused_unsafe , unused_variables , unused_qualifications)] pub mod generics { # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_crate_models_llmq_entry_LLMQEntry { pub count : usize , pub values : * mut * mut crate :: fermented :: types :: models :: llmq_entry :: LLMQEntry , } impl ferment_interfaces :: FFIConversion < Vec < crate :: models :: llmq_entry :: LLMQEntry > > for Vec_crate_models_llmq_entry_LLMQEntry { unsafe fn ffi_from_const (ffi : * const Vec_crate_models_llmq_entry_LLMQEntry) -> Vec < crate :: models :: llmq_entry :: LLMQEntry > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < crate :: models :: llmq_entry :: LLMQEntry >) -> * const Vec_crate_models_llmq_entry_LLMQEntry { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_crate_models_llmq_entry_LLMQEntry) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_crate_models_llmq_entry_LLMQEntry { type Value = Vec < crate :: models :: llmq_entry :: LLMQEntry > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_complex_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: to_complex_vec (obj . into_iter ()) }) } } impl Drop for Vec_crate_models_llmq_entry_LLMQEntry { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256 { pub count : usize , pub keys : * mut * mut crate :: fermented :: types :: common :: block :: Block , pub values : * mut * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , } impl ferment_interfaces :: FFIConversion < std :: collections :: BTreeMap < crate :: common :: block :: Block , crate :: crypto :: byte_util :: UInt256 > > for std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256 { unsafe fn ffi_from_const (ffi : * const std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256) -> std :: collections :: BTreeMap < crate :: common :: block :: Block , crate :: crypto :: byte_util :: UInt256 > { let ffi_ref = & * ffi ; ferment_interfaces :: fold_to_map (ffi_ref . count , ffi_ref . keys , ffi_ref . values , | o | ferment_interfaces :: FFIConversion :: ffi_from (o) , | o | ferment_interfaces :: FFIConversion :: ffi_from (o)) } unsafe fn ffi_to_const (obj : std :: collections :: BTreeMap < crate :: common :: block :: Block , crate :: crypto :: byte_util :: UInt256 >) -> * const std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256 { ferment_interfaces :: boxed (Self { count : obj . len () , keys : ferment_interfaces :: to_complex_vec (obj . keys () . cloned ()) , values : ferment_interfaces :: to_complex_vec (obj . values () . cloned ()) }) } unsafe fn destroy (ffi : * mut std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl Drop for std_collections_Map_keys_crate_common_block_Block_values_crate_crypto_byte_util_UInt256 { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . keys , self . count) ; ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_crate_models_snapshot_LLMQSnapshot { pub count : usize , pub values : * mut * mut crate :: fermented :: types :: models :: snapshot :: LLMQSnapshot , } impl ferment_interfaces :: FFIConversion < Vec < crate :: models :: snapshot :: LLMQSnapshot > > for Vec_crate_models_snapshot_LLMQSnapshot { unsafe fn ffi_from_const (ffi : * const Vec_crate_models_snapshot_LLMQSnapshot) -> Vec < crate :: models :: snapshot :: LLMQSnapshot > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < crate :: models :: snapshot :: LLMQSnapshot >) -> * const Vec_crate_models_snapshot_LLMQSnapshot { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_crate_models_snapshot_LLMQSnapshot) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_crate_models_snapshot_LLMQSnapshot { type Value = Vec < crate :: models :: snapshot :: LLMQSnapshot > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_complex_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: to_complex_vec (obj . into_iter ()) }) } } impl Drop for Vec_crate_models_snapshot_LLMQSnapshot { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_u8 { pub count : usize , pub values : * mut u8 , } impl ferment_interfaces :: FFIConversion < Vec < u8 > > for Vec_u8 { unsafe fn ffi_from_const (ffi : * const Vec_u8) -> Vec < u8 > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < u8 >) -> * const Vec_u8 { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_u8) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_u8 { type Value = Vec < u8 > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_primitive_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: boxed_vec (obj) }) } } impl Drop for Vec_u8 { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { pub count : usize , pub keys : * mut * mut crate :: fermented :: types :: chain :: common :: llmq_type :: LLMQType , pub values : * mut * mut crate :: fermented :: generics :: std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry , } impl ferment_interfaces :: FFIConversion < std :: collections :: BTreeMap < crate :: chain :: common :: llmq_type :: LLMQType , std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: llmq_entry :: LLMQEntry > > > for std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { unsafe fn ffi_from_const (ffi : * const std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry) -> std :: collections :: BTreeMap < crate :: chain :: common :: llmq_type :: LLMQType , std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: llmq_entry :: LLMQEntry > > { let ffi_ref = & * ffi ; ferment_interfaces :: fold_to_map (ffi_ref . count , ffi_ref . keys , ffi_ref . values , | o | ferment_interfaces :: FFIConversion :: ffi_from (o) , | o | ferment_interfaces :: FFIConversion :: ffi_from (o)) } unsafe fn ffi_to_const (obj : std :: collections :: BTreeMap < crate :: chain :: common :: llmq_type :: LLMQType , std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: llmq_entry :: LLMQEntry > >) -> * const std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { ferment_interfaces :: boxed (Self { count : obj . len () , keys : ferment_interfaces :: to_complex_vec (obj . keys () . cloned ()) , values : ferment_interfaces :: to_complex_vec (obj . values () . cloned ()) }) } unsafe fn destroy (ffi : * mut std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl Drop for std_collections_Map_keys_crate_chain_common_llmq_type_LLMQType_values_std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . keys , self . count) ; ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry { pub count : usize , pub keys : * mut * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub values : * mut * mut crate :: fermented :: types :: models :: masternode_entry :: MasternodeEntry , } impl ferment_interfaces :: FFIConversion < std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: masternode_entry :: MasternodeEntry > > for std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry { unsafe fn ffi_from_const (ffi : * const std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry) -> std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: masternode_entry :: MasternodeEntry > { let ffi_ref = & * ffi ; ferment_interfaces :: fold_to_map (ffi_ref . count , ffi_ref . keys , ffi_ref . values , | o | ferment_interfaces :: FFIConversion :: ffi_from (o) , | o | ferment_interfaces :: FFIConversion :: ffi_from (o)) } unsafe fn ffi_to_const (obj : std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: masternode_entry :: MasternodeEntry >) -> * const std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry { ferment_interfaces :: boxed (Self { count : obj . len () , keys : ferment_interfaces :: to_complex_vec (obj . keys () . cloned ()) , values : ferment_interfaces :: to_complex_vec (obj . values () . cloned ()) }) } unsafe fn destroy (ffi : * mut std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl Drop for std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_masternode_entry_MasternodeEntry { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . keys , self . count) ; ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_crate_processing_mn_listdiff_result_MNListDiffResult { pub count : usize , pub values : * mut * mut crate :: fermented :: types :: processing :: mn_listdiff_result :: MNListDiffResult , } impl ferment_interfaces :: FFIConversion < Vec < crate :: processing :: mn_listdiff_result :: MNListDiffResult > > for Vec_crate_processing_mn_listdiff_result_MNListDiffResult { unsafe fn ffi_from_const (ffi : * const Vec_crate_processing_mn_listdiff_result_MNListDiffResult) -> Vec < crate :: processing :: mn_listdiff_result :: MNListDiffResult > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < crate :: processing :: mn_listdiff_result :: MNListDiffResult >) -> * const Vec_crate_processing_mn_listdiff_result_MNListDiffResult { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_crate_processing_mn_listdiff_result_MNListDiffResult) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_crate_processing_mn_listdiff_result_MNListDiffResult { type Value = Vec < crate :: processing :: mn_listdiff_result :: MNListDiffResult > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_complex_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: to_complex_vec (obj . into_iter ()) }) } } impl Drop for Vec_crate_processing_mn_listdiff_result_MNListDiffResult { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct std_collections_Map_keys_crate_common_block_Block_values_bool { pub count : usize , pub keys : * mut * mut crate :: fermented :: types :: common :: block :: Block , pub values : * mut bool , } impl ferment_interfaces :: FFIConversion < std :: collections :: BTreeMap < crate :: common :: block :: Block , bool > > for std_collections_Map_keys_crate_common_block_Block_values_bool { unsafe fn ffi_from_const (ffi : * const std_collections_Map_keys_crate_common_block_Block_values_bool) -> std :: collections :: BTreeMap < crate :: common :: block :: Block , bool > { let ffi_ref = & * ffi ; ferment_interfaces :: fold_to_map (ffi_ref . count , ffi_ref . keys , ffi_ref . values , | o | ferment_interfaces :: FFIConversion :: ffi_from (o) , | o | o) } unsafe fn ffi_to_const (obj : std :: collections :: BTreeMap < crate :: common :: block :: Block , bool >) -> * const std_collections_Map_keys_crate_common_block_Block_values_bool { ferment_interfaces :: boxed (Self { count : obj . len () , keys : ferment_interfaces :: to_complex_vec (obj . keys () . cloned ()) , values : ferment_interfaces :: to_primitive_vec (obj . values () . cloned ()) }) } unsafe fn destroy (ffi : * mut std_collections_Map_keys_crate_common_block_Block_values_bool) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl Drop for std_collections_Map_keys_crate_common_block_Block_values_bool { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . keys , self . count) ; ferment_interfaces :: unbox_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_crate_crypto_byte_util_UInt256 { pub count : usize , pub values : * mut * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , } impl ferment_interfaces :: FFIConversion < Vec < crate :: crypto :: byte_util :: UInt256 > > for Vec_crate_crypto_byte_util_UInt256 { unsafe fn ffi_from_const (ffi : * const Vec_crate_crypto_byte_util_UInt256) -> Vec < crate :: crypto :: byte_util :: UInt256 > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < crate :: crypto :: byte_util :: UInt256 >) -> * const Vec_crate_crypto_byte_util_UInt256 { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_crate_crypto_byte_util_UInt256) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_crate_crypto_byte_util_UInt256 { type Value = Vec < crate :: crypto :: byte_util :: UInt256 > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_complex_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: to_complex_vec (obj . into_iter ()) }) } } impl Drop for Vec_crate_crypto_byte_util_UInt256 { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_i32 { pub count : usize , pub values : * mut i32 , } impl ferment_interfaces :: FFIConversion < Vec < i32 > > for Vec_i32 { unsafe fn ffi_from_const (ffi : * const Vec_i32) -> Vec < i32 > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < i32 >) -> * const Vec_i32 { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_i32) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_i32 { type Value = Vec < i32 > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_primitive_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: boxed_vec (obj) }) } } impl Drop for Vec_i32 { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_crate_tx_transaction_TransactionOutput { pub count : usize , pub values : * mut * mut crate :: fermented :: types :: tx :: transaction :: TransactionOutput , } impl ferment_interfaces :: FFIConversion < Vec < crate :: tx :: transaction :: TransactionOutput > > for Vec_crate_tx_transaction_TransactionOutput { unsafe fn ffi_from_const (ffi : * const Vec_crate_tx_transaction_TransactionOutput) -> Vec < crate :: tx :: transaction :: TransactionOutput > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < crate :: tx :: transaction :: TransactionOutput >) -> * const Vec_crate_tx_transaction_TransactionOutput { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_crate_tx_transaction_TransactionOutput) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_crate_tx_transaction_TransactionOutput { type Value = Vec < crate :: tx :: transaction :: TransactionOutput > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_complex_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: to_complex_vec (obj . into_iter ()) }) } } impl Drop for Vec_crate_tx_transaction_TransactionOutput { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct Vec_crate_tx_transaction_TransactionInput { pub count : usize , pub values : * mut * mut crate :: fermented :: types :: tx :: transaction :: TransactionInput , } impl ferment_interfaces :: FFIConversion < Vec < crate :: tx :: transaction :: TransactionInput > > for Vec_crate_tx_transaction_TransactionInput { unsafe fn ffi_from_const (ffi : * const Vec_crate_tx_transaction_TransactionInput) -> Vec < crate :: tx :: transaction :: TransactionInput > { ferment_interfaces :: FFIVecConversion :: decode (& * ffi) } unsafe fn ffi_to_const (obj : Vec < crate :: tx :: transaction :: TransactionInput >) -> * const Vec_crate_tx_transaction_TransactionInput { ferment_interfaces :: FFIVecConversion :: encode (obj) } unsafe fn destroy (ffi : * mut Vec_crate_tx_transaction_TransactionInput) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl ferment_interfaces :: FFIVecConversion for Vec_crate_tx_transaction_TransactionInput { type Value = Vec < crate :: tx :: transaction :: TransactionInput > ; unsafe fn decode (& self) -> Self :: Value { ferment_interfaces :: from_complex_vec (self . values , self . count) } unsafe fn encode (obj : Self :: Value) -> * mut Self { ferment_interfaces :: boxed (Self { count : obj . len () , values : ferment_interfaces :: to_complex_vec (obj . into_iter ()) }) } } impl Drop for Vec_crate_tx_transaction_TransactionInput { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { pub count : usize , pub keys : * mut * mut crate :: fermented :: types :: crypto :: byte_util :: UInt256 , pub values : * mut * mut crate :: fermented :: types :: models :: llmq_entry :: LLMQEntry , } impl ferment_interfaces :: FFIConversion < std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: llmq_entry :: LLMQEntry > > for std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { unsafe fn ffi_from_const (ffi : * const std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry) -> std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: llmq_entry :: LLMQEntry > { let ffi_ref = & * ffi ; ferment_interfaces :: fold_to_map (ffi_ref . count , ffi_ref . keys , ffi_ref . values , | o | ferment_interfaces :: FFIConversion :: ffi_from (o) , | o | ferment_interfaces :: FFIConversion :: ffi_from (o)) } unsafe fn ffi_to_const (obj : std :: collections :: BTreeMap < crate :: crypto :: byte_util :: UInt256 , crate :: models :: llmq_entry :: LLMQEntry >) -> * const std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { ferment_interfaces :: boxed (Self { count : obj . len () , keys : ferment_interfaces :: to_complex_vec (obj . keys () . cloned ()) , values : ferment_interfaces :: to_complex_vec (obj . values () . cloned ()) }) } unsafe fn destroy (ffi : * mut std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl Drop for std_collections_Map_keys_crate_crypto_byte_util_UInt256_values_crate_models_llmq_entry_LLMQEntry { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . keys , self . count) ; ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } # [repr (C)] # [derive (Clone)] # [allow (non_camel_case_types)] pub struct std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey { pub count : usize , pub keys : * mut * mut crate :: fermented :: types :: common :: block :: Block , pub values : * mut * mut crate :: fermented :: types :: models :: operator_public_key :: OperatorPublicKey , } impl ferment_interfaces :: FFIConversion < std :: collections :: BTreeMap < crate :: common :: block :: Block , crate :: models :: operator_public_key :: OperatorPublicKey > > for std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey { unsafe fn ffi_from_const (ffi : * const std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey) -> std :: collections :: BTreeMap < crate :: common :: block :: Block , crate :: models :: operator_public_key :: OperatorPublicKey > { let ffi_ref = & * ffi ; ferment_interfaces :: fold_to_map (ffi_ref . count , ffi_ref . keys , ffi_ref . values , | o | ferment_interfaces :: FFIConversion :: ffi_from (o) , | o | ferment_interfaces :: FFIConversion :: ffi_from (o)) } unsafe fn ffi_to_const (obj : std :: collections :: BTreeMap < crate :: common :: block :: Block , crate :: models :: operator_public_key :: OperatorPublicKey >) -> * const std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey { ferment_interfaces :: boxed (Self { count : obj . len () , keys : ferment_interfaces :: to_complex_vec (obj . keys () . cloned ()) , values : ferment_interfaces :: to_complex_vec (obj . values () . cloned ()) }) } unsafe fn destroy (ffi : * mut std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey) { ferment_interfaces :: unbox_any (ffi) ; ; } } impl Drop for std_collections_Map_keys_crate_common_block_Block_values_crate_models_operator_public_key_OperatorPublicKey { fn drop (& mut self) { unsafe { ferment_interfaces :: unbox_any_vec_ptr (self . keys , self . count) ; ferment_interfaces :: unbox_any_vec_ptr (self . values , self . count) ; } } } } \ No newline at end of file diff --git a/dash-spv-masternode-processor/src/keys/bls_key.rs b/dash-spv-masternode-processor/src/keys/bls_key.rs index 937c8903..8b63171a 100644 --- a/dash-spv-masternode-processor/src/keys/bls_key.rs +++ b/dash-spv-masternode-processor/src/keys/bls_key.rs @@ -1,6 +1,7 @@ use bls_signatures::bip32::{ChainCode, ExtendedPrivateKey, ExtendedPublicKey}; use bls_signatures::{BasicSchemeMPL, BlsError, G1Element, G2Element, LegacySchemeMPL, PrivateKey, Scheme}; -use hashes::{Hash, hex::FromHex, sha256, sha256d}; +use hashes::{Hash, hex::FromHex, sha256, sha256d, hex}; +use hashes::hex::ToHex; use crate::chain::{derivation::IIndexPath, ScriptMap}; use crate::consensus::Encodable; use crate::crypto::{UInt256, UInt384, UInt768, byte_util::{AsBytes, BytesDecodable, Zeroable}, UInt160}; @@ -8,6 +9,8 @@ use crate::keys::{IKey, KeyKind, dip14::{IChildKeyDerivation, SignKey}}; use crate::keys::crypto_data::{CryptoData, DHKey}; use crate::models::OperatorPublicKey; use crate::util::{base58, data_ops::hex_with_data, sec_vec::SecVec}; +#[cfg(feature = "use_serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[derive(Clone, Debug, Default)] pub struct BLSKey { @@ -69,6 +72,9 @@ impl IKey for BLSKey { fn verify(&mut self, message_digest: &[u8], signature: &[u8]) -> bool { self.verify_uint768(UInt256::from(message_digest), UInt768::from(signature)) } + fn verify_with_uint(&mut self, message_digest: UInt256, signature: &[u8]) -> bool { + self.verify_uint768(message_digest, UInt768::from(signature)) + } fn secret_key(&self) -> UInt256 { self.seckey @@ -372,9 +378,23 @@ impl BLSKey { } pub fn verify_uint768(&self, digest: UInt256, signature: UInt768) -> bool { + let digest_hex = digest.as_bytes().to_hex(); + let signature_hex = signature.as_bytes().to_hex(); + Self::verify_message_with_key(self, digest.as_bytes(), signature) } + pub fn verify_insecure(&self, message: &[u8], signature: UInt768) -> bool { + return self.bls_public_key() + .map_or(false, |public_key| + match g2_element_from_bytes(false, signature.as_bytes()) { + Ok(signature) => + BasicSchemeMPL::new().verify(&public_key, message, &signature), + _ => false + } + ); + } + pub fn verify_with_public_key(digest: UInt256, signature: UInt768, public_key: UInt384, use_legacy: bool) -> bool { Self::verify_message_with_key(&BLSKey::key_with_public_key(public_key, use_legacy), digest.as_bytes(), signature) } diff --git a/dash-spv-masternode-processor/src/keys/ecdsa_key.rs b/dash-spv-masternode-processor/src/keys/ecdsa_key.rs index 905ffd6d..c8410ec8 100644 --- a/dash-spv-masternode-processor/src/keys/ecdsa_key.rs +++ b/dash-spv-masternode-processor/src/keys/ecdsa_key.rs @@ -5,6 +5,8 @@ use hashes::sha256; use hashes::hex::{FromHex, ToHex}; use secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; use secp256k1::Secp256k1; +use logging::*; +use tracing::*; use crate::chain::bip::bip32; use crate::chain::common::ChainType; use crate::chain::derivation::{BIP32_HARD, IIndexPath, IndexPath}; @@ -255,7 +257,7 @@ impl IKey for ECDSAKey { fn sign(&self, data: &[u8]) -> Vec { if self.seckey.is_zero() { - warn!("There is no seckey for sign"); + log_warn!(target: "masternode-processor", "There is no seckey for sign"); return vec![]; } match (Self::message_from_bytes(data), self.secret_key()) { @@ -457,7 +459,7 @@ impl ECDSAKey { pub fn compact_sign(&self, message_digest: UInt256) -> [u8; 65] { let mut sig = [0u8; 65]; if self.seckey.is_zero() { - warn!("Can't sign with a public key"); + log_warn!(target: "masternode-processor", "Can't sign with a public key"); return sig; } let secp = secp256k1::Secp256k1::new(); diff --git a/dash-spv-masternode-processor/src/keys/ed25519_key.rs b/dash-spv-masternode-processor/src/keys/ed25519_key.rs index 5627d886..4015e61e 100644 --- a/dash-spv-masternode-processor/src/keys/ed25519_key.rs +++ b/dash-spv-masternode-processor/src/keys/ed25519_key.rs @@ -4,6 +4,8 @@ use byte::ctx::Bytes; use ed25519_dalek::{Signature, SignatureError, Signer, SigningKey, Verifier, VerifyingKey}; use hashes::hex::{FromHex, ToHex}; use hashes::sha256; +use logging::*; +use tracing::*; use crate::crypto::{UInt160, UInt256, UInt512, byte_util::{AsBytes, Zeroable}, ECPoint}; use crate::chain::{derivation::IIndexPath, ScriptMap}; use crate::consensus::Encodable; @@ -33,14 +35,14 @@ impl IKey for ED25519Key fn sign(&self, data: &[u8]) -> Vec { if self.seckey.is_zero() { - warn!("There is no seckey for sign"); + log_warn!(target: "masternode-processor", "There is no seckey for sign"); return vec![]; } let signing_key: SigningKey = self.seckey.into(); match signing_key.try_sign(data) { Ok(signature) => signature.to_vec(), Err(err) => { - warn!("ED25519Key::sign::error {}", err); + log_warn!(target: "masternode-processor", "ED25519Key::sign::error {}", err); vec![] } } diff --git a/dash-spv-masternode-processor/src/keys/key.rs b/dash-spv-masternode-processor/src/keys/key.rs index a115f7ec..a8e100cf 100644 --- a/dash-spv-masternode-processor/src/keys/key.rs +++ b/dash-spv-masternode-processor/src/keys/key.rs @@ -5,6 +5,8 @@ use crate::crypto::{UInt256, UInt384, UInt768}; use crate::keys::{BLSKey, ECDSAKey, ED25519Key, IKey}; use crate::types::opaque_key::{AsOpaqueKey, OpaqueKey}; use crate::util::sec_vec::SecVec; +#[cfg(feature = "use_serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/dash-spv-masternode-processor/src/keys/mod.rs b/dash-spv-masternode-processor/src/keys/mod.rs index 3fb9e0ec..71d64b90 100644 --- a/dash-spv-masternode-processor/src/keys/mod.rs +++ b/dash-spv-masternode-processor/src/keys/mod.rs @@ -36,6 +36,9 @@ pub trait IKey: Send + Sync + Debug { fn verify(&mut self, message_digest: &[u8], signature: &[u8]) -> bool { panic!("Should be overriden in implementation") } + fn verify_with_uint(&mut self, message_digest: UInt256, signature: &[u8]) -> bool { + panic!("Should be overriden in implementation") + } fn secret_key(&self) -> UInt256 { panic!("Should be overriden in implementation") } diff --git a/dash-spv-masternode-processor/src/lib.rs b/dash-spv-masternode-processor/src/lib.rs index 3eb4f00c..a18c3c9a 100644 --- a/dash-spv-masternode-processor/src/lib.rs +++ b/dash-spv-masternode-processor/src/lib.rs @@ -5,8 +5,6 @@ pub mod processing; pub extern crate bitcoin_hashes as hashes; pub extern crate secp256k1; -#[macro_use] extern crate log; -extern crate simplelog; #[cfg(test)] mod lib_tests; diff --git a/dash-spv-masternode-processor/src/models/llmq_entry.rs b/dash-spv-masternode-processor/src/models/llmq_entry.rs index 5f76cf74..057a6f43 100644 --- a/dash-spv-masternode-processor/src/models/llmq_entry.rs +++ b/dash-spv-masternode-processor/src/models/llmq_entry.rs @@ -5,6 +5,8 @@ use std::convert::Into; use serde::ser::SerializeStruct; #[cfg(feature = "generate-dashj-tests")] use serde::{Serialize, Serializer}; +use logging::*; +use tracing::*; use crate::chain::common::{ChainType, IHaveChainSettings, LLMQType}; use crate::common::LLMQVersion; use crate::consensus::{encode::VarInt, Encodable, WriteExt}; @@ -313,7 +315,7 @@ impl LLMQEntry { fn validate_bitset(bitset: Vec, count: VarInt) -> bool { if bitset.len() != (count.0 as usize + 7) / 8 { - warn!( + log_debug!(target: "masternode-processor", "Error: The byte size of the bitvectors ({}) must match “(quorumSize + 7) / 8 ({})", bitset.len(), (count.0 + 7) / 8 @@ -330,7 +332,7 @@ impl LLMQEntry { None => 0, }; if last_byte & mask != 0 { - warn!("Error: No out-of-range bits should be set in byte representation of the bitvector"); + log_debug!(target: "masternode-processor", "Error: No out-of-range bits should be set in byte representation of the bitvector"); return false; } } @@ -343,26 +345,26 @@ impl LLMQEntry { let is_valid_signers = Self::validate_bitset(self.signers_bitset.clone(), self.signers_count); if !is_valid_signers { - warn!("Error: signers_bitset is invalid ({:?} {})", self.signers_bitset, self.signers_count); + log_debug!(target: "masternode-processor", "Error: signers_bitset is invalid ({:?} {})", self.signers_bitset, self.signers_count); return LLMQPayloadValidationStatus::InvalidSigners(self.signers_bitset.to_hex()); } let is_valid_members = Self::validate_bitset(self.valid_members_bitset.clone(), self.valid_members_count); if !is_valid_members { - warn!("Error: valid_members_bitset is invalid ({:?} {})", self.valid_members_bitset, self.valid_members_count); + log_debug!(target: "masternode-processor", "Error: valid_members_bitset is invalid ({:?} {})", self.valid_members_bitset, self.valid_members_count); return LLMQPayloadValidationStatus::InvalidMembers(self.valid_members_bitset.to_hex()); } let quorum_threshold = self.llmq_type.threshold() as u64; // The number of set bits in the signers and validMembers bitvectors must be at least >= quorumThreshold let signers_bitset_true_bits_count = self.signers_bitset.as_slice().true_bits_count(); if signers_bitset_true_bits_count < quorum_threshold { - warn!("Error: The number of set bits in the signers {} must be >= quorumThreshold {}", signers_bitset_true_bits_count, quorum_threshold); + log_debug!(target: "masternode-processor", "Error: The number of set bits in the signers {} must be >= quorumThreshold {}", signers_bitset_true_bits_count, quorum_threshold); return LLMQPayloadValidationStatus::SignersBelowThreshold { actual: signers_bitset_true_bits_count, threshold: quorum_threshold }; } let valid_members_bitset_true_bits_count = self.valid_members_bitset.as_slice().true_bits_count(); if valid_members_bitset_true_bits_count < quorum_threshold { - warn!("Error: The number of set bits in the valid members bitvector {} must be >= quorumThreshold {}", valid_members_bitset_true_bits_count, quorum_threshold); + log_debug!(target: "masternode-processor", "Error: The number of set bits in the valid members bitvector {} must be >= quorumThreshold {}", valid_members_bitset_true_bits_count, quorum_threshold); return LLMQPayloadValidationStatus::SignersBelowThreshold { actual: valid_members_bitset_true_bits_count, threshold: quorum_threshold }; } LLMQPayloadValidationStatus::Ok @@ -397,17 +399,17 @@ impl LLMQEntry { operator_keys, use_legacy); if !all_commitment_aggregated_signature_validated { - println!("••• INVALID AGGREGATED SIGNATURE {}: {:?} ({})", block_height, self.llmq_type, self.all_commitment_aggregated_signature); + log_debug!(target: "masternode-processor", "••• INVALID AGGREGATED SIGNATURE {}: {:?} ({})", block_height, self.llmq_type, self.all_commitment_aggregated_signature); return LLMQValidationStatus::InvalidAggregatedSignature; } // The sig must validate against the commitmentHash and all public keys determined by the signers bitvector. // This is an aggregated BLS signature verification. let quorum_signature_validated = BLSKey::verify_quorum_signature(commitment_hash.as_bytes(), self.threshold_signature.as_bytes(), self.public_key.as_bytes(), use_legacy); if !quorum_signature_validated { - println!("••• INVALID QUORUM SIGNATURE {}: {:?} ({})", block_height, self.llmq_type, self.threshold_signature); + log_debug!(target: "masternode-processor", "••• INVALID QUORUM SIGNATURE {}: {:?} ({})", block_height, self.llmq_type, self.threshold_signature); return LLMQValidationStatus::InvalidQuorumSignature; } - println!("••• quorum {:?} validated at {}", self.llmq_type, block_height); + log_debug!(target: "masternode-processor", "••• quorum {:?} validated at {}", self.llmq_type, block_height); LLMQValidationStatus::Verified } } diff --git a/dash-spv-masternode-processor/src/models/masternode_entry.rs b/dash-spv-masternode-processor/src/models/masternode_entry.rs index deebbf29..a02859e0 100644 --- a/dash-spv-masternode-processor/src/models/masternode_entry.rs +++ b/dash-spv-masternode-processor/src/models/masternode_entry.rs @@ -4,6 +4,8 @@ use std::collections::BTreeMap; use serde::{Serialize, Serializer}; #[cfg(feature = "generate-dashj-tests")] use serde::ser::SerializeStruct; +use logging::*; +use tracing::*; use crate::chain::constants::CORE_PROTO_19_2; use crate::common::{Block, MasternodeType, SocketAddress}; use crate::consensus::Encodable; @@ -309,7 +311,7 @@ impl MasternodeEntry { let distance = height - block_height; if distance < min_distance { min_distance = distance; - info!("SME operator public key for proTxHash {:?} : Using {:?} instead of {:?} for list at block height {block_height}", key, used_previous_operator_public_key_at_block_hash, self.provider_registration_transaction_hash); + log_debug!(target: "masternode-processor", "SME operator public key for proTxHash {:?} : Using {:?} instead of {:?} for list at block height {block_height}", key, used_previous_operator_public_key_at_block_hash, self.provider_registration_transaction_hash); used_previous_operator_public_key_at_block_hash = key; } } @@ -329,7 +331,7 @@ impl MasternodeEntry { let distance = height - block_height; if distance < min_distance { min_distance = distance; - info!("SME Hash for proTxHash {:?} : Using {hash} instead of {used_hash} for list at block height {block_height}", self.provider_registration_transaction_hash); + log_debug!(target: "masternode-processor", "SME Hash for proTxHash {:?} : Using {hash} instead of {used_hash} for list at block height {block_height}", self.provider_registration_transaction_hash); used_hash = hash; } } diff --git a/dash-spv-masternode-processor/src/models/masternode_list.rs b/dash-spv-masternode-processor/src/models/masternode_list.rs index 361cb240..e0f21cad 100644 --- a/dash-spv-masternode-processor/src/models/masternode_list.rs +++ b/dash-spv-masternode-processor/src/models/masternode_list.rs @@ -1,5 +1,7 @@ use std::cmp::min; use std::collections::BTreeMap; +use logging::*; +use tracing::*; use crate::chain::common::{ChainType, IHaveChainSettings, LLMQType}; use crate::common::MasternodeType; use crate::consensus::Encodable; @@ -122,7 +124,7 @@ impl MasternodeList { && ct_q_merkle_root.is_some() && ct_q_merkle_root.unwrap() == q_merkle_root.unwrap(); if !has_valid_quorum_list_root { - warn!("LLMQ Merkle root not valid for DML on block {} version {} ({:?} wanted - {:?} calculated)", + log_info!(target: "masternode-processor", "LLMQ Merkle root not valid for DML on block {} version {} ({:?} wanted - {:?} calculated)", tx.height, tx.base.version, tx.merkle_root_llmq_list, diff --git a/dash-spv-masternode-processor/src/processing/processor.rs b/dash-spv-masternode-processor/src/processing/processor.rs index d82091eb..71be6a0c 100644 --- a/dash-spv-masternode-processor/src/processing/processor.rs +++ b/dash-spv-masternode-processor/src/processing/processor.rs @@ -1,5 +1,7 @@ use std::collections::{BTreeMap, HashSet}; use std::ptr::null; +use logging::*; +use tracing::*; use crate::{common, models, types}; use crate::chain::common::{ChainType, IHaveChainSettings, LLMQType, LLMQParams}; use crate::crypto::{byte_util::{Reversable, Zeroable}, UInt256, UInt768}; @@ -135,10 +137,10 @@ impl MasternodeProcessor { if let Some(best_cl_signature) = self.find_cl_signature(work_block_hash, cached_cl_signatures) { return LLMQModifierType::CoreV20(llmq_type, work_block_height, best_cl_signature); } else { - println!("llmq_modifier_type: clsig not found for block hash: {} ({})", work_block_hash, work_block_hash.reversed()); + log_debug!(target: "masternode-processor", "llmq_modifier_type: clsig not found for block hash: {} ({})", work_block_hash, work_block_hash.reversed()); } } else { - println!("llmq_modifier_type: block not found for height: {}", work_block_height); + log_debug!(target: "masternode-processor", "llmq_modifier_type: block not found for height: {}", work_block_height); } } LLMQModifierType::PreCoreV20(llmq_type, work_block_hash) @@ -375,10 +377,10 @@ impl MasternodeProcessor { signatures.insert(llmq_hash_minus_8, signature.clone()); cache.cl_signatures.insert(llmq_hash_minus_8, signature.clone()); } else { - println!("WARN: unknown hash for {}", llmq_height - 8); + log_debug!(target: "masternode-processor", "unknown hash for {}", llmq_height - 8); } } else { - println!("WARN: unknown height for {}", quorum.llmq_hash); + log_debug!(target: "masternode-processor", "unknown height for {}", quorum.llmq_hash); } } if verification_context.should_validate_quorum_of_type(quorum.llmq_type, self.chain_type) { @@ -533,11 +535,11 @@ impl MasternodeProcessor { // println!("{:#?}", sorted_combined_mns_list.iter().map(|n| n.provider_registration_transaction_hash.reversed()).collect::>()); snapshot.apply_skip_strategy(sorted_combined_mns_list, quorum_count, quarter_size) } else { - info!("MISSING: snapshot for block at height: {}: {}", work_block_height, work_block_hash); + log_debug!(target: "masternode-processor", "MISSING: snapshot for block at height: {}: {}", work_block_height, work_block_hash); vec![] } } else { - info!("MISSING: masternode_list for block at height: {}: {}", work_block_height, work_block_hash); + log_debug!(target: "masternode-processor", "MISSING: masternode_list for block at height: {}: {}", work_block_height, work_block_hash); vec![] } } @@ -574,7 +576,7 @@ impl MasternodeProcessor { // println!("{:#?}", masternode_list); // println!("••••"); if masternode_list.masternodes.len() < quarter_size { - println!("models list at {}: {} has less masternodes ({}) then required for quarter size: ({})", work_block_height, work_block_hash, masternode_list.masternodes.len(), quarter_size); + log_debug!(target: "masternode-processor", "models list at {}: {} has less masternodes ({}) then required for quarter size: ({})", work_block_height, work_block_hash, masternode_list.masternodes.len(), quarter_size); quarter_quorum_members } else { let mut used_at_h_masternodes = Vec::::new(); @@ -647,7 +649,7 @@ impl MasternodeProcessor { } if idx == initial_loop_idx { if !updated { - println!("there are not enough MNs {}: {} then required for quarter size: ({})", work_block_height, work_block_hash, quarter_size); + log_debug!(target: "masternode-processor", "there are not enough MNs {}: {} then required for quarter size: ({})", work_block_height, work_block_hash, quarter_size); return vec![Vec::::new(); quorum_count]; } updated = false; @@ -662,7 +664,7 @@ impl MasternodeProcessor { quarter_quorum_members } } else { - println!("missing models list for height: {}: {}", work_block_height, work_block_hash); + log_debug!(target: "masternode-processor", "missing models list for height: {}: {}", work_block_height, work_block_hash); quarter_quorum_members } } diff --git a/dash-spv-masternode-processor/src/tests/bindings/keys.rs b/dash-spv-masternode-processor/src/tests/bindings/keys.rs index 2710eeb6..7c0fb1da 100644 --- a/dash-spv-masternode-processor/src/tests/bindings/keys.rs +++ b/dash-spv-masternode-processor/src/tests/bindings/keys.rs @@ -8,6 +8,8 @@ use crate::crypto::{UInt256, UInt384}; use crate::ffi::IndexPathData; use crate::keys::KeyKind; use crate::types::opaque_key::OpaqueKey; +#[cfg(feature = "use_serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[test] fn test_keys() { diff --git a/dash-spv-masternode-processor/src/tx/transaction.rs b/dash-spv-masternode-processor/src/tx/transaction.rs index 343f2c56..2815babe 100644 --- a/dash-spv-masternode-processor/src/tx/transaction.rs +++ b/dash-spv-masternode-processor/src/tx/transaction.rs @@ -1,9 +1,15 @@ +use std::io; + use byte::ctx::Endian; use byte::{BytesExt, TryRead, LE}; use hashes::hex::ToHex; -use crate::consensus::encode::VarInt; -use crate::consensus::Encodable; +use crate::blockdata::opcodes::all::OP_RETURN; +use crate::consensus::encode::{VarInt, self}; +use crate::consensus::{Encodable, Decodable}; +use crate::crypto::byte_util::{Reversable, Zeroable}; use crate::crypto::{UInt256, VarBytes}; +use crate::util::data_append::DataAppend; +use crate::util::script::{ScriptType, ScriptElement}; // block height indicating transaction is unconfirmed pub const TX_UNCONFIRMED: i32 = i32::MAX; @@ -30,6 +36,13 @@ pub enum TransactionType { CreditFunding = 255, } +impl Decodable for TransactionType { + #[inline] + fn consensus_decode(mut d: D) -> Result { + Ok(TransactionType::from(u16::consensus_decode(&mut d)?)) + } +} + impl From for TransactionType { fn from(orig: u16) -> Self { match orig { @@ -65,7 +78,7 @@ impl TransactionType { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct TransactionInput { pub input_hash: UInt256, pub index: u32, @@ -74,22 +87,45 @@ pub struct TransactionInput { pub sequence: u32, } +impl Encodable for TransactionInput { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += self.input_hash.consensus_encode(&mut writer)?; + offset += self.index.consensus_encode(&mut writer)?; + offset += match self.signature { + Some(ref signature) => signature.consensus_encode(&mut writer)?, + None => 0 + }; + offset += self.sequence.consensus_encode(&mut writer)?; + Ok(offset) + } +} + +impl Decodable for TransactionInput { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let input_hash = UInt256::consensus_decode(&mut d)?; + let index = u32::consensus_decode(&mut d)?; + let signature: Option> = Vec::consensus_decode(&mut d).ok(); + let sequence = u32::consensus_decode(&mut d)?; + Ok(Self { input_hash, index, signature, sequence, script: None }) + } +} + + impl std::fmt::Debug for TransactionInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TransactionInput") - .field("input_hash", &self.input_hash) + .field("input_hash", &self.input_hash.reversed()) .field("index", &self.index) .field( "script", - &self.script.as_ref().unwrap_or(&Vec::::new()).to_hex(), + &self.script.as_ref().map_or("None".to_string(), |s| s.to_hex()), ) .field( "signature", - &self - .signature - .as_ref() - .unwrap_or(&Vec::::new()) - .to_hex(), + &self.signature.as_ref().map_or("None".to_string(), |s| s.to_hex()), ) .field("sequence", &self.sequence) .finish() @@ -117,19 +153,41 @@ impl<'a> TryRead<'a, Endian> for TransactionInput { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct TransactionOutput { pub amount: u64, pub script: Option>, pub address: Option>, } +impl Encodable for TransactionOutput { + #[inline] + fn consensus_encode(&self, mut writer: W) -> Result { + let mut offset = 0; + offset += self.amount.consensus_encode(&mut writer)?; + offset += match self.script { + Some(ref script) => script.consensus_encode(&mut writer)?, + None => 0 + }; + Ok(offset) + } +} + +impl Decodable for TransactionOutput { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let amount = u64::consensus_decode(&mut d)?; + let script: Option> = Vec::consensus_decode(&mut d).ok(); + Ok(TransactionOutput { amount, script, address: None }) + } +} + impl std::fmt::Debug for TransactionOutput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TransactionOutput") .field("amount", &self.amount) - .field("script", &self.script.as_ref().unwrap_or(&Vec::::new()).to_hex()) - .field("address", &self.address.as_ref().unwrap_or(&Vec::::new()).to_hex()) + .field("script", &self.script.as_ref().map_or("None".to_string(), |s| s.to_hex())) + .field("address", &self.address.as_ref().map_or("None".to_string(), |s| s.to_hex())) .finish() } } @@ -151,6 +209,52 @@ impl<'a> TryRead<'a, Endian> for TransactionOutput { } } +impl TransactionOutput { + const MAX_SCRIPT_SIZE: usize = 10000; + + pub fn script_pub_key_type(&self) -> ScriptType { + if let Some(ref script) = self.script { + match script.script_elements()[..] { + // pay-to-pubkey-hash scriptPubKey + [ScriptElement::Number(0x76/*OP_DUP*/), ScriptElement::Number(0xa9/*OP_HASH160*/), ScriptElement::Data(data, len @ b'\x14'), ScriptElement::Number(0x88/*OP_EQUALVERIFY*/), ScriptElement::Number(0xac/*OP_CHECKSIG*/)] => + ScriptType::PayToPubkeyHash, + // pay-to-script-hash scriptPubKey + [ScriptElement::Number(0xa9/*OP_HASH160*/), ScriptElement::Data(data, len @ b'\x14'), ScriptElement::Number(0x87/*OP_EQUAL*/)] => + ScriptType::PayToScriptHash, + // pay-to-pubkey scriptPubKey + [ScriptElement::Data(data, len @ 33u8 | len @ 65u8), ScriptElement::Number(0xac/*OP_CHECKSIG*/)] => + ScriptType::PayToPubkey, + // unknown script type + _ => ScriptType::Unknown, + } + } else { + return ScriptType::Unknown; + } + } + + /// Returns whether the script is guaranteed to fail at execution, + /// regardless of the initial stack. This allows outputs to be pruned + /// instantly when entering the UTXO set. + pub fn is_script_unspendable(&self) -> bool { + if let Some(ref script) = self.script { + let script_elements = script.script_elements(); + + return script_elements.len() > 0 && script_elements[0] == ScriptElement::Number(OP_RETURN.into_u8()) || + script.len() > Self::MAX_SCRIPT_SIZE; + } else { + return true; + } + } + + pub fn is_op_return(&self) -> bool { + if let Some(ref script) = self.script { + return script.script_elements().len() > 0 && script.script_elements()[0] == ScriptElement::Number(OP_RETURN.into_u8()); + } else { + return false; + } + } +} + pub trait ITransaction { fn payload_data(&self) -> Vec; fn payload_data_for(&self) -> Vec; @@ -162,7 +266,7 @@ pub trait ITransaction { fn tx_type(&self) -> TransactionType; } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Transaction { pub inputs: Vec, pub outputs: Vec, @@ -285,6 +389,29 @@ impl Transaction { } } +impl Decodable for Transaction { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let version = u16::consensus_decode(&mut d)?; + let tx_type = TransactionType::consensus_decode(&mut d)?; + let inputs: Vec = Vec::consensus_decode(&mut d)?; + let outputs: Vec = Vec::consensus_decode(&mut d)?; + let lock_time = u32::consensus_decode(&mut d)?; + let mut tx = Self { + inputs, + outputs, + version, + tx_type, + lock_time, + block_height: TX_UNCONFIRMED as u32, + tx_hash: None, + payload_offset: 0, + }; + tx.tx_hash = (tx_type == TransactionType::Classic).then_some(UInt256::sha256d(tx.to_data())); + Ok(tx) + } +} + impl<'a> TryRead<'a, Endian> for Transaction { fn try_read(bytes: &'a [u8], endian: Endian) -> byte::Result<(Self, usize)> { let offset = &mut 0; diff --git a/dash-spv-masternode-processor/src/util/script.rs b/dash-spv-masternode-processor/src/util/script.rs index 9ddf830c..0b07f8b3 100644 --- a/dash-spv-masternode-processor/src/util/script.rs +++ b/dash-spv-masternode-processor/src/util/script.rs @@ -1,7 +1,7 @@ use std::fmt::{Debug, Formatter}; use hashes::hex::ToHex; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum ScriptType { PayToPubkey, PayToPubkeyHash, diff --git a/logging/Cargo.toml b/logging/Cargo.toml new file mode 100644 index 00000000..bc4bb683 --- /dev/null +++ b/logging/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "logging" +version = "0.1.0" +edition = "2021" +description = "Logging framework" +authors.workspace = true +documentation.workspace = true +homepage.workspace = true +license.workspace = true +keywords.workspace = true + +[dependencies] +tracing = { version = "0.1.40", features = ["max_level_debug"] } #"release_max_level_warn" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tracing-appender = "0.2" +dirs = "4.0" +dirs-next = "2.0.0" # Optional, for dynamic log file paths \ No newline at end of file diff --git a/logging/src/lib.rs b/logging/src/lib.rs new file mode 100644 index 00000000..888649ff --- /dev/null +++ b/logging/src/lib.rs @@ -0,0 +1,240 @@ +#[cfg(target_os = "ios")] +use tracing::{error, warn, info, debug}; + +#[cfg(target_os = "ios")] +use std::sync::Once; + +#[cfg(target_os = "ios")] +use std::path::PathBuf; + +#[cfg(target_os = "ios")] +use dirs::document_dir; + +#[cfg(target_os = "ios")] +use std::fs::{File, OpenOptions}; + +#[cfg(target_os = "ios")] +use std::io::{Write, Result}; // Importing std::io::Result only for iOS + +#[cfg(target_os = "ios")] +use std::sync::Mutex; +use dirs::download_dir; +#[cfg(target_os = "ios")] +use tracing_subscriber::fmt::writer::BoxMakeWriter; + +#[cfg(target_os = "ios")] +use tracing_subscriber::fmt::{MakeWriter, SubscriberBuilder}; + +#[cfg(target_os = "ios")] +use tracing_appender::rolling::{RollingFileAppender, Rotation}; + +#[cfg(target_os = "ios")] +static INIT: Once = Once::new(); + +// Function to initialize logging +// Custom MakeWriter struct that holds a Mutex around a File +#[cfg(target_os = "ios")] +struct MutexMakeWriter { + file: Mutex, +} + +#[cfg(target_os = "ios")] +impl MutexMakeWriter { + fn new(file: File) -> Self { + Self { + file: Mutex::new(file), + } + } +} + +// Implement MakeWriter for MutexMakeWriter to be used in tracing_subscriber +#[cfg(target_os = "ios")] +impl<'a> MakeWriter<'a> for MutexMakeWriter { + type Writer = MutexWriter<'a>; + + fn make_writer(&'a self) -> Self::Writer { + MutexWriter { + guard: self.file.lock().unwrap(), + } + } +} + +// MutexWriter struct to handle writing to the file +#[cfg(target_os = "ios")] +struct MutexWriter<'a> { + guard: std::sync::MutexGuard<'a, File>, +} + +// Implement the Write trait for MutexWriter +#[cfg(target_os = "ios")] +impl<'a> Write for MutexWriter<'a> { + fn write(&mut self, buf: &[u8]) -> Result { + self.guard.write(buf) + } + + fn flush(&mut self) -> Result<()> { + self.guard.flush() + } +} + +#[cfg(target_os = "ios")] +pub fn init_logging() { + INIT.call_once(|| { + // Get the path to the cache directory. + let cache_path = match dirs_next::cache_dir() { + Some(path) => path, + None => panic!("Failed to find the cache directory"), + }; + + // Create the log directory if it doesn't exist. + let log_dir = cache_path.join("Logs"); + if !log_dir.exists() { + std::fs::create_dir_all(&log_dir).expect("Failed to create log directory"); + } + + let file_appender = RollingFileAppender::builder() + .rotation(Rotation::DAILY) + .filename_prefix("processor") + .filename_suffix("log") + .max_log_files(5) + .build(log_dir) + .expect("Failed to create file appender"); + + // Initialize the subscriber with file-based logging + let subscriber = SubscriberBuilder::default() + .with_writer(file_appender) + .with_ansi(false) // Disable ANSI colors + .with_max_level(if cfg!(debug_assertions) { + tracing::Level::DEBUG + } else { + tracing::Level::INFO + }) + .finish(); + + tracing::subscriber::set_global_default(subscriber) + .expect("Unable to set global subscriber"); + println!("logger initialized"); + }); +} + +#[cfg(not(target_os = "ios"))] +pub fn init_logging() { + // No-op for non-iOS platforms + println!("Logging is set to println! on this platform."); +} + +// Conditional macro for logging errors with optional log prefix +#[cfg(target_os = "ios")] +#[macro_export] +macro_rules! log_error { + (target: $target:expr, $($arg:tt)*) => { + { + error!(target: $target, $($arg)*); // Logs to file via tracing + println!("ERROR [{}]: {}", $target, format!($($arg)*)); // Console output + } + }; + ($($arg:tt)*) => { + { + error!(target: "default_log_prefix", $($arg)*); // Logs to file via tracing + println!("ERROR [default_log_prefix]: {}", format!($($arg)*)); // Console output + } + }; +} + +#[cfg(not(target_os = "ios"))] +#[macro_export] // Ensures the macro is available across the crate +macro_rules! log_error { + (target: $target:expr, $($arg:tt)*) => { + println!("[{}] ERROR: {}", $target, format!($($arg)*)) + }; + ($($arg:tt)*) => { + println!("[default_log_prefix] ERROR: {}", format!($($arg)*)) + }; +} + +// Conditional macro for logging warnings with optional log prefix +#[cfg(target_os = "ios")] +#[macro_export] +macro_rules! log_warn { + (target: $target:expr, $($arg:tt)*) => { + { + warn!(target: $target, $($arg)*); // Logs to file via tracing + println!("WARN [{}]: {}", $target, format!($($arg)*)); // Console output + } + }; + ($($arg:tt)*) => { + { + warn!(target: "default_log_prefix", $($arg)*); // Logs to file via tracing + println!("WARN [default_log_prefix]: {}", format!($($arg)*)); // Console output + } + }; +} + +#[cfg(not(target_os = "ios"))] +#[macro_export] // Ensures the macro is available across the crate +macro_rules! log_warn { + (target: $target:expr, $($arg:tt)*) => { + println!("[{}] WARN: {}", $target, format!($($arg)*)) + }; + ($($arg:tt)*) => { + println!("[default_log_prefix] WARN: {}", format!($($arg)*)) + }; +} + +// Conditional macro for logging info with optional log prefix +#[cfg(target_os = "ios")] +#[macro_export] +macro_rules! log_info { + (target: $target:expr, $($arg:tt)*) => { + { + info!(target: $target, $($arg)*); // Logs to file via tracing + println!("INFO [{}]: {}", $target, format!($($arg)*)); // Console output + } + }; + ($($arg:tt)*) => { + { + info!(target: "default_log_prefix", $($arg)*); // Logs to file via tracing + println!("INFO [default_log_prefix]: {}", format!($($arg)*)); // Console output + } + }; +} + +#[cfg(not(target_os = "ios"))] +#[macro_export] // Ensures the macro is available across the crate +macro_rules! log_info { + (target: $target:expr, $($arg:tt)*) => { + println!("[{}] INFO: {}", $target, format!($($arg)*)) + }; + ($($arg:tt)*) => { + println!("[default_log_prefix] INFO: {}", format!($($arg)*)) + }; +} + +// Conditional macro for logging info with optional log prefix +#[cfg(target_os = "ios")] +#[macro_export] +macro_rules! log_debug { + (target: $target:expr, $($arg:tt)*) => { + { + debug!(target: $target, $($arg)*); // Logs to file via tracing + println!("DEBUG [{}]: {}", $target, format!($($arg)*)); // Console output + } + }; + ($($arg:tt)*) => { + { + debug!(target: "default_log_prefix", $($arg)*); // Logs to file via tracing + println!("DEBUG [default_log_prefix]: {}", format!($($arg)*)); // Console output + } + }; +} + +#[cfg(not(target_os = "ios"))] +#[macro_export] // Ensures the macro is available across the crate +macro_rules! log_debug { + (target: $target:expr, $($arg:tt)*) => { + println!("[{}] DEBUG: {}", $target, format!($($arg)*)) + }; + ($($arg:tt)*) => { + println!("[default_log_prefix] DEBUG: {}", format!($($arg)*)) + }; +} \ No newline at end of file diff --git a/reachability/Cargo.toml b/reachability/Cargo.toml index 0c26e408..050c59ef 100644 --- a/reachability/Cargo.toml +++ b/reachability/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] libc = "0.2.158" +logging = { path = "../logging" } +tracing = "0.1.40" system-configuration = "0.5.0" tokio = { version = "1.25.0", features = ["full"] } diff --git a/reachability/src/lib.rs b/reachability/src/lib.rs index 7b27586c..bc6ba441 100644 --- a/reachability/src/lib.rs +++ b/reachability/src/lib.rs @@ -4,6 +4,8 @@ use std::{ptr, thread}; use std::sync::{Arc, RwLock}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::Duration; +use logging::*; +use tracing::*; use system_configuration::core_foundation::base::{kCFAllocatorDefault, TCFType}; use system_configuration::core_foundation::runloop::{__CFRunLoop, __CFRunLoopSource, CFRunLoopAddSource, CFRunLoopContainsSource, CFRunLoopGetCurrent, CFRunLoopRef, CFRunLoopRemoveSource, CFRunLoopRun, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopStop, kCFRunLoopCommonModes}; @@ -92,17 +94,17 @@ impl MonitorContext { Self { host, receiver } } extern "C" fn schedule_callback(info: *const c_void, run_loop_ref: CFRunLoopRef, _run_loop_mode: CFStringRef) { - println!("MonitorContext::schedule_callback {:?} {:?}", info, run_loop_ref); + log_info!(target: "MonitorContext", "schedule_callback {:?} {:?}", info, run_loop_ref); // let context: &mut Self = unsafe { &mut (*(info as *mut _)) }; } extern "C" fn cancel_callback(info: *const c_void, run_loop_ref: CFRunLoopRef, _run_loop_mode: CFStringRef) { - println!("MonitorContext::cancel_callback {:?} {:?}", info, run_loop_ref); + log_info!(target: "MonitorContext", "cancel_callback {:?} {:?}", info, run_loop_ref); // let context: &mut Self = unsafe { &mut (*(info as *mut _)) }; } extern "C" fn perform_callback(info: *const c_void) { - println!("MonitorContext::perform_callback {:?}", info); + log_info!(target: "MonitorContext", "perform_callback {:?}", info); } extern "C" fn copy_ctx_description(_ctx: *const c_void) -> CFStringRef { @@ -291,7 +293,7 @@ impl ReachabilityManager { } pub fn start_monitoring(&mut self) { - println!("ReachabilityManager::start"); + log_info!(target: "ReachabilityManager", "start"); let (sender, receiver) = channel(); self.is_running = true; self.sender = Some(Arc::new(sender)); @@ -300,7 +302,7 @@ impl ReachabilityManager { } pub fn stop_monitoring(&mut self) { - println!("ReachabilityManager::stop"); + log_info!(target: "ReachabilityManager", "stop"); self.is_running = false; self.sender.as_ref().unwrap().send(Command::STOP).unwrap(); self.handle.take().unwrap().join().unwrap();