diff --git a/Cargo.lock b/Cargo.lock index f7dec06..4e391df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -55,15 +55,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "argon2" @@ -114,9 +114,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert_cmd" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" +checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" dependencies = [ "anstyle", "bstr", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "bdk_chain" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5d691fd092aacec7e05046b7d04897d58d6d65ed3152cb6cf65dababcfabed" +checksum = "c290eff038799a8ac0c5a82b6160a9ca456baa299a6f22b262c771342d2846c0" dependencies = [ "bdk_core", "bitcoin", @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "bdk_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dbbe4aad0c898bfeb5253c222be3ea3dccfb380a07e72c87e3e4ed6664a6753" +checksum = "cb3028782f6bf14a6df987244333d34e6b272b5a40a53e4879ec2dfd82275a3a" dependencies = [ "bitcoin", "hashbrown 0.14.5", @@ -523,9 +523,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -561,9 +561,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "shlex", @@ -583,9 +583,9 @@ checksum = "4b4b0fc281743d80256607bd65e8beedc42cb0787ea119c85b81b4c0eab85e5f" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "num-traits", @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.59" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -605,9 +605,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -617,9 +617,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -629,15 +629,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "const_format" @@ -754,9 +754,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -764,11 +764,10 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", @@ -778,33 +777,20 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", "syn", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -887,7 +873,7 @@ dependencies = [ "byteorder", "libc", "log", - "rustls 0.23.36", + "rustls 0.23.37", "serde", "serde_json", "webpki-roots", @@ -982,6 +968,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -998,6 +999,17 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.32" @@ -1033,6 +1045,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1072,19 +1085,19 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -1160,12 +1173,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -1263,9 +1270,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1278,7 +1285,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1393,15 +1399,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "once_cell", "wasm-bindgen", @@ -1433,19 +1439,20 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "bitflags 2.11.0", "libc", - "redox_syscall 0.7.1", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -1461,9 +1468,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "lock_api" @@ -1581,9 +1588,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -1649,9 +1656,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -1662,21 +1669,11 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1732,18 +1729,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -1752,9 +1749,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -1768,6 +1765,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "polonius-the-crab" version = "0.5.0" @@ -1927,9 +1930,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ "bitflags 2.11.0", "memchr", @@ -1947,9 +1950,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1960,6 +1963,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -2030,9 +2039,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ "bitflags 2.11.0", ] @@ -2082,9 +2091,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "ring" @@ -2150,9 +2159,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.11.0", "errno", @@ -2175,15 +2184,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.9", + "rustls-webpki 0.103.10", "subtle", "zeroize", ] @@ -2209,9 +2218,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", @@ -2224,6 +2233,22 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "saa" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c7f49c9d5caa3bf4b3106900484b447b9253fe99670ceb81cb6cb5027855e1" + +[[package]] +name = "scc" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f448f7d881535036452f0cac656a41463807f095eda504890764ca7d11e2a2ea" +dependencies = [ + "saa", + "sdd", +] + [[package]] name = "schemars" version = "0.9.0" @@ -2264,6 +2289,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sdd" +version = "4.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ca0e33fc1ae39e36b2d1fdfc8ee470b26397b642ff87572a59a36ff4f2340b" + [[package]] name = "secp" version = "0.6.0" @@ -2369,9 +2400,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", @@ -2388,9 +2419,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ "darling", "proc-macro2", @@ -2436,18 +2467,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simple-semaphore" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0683532a4eb6f0376118e2c48cbc87e808a9955f1675236a53dbd2cf1d261b7d" -dependencies = [ - "num_cpus", -] +checksum = "373fc06169acd9b556ee8cbacdf2237dd10e83d725caba1333b967b15628d73b" [[package]] name = "slab" @@ -2463,12 +2491,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2485,9 +2513,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2502,9 +2530,9 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "tar" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" dependencies = [ "filetime", "libc", @@ -2513,12 +2541,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2633,9 +2661,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -2648,9 +2676,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -2665,9 +2693,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -2701,9 +2729,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", @@ -2730,9 +2758,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e" +checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" dependencies = [ "prettyplease", "proc-macro2", @@ -2742,9 +2770,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", "prost", @@ -2753,9 +2781,9 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e" +checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" dependencies = [ "prettyplease", "proc-macro2", @@ -2843,9 +2871,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -2935,11 +2963,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -3029,9 +3057,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -3042,9 +3070,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3052,9 +3080,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -3065,9 +3093,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -3208,16 +3236,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -3235,31 +3254,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -3268,96 +3270,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -3464,18 +3418,18 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -3490,25 +3444,22 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zeromq" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a4528179201f6eecf211961a7d3276faa61554c82651ecc66387f68fc3004bd" +checksum = "b32e1e46c4e278efd0c1153f2db0113924b9bc5fff9f9221853d035e2d26fadf" dependencies = [ "async-trait", "asynchronous-codec", "bytes", "crossbeam-queue", - "dashmap", - "futures-channel", - "futures-io", - "futures-task", - "futures-util", + "futures", "log", "num-traits", "once_cell", "parking_lot", - "rand 0.8.5", + "rand 0.9.2", "regex", + "scc", "thiserror 1.0.69", "tokio", "tokio-util", diff --git a/Cargo.toml b/Cargo.toml index e48fff0..dbac0ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,9 @@ [workspace] resolver = "3" members = ["protocol", "rpc", "wallet", "testenv", "bmp_tracing", "mem"] -exclude = ["poc"] [workspace.dependencies] -anyhow = "1.0.101" +anyhow = "1.0.102" argon2 = { version = "0.5.3", default-features = false } base64 = "0.22.1" bdk_bitcoind_rpc = "0.22.0" @@ -20,13 +19,14 @@ rand = "0.9.2" rand_chacha = "0.9.0" rusqlite = { version = "0.31.0", features = ["bundled-sqlcipher"] } secp = "0.6.0" -simple-semaphore = "0.2.0" -tempfile = "3.25.0" +simple-semaphore = "1.0.0" +tempfile = "3.27.0" thiserror = "2.0.18" -tokio = "1.49.0" +tokio = "1.50.0" +tokio-stream = "0.1.18" tracing = "0.1.44" -tracing-core = { version = "0.1.35", default-features = false } -tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } +tracing-core = { version = "0.1.36", default-features = false } +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } zeroize = "1.8.2" [workspace.lints.rust] diff --git a/bmp_tracing/src/lib.rs b/bmp_tracing/src/lib.rs index df4839c..4054a40 100644 --- a/bmp_tracing/src/lib.rs +++ b/bmp_tracing/src/lib.rs @@ -1,15 +1,14 @@ -pub use tracing; -pub use tracing_subscriber; - use std::error::Error as _; use std::fs::File; use std::io; use std::path::PathBuf; use tracing_subscriber::filter::EnvFilter; -use tracing_subscriber::{ - Layer, fmt, layer::SubscriberExt as _, registry::LookupSpan, util::SubscriberInitExt as _, -}; +use tracing_subscriber::layer::SubscriberExt as _; +use tracing_subscriber::registry::LookupSpan; +use tracing_subscriber::util::SubscriberInitExt as _; +use tracing_subscriber::{Layer, fmt}; +pub use {tracing, tracing_subscriber}; #[derive(Debug, Clone)] #[expect(clippy::exhaustive_enums)] diff --git a/mem/Cargo.toml b/mem/Cargo.toml index 2dc5a96..f3ecf95 100644 --- a/mem/Cargo.toml +++ b/mem/Cargo.toml @@ -5,11 +5,11 @@ edition = "2024" [dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } -zeromq = { version = "0.4", default-features = false, features = ["tokio-runtime", "tcp-transport"] } +zeromq = { version = "0.5", default-features = false, features = ["tokio-runtime", "tcp-transport"] } anyhow = { workspace = true } bdk_wallet = { workspace = true } hex = { workspace = true } -tokio-stream = "0.1.18" +tokio-stream = { workspace = true } [dev-dependencies] testenv = { path = "../testenv" } diff --git a/mem/src/lib.rs b/mem/src/lib.rs index 96e8819..0f97120 100644 --- a/mem/src/lib.rs +++ b/mem/src/lib.rs @@ -8,9 +8,7 @@ use zeromq::{Socket as _, SocketRecv as _, SubSocket}; pub async fn stream_unconfirmed_tx(zmq_connect: &str) -> ReceiverStream { // Subscribe to raw transactions via ZMQ let mut sub = SubSocket::new(); - sub.connect(zmq_connect) - .await - .expect("zmq connect"); + sub.connect(zmq_connect).await.expect("zmq connect"); sub.subscribe("rawtx").await.expect("zmq subscribe"); // Small delay to let the subscription propagate @@ -21,13 +19,12 @@ pub async fn stream_unconfirmed_tx(zmq_connect: &str) -> ReceiverStream Result<()> { let address = env.new_address()?; let amount = Amount::from_sat(50_000); let txid = env.fund_address(&address, amount)?; - env.debug_tx(txid); // output link to inspect the transaction iff esplorer is running. + env.debug_tx(txid); // output link to inspect the transaction iff esplora is running. let zmq_tx = tokio::time::timeout( Duration::from_secs(5), diff --git a/protocol/src/protocol_musig_adaptor.rs b/protocol/src/protocol_musig_adaptor.rs index 6f0f245..39748e6 100644 --- a/protocol/src/protocol_musig_adaptor.rs +++ b/protocol/src/protocol_musig_adaptor.rs @@ -6,8 +6,8 @@ use bdk_wallet::bitcoin::{ }; use musig2::secp::{MaybeScalar, Point}; use musig2::{PartialSignature, PubNonce}; - use wallet::protocol_wallet_api::MemWallet; + use crate::multisig::{KeyCtx, SigCtx}; use crate::receiver::{Receiver, ReceiverList}; use crate::transaction::{ @@ -67,10 +67,10 @@ pub struct Round2Parameter { #[derive(Debug)] pub struct Round3Parameter { // DepositTx -------- - pub deposit_txid: Txid, // only for verification / fast fail + // only for verification / fast fail + pub deposit_txid: Txid, // SwapTx -------------- // aggregated adaptive signature for SwapTx, - pub swap_part_sig: PartialSignature, pub p_part_peer: PartialSignature, pub q_part_peer: PartialSignature, @@ -314,7 +314,8 @@ impl BMPProtocol { /// can raise the fees with CPFP to get it mined before `ClaimTx` can be broadcast. /// /// `RedirectTx` Bob spends from `WarningTx` Alice, that's important. -/// Sending funds to the DAO is done by having a list of addresses (from contributors) and percentages. (must add up to 100%) +/// Sending funds to the DAO is done by having a list of addresses (from contributors) and +/// percentages. (must add up to 100%) #[derive(Default)] pub struct RedirectTx { pub sig: SigCtx, @@ -544,8 +545,8 @@ impl SwapTx { self.swap_spend.clone() } - /// even though only the seller gets a `SwapTx` transaction, both parties are constructing the transaction - /// and only the buyer will send the seller the signature. + /// even though only the seller gets a `SwapTx` transaction, both parties are constructing the + /// transaction and only the buyer will send the seller the signature. fn new(role: ProtocolRole) -> Self { Self { role, @@ -655,11 +656,11 @@ impl DepositTx { let psbt = if ctx.am_buyer() { self.builder - .init_buyers_half_psbt(&mut ctx.funds, &mut rand::rng())? + .init_buyers_half_psbt(&mut ctx.funds, &mut rand::rng())? .buyers_half_psbt()? } else { self.builder - .init_sellers_half_psbt(&mut ctx.funds, &mut rand::rng())? + .init_sellers_half_psbt(&mut ctx.funds, &mut rand::rng())? .sellers_half_psbt()? }; Ok(psbt.clone()) @@ -687,8 +688,10 @@ impl DepositTx { Ok(()) } - fn transfer_sig_and_broadcast(&mut self, ctx: &mut BMPContext, - psbt_bob: Psbt, // bobs psbt should be same as mine but have bob's sig + fn transfer_sig_and_broadcast( + &mut self, + ctx: &mut BMPContext, + psbt_bob: Psbt, // bobs psbt should be same as mine but have bob's sig ) -> anyhow::Result { self.builder.combine_psbts(psbt_bob)?; diff --git a/protocol/src/psbt.rs b/protocol/src/psbt.rs index c44e9e0..b3dd7a8 100644 --- a/protocol/src/psbt.rs +++ b/protocol/src/psbt.rs @@ -1,4 +1,5 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::mem; use std::sync::LazyLock; use bdk_wallet::bitcoin::amount::CheckedSum as _; @@ -10,6 +11,7 @@ use bdk_wallet::bitcoin::{ Address, Amount, FeeRate, Network, OutPoint, Psbt, ScriptBuf, Sequence, TapSighashType, Transaction, TxIn, TxOut, Weight, Witness, XOnlyPublicKey, absolute, psbt, script, secp256k1, }; +use bdk_wallet::miniscript::psbt::PsbtExt as _; use bdk_wallet::miniscript::{Descriptor, ToPublicKey as _}; use bdk_wallet::{KeychainKind, SignOptions, TxOrdering, Wallet}; use rand::{RngCore, SeedableRng as _}; @@ -54,11 +56,10 @@ struct MockTradeWallet, As: Iterator> new_addresses: As, signature_map: BTreeMap, internal_key: Option, + script_sigs: BTreeMap>, } -impl, As: Iterator> TradeWallet - for MockTradeWallet -{ +impl, As: Iterator> TradeWallet for MockTradeWallet { fn network(&self) -> Network { Network::Regtest } fn new_address(&mut self) -> Result
{ @@ -140,12 +141,38 @@ impl, As: Iterator> TradeWallet } fn sign_selected_inputs(&self, psbt: &mut Psbt, is_selected: &dyn Fn(&OutPoint) -> bool) -> Result<()> { + let mut script_sigs = self.script_sigs.clone(); + for (input, TxIn { previous_output, .. }) in psbt.inputs.iter_mut().zip(&psbt.unsigned_tx.input) { if is_selected(previous_output) { - let signature = self.signature_map.get(previous_output) - .ok_or(TransactionErrorKind::MissingSignature)?; - input.final_script_witness = Some(Witness::p2tr_key_spend(signature)); + for (key, (leaf_hashes, _)) in &input.tap_key_origins { + if let Some(sig) = script_sigs.get_mut(key).and_then(Vec::pop) { + for leaf_hash in leaf_hashes { + input.tap_script_sigs.insert((*key, *leaf_hash), sig); + } + } + } + if let Some(signature) = self.signature_map.get(previous_output) { + // Mock keyspend: + input.final_script_witness = Some(Witness::p2tr_key_spend(signature)); + input.redact_sensitive_fields(); + } else if input.tap_key_origins.len() == input.tap_script_sigs.len() + 1 { + // Mock script spend (assumes only one path): + if let Some((control_block, (script, _))) = input.tap_scripts.first_key_value() { + let mut wit = Witness::new(); + // For the purpose of the mock, assume that (pubkey, leaf-hash) -> signature + // mappings occur in the opposite order that the signatures need to be added + // to the witness. This won't be true in general. + for sig in input.tap_script_sigs.values().rev() { + wit.push(sig.serialize()); + } + wit.push(script.as_bytes()); + wit.push(control_block.serialize()); + input.final_script_witness = Some(wit); + input.redact_sensitive_fields(); + } + } } } Ok(()) @@ -169,9 +196,15 @@ pub fn mock_buyer_trade_wallet() -> impl TradeWallet { ].map(|a| a.parse::>() .expect("hardcoded addresses should be valid").assume_checked()).into_iter(); let internal_key = - "0000000000000000000000000000000000000000000000000000000000000001".parse().ok(); + "51494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295".parse().ok(); + let script_sigs = script_sigs(internal_key.as_slice(), &[ + "5564448d3c5f024eaf2c65024a0c6e7a9066eb0390f8ffaeee2feacde310fabf\ + 87f3a8d8ad7fb125d7a6f68a282cfab8cd3178262a1fd0c2d06a598c8c454af8", + "652d0abaa3b4f8c7dd85ac9d523d44f768c8e1541aded79165c3cdfb3ba35d62\ + eef114e89becb490a80cfdab946d2d91748ccea501ceb4f08655dcc2868c0463", + ]); - MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key } + MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key, script_sigs } } //noinspection SpellCheckingInspection @@ -195,17 +228,30 @@ pub fn mock_seller_trade_wallet() -> impl TradeWallet { ].map(|a| a.parse::>() .expect("hardcoded addresses should be valid").assume_checked()).into_iter(); let internal_key = - "0000000000000000000000000000000000000000000000000000000000000002".parse().ok(); + "fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f".parse().ok(); + let script_sigs = script_sigs(internal_key.as_slice(), &[ + "87790f7eb3e98eb1b4dadc55ff5762275c4e3c02c6491abb26c8eabfada55b4b\ + 3f2627f627919d667be8f191a1b275b01549ab24e5eeda0019f83c658840500e", + "52fe2e44a4789a0f9bc406da144dcacca2621d2c1286e2d8f9913425c9927288\ + 13d658a3334a4070ce585cb907a67604fc74578e84e714c38c6547377fac133e", + ]); - MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key } + MockTradeWallet { funding_coins, new_addresses, signature_map, internal_key, script_sigs } } -fn signature_map(funding_coins: &[TxOutput], signatures: &[&'static str]) -> BTreeMap { - let signatures = signatures.iter().map(|s| Signature { +fn signature_vec(signatures: &[&'static str]) -> Vec { + signatures.iter().map(|s| Signature { signature: s.parse().expect("hardcoded signatures should be valid"), sighash_type: TapSighashType::Default, - }); - funding_coins.iter().map(|o| o.outpoint).zip(signatures).collect() + }).collect() +} + +fn signature_map(funding_coins: &[TxOutput], signatures: &[&'static str]) -> BTreeMap { + funding_coins.iter().map(|o| o.outpoint).zip(signature_vec(signatures)).collect() +} + +fn script_sigs(iks: &[XOnlyPublicKey], signatures: &[&'static str]) -> BTreeMap> { + iks.iter().map(|k| (*k, signature_vec(signatures))).collect() } impl TradeWallet for Wallet { @@ -238,19 +284,19 @@ impl TradeWallet for Wallet { ) -> Result { let mut builder = self.build_tx(); builder - .ordering(TxOrdering::Untouched) - .nlocktime(absolute::LockTime::ZERO) - .fee_rate(fee_rate) - .add_recipient(half_deposit_placeholder_spk(rng), deposit_amount); + .ordering(TxOrdering::Untouched) + .nlocktime(absolute::LockTime::ZERO) + .fee_rate(fee_rate) + .add_recipient(half_deposit_placeholder_spk(rng), deposit_amount); for receiver in trade_fee_receivers { builder.add_recipient(receiver.address.script_pubkey(), receiver.amount); } let mut psbt = builder.finish()?; // Calculate tx fee overpay unconditionally, as this performs additional checks on the PSBT: let overpay_msat: u64 = half_psbt_fee_overpay_msat(&psbt, fee_rate) - .ok_or(TransactionErrorKind::Overflow)? - .try_into() - .map_err(|_| TransactionErrorKind::InvalidPsbt)?; + .ok_or(TransactionErrorKind::Overflow)? + .try_into() + .map_err(|_| TransactionErrorKind::InvalidPsbt)?; let change_output_index = 1 + trade_fee_receivers.len(); if psbt.unsigned_tx.output.len() > change_output_index { // Correct any tx fee overpay due to overly conservative input witness size estimation @@ -274,6 +320,13 @@ impl TradeWallet for Wallet { if is_selected(&psbt.unsigned_tx.input[i].previous_output) { psbt.inputs[i].final_script_sig = psbt_copy.inputs[i].final_script_sig.take(); psbt.inputs[i].final_script_witness = psbt_copy.inputs[i].final_script_witness.take(); + psbt.inputs[i].tap_script_sigs = mem::take(&mut psbt_copy.inputs[i].tap_script_sigs); + + if !psbt.inputs[i].tap_script_sigs.is_empty() { + // BDK couldn't finalize the selected input. Try to finalize it ourselves using + // the `miniscript` lib, ignoring any errors that might occur. + let _ = psbt.finalize_inp_mut(&*LIBSECP256K1_CTX, i); + } } } Ok(()) @@ -301,9 +354,9 @@ impl TradeWallet for MemWallet { rng: &mut dyn RngCore, ) -> Result { let mut res: Vec<(ScriptBuf, Amount)> = trade_fee_receivers - .iter() - .map(|receiver| (receiver.address.script_pubkey(), deposit_amount)) - .collect(); + .iter() + .map(|receiver| (receiver.address.script_pubkey(), deposit_amount)) + .collect(); res.push((half_deposit_placeholder_spk(rng), deposit_amount)); let mut psbt = self.create_psbt(res, fee_rate)?; // Calculate tx fee overpay unconditionally, as this performs additional checks on the PSBT: @@ -348,6 +401,7 @@ impl Redact for psbt::Input { fn redact_sensitive_fields(&mut self) { self.tap_key_origins.clear(); self.tap_scripts.clear(); + self.tap_script_sigs.clear(); self.tap_internal_key = None; self.tap_merkle_root = None; } @@ -463,7 +517,7 @@ pub(crate) fn merge_psbt_halves( fn re(dest: &mut Vec, src: &[T]) -> Vec { let mut cloned_src = Vec::with_capacity(src.len() + dest.len()); cloned_src.extend(src.iter().cloned()); - std::mem::replace(dest, cloned_src) + mem::replace(dest, cloned_src) } use std::convert::identity as id; @@ -544,6 +598,17 @@ pub(crate) fn set_payouts_and_shuffle(psbt: &mut Psbt, buyer_payout: &mut TxOutp [buyer_payout.outpoint.txid, seller_payout.outpoint.txid] = [txid; 2]; } +pub(crate) fn extract_signed_tx(psbt: &Psbt) -> Result { + if !is_well_formed(psbt) || psbt.inputs.iter().any(|input| input.final_script_sig.is_some()) { + return Err(TransactionErrorKind::InvalidPsbt); + } + if psbt.inputs.iter().any(|input| input.final_script_witness.is_none()) { + return Err(TransactionErrorKind::MissingSignature); + } + // TODO: Report undocumented panics in `Psbt::extract_tx` & `Psbt::fee` if the PSBT is malformed. + Ok(psbt.clone().extract_tx()?) +} + #[cfg(test)] mod tests { use bdk_wallet::psbt::PsbtUtils as _; diff --git a/protocol/src/script_paths.rs b/protocol/src/script_paths.rs index 0d421bf..e90879a 100644 --- a/protocol/src/script_paths.rs +++ b/protocol/src/script_paths.rs @@ -1,6 +1,7 @@ use bdk_wallet::bitcoin::opcodes::all::{OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CSV}; use bdk_wallet::bitcoin::taproot::TaprootBuilder; use bdk_wallet::bitcoin::{ScriptBuf, TapNodeHash, XOnlyPublicKey, relative, script}; +use bdk_wallet::miniscript::{DefiniteDescriptorKey, Descriptor}; use crate::transaction::NetworkParams; @@ -12,6 +13,15 @@ pub fn warning_escrow_merkle_root(claim_pub_key: &XOnlyPublicKey, network: impl single_path_merkle_root(claim_script(claim_pub_key, network.claim_lock_time())) } +// FIXME: Can panic; improve: +pub fn deposit_payout_descriptor( + internal_key: &XOnlyPublicKey, + buyer_pub_key: &XOnlyPublicKey, + seller_pub_key: &XOnlyPublicKey, +) -> Descriptor { + format!("tr({internal_key},and_v(v:pk({buyer_pub_key}),pk({seller_pub_key})))").parse().unwrap() +} + fn single_path_merkle_root(script: ScriptBuf) -> TapNodeHash { TaprootBuilder::with_capacity(1) .add_leaf(0, script) diff --git a/protocol/src/transaction.rs b/protocol/src/transaction.rs index b2f9fb2..d6cfaf7 100644 --- a/protocol/src/transaction.rs +++ b/protocol/src/transaction.rs @@ -9,14 +9,16 @@ use bdk_wallet::bitcoin::{ Address, Amount, FeeRate, Network, OutPoint, Psbt, Sequence, TapSighash, TapSighashType, Transaction, TxIn, TxOut, Txid, Weight, Witness, absolute, relative, script, }; +use bdk_wallet::miniscript::DefiniteDescriptorKey; +use bdk_wallet::miniscript::psbt::PsbtInputExt as _; use paste::paste; use rand::RngCore; use relative::LockTime; use thiserror::Error; use crate::psbt::{ - TradeWallet, check_placeholder_output, check_receiver_outputs, merge_psbt_halves, prevout_set, - set_payouts_and_shuffle, + TradeWallet, check_placeholder_output, check_receiver_outputs, extract_signed_tx, + merge_psbt_halves, prevout_set, set_payouts_and_shuffle, }; use crate::receiver::ReceiverList; @@ -35,6 +37,7 @@ pub const ANCHOR_AMOUNT: Amount = Amount::from_sat(330); pub const SIGNED_FORWARDING_TX_WEIGHT: Weight = Weight::from_wu(444); pub const SIGNED_WARNING_TX_WEIGHT: Weight = Weight::from_wu(846); pub const SIGNED_REDIRECT_TX_BASE_WEIGHT: Weight = SIGNED_FORWARDING_TX_WEIGHT; +pub const SIGNED_CUSTOM_PAYOUT_TX_WEIGHT: Weight = Weight::from_wu(1182); pub trait NetworkParams { fn warning_lock_time(&self) -> LockTime; @@ -311,12 +314,11 @@ impl DepositTxBuilder { } pub fn combine_psbts(&mut self, other: Psbt) -> Result<&mut Self> { - // TODO: We may need to do some validation of the provided PSBT. self.psbt.as_mut().ok_or(TransactionErrorKind::MissingTransaction)?.combine(other)?; Ok(self) } - pub fn signed_tx(&self) -> Result { Ok(self.psbt()?.clone().extract_tx()?) } + pub fn signed_tx(&self) -> Result { extract_signed_tx(self.psbt()?) } } #[derive(Default)] @@ -359,7 +361,7 @@ impl WarningTxBuilder { pub fn compute_unsigned_tx(&mut self) -> Result<&mut Self> { let escrow_output = TxOut { value: Self::escrow_amount( - [self.buyer_input()?.prevout.value, self.seller_input()?.prevout.value], + self.inputs()?.map(|input| input.prevout.value), *self.fee_rate()?)?, script_pubkey: self.escrow_address()?.script_pubkey(), }; @@ -529,6 +531,95 @@ impl WithFixedInputs<1> for ForwardingTxBuilder { fn inputs(&self) -> Result<[&TxOutput; 1]> { Ok([self.input()?]) } } +type Descriptor = bdk_wallet::miniscript::Descriptor; + +#[derive(Default)] +pub struct CustomPayoutTxBuilder { + // Supplied fields: + buyer_input: Option, + seller_input: Option, + buyer_input_descriptor: Option, + seller_input_descriptor: Option, + buyer_payout_address: Option
, + seller_payout_address: Option
, + seller_payout_amount_excluding_fee: Option, + fee_rate: Option, + // Derived fields: + psbt: Option, +} + +impl CustomPayoutTxBuilder { + make_getter_setter!(buyer_input: TxOutput); + make_getter_setter!(seller_input: TxOutput); + make_getter_setter!(buyer_input_descriptor: Descriptor); + make_getter_setter!(seller_input_descriptor: Descriptor); + make_getter_setter!(buyer_payout_address: Address); + make_getter_setter!(seller_payout_address: Address); + make_getter_setter!(seller_payout_amount_excluding_fee: Amount); + make_getter_setter!(fee_rate: FeeRate); + make_getter!(psbt: Psbt: Transaction); + + // FIXME: We need to enforce dust limits and prevent fee over/underpayment due to non-P2TR payout addresses. + fn payout_amounts( + input_amounts: impl IntoIterator, + seller_payout_amount_excluding_fee: Amount, + fee_rate: FeeRate, + ) -> Option<[Amount; 2]> { + let total_fee = fee_rate.checked_mul_by_weight(SIGNED_CUSTOM_PAYOUT_TX_WEIGHT)?; + let seller_payout = seller_payout_amount_excluding_fee + .checked_sub(total_fee / 2)?; + let buyer_payout = input_amounts.into_iter().checked_sum()?.checked_sub(seller_payout)? + .checked_sub(total_fee)?; + Some([buyer_payout, seller_payout]) + } + + pub fn compute_unsigned_tx(&mut self) -> Result<&mut Self> { + let [buyer_payout_amount, seller_payout_amount] = Self::payout_amounts( + self.inputs()?.map(|input| input.prevout.value), + *self.seller_payout_amount_excluding_fee()?, + *self.fee_rate()?, + ).ok_or(TransactionErrorKind::Overflow)?; + let buyer_payout = TxOut { + value: buyer_payout_amount, + script_pubkey: self.buyer_payout_address()?.script_pubkey(), + }; + let seller_payout = TxOut { + value: seller_payout_amount, + script_pubkey: self.seller_payout_address()?.script_pubkey(), + }; + let tx = Transaction { + version: Version::TWO, + lock_time: absolute::LockTime::ZERO, + input: self.tx_ins(LockTime::ZERO)?.to_vec(), + output: vec![buyer_payout, seller_payout], + }; + let mut psbt = Psbt::from_unsigned_tx(tx).expect("tx is unsigned by construction"); + psbt.inputs[0].witness_utxo = Some(self.buyer_input()?.prevout.clone()); + psbt.inputs[1].witness_utxo = Some(self.seller_input()?.prevout.clone()); + psbt.inputs[0].update_with_descriptor_unchecked(self.buyer_input_descriptor()?)?; + psbt.inputs[1].update_with_descriptor_unchecked(self.seller_input_descriptor()?)?; + self.psbt = Some(psbt); + Ok(self) + } + + pub fn sign_partial(&mut self, trade_wallet: &(impl TradeWallet + ?Sized)) -> Result<&mut Self> { + let psbt = self.psbt.as_mut().ok_or(TransactionErrorKind::MissingTransaction)?; + trade_wallet.sign_selected_inputs(psbt, &|_| true)?; + Ok(self) + } + + pub fn combine_psbts(&mut self, other: Psbt) -> Result<&mut Self> { + self.psbt.as_mut().ok_or(TransactionErrorKind::MissingTransaction)?.combine(other)?; + Ok(self) + } + + pub fn signed_tx(&self) -> Result { extract_signed_tx(self.psbt()?) } +} + +impl WithFixedInputs<2> for CustomPayoutTxBuilder { + fn inputs(&self) -> Result<[&TxOutput; 2]> { Ok([self.buyer_input()?, self.seller_input()?]) } +} + pub type Result = std::result::Result; #[derive(Error, Debug)] @@ -539,6 +630,8 @@ pub enum TransactionErrorKind { MissingLockTime, #[error("missing tx output")] MissingTxOutput, + #[error("missing descriptor")] + MissingDescriptor, #[error("missing address")] MissingAddress, #[error("missing BTC amount")] @@ -589,6 +682,7 @@ mod tests { use super::*; use crate::psbt::{mock_buyer_trade_wallet, mock_seller_trade_wallet}; use crate::receiver::Receiver; + use crate::script_paths::deposit_payout_descriptor; // Valid signed txs pulled from an integration test run. We should be able to rebuild them exactly... @@ -666,7 +760,7 @@ mod tests { #[test] fn test_deposit_tx_builder() -> Result<()> { - let builder = filled_deposit_tx_builder()?; + let builder = filled_deposit_tx_builder(false)?; let signed_tx = builder.signed_tx()?; assert_eq!(tx(SIGNED_DEPOSIT_TX), signed_tx); @@ -675,7 +769,7 @@ mod tests { #[test] fn test_swap_tx_builder() -> Result<()> { - let builder = filled_swap_tx_builder(&filled_deposit_tx_builder()?)?; + let builder = filled_swap_tx_builder(&filled_deposit_tx_builder(false)?)?; let signed_tx = builder.signed_tx()?; assert_eq!(&tx(SIGNED_SWAP_TX), signed_tx); @@ -684,7 +778,7 @@ mod tests { #[test] fn test_warning_tx_builder() -> Result<()> { - let builder = filled_warning_tx_builder(&filled_deposit_tx_builder()?)?; + let builder = filled_warning_tx_builder(&filled_deposit_tx_builder(false)?)?; let signed_tx = builder.signed_tx()?; assert_eq!(&tx(SIGNED_SELLERS_WARNING_TX), signed_tx); @@ -694,7 +788,7 @@ mod tests { #[test] fn test_redirect_tx_builder() -> Result<()> { let builder = filled_redirect_tx_builder( - &filled_warning_tx_builder(&filled_deposit_tx_builder()?)?)?; + &filled_warning_tx_builder(&filled_deposit_tx_builder(false)?)?)?; let signed_tx = builder.signed_tx()?; assert_eq!(&tx(SIGNED_BUYERS_REDIRECT_TX), signed_tx); @@ -704,23 +798,58 @@ mod tests { #[test] fn test_claim_tx_builder() -> Result<()> { let builder = filled_claim_tx_builder( - &filled_warning_tx_builder(&filled_deposit_tx_builder()?)?)?; + &filled_warning_tx_builder(&filled_deposit_tx_builder(false)?)?)?; let signed_tx = builder.signed_tx()?; assert_eq!(&tx(SIGNED_SELLERS_CLAIM_TX), signed_tx); Ok(()) } + //noinspection SpellCheckingInspection + #[test] + fn test_custom_payout_tx_builder() -> Result<()> { + let fake_buyer_trade_wallet = bdk_wallet::test_utils::get_funded_wallet_single( + "tr(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh)").0; + let fake_seller_trade_wallet = bdk_wallet::test_utils::get_funded_wallet_single( + "tr(cNaQCDwmmh4dS9LzCgVtyy1e1xjCJ21GUDHe9K98nzb689JvinGV)").0; + + let mut builder0 = filled_unsigned_custom_payout_tx_builder( + &filled_deposit_tx_builder(true)?)?; + let mut builder1 = filled_unsigned_custom_payout_tx_builder( + &filled_deposit_tx_builder(true)?)?; + + // Check that signing with the mock buyer/seller wallets has exactly the same effect as + // signing with the above fake BDK trade wallets (with the particular params chosen)... + + builder0.sign_partial(&fake_buyer_trade_wallet)?; + builder1.sign_partial(&mock_buyer_trade_wallet())?; + assert_eq!(builder0.psbt()?, builder1.psbt()?); + + builder0.sign_partial(&fake_seller_trade_wallet)?; + builder1.sign_partial(&mock_seller_trade_wallet())?; + assert_eq!(builder0.psbt()?, builder1.psbt()?); + + // Now check the final signed tx... + + let signed_tx = builder0.signed_tx()?; + assert_eq!(SIGNED_CUSTOM_PAYOUT_TX_WEIGHT, signed_tx.weight()); + Ok(()) + } + fn tx(hex: &str) -> Transaction { consensus::deserialize(&hex!(hex)).unwrap() } fn sig(hex: &str) -> Signature { Signature::from_slice(&hex!(hex)).unwrap() } //noinspection SpellCheckingInspection - fn filled_deposit_tx_builder() -> Result { - let buyer_payout_address = "bcrt1p2zhgww7pvedvm2rg7d0zqh96crfhuudd9s0l3yc09ncaaxmuzvds5zhh8t" - .parse::>()?.require_network(Network::Regtest)?; - let seller_payout_address = "bcrt1pvenudpqy5j9n96uq5ng30ktvsvgx6qk2pk8ue446qdy24t854gnq5sp2l6" - .parse::>()?.require_network(Network::Regtest)?; + fn filled_deposit_tx_builder(for_custom_payout: bool) -> Result { + let [buyer_payout_address, seller_payout_address] = if for_custom_payout { + ["bcrt1ppavdcmx98s6xg86csr9t5nkul746kvhpplkveylsfeg7g88csm3qfyegr3"; 2] + } else { + [ + "bcrt1p2zhgww7pvedvm2rg7d0zqh96crfhuudd9s0l3yc09ncaaxmuzvds5zhh8t", + "bcrt1pvenudpqy5j9n96uq5ng30ktvsvgx6qk2pk8ue446qdy24t854gnq5sp2l6", + ] + }.map(|addr| addr.parse::>().unwrap().assume_checked()); // The RNG seed determines the shuffling of the deposit inputs & outputs. The byte 0x96 is // the smallest (of the two) array fill values which happen to give the correct shuffling. @@ -823,4 +952,39 @@ mod tests { .compute_signed_tx()?; Ok(builder) } + + //noinspection SpellCheckingInspection + fn filled_unsigned_custom_payout_tx_builder(deposit_tx_builder: &DepositTxBuilder) -> Result { + let buyer_input_internal_key = + "b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55".parse().unwrap(); + let seller_input_internal_key = + "b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55".parse().unwrap(); + let buyer_pub_key = + "51494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295".parse().unwrap(); + let seller_pub_key = + "fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f".parse().unwrap(); + + let buyer_input_descriptor = deposit_payout_descriptor( + &buyer_input_internal_key, &buyer_pub_key, &seller_pub_key); + let seller_input_descriptor = deposit_payout_descriptor( + &seller_input_internal_key, &buyer_pub_key, &seller_pub_key); + + let buyer_payout_address = "bcrt1p2zhgww7pvedvm2rg7d0zqh96crfhuudd9s0l3yc09ncaaxmuzvds5zhh8t" + .parse::>()?.require_network(Network::Regtest)?; + let seller_payout_address = "bcrt1pvenudpqy5j9n96uq5ng30ktvsvgx6qk2pk8ue446qdy24t854gnq5sp2l6" + .parse::>()?.require_network(Network::Regtest)?; + + let mut builder = CustomPayoutTxBuilder::default(); + builder + .set_buyer_input(deposit_tx_builder.buyer_payout()?.clone()) + .set_seller_input(deposit_tx_builder.seller_payout()?.clone()) + .set_buyer_input_descriptor(buyer_input_descriptor) + .set_seller_input_descriptor(seller_input_descriptor) + .set_buyer_payout_address(buyer_payout_address) + .set_seller_payout_address(seller_payout_address) + .set_seller_payout_amount_excluding_fee(Amount::from_sat(30_000_000)) + .set_fee_rate(FeeRate::from_sat_per_vb_unchecked(15)) + .compute_unsigned_tx()?; + Ok(builder) + } } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 80571a8..a7875e9 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -16,29 +16,29 @@ prost = "0.14.3" protocol = { path = "../protocol" } rand = { workspace = true } serde = { version = "1.0.228", features = ["derive"] } -serde_with = { version = "3.16.1", features = ["base64", "hex"] } +serde_with = { version = "3.18.0", features = ["base64", "hex"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } -tokio-stream = "0.1.18" -tonic = "0.14.4" -tonic-prost = "0.14.4" +tokio-stream = { workspace = true } +tonic = "0.14.5" +tonic-prost = "0.14.5" tracing = { workspace = true } unimock = { version = "0.6.8", optional = true } # Dependencies used only by the binary target(s): # TODO: Consider making a workspace of separate packages to avoid pulling these into the library: bmp_tracing = { workspace = true } -clap = { version = "4.5.59", features = ["derive"] } +clap = { version = "4.6.0", features = ["derive"] } wallet = { path = "../wallet" } # TODO move this to dev-dependencies: testenv = { path = "../testenv" } [build-dependencies] -tonic-prost-build = "0.14.4" +tonic-prost-build = "0.14.5" [dev-dependencies] rpc = { path = ".", features = ["unimock"] } anyhow = { workspace = true } -assert_cmd = "2.1.2" +assert_cmd = "2.2.0" const_format = { workspace = true } predicates = "3.1.4" diff --git a/rpc/build.rs b/rpc/build.rs index 8866cc4..5dfe2bb 100644 --- a/rpc/build.rs +++ b/rpc/build.rs @@ -26,7 +26,8 @@ fn main() -> Result<(), Box> { // Add Serde serialization for musigrpc request types... .serde_serialized_types(&[ "ReceiverAddressAndAmount", "PartialSignaturesRequest", "DepositTxSignatureRequest", - "PublishDepositTxRequest", "SubscribeTxConfirmationStatusRequest", "ContractualTxIds" + "PublishDepositTxRequest", "SubscribeTxConfirmationStatusRequest", "ContractualTxIds", + "CustomPayoutPsbtRequest" ]) .serde_serialized_type("PubKeySharesRequest", &[ enum_field("myRole", "Role") @@ -56,6 +57,9 @@ fn main() -> Result<(), Box> { .serde_serialized_type("CloseTradeRequest", &[ opt_base64("myOutputPeersPrvKeyShare"), opt_hex("swapTx") ]) + .serde_serialized_type("CustomCloseTradeRequest", &[ + base64("peersCustomPayoutPsbt") + ]) .serde_serialized_enum("Role") // Add Serde serialization for musigrpc response types... @@ -72,6 +76,12 @@ fn main() -> Result<(), Box> { .serde_serialized_type("CloseTradeResponse", &[ base64("peerOutputPrvKeyShare") ]) + .serde_serialized_type("CustomPayoutPsbt", &[ + base64("psbt") + ]) + .serde_serialized_type("CustomCloseTradeResponse", &[ + hex("customPayoutTx") + ]) // Now compile all the protos... .compile_protos( diff --git a/rpc/src/main/java/bisq/TradeProtocolClient.java b/rpc/src/main/java/bisq/TradeProtocolClient.java index 0e34ade..e3d4382 100644 --- a/rpc/src/main/java/bisq/TradeProtocolClient.java +++ b/rpc/src/main/java/bisq/TradeProtocolClient.java @@ -31,6 +31,7 @@ public static void main(String[] args) { client.testMusigService_twoParties(1, TradeType.TAKER_IS_BUYER, ClosureType.UNCOOPERATIVE); client.testMusigService_twoParties(2, TradeType.TAKER_IS_SELLER, ClosureType.COOPERATIVE); client.testMusigService_twoParties(3, TradeType.TAKER_IS_SELLER, ClosureType.UNCOOPERATIVE); + client.testMusigService_twoParties(4, TradeType.TAKER_IS_BUYER, ClosureType.MEDIATED); } finally { channel.shutdown(); } @@ -44,7 +45,7 @@ private enum TradeType { * Clean (unmediated) closure types. **/ private enum ClosureType { - COOPERATIVE, UNCOOPERATIVE + COOPERATIVE, UNCOOPERATIVE, MEDIATED } private void testMusigService_twoParties(int tradeNum, TradeType tradeType, ClosureType closureType) { @@ -258,6 +259,11 @@ private void setupTakerIsSellerTrade(String buyerTradeId, String sellerTradeId) } private void doRestOfTrade(String buyerTradeId, String sellerTradeId, ClosureType closureType) { + if (closureType == ClosureType.MEDIATED) { + doMediatedTradeClosure(buyerTradeId, sellerTradeId); + return; + } + // DELAY: Buyer makes fiat payment. // (Buyer calls 'GetPartialSignatures' a second time, this time with the ready-to-release flag set, so that his @@ -334,6 +340,38 @@ private void doRestOfTrade(String buyerTradeId, String sellerTradeId, ClosureTyp } } + private void doMediatedTradeClosure(String buyerTradeId, String sellerTradeId) { + // DELAY: Buyer accepts solution from mediator (cancelled trade, 50% buyer security deposit penalty). + + var buyersCustomPayoutPsbt = stub.signCustomPayoutTx(CustomPayoutPsbtRequest.newBuilder() + .setTradeId(buyerTradeId) + .setSellersPayoutAmountExcludingFee(245_000) + .setFeeRate(3_750) // 15.0 sats per vbyte + .build()); + System.out.println("Got reply: " + buyersCustomPayoutPsbt); + + // Buyer sends Custom Payout PSBT to seller. + + // DELAY: Seller accepts solution from mediator. + + var sellersCustomPayoutPsbt = stub.signCustomPayoutTx(CustomPayoutPsbtRequest.newBuilder() + .setTradeId(sellerTradeId) + .setSellersPayoutAmountExcludingFee(245_000) + .setFeeRate(3_750) // 15.0 sats per vbyte + .build()); + System.out.println("Got reply: " + sellersCustomPayoutPsbt); + + // *** SELLER CUSTOM-CLOSES TRADE *** + var sellersCustomCloseTradeResponse = stub.customCloseTrade(CustomCloseTradeRequest.newBuilder() + .setTradeId(sellerTradeId) + .setPeersCustomPayoutPsbt(buyersCustomPayoutPsbt.getPsbt()) + .build()); + System.out.println("Got reply: " + sellersCustomCloseTradeResponse); + // ********************************** + + // (Buyer picks up Custom Payout Tx from bitcoin network, closing his trade.) + } + @SuppressWarnings("SpellCheckingInspection") private static ReceiverAddressAndAmount mockTradeFeeReceiver() { return ReceiverAddressAndAmount.newBuilder() diff --git a/rpc/src/main/proto/rpc.proto b/rpc/src/main/proto/rpc.proto index 118f017..22db7b6 100644 --- a/rpc/src/main/proto/rpc.proto +++ b/rpc/src/main/proto/rpc.proto @@ -17,6 +17,10 @@ service Musig { rpc SignSwapTx (SwapTxSignatureRequest) returns (SwapTxSignatureResponse); rpc CloseTrade (CloseTradeRequest) returns (CloseTradeResponse); + + rpc SignCustomPayoutTx (CustomPayoutPsbtRequest) returns (CustomPayoutPsbt); + + rpc CustomCloseTrade (CustomCloseTradeRequest) returns (CustomCloseTradeResponse); } // TODO: Same as 'trade.TradeRole' from Bisq2 protos (minus 'UNSPECIFIED' variant, which should probably be added): @@ -143,3 +147,25 @@ message CloseTradeRequest { message CloseTradeResponse { bytes peerOutputPrvKeyShare = 1; } + +message CustomPayoutPsbtRequest { + string tradeId = 1; + uint64 sellersPayoutAmountExcludingFee = 2; // sats + uint64 feeRate = 3; // sats per kwu +} + +message CustomPayoutPsbt { + bytes psbt = 1; + string txid = 2; + uint64 buyersPayoutAmountIncludingFee = 3; // sats + uint64 sellersPayoutAmountIncludingFee = 4; // sats +} + +message CustomCloseTradeRequest { + string tradeId = 1; + bytes peersCustomPayoutPsbt = 2; +} + +message CustomCloseTradeResponse { + bytes customPayoutTx = 1; +} diff --git a/rpc/src/protocol.rs b/rpc/src/protocol.rs index 6a5aa55..012f6fd 100644 --- a/rpc/src/protocol.rs +++ b/rpc/src/protocol.rs @@ -13,8 +13,8 @@ use protocol::psbt::{self, TradeWallet}; use protocol::receiver::{Receiver, ReceiverList}; use protocol::script_paths; use protocol::transaction::{ - DepositTxBuilder, ForwardingTxBuilder, NetworkParams as _, RedirectTxBuilder, WarningTxBuilder, - WithWitnesses as _, + CustomPayoutTxBuilder, DepositTxBuilder, ForwardingTxBuilder, NetworkParams as _, + RedirectTxBuilder, WarningTxBuilder, WithWitnesses as _, }; use thiserror::Error; @@ -48,6 +48,7 @@ pub struct TradeModel { keys: Keys, deposit_tx: DepositTx, swap_tx: SwapTx, + custom_payout_tx: CustomPayoutTx, buyer_txs: ArbitrationTxs, seller_txs: ArbitrationTxs, } @@ -107,6 +108,11 @@ struct ClaimTx { input_sig_ctx: SigCtx, } +#[derive(Default)] +struct CustomPayoutTx { + builder: CustomPayoutTxBuilder, +} + pub struct ExchangedKeys<'a, S: Storage> { pub buyer_payout: S::Store<'a, Point>, pub seller_payout: S::Store<'a, Point>, @@ -262,16 +268,22 @@ impl TradeModel { self.keys.seller_payout_ctx.aggregate_pub_key_shares()?; let [buyer_pub_key, seller_pub_key] = self.keys.multisig_script_keys()?; + let [buyer_internal_key, seller_internal_key] = self.keys.internal_keys()?; + let deposit_merkle_root = script_paths::deposit_payout_merkle_root(buyer_pub_key, seller_pub_key); let buyer_payout_tweaked_key_ctx = self.keys.buyer_payout_ctx.with_taproot_tweak( Some(&deposit_merkle_root))?; let seller_payout_tweaked_key_ctx = self.keys.seller_payout_ctx.with_taproot_tweak( Some(&deposit_merkle_root))?; - let buyer_payout_address = buyer_payout_tweaked_key_ctx.p2tr_address(network); - let seller_payout_address = seller_payout_tweaked_key_ctx.p2tr_address(network); - self.deposit_tx.builder.set_buyer_payout_address(buyer_payout_address); - self.deposit_tx.builder.set_seller_payout_address(seller_payout_address); + self.deposit_tx.builder + .set_buyer_payout_address(buyer_payout_tweaked_key_ctx.p2tr_address(network)) + .set_seller_payout_address(seller_payout_tweaked_key_ctx.p2tr_address(network)); + self.custom_payout_tx.builder + .set_buyer_input_descriptor(script_paths::deposit_payout_descriptor( + &buyer_internal_key, buyer_pub_key, seller_pub_key)) + .set_seller_input_descriptor(script_paths::deposit_payout_descriptor( + &seller_internal_key, buyer_pub_key, seller_pub_key)); self.buyer_txs.warning.buyer_input_sig_ctx.set_tweaked_key_ctx(buyer_payout_tweaked_key_ctx.clone()); self.seller_txs.warning.buyer_input_sig_ctx.set_tweaked_key_ctx(buyer_payout_tweaked_key_ctx); @@ -361,10 +373,14 @@ impl TradeModel { let seller_payout = self.deposit_tx.builder.seller_payout()?; for txs in [&mut self.buyer_txs, &mut self.seller_txs] { - txs.warning.builder.set_buyer_input(buyer_payout.clone()); - txs.warning.builder.set_seller_input(seller_payout.clone()); + txs.warning.builder + .set_buyer_input(buyer_payout.clone()) + .set_seller_input(seller_payout.clone()); } self.swap_tx.builder.set_input(seller_payout.clone()); + self.custom_payout_tx.builder + .set_buyer_input(buyer_payout.clone()) + .set_seller_input(seller_payout.clone()); Ok(()) } @@ -663,6 +679,40 @@ impl TradeModel { } Ok(()) } + + pub fn set_custom_payout_tx_fee_rate(&mut self, fee_rate: FeeRate) { + self.custom_payout_tx.builder.set_fee_rate(fee_rate); + } + + pub fn set_sellers_custom_payout_amount_excluding_fee(&mut self, amount: Amount) { + self.custom_payout_tx.builder.set_seller_payout_amount_excluding_fee(amount); + } + + pub fn compute_custom_payout_tx(&mut self) -> Result<()> { + self.custom_payout_tx.builder + .set_buyer_payout_address(self.buyer_txs.claim.builder.payout_address()?.clone()) + .set_seller_payout_address(self.seller_txs.claim.builder.payout_address()?.clone()) + .compute_unsigned_tx()?; + Ok(()) + } + + pub fn sign_custom_payout_psbt(&mut self) -> Result<()> { + self.custom_payout_tx.builder.sign_partial(&*self.trade_wallet()?)?; + Ok(()) + } + + pub fn get_custom_payout_psbt(&self) -> Option<&Psbt> { + self.custom_payout_tx.builder.psbt().ok() + } + + pub fn combine_custom_payout_psbts(&mut self, other: Psbt) -> Result<()> { + self.custom_payout_tx.builder.combine_psbts(other)?; + Ok(()) + } + + pub fn get_signed_custom_payout_tx(&self) -> Option { + self.custom_payout_tx.builder.signed_tx().ok() + } } impl Keys { @@ -697,6 +747,11 @@ impl Keys { [self.peers_multisig_script_key.as_ref()?, self.my_multisig_script_key.as_ref()?] }))().ok_or(ProtocolErrorKind::MissingScriptKey) } + + fn internal_keys(&self) -> Result<[XOnlyPublicKey; 2]> { + Ok([self.buyer_payout_ctx.aggregated_key()?, self.seller_payout_ctx.aggregated_key()?] + .map(|p| p.pub_key().to_public_key().into())) + } } type Result = std::result::Result; diff --git a/rpc/src/server.rs b/rpc/src/server.rs index 49617a0..c6f87d6 100644 --- a/rpc/src/server.rs +++ b/rpc/src/server.rs @@ -16,7 +16,8 @@ use tracing::{Span, debug, error, info, instrument, trace}; use crate::pb::convert::{CheckInSignedRange as _, TryProtoInto}; pub use crate::pb::musigrpc::musig_server::MusigServer; use crate::pb::musigrpc::{ - CloseTradeRequest, CloseTradeResponse, DepositPsbt, DepositTxSignatureRequest, + CloseTradeRequest, CloseTradeResponse, CustomCloseTradeRequest, CustomCloseTradeResponse, + CustomPayoutPsbt, CustomPayoutPsbtRequest, DepositPsbt, DepositTxSignatureRequest, NonceSharesMessage, NonceSharesRequest, PartialSignaturesMessage, PartialSignaturesRequest, PubKeySharesRequest, PubKeySharesResponse, PublishDepositTxRequest, SubscribeTxConfirmationStatusRequest, SwapTxSignatureRequest, SwapTxSignatureResponse, @@ -215,6 +216,9 @@ impl musig_server::Musig for MusigImpl { trade_model.aggregate_private_keys_for_my_output()?; } else { // Peer unresponsive -- force-close our trade by publishing the swap tx. For seller only. + trade_model.get_signed_swap_tx() + .ok_or_else(|| Status::internal("missing signed swap tx"))?; + info!("*** BROADCAST SWAP TX ***"); // TODO: Implement broadcast. } let my_prv_key_share = trade_model.get_my_private_key_share_for_peer_output() @@ -223,6 +227,43 @@ impl musig_server::Musig for MusigImpl { Ok(CloseTradeResponse { peer_output_prv_key_share: my_prv_key_share.serialize().into() }) }) } + + #[instrument(skip_all)] + async fn sign_custom_payout_tx(&self, request: Request) -> Result> { + handle_musig_request(request, move |request, trade_model| { + trade_model.set_sellers_custom_payout_amount_excluding_fee( + Amount::from_sat(request.sellers_payout_amount_excluding_fee.check_in_signed_range()?)); + trade_model.set_custom_payout_tx_fee_rate( + FeeRate::from_sat_per_kwu(request.fee_rate.check_in_signed_range()?)); + trade_model.compute_custom_payout_tx()?; + trade_model.sign_custom_payout_psbt()?; + let psbt = trade_model.get_custom_payout_psbt() + .ok_or_else(|| Status::internal("missing custom payout PSBT"))?; + + Ok(CustomPayoutPsbt { + psbt: psbt.serialize(), + txid: psbt.unsigned_tx.compute_txid().to_string(), + buyers_payout_amount_including_fee: psbt.unsigned_tx.output[0].value.to_sat(), + sellers_payout_amount_including_fee: psbt.unsigned_tx.output[1].value.to_sat(), + }) + }) + } + + #[instrument(skip_all)] + async fn custom_close_trade(&self, request: Request) -> Result> { + handle_musig_request(request, move |request, trade_model| { + let peers_psbt = request.peers_custom_payout_psbt.try_proto_into()?; + trade_model.combine_custom_payout_psbts(peers_psbt)?; + // Sign custom payout PSBT again to finalize it: + trade_model.sign_custom_payout_psbt()?; + let custom_payout_tx = trade_model.get_signed_custom_payout_tx() + .ok_or_else(|| Status::internal("missing signed custom payout tx"))?; + + info!("*** BROADCAST CUSTOM PAYOUT TX ***"); // TODO: Implement broadcast. + + Ok(CustomCloseTradeResponse { custom_payout_tx: consensus::serialize(&custom_payout_tx) }) + }) + } } fn mock_tx_confirmation_status_stream(trade_id: String, tx: Vec) -> impl Stream> { @@ -352,6 +393,8 @@ impl_musig_req!(PublishDepositTxRequest); impl_musig_req!(SubscribeTxConfirmationStatusRequest); impl_musig_req!(SwapTxSignatureRequest); impl_musig_req!(CloseTradeRequest); +impl_musig_req!(CustomPayoutPsbtRequest); +impl_musig_req!(CustomCloseTradeRequest); // TODO: These wrapper fns don't work with async handlers, and should eventually be changed to do so: diff --git a/rpc/tests/wallet_service.rs b/rpc/tests/wallet_service.rs index 15adaf3..a50f782 100644 --- a/rpc/tests/wallet_service.rs +++ b/rpc/tests/wallet_service.rs @@ -9,8 +9,7 @@ use rpc::wallet::{TxConfidence, WalletService, WalletServiceImpl}; use testenv::TestEnv; use tokio::time::{self, Duration}; -// TODO fix this test, I guess we need to rewrite it, may be the whole streaming of transaction -// events. +// TODO fix this test, I guess we need to rewrite it, may be the whole streaming of transaction events. #[tokio::test(flavor = "multi_thread", worker_threads = 1)] #[ignore = "needs to be fixed"] async fn test_wallet_service_mine_single_tx() -> Result<()> { @@ -66,8 +65,7 @@ async fn test_wallet_service_mine_single_tx() -> Result<()> { } async fn start_wallet_service(rpc_client: bitcoincore_rpc::Client) -> Arc { - let wallet_service = Arc::new(WalletServiceImpl::create_with_rpc_params( - rpc_client)); + let wallet_service = Arc::new(WalletServiceImpl::create_with_rpc_params(rpc_client)); assert_eq!(wallet_service.balance(), Balance::default()); wallet_service.clone().spawn_connection(); diff --git a/testenv/Cargo.toml b/testenv/Cargo.toml index ef57cf2..2f86843 100644 --- a/testenv/Cargo.toml +++ b/testenv/Cargo.toml @@ -9,13 +9,13 @@ anyhow = { workspace = true } bdk_bitcoind_rpc = { workspace = true } bdk_electrum = { workspace = true } bdk_wallet = { workspace = true, features = ["test-utils"] } +bmp_tracing = { workspace = true } hex = { workspace = true } rand = { workspace = true } secp = { workspace = true } simple-semaphore = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["net"] } -bmp_tracing = { workspace = true } electrsd = { version = "0.36.1", features = [ "esplora_a33e97e1", diff --git a/testenv/src/lib.rs b/testenv/src/lib.rs index 37a1412..2848848 100644 --- a/testenv/src/lib.rs +++ b/testenv/src/lib.rs @@ -163,7 +163,7 @@ impl TestEnv { } /// Generate a new temporary directory - pub fn get_tmp_dir(&self) -> anyhow::Result { + pub fn get_tmp_dir(&self) -> Result { let dir = TempDir::new()?; Ok(dir) } @@ -179,7 +179,8 @@ impl TestEnv { Self::new_with_conf(config) } - /// ZMQ socket for raw transaction notifications (set when created via [`enable_zmq`](Self::enable_zmq)). + /// ZMQ socket for raw transaction notifications (set when created via + /// [`enable_zmq`](Self::enable_zmq)). pub fn zmq_pub_raw_tx_socket(&self) -> Option { self.bitcoind .params @@ -187,7 +188,8 @@ impl TestEnv { .map(|socket| format!("tcp://{socket}")) } - /// ZMQ socket for raw block notifications (set when created via [`enable_zmq`](Self::enable_zmq)). + /// ZMQ socket for raw block notifications (set when created via + /// [`enable_zmq`](Self::enable_zmq)). pub fn zmq_pub_raw_block_socket(&self) -> Option { self.bitcoind.params.zmq_pub_raw_block_socket } @@ -289,28 +291,18 @@ impl TestEnv { let container_name = format!("btc-explorer-{browser_port}"); let mut container = std::process::Command::new("podman"); - container.args([ - "run", - "--rm", - "--name", - &container_name, - "-p", - &format!("{browser_port}:3002"), + #[rustfmt::skip] + container.args(["run", "--rm", + "--name", &container_name, + "-p", &format!("{browser_port}:3002"), "--add-host=host.containers.internal:host-gateway", - "-e", - "BTCEXP_BITCOIND_HOST=host.containers.internal", - "-e", - "BTCEXP_HOST=0.0.0.0", - "-e", - &format!("BTCEXP_BITCOIND_PORT={bitcoind_rpc_port}"), - "-e", - "BTCEXP_BITCOIND_USER=bitcoin", - "-e", - &format!("BTCEXP_BITCOIND_PASS={}", self.bitcoin_rpc_pwd), - "-e", - "BTCEXP_ADDRESS_API=electrum", - "-e", - &format!("BTCEXP_ELECTRUM_SERVERS=tcp://host.containers.internal:{electrum_port}"), + "-e", "BTCEXP_BITCOIND_HOST=host.containers.internal", + "-e", "BTCEXP_HOST=0.0.0.0", + "-e", &format!("BTCEXP_BITCOIND_PORT={bitcoind_rpc_port}"), + "-e", "BTCEXP_BITCOIND_USER=bitcoin", + "-e", &format!("BTCEXP_BITCOIND_PASS={}", self.bitcoin_rpc_pwd), + "-e", "BTCEXP_ADDRESS_API=electrum", + "-e", &format!("BTCEXP_ELECTRUM_SERVERS=tcp://host.containers.internal:{electrum_port}"), "docker.io/getumbrel/btc-rpc-explorer:v3.5.1", ]); diff --git a/wallet/src/bmp_wallet.rs b/wallet/src/bmp_wallet.rs index 2bf9252..b85602d 100644 --- a/wallet/src/bmp_wallet.rs +++ b/wallet/src/bmp_wallet.rs @@ -555,7 +555,7 @@ impl WalletApi for BMPWallet { for key in &pubkeys { let path_str = self.db.path().expect("DB path should not be empty").replace(Self::DB_NAME, ""); let db_path = Path::new(&path_str).join(format!("bmp_{}.db3", key.to_lower_hex_string())); - + let mut db = Connection::open(db_path)?; let imported_wallet_opt = Wallet::load() .check_network(self.wallet.network()) @@ -760,6 +760,7 @@ impl DerefMut for BMPWallet { #[cfg(test)] mod tests { use std::str::FromStr; + use bdk_kyoto::FeeRate; use bdk_wallet::bitcoin::hashes::Hash as _; use bdk_wallet::bitcoin::{Address, AddressType, Amount, BlockHash, Network, Weight, psbt}; @@ -769,13 +770,13 @@ mod tests { use bmp_tracing::tracing; use rand::RngCore as _; use secp::Scalar; - use tempfile::{tempdir, TempDir}; + use tempfile::{TempDir, tempdir}; use crate::bmp_wallet::{BMPWallet, WalletApi as _}; use crate::test_utils::{MockedBDKElectrum, derive_public_key, load_imported_wallet}; fn get_dir() -> TempDir { - tempdir().unwrap() + tempdir().unwrap() } fn new_private_key() -> Scalar { diff --git a/wallet/src/protocol_wallet_api.rs b/wallet/src/protocol_wallet_api.rs index 245124e..ad1486c 100644 --- a/wallet/src/protocol_wallet_api.rs +++ b/wallet/src/protocol_wallet_api.rs @@ -1,3 +1,6 @@ +use std::io::Write; +use std::sync::LazyLock; + use bdk_electrum::BdkElectrumClient; use bdk_electrum::bdk_core::bitcoin::bip32::Xpriv; use bdk_electrum::bdk_core::bitcoin::{Transaction, Txid, XOnlyPublicKey, absolute, secp256k1}; @@ -6,12 +9,11 @@ use bdk_wallet::bitcoin::{Address, Amount, FeeRate, Network, OutPoint, Psbt, Scr use bdk_wallet::descriptor::{Descriptor, ExtendedDescriptor}; use bdk_wallet::miniscript::ToPublicKey; use bdk_wallet::miniscript::descriptor::ConversionError; +use bdk_wallet::miniscript::psbt::PsbtExt as _; use bdk_wallet::template::{Bip86, DescriptorTemplate}; use bdk_wallet::{AddressInfo, KeychainKind, SignOptions, TxOrdering, Wallet}; use rand::RngCore; use secp::Scalar; -use std::io::Write; -use std::sync::LazyLock; use thiserror::Error; /// The Protocol Wallet API is used by the protocol to create and sign transactions. @@ -49,6 +51,7 @@ pub struct MemWallet { wallet: Wallet, client: BdkElectrumClient, } + // TODO think about stop_gap and batch_size const STOP_GAP: usize = 50; const BATCH_SIZE: usize = 5; @@ -63,25 +66,26 @@ impl MemWallet { pub fn reveal_next_address(&mut self) -> Address { self.wallet - .reveal_next_address(KeychainKind::External) - .address + .reveal_next_address(KeychainKind::External) + .address } pub fn public_descriptor(&self, chain: KeychainKind) -> &ExtendedDescriptor { self.wallet.public_descriptor(chain) } + pub fn new_internal_key(&mut self) -> Result { if let Descriptor::Tr(tr) = self.public_descriptor(KeychainKind::External) { let ik = tr.internal_key().clone(); let index = self - .wallet - .reveal_next_address(KeychainKind::External) - .index; + .wallet + .reveal_next_address(KeychainKind::External) + .index; return Ok(ik - .at_derivation_index(index)? - .derive_public_key(&*LIBSECP256K1_CTX)? - .to_x_only_pubkey()); + .at_derivation_index(index)? + .derive_public_key(&*LIBSECP256K1_CTX)? + .to_x_only_pubkey()); } Err(WalletErrorKind::NotTaprootAddress) } @@ -93,10 +97,10 @@ impl MemWallet { ) -> anyhow::Result { let mut builder = self.wallet.build_tx(); builder - .ordering(TxOrdering::Untouched) - .nlocktime(absolute::LockTime::ZERO) - .fee_rate(fee_rate) - .set_recipients(recipients); + .ordering(TxOrdering::Untouched) + .nlocktime(absolute::LockTime::ZERO) + .fee_rate(fee_rate) + .set_recipients(recipients); Ok(builder.finish()?) } @@ -118,7 +122,15 @@ impl MemWallet { if is_selected(&psbt.unsigned_tx.input[i].previous_output) { psbt.inputs[i].final_script_sig = psbt_copy.inputs[i].final_script_sig.take(); psbt.inputs[i].final_script_witness = - psbt_copy.inputs[i].final_script_witness.take(); + psbt_copy.inputs[i].final_script_witness.take(); + psbt.inputs[i].tap_script_sigs = + std::mem::take(&mut psbt_copy.inputs[i].tap_script_sigs); + + if !psbt.inputs[i].tap_script_sigs.is_empty() { + // BDK couldn't finalize the selected input. Try to finalize it ourselves using + // the `miniscript` lib, ignoring any errors that might occur. + let _ = psbt.finalize_inp_mut(&*LIBSECP256K1_CTX, i); + } } } Ok(()) @@ -150,18 +162,18 @@ impl MemWallet { ); let (descriptor, external_map, _) = Bip86(xprv, KeychainKind::External) - .build(network) - .expect("Failed to build external descriptor"); + .build(network) + .expect("Failed to build external descriptor"); let (change_descriptor, internal_map, _) = Bip86(xprv, KeychainKind::Internal) - .build(network) - .expect("Failed to build internal descriptor"); + .build(network) + .expect("Failed to build internal descriptor"); let wallet = Wallet::create(descriptor, change_descriptor) - .network(network) - .keymap(KeychainKind::External, external_map) - .keymap(KeychainKind::Internal, internal_map) - .create_wallet_no_persist()?; + .network(network) + .keymap(KeychainKind::External, external_map) + .keymap(KeychainKind::Internal, internal_map) + .create_wallet_no_persist()?; Ok(Self { wallet, client }) } @@ -170,7 +182,7 @@ impl MemWallet { // Populate the electrum client's transaction cache so it doesn't re-download transaction we // already have. self.client - .populate_tx_cache(self.wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); + .populate_tx_cache(self.wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); let request = self.wallet.start_full_scan().inspect({ let mut stdout = std::io::stdout(); @@ -181,8 +193,8 @@ impl MemWallet { }); tracing::info!("requesting update..."); let update = self - .client - .full_scan(request, STOP_GAP, BATCH_SIZE, false)?; + .client + .full_scan(request, STOP_GAP, BATCH_SIZE, false)?; self.wallet.apply_update(update)?; Ok(()) }