diff --git a/.conf.model b/.conf.model index 6f7b470..3c8e8fe 100644 --- a/.conf.model +++ b/.conf.model @@ -3,4 +3,5 @@ ws_url="" wallet_name="default" network="signet" electrum_url="tcp://localhost:60601" +blindbit_url="tcp://localhost:8000" zmq_url="" diff --git a/Cargo.lock b/Cargo.lock index 5f619ab..ea88581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,6 +88,19 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "async-compression" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -186,20 +199,6 @@ 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" @@ -209,10 +208,10 @@ dependencies = [ "base64 0.21.7", "bech32 0.10.0-beta", "bitcoin-internals", - "bitcoin_hashes 0.13.0", + "bitcoin_hashes", "hex-conservative", "hex_lit", - "secp256k1 0.28.2", + "secp256k1", "serde", ] @@ -225,22 +224,6 @@ 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" @@ -271,7 +254,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "856ffbee2e492c23bca715d72ea34aae80d58400f2bda26a82015d6bc2ec3662" dependencies = [ - "bitcoin 0.31.2", + "bitcoin", "serde", "serde_json", ] @@ -315,6 +298,8 @@ version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -324,6 +309,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.41" @@ -337,33 +328,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - [[package]] name = "cipher" version = "0.4.4" @@ -389,6 +353,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -423,12 +396,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crunchy" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - [[package]] name = "crypto-common" version = "0.1.6" @@ -436,7 +403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -552,24 +519,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[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" @@ -589,6 +538,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -716,8 +675,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -727,9 +688,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -761,16 +724,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "half" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" -dependencies = [ - "cfg-if", - "crunchy", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -803,6 +756,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-conservative" @@ -827,6 +783,29 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" @@ -839,6 +818,66 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1007,12 +1046,38 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1062,6 +1127,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.7.5" @@ -1191,6 +1262,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "polyval" version = "0.6.2" @@ -1262,6 +1339,61 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.40" @@ -1284,8 +1416,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1295,7 +1437,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1307,6 +1459,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1385,6 +1546,47 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + [[package]] name = "ring" version = "0.17.14" @@ -1415,24 +1617,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] -name = "rustls" -version = "0.21.12" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustls" +version = "0.23.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ - "log", + "once_cell", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] @@ -1466,27 +1687,16 @@ 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", - "ciborium", "env_logger", "js-sys", "log", - "rand", + "rand 0.8.5", "rs_merkle", "serde", "serde-wasm-bindgen", @@ -1494,6 +1704,7 @@ dependencies = [ "sp_client", "tsify", "wasm-bindgen", + "zstd", ] [[package]] @@ -1501,8 +1712,8 @@ name = "sdk_relay" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "bitcoincore-rpc", - "electrum-client", "env_logger", "futures-util", "hex", @@ -1518,38 +1729,18 @@ dependencies = [ "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", + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys", "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" @@ -1613,6 +1804,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.13.0" @@ -1689,9 +1892,9 @@ checksum = "616e15864460d57052bee829c5a8ad4e910b8ff1089d3ebb1f65f398f134dc93" dependencies = [ "bech32 0.9.1", "bimap", - "bitcoin_hashes 0.13.0", + "bitcoin_hashes", "hex", - "secp256k1 0.28.2", + "secp256k1", "serde", "serde_json", ] @@ -1721,18 +1924,20 @@ dependencies = [ [[package]] name = "sp_client" version = "0.1.0" -source = "git+https://github.com/Sosthene00/sp-client.git?branch=dev#216c4f3e5b4a517ba7de17adc3c2386d67db2fcb" dependencies = [ "anyhow", "async-trait", "bdk_coin_select", - "bitcoin 0.31.2", + "bitcoin", "futures", + "hex", "log", "rayon", + "reqwest", "serde", "serde_json", "silentpayments", + "wasm-bindgen", ] [[package]] @@ -1764,6 +1969,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -1796,7 +2010,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1810,6 +2033,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.41" @@ -1851,6 +2085,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +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.45.1" @@ -1880,6 +2129,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -1917,6 +2176,76 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tsify" version = "0.4.5" @@ -1952,9 +2281,9 @@ dependencies = [ "http", "httparse", "log", - "rand", + "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -2027,6 +2356,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2068,6 +2406,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -2111,22 +2462,22 @@ dependencies = [ ] [[package]] -name = "webpki" -version = "0.22.4" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "ring", - "untrusted", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webpki-roots" -version = "0.22.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ - "webpki", + "rustls-pki-types", ] [[package]] @@ -2381,6 +2732,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zeromq" version = "0.4.1" @@ -2400,9 +2757,9 @@ dependencies = [ "num-traits", "once_cell", "parking_lot", - "rand", + "rand 0.8.5", "regex", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "uuid", @@ -2440,3 +2797,31 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index cda27c3..cb610e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [dependencies] anyhow = "1.0" +async-trait = "0.1" 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" diff --git a/src/commit.rs b/src/commit.rs index aad4bf1..051d298 100644 --- a/src/commit.rs +++ b/src/commit.rs @@ -18,8 +18,11 @@ use sdk_common::{ sp_client::{silentpayments::SilentPaymentAddress, RecipientAddress}, }; -use crate::message::{broadcast_message, BroadcastType}; use crate::{lock_freezed_utxos, MutexExt, DAEMON, STORAGE, WALLET}; +use crate::{ + message::{broadcast_message, BroadcastType}, + CHAIN_TIP, +}; pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result { let mut processes = lock_processes()?; @@ -48,10 +51,12 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result Result { return Err(Error::msg("Process is not a pairing process")); } - if let Some(paired_addresses) = commit_msg.public_data.get("pairedAddresses") { - let paired_addresses: Vec = - sdk_common::serialization::ciborium_deserialize(paired_addresses)?; + if let Ok(paired_addresses) = commit_msg.public_data.get_as_json("pairedAddresses") { + let paired_addresses: Vec = serde_json::from_value(paired_addresses.clone())?; let mut memberlist = lock_members()?; memberlist.insert(commit_msg.process_id, Member::new(paired_addresses)); return Ok(commit_msg.process_id); diff --git a/src/config.rs b/src/config.rs index ffd9197..496bfe3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ pub struct Config { pub ws_url: String, pub wallet_name: String, pub network: Network, - pub electrum_url: String, + pub blindbit_url: String, pub zmq_url: String, pub data_dir: String, } @@ -63,9 +63,9 @@ impl Config { .ok_or(Error::msg("no \"network\""))? .trim_matches('\"'), )?, - electrum_url: file_content - .remove("electrum_url") - .ok_or(Error::msg("No \"electrum_url\""))? + blindbit_url: file_content + .remove("blindbit_url") + .ok_or(Error::msg("No \"blindbit_url\""))? .to_owned(), zmq_url: file_content .remove("zmq_url") @@ -73,8 +73,7 @@ impl Config { .to_owned(), data_dir: file_content .remove("data_dir") - .ok_or(Error::msg("No \"data_dir\""))? - .to_owned(), + .unwrap_or_else(|| ".4nk".to_string()), }; Ok(config) diff --git a/src/electrumclient.rs b/src/electrumclient.rs deleted file mode 100644 index ddc1777..0000000 --- a/src/electrumclient.rs +++ /dev/null @@ -1,14 +0,0 @@ -use electrum_client::{Client, ConfigBuilder}; -use log::info; - -const VALIDATE_DOMAIN: bool = false; // self-signed cert, so we don't validate - -pub fn create_electrum_client(electrum_url: &str) -> anyhow::Result { - let config = ConfigBuilder::new() - .validate_domain(VALIDATE_DOMAIN) - .build(); - let electrum_client = Client::from_config(electrum_url, config)?; - info!("ssl client {}", electrum_url); - - Ok(electrum_client) -} diff --git a/src/faucet.rs b/src/faucet.rs index 8331ddf..348ede1 100644 --- a/src/faucet.rs +++ b/src/faucet.rs @@ -95,7 +95,7 @@ fn faucet_send( // We filter out the freezed utxos from available list let available_outpoints: Vec<(OutPoint, OwnedOutput)> = sp_wallet - .get_outputs() + .get_unspent_outputs() .iter() .filter_map(|(outpoint, output)| { if !freezed_utxos.contains(&outpoint) { diff --git a/src/main.rs b/src/main.rs index bf1307b..7188462 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,11 +7,11 @@ use std::{ net::SocketAddr, path::PathBuf, str::FromStr, - sync::{atomic::AtomicU64, Mutex, MutexGuard, OnceLock}, + sync::{atomic::AtomicU32, Arc, Mutex, MutexGuard, OnceLock}, }; use bitcoincore_rpc::{ - bitcoin::secp256k1::SecretKey, + bitcoin::{hashes::Hash, secp256k1::SecretKey}, json::{self as bitcoin_json}, }; use commit::{lock_members, MEMBERLIST}; @@ -19,15 +19,7 @@ use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt}; use log::{debug, error, warn}; use message::{broadcast_message, process_message, BroadcastType, MessageCache, MESSAGECACHE}; use scan::{check_transaction_alone, compute_partial_tweak_to_transaction}; -use sdk_common::sp_client::{ - bitcoin::secp256k1::rand::{thread_rng, Rng}, - bitcoin::OutPoint, - SpClient, SpendKey, -}; -use sdk_common::{ - error::AnkError, - network::{AnkFlag, NewTxMessage}, -}; +use sdk_common::network::{AnkFlag, NewTxMessage}; use sdk_common::{ network::HandshakeMessage, pcd::Member, @@ -41,10 +33,16 @@ use sdk_common::{ Amount, Network, Transaction, }, silentpayments::SilentPaymentAddress, - OwnedOutput, }, MutexExt, }; +use sdk_common::{ + sp_client::{ + bitcoin::{secp256k1::rand::thread_rng, OutPoint}, + OutputSpendStatus, SpClient, SpendKey, + }, + updates::{init_update_sink, NativeUpdateSink, StateUpdate}, +}; use serde_json::Value; use tokio::net::{TcpListener, TcpStream}; @@ -58,7 +56,6 @@ use zeromq::{Socket, SocketRecv}; mod commit; mod config; mod daemon; -mod electrumclient; mod faucet; mod message; mod scan; @@ -69,6 +66,8 @@ use crate::{ scan::scan_blocks, }; +pub const WITH_CUTTHROUGH: bool = false; // We'd rather catch everything for this use case + type Tx = UnboundedSender; type PeerMap = Mutex>; @@ -77,7 +76,7 @@ pub(crate) static PEERMAP: OnceLock = OnceLock::new(); pub(crate) static DAEMON: OnceLock>> = OnceLock::new(); -static CHAIN_TIP: AtomicU64 = AtomicU64::new(0); +static CHAIN_TIP: AtomicU32 = AtomicU32::new(0); pub static FREEZED_UTXOS: OnceLock>> = OnceLock::new(); @@ -205,7 +204,7 @@ async fn handle_connection( our_sp_address.to_string(), OutPointMemberMap(members), OutPointProcessMap(processes), - current_tip, + current_tip.into(), ); if let Err(e) = broadcast_message( @@ -246,7 +245,6 @@ async fn handle_connection( } fn create_new_tx_message(transaction: Vec) -> Result { - // debug!("Creating tx message"); let tx: Transaction = deserialize(&transaction)?; if tx.is_coinbase() { @@ -267,7 +265,63 @@ fn create_new_tx_message(transaction: Vec) -> Result { )) } -async fn handle_zmq(zmq_url: String, electrum_url: String) { +async fn handle_scan_updates( + scan_rx: std::sync::mpsc::Receiver, +) { + while let Ok(update) = scan_rx.recv() { + log::debug!("Received scan update: {:?}", update); + } +} + +async fn handle_state_updates( + state_rx: std::sync::mpsc::Receiver, +) { + while let Ok(update) = state_rx.recv() { + match update { + StateUpdate::Update { + blkheight, + blkhash, + found_outputs, + found_inputs, + } => { + // We update the wallet with found outputs and inputs + let mut sp_wallet = WALLET.get().unwrap().lock_anyhow().unwrap(); + // inputs first + for outpoint in found_inputs { + sp_wallet.mark_output_mined(&outpoint, blkhash); + } + sp_wallet.get_mut_outputs().extend(found_outputs); + sp_wallet.set_last_scan(blkheight.to_consensus_u32()); + let json = serde_json::to_value(sp_wallet.clone()).unwrap(); + STORAGE + .get() + .unwrap() + .lock_anyhow() + .unwrap() + .wallet_file + .save(&json) + .unwrap(); + } + StateUpdate::NoUpdate { blkheight } => { + // We just keep the current height to update the last_scan + debug!("No update, setting last scan at {}", blkheight); + let mut sp_wallet = WALLET.get().unwrap().lock_anyhow().unwrap(); + sp_wallet.set_last_scan(blkheight.to_consensus_u32()); + let json = serde_json::to_value(sp_wallet.clone()).unwrap(); + STORAGE + .get() + .unwrap() + .lock_anyhow() + .unwrap() + .wallet_file + .save(&json) + .unwrap(); + } + } + } +} + +async fn handle_zmq(zmq_url: String, blindbit_url: String) { debug!("Starting listening on Core"); let mut socket = zeromq::SubSocket::new(); socket.connect(&zmq_url).await.unwrap(); @@ -297,13 +351,51 @@ async fn handle_zmq(zmq_url: String, electrum_url: String) { continue; } }, - Ok("hashblock") => match scan_blocks(0, &electrum_url) { - Ok(_) => continue, - Err(e) => { - error!("{}", e); - continue; + Ok("hashblock") => { + let current_height = DAEMON + .get() + .unwrap() + .lock_anyhow() + .unwrap() + .get_current_height() + .unwrap(); + CHAIN_TIP.store(current_height as u32, std::sync::atomic::Ordering::SeqCst); + + // Add retry logic for hashblock processing + let mut retry_count = 0; + const MAX_RETRIES: u32 = 4; + const RETRY_DELAY_MS: u64 = 1000; // 1 second initial delay + + loop { + match scan_blocks(0, &blindbit_url).await { + Ok(_) => { + debug!("Successfully scanned blocks after {} retries", retry_count); + break; + } + Err(e) => { + retry_count += 1; + if retry_count >= MAX_RETRIES { + error!( + "Failed to scan blocks after {} retries: {}", + MAX_RETRIES, e + ); + break; + } + + // Exponential backoff: 1s, 2s, 4s + let delay_ms = RETRY_DELAY_MS * (1 << (retry_count - 1)); + warn!( + "Scan failed (attempt {}/{}), retrying in {}ms: {}", + retry_count, MAX_RETRIES, delay_ms, e + ); + + tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)) + .await; + } + } } - }, + continue; + } _ => { error!("Unexpected message in zmq"); continue; @@ -356,6 +448,9 @@ async fn main() -> Result<()> { .get_current_height()? .try_into()?; + // Set CHAIN_TIP + CHAIN_TIP.store(current_tip, std::sync::atomic::Ordering::SeqCst); + let mut app_dir = PathBuf::from_str(&env::var("HOME")?)?; app_dir.push(config.data_dir); let mut wallet_file = app_dir.clone(); @@ -470,16 +565,24 @@ async fn main() -> Result<()> { STORAGE.set(Mutex::new(storage)).unwrap(); + let (sink, scan_rx, state_rx) = NativeUpdateSink::new(); + init_update_sink(Arc::new(sink)); + + // Spawn the update handlers + tokio::spawn(handle_scan_updates(scan_rx)); + tokio::spawn(handle_state_updates(state_rx)); + if last_scan < current_tip { log::info!("Scanning for our outputs"); - scan_blocks(current_tip - last_scan, &config.electrum_url)?; + scan_blocks(current_tip - last_scan, &config.blindbit_url).await?; } - Set CHAIN_TIP - CHAIN_TIP.store(current_tip as u64, std::sync::atomic::Ordering::SeqCst); - // Subscribe to Bitcoin Core - tokio::spawn(handle_zmq(config.zmq_url, config.electrum_url)); + let zmq_url = config.zmq_url.clone(); + let blindbit_url = config.blindbit_url.clone(); + tokio::spawn(async move { + handle_zmq(zmq_url, blindbit_url).await; + }); // Create the event loop and TCP listener we'll accept connections on. let try_socket = TcpListener::bind(config.ws_url).await; diff --git a/src/scan.rs b/src/scan.rs index 76bc0da..d2fb3d8 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -1,24 +1,38 @@ use std::collections::HashMap; +use std::collections::HashSet; use std::str::FromStr; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use std::sync::Arc; use std::sync::MutexGuard; +use anyhow::bail; use anyhow::{Error, Result}; use bitcoincore_rpc::bitcoin::absolute::Height; +use bitcoincore_rpc::bitcoin::hashes::sha256; use bitcoincore_rpc::bitcoin::hashes::Hash; -use electrum_client::ElectrumApi; +use bitcoincore_rpc::bitcoin::Amount; +use futures_util::Stream; +use log::info; use sdk_common::silentpayments::SpWallet; 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::silentpayments::utils::receiving::{ calculate_tweak_data, get_pubkey_from_input, }; -use sdk_common::sp_client::{OutputSpendStatus, OwnedOutput}; +use sdk_common::sp_client::BlockData; +use sdk_common::sp_client::ChainBackend; +use sdk_common::sp_client::FilterData; +use sdk_common::sp_client::SpClient; +use sdk_common::sp_client::Updater; +use sdk_common::sp_client::{BlindbitBackend, OutputSpendStatus, OwnedOutput, SpScanner}; +use sdk_common::updates::StateUpdater; use tokio::time::Instant; -use crate::{electrumclient, MutexExt, DAEMON, STORAGE, WALLET}; +use crate::CHAIN_TIP; +use crate::{MutexExt, DAEMON, STORAGE, WALLET, WITH_CUTTHROUGH}; pub fn compute_partial_tweak_to_transaction(tx: &Transaction) -> Result { let daemon = DAEMON.get().ok_or(Error::msg("DAEMON not initialized"))?; @@ -236,23 +250,318 @@ fn scan_block_inputs( Ok(found) } -pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Result<()> { +pub struct NativeSpScanner<'a> { + updater: Box, + backend: Box, + client: SpClient, + keep_scanning: &'a AtomicBool, // used to interrupt scanning + owned_outpoints: HashSet, // used to scan block inputs +} + +impl<'a> NativeSpScanner<'a> { + pub fn new( + client: SpClient, + updater: Box, + backend: Box, + owned_outpoints: HashSet, + keep_scanning: &'a AtomicBool, + ) -> Self { + Self { + client, + updater, + backend, + owned_outpoints, + keep_scanning, + } + } + + pub async fn process_blocks( + &mut self, + start: Height, + end: Height, + block_data_stream: impl Stream> + Unpin + Send, + ) -> Result<()> { + use sdk_common::sp_client::futures::StreamExt; + use std::time::{Duration, Instant}; + + let mut update_time = Instant::now(); + let mut stream = block_data_stream; + + while let Some(blockdata) = stream.next().await { + let blockdata = blockdata?; + let blkheight = blockdata.blkheight; + let blkhash = blockdata.blkhash; + + // stop scanning and return if interrupted + if self.should_interrupt() { + self.save_state()?; + return Ok(()); + } + + let mut save_to_storage = false; + + // always save on last block or after 30 seconds since last save + if blkheight == end || update_time.elapsed() > Duration::from_secs(30) { + save_to_storage = true; + } + + let (found_outputs, found_inputs) = self.process_block(blockdata).await?; + + if !found_outputs.is_empty() { + save_to_storage = true; + self.record_outputs(blkheight, blkhash, found_outputs)?; + } + + if !found_inputs.is_empty() { + save_to_storage = true; + self.record_inputs(blkheight, blkhash, found_inputs)?; + } + + // tell the updater we scanned this block + self.record_progress(start, blkheight, end)?; + + if save_to_storage { + self.save_state()?; + update_time = Instant::now(); + } + } + + Ok(()) + } +} + +#[async_trait::async_trait] +impl<'a> SpScanner for NativeSpScanner<'a> { + async fn scan_blocks( + &mut self, + start: Height, + end: Height, + dust_limit: Amount, + with_cutthrough: bool, + ) -> Result<()> { + if start > end { + bail!("bigger start than end: {} > {}", start, end); + } + + info!("start: {} end: {}", start, end); + let start_time: Instant = Instant::now(); + + // get block data stream + let range = start.to_consensus_u32()..=end.to_consensus_u32(); + let block_data_stream = self.get_block_data_stream(range, dust_limit, with_cutthrough); + + // process blocks using block data stream + self.process_blocks(start, end, block_data_stream).await?; + + // time elapsed for the scan + info!( + "Blindbit scan complete in {} seconds", + start_time.elapsed().as_secs() + ); + + Ok(()) + } + + async fn process_block( + &mut self, + blockdata: BlockData, + ) -> Result<(HashMap, HashSet)> { + let BlockData { + blkheight, + tweaks, + new_utxo_filter, + spent_filter, + .. + } = blockdata; + + let outs = self + .process_block_outputs(blkheight, tweaks, new_utxo_filter) + .await?; + + // after processing outputs, we add the found outputs to our list + self.owned_outpoints.extend(outs.keys()); + + let ins = self.process_block_inputs(blkheight, spent_filter).await?; + + // after processing inputs, we remove the found inputs + self.owned_outpoints.retain(|item| !ins.contains(item)); + + Ok((outs, ins)) + } + + async fn process_block_outputs( + &self, + blkheight: Height, + tweaks: Vec, + new_utxo_filter: FilterData, + ) -> Result> { + let mut res = HashMap::new(); + + if !tweaks.is_empty() { + let secrets_map = self.client.get_script_to_secret_map(tweaks)?; + + //last_scan = last_scan.max(n as u32); + let candidate_spks: Vec<&[u8; 34]> = secrets_map.keys().collect(); + + //get block gcs & check match + let blkfilter = BlockFilter::new(&new_utxo_filter.data); + let blkhash = new_utxo_filter.block_hash; + + let matched_outputs = Self::check_block_outputs(blkfilter, blkhash, candidate_spks)?; + + //if match: fetch and scan utxos + if matched_outputs { + info!("matched outputs on: {}", blkheight); + let found = self.scan_utxos(blkheight, secrets_map).await?; + + if !found.is_empty() { + for (label, utxo, tweak) in found { + let outpoint = OutPoint { + txid: utxo.txid, + vout: utxo.vout, + }; + + let out = OwnedOutput { + blockheight: blkheight, + tweak: tweak.to_be_bytes(), + amount: utxo.value, + script: utxo.scriptpubkey, + label, + spend_status: OutputSpendStatus::Unspent, + }; + + res.insert(outpoint, out); + } + } + } + } + Ok(res) + } + + async fn process_block_inputs( + &self, + blkheight: Height, + spent_filter: FilterData, + ) -> Result> { + let mut res = HashSet::new(); + + let blkhash = spent_filter.block_hash; + + // first get the 8-byte hashes used to construct the input filter + let input_hashes_map = self.get_input_hashes(blkhash)?; + + // check against filter + let blkfilter = BlockFilter::new(&spent_filter.data); + let matched_inputs = self.check_block_inputs( + blkfilter, + blkhash, + input_hashes_map.keys().cloned().collect(), + )?; + + // if match: download spent data, collect the outpoints that are spent + if matched_inputs { + info!("matched inputs on: {}", blkheight); + let spent = self.backend.spent_index(blkheight).await?.data; + + for spent in spent { + let hex: &[u8] = spent.as_ref(); + + if let Some(outpoint) = input_hashes_map.get(hex) { + res.insert(*outpoint); + } + } + } + Ok(res) + } + + fn get_block_data_stream( + &self, + range: std::ops::RangeInclusive, + dust_limit: Amount, + with_cutthrough: bool, + ) -> std::pin::Pin> + Send>> { + self.backend + .get_block_data_for_range(range, dust_limit, with_cutthrough) + } + + fn should_interrupt(&self) -> bool { + !self + .keep_scanning + .load(std::sync::atomic::Ordering::Relaxed) + } + + fn save_state(&mut self) -> Result<()> { + self.updater.save_to_persistent_storage() + } + + fn record_outputs( + &mut self, + height: Height, + block_hash: BlockHash, + outputs: HashMap, + ) -> Result<()> { + self.updater.record_block_outputs(height, block_hash, outputs) + } + + fn record_inputs( + &mut self, + height: Height, + block_hash: BlockHash, + inputs: HashSet, + ) -> Result<()> { + self.updater.record_block_inputs(height, block_hash, inputs) + } + + fn record_progress(&mut self, start: Height, current: Height, end: Height) -> Result<()> { + self.updater.record_scan_progress(start, current, end) + } + + fn client(&self) -> &SpClient { + &self.client + } + + fn backend(&self) -> &dyn ChainBackend { + self.backend.as_ref() + } + + fn updater(&mut self) -> &mut dyn Updater { + self.updater.as_mut() + } + + // Override the default get_input_hashes implementation to use owned_outpoints + fn get_input_hashes(&self, blkhash: BlockHash) -> Result> { + let mut map: HashMap<[u8; 8], OutPoint> = HashMap::new(); + + for outpoint in &self.owned_outpoints { + let mut arr = [0u8; 68]; + arr[..32].copy_from_slice(&outpoint.txid.to_raw_hash().to_byte_array()); + arr[32..36].copy_from_slice(&outpoint.vout.to_le_bytes()); + arr[36..].copy_from_slice(&blkhash.to_byte_array()); + let hash = sha256::Hash::hash(&arr); + + let mut res = [0u8; 8]; + res.copy_from_slice(&hash[..8]); + + map.insert(res, outpoint.clone()); + } + + Ok(map) + } +} + +pub async fn scan_blocks(mut n_blocks_to_scan: u32, blindbit_url: &str) -> anyhow::Result<()> { log::info!("Starting a rescan"); - let electrum_client = electrumclient::create_electrum_client(electrum_url)?; - let mut sp_wallet = WALLET - .get() - .ok_or(Error::msg("Wallet not initialized"))? - .lock_anyhow()?; - - let core = DAEMON - .get() - .ok_or(Error::msg("DAEMON not initialized"))? - .lock_anyhow()?; - - let secp = Secp256k1::new(); - let scan_height = sp_wallet.get_last_scan(); - let tip_height: u32 = core.get_current_height()?.try_into()?; + // Get all the data we need upfront, before any async operations + let (sp_wallet, scan_height, tip_height) = { + let sp_wallet = WALLET + .get() + .ok_or(Error::msg("Wallet not initialized"))? + .lock_anyhow()?; + let scan_height = sp_wallet.get_last_scan(); + let tip_height: u32 = CHAIN_TIP.load(Ordering::Relaxed).try_into()?; + (sp_wallet.clone(), scan_height, tip_height) + }; // 0 means scan to tip if n_blocks_to_scan == 0 { @@ -270,73 +579,32 @@ pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Res return Ok(()); } + let updater = StateUpdater::new(); + let backend = BlindbitBackend::new(blindbit_url.to_string())?; + + let owned_outpoints = sp_wallet.get_unspent_outputs().keys().map(|o| *o).collect(); + + let keep_scanning = Arc::new(AtomicBool::new(true)); + 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_sp_client().get_scan_key(); - let start_time = Instant::now(); + let mut scanner = NativeSpScanner::new( + sp_wallet.get_sp_client().clone(), + Box::new(updater), + Box::new(backend), + owned_outpoints, + &keep_scanning, + ); - for (blkheight, blkhash, blkfilter) in filters { - let spk2secret = match tweak_data_map.remove(&blkheight) { - Some(tweak_data_vec) => get_script_to_secret_map( - &sp_wallet.get_sp_client().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 owned_spks: Vec> = sp_wallet - .get_outputs() - .iter() - .map(|(_, output)| { - let script = output.script.to_bytes(); - 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_wallet.get_sp_client().sp_receiver, - &blk.txdata, - blkheight.into(), - spk2secret, - )?; - if !utxo_created_in_block.is_empty() { - sp_wallet.get_mut_outputs().extend(utxo_created_in_block); - } - - // update the list of outputs just in case - // utxos may be created and destroyed in the same block - // search inputs and mark as mined - let utxo_destroyed_in_block = scan_block_inputs(sp_wallet.get_outputs(), blk.txdata)?; - if !utxo_destroyed_in_block.is_empty() { - let outputs = sp_wallet.get_mut_outputs(); - for outpoint in utxo_destroyed_in_block { - if let Some(output) = outputs.get_mut(&outpoint) { - output.spend_status = - OutputSpendStatus::Mined(blkhash.as_raw_hash().to_byte_array()); - } - } - } - } - } + let dust_limit = Amount::from_sat(0); // We don't really have a dust limit for this use case + scanner + .scan_blocks( + Height::from_consensus(start)?, + Height::from_consensus(end)?, + dust_limit, + WITH_CUTTHROUGH, + ) + .await?; // time elapsed for the scan log::info!( @@ -344,14 +612,5 @@ pub fn scan_blocks(mut n_blocks_to_scan: u32, electrum_url: &str) -> anyhow::Res start_time.elapsed().as_secs() ); - // update last_scan height - sp_wallet.set_last_scan(end); - STORAGE - .get() - .unwrap() - .lock_anyhow()? - .wallet_file - .save(&serde_json::to_value(sp_wallet.clone())?)?; - Ok(()) }