From 18a043b42c2552ca62a2103cf09971e9d58783ed Mon Sep 17 00:00:00 2001 From: Chris Guida Date: Wed, 6 Dec 2023 16:16:10 -0600 Subject: [PATCH] Feat/bitcoind backend (#33) * wip add bitcoind backend * wip fix timeout * allow host/port from options * wip call scanblocks from add not ls * filter false positives * wip change to use new scanblocks rpc, doesn't build * use bdk wallet_rpc custom example, builds now * remove print statements * fix agressive locking and don't rescan every block. we still rescan if the same wallet is added again * don't use local deps * return more helpful error when wallet in datastore is invalid * even better error message * fix chain initialization connect error * cleanup * fix brpc_user error msg and cleanup logging * more cleanup * more cleanup --- Cargo.lock | 805 ++++++-------------------------------------------- Cargo.toml | 17 +- src/main.rs | 192 +++++++++--- src/state.rs | 17 +- src/wallet.rs | 624 ++++++++++++++++++-------------------- 5 files changed, 568 insertions(+), 1087 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f95781f..48fb625 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,17 +80,6 @@ version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" -[[package]] -name = "async-trait" -version = "0.1.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -126,11 +115,11 @@ checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bdk" -version = "1.0.0-alpha.1" -source = "git+https://github.com/bitcoindevkit/bdk?rev=8f38e96e4542db2378e2e64cd9289638ee86ba1a#8f38e96e4542db2378e2e64cd9289638ee86ba1a" +version = "1.0.0-alpha.2" +source = "git+https://github.com/chrisguida/bdk?rev=cc32e69a12b8c30e571e6261a8b99dec2cef6ff5#cc32e69a12b8c30e571e6261a8b99dec2cef6ff5" dependencies = [ "bdk_chain", - "bitcoin", + "bitcoin 0.30.2", "getrandom", "js-sys", "log", @@ -142,29 +131,18 @@ dependencies = [ [[package]] name = "bdk_chain" -version = "0.5.0" -source = "git+https://github.com/bitcoindevkit/bdk?rev=8f38e96e4542db2378e2e64cd9289638ee86ba1a#8f38e96e4542db2378e2e64cd9289638ee86ba1a" +version = "0.6.0" +source = "git+https://github.com/chrisguida/bdk?rev=cc32e69a12b8c30e571e6261a8b99dec2cef6ff5#cc32e69a12b8c30e571e6261a8b99dec2cef6ff5" dependencies = [ - "bitcoin", + "bitcoin 0.30.2", "miniscript", "serde", ] -[[package]] -name = "bdk_esplora" -version = "0.3.0" -source = "git+https://github.com/bitcoindevkit/bdk?rev=8f38e96e4542db2378e2e64cd9289638ee86ba1a#8f38e96e4542db2378e2e64cd9289638ee86ba1a" -dependencies = [ - "async-trait", - "bdk_chain", - "esplora-client", - "futures", -] - [[package]] name = "bdk_file_store" version = "0.2.0" -source = "git+https://github.com/bitcoindevkit/bdk?rev=8f38e96e4542db2378e2e64cd9289638ee86ba1a#8f38e96e4542db2378e2e64cd9289638ee86ba1a" +source = "git+https://github.com/chrisguida/bdk?rev=cc32e69a12b8c30e571e6261a8b99dec2cef6ff5#cc32e69a12b8c30e571e6261a8b99dec2cef6ff5" dependencies = [ "bdk_chain", "bincode", @@ -191,14 +169,34 @@ name = "bitcoin" version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" +dependencies = [ + "bech32", + "bitcoin_hashes 0.11.0", + "secp256k1 0.24.3", + "serde", +] + +[[package]] +name = "bitcoin" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" dependencies = [ "base64 0.13.1", "bech32", - "bitcoin_hashes", - "secp256k1", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1 0.27.0", "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.11.0" @@ -209,10 +207,38 @@ dependencies = [ ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "bitcoin_hashes" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", + "serde", +] + +[[package]] +name = "bitcoincore-rpc" +version = "0.17.0" +source = "git+https://github.com/chrisguida/rust-bitcoincore-rpc?branch=feat/scanblocks#23927f3e8b3bd413e0bf8b7c42b9531345bdd1a3" +dependencies = [ + "bitcoin-private", + "bitcoincore-rpc-json", + "jsonrpc", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoincore-rpc-json" +version = "0.17.0" +source = "git+https://github.com/chrisguida/rust-bitcoincore-rpc?branch=feat/scanblocks#23927f3e8b3bd413e0bf8b7c42b9531345bdd1a3" +dependencies = [ + "bitcoin 0.30.2", + "bitcoin-private", + "serde", + "serde_json", +] [[package]] name = "bitflags" @@ -226,12 +252,6 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "bytes" version = "1.4.0" @@ -318,7 +338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da3b630e345cdfc6f64315414b50815a9eeabbf12438413798bf09e9e79be8b8" dependencies = [ "anyhow", - "bitcoin", + "bitcoin 0.29.2", "bytes", "futures-util", "hex", @@ -335,46 +355,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - [[package]] name = "env_logger" version = "0.10.0" @@ -409,65 +389,6 @@ dependencies = [ "libc", ] -[[package]] -name = "esplora-client" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e11244e7fd8b0beee0a3c62137c4bd9f756fe2c492ccf93171f81467b59200" -dependencies = [ - "bitcoin", - "log", - "reqwest", - "serde", - "ureq", -] - -[[package]] -name = "fastrand" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" - -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - [[package]] name = "futures" version = "0.3.28" @@ -574,31 +495,6 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" -[[package]] -name = "h2" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "heck" version = "0.4.1" @@ -617,6 +513,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "home" version = "0.5.5" @@ -626,109 +528,12 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "hyper" -version = "0.14.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.4.9", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -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", -] - -[[package]] -name = "ipnet" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" - [[package]] name = "is-terminal" version = "0.4.9" @@ -756,10 +561,14 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +name = "jsonrpc" +version = "0.14.0" +source = "git+https://github.com/chrisguida/rust-jsonrpc?branch=feat/simple-http-timeout#4b3156e02bcad67e9e11ca972dadc323700188b9" +dependencies = [ + "base64 0.13.1", + "serde", + "serde_json", +] [[package]] name = "libc" @@ -785,19 +594,14 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "miniscript" -version = "9.0.2" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5b106477a0709e2da253e5559ba4ab20a272f8577f1eefff72f3a905b5d35f5" +checksum = "1eb102b66b2127a872dbcc73095b7b47aeb9d92f7b03c2b2298253ffc82c7594" dependencies = [ - "bitcoin", + "bitcoin 0.30.2", + "bitcoin-private", "serde", ] @@ -821,24 +625,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "num_cpus" version = "1.16.0" @@ -864,56 +650,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "openssl" -version = "0.10.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - [[package]] name = "pin-project-lite" version = "0.2.12" @@ -926,12 +662,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -986,15 +716,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "regex" version = "1.9.3" @@ -1024,59 +745,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" -[[package]] -name = "reqwest" -version = "0.11.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" -dependencies = [ - "base64 0.21.2", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tokio-socks", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1089,45 +757,13 @@ version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.4.0", + "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] -[[package]] -name = "rustls" -version = "0.21.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.3", - "sct", -] - -[[package]] -name = "rustls-webpki" -version = "0.100.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "ryu" version = "1.0.15" @@ -1135,33 +771,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "sct" -version = "0.7.0" +name = "secp256k1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ - "ring", - "untrusted", + "bitcoin_hashes 0.11.0", + "secp256k1-sys 0.6.1", + "serde", ] [[package]] name = "secp256k1" -version = "0.24.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.12.0", "rand", - "secp256k1-sys", + "secp256k1-sys 0.8.1", "serde", ] @@ -1175,26 +803,12 @@ dependencies = [ ] [[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" +name = "secp256k1-sys" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" dependencies = [ - "core-foundation-sys", - "libc", + "cc", ] [[package]] @@ -1228,18 +842,6 @@ 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 = "slab" version = "0.4.8" @@ -1256,8 +858,8 @@ dependencies = [ "anyhow", "base64 0.21.2", "bdk", - "bdk_esplora", "bdk_file_store", + "bitcoincore-rpc", "clap", "cln-plugin", "cln-rpc", @@ -1268,16 +870,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.3" @@ -1288,23 +880,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "socks" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" -dependencies = [ - "byteorder", - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "strsim" version = "0.10.0" @@ -1322,19 +897,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys", -] - [[package]] name = "termcolor" version = "1.2.0" @@ -1344,41 +906,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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.31.0" @@ -1391,7 +918,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.3", + "socket2", "tokio-macros", "windows-sys", ] @@ -1407,28 +934,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" -dependencies = [ - "either", - "futures-util", - "thiserror", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.14" @@ -1454,12 +959,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.37" @@ -1480,90 +979,18 @@ dependencies = [ "once_cell", ] -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "ureq" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" -dependencies = [ - "base64 0.21.2", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-webpki 0.100.1", - "serde", - "serde_json", - "socks", - "url", - "webpki-roots", -] - -[[package]] -name = "url" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[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.0+wasi-snapshot-preview1" @@ -1595,18 +1022,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.87" @@ -1636,25 +1051,6 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" -[[package]] -name = "web-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki 0.100.1", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1751,12 +1147,3 @@ name = "windows_x86_64_msvc" version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] diff --git a/Cargo.toml b/Cargo.toml index 1ddcaf6..8eca6d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,15 +9,22 @@ edition = "2021" anyhow = "1.0" base64 = "0.21.2" # bdk = "0.27.1" -# bdk = { version = "0.28", default-features=false, features = ["std", "key-value-db","async-interface", "use-esplora-async"] } -bdk = { git = "https://github.com/bitcoindevkit/bdk", version = "1.0.0-alpha.1", rev = "8f38e96e4542db2378e2e64cd9289638ee86ba1a" } +# bdk = { version = "0.29", default-features=false, features = ["std", "key-value-db","async-interface", "use-esplora-async", "rpc"] } +# bdk = { git = "https://github.com/bitcoindevkit/bdk", version = "1.0.0-alpha.1", rev = "8f38e96e4542db2378e2e64cd9289638ee86ba1a" } +bdk = { git = "https://github.com/chrisguida/bdk", version = "1.0.0-alpha.2", rev = "cc32e69a12b8c30e571e6261a8b99dec2cef6ff5" } +# bdk = "1.0.0-alpha.2" +# bdk = { path = "../../lib/bdk/crates/bdk" } # bdk = { path = "../bdk/crates/bdk" } # bdk_esplora = { version = "0.3.0", features = ["async-https"] } -# bdk_esplora = { path = "../bdk/crates/esplora", features = ["async-https"] } -bdk_esplora = { git = "https://github.com/bitcoindevkit/bdk", rev = "8f38e96e4542db2378e2e64cd9289638ee86ba1a" } +# bdk_esplora = { path = "../../lib/bdk/crates/esplora", features = ["async-https"] } +# bdk_esplora = { git = "https://github.com/bitcoindevkit/bdk", rev = "8f38e96e4542db2378e2e64cd9289638ee86ba1a" } # bdk_file_store = { version = "0.2.0" } +# bdk_file_store = { path = "../../lib/bdk/crates/file_store" } # bdk_file_store = { path = "../bdk/crates/file_store" } -bdk_file_store ={ git = "https://github.com/bitcoindevkit/bdk", rev = "8f38e96e4542db2378e2e64cd9289638ee86ba1a" } +# bdk_file_store = { git = "https://github.com/bitcoindevkit/bdk", rev = "8f38e96e4542db2378e2e64cd9289638ee86ba1a" } +bdk_file_store = { git = "https://github.com/chrisguida/bdk", rev = "cc32e69a12b8c30e571e6261a8b99dec2cef6ff5" } +# bitcoincore-rpc = { path = "../rust-bitcoincore-rpc/client" } +bitcoincore-rpc = { git = "https://github.com/chrisguida/rust-bitcoincore-rpc", branch = "feat/scanblocks" } clap = { version = "4.4.0", features = ["derive"] } cln-plugin = { git = "https://github.com/elementsproject/lightning", version = "0.1.4" } # cln-plugin = { path = "../../lightning/plugins" } diff --git a/src/main.rs b/src/main.rs index 43bfc0c..8bed6aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ #[macro_use] extern crate serde_json; +use bdk::chain::tx_graph::CanonicalTx; +use bdk::chain::ConfirmationTimeAnchor; use clap::error::ErrorKind; use clap::{CommandFactory, Parser, Subcommand}; use cln_rpc::model::DatastoreMode; @@ -21,24 +23,42 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::sync::Mutex; -use anyhow::Ok; use smaug::wallet::{AddArgs, DescriptorWallet, SMAUG_DATADIR, UTXO_DEPOSIT_TAG, UTXO_SPENT_TAG}; use cln_plugin::{anyhow, messages, options, Builder, Error, Plugin}; use tokio; -use bdk::TransactionDetails; +use bdk::bitcoin::Transaction; use smaug::state::{Smaug, State}; #[tokio::main] -// #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), anyhow::Error> { let builder = Builder::new(tokio::io::stdin(), tokio::io::stdout()) .option(options::ConfigOption::new( - "wd_network", + "smaug_network", options::Value::OptString, "Which network to use: [bitcoin, testnet, signet, regtest, mutinynet]", )) + .option(options::ConfigOption::new( + "smaug_brpc_host", + options::Value::String("127.0.0.1".to_owned()), + "Bitcoind RPC host (default 127.0.0.1)", + )) + .option(options::ConfigOption::new( + "smaug_brpc_port", + options::Value::Integer(8332), + "Bitcoind RPC port (default 8332)", + )) + .option(options::ConfigOption::new( + "smaug_brpc_user", + options::Value::OptString, + "Bitcoind RPC user (Required)", + )) + .option(options::ConfigOption::new( + "smaug_brpc_pass", + options::Value::OptString, + "Bitcoind RPC password (Required)", + )) .notification(messages::NotificationTopic::new(UTXO_DEPOSIT_TAG)) .notification(messages::NotificationTopic::new(UTXO_SPENT_TAG)) .rpcmethod( @@ -58,34 +78,81 @@ async fn main() -> Result<(), anyhow::Error> { configured_plugin.configuration() ); log::debug!( - "wd_network = {:?}, cln_network = {}", - configured_plugin.option("wd_network"), + "smaug_network = {:?}, cln_network = {}", + configured_plugin.option("smaug_network"), configured_plugin.configuration().network ); - let network = match configured_plugin.option("wd_network") { - Some(wd_network) => match wd_network.as_str() { + let network = match configured_plugin.option("smaug_network") { + Some(smaug_network) => match smaug_network.as_str() { Some(wdn) => wdn.to_owned(), None => configured_plugin.configuration().network, }, None => configured_plugin.configuration().network, }; + let brpc_host = match configured_plugin.option("smaug_brpc_host") { + Some(smaug_brpc_host) => match smaug_brpc_host.as_str() { + Some(sbh) => sbh.to_owned(), + None => return Err(anyhow!("must specify smaug_brpc_host")), + }, + None => { + return Err(anyhow!( + "must specify smaug_brpc_host (your bitcoind instance rpc host)" + )) + } + }; + let brpc_port: u16 = match configured_plugin.option("smaug_brpc_port") { + Some(smaug_brpc_port) => match smaug_brpc_port.as_i64() { + Some(sbp) => u16::try_from(sbp)?, + None => { + return Err(anyhow!( + "must specify smaug_brpc_port (your bitcoind instance rpcport)" + )) + } + }, + None => return Err(anyhow!("must specify smaug_brpc_port")), + }; + let brpc_user = match configured_plugin.option("smaug_brpc_user") { + Some(smaug_brpc_user) => match smaug_brpc_user.as_str() { + Some(wdn) => wdn.to_owned(), + None => return Err(anyhow!("must specify smaug_brpc_user")), + }, + None => { + return Err(anyhow!( + "must specify smaug_brpc_user (your bitcoind instance rpcuser)" + )) + } + }; + let brpc_pass = match configured_plugin.option("smaug_brpc_pass") { + Some(smaug_brpc_pass) => match smaug_brpc_pass.as_str() { + Some(wdn) => wdn.to_owned(), + None => { + return Err(anyhow!( + "must specify smaug_brpc_pass (your bitcoind instance rpcpassword)" + )) + } + }, + None => return Err(anyhow!("must specify smaug_brpc_pass")), + }; let ln_dir: PathBuf = configured_plugin.configuration().lightning_dir.into(); // Create data dir if it does not exist fs::create_dir_all(ln_dir.join(SMAUG_DATADIR)).unwrap_or_else(|e| { log::error!("Cannot create data dir: {e:?}"); std::process::exit(1); }); - log::debug!("network = {}", network); + log::trace!("network = {}", network); let rpc_file = configured_plugin.configuration().rpc_file; let p = Path::new(&rpc_file); let mut rpc = ClnRpc::new(p).await?; + log::trace!("calling listdatastore"); + let lds_response = rpc .call(Request::ListDatastore(ListdatastoreRequest { key: Some(vec!["smaug".to_owned()]), })) .await .map_err(|e| anyhow!("Error calling listdatastore: {:?}", e))?; + log::trace!("fetching wallets from listdatastore response"); let wallets: BTreeMap = match lds_response { Response::ListDatastore(r) => match r.datastore.is_empty() { true => BTreeMap::new(), @@ -93,22 +160,38 @@ async fn main() -> Result<(), anyhow::Error> { Some(deserialized) => match serde_json::from_str(&deserialized) { core::result::Result::Ok(dws) => dws, core::result::Result::Err(e) => { - log::error!("{}", e); + // sometimes log::error! doesn't execute before plugin is killed, so we use eprintln! here instead + eprintln!( + "Error parsing wallet from datastore: {:?}", + &r.datastore[0].string + ); + eprintln!("{}", e); + eprintln!("This is probably due to an outdated wallet format."); + eprintln!("Please delete the wallet with `lightning-cli deldatastore smaug` and restart Smaug."); return Err(e.into()); } }, None => BTreeMap::new(), }, }, - _ => panic!(), + _ => panic!("Unrecognized type returned from listdatastore call, exiting"), }; + log::trace!("creating plugin state"); let watch_descriptor = Smaug { wallets, network: network.clone(), + brpc_host: brpc_host.clone(), + brpc_port: brpc_port.clone(), + brpc_user: brpc_user.clone(), + brpc_pass: brpc_pass.clone(), db_dir: ln_dir.join(SMAUG_DATADIR), }; let plugin_state = Arc::new(Mutex::new(watch_descriptor.clone())); + log::trace!("getting lock on state"); + plugin_state.lock().await.network = network; + log::trace!("starting Smaug"); + let plugin = configured_plugin.start(plugin_state).await?; log::info!("Smaug started"); plugin.join().await @@ -138,7 +221,6 @@ enum Commands { #[command(alias = "del", alias = "delete", alias = "remove")] Rm { /// Deterministic name (concatenated checksums) of wallet to delete - // #[arg(short, long)] descriptor_name: String, }, /// List descriptor wallets currently being watched @@ -205,23 +287,30 @@ async fn parse_command( } } -async fn add( - plugin: Plugin, - // v: serde_json::Value, - args: AddArgs, -) -> Result { +async fn add(plugin: Plugin, args: AddArgs) -> Result { let mut dw = DescriptorWallet::from_args(args, plugin.state().lock().await.network.clone()) .map_err(|e| anyhow!("error parsing args: {}", e))?; - // dw.network = ); log::trace!("params = {:?}", dw); - let wallet = dw - .fetch_wallet(plugin.state().lock().await.db_dir.clone()) + let (db_dir, brpc_host, brpc_port, brpc_user, brpc_pass) = { + let state = plugin.state().lock().await; + ( + state.db_dir.clone(), + // FIXME: actually use the RpcConnection struct instead of this nonsense + state.brpc_host.clone(), + state.brpc_port.clone(), + state.brpc_user.clone(), + state.brpc_pass.clone(), + ) + }; + let mut dw_clone = dw.clone(); + let wallet = dw_clone + .fetch_wallet(db_dir, brpc_host, brpc_port, brpc_user, brpc_pass) .await?; let bdk_transactions_iter = wallet.transactions(); - let mut transactions = Vec::::new(); + let mut transactions = Vec::>::new(); for bdk_transaction in bdk_transactions_iter { - log::trace!("BDK transaction = {:?}", bdk_transaction.node.tx); - transactions.push(wallet.get_tx(bdk_transaction.node.txid, true).unwrap()); + log::trace!("BDK transaction = {:?}", bdk_transaction.tx_node.tx); + transactions.push(bdk_transaction); } if transactions.len() > 0 { @@ -236,9 +325,12 @@ async fn add( log::debug!("no new txs this time"); } } + // FIXME: this is horrible, please find a better way to do this + dw.update_last_synced(dw_clone.last_synced.unwrap()); log::trace!("waiting for wallet lock"); plugin.state().lock().await.add_descriptor_wallet(&dw)?; + log::trace!("add_descriptor_wallet"); let wallets_str = json!(plugin.state().lock().await.wallets).to_string(); let rpc_file = plugin.configuration().rpc_file; let p = Path::new(&rpc_file); @@ -254,7 +346,7 @@ async fn add( })) .await .map_err(|e| anyhow!("Error calling listdatastore: {:?}", e))?; - log::trace!("wallet added"); + log::info!("wallet added"); let message = format!( "Wallet with deterministic name {} successfully added", &dw.get_name()? @@ -272,11 +364,10 @@ struct ListResponseItem { pub network: Option, } -async fn list( - plugin: Plugin, - // _v: serde_json::Value, -) -> Result { - let wallets = &plugin.state().lock().await.wallets; +async fn list(plugin: Plugin) -> Result { + let state = &plugin.state().lock().await; + + let wallets = state.wallets.clone(); let mut result = BTreeMap::::new(); for (wallet_name, wallet) in wallets { result.insert( @@ -295,7 +386,6 @@ async fn list( async fn delete( plugin: Plugin, - // v: serde_json::Value, descriptor_name: String, ) -> Result { let wallets = &mut plugin.state().lock().await.wallets; @@ -325,13 +415,19 @@ async fn delete( async fn block_added_handler(plugin: Plugin, v: serde_json::Value) -> Result<(), Error> { log::trace!("Got a block_added notification: {}", v); - log::trace!("Smaug state!!! {:?}", plugin.state().lock().await.wallets.clone()); - - - log::trace!("waiting for db_dir lock in block_handler"); - let db_dir = { + log::trace!( + "Smaug state!!! {:?}", + plugin.state().lock().await.wallets.clone() + ); + let (db_dir, brpc_host, brpc_port, brpc_user, brpc_pass) = { let state = plugin.state().lock().await; - state.db_dir.clone() + ( + state.db_dir.clone(), + state.brpc_host.clone(), + state.brpc_port.clone(), + state.brpc_user.clone(), + state.brpc_pass.clone(), + ) }; log::trace!("waiting for wallet lock in block_handler"); @@ -343,15 +439,23 @@ async fn block_added_handler(plugin: Plugin, v: serde_json::Value) -> Res for (_dw_desc, dw) in descriptor_wallets.iter_mut() { log::trace!("fetching wallet in block_handler: {:?}", dw); - let wallet = dw - .fetch_wallet(db_dir.clone()) - .await?; + let mut dw_clone = dw.clone(); + let wallet = dw_clone + .fetch_wallet( + db_dir.clone(), + brpc_host.clone(), + brpc_port.clone(), + brpc_user.clone(), + brpc_pass.clone(), + ) + .await?; + log::trace!("...fetched wallet in block_handler"); let bdk_transactions_iter = wallet.transactions(); - let mut transactions = Vec::::new(); + let mut transactions = Vec::>::new(); for bdk_transaction in bdk_transactions_iter { - log::trace!("BDK transaction = {:?}", bdk_transaction.node.tx); - transactions.push(wallet.get_tx(bdk_transaction.node.txid, true).unwrap()); + log::trace!("BDK transaction = {:?}", bdk_transaction.tx_node.tx); + transactions.push(bdk_transaction); } if transactions.len() > 0 { @@ -370,6 +474,10 @@ async fn block_added_handler(plugin: Plugin, v: serde_json::Value) -> Res } else { log::debug!("found no transactions"); } + log::debug!("scanned up to height {}", dw_clone.last_synced.unwrap()); + + // FIXME: this is horrible, please find a better way to do this + dw.update_last_synced(dw_clone.last_synced.unwrap()); } log::trace!("returning from block_added_handler"); Ok(()) diff --git a/src/state.rs b/src/state.rs index da72fe1..a54b8eb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,9 +11,17 @@ pub type State = Arc>; pub struct Smaug { /// A collection of descriptors the plugin is watching. pub wallets: BTreeMap, - // The network relevant to our wallets + /// The network relevant to our wallets pub network: String, - // The db path relevant to our wallets + /// Bitcoind RPC host + pub brpc_host: String, + /// Bitcoind RPC port + pub brpc_port: u16, + /// Bitcoind RPC user + pub brpc_user: String, + /// Bitcoind RPC password + pub brpc_pass: String, + /// The db path relevant to our wallets pub db_dir: PathBuf, } @@ -22,6 +30,10 @@ impl Smaug { Self { wallets: BTreeMap::new(), network: bitcoin::Network::Bitcoin.to_string(), + brpc_host: String::from("127.0.0.1"), + brpc_port: 8332, + brpc_user: String::from("bitcoin"), + brpc_pass: String::from("password"), db_dir: PathBuf::new(), } } @@ -30,6 +42,7 @@ impl Smaug { &mut self, wallet: &DescriptorWallet, ) -> Result<(), anyhow::Error> { + log::trace!("add_descriptor_wallet called"); self.wallets.insert(wallet.get_name()?, wallet.clone()); Ok(()) } diff --git a/src/wallet.rs b/src/wallet.rs index e216579..8e8fab7 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -2,25 +2,28 @@ use anyhow::anyhow; use bdk::{ bitcoin::{ secp256k1::{All, Secp256k1}, - Network, Txid, + Network, Transaction, Txid, }, - chain::{keychain::LocalChangeSet, ConfirmationTime, ConfirmationTimeAnchor}, + chain::{tx_graph::CanonicalTx, BlockId, ChainPosition, ConfirmationTimeAnchor}, wallet::wallet_name_from_descriptor, - KeychainKind, TransactionDetails, Wallet, + Wallet, }; -use bdk_esplora::{esplora_client, EsploraAsyncExt}; use bdk_file_store::Store; +use bitcoincore_rpc::{ + bitcoincore_rpc_json::{ + ScanBlocksOptions, ScanBlocksRequest, ScanBlocksRequestDescriptor, ScanBlocksResult, + }, + Auth, Client, RpcApi, +}; use clap::{command, Parser}; use cln_plugin::{Error, Plugin}; use serde::{Deserialize, Serialize}; use serde_json::json; -use std::{collections::BTreeMap, fmt, io::Write, path::PathBuf}; +use std::{collections::BTreeMap, fmt, path::PathBuf, time::Duration}; use crate::state::State; pub const SMAUG_DATADIR: &str = ".smaug"; -const STOP_GAP: usize = 50; -const PARALLEL_REQUESTS: usize = 5; pub const UTXO_DEPOSIT_TAG: &str = "utxo_deposit"; pub const UTXO_SPENT_TAG: &str = "utxo_spent"; @@ -35,6 +38,14 @@ pub enum WatchError { InvalidFormat(String), } +#[derive(Debug, Deserialize, Serialize, Clone)] +struct RpcConnection { + pub host: String, + pub port: u16, + pub user: String, + pub pass: String, +} + impl std::error::Error for WatchError {} impl std::fmt::Display for WatchError { @@ -105,9 +116,9 @@ pub struct DescriptorWallet { pub change_descriptor: Option, pub birthday: Option, pub gap: Option, - // pub last_synced: Option, + pub last_synced: Option, // #[serde(skip_serializing, skip_deserializing)] - pub transactions: BTreeMap, + pub transactions: BTreeMap, pub network: Option, } impl DescriptorWallet { @@ -142,6 +153,7 @@ impl DescriptorWallet { gap: args.gap, transactions: BTreeMap::new(), network: Some(network), + last_synced: None, }) } @@ -151,9 +163,9 @@ impl DescriptorWallet { change_descriptor: None, birthday: None, gap: None, - // last_synced: None, transactions: BTreeMap::new(), network: None, + last_synced: None, }) } @@ -207,23 +219,23 @@ impl DescriptorWallet { amount * 1000 } - // pub fn update_last_synced(&mut self, last_synced: BlockTime) { - // self.last_synced = Some(last_synced); - // } - - pub fn update_transactions( + pub fn update_transactions<'a>( &mut self, - transactions: Vec, - ) -> Vec { + transactions: Vec>, + ) -> Vec> { let mut new_txs = vec![]; for tx in transactions { - if !self.transactions.contains_key(&tx.txid) { + if !self.transactions.contains_key(&tx.tx_node.txid) { new_txs.push(tx.clone()); - self.transactions.insert(tx.txid, tx); + self.transactions + .insert(tx.tx_node.txid.clone(), tx.tx_node.tx.clone()); } } new_txs - // self.transactions = transactions; + } + + pub fn update_last_synced(&mut self, height: u32) { + self.last_synced = Some(height); } pub fn get_network(&self) -> Result { @@ -231,7 +243,9 @@ impl DescriptorWallet { } pub fn get_name(&self) -> Result { + log::trace!("get_name called"); let network = get_network(self.network.clone()); + log::trace!("get_network succeeded"); Ok(wallet_name_from_descriptor( &self.descriptor, self.change_descriptor.as_ref(), @@ -241,29 +255,19 @@ impl DescriptorWallet { } pub async fn fetch_wallet<'a>( - &self, + &mut self, db_dir: PathBuf, - ) -> Result>>, Error> - { + brpc_host: String, + brpc_port: u16, + brpc_user: String, + brpc_pass: String, + ) -> Result>, Error> { log::trace!("creating path"); let db_filename = self.get_name()?; - let db_path = db_dir - // .join(DATADIR) - .join(format!("{}.db", db_filename,)); + let db_path = db_dir.join(format!("{}.db", db_filename,)); log::trace!("searching for path: {:?}", db_path); let db = Store::::new_from_path(SMAUG_DATADIR.as_bytes(), db_path)?; log::trace!("db created!"); - // let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)"; - // mutinynet_descriptor = "wpkh(tprv8ZgxMBicQKsPdSAgthqLZ5ZWQkm5As4V3qNA5G8KKxGuqdaVVtBhytrUqRGPm4RxTktSdvch8JyUdfWR8g3ddrC49WfZnj4iGZN8y5L8NPZ/*)" - let _mutinynet_descriptor_ext = "wpkh(tprv8ZgxMBicQKsPdSAgthqLZ5ZWQkm5As4V3qNA5G8KKxGuqdaVVtBhytrUqRGPm4RxTktSdvch8JyUdfWR8g3ddrC49WfZnj4iGZN8y5L8NPZ/84'/0'/0'/0/*)"; - let _mutinynet_descriptor_int = "wpkh(tprv8ZgxMBicQKsPdSAgthqLZ5ZWQkm5As4V3qNA5G8KKxGuqdaVVtBhytrUqRGPm4RxTktSdvch8JyUdfWR8g3ddrC49WfZnj4iGZN8y5L8NPZ/84'/0'/0'/1/*)"; - let _mutinynet_descriptor_ext_2 = "wpkh(tprv8ZgxMBicQKsPeRye8MhHA8hLxMuomycmGYXyRs7zViNck2VJsCJMTPt81Que8qp3PyPgQRnN7Gb1JyBVBKgj8AKEoEmmYxYDwzZJ63q1yjA/84'/0'/0'/0/*)"; - let _mutinynet_descriptor_int_2 = "wpkh(tprv8ZgxMBicQKsPeRye8MhHA8hLxMuomycmGYXyRs7zViNck2VJsCJMTPt81Que8qp3PyPgQRnN7Gb1JyBVBKgj8AKEoEmmYxYDwzZJ63q1yjA/84'/0'/0'/1/*)"; - // let external_descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; - // let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/1/*)"; - - // let external_descriptor = mutinynet_descriptor_ext; - // let internal_descriptor = mutinynet_descriptor_int; let external_descriptor = self.descriptor.clone(); let internal_descriptor = self.change_descriptor.clone(); let mut wallet = Wallet::new( @@ -279,47 +283,65 @@ impl DescriptorWallet { log::trace!("Syncing..."); log::debug!("using network: {}", json!(self.network).as_str().unwrap()); - log::debug!( - "using esplora url: {}", - get_network_url(json!(self.network).as_str().unwrap()).as_str() - ); - let client = - // esplora_client::Builder::new("https://blockstream.info/testnet/api").build_async()?; - esplora_client::Builder::new( - get_network_url( - json!(self.network).as_str().unwrap() - ).as_str() - ).build_async()?; - - let local_chain = wallet.checkpoints(); - let keychain_spks = wallet - .spks_of_all_keychains() - .into_iter() - .map(|(k, k_spks)| { - let mut once = Some(()); - let mut stdout = std::io::stdout(); - let k_spks = k_spks - .inspect(move |(spk_i, _)| match once.take() { - Some(_) => log::debug!("\nScanning keychain [{:?}]", k), - None => log::trace!(" {:<3}", spk_i), - }) - .inspect(move |_| stdout.flush().expect("must flush")); - (k, k_spks) - }) - .collect(); - log::trace!("Finished scanning"); - let update = client - .scan( - local_chain, - keychain_spks, - [], - [], - STOP_GAP, - PARALLEL_REQUESTS, - ) - .await?; - wallet.apply_update(update)?; - wallet.commit()?; + + let rpc_client = Client::new_with_timeout( + &format!("http://{}:{}", brpc_host.clone(), brpc_port.clone()), + Auth::UserPass(brpc_user.clone(), brpc_pass.clone()), // Auth::CookieFile(PathBuf::from("/home/cguida/.bitcoin/regtest/.cookie")) + Duration::from_secs(3600), + )?; + + let external_descriptor = ScanBlocksRequestDescriptor::Extended { + desc: external_descriptor.to_string(), + range: None, + }; + let mut descriptors_vec = vec![external_descriptor]; + + if let Some(id) = internal_descriptor { + descriptors_vec.push(ScanBlocksRequestDescriptor::Extended { + desc: id.to_string(), + range: None, + }); + } + + wallet.set_lookahead_for_all(20)?; + + log::info!("last_synced = {:?}", self.last_synced); + let start_height: Option = match self.last_synced { + Some(ct) => Some(ct.into()), + None => None, + }; + + let descriptors = &descriptors_vec[..]; + let request = ScanBlocksRequest { + scanobjects: descriptors, + start_height, + stop_height: None, + filtertype: None, + options: Some(ScanBlocksOptions { + filter_false_positives: Some(true), + }), + }; + let res: ScanBlocksResult = rpc_client.scan_blocks_blocking(request)?; + log::trace!("scanblocks result: {:?}", res); + log::trace!("wallet = {:?}", wallet); + + let chain_tip = wallet.latest_checkpoint(); + let mut prev_block_id = match chain_tip { + Some(ct) => Some(ct.block_id()), + None => None, + }; + + for bh in res.relevant_blocks { + let block = rpc_client.get_block(&bh)?; + let height: u32 = block.bip34_block_height()?.try_into().unwrap(); + wallet.apply_block_relevant(block.clone(), prev_block_id, height)?; + wallet.commit()?; + prev_block_id = Some(BlockId { height, hash: bh }); + } + + self.update_last_synced(res.to_height.try_into().unwrap()); + + log::debug!("last_synced after scan = {:?}", self.last_synced); let balance = wallet.get_balance(); log::trace!("Wallet balance after syncing: {} sats", balance.total()); @@ -330,104 +352,88 @@ impl DescriptorWallet { async fn spend_tx_notify<'a>( &self, plugin: &Plugin, - wallet: &Wallet>>, - tx: &TransactionDetails, + wallet: &Wallet>, + tx: &CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>, ) -> Result<(), Error> { - match tx.transaction.clone() { - Some(t) => { - // send spent notification for each input - for input in t.input.iter() { - if let Some(po) = wallet.tx_graph().get_txout(input.previous_output) { - match tx.confirmation_time { - ConfirmationTime::Unconfirmed { .. } => { - continue; - } - ConfirmationTime::Confirmed { height, time } => { - let acct = format!("smaug:{}", self.get_name()?); - let amount = po.value; - let outpoint = format!("{}", input.previous_output.to_string()); - log::trace!("outpoint = {}", format!("{}", outpoint)); - let onchain_spend = json!({UTXO_SPENT_TAG: { - "account": acct, - "outpoint": outpoint, - "spending_txid": tx.txid.to_string(), - "amount_msat": Self::sats_to_msats(amount), - "coin_type": "bcrt", - "timestamp": format!("{}", time), - "blockheight": format!("{}", height), - }}); - log::trace!("INSIDE SEND SPEND NOTIFICATION ON SMAUG SIDE"); - let cloned_plugin = plugin.clone(); - tokio::spawn(async move { - if let Err(e) = cloned_plugin - .send_custom_notification( - UTXO_SPENT_TAG.to_string(), - onchain_spend, - ) - .await - { - log::error!("Error sending custom notification: {:?}", e); - } - }); + // send spent notification for each input + for input in tx.tx_node.tx.input.iter() { + if let Some(po) = wallet.tx_graph().get_txout(input.previous_output) { + match tx.chain_position { + ChainPosition::Unconfirmed(_) => { + continue; + } + ChainPosition::Confirmed(a) => { + let acct = format!("smaug:{}", self.get_name()?); + let amount = po.value; + let outpoint = format!("{}", input.previous_output.to_string()); + log::trace!("outpoint = {}", format!("{}", outpoint)); + let onchain_spend = json!({UTXO_SPENT_TAG: { + "account": acct, + "outpoint": outpoint, + "spending_txid": tx.tx_node.txid, + "amount_msat": Self::sats_to_msats(amount), + "coin_type": "bcrt", + "timestamp": format!("{}", a.confirmation_time), + "blockheight": format!("{}", a.confirmation_height), + }}); + log::trace!("INSIDE SEND SPEND NOTIFICATION ON SMAUG SIDE"); + let cloned_plugin = plugin.clone(); + tokio::spawn(async move { + if let Err(e) = cloned_plugin + .send_custom_notification(UTXO_SPENT_TAG.to_string(), onchain_spend) + .await + { + log::error!("Error sending custom notification: {:?}", e); } - } - } else { - log::trace!("Transaction prevout not found"); + }); } } + } else { + log::trace!("Transaction prevout not found"); + } + } - // send deposit notification for every output, since all of them are spends from our wallet - for (vout, output) in t.output.iter().enumerate() { - match tx.confirmation_time { - ConfirmationTime::Unconfirmed { .. } => { - continue; - } - ConfirmationTime::Confirmed { height, time } => { - let acct: String; - let transfer_from: String; - if wallet.is_mine(&output.script_pubkey) { - acct = format!("smaug:{}", self.get_name()?); - transfer_from = "external".to_owned(); - } else { - transfer_from = format!("smaug:{}", self.get_name()?); - acct = "external".to_owned(); - } - let amount = output.value; - let outpoint = format!("{}:{}", tx.txid.to_string(), vout.to_string()); - log::trace!( - "outpoint = {}", - format!("{}:{}", tx.txid.to_string(), vout.to_string()) - ); - let onchain_deposit = json!({UTXO_DEPOSIT_TAG:{ - "account": acct, - "transfer_from": transfer_from, - "outpoint": outpoint, - "spending_txid": tx.txid.to_string(), - "amount_msat": Self::sats_to_msats(amount), - "coin_type": "bcrt", - "timestamp": format!("{}", time), - "blockheight": format!("{}", height), - }}); - log::trace!("INSIDE SEND DEPOSIT NOTIFICATION ON SMAUG SIDE"); - let cloned_plugin = plugin.clone(); - tokio::spawn(async move { - if let Err(e) = cloned_plugin - .send_custom_notification( - UTXO_DEPOSIT_TAG.to_string(), - onchain_deposit, - ) - .await - { - log::error!("Error sending custom notification: {:?}", e); - } - }); - } + // send deposit notification for every output, since all of them are spends from our wallet + for (vout, output) in tx.tx_node.tx.output.iter().enumerate() { + match tx.chain_position { + ChainPosition::Unconfirmed(_) => { + continue; + } + ChainPosition::Confirmed(a) => { + let acct: String; + let transfer_from: String; + if wallet.is_mine(&output.script_pubkey) { + acct = format!("smaug:{}", self.get_name()?); + transfer_from = "external".to_owned(); + } else { + transfer_from = format!("smaug:{}", self.get_name()?); + acct = "external".to_owned(); } + let amount = output.value; + let outpoint = format!("{}:{}", tx.tx_node.txid.to_string(), vout.to_string()); + log::trace!("outpoint = {}", format!("{}:{}", tx.tx_node.txid, vout)); + let onchain_deposit = json!({UTXO_DEPOSIT_TAG:{ + "account": acct, + "transfer_from": transfer_from, + "outpoint": outpoint, + "spending_txid": tx.tx_node.txid, + "amount_msat": Self::sats_to_msats(amount), + "coin_type": "bcrt", + "timestamp": format!("{}", a.confirmation_time), + "blockheight": format!("{}", a.confirmation_height), + }}); + log::trace!("INSIDE SEND DEPOSIT NOTIFICATION ON SMAUG SIDE"); + let cloned_plugin = plugin.clone(); + tokio::spawn(async move { + if let Err(e) = cloned_plugin + .send_custom_notification(UTXO_DEPOSIT_TAG.to_string(), onchain_deposit) + .await + { + log::error!("Error sending custom notification: {:?}", e); + } + }); } } - None => { - log::debug!("TransactionDetails is missing a Transaction"); - } } Ok(()) } @@ -438,69 +444,56 @@ impl DescriptorWallet { async fn receive_tx_notify<'a>( &self, plugin: &Plugin, - wallet: &Wallet>>, - tx: &TransactionDetails, + wallet: &Wallet>, + tx: &CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>, ) -> Result<(), Error> { - match tx.transaction.clone() { - Some(t) => { - for (vout, output) in t.output.iter().enumerate() { - if wallet.is_mine(&output.script_pubkey) { - match tx.confirmation_time { - ConfirmationTime::Unconfirmed { .. } => { - continue; - } - ConfirmationTime::Confirmed { height, time } => { - let acct: String; - let transfer_from: String; - if wallet.is_mine(&output.script_pubkey) { - acct = format!("smaug:{}", self.get_name()?); - transfer_from = "external".to_owned(); - } else { - // transfer_from = format!( - // "smaug:{}", - // self.get_name? - // ); - // acct = "external".to_owned(); - continue; - } - let amount = output.value; - let outpoint = - format!("{}:{}", tx.txid.to_string(), vout.to_string()); - log::trace!( - "outpoint = {}", - format!("{}:{}", tx.txid.to_string(), vout.to_string()) - ); - let onchain_deposit = json!({UTXO_DEPOSIT_TAG: { - "account": acct, - "transfer_from": transfer_from, - "outpoint": outpoint, - "spending_txid": tx.txid.to_string(), - "amount_msat": Self::sats_to_msats(amount), - "coin_type": "bcrt", - "timestamp": format!("{}", time), - "blockheight": format!("{}", height), - }}); - log::trace!("INSIDE SEND DEPOSIT NOTIFICATION ON SMAUG SIDE"); - let cloned_plugin = plugin.clone(); - tokio::spawn(async move { - if let Err(e) = cloned_plugin - .send_custom_notification( - UTXO_DEPOSIT_TAG.to_string(), - onchain_deposit, - ) - .await - { - log::error!("Error sending custom notification: {:?}", e); - } - }); - } + for (vout, output) in tx.tx_node.tx.output.iter().enumerate() { + if wallet.is_mine(&output.script_pubkey) { + match tx.chain_position { + ChainPosition::Unconfirmed(_) => { + continue; + } + ChainPosition::Confirmed(a) => { + let acct: String; + let transfer_from: String; + if wallet.is_mine(&output.script_pubkey) { + acct = format!("smaug:{}", self.get_name()?); + transfer_from = "external".to_owned(); + } else { + continue; } + let amount = output.value; + let outpoint = format!("{}:{}", tx.tx_node.txid, vout); + log::trace!( + "outpoint = {}", + format!("{}:{}", tx.tx_node.txid.to_string(), vout.to_string()) + ); + let onchain_deposit = json!({UTXO_DEPOSIT_TAG: { + "account": acct, + "transfer_from": transfer_from, + "outpoint": outpoint, + "spending_txid": tx.tx_node.txid.to_string(), + "amount_msat": Self::sats_to_msats(amount), + "coin_type": "bcrt", + "timestamp": format!("{}", a.confirmation_time), + "blockheight": format!("{}", a.confirmation_height), + }}); + log::trace!("INSIDE SEND DEPOSIT NOTIFICATION ON SMAUG SIDE"); + let cloned_plugin = plugin.clone(); + tokio::spawn(async move { + if let Err(e) = cloned_plugin + .send_custom_notification( + UTXO_DEPOSIT_TAG.to_string(), + onchain_deposit, + ) + .await + { + log::error!("Error sending custom notification: {:?}", e); + } + }); } } } - None => { - log::debug!("TransactionDetails is missing a Transaction"); - } } Ok(()) } @@ -512,99 +505,37 @@ impl DescriptorWallet { async fn shared_tx_notify<'a>( &self, plugin: &Plugin, - wallet: &Wallet>>, - tx: &TransactionDetails, + wallet: &Wallet>, + tx: &CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>, ) -> Result<(), Error> { - match tx.transaction.clone() { - Some(t) => { - // send spent notification for each input that spends one of our outputs - for input in t.input.iter() { - if let Some(po) = wallet.tx_graph().get_txout(input.previous_output) { - match tx.confirmation_time { - ConfirmationTime::Unconfirmed { .. } => { - continue; - } - ConfirmationTime::Confirmed { height, time } => { - if wallet.is_mine(&po.script_pubkey) { - let acct = format!("smaug:{}", self.get_name()?); - let amount = po.value; - let outpoint = format!("{}", input.previous_output.to_string()); - log::trace!("outpoint = {}", format!("{}", outpoint)); - let onchain_spend = json!({UTXO_SPENT_TAG: { - "account": acct, - "outpoint": outpoint, - "spending_txid": tx.txid.to_string(), - "amount_msat": Self::sats_to_msats(amount), - "coin_type": "bcrt", - "timestamp": format!("{}", time), - "blockheight": format!("{}", height), - }}); - log::trace!("INSIDE SEND SPEND NOTIFICATION ON SMAUG SIDE"); - let cloned_plugin = plugin.clone(); - tokio::spawn(async move { - if let Err(e) = cloned_plugin - .send_custom_notification( - UTXO_SPENT_TAG.to_string(), - onchain_spend, - ) - .await - { - log::error!( - "Error sending custom notification: {:?}", - e - ); - } - }); - } - } - } - } else { - log::debug!("Transaction prevout not found"); + for input in tx.tx_node.input.iter() { + if let Some(po) = wallet.tx_graph().get_txout(input.previous_output) { + match tx.chain_position { + ChainPosition::Unconfirmed(_) => { + continue; } - } - - // send deposit notification for every output, since all of them *might be* spends from our wallet. - // store them in a temp account and let the user update later as needed. - for (vout, output) in t.output.iter().enumerate() { - match tx.confirmation_time { - ConfirmationTime::Unconfirmed { .. } => { - continue; - } - ConfirmationTime::Confirmed { height, time } => { - let acct: String; - let transfer_from: String; - let our_acct = format!("smaug:{}:shared_outputs", self.get_name()?); - let ext_acct = "external".to_owned(); - if wallet.is_mine(&output.script_pubkey) { - acct = our_acct; - transfer_from = ext_acct; - } else { - acct = ext_acct; - transfer_from = our_acct; - } - let amount = output.value; - let outpoint = format!("{}:{}", tx.txid.to_string(), vout.to_string()); - log::trace!( - "outpoint = {}", - format!("{}:{}", tx.txid.to_string(), vout.to_string()) - ); - let onchain_deposit = json!({UTXO_DEPOSIT_TAG: { - "account": acct, - "transfer_from": transfer_from, - "outpoint": outpoint, - "spending_txid": tx.txid.to_string(), - "amount_msat": Self::sats_to_msats(amount), - "coin_type": "bcrt", - "timestamp": format!("{}", time), - "blockheight": format!("{}", height), + ChainPosition::Confirmed(a) => { + if wallet.is_mine(&po.script_pubkey) { + let acct = format!("smaug:{}", self.get_name()?); + let amount = po.value; + let outpoint = format!("{}", input.previous_output.to_string()); + log::trace!("outpoint = {}", format!("{}", outpoint)); + let onchain_spend = json!({UTXO_SPENT_TAG: { + "account": acct, + "outpoint": outpoint, + "spending_txid": tx.tx_node.txid.to_string(), + "amount_msat": Self::sats_to_msats(amount), + "coin_type": "bcrt", + "timestamp": format!("{}", a.confirmation_time), + "blockheight": format!("{}", a.confirmation_height), }}); - log::trace!("INSIDE SEND DEPOSIT NOTIFICATION ON SMAUG SIDE"); + log::trace!("INSIDE SEND SPEND NOTIFICATION ON SMAUG SIDE"); let cloned_plugin = plugin.clone(); tokio::spawn(async move { if let Err(e) = cloned_plugin .send_custom_notification( - UTXO_DEPOSIT_TAG.to_string(), - onchain_deposit, + UTXO_SPENT_TAG.to_string(), + onchain_spend, ) .await { @@ -614,9 +545,54 @@ impl DescriptorWallet { } } } + } else { + log::debug!("Transaction prevout not found"); } - None => { - log::debug!("TransactionDetails is missing a Transaction"); + } + + // send deposit notification for every output, since all of them *might be* spends from our wallet. + // store them in a temp account and let the user update later as needed. + for (vout, output) in tx.tx_node.tx.output.iter().enumerate() { + match tx.chain_position { + ChainPosition::Unconfirmed(_) => { + continue; + } + ChainPosition::Confirmed(a) => { + let acct: String; + let transfer_from: String; + let our_acct = format!("smaug:{}:shared_outputs", self.get_name()?); + let ext_acct = "external".to_owned(); + if wallet.is_mine(&output.script_pubkey) { + acct = our_acct; + transfer_from = ext_acct; + } else { + acct = ext_acct; + transfer_from = our_acct; + } + let amount = output.value; + let outpoint = format!("{}:{}", tx.tx_node.txid, vout); + log::trace!("outpoint = {}", format!("{}:{}", tx.tx_node.txid, vout)); + let onchain_deposit = json!({UTXO_DEPOSIT_TAG: { + "account": acct, + "transfer_from": transfer_from, + "outpoint": outpoint, + "spending_txid": tx.tx_node.txid, + "amount_msat": Self::sats_to_msats(amount), + "coin_type": "bcrt", + "timestamp": format!("{}", a.confirmation_time), + "blockheight": format!("{}", a.confirmation_height), + }}); + log::trace!("INSIDE SEND DEPOSIT NOTIFICATION ON SMAUG SIDE"); + let cloned_plugin = plugin.clone(); + tokio::spawn(async move { + if let Err(e) = cloned_plugin + .send_custom_notification(UTXO_DEPOSIT_TAG.to_string(), onchain_deposit) + .await + { + log::error!("Error sending custom notification: {:?}", e); + } + }); + } } } Ok(()) @@ -625,12 +601,12 @@ impl DescriptorWallet { pub async fn send_notifications_for_tx<'a>( &self, plugin: &Plugin, - wallet: &Wallet>>, - tx: TransactionDetails, + wallet: &Wallet>, + tx: CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>, ) -> Result<(), Error> { - log::debug!("sending notifs for txid/tx: {:?} {:?}", tx.txid, tx); + log::debug!("sending notifs for txid/tx: {:?} {:?}", tx.tx_node.txid, tx); // we own all inputs - if tx.clone().transaction.unwrap().input.iter().all(|x| { + if tx.clone().tx_node.tx.input.iter().all(|x| { match wallet.tx_graph().get_txout(x.previous_output) { Some(o) => { log::trace!( @@ -650,7 +626,7 @@ impl DescriptorWallet { self.spend_tx_notify(plugin, wallet, &tx).await?; } else // we own no inputs - if !tx.clone().transaction.unwrap().input.iter().any(|x| { + if !tx.clone().tx_node.tx.input.iter().any(|x| { match wallet.tx_graph().get_txout(x.previous_output) { Some(o) => { log::trace!( @@ -674,14 +650,6 @@ impl DescriptorWallet { log::debug!("sending shared notif"); self.shared_tx_notify(plugin, wallet, &tx).await?; } - - // if tx.sent > 0 { - - // } - - // if tx.received > 0 { - - // } Ok(()) } } @@ -697,10 +665,8 @@ impl TryFrom for DescriptorWallet { let param_count = a.len(); match param_count { - // 1 => DescriptorWallet::try_from(a.pop().unwrap()), 1..=4 => { let descriptor = a.get(0).unwrap().as_str().ok_or_else(|| WatchError::InvalidDescriptor("descriptor must be a string".to_string()))?; - // let change_descriptor = Some(a.get(1).unwrap().as_str().ok_or_else(|| WatchError::InvalidChangeDescriptor("change_descriptor must be a string".to_string()))?); log::trace!("try_from array: change_descriptor = {:?}", a.get(1)); let change_descriptor = if let Some(cd) = a.get(1) { Some(cd.as_str().ok_or_else(|| WatchError::InvalidChangeDescriptor(format!("change_descriptor must be a string. Received: {cd}")))?)