diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d5b92e7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1907 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bech32" +version = "0.10.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" + +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + +[[package]] +name = "bitcoin" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" +dependencies = [ + "bech32 0.9.1", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1 0.27.0", + "serde", +] + +[[package]] +name = "bitcoin" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" +dependencies = [ + "base64 0.21.7", + "bech32 0.10.0-beta", + "bitcoin-internals", + "bitcoin_hashes 0.13.0", + "hex-conservative", + "hex_lit", + "secp256k1 0.28.2", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", + "serde", +] + +[[package]] +name = "bitcoincore-rpc" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb70725a621848c83b3809913d5314c0d20ca84877d99dd909504b564edab00" +dependencies = [ + "bitcoincore-rpc-json", + "jsonrpc", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoincore-rpc-json" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856ffbee2e492c23bca715d72ea34aae80d58400f2bda26a82015d6bc2ec3662" +dependencies = [ + "bitcoin 0.31.2", + "serde", + "serde_json", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.5", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "electrum-client" +version = "0.18.0" +source = "git+https://github.com/cygnet3/rust-electrum-client?branch=sp_tweaks#9332bb9681e9b47d806000da48408229d712e4e6" +dependencies = [ + "bitcoin 0.30.2", + "bitcoin-private", + "byteorder", + "libc", + "log", + "rustls", + "serde", + "serde_json", + "webpki", + "webpki-roots", + "winapi", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2" +dependencies = [ + "base64 0.13.1", + "serde", + "serde_json", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sdk_common" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "anyhow", + "rand", + "serde", + "serde_json", + "sp_client 0.1.0 (git+https://github.com/Sosthene00/sp-backend?branch=sp_client)", + "tsify", + "uuid", + "wasm-bindgen", +] + +[[package]] +name = "sdk_relay" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitcoincore-rpc", + "electrum-client", + "env_logger", + "futures-util", + "hex", + "log", + "sdk_common", + "serde", + "serde_json", + "serde_with", + "sp_client 0.1.0 (git+https://github.com/Sosthene00/sp-client?branch=sp_client)", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "zeromq", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "bitcoin_hashes 0.12.0", + "secp256k1-sys 0.8.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "bitcoin_hashes 0.13.0", + "rand", + "secp256k1-sys 0.9.2", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +dependencies = [ + "base64 0.21.7", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "silentpayments" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3a0169e01dc753fe00f82d0b08a68d49e69f912f592e95ff5ade8b623728a5" +dependencies = [ + "bech32 0.9.1", + "bimap", + "bitcoin_hashes 0.13.0", + "hex", + "secp256k1 0.28.2", + "serde", + "serde_json", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "sp_client" +version = "0.1.0" +source = "git+https://github.com/Sosthene00/sp-backend?branch=sp_client#4eaa51ed306fc939f487c0e14208bf36da8c8a26" +dependencies = [ + "anyhow", + "bitcoin 0.31.2", + "serde", + "serde_json", + "silentpayments", +] + +[[package]] +name = "sp_client" +version = "0.1.0" +source = "git+https://github.com/Sosthene00/sp-client?branch=sp_client#4eaa51ed306fc939f487c0e14208bf36da8c8a26" +dependencies = [ + "anyhow", + "bitcoin 0.31.2", + "serde", + "serde_json", + "silentpayments", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tsify" +version = "0.4.5" +source = "git+https://github.com/Sosthene00/tsify?branch=next#8a5a550d2ab41612cef88a3a3de2a94639b0d3fc" +dependencies = [ + "gloo-utils", + "serde", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.4.5" +source = "git+https://github.com/Sosthene00/tsify?branch=next#8a5a550d2ab41612cef88a3a3de2a94639b0d3fc" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zeromq" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ad3ffd65d6ae06a9eece312a64c3dfa2151a70a5c99051e2080828653cbda45" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "crossbeam-queue", + "dashmap", + "futures-channel", + "futures-io", + "futures-task", + "futures-util", + "log", + "num-traits", + "once_cell", + "parking_lot", + "rand", + "regex", + "thiserror", + "tokio", + "tokio-util", + "uuid", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fccc035 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sdk_relay" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +bitcoincore-rpc = { version = "0.18" } +electrum-client = { git = "https://github.com/cygnet3/rust-electrum-client", branch = "sp_tweaks" } +env_logger = "0.9" +futures-util = { version = "0.3.28", default-features = false, features = ["sink", "std"] } +hex = "0.4.3" +log = "0.4.20" +sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "demo" } +serde = { version = "1.0.193", features = ["derive"]} +serde_json = "1.0" +serde_with = "3.6.0" +tokio = { version = "1.0.0", features = ["io-util", "rt-multi-thread", "macros", "sync"] } +tokio-stream = "0.1" +tokio-tungstenite = "0.21.0" +zeromq = "0.3.5" diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 0000000..a732ba3 --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,450 @@ +use anyhow::{Context, Error, Result}; + +use bitcoincore_rpc::json::{ + CreateRawTransactionInput, ListUnspentQueryOptions, ListUnspentResultEntry, + WalletCreateFundedPsbtOptions, +}; +use bitcoincore_rpc::{json, jsonrpc, Auth, Client, RpcApi}; +use sdk_common::sp_client::bitcoin::bip158::BlockFilter; +use sdk_common::sp_client::bitcoin::{ + block, Address, Amount, Block, BlockHash, Network, OutPoint, Psbt, ScriptBuf, Sequence, + Transaction, TxIn, TxOut, Txid, +}; +use sdk_common::sp_client::bitcoin::{consensus::deserialize, hashes::hex::FromHex}; +// use crossbeam_channel::Receiver; +// use parking_lot::Mutex; +use serde_json::{json, Value}; + +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::time::Duration; + +use crate::FAUCET_AMT; + +pub struct SensitiveAuth(pub Auth); + +impl SensitiveAuth { + pub(crate) fn get_auth(&self) -> Auth { + self.0.clone() + } +} + +enum PollResult { + Done(Result<()>), + Retry, +} + +fn rpc_poll(client: &mut Client, skip_block_download_wait: bool) -> PollResult { + match client.get_blockchain_info() { + Ok(info) => { + if skip_block_download_wait { + // bitcoind RPC is available, don't wait for block download to finish + return PollResult::Done(Ok(())); + } + let left_blocks = info.headers - info.blocks; + if info.initial_block_download || left_blocks > 0 { + log::info!( + "waiting for {} blocks to download{}", + left_blocks, + if info.initial_block_download { + " (IBD)" + } else { + "" + } + ); + return PollResult::Retry; + } + PollResult::Done(Ok(())) + } + Err(err) => { + if let Some(e) = extract_bitcoind_error(&err) { + if e.code == -28 { + log::debug!("waiting for RPC warmup: {}", e.message); + return PollResult::Retry; + } + } + PollResult::Done(Err(err).context("daemon not available")) + } + } +} + +fn read_cookie(path: &Path) -> Result<(String, String)> { + // Load username and password from bitcoind cookie file: + // * https://github.com/bitcoin/bitcoin/pull/6388/commits/71cbeaad9a929ba6a7b62d9b37a09b214ae00c1a + // * https://bitcoin.stackexchange.com/questions/46782/rpc-cookie-authentication + let mut file = File::open(path) + .with_context(|| format!("failed to open bitcoind cookie file: {}", path.display()))?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .with_context(|| format!("failed to read bitcoind cookie from {}", path.display()))?; + + let parts: Vec<&str> = contents.splitn(2, ':').collect(); + anyhow::ensure!( + parts.len() == 2, + "failed to parse bitcoind cookie - missing ':' separator" + ); + Ok((parts[0].to_owned(), parts[1].to_owned())) +} + +fn rpc_connect(rpcwallet: Option, network: Network, mut rpc_url: String) -> Result { + match rpcwallet { + Some(rpcwallet) => rpc_url.push_str(&rpcwallet), + None => (), + } + + // Allow `wait_for_new_block` to take a bit longer before timing out. + // See https://github.com/romanz/electrs/issues/495 for more details. + let builder = jsonrpc::simple_http::SimpleHttpTransport::builder() + .url(&rpc_url)? + .timeout(Duration::from_secs(30)); + let home = env::var("HOME")?; + let mut cookie_path = PathBuf::from_str(&home)?; + cookie_path.push(".bitcoin"); + cookie_path.push(network.to_core_arg()); + cookie_path.push(".cookie"); + let daemon_auth = SensitiveAuth(Auth::CookieFile(cookie_path)); + let builder = match daemon_auth.get_auth() { + Auth::None => builder, + Auth::UserPass(user, pass) => builder.auth(user, Some(pass)), + Auth::CookieFile(path) => { + let (user, pass) = read_cookie(&path)?; + builder.auth(user, Some(pass)) + } + }; + Ok(Client::from_jsonrpc(jsonrpc::Client::with_transport( + builder.build(), + ))) +} + +#[derive(Debug)] +pub struct Daemon { + // p2p: Mutex, + rpc: Client, +} + +impl Daemon { + pub(crate) fn connect( + rpcwallet: Option, + rpc_url: String, + network: Network, + // config: &Config, + // exit_flag: &ExitFlag, + // metrics: &Metrics, + ) -> Result { + let mut rpc = rpc_connect(rpcwallet, network, rpc_url)?; + + loop { + match rpc_poll(&mut rpc, false) { + PollResult::Done(result) => { + result.context("bitcoind RPC polling failed")?; + break; // on success, finish polling + } + PollResult::Retry => { + std::thread::sleep(std::time::Duration::from_secs(1)); // wait a bit before polling + } + } + } + + let network_info = rpc.get_network_info()?; + // if network_info.version < 21_00_00 { + // bail!("electrs requires bitcoind 0.21+"); + // } + if !network_info.network_active { + anyhow::bail!("electrs requires active bitcoind p2p network"); + } + let info = rpc.get_blockchain_info()?; + if info.pruned { + anyhow::bail!("electrs requires non-pruned bitcoind node"); + } + + // let p2p = tokio::sync::Mutex::new(Connection::connect( + // config.network, + // config.daemon_p2p_addr, + // metrics, + // config.signet_magic, + // )?); + Ok(Self { rpc }) + } + + pub(crate) fn estimate_fee(&self, nblocks: u16) -> Result { + let res = self + .rpc + .estimate_smart_fee(nblocks, None) + .context("failed to estimate fee")?; + if res.errors.is_some() { + Err(Error::msg(serde_json::to_string(&res.errors.unwrap())?)) + } else { + Ok(res.fee_rate.unwrap()) + } + } + + pub(crate) fn get_relay_fee(&self) -> Result { + Ok(self + .rpc + .get_network_info() + .context("failed to get relay fee")? + .relay_fee) + } + + pub(crate) fn get_current_height(&self) -> Result { + Ok(self + .rpc + .get_block_count() + .context("failed to get block count")?) + } + + pub(crate) fn get_block(&self, block_hash: BlockHash) -> Result { + Ok(self + .rpc + .get_block(&block_hash) + .context("failed to get block")?) + } + + pub(crate) fn get_filters(&self, block_height: u32) -> Result<(u32, BlockHash, BlockFilter)> { + let block_hash = self.rpc.get_block_hash(block_height.try_into()?)?; + let filter = self + .rpc + .get_block_filter(&block_hash) + .context("failed to get block filter")? + .into_filter(); + Ok((block_height, block_hash, filter)) + } + + pub(crate) fn list_unspent_from_to( + &self, + minamt: Option, + ) -> Result> { + let minimum_sum_amount = if minamt.is_none() || minamt <= FAUCET_AMT.checked_mul(2) { + FAUCET_AMT.checked_mul(2) + } else { + minamt + }; + Ok(self.rpc.list_unspent( + None, + None, + None, + Some(false), + Some(ListUnspentQueryOptions { + minimum_sum_amount, + ..Default::default() + }), + )?) + } + + pub(crate) fn create_psbt( + &self, + unspents: &[ListUnspentResultEntry], + spk: ScriptBuf, + network: Network, + ) -> Result { + let inputs: Vec = unspents + .iter() + .map(|utxo| CreateRawTransactionInput { + txid: utxo.txid, + vout: utxo.vout, + sequence: None, + }) + .collect(); + let address = Address::from_script(&spk, network)?; + let total_amt = unspents + .iter() + .fold(Amount::from_sat(0), |acc, x| acc + x.amount); + + if total_amt < FAUCET_AMT { + return Err(Error::msg("Not enought funds")); + } + + let mut outputs = HashMap::new(); + outputs.insert(address.to_string(), total_amt); + + let options = WalletCreateFundedPsbtOptions { + subtract_fee_from_outputs: vec![0], + ..Default::default() + }; + + let wallet_create_funded_result = + self.rpc + .wallet_create_funded_psbt(&inputs, &outputs, None, Some(options), None)?; + + Ok(wallet_create_funded_result.psbt.to_string()) + } + + pub(crate) fn process_psbt(&self, psbt: String) -> Result { + let processed_psbt = self.rpc.wallet_process_psbt(&psbt, None, None, None)?; + match processed_psbt.complete { + true => Ok(processed_psbt.psbt), + false => Err(Error::msg("Failed to complete the psbt")), + } + } + + pub(crate) fn finalize_psbt(&self, psbt: String) -> Result { + let final_tx = self.rpc.finalize_psbt(&psbt, Some(false))?; + + match final_tx.complete { + true => Ok(final_tx + .psbt + .expect("We shouldn't have an empty psbt for a complete return")), + false => Err(Error::msg("Failed to finalize psbt")), + } + } + + pub(crate) fn get_network(&self) -> Result { + let blockchain_info = self.rpc.get_blockchain_info()?; + + Ok(blockchain_info.chain) + } + + pub(crate) fn test_mempool_accept( + &self, + tx: &Transaction, + ) -> Result { + let res = self.rpc.test_mempool_accept(&vec![tx])?; + + Ok(res.get(0).unwrap().clone()) + } + + pub(crate) fn broadcast(&self, tx: &Transaction) -> Result { + let txid = self.rpc.send_raw_transaction(tx)?; + + Ok(txid) + } + + pub(crate) fn get_transaction_info( + &self, + txid: &Txid, + blockhash: Option, + ) -> Result { + // No need to parse the resulting JSON, just return it as-is to the client. + self.rpc + .call( + "getrawtransaction", + &[json!(txid), json!(true), json!(blockhash)], + ) + .context("failed to get transaction info") + } + + pub(crate) fn get_transaction_hex( + &self, + txid: &Txid, + blockhash: Option, + ) -> Result { + use sdk_common::sp_client::bitcoin::consensus::serde::{hex::Lower, Hex, With}; + + let tx = self.get_transaction(txid, blockhash)?; + #[derive(serde::Serialize)] + #[serde(transparent)] + struct TxAsHex(#[serde(with = "With::>")] Transaction); + serde_json::to_value(TxAsHex(tx)).map_err(Into::into) + } + + pub(crate) fn get_transaction( + &self, + txid: &Txid, + blockhash: Option, + ) -> Result { + self.rpc + .get_raw_transaction(txid, blockhash.as_ref()) + .context("failed to get transaction") + } + + pub(crate) fn get_block_txids(&self, blockhash: BlockHash) -> Result> { + Ok(self + .rpc + .get_block_info(&blockhash) + .context("failed to get block txids")? + .tx) + } + + pub(crate) fn get_mempool_txids(&self) -> Result> { + self.rpc + .get_raw_mempool() + .context("failed to get mempool txids") + } + + pub(crate) fn get_mempool_entries( + &self, + txids: &[Txid], + ) -> Result>> { + let client = self.rpc.get_jsonrpc_client(); + log::debug!("getting {} mempool entries", txids.len()); + let args: Vec<_> = txids + .iter() + .map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()]) + .collect(); + let reqs: Vec<_> = args + .iter() + .map(|a| client.build_request("getmempoolentry", a)) + .collect(); + let res = client.send_batch(&reqs).context("batch request failed")?; + log::debug!("got {} mempool entries", res.len()); + Ok(res + .into_iter() + .map(|r| { + r.context("missing response")? + .result::() + .context("invalid response") + }) + .collect()) + } + + pub(crate) fn get_mempool_transactions( + &self, + txids: &[Txid], + ) -> Result>> { + let client = self.rpc.get_jsonrpc_client(); + log::debug!("getting {} transactions", txids.len()); + let args: Vec<_> = txids + .iter() + .map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()]) + .collect(); + let reqs: Vec<_> = args + .iter() + .map(|a| client.build_request("getrawtransaction", a)) + .collect(); + let res = client.send_batch(&reqs).context("batch request failed")?; + log::debug!("got {} mempool transactions", res.len()); + Ok(res + .into_iter() + .map(|r| -> Result { + let tx_hex = r + .context("missing response")? + .result::() + .context("invalid response")?; + let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?; + deserialize(&tx_bytes).context("invalid transaction") + }) + .collect()) + } + + // pub(crate) fn get_new_headers(&self, chain: &Chain) -> Result> { + // self.p2p.lock().get_new_headers(chain) + // } + + // pub(crate) fn for_blocks(&self, blockhashes: B, func: F) -> Result<()> + // where + // B: IntoIterator, + // F: FnMut(BlockHash, SerBlock), + // { + // self.p2p.lock().for_blocks(blockhashes, func) + // } + + // pub(crate) fn new_block_notification(&self) -> Receiver<()> { + // self.p2p.lock().new_block_notification() + // } +} + +pub(crate) type RpcError = bitcoincore_rpc::jsonrpc::error::RpcError; + +pub(crate) fn extract_bitcoind_error(err: &bitcoincore_rpc::Error) -> Option<&RpcError> { + use bitcoincore_rpc::{ + jsonrpc::error::Error::Rpc as ServerError, Error::JsonRpc as JsonRpcError, + }; + match err { + JsonRpcError(ServerError(e)) => Some(e), + _ => None, + } +} diff --git a/src/electrumclient.rs b/src/electrumclient.rs new file mode 100644 index 0000000..897d533 --- /dev/null +++ b/src/electrumclient.rs @@ -0,0 +1,15 @@ +use electrum_client::{Client, ConfigBuilder}; +use log::info; + +const ELECTRS_URI: &str = "ssl://silentpayments.dev:51002"; +const VALIDATE_DOMAIN: bool = false; // self-signed cert, so we don't validate + +pub fn create_electrum_client() -> anyhow::Result { + let config = ConfigBuilder::new() + .validate_domain(VALIDATE_DOMAIN) + .build(); + let electrum_client = Client::from_config(ELECTRS_URI, config)?; + info!("ssl client {}", ELECTRS_URI); + + Ok(electrum_client) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1261e40 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,884 @@ +use std::{ + collections::HashMap, + env, + fmt::Debug, + fs, + net::SocketAddr, + path::PathBuf, + str::FromStr, + sync::{Arc, Mutex, MutexGuard, OnceLock}, + time::{Duration, Instant}, +}; + +use bitcoincore_rpc::json::{self as bitcoin_json}; +use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt}; +use log::{debug, error, info, warn}; +use sdk_common::sp_client::bitcoin::{ + absolute::LockTime, + consensus::{deserialize, serialize}, + hex::{DisplayHex, FromHex}, + key::TapTweak, + script::PushBytesBuf, + sighash::{Prevouts, SighashCache}, + taproot::Signature, + transaction::Version, + Amount, Network, OutPoint, Psbt, ScriptBuf, TapSighashType, Transaction, TxIn, TxOut, Witness, + XOnlyPublicKey, +}; +use sdk_common::sp_client::{ + bitcoin::secp256k1::{ + rand::{thread_rng, Rng}, + Keypair, Message as Secp256k1Message, PublicKey, Secp256k1, ThirtyTwoByteHash, + }, + spclient::SpWallet, +}; +use sdk_common::{ + error::AnkError, + network::{AnkFlag, AnkNetworkMsg, FaucetMessage, NewTxMessage}, + silentpayments::create_transaction_for_address_with_shared_secret, +}; + +use sdk_common::sp_client::db::{JsonFile, Storage}; +use sdk_common::sp_client::silentpayments::sending::{ + generate_recipient_pubkeys, SilentPaymentAddress, +}; +use sdk_common::sp_client::silentpayments::utils::receiving::{ + calculate_tweak_data, get_pubkey_from_input, +}; +use sdk_common::sp_client::silentpayments::utils::sending::calculate_partial_secret; +use sdk_common::sp_client::spclient::{derive_keys_from_seed, Recipient, SpClient, SpendKey}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; +use tokio::{ + net::{TcpListener, TcpStream}, + time, +}; +use tokio_stream::wrappers::UnboundedReceiverStream; +use tokio_tungstenite::tungstenite::Message; + +use anyhow::{Error, Result}; +use zeromq::{Socket, SocketRecv}; + +mod daemon; +mod electrumclient; +mod scan; + +use crate::{daemon::Daemon, scan::scan_blocks}; + +type Tx = UnboundedSender; + +type PeerMap = Arc>>; + +type SharedDaemon = Arc>; + +static MESSAGECACHE: OnceLock = OnceLock::new(); + +const MESSAGECACHEDURATION: Duration = Duration::from_secs(10); +const MESSAGECACHEINTERVAL: Duration = Duration::from_secs(2); + +#[derive(Debug)] +struct MessageCache { + store: Mutex>, +} + +impl MessageCache { + fn new() -> Self { + Self { + store: Mutex::new(HashMap::new()), + } + } + + fn insert(&self, key: String) { + let mut store = self.store.lock().unwrap(); + store.insert(key.clone(), Instant::now()); + } + + fn contains(&self, key: &str) -> bool { + let store = self.store.lock().unwrap(); + store.contains_key(key) + } +} + +async fn clean_up() { + let cache = MESSAGECACHE.get().unwrap(); + + let mut interval = time::interval(MESSAGECACHEINTERVAL); + + loop { + interval.tick().await; + + let mut store = cache.store.lock().unwrap(); + + let now = Instant::now(); + let to_rm: Vec = store + .iter() + .filter_map(|(entry, entrytime)| { + if let Some(duration) = now.checked_duration_since(*entrytime) { + if duration > MESSAGECACHEDURATION { + Some(entry.clone()) + } else { + None + } + } else { + None + } + }) + .collect(); + + for key in to_rm { + store.remove(&key); + } + } +} + +const FAUCET_AMT: Amount = Amount::from_sat(100_000); + +pub(crate) trait MutexExt { + fn lock_anyhow(&self) -> Result, Error>; +} + +impl MutexExt for Mutex { + fn lock_anyhow(&self) -> Result, Error> { + self.lock() + .map_err(|e| Error::msg(format!("Failed to lock: {}", e))) + } +} + +#[derive(Debug)] +struct SilentPaymentWallet { + sp_wallet: Mutex, + storage: Mutex, +} + +impl SilentPaymentWallet { + pub fn get_wallet(&self) -> Result> { + self.sp_wallet.lock_anyhow() + } + + pub fn save(&self) -> Result<()> { + self.storage.lock_anyhow()?.save(&self.sp_wallet) + } +} + +enum BroadcastType { + Sender(SocketAddr), + #[allow(dead_code)] + ExcludeSender(SocketAddr), + #[allow(dead_code)] + ToAll, +} + +fn broadcast_message( + peers: PeerMap, + flag: AnkFlag, + payload: String, + broadcast: BroadcastType, +) -> Result<()> { + let ank_msg = AnkNetworkMsg { + flag, + content: payload, + }; + let msg = Message::Text(serde_json::to_string(&ank_msg)?); + log::debug!("Broadcasting message: {}", msg); + match broadcast { + BroadcastType::Sender(addr) => { + peers + .lock() + .map_err(|e| Error::msg(format!("Failed to lock peers: {}", e.to_string())))? + .iter() + .find(|(peer_addr, _)| peer_addr == &&addr) + .ok_or(Error::msg("Failed to find the sender in the peer_map"))? + .1 + .send(msg)?; + } + BroadcastType::ExcludeSender(addr) => { + peers + .lock() + .map_err(|e| Error::msg(format!("Failed to lock peers: {}", e.to_string())))? + .iter() + .filter(|(peer_addr, _)| peer_addr != &&addr) + .for_each(|(_, peer_tx)| { + let _ = peer_tx.send(msg.clone()); + }); + } + BroadcastType::ToAll => { + peers + .lock() + .map_err(|e| Error::msg(format!("Failed to lock peers: {}", e.to_string())))? + .iter() + .for_each(|(_, peer_tx)| { + let _ = peer_tx.send(msg.clone()); + }); + } + } + Ok(()) +} + +fn spend_from_core( + dest: XOnlyPublicKey, + daemon: Arc>, +) -> Result<(Transaction, Amount)> { + let core = daemon + .lock() + .map_err(|e| Error::msg(format!("Failed to lock daemon: {}", e.to_string())))?; + let unspent_list: Vec = + core.list_unspent_from_to(None)?; + + if !unspent_list.is_empty() { + let network = core.get_network()?; + + let spk = ScriptBuf::new_p2tr_tweaked(dest.dangerous_assume_tweaked()); + + let new_psbt = core.create_psbt(&unspent_list, spk, network)?; + let processed_psbt = core.process_psbt(new_psbt)?; + let finalize_psbt_result = core.finalize_psbt(processed_psbt)?; + let final_psbt = Psbt::from_str(&finalize_psbt_result)?; + let total_fee = final_psbt.fee()?; + let final_tx = final_psbt.extract_tx()?; + let fee_rate = total_fee + .checked_div(final_tx.weight().to_vbytes_ceil()) + .unwrap(); + + Ok((final_tx, fee_rate)) + } else { + // we don't have enough available coins to pay for this faucet request + Err(Error::msg("No spendable outputs")) + } +} + +fn faucet_send( + sp_address: SilentPaymentAddress, + commitment: &str, + sp_wallet: Arc, + shared_daemon: SharedDaemon, +) -> Result { + let mut first_tx: Option = None; + let final_tx: Transaction; + + // do we have a sp output available ? + let available_outpoints = sp_wallet.get_wallet()?.get_outputs().to_spendable_list(); + + let available_amt = available_outpoints + .iter() + .fold(Amount::from_sat(0), |acc, (_, x)| acc + x.amount); + + // If we don't have at least 4 times the amount we need to send, we take some reserves out + if available_amt > FAUCET_AMT.checked_mul(4).unwrap() { + let mut total_amt = Amount::from_sat(0); + let mut inputs = HashMap::new(); + for (outpoint, output) in available_outpoints { + total_amt += output.amount; + inputs.insert(outpoint, output); + if total_amt >= FAUCET_AMT { + break; + } + } + + let recipient = Recipient { + address: sp_address.into(), + amount: FAUCET_AMT, + nb_outputs: 1, + }; + + let fee_estimate = shared_daemon + .lock_anyhow()? + .estimate_fee(6) + .unwrap_or(Amount::from_sat(1000)) + .checked_div(1000) + .unwrap(); + + log::debug!("fee estimate for 6 blocks: {}", fee_estimate); + + let wallet = sp_wallet.get_wallet()?; + + let signed_psbt = create_transaction_for_address_with_shared_secret( + recipient, + &wallet, + Some(commitment), + fee_estimate, + )?; + + let psbt = Psbt::from_str(&signed_psbt)?; + + final_tx = psbt.extract_tx()?; + } else { + // let's try to spend directly from the mining address + let secp = Secp256k1::signing_only(); + let keypair = Keypair::new(&secp, &mut thread_rng()); + + // we first spend from core to the pubkey we just created + let (core_tx, fee_rate) = + spend_from_core(keypair.x_only_public_key().0, shared_daemon.clone())?; + + // check that the first output of the transaction pays to the key we just created + debug_assert!( + core_tx.output[0].script_pubkey + == ScriptBuf::new_p2tr_tweaked( + keypair.x_only_public_key().0.dangerous_assume_tweaked() + ) + ); + + // This is ugly and can be streamlined + // create a new transaction that spends the newly created UTXO to the sp_address + let mut faucet_tx = Transaction { + input: vec![TxIn { + previous_output: OutPoint::new(core_tx.txid(), 0), + ..Default::default() + }], + output: vec![], + version: Version::TWO, + lock_time: LockTime::ZERO, + }; + + // now do the silent payment operations with the final recipient address + let partial_secret = calculate_partial_secret( + &[(keypair.secret_key(), true)], + &[(core_tx.txid().to_string(), 0)], + )?; + + let ext_output_key: XOnlyPublicKey = + generate_recipient_pubkeys(vec![sp_address.into()], partial_secret)? + .into_values() + .flatten() + .collect::>() + .get(0) + .expect("Failed to generate keys") + .to_owned(); + let change_sp_address = sp_wallet.get_wallet()?.get_client().get_receiving_address(); + let change_output_key: XOnlyPublicKey = + generate_recipient_pubkeys(vec![change_sp_address], partial_secret)? + .into_values() + .flatten() + .collect::>() + .get(0) + .expect("Failed to generate keys") + .to_owned(); + + let ext_spk = ScriptBuf::new_p2tr_tweaked(ext_output_key.dangerous_assume_tweaked()); + let change_spk = ScriptBuf::new_p2tr_tweaked(change_output_key.dangerous_assume_tweaked()); + + let mut op_return = PushBytesBuf::new(); + op_return.extend_from_slice(&Vec::from_hex(commitment)?)?; + let data_spk = ScriptBuf::new_op_return(op_return); + + // Take some margin to pay for the fees + if core_tx.output[0].value < FAUCET_AMT * 4 { + return Err(Error::msg("Not enough funds")); + } + + let change_amt = core_tx.output[0].value.checked_sub(FAUCET_AMT).unwrap(); + + faucet_tx.output.push(TxOut { + value: FAUCET_AMT, + script_pubkey: ext_spk, + }); + faucet_tx.output.push(TxOut { + value: change_amt, + script_pubkey: change_spk, + }); + faucet_tx.output.push(TxOut { + value: Amount::from_sat(0), + script_pubkey: data_spk, + }); + + // dummy signature only used for fee estimation + faucet_tx.input[0].witness.push([1; 64].to_vec()); + + let abs_fee = fee_rate + .checked_mul(faucet_tx.weight().to_vbytes_ceil()) + .ok_or_else(|| Error::msg("Fee rate multiplication overflowed"))?; + + // reset the witness to empty + faucet_tx.input[0].witness = Witness::new(); + + faucet_tx.output[1].value -= abs_fee; + + let first_tx_outputs = vec![core_tx.output[0].clone()]; + let prevouts = Prevouts::All(&first_tx_outputs); + + let hash_ty = TapSighashType::Default; + + let mut cache = SighashCache::new(&faucet_tx); + + let sighash = cache.taproot_key_spend_signature_hash(0, &prevouts, hash_ty)?; + + let msg = Secp256k1Message::from_digest(sighash.into_32()); + + let sig = secp.sign_schnorr_with_rng(&msg, &keypair, &mut thread_rng()); + let final_sig = Signature { sig, hash_ty }; + + faucet_tx.input[0].witness.push(final_sig.to_vec()); + + first_tx = Some(core_tx); + + final_tx = faucet_tx; + } + + if let Ok(daemon) = shared_daemon.lock() { + // broadcast one or two transactions + if first_tx.is_some() { + daemon.broadcast(&first_tx.unwrap())?; + } + let txid = daemon.broadcast(&final_tx)?; + debug!("Sent tx {}", txid); + } else { + return Err(Error::msg("Failed to lock daemon")); + } + + Ok(final_tx) +} + +fn handle_faucet_request( + msg: &FaucetMessage, + sp_wallet: Arc, + shared_daemon: SharedDaemon, +) -> Result { + let sp_address = SilentPaymentAddress::try_from(msg.sp_address.as_str())?; + debug!("Sending bootstrap coins to {}", sp_address); + // send bootstrap coins to this sp_address + let tx = faucet_send( + sp_address, + &msg.commitment, + sp_wallet.clone(), + shared_daemon.clone(), + )?; + + // get the tweak + let partial_tweak = compute_partial_tweak_to_transaction(&tx, shared_daemon.clone())?; + + // get current blockheight + let blkheight: u32 = shared_daemon + .lock_anyhow()? + .get_current_height()? + .try_into()?; + + // update our sp_client with the change output(s) + sp_wallet + .get_wallet()? + .update_wallet_with_transaction(&tx, blkheight, partial_tweak)?; + + debug!("{:?}", sp_wallet); + + debug!("updated the wallet"); + // save to disk + sp_wallet.save()?; + + debug!("saved the wallet"); + Ok(NewTxMessage::new( + serialize(&tx).to_lower_hex_string(), + Some(partial_tweak.to_string()), + )) +} + +fn handle_new_tx_request(new_tx_msg: &mut NewTxMessage, shared_daemon: SharedDaemon) -> Result<()> { + let tx = deserialize::(&Vec::from_hex(&new_tx_msg.transaction)?)?; + let mempool_accept = shared_daemon.lock_anyhow()?.test_mempool_accept(&tx)?; + if !mempool_accept.allowed { + return Err(AnkError::NewTxError(mempool_accept.reject_reason.unwrap()))?; + } + if new_tx_msg.tweak_data.is_none() { + // we add the tweak_data + let partial_tweak = compute_partial_tweak_to_transaction(&tx, shared_daemon.clone())?; + new_tx_msg.tweak_data = Some(partial_tweak.to_string()); + } + + // we try to broadcast it + shared_daemon.lock_anyhow()?.broadcast(&tx)?; + + Ok(()) +} + +async fn handle_connection( + peers: PeerMap, + shared_daemon: SharedDaemon, + sp_wallet: Arc, + raw_stream: TcpStream, + addr: SocketAddr, +) { + debug!("Incoming TCP connection from: {}", addr); + + let ws_stream = tokio_tungstenite::accept_async(raw_stream) + .await + .expect("Error during the websocket handshake occurred"); + debug!("WebSocket connection established"); + + // Insert the write part of this peer to the peer map. + let (tx, rx) = unbounded_channel(); + match peers.lock_anyhow() { + Ok(mut peer_map) => peer_map.insert(addr, tx), + Err(e) => { + log::error!("{}", e); + panic!(); + } + }; + + let (outgoing, incoming) = ws_stream.split(); + + let broadcast_incoming = incoming.try_for_each(|msg| { + let peers = peers.clone(); + if let Ok(raw_msg) = msg.to_text() { + debug!("Received msg: {}", raw_msg); + let cache = MESSAGECACHE.get().expect("Cache should be initialized"); + if cache.contains(raw_msg) { + debug!("Message already processed, dropping"); + return future::ok(()); + } else { + cache.insert(raw_msg.to_owned()); + } + let parsed = serde_json::from_str::(raw_msg); + match parsed { + Ok(ank_msg) => match ank_msg.flag { + AnkFlag::Faucet => { + debug!("Received a faucet message"); + if let Ok(mut content) = + serde_json::from_str::(&ank_msg.content) + { + match handle_faucet_request( + &content, + sp_wallet.clone(), + shared_daemon.clone(), + ) { + Ok(new_tx_msg) => { + debug!( + "Obtained new_tx_msg: {}", + serde_json::to_string(&new_tx_msg).unwrap() + ); + } + Err(e) => { + log::error!("Failed to send faucet tx: {}", e); + content.error = Some(e.into()); + let payload = serde_json::to_string(&content) + .expect("Message type shouldn't fail"); + if let Err(e) = broadcast_message( + peers.clone(), + AnkFlag::Faucet, + payload, + BroadcastType::Sender(addr), + ) { + log::error!("Failed to broadcast message: {}", e); + } + } + } + } else { + log::error!("Invalid content for faucet message"); + } + } + AnkFlag::NewTx => { + debug!("Received a new tx message"); + if let Ok(mut new_tx_msg) = + serde_json::from_str::(&ank_msg.content) + { + match handle_new_tx_request(&mut new_tx_msg, shared_daemon.clone()) { + Ok(new_tx_msg) => { + // Repeat the msg to all except sender + if let Err(e) = broadcast_message( + peers.clone(), + AnkFlag::NewTx, + serde_json::to_string(&new_tx_msg) + .expect("This should not fail"), + BroadcastType::ExcludeSender(addr), + ) { + log::error!("Failed to send message with error: {}", e); + } + } + Err(e) => { + log::error!("handle_new_tx_request returned error: {}", e); + new_tx_msg.error = Some(e.into()); + if let Err(e) = broadcast_message( + peers.clone(), + AnkFlag::NewTx, + serde_json::to_string(&new_tx_msg) + .expect("This shouldn't fail"), + BroadcastType::Sender(addr), + ) { + log::error!("Failed to broadcast message: {}", e); + } + } + } + } else { + log::error!("Invalid content for new_tx message"); + } + } + AnkFlag::Cipher => { + // For now we just send it to everyone + debug!("Received a cipher message"); + if let Err(e) = broadcast_message( + peers.clone(), + AnkFlag::Cipher, + ank_msg.content, + BroadcastType::ExcludeSender(addr), + ) { + log::error!("Failed to send message with error: {}", e); + } + } + AnkFlag::Unknown => { + debug!("Received an unknown message"); + if let Err(e) = broadcast_message( + peers.clone(), + AnkFlag::Unknown, + ank_msg.content, + BroadcastType::ExcludeSender(addr), + ) { + log::error!("Failed to send message with error: {}", e); + } + } + }, + Err(_) => log::error!("Failed to parse network message"), + } + } else { + // we don't care + log::debug!("Received non-text message {} from peer {}", msg, addr); + } + future::ok(()) + }); + + let receive_from_others = UnboundedReceiverStream::new(rx) + .map(Ok) + .forward(outgoing) + .map(|result| { + if let Err(e) = result { + debug!("Error sending message: {}", e); + } + }); + + pin_mut!(broadcast_incoming, receive_from_others); + future::select(broadcast_incoming, receive_from_others).await; + + debug!("{} disconnected", &addr); + peers.lock().unwrap().remove(&addr); +} + +fn compute_partial_tweak_to_transaction( + tx: &Transaction, + daemon: Arc>, +) -> Result { + let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len()); + let mut pubkeys: Vec = Vec::with_capacity(tx.input.len()); + for input in tx.input.iter() { + outpoints.push(( + input.previous_output.txid.to_string(), + input.previous_output.vout, + )); + let prev_tx = daemon + .lock_anyhow()? + .get_transaction(&input.previous_output.txid, None) + .map_err(|e| Error::msg(format!("Failed to find previous transaction: {}", e)))?; + + if let Some(output) = prev_tx.output.get(input.previous_output.vout as usize) { + match get_pubkey_from_input( + &input.script_sig.to_bytes(), + &input.witness.to_vec(), + &output.script_pubkey.to_bytes(), + ) { + Ok(Some(pubkey)) => pubkeys.push(pubkey), + Ok(None) => continue, + Err(e) => { + return Err(Error::msg(format!( + "Can't extract pubkey from input: {}", + e + ))) + } + } + } else { + return Err(Error::msg("Transaction with a non-existing input")); + } + } + + let input_pub_keys: Vec<&PublicKey> = pubkeys.iter().collect(); + let partial_tweak = calculate_tweak_data(&input_pub_keys, &outpoints)?; + Ok(partial_tweak) +} + +fn create_new_tx_message(transaction: Vec, daemon: Arc>) -> Result { + // debug!("Creating tx message"); + let tx: Transaction = deserialize(&transaction)?; + + if tx.is_coinbase() { + return Err(Error::msg("Can't process coinbase transaction")); + } + + let partial_tweak = compute_partial_tweak_to_transaction(&tx, daemon)?; + Ok(NewTxMessage::new( + transaction.to_lower_hex_string(), + Some(partial_tweak.to_string()), + )) +} + +async fn handle_zmq(peers: PeerMap, shared_daemon: SharedDaemon) { + debug!("Starting listening on Core"); + let mut socket = zeromq::SubSocket::new(); + socket.connect("tcp://127.0.0.1:29100").await.unwrap(); + socket.subscribe("rawtx").await.unwrap(); + // socket.subscribe("hashblock"); + loop { + let core_msg = match socket.recv().await { + Ok(m) => m, + Err(e) => { + error!("Zmq error: {}", e); + continue; + } + }; + debug!("Received a message"); + + let payload: String = if let (Some(topic), Some(data)) = (core_msg.get(0), core_msg.get(1)) + { + // debug!("topic: {}", std::str::from_utf8(&topic).unwrap()); + match std::str::from_utf8(&topic) { + Ok("rawtx") => match create_new_tx_message(data.to_vec(), shared_daemon.clone()) { + Ok(m) => { + debug!("Created message"); + serde_json::to_string(&m).expect("This shouldn't fail") + } + Err(e) => { + error!("{}", e); + continue; + } + }, + Ok("hashblock") => todo!(), + _ => { + error!("Unexpected message in zmq"); + continue; + } + } + } else { + error!("Empty message"); + continue; + }; + + debug!("Broadcasting message {}", payload); + if let Err(e) = + broadcast_message(peers.clone(), AnkFlag::NewTx, payload, BroadcastType::ToAll) + { + log::error!("{}", e.to_string()); + } + } +} + +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<()> { + env_logger::init(); + + // This is rudimentary, if you change the network don't forget to change rpc_url either, we won't do that for you + let rpc_url = env::args() + .nth(1) + .unwrap_or_else(|| "127.0.0.1:38332".to_owned()); + let listening_addr = env::args() + .nth(2) + .unwrap_or_else(|| "127.0.0.1:8090".to_string()); + let wallet_name = env::args().nth(3).unwrap_or_else(|| "default".to_owned()); + let network_arg: String = env::args().nth(4).unwrap_or_else(|| "signet".to_owned()); + let core_wallet: Option = env::args().nth(5); + + let network = Network::from_core_arg(&network_arg)?; + + if network == Network::Bitcoin { + warn!("Running on mainnet, you're on your own"); + } + + MESSAGECACHE + .set(MessageCache::new()) + .expect("Message Cache initialization failed"); + + tokio::spawn(clean_up()); + + let peers = PeerMap::new(Mutex::new(HashMap::new())); + + // Connect the rpc daemon + let shared_daemon = Arc::new(Mutex::new(Daemon::connect(core_wallet, rpc_url, network)?)); + + let current_tip: u32 = shared_daemon + .lock_anyhow()? + .get_current_height()? + .try_into()?; + + let mut config_dir = PathBuf::from_str(&env::var("HOME")?)?; + config_dir.push(".4nk"); + let sp_wallet_file = JsonFile::new(&config_dir, &PathBuf::from_str(&wallet_name)?); + fs::create_dir_all(config_dir)?; + + // load an existing sp_wallet, or create a new one + let is_testnet = if network == Network::Bitcoin { + false + } else { + true + }; + let sp_wallet = match >::load(&sp_wallet_file) { + Err(_) => { + let mut seed = [0u8; 64]; + thread_rng().fill(&mut seed); + let (scan_sk, spend_sk) = derive_keys_from_seed(&seed, is_testnet) + .expect("Couldn't generate a new sp_wallet"); + let new_client = SpClient::new( + wallet_name, + scan_sk, + SpendKey::Secret(spend_sk), + None, + is_testnet, + ) + .expect("Failed to create a new SpClient"); + + let mut wallet = SpWallet::new(new_client, None)?; + + // set birthday to avoid unnecessary scanning + let outputs = wallet.get_mut_outputs(); + outputs.set_birthday(current_tip); + outputs.update_last_scan(current_tip); + + wallet + } + Ok(wallet) => wallet, + }; + + log::info!( + "Using wallet {} with address {}", + sp_wallet.get_client().label, + sp_wallet.get_client().get_receiving_address() + ); + + log::info!( + "Found {} outputs for a total balance of {}", + sp_wallet.get_outputs().to_spendable_list().len(), + sp_wallet.get_outputs().get_balance() + ); + + let last_scan = sp_wallet.get_outputs().get_last_scan(); + + let shared_sp_wallet = Mutex::new(sp_wallet); + let shared_wallet_storage = Mutex::new(sp_wallet_file); + + let sp_wallet = Arc::new(SilentPaymentWallet { + sp_wallet: shared_sp_wallet, + storage: shared_wallet_storage, + }); + + sp_wallet.save()?; + + if last_scan < current_tip { + log::info!("Scanning for our outputs"); + scan_blocks( + shared_daemon.clone(), + sp_wallet.clone(), + current_tip - last_scan, + )?; + } + + // Subscribe to Bitcoin Core + tokio::spawn(handle_zmq(peers.clone(), shared_daemon.clone())); + + // Create the event loop and TCP listener we'll accept connections on. + let try_socket = TcpListener::bind(&listening_addr).await; + let listener = try_socket.expect("Failed to bind"); + debug!("Listening on: {}", listening_addr); + + // Let's spawn the handling of each connection in a separate task. + while let Ok((stream, addr)) = listener.accept().await { + tokio::spawn(handle_connection( + peers.clone(), + shared_daemon.clone(), + sp_wallet.clone(), + stream, + addr, + )); + } + + Ok(()) +} diff --git a/src/scan.rs b/src/scan.rs new file mode 100644 index 0000000..aac69ca --- /dev/null +++ b/src/scan.rs @@ -0,0 +1,286 @@ +use std::collections::HashMap; +use std::str::FromStr; +use std::sync::Arc; + +use anyhow::{Error, Result}; +use electrum_client::ElectrumApi; +use hex::FromHex; +use sdk_common::sp_client::bitcoin::bip158::BlockFilter; +use sdk_common::sp_client::bitcoin::hex::DisplayHex; +use sdk_common::sp_client::bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey}; +use sdk_common::sp_client::bitcoin::{BlockHash, OutPoint, Transaction, TxOut, XOnlyPublicKey}; +use sdk_common::sp_client::silentpayments::receiving::Receiver; +use sdk_common::sp_client::spclient::{OutputSpendStatus, OwnedOutput}; +use tokio::time::Instant; + +use crate::{electrumclient, MutexExt, SharedDaemon, SilentPaymentWallet}; + +fn get_script_to_secret_map( + sp_receiver: &Receiver, + tweak_data_vec: Vec, + scan_key_scalar: Scalar, + secp: &Secp256k1, +) -> Result> { + let mut res = HashMap::new(); + let shared_secrets: Result> = tweak_data_vec + .into_iter() + .map(|s| { + let x = PublicKey::from_str(&s).map_err(Error::new)?; + x.mul_tweak(secp, &scan_key_scalar).map_err(Error::new) + }) + .collect(); + let shared_secrets = shared_secrets?; + + for shared_secret in shared_secrets { + let spks = sp_receiver.get_spks_from_shared_secret(&shared_secret)?; + + for spk in spks.into_values() { + res.insert(spk, shared_secret); + } + } + Ok(res) +} + +fn check_block( + blkfilter: BlockFilter, + blkhash: BlockHash, + candidate_spks: Vec<&[u8; 34]>, + owned_spks: Vec>, +) -> Result { + // check output scripts + let mut scripts_to_match: Vec<_> = candidate_spks.into_iter().map(|spk| spk.as_ref()).collect(); + + // check input scripts + scripts_to_match.extend(owned_spks.iter().map(|spk| spk.as_slice())); + + // note: match will always return true for an empty query! + if !scripts_to_match.is_empty() { + Ok(blkfilter.match_any(&blkhash, &mut scripts_to_match.into_iter())?) + } else { + Ok(false) + } +} + +fn scan_block_outputs( + sp_receiver: &Receiver, + txdata: &Vec, + blkheight: u64, + spk2secret: HashMap<[u8; 34], PublicKey>, +) -> Result> { + let mut res: HashMap = HashMap::new(); + + // loop over outputs + for tx in txdata { + let txid = tx.txid(); + + // collect all taproot outputs from transaction + let p2tr_outs: Vec<(usize, &TxOut)> = tx + .output + .iter() + .enumerate() + .filter(|(_, o)| o.script_pubkey.is_p2tr()) + .collect(); + + if p2tr_outs.is_empty() { + continue; + }; // no taproot output + + let mut secret: Option = None; + // Does this transaction contains one of the outputs we already found? + for spk in p2tr_outs.iter().map(|(_, o)| &o.script_pubkey) { + if let Some(s) = spk2secret.get(spk.as_bytes()) { + // we might have at least one output in this transaction + secret = Some(*s); + break; + } + } + + if secret.is_none() { + continue; + }; // we don't have a secret that matches any of the keys + + // Now we can just run sp_receiver on all the p2tr outputs + let xonlykeys: Result> = p2tr_outs + .iter() + .map(|(_, o)| { + XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..]).map_err(Error::new) + }) + .collect(); + + let ours = sp_receiver.scan_transaction(&secret.unwrap(), xonlykeys?)?; + for (label, map) in ours { + res.extend(p2tr_outs.iter().filter_map(|(i, o)| { + match XOnlyPublicKey::from_slice(&o.script_pubkey.as_bytes()[2..]) { + Ok(key) => { + if let Some(scalar) = map.get(&key) { + match SecretKey::from_slice(&scalar.to_be_bytes()) { + Ok(tweak) => { + let outpoint = OutPoint { + txid, + vout: *i as u32, + }; + let label_str: Option; + if let Some(l) = &label { + label_str = + Some(l.as_inner().to_be_bytes().to_lower_hex_string()); + } else { + label_str = None; + } + return Some(( + outpoint, + OwnedOutput { + blockheight: blkheight as u32, + tweak: hex::encode(tweak.secret_bytes()), + amount: o.value, + script: hex::encode(o.script_pubkey.as_bytes()), + label: label_str, + spend_status: OutputSpendStatus::Unspent, + }, + )); + } + Err(_) => { + return None; + } + } + } + None + } + Err(_) => None, + } + })); + } + } + Ok(res) +} + +fn scan_block_inputs( + our_outputs: HashMap, + txdata: Vec, +) -> Result> { + let mut found = vec![]; + + for tx in txdata { + for input in tx.input { + let prevout = input.previous_output; + + if our_outputs.contains_key(&prevout) { + found.push(prevout); + } + } + } + Ok(found) +} + +pub fn scan_blocks( + shared_daemon: SharedDaemon, + sp_wallet: Arc, + mut n_blocks_to_scan: u32, +) -> anyhow::Result<()> { + log::info!("Starting a rescan"); + let electrum_client = electrumclient::create_electrum_client()?; + + let core = shared_daemon.lock_anyhow()?; + + let secp = Secp256k1::new(); + let scan_height = sp_wallet.get_wallet()?.get_outputs().get_last_scan(); + let tip_height: u32 = core.get_current_height()?.try_into()?; + + // 0 means scan to tip + if n_blocks_to_scan == 0 { + n_blocks_to_scan = tip_height - scan_height; + } + + let start = scan_height + 1; + let end = if scan_height + n_blocks_to_scan <= tip_height { + scan_height + n_blocks_to_scan + } else { + tip_height + }; + + if start > end { + return Ok(()); + } + + log::info!("start: {} end: {}", start, end); + let mut filters: Vec<(u32, BlockHash, BlockFilter)> = vec![]; + for blkheight in start..=end { + filters.push(core.get_filters(blkheight)?); + } + + let mut tweak_data_map = electrum_client.sp_tweaks(start as usize)?; + + let scan_sk = sp_wallet.get_wallet()?.get_client().get_scan_key(); + + let sp_receiver = sp_wallet.get_wallet()?.get_client().sp_receiver.clone(); + let start_time = Instant::now(); + + for (blkheight, blkhash, blkfilter) in filters { + let spk2secret = match tweak_data_map.remove(&(&blkheight)) { + Some(tweak_data_vec) => { + get_script_to_secret_map(&sp_receiver, tweak_data_vec, scan_sk.into(), &secp)? + } + None => HashMap::new(), + }; + + // check if new possible outputs are payments to us + let candidate_spks: Vec<&[u8; 34]> = spk2secret.keys().collect(); + + // check if owned inputs are spent + let our_outputs: HashMap = + sp_wallet.get_wallet()?.get_outputs().to_outpoints_list(); + + let owned_spks: Result>> = our_outputs + .iter() + .map(|(_, output)| { + let script = Vec::from_hex(&output.script).map_err(|e| Error::new(e)); + script + }) + .collect(); + + let matched = check_block(blkfilter, blkhash, candidate_spks, owned_spks?)?; + + if matched { + let blk = core.get_block(blkhash)?; + + // scan block for new outputs, and add them to our list + let utxo_created_in_block = + scan_block_outputs(&sp_receiver, &blk.txdata, blkheight.into(), spk2secret)?; + if !utxo_created_in_block.is_empty() { + sp_wallet + .get_wallet()? + .get_mut_outputs() + .extend_from(utxo_created_in_block); + } + + // update the list of outputs just in case + // utxos may be created and destroyed in the same block + let updated_outputs: HashMap = + sp_wallet.get_wallet()?.get_outputs().to_outpoints_list(); + + // search inputs and mark as mined + let utxo_destroyed_in_block = scan_block_inputs(updated_outputs, blk.txdata)?; + if !utxo_destroyed_in_block.is_empty() { + let mut wallet = sp_wallet.get_wallet()?; + let outputs = wallet.get_mut_outputs(); + for outpoint in utxo_destroyed_in_block { + outputs.mark_mined(outpoint, blkhash)?; + } + } + } + } + + // time elapsed for the scan + log::info!( + "Scan complete in {} seconds", + start_time.elapsed().as_secs() + ); + + // update last_scan height + sp_wallet + .get_wallet()? + .get_mut_outputs() + .update_last_scan(end); + sp_wallet.save()?; + + Ok(()) +}