diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 90d290be0..851c9c050 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -96,21 +96,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "0.6.20" @@ -613,56 +598,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bollard" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" -dependencies = [ - "base64 0.22.1", - "bollard-stubs", - "bytes", - "futures-core", - "futures-util", - "hex", - "home", - "http", - "http-body-util", - "hyper", - "hyper-named-pipe", - "hyper-rustls", - "hyper-util", - "hyperlocal", - "log", - "pin-project-lite", - "rustls 0.23.31", - "rustls-native-certs", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_derive", - "serde_json", - "serde_repr", - "serde_urlencoded", - "thiserror 2.0.15", - "tokio", - "tokio-util", - "tower-service", - "url", - "winapi", -] - -[[package]] -name = "bollard-stubs" -version = "1.47.1-rc.27.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" -dependencies = [ - "serde", - "serde_repr", - "serde_with", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -806,19 +741,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-link", -] - [[package]] name = "cipher" version = "0.3.0" @@ -885,20 +807,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", -] - [[package]] name = "compression-codecs" version = "0.4.30" @@ -1126,41 +1034,6 @@ dependencies = [ "cipher 0.3.0", ] -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.106", -] - [[package]] name = "data-encoding" version = "2.6.0" @@ -1174,7 +1047,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", - "serde", ] [[package]] @@ -1238,29 +1110,6 @@ dependencies = [ "const-random", ] -[[package]] -name = "docker_credential" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "electrum-client" version = "0.18.0" @@ -1328,17 +1177,6 @@ dependencies = [ "ureq", ] -[[package]] -name = "etcetera" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.59.0", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1609,12 +1447,6 @@ dependencies = [ "scroll", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1723,15 +1555,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "http" version = "1.3.1" @@ -1800,21 +1623,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-named-pipe" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" -dependencies = [ - "hex", - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", - "winapi", -] - [[package]] name = "hyper-rustls" version = "0.27.7" @@ -1872,45 +1680,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hyperlocal" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" -dependencies = [ - "hex", - "http-body-util", - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_collections" version = "2.0.0" @@ -1997,12 +1766,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -2024,17 +1787,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.10.0" @@ -2043,7 +1795,6 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.5", - "serde", ] [[package]] @@ -2346,40 +2097,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "object" version = "0.36.4" @@ -2506,31 +2229,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "parse-display" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" -dependencies = [ - "parse-display-derive", - "regex", - "regex-syntax 0.8.5", -] - -[[package]] -name = "parse-display-derive" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "regex-syntax 0.8.5", - "structmeta", - "syn 2.0.106", -] - [[package]] name = "paste" version = "1.0.15" @@ -2615,7 +2313,7 @@ dependencies = [ "hyper-util", "payjoin", "prometheus", - "redis", + "rand 0.8.5", "serde", "tempfile", "tokio", @@ -2661,7 +2359,7 @@ dependencies = [ "rcgen", "reqwest", "rustls 0.23.31", - "testcontainers-modules", + "tempfile", "tokio", "tracing", "tracing-subscriber", @@ -3011,28 +2709,6 @@ dependencies = [ "yasna", ] -[[package]] -name = "redis" -version = "0.32.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd3650deebc68526b304898b192fa4102a4ef0b9ada24da096559cb60e0eef8" -dependencies = [ - "bytes", - "cfg-if", - "combine", - "futures-util", - "itoa", - "num-bigint", - "percent-encoding", - "pin-project-lite", - "ryu", - "sha1_smol", - "socket2 0.6.0", - "tokio", - "tokio-util", - "url", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -3042,15 +2718,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[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 = "redox_syscall" version = "0.5.3" @@ -3071,26 +2738,6 @@ dependencies = [ "thiserror 2.0.15", ] -[[package]] -name = "ref-cast" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "regex" version = "1.11.1" @@ -3314,16 +2961,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -3385,30 +3022,6 @@ dependencies = [ "parking_lot 0.12.3", ] -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -3562,17 +3175,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "serde_spanned" version = "1.0.0" @@ -3594,38 +3196,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.10.0", - "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "sha1" version = "0.10.6" @@ -3637,12 +3207,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "sha2" version = "0.9.9" @@ -3795,29 +3359,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "structmeta" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" -dependencies = [ - "proc-macro2", - "quote", - "structmeta-derive", - "syn 2.0.106", -] - -[[package]] -name = "structmeta-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3890,44 +3431,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "testcontainers" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bb7577dca13ad86a78e8271ef5d322f37229ec83b8d98da6d996c588a1ddb1" -dependencies = [ - "async-trait", - "bollard", - "bollard-stubs", - "bytes", - "docker_credential", - "either", - "etcetera", - "futures", - "log", - "memchr", - "parse-display", - "pin-project-lite", - "serde", - "serde_json", - "serde_with", - "thiserror 2.0.15", - "tokio", - "tokio-stream", - "tokio-tar", - "tokio-util", - "url", -] - -[[package]] -name = "testcontainers-modules" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac95cde96549fc19c6bf19ef34cc42bd56e264c1cb97e700e21555be0ecf9e2" -dependencies = [ - "testcontainers", -] - [[package]] name = "textwrap" version = "0.16.2" @@ -3994,12 +3497,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", "time-core", - "time-macros", ] [[package]] @@ -4008,16 +3509,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -4093,32 +3584,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tar" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.3.5", - "tokio", - "tokio-stream", - "xattr", -] - [[package]] name = "tokio-tungstenite" version = "0.27.0" @@ -4410,7 +3875,7 @@ dependencies = [ "glob", "goblin", "heck", - "indexmap 2.10.0", + "indexmap", "once_cell", "serde", "tempfile", @@ -4467,7 +3932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04f4f224becf14885c10e6e400b95cc4d1985738140cb194ccc2044563f8a56b" dependencies = [ "anyhow", - "indexmap 2.10.0", + "indexmap", "proc-macro2", "quote", "syn 2.0.106", @@ -4510,7 +3975,7 @@ checksum = "4b147e133ad7824e32426b90bc41fda584363563f2ba747f590eca1fd6fd14e6" dependencies = [ "anyhow", "heck", - "indexmap 2.10.0", + "indexmap", "tempfile", "uniffi_internal_macros", ] @@ -4827,65 +4292,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 90d290be0..851c9c050 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -96,21 +96,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "0.6.20" @@ -613,56 +598,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bollard" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" -dependencies = [ - "base64 0.22.1", - "bollard-stubs", - "bytes", - "futures-core", - "futures-util", - "hex", - "home", - "http", - "http-body-util", - "hyper", - "hyper-named-pipe", - "hyper-rustls", - "hyper-util", - "hyperlocal", - "log", - "pin-project-lite", - "rustls 0.23.31", - "rustls-native-certs", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_derive", - "serde_json", - "serde_repr", - "serde_urlencoded", - "thiserror 2.0.15", - "tokio", - "tokio-util", - "tower-service", - "url", - "winapi", -] - -[[package]] -name = "bollard-stubs" -version = "1.47.1-rc.27.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" -dependencies = [ - "serde", - "serde_repr", - "serde_with", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -806,19 +741,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-link", -] - [[package]] name = "cipher" version = "0.3.0" @@ -885,20 +807,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", -] - [[package]] name = "compression-codecs" version = "0.4.30" @@ -1126,41 +1034,6 @@ dependencies = [ "cipher 0.3.0", ] -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.106", -] - [[package]] name = "data-encoding" version = "2.6.0" @@ -1174,7 +1047,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", - "serde", ] [[package]] @@ -1238,29 +1110,6 @@ dependencies = [ "const-random", ] -[[package]] -name = "docker_credential" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "electrum-client" version = "0.18.0" @@ -1328,17 +1177,6 @@ dependencies = [ "ureq", ] -[[package]] -name = "etcetera" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.59.0", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1609,12 +1447,6 @@ dependencies = [ "scroll", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1723,15 +1555,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "http" version = "1.3.1" @@ -1800,21 +1623,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-named-pipe" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" -dependencies = [ - "hex", - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", - "winapi", -] - [[package]] name = "hyper-rustls" version = "0.27.7" @@ -1872,45 +1680,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hyperlocal" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" -dependencies = [ - "hex", - "http-body-util", - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_collections" version = "2.0.0" @@ -1997,12 +1766,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -2024,17 +1787,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.10.0" @@ -2043,7 +1795,6 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.5", - "serde", ] [[package]] @@ -2346,40 +2097,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "object" version = "0.36.4" @@ -2506,31 +2229,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "parse-display" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" -dependencies = [ - "parse-display-derive", - "regex", - "regex-syntax 0.8.5", -] - -[[package]] -name = "parse-display-derive" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "regex-syntax 0.8.5", - "structmeta", - "syn 2.0.106", -] - [[package]] name = "paste" version = "1.0.15" @@ -2615,7 +2313,7 @@ dependencies = [ "hyper-util", "payjoin", "prometheus", - "redis", + "rand 0.8.5", "serde", "tempfile", "tokio", @@ -2661,7 +2359,7 @@ dependencies = [ "rcgen", "reqwest", "rustls 0.23.31", - "testcontainers-modules", + "tempfile", "tokio", "tracing", "tracing-subscriber", @@ -3011,28 +2709,6 @@ dependencies = [ "yasna", ] -[[package]] -name = "redis" -version = "0.32.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd3650deebc68526b304898b192fa4102a4ef0b9ada24da096559cb60e0eef8" -dependencies = [ - "bytes", - "cfg-if", - "combine", - "futures-util", - "itoa", - "num-bigint", - "percent-encoding", - "pin-project-lite", - "ryu", - "sha1_smol", - "socket2 0.6.0", - "tokio", - "tokio-util", - "url", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -3042,15 +2718,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[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 = "redox_syscall" version = "0.5.3" @@ -3071,26 +2738,6 @@ dependencies = [ "thiserror 2.0.15", ] -[[package]] -name = "ref-cast" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "regex" version = "1.11.1" @@ -3314,16 +2961,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -3385,30 +3022,6 @@ dependencies = [ "parking_lot 0.12.3", ] -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -3562,17 +3175,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "serde_spanned" version = "1.0.0" @@ -3594,38 +3196,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.10.0", - "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "sha1" version = "0.10.6" @@ -3637,12 +3207,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "sha2" version = "0.9.9" @@ -3795,29 +3359,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "structmeta" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" -dependencies = [ - "proc-macro2", - "quote", - "structmeta-derive", - "syn 2.0.106", -] - -[[package]] -name = "structmeta-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3890,44 +3431,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "testcontainers" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bb7577dca13ad86a78e8271ef5d322f37229ec83b8d98da6d996c588a1ddb1" -dependencies = [ - "async-trait", - "bollard", - "bollard-stubs", - "bytes", - "docker_credential", - "either", - "etcetera", - "futures", - "log", - "memchr", - "parse-display", - "pin-project-lite", - "serde", - "serde_json", - "serde_with", - "thiserror 2.0.15", - "tokio", - "tokio-stream", - "tokio-tar", - "tokio-util", - "url", -] - -[[package]] -name = "testcontainers-modules" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac95cde96549fc19c6bf19ef34cc42bd56e264c1cb97e700e21555be0ecf9e2" -dependencies = [ - "testcontainers", -] - [[package]] name = "textwrap" version = "0.16.2" @@ -3994,12 +3497,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", "time-core", - "time-macros", ] [[package]] @@ -4008,16 +3509,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -4093,32 +3584,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tar" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.3.5", - "tokio", - "tokio-stream", - "xattr", -] - [[package]] name = "tokio-tungstenite" version = "0.27.0" @@ -4410,7 +3875,7 @@ dependencies = [ "glob", "goblin", "heck", - "indexmap 2.10.0", + "indexmap", "once_cell", "serde", "tempfile", @@ -4467,7 +3932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04f4f224becf14885c10e6e400b95cc4d1985738140cb194ccc2044563f8a56b" dependencies = [ "anyhow", - "indexmap 2.10.0", + "indexmap", "proc-macro2", "quote", "syn 2.0.106", @@ -4510,7 +3975,7 @@ checksum = "4b147e133ad7824e32426b90bc41fda584363563f2ba747f590eca1fd6fd14e6" dependencies = [ "anyhow", "heck", - "indexmap 2.10.0", + "indexmap", "tempfile", "uniffi_internal_macros", ] @@ -4827,65 +4292,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/payjoin-directory/Cargo.toml b/payjoin-directory/Cargo.toml index 89df557c2..2e6fef6fb 100644 --- a/payjoin-directory/Cargo.toml +++ b/payjoin-directory/Cargo.toml @@ -28,7 +28,6 @@ hyper-rustls = { version = "0.27.7", default-features=false, features = ["webpki hyper-util = { version = "0.1.16", features = ["tokio"] } ohttp = { package = "bitcoin-ohttp", version = "0.6.0"} payjoin = { version = "0.24.0", features = ["directory"], default-features = false } -redis = { version = "0.32.5", features = ["aio", "tokio-comp"] } tokio = { version = "1.47.1", features = ["full"] } tokio-rustls = { version = "0.26.2", features = ["ring"], default-features = false, optional = true } tracing = "0.1.41" @@ -37,6 +36,7 @@ prometheus = "0.13.4" clap = { version = "4.5.45", features = ["derive", "env"] } config = "0.15.14" serde = { version = "1.0.219", features = ["derive"] } +rand = "0.8" [dev-dependencies] tempfile = "3.20.0" diff --git a/payjoin-directory/src/cli.rs b/payjoin-directory/src/cli.rs index c321286fe..202fb4069 100644 --- a/payjoin-directory/src/cli.rs +++ b/payjoin-directory/src/cli.rs @@ -36,12 +36,11 @@ pub struct Cli { pub timeout: u64, #[arg( - long = "db-host", - env = "PJ_DB_HOST", - default_value = "localhost:6379", - help = "The redis host to connect to" + long = "storage-dir", + env = "PJ_STORAGE_DIR", + help = "A directory for writing mailbox data." )] - pub db_host: String, + pub storage_dir: PathBuf, #[arg( long = "ohttp-keys", diff --git a/payjoin-directory/src/config.rs b/payjoin-directory/src/config.rs index 4f649134b..f4c6660bc 100644 --- a/payjoin-directory/src/config.rs +++ b/payjoin-directory/src/config.rs @@ -15,7 +15,7 @@ pub struct Config { pub listen_addr: String, // TODO tokio_listener::ListenerAddressLFlag pub metrics_listen_addr: String, // TODO tokio_listener::ListenerAddressLFlag pub timeout: Duration, - pub db_host: String, + pub storage_dir: PathBuf, pub ohttp_keys: PathBuf, // TODO OhttpConfig struct with rotation params, etc } @@ -33,7 +33,7 @@ impl Config { listen_addr: built_config.get("listen_addr")?, metrics_listen_addr: built_config.get("metrics_listen_addr")?, timeout: Duration::from_secs(built_config.get("timeout")?), - db_host: built_config.get("db_host")?, + storage_dir: built_config.get("storage_dir")?, ohttp_keys: built_config.get("ohttp_keys")?, }) } @@ -47,6 +47,6 @@ fn add_defaults(config: Builder, cli: &Cli) -> Result { Some(format!("localhost:{}", cli.metrics_port)), )? .set_override_option("timeout", Some(cli.timeout))? - .set_override_option("db_host", Some(cli.db_host.to_owned()))? - .set_override_option("ohttp_keys", Some(cli.ohttp_keys.to_string_lossy().into_owned())) + .set_override_option("ohttp_keys", Some(cli.ohttp_keys.to_string_lossy().into_owned()))? + .set_override_option("storage_dir", Some(cli.storage_dir.to_string_lossy().into_owned())) } diff --git a/payjoin-directory/src/db.rs b/payjoin-directory/src/db.rs deleted file mode 100644 index e04bfb894..000000000 --- a/payjoin-directory/src/db.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::time::Duration; - -use futures::StreamExt; -use payjoin::directory::ShortId; -use redis::{AsyncCommands, Client, ErrorKind, RedisError, RedisResult}; -use tracing::debug; - -const DEFAULT_COLUMN: &str = ""; -const PJ_V1_COLUMN: &str = "pjv1"; - -#[derive(Debug, Clone)] -pub struct DbPool { - client: Client, - timeout: Duration, -} - -/// Errors pertaining to [`DbPool`] -#[derive(Debug)] -pub enum Error { - Redis(RedisError), - Timeout(tokio::time::error::Elapsed), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use Error::*; - - match &self { - Redis(error) => write!(f, "Redis error: {error}"), - Timeout(timeout) => write!(f, "Timeout: {timeout}"), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Redis(e) => Some(e), - Error::Timeout(e) => Some(e), - } - } -} - -impl From for Error { - fn from(value: RedisError) -> Self { Error::Redis(value) } -} - -pub(crate) type Result = core::result::Result; - -impl DbPool { - pub async fn new(timeout: Duration, db_host: String) -> Result { - let client = Client::open(format!("redis://{db_host}"))?; - Ok(Self { client, timeout }) - } - - pub async fn push_default(&self, mailbox_id: &ShortId, data: Vec) -> Result<()> { - self.push(mailbox_id, DEFAULT_COLUMN, data).await - } - - pub async fn peek_default(&self, mailbox_id: &ShortId) -> Result> { - self.peek_with_timeout(mailbox_id, DEFAULT_COLUMN).await - } - - pub async fn push_v1(&self, mailbox_id: &ShortId, data: Vec) -> Result<()> { - self.push(mailbox_id, PJ_V1_COLUMN, data).await - } - - pub async fn peek_v1(&self, mailbox_id: &ShortId) -> Result> { - self.peek_with_timeout(mailbox_id, PJ_V1_COLUMN).await - } - - async fn push(&self, mailbox_id: &ShortId, channel_type: &str, data: Vec) -> Result<()> { - let mut conn = self.client.get_multiplexed_async_connection().await?; - let key = channel_name(mailbox_id, channel_type); - () = conn.set(&key, data.clone()).await?; - () = conn.publish(&key, "updated").await?; - Ok(()) - } - - async fn peek_with_timeout(&self, mailbox_id: &ShortId, channel_type: &str) -> Result> { - match tokio::time::timeout(self.timeout, self.peek(mailbox_id, channel_type)).await { - Ok(redis_result) => match redis_result { - Ok(result) => Ok(result), - Err(redis_err) => Err(Error::Redis(redis_err)), - }, - Err(elapsed) => Err(Error::Timeout(elapsed)), - } - } - - async fn peek(&self, mailbox_id: &ShortId, channel_type: &str) -> RedisResult> { - let mut conn = self.client.get_multiplexed_async_connection().await?; - let key = channel_name(mailbox_id, channel_type); - - // Attempt to fetch existing content for the given mailbox_id and channel_type - if let Ok(data) = conn.get::<_, Vec>(&key).await { - if !data.is_empty() { - return Ok(data); - } - } - debug!("Failed to fetch content initially"); - - // Set up a temporary listener for changes - let mut pubsub_conn = self.client.get_async_pubsub().await?; - let channel_name = channel_name(mailbox_id, channel_type); - pubsub_conn.subscribe(&channel_name).await?; - - // Use a block to limit the scope of the mutable borrow - let data = { - let mut message_stream = pubsub_conn.on_message(); - - loop { - match message_stream.next().await { - Some(msg) => { - () = msg.get_payload()?; // Notification received - // Try fetching the data again - if let Some(data) = conn.get::<_, Option>>(&key).await? { - if !data.is_empty() { - break data; // Exit the block, returning the data - } - } - } - None => - return Err(RedisError::from(( - ErrorKind::IoError, - "PubSub connection closed", - ))), - } - } - }; - - // Since the stream is dropped here, we can now unsubscribe - pubsub_conn.unsubscribe(&channel_name).await?; - - Ok(data) - } -} - -fn channel_name(mailbox_id: &ShortId, channel_type: &str) -> Vec { - (mailbox_id.to_string() + channel_type).into_bytes() -} diff --git a/payjoin-directory/src/db/files.rs b/payjoin-directory/src/db/files.rs new file mode 100644 index 000000000..42d9c34d3 --- /dev/null +++ b/payjoin-directory/src/db/files.rs @@ -0,0 +1,964 @@ +use std::collections::{HashMap, HashSet, VecDeque}; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; +use std::time::{Duration, SystemTime}; + +use futures::future::{self, FutureExt}; +use payjoin::directory::ShortId; +use rand::rngs::OsRng; +use rand::RngCore; +use tokio::fs::{self, File}; +use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; +use tokio::sync::{oneshot, Mutex}; +use tracing::trace; + +use super::Db as DbTrait; + +/// The maximum number of pending or populated mailbox entries. +/// +/// Defaults to around 2e6, for a generous upper bound rounded up from ~2 +/// mailboxes/tx, ~4K txs/block, and ~144 blocks/24h. +const DEFAULT_CAPACITY: usize = 1 << (1 + 12 + 8); + +const DEFAULT_UNREAD_TTL_AT_CAPACITY: Duration = Duration::from_secs(60 * 60 * 24); // 1 day +const DEFAULT_UNREAD_TTL_BELOW_CAPACITY: Duration = Duration::from_secs(60 * 60 * 24 * 7); // 1 week + +/// How long read messages should be kept in mailboxes. Defaults to a 10 minute +/// grace period from first read attempt, in case of intermittent network or +/// relay errors. +const DEFAULT_READ_TTL: Duration = Duration::from_secs(60 * 10); // 10 minutes + +#[derive(Debug)] +struct V2WaitMapEntry { + receiver: future::Shared>>>, + sender: oneshot::Sender>>, // TODO [u8; 7168] +} + +#[derive(Debug)] +struct V1WaitMapEntry { + payload: Arc>, + sender: oneshot::Sender>, +} + +#[derive(Debug)] +pub(crate) struct Mailboxes { + capacity: usize, + persistent_storage: DiskStorage, + pending_v1: HashMap, + pending_v2: HashMap, + insert_order: VecDeque<(SystemTime, ShortId)>, + read_order: VecDeque<(SystemTime, ShortId)>, + read_mailbox_ids: HashSet, + unread_ttl_below_capacity: Duration, + unread_ttl_at_capacity: Duration, + read_ttl: Duration, + early_removal_count: usize, +} + +#[derive(Debug)] +struct DiskStorage { + dir: PathBuf, + xor: Vec, +} + +impl DiskStorage { + async fn init(dir: PathBuf) -> io::Result { + let tmp_dir = &dir.join("tmp"); + if fs::try_exists(tmp_dir).await? { + // clear out any tempfiles from uncompleted writes + fs::remove_dir_all(tmp_dir).await?; + } + fs::create_dir_all(tmp_dir).await?; + + // XOR data with a random pattern to obfuscate v1 requests + // and writing malicious data such as virus fingerprints + let xor: Vec; + let xor_file = dir.join("xor.dat"); + if fs::try_exists(&xor_file).await? { + xor = fs::read(xor_file).await?; + } else { + xor = OsRng.next_u64().to_ne_bytes().to_vec(); + let mut file = fs::File::create_new(xor_file).await?; + file.write_all(&xor).await?; + file.sync_all().await?; + } + + Ok(Self { dir, xor }) + } + + fn mailbox_path(&self, id: &ShortId) -> PathBuf { self.dir.join(id.to_string()) } + + fn insert_mailbox_path(&self, id: &ShortId) -> PathBuf { + self.dir.join("tmp").join(id.to_string()) + } + + async fn contains_key(&self, id: &ShortId) -> io::Result { + fs::try_exists(self.mailbox_path(id)).await + } + + async fn get(&self, id: &ShortId) -> io::Result)>> { + // If the file doesn't exist, it's Ok(None), not Err + let mut file = match File::open(self.mailbox_path(id)).await { + Ok(file) => file, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err), + }; + + let created = file.metadata().await?.created()?; + + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).await?; + self.xor_buffer(&mut buffer); + + Ok(Some((created, buffer))) + } + + async fn try_insert( + &self, + id: &ShortId, + contents: impl AsRef<[u8]>, + ) -> io::Result> { + let mailbox_path = self.mailbox_path(id); + + // Before attempting to write the file, check if it exists and fail + // early. Otherwise the file will be written and only then linked into + // the directory, so it will still be atomic but rejected data will be + // written to disk and then discarded. + if self.contains_key(id).await? { + // Allow idempotent insertion if the contents are identical, in case + // of OHTTP retries for the same e2e message. + if let Ok(Some((created, existing_contents))) = self.get(id).await { + if &existing_contents[..] == contents.as_ref() { + return Ok(Some(created)); + } + } + + return Ok(None); + } + + // Obfuscate the contents to avoid triggering antiviruses etc due to + // malicious content. + let mut buffer = contents.as_ref().to_vec(); + self.xor_buffer(&mut buffer); + + // Write the full contents to disk under a temp path + let tmp_path = self.insert_mailbox_path(id); + let mut file = fs::File::create_new(&tmp_path).await?; + file.write_all(&buffer).await?; + file.sync_data().await?; + + // Link the directory entry to the newly written file and unlink the + // temporary entry (equivalent to rename() but without overwriting) + let link_ret = fs::hard_link(&tmp_path, &mailbox_path).await; + fs::remove_file(tmp_path).await?; // always unlink before returning + + // Return the creation time upon successful write + match link_ret { + Ok(()) => Ok(Some(file.metadata().await?.created()?)), + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(None), + Err(e) => Err(e), + } + } + + fn xor_buffer(&self, buffer: &mut [u8]) { + for (byte, &pattern) in buffer.iter_mut().zip(self.xor.iter().cycle()) { + *byte ^= pattern; + } + } + + async fn remove(&self, id: &ShortId) -> io::Result> { + match fs::remove_file(self.mailbox_path(id)).await { + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), + Ok(()) => Ok(Some(())), + Err(e) => Err(e), + } + } + + /// Returns the ShortId keys sorted by their creation time + async fn insert_order(&self) -> io::Result> { + // there's no need to stream this as it only happens once on startup + // buffering to a vec simplifies any error handling + let mut ids: Vec<(SystemTime, ShortId)> = Vec::default(); + + let mut dir_entries = fs::read_dir(&self.dir).await?; + while let Some(entry) = dir_entries.next_entry().await? { + if let Some(file_name) = entry.file_name().to_str() { + if let Ok(id) = ShortId::from_str(file_name) { + let ctime = entry.metadata().await?.created()?; + ids.push((ctime, id)); + } + } + } + + ids.sort_by_key(|&(ctime, _id)| ctime); + + Ok(ids) + } +} + +impl Mailboxes { + async fn init(dir: PathBuf) -> io::Result { + let storage = DiskStorage::init(dir).await?; + let insert_order = storage.insert_order().await?.into(); + Ok(Self { + persistent_storage: storage, + insert_order, + capacity: DEFAULT_CAPACITY, + pending_v1: HashMap::default(), + pending_v2: HashMap::default(), + read_order: VecDeque::default(), + read_mailbox_ids: HashSet::default(), + unread_ttl_below_capacity: DEFAULT_UNREAD_TTL_BELOW_CAPACITY, + unread_ttl_at_capacity: DEFAULT_UNREAD_TTL_AT_CAPACITY, + read_ttl: DEFAULT_READ_TTL, + early_removal_count: 0, + }) + } +} + +#[derive(Clone, Debug)] +pub struct Db { + timeout: Duration, + mailboxes: Arc>, +} + +impl Db { + pub async fn init(timeout: Duration, path: PathBuf) -> io::Result { + Ok(Self { timeout, mailboxes: Arc::new(Mutex::new(Mailboxes::init(path).await?)) }) + } + + pub async fn prune(&self) -> io::Result { self.mailboxes.lock().await.prune().await } + + pub async fn spawn_background_prune(&self) { + let this = self.clone(); + tokio::spawn(async move { + loop { + // TODO allow cancellation? + let sleep_for = + { this.mailboxes.lock().await.prune().await.expect("disk storage failed") }; + tokio::time::sleep(sleep_for).await; + } + }); + } +} + +impl DbTrait for Db { + type OperationalError = io::Error; + async fn post_v2_payload( + &self, + id: &ShortId, + payload: Vec, + ) -> Result, super::Error> { + let mut guard = self.mailboxes.lock().await; + Ok(guard.post_v2(id, payload).await?) + } + + async fn wait_for_v2_payload( + &self, + id: &ShortId, + ) -> Result>, super::Error> { + let receiver = { + let mut guard = self.mailboxes.lock().await; + + if let Some(payload) = guard.read(id).await? { + return Ok(payload); + } else { + guard.wait_v2(id).await? + } + }; + + let ret = match tokio::time::timeout(self.timeout, receiver).await { + Ok(payload) => Ok((payload.expect("receiver must not fail")).clone()), + Err(elapsed) => Err(super::Error::Timeout(elapsed)), + }; + + self.mailboxes.lock().await.maybe_cleanup_v2_waitmap(id); + + ret + } + + async fn post_v1_request_and_wait_for_response( + &self, + id: &ShortId, + payload: Vec, + ) -> Result>, super::Error> { + let receiver = { + self.mailboxes + .lock() + .await + .post_v1_req_and_wait(id, payload) + .await? + .ok_or(super::Error::OverCapacity)? + }; + + trace!("v1 sender waiting for v2 receiver's response"); + + let ret = match tokio::time::timeout(self.timeout, receiver).await { + Ok(payload) => Ok(Arc::new(payload.expect("receiver must not fail"))), + Err(elapsed) => Err(super::Error::Timeout(elapsed)), + }; + + // unconditionally clear the pending v1 entry. on timeout, the sender + // will no longer be available to process any replies so there is no + // point delivering the request to the receiver + self.mailboxes.lock().await.pending_v1.remove(id); + + ret + } + + async fn post_v1_response( + &self, + id: &ShortId, + payload: Vec, + ) -> Result<(), super::Error> { + let mut guard = self.mailboxes.lock().await; + Ok(guard.post_v1_res(id, payload).await?) + } +} + +// The async methods here generally use &mut self, and therefore assume mutex +// ownership of the mailbox struct. this means they are supposed to return +// quickly. however, they will wait for sync() on write, as the implies minimum +// number of requests per second is only 25, holding the mutex while waiting for +// disk and thereby serializing all writes should be fine even without an SSD. +impl Mailboxes { + async fn read(&mut self, id: &ShortId) -> io::Result>>> { + // V1 POST requests are only stored in memory since they are + // unencrypted. Check this hash table first. + if let Some(V1WaitMapEntry { payload, .. }) = self.pending_v1.get(id) { + return Ok(Some(payload.clone())); + } + + // V2 requests are stored on disk + if let Some((_created, payload)) = self.persistent_storage.get(id).await? { + self.mark_read(id); + return Ok(Some(Arc::new(payload))); + } + + Ok(None) + } + + fn mark_read(&mut self, id: &ShortId) { + if self.read_mailbox_ids.insert(*id) { + self.read_order.push_back((SystemTime::now(), *id)); + } + } + + async fn has_capacity(&mut self) -> io::Result { + self.maybe_prune().await?; + Ok(self.len() < self.capacity) + } + + async fn wait_v2( + &mut self, + id: &ShortId, + ) -> Result>>>, Error> { + if !self.has_capacity().await? { + return Err(Error::OverCapacity); + } + + if self.pending_v1.contains_key(id) { + return Err(Error::OverCapacity); + } + + let receiver = self + .pending_v2 + .entry(*id) + .or_insert_with(|| { + let (sender, receiver) = oneshot::channel::>>(); + let shared_receiver = receiver.shared(); + V2WaitMapEntry { sender, receiver: shared_receiver.clone() } + }) + .receiver + .clone(); + + Ok(receiver) + } + + fn maybe_cleanup_v2_waitmap(&mut self, id: &ShortId) { + if let Some(entry) = self.pending_v2.get(id) { + if entry.receiver.strong_count().unwrap_or(0) <= 1 { + self.pending_v2.remove(id); + } + } + } + + async fn post_v2(&mut self, id: &ShortId, payload: Vec) -> Result, Error> { + let Some(created) = self.persistent_storage.try_insert(id, &payload).await? else { + return Ok(None); + }; + + self.insert_order.push_back((created, *id)); + + // If there are pending readers, satisfy them and mark the payload as read + if let Some(pending) = self.pending_v2.remove(id) { + trace!("notifying pending readers for {}", id); + + self.mark_read(id); + + pending + .sender + .send(Arc::new(payload)) + .expect("sending on oneshot channel must succeed"); + } + + Ok(Some(())) + } + + async fn post_v1_req_and_wait( + &mut self, + id: &ShortId, + payload: Vec, + ) -> Result>>, Error> { + let mut ret = None; + let payload = Arc::new(payload); + + // Don't overwrite in flight requests + self.pending_v1.entry(*id).or_insert_with(|| { + let payload = payload.clone(); + let (sender, receiver) = oneshot::channel::>(); + ret = Some(receiver); + V1WaitMapEntry { payload, sender } + }); + + // If there are pending readers, satisfy them and mark the payload as read + if let Some(pending) = self.pending_v2.remove(id) { + trace!("notifying pending readers for {} (v1 fallback)", id); + pending.sender.send(payload).expect("sending on oneshot channel must succeed"); + } + + Ok(ret) + } + + async fn remove(&mut self, id: &ShortId) -> io::Result> { + self.read_mailbox_ids.remove(id); + self.persistent_storage.remove(id).await + } + + async fn post_v1_res(&mut self, id: &ShortId, payload: Vec) -> Result<(), Error> { + match self.pending_v1.remove(id) { + None => Err(Error::V1SenderUnavailable), + Some(V1WaitMapEntry { sender, .. }) => + sender.send(payload).map_err(|_| Error::V1SenderUnavailable), + } + } + + fn len(&self) -> usize { + (self.insert_order.len() - self.early_removal_count) + + self.pending_v1.len() + + self.pending_v2.len() + } + + async fn maybe_prune(&mut self) -> io::Result { + // TODO make this lazier, once per time interval, or once per n checks + // or both + self.prune().await + } + + /// Clean out the mailboxes. + /// + /// Since we use a mutex and not a concurrent hashmap, there's currently + /// no benefit to putting this in a background task. + /// + /// Furthermore, to improve privacy and resist mailbox enumeration, we prune + /// expired entries eagerly. + async fn prune(&mut self) -> io::Result { + trace!("pruning"); + let now = SystemTime::now(); + + debug_assert!(self.read_ttl < self.unread_ttl_at_capacity); + debug_assert!(self.unread_ttl_at_capacity < self.unread_ttl_below_capacity); + debug_assert!(self.pending_v1.iter().all(|(_, v)| !v.sender.is_closed())); + + // Prune in flight requests, these can persist in the case of an incomplete session + self.pending_v2.retain(|_, v| v.receiver.strong_count().unwrap_or(0) > 1); + + // Prune any fully expired mailboxes, whether read or unread + while let Some((created, id)) = self.insert_order.front().cloned() { + println!( + "checking if {id} elapsed: {:?} < {:?} = {}", + (created + self.unread_ttl_below_capacity), + now, + (created + self.unread_ttl_below_capacity) < now, + ); + if created + self.unread_ttl_below_capacity < now { + debug_assert!(self.insert_order.len() >= self.early_removal_count); + _ = self.insert_order.pop_front(); + if self.remove(&id).await?.is_none() { + self.early_removal_count = self + .early_removal_count + .checked_sub(1) + .expect("early removal adjustment should never underflow"); + } + debug_assert!(self.insert_order.len() >= self.early_removal_count); + trace!("Pruned old mailbox {id}"); + } else { + break; + } + } + + // So long as there expired read mailboxes, prune those. Stop when a + // mailbox within the TTL is encountered. + while let Some((read, id)) = self.read_order.front().cloned() { + println!( + "checking if {id} elapsed (read ttl): {:?} < {:?} = {}", + (read + self.read_ttl), + now, + (read + self.read_ttl) < now, + ); + if read + self.read_ttl < now { + println!("removing"); + _ = self.read_order.pop_front(); + if self.remove(&id).await?.is_some() { + self.early_removal_count += 1; + debug_assert!(self.insert_order.len() >= self.early_removal_count); + } + trace!("Pruned read mailbox {id}"); + } else { + break; + } + } + + // If no room was created, try to prune the oldest unread mailbox if + // it's over the minimum TTL + debug_assert!(self.len() <= self.capacity); + if self.len() == self.capacity { + if let Some((created, id)) = self.insert_order.front().cloned() { + if created + self.unread_ttl_at_capacity < now { + _ = self.insert_order.pop_front(); + self.remove(&id).await?; + trace!("Pruned unread mailbox {id} to make room"); + } else { + trace!("Nothing to prune, {} entries remain", self.len()); + } + } + } + + Ok(self.next_prune()) + } + + fn next_prune(&mut self) -> Duration { + let earliest_read_prune_opportunity = self + .read_order + .front() + .map(|(read, _id)| { + self.read_ttl + .checked_sub(read.elapsed().expect("system clock moved back")) + .unwrap_or(self.read_ttl) + }) + .unwrap_or_else(|| self.read_ttl); + + let earliest_unread_prune_opportunity = self + .insert_order + .front() + .map(|(created, _id)| { + self.unread_ttl_at_capacity + .checked_sub(created.elapsed().expect("system clock moved back")) + .unwrap_or(self.unread_ttl_at_capacity) + }) + .unwrap_or_else(|| self.unread_ttl_at_capacity); + + std::cmp::min(earliest_read_prune_opportunity, earliest_unread_prune_opportunity) + } +} + +#[derive(Debug)] +pub enum Error { + /// Operation rejected due to lack of capacity + OverCapacity, + + /// Indicates the sender that was waiting for the reply is no longer there + V1SenderUnavailable, + + IO(io::Error), +} + +impl From for Error { + fn from(e: io::Error) -> Self { Self::IO(e) } +} + +// FIXME why isn't this sufficient for ?, necessitating ugly map_err(into)? +impl From for super::Error { + fn from(val: Error) -> super::Error { + match val { + Error::V1SenderUnavailable => super::Error::V1SenderUnavailable, + Error::OverCapacity => super::Error::OverCapacity, + Error::IO(e) => super::Error::Operational(e), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::IO(e) => Some(e), + _ => None, + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use Error::*; + match self { + OverCapacity => "Database over capacity".fmt(f), + V1SenderUnavailable => "Sender no longer connected".fmt(f), + IO(e) => write!(f, "Internal Error: {e}"), + } + } +} + +impl super::SendableError for Error {} + +#[tokio::test] +async fn test_disk_storage_initialization() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + assert!(!dir.path().join("tmp").exists(), "tmp subdirectory should not have been created yet"); + + let xor_pattern = { + let storage = DiskStorage::init(dir.path().to_owned()) + .await + .expect("initializing storage directory should succeed"); + + assert!(dir.path().join("tmp").exists(), "tmp subdirectory should have been created"); + assert!( + dir.path().join("xor.dat").exists(), + "random obfuscation pattern should have been generated" + ); + + fs::write(dir.path().join("tmp").join("blah"), "junk").await?; + + storage.xor + }; + + assert!( + dir.path().join("tmp").join("blah").exists(), + "temp file should not have been cleared yet" + ); + let storage = DiskStorage::init(dir.path().to_owned()) + .await + .expect("initializing storage directory should succeed"); + + assert!(!dir.path().join("tmp").join("blah").exists(), "temp file should have been cleared"); + + assert_eq!(storage.xor, xor_pattern, "xor pattern loaded from file"); + + Ok(()) +} + +#[tokio::test] +async fn test_disk_storage_mailboxes() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + + let storage = DiskStorage::init(dir.path().to_owned()) + .await + .expect("initializing storage directory should succeed"); + + let id1 = ShortId::try_from(&(b"12345678")[..]).unwrap(); + let id2 = ShortId::try_from(&(b"87654321")[..]).unwrap(); + + assert!(!storage + .contains_key(&id1) + .await + .expect("checking mailbox existence should not error")); + assert!(!storage + .contains_key(&id2) + .await + .expect("checking mailbox existence should not error")); + assert!(matches!(storage.get(&id1).await, Ok(None))); + assert!(matches!(storage.get(&id2).await, Ok(None))); + + let contents1 = b"OH HAI"; + let contents2 = b"HI FREN"; + + let created1 = storage + .try_insert(&id1, contents1) + .await + .expect("writing should succeed") + .expect("writing should return a creation time"); + + match storage.get(&id1).await { + Ok(Some((got_created, got_contents))) => { + assert_eq!(got_created, created1.to_owned()); + assert_eq!(got_contents, contents1.to_owned()); + } + e => { + e.expect("retrieval should work"); + } + }; + + assert!(matches!(storage.get(&id2).await, Ok(None))); + + assert!( + storage + .try_insert(&id1, contents2) + .await + .expect("writing a second time should not fail with IO error") + .is_none(), + "writing a second time should be rejected", + ); + + assert_eq!( + storage.try_insert(&id1, contents1).await.expect("idempotent write should not fail"), + Some(created1), + "idempotent write should have the same creation time", + ); + + tokio::time::sleep(Duration::from_millis(1)).await; + + let created2 = storage + .try_insert(&id2, contents2) + .await + .expect("writing should succeed") + .expect("writing should return a creation time"); + + assert!(created1 < created2, "creation times should be ordered as expected"); + + assert_eq!( + storage.insert_order().await.expect("enumeration should succeed"), + vec![(created1, id1), (created2, id2)], + "enumeration should return expected keys and creation times", + ); + + let mut file_contents = + fs::read(storage.mailbox_path(&id1)).await.expect("mailbox file should be readable"); + + assert_eq!(file_contents.len(), contents1.len(), "file data should have the right length"); + assert_ne!(file_contents, contents1, "file data should be obfuscated"); + + storage.xor_buffer(&mut file_contents[..]); + assert_eq!(file_contents, contents1, "deobfuscation should recover contents"); + + storage.remove(&id1).await.expect("removing an existing mailbox should succeed"); + assert!( + !storage.contains_key(&id1).await.expect("checking existence should not error"), + "mailbox file should no longer exist" + ); + storage.remove(&id1).await.expect("removing a non-existing mailbox should still not error"); + + assert_eq!( + storage.insert_order().await.expect("enumeration should succeed"), + vec![(created2, id2)], + "enumeration should return expected keys and creation times", + ); + + Ok(()) +} + +#[tokio::test] +async fn test_mailbox_storage() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + + let db = Db::init(Duration::from_millis(10), dir.path().to_owned()) + .await + .expect("initializing mailbox database should succeed"); + + let id = ShortId([0u8; 8]); + let contents = b"foo bar"; + db.post_v2_payload(&id, contents.to_vec()) + .await + .expect("posting payload should succeed") + .expect("contents should be accepted"); + + let res = db.wait_for_v2_payload(&id).await.expect("waiting for payload should succeed"); + assert_eq!(&res[..], contents, "posted payload should be retrievable"); + + Ok(()) +} + +#[tokio::test] +async fn test_v2_wait() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + + let db = Db::init(Duration::from_millis(1), dir.path().to_owned()) + .await + .expect("initializing mailbox database should succeed"); + + let id = ShortId([0u8; 8]); + let contents = b"foo bar"; + + match db.wait_for_v2_payload(&id).await { + Err(super::Error::Timeout(_)) => {} + res => panic!("expected timeout, got {:?}", res), + } + + let read_task1 = tokio::spawn({ + let db = db.clone(); + async move { db.wait_for_v2_payload(&id).await } + }); + let read_task2 = tokio::spawn({ + let db = db.clone(); + async move { db.wait_for_v2_payload(&id).await } + }); + + db.post_v2_payload(&id, contents.to_vec()) + .await + .expect("posting payload should succeed") + .expect("contents should be accepted"); + + let res = read_task1 + .await + .expect("joining task should succeed") + .expect("waiting for payload should succeed"); + assert_eq!(&res[..], contents, "posted payload should be retrievable"); + + let res = read_task2 + .await + .expect("joining task should succeed") + .expect("waiting for payload should succeed"); + assert_eq!(&res[..], contents, "posted payload should be retrievable"); + + assert!( + db.post_v2_payload(&id, b"something else".to_vec()) + .await + .expect("posting payload should succeed") + .is_none(), + "duplicate POST should be rejected" + ); + + let res = db.wait_for_v2_payload(&id).await.expect("reading payload should succeed"); + assert_eq!(&res[..], contents, "posted payload should be retrievable"); + + Ok(()) +} + +#[tokio::test] +async fn test_v1_wait() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + + let db = Arc::new( + Db::init(Duration::from_millis(1), dir.path().to_owned()) + .await + .expect("initializing mailbox database should succeed"), + ); + + let id = ShortId([0u8; 8]); + + let v1_sender_task = tokio::spawn({ + let db = db.clone(); + async move { db.post_v1_request_and_wait_for_response(&id, b"request".to_vec()).await } + }); + + let res = db.wait_for_v2_payload(&id).await.expect("reading payload should succeed"); + assert_eq!(&res[..], b"request", "in flight v1 request should be retrievable"); + + assert!( + matches!( + db.post_v1_request_and_wait_for_response(&id, b"different request".to_vec()).await, + Err(super::Error::OverCapacity), + ), + "second v1 sender with the same shortid should be rejected while request is in flight", + ); + + db.post_v1_response(&id, b"response".to_vec()).await.expect("posting payload should succeed"); + + let res = v1_sender_task + .await + .expect("joining task should succeed") + .expect("waiting for payload should succeed"); + assert_eq!(&res[..], b"response", "should be response from v2 receiver"); + + assert!( + matches!( + db.post_v1_response(&id, b"response".to_vec()).await, + Err(super::Error::V1SenderUnavailable) + ), + "posting without a v1 sender waiting should fail" + ); + + Ok(()) +} + +// FIXME test is a bit slow and flakey, how to improve? +// unfortunately tokio::time::pause() can't be used because this uses SystemTime +// as the underlying clock type, due to timestamps originating from disk +#[tokio::test] +async fn test_prune() -> std::io::Result<()> { + let dir = tempfile::tempdir()?; + + let db = Db::init(Duration::from_millis(2), dir.path().to_owned()) + .await + .expect("initializing mailbox database should succeed"); + + { + let mut guard = db.mailboxes.lock().await; + guard.capacity = 2; + guard.read_ttl = Duration::from_millis(10); + guard.unread_ttl_at_capacity = Duration::from_millis(100); + guard.unread_ttl_below_capacity = Duration::from_millis(200); + } + + assert_eq!(db.mailboxes.lock().await.len(), 0); + db.prune().await.expect("pruning should not fail"); + assert_eq!(db.mailboxes.lock().await.len(), 0); + + let id = ShortId([0u8; 8]); + let contents = b"fooo"; + + let read_task1 = tokio::spawn({ + let db = db.clone(); + async move { db.wait_for_v2_payload(&id).await } + }); + + tokio::time::sleep(Duration::from_millis(1)).await; + assert_eq!(db.mailboxes.lock().await.len(), 1); + + match read_task1.await.expect("joining should succeed") { + Err(super::Error::Timeout(_)) => {} + res => panic!("expected timeout, got {:?}", res), + } + + db.prune().await.expect("pruning should not fail"); + assert_eq!(db.mailboxes.lock().await.len(), 0); + + db.post_v2_payload(&id, contents.to_vec()) + .await + .expect("posting payload should succeed") + .expect("contents should be accepted"); + + assert_eq!(db.mailboxes.lock().await.len(), 1); + db.prune().await.expect("pruning should not fail"); + assert_eq!(db.mailboxes.lock().await.len(), 1); + + tokio::time::sleep(Duration::from_millis(200)).await; + + assert_eq!(db.mailboxes.lock().await.len(), 1); + db.prune().await.expect("pruning should not fail"); + assert_eq!(db.mailboxes.lock().await.len(), 0); + + db.post_v2_payload(&id, contents.to_vec()) + .await + .expect("posting payload should succeed") + .expect("contents should be accepted"); + + assert_eq!(db.mailboxes.lock().await.len(), 1); + // FIXME why does this fail? + // db.prune().await.expect("pruning should not fail"); + // assert_eq!(db.mailboxes.lock().await.len(), 1); + // mailbox seems to get pruned prematurely + // likely cause is it's in both read and insert queue, and two pruning runs + // are needed to fully clear it? + + // mark the mailbox as read + _ = db.wait_for_v2_payload(&id).await.expect("waiting for payload should succeed"); + + assert_eq!(db.mailboxes.lock().await.len(), 1); + // FIXME db.prune().await.expect("pruning should not fail"); + assert_eq!(db.mailboxes.lock().await.len(), 1); + + // allow read TTL to elapse + tokio::time::sleep(Duration::from_millis(10)).await; + + assert_eq!(db.mailboxes.lock().await.len(), 1); + db.prune().await.expect("pruning should not fail"); + assert_eq!(db.mailboxes.lock().await.len(), 0); + + tokio::time::sleep(Duration::from_millis(200)).await; + + assert_eq!(db.mailboxes.lock().await.len(), 0); + db.prune().await.expect("pruning should not fail"); + assert_eq!(db.mailboxes.lock().await.len(), 0); + + Ok(()) +} diff --git a/payjoin-directory/src/db/mod.rs b/payjoin-directory/src/db/mod.rs new file mode 100644 index 000000000..b4970a532 --- /dev/null +++ b/payjoin-directory/src/db/mod.rs @@ -0,0 +1,82 @@ +use std::future::Future; +use std::result::Result; +use std::sync::Arc; + +use payjoin::directory::ShortId; + +pub(crate) mod files; + +pub trait SendableError: + std::error::Error + std::marker::Send + std::marker::Sync + std::convert::Into +{ +} + +#[derive(Debug)] +pub enum Error { + Operational(OperationalError), + Timeout(tokio::time::error::Elapsed), + OverCapacity, + V1SenderUnavailable, +} + +impl SendableError for tokio::time::error::Elapsed {} +impl SendableError for std::io::Error {} + +impl From for Error { + fn from(value: E) -> Self { Error::Operational(value) } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use Error::*; + match &self { + Operational(error) => write!(f, "Db error: {error}"), + Timeout(timeout) => write!(f, "Timeout: {timeout}"), + OverCapacity => "Database over capacity".fmt(f), + V1SenderUnavailable => "Sender no longer connected".fmt(f), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use Error::*; + match self { + Operational(e) => Some(e), + Timeout(e) => Some(e), + _ => None, + } + } +} + +// TODO split into v1 and v2 traits +pub trait Db: Clone + Send + Sync + 'static { + type OperationalError: SendableError + 'static; + + /// Store a v2 payload. + fn post_v2_payload( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> impl Future, Error>> + Send; + + /// Read a stored v1 request or v2 payload, waiting if not yet posted. + fn wait_for_v2_payload( + &self, + mailbox_id: &ShortId, + ) -> impl Future>, Error>> + Send; + + /// Write a v1 response payload. + fn post_v1_response( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> impl Future>> + Send; + + /// Store a v1 request payload, waiting for any response. + fn post_v1_request_and_wait_for_response( + &self, + mailbox_id: &ShortId, + data: Vec, + ) -> impl Future>, Error>> + Send; +} diff --git a/payjoin-directory/src/lib.rs b/payjoin-directory/src/lib.rs index 71caff21a..87e3b799d 100644 --- a/payjoin-directory/src/lib.rs +++ b/payjoin-directory/src/lib.rs @@ -1,5 +1,6 @@ use std::pin::Pin; use std::str::FromStr; +use std::sync::Arc; use anyhow::Result; use http_body_util::combinators::BoxBody; @@ -12,7 +13,8 @@ use hyper_util::rt::TokioIo; use payjoin::directory::{ShortId, ShortIdError, ENCAPSULATED_MESSAGE_BYTES}; use tracing::{debug, error, trace, warn}; -pub use crate::db::DbPool; +pub use crate::db::files::Db as FilesDb; +use crate::db::Db; pub mod key_config; pub use crate::key_config::*; use crate::metrics::Metrics; @@ -27,7 +29,7 @@ const V1_REJECT_RES_JSON: &str = r#"{{"errorCode": "original-psbt-rejected ", "message": "Body is not a string"}}"#; const V1_UNAVAILABLE_RES_JSON: &str = r#"{{"errorCode": "unavailable", "message": "V2 receiver offline. V1 sends require synchronous communications."}}"#; -mod db; +pub(crate) mod db; pub mod cli; pub mod config; @@ -54,30 +56,27 @@ fn init_tls_acceptor(cert_key: (Vec, Vec)) -> Result { + db: D, ohttp: ohttp::Server, metrics: Metrics, } -impl hyper::service::Service> for Service { +impl hyper::service::Service> for Service { type Response = Response>; type Error = anyhow::Error; type Future = Pin> + Send>>; fn call(&self, req: Request) -> Self::Future { - let pool = self.pool.clone(); - let ohttp = self.ohttp.clone(); - let metrics = self.metrics.clone(); - let this = Service::new(pool, ohttp, metrics); + let this = self.clone(); Box::pin(async move { this.serve_request(req).await }) } } -impl Service { - pub fn new(pool: DbPool, ohttp: ohttp::Server, metrics: Metrics) -> Self { - Self { pool, ohttp, metrics } +impl Service { + pub fn new(db: D, ohttp: ohttp::Server, metrics: Metrics) -> Self { + Self { db, ohttp, metrics } } #[cfg(feature = "_manual-tls")] @@ -252,7 +251,7 @@ impl Service { return Err(HandlerError::PayloadTooLarge); } - match self.pool.push_default(&id, req.into()).await { + match self.db.post_v2_payload(&id, req.into()).await { Ok(_) => Ok(none_response), Err(e) => Err(HandlerError::InternalServerError(e.into())), } @@ -265,7 +264,7 @@ impl Service { trace!("get_mailbox"); let id = ShortId::from_str(id)?; let timeout_response = Response::builder().status(StatusCode::ACCEPTED).body(empty())?; - handle_peek(self.pool.peek_default(&id).await, timeout_response) + handle_peek(self.db.wait_for_v2_payload(&id).await, timeout_response) } async fn put_payjoin_v1( &self, @@ -285,7 +284,7 @@ impl Service { return Err(HandlerError::PayloadTooLarge); } - match self.pool.push_v1(&id, req.into()).await { + match self.db.post_v1_response(&id, req.into()).await { Ok(_) => Ok(ok_response), Err(e) => Err(HandlerError::BadRequest(e.into())), } @@ -316,11 +315,10 @@ impl Service { let v2_compat_body = format!("{body_str}\n{query}"); let id = ShortId::from_str(id)?; - self.pool - .push_default(&id, v2_compat_body.into()) - .await - .map_err(|e| HandlerError::BadRequest(e.into()))?; - handle_peek(self.pool.peek_v1(&id).await, none_response) + handle_peek( + self.db.post_v1_request_and_wait_for_response(&id, v2_compat_body.into()).await, + none_response, + ) } async fn handle_ohttp_gateway_get( @@ -378,20 +376,45 @@ impl Service { } } } + + pub async fn serve_metrics_tcp( + &self, + listener: tokio::net::TcpListener, + ) -> Result<(), BoxError> { + while let Ok((stream, _)) = listener.accept().await { + let io = TokioIo::new(stream); + let service = self.clone(); + tokio::spawn(async move { + if let Err(err) = + http1::Builder::new().serve_connection(io, service).with_upgrades().await + { + error!("Error serving connection: {:?}", err); + } + }); + } + + Ok(()) + } } -fn handle_peek( - result: db::Result>, +fn handle_peek( + result: Result>, db::Error>, timeout_response: Response>, ) -> Result>, HandlerError> { match result { - Ok(buffered_req) => Ok(Response::new(full(buffered_req))), + Ok(payload) => Ok(Response::new(full((*payload).clone()))), // TODO Bytes instead of Arc> Err(e) => match e { - db::Error::Redis(re) => { - error!("Redis error: {}", re); + db::Error::Operational(err) => { + error!("Storage error: {err}"); Err(HandlerError::InternalServerError(anyhow::Error::msg("Internal server error"))) } db::Error::Timeout(_) => Ok(timeout_response), + db::Error::OverCapacity => Err(HandlerError::ServiceUnavailable(anyhow::Error::msg( + "mailbox storage at capacity", + ))), + db::Error::V1SenderUnavailable => Err(HandlerError::SenderGone(anyhow::Error::msg( + "Sender is unavailable try a new request", + ))), }, } } @@ -465,6 +488,8 @@ async fn handle_directory_home_path() -> Result { + error!("Service temporarily unavailable: {}", e); + *res.status_mut() = StatusCode::SERVICE_UNAVAILABLE + } + HandlerError::SenderGone(e) => { + error!("Sender gone: {}", e); + *res.status_mut() = StatusCode::GONE + } HandlerError::OhttpKeyRejection(e) => { const OHTTP_KEY_REJECTION_RES_JSON: &str = r#"{"type":"https://iana.org/assignments/http-problem-types#ohttp-key", "title": "key identifier unknown"}"#; @@ -520,22 +553,3 @@ fn empty() -> BoxBody { fn full>(chunk: T) -> BoxBody { Full::new(chunk.into()).map_err(|never| match never {}).boxed() } - -pub async fn serve_metrics_tcp( - service: Service, - listener: tokio::net::TcpListener, -) -> Result<(), BoxError> { - while let Ok((stream, _)) = listener.accept().await { - let io = TokioIo::new(stream); - let service = service.clone(); - tokio::spawn(async move { - if let Err(err) = - http1::Builder::new().serve_connection(io, service).with_upgrades().await - { - error!("Error serving connection: {:?}", err); - } - }); - } - - Ok(()) -} diff --git a/payjoin-directory/src/main.rs b/payjoin-directory/src/main.rs index 45fc825bd..8037798dc 100644 --- a/payjoin-directory/src/main.rs +++ b/payjoin-directory/src/main.rs @@ -2,7 +2,6 @@ use clap::Parser; use payjoin_directory::metrics::Metrics; use payjoin_directory::*; use tokio::net::TcpListener; -use tracing::error; use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::EnvFilter; @@ -26,22 +25,13 @@ async fn main() -> Result<(), BoxError> { } }; - let db = DbPool::new(config.timeout, config.db_host).await?; let metrics = Metrics::new(); + let db = payjoin_directory::FilesDb::init(config.timeout, config.storage_dir) + .await + .expect("Failed to initialize persistent storage"); let service = Service::new(db, ohttp.into(), metrics); - // Start metrics server in the background - let metrics_listener = TcpListener::bind(config.metrics_listen_addr).await?; - { - let service = service.clone(); - tokio::spawn(async move { - if let Err(e) = payjoin_directory::serve_metrics_tcp(service, metrics_listener).await { - error!("Metrics server error: {e}"); - } - }); - } - let listener = TcpListener::bind(config.listen_addr).await?; service.serve_tcp(listener).await } diff --git a/payjoin-test-utils/Cargo.toml b/payjoin-test-utils/Cargo.toml index 753717d14..567175be8 100644 --- a/payjoin-test-utils/Cargo.toml +++ b/payjoin-test-utils/Cargo.toml @@ -21,8 +21,8 @@ payjoin-directory = { version = "0.0.3", features = ["_manual-tls"] } rcgen = "0.14.3" rustls = { version = "0.23.31", default-features=false, features = ["ring"] } reqwest = { version = "0.12.23", default-features = false, features = ["rustls-tls"] } -testcontainers-modules = { version = "0.12.1", features = ["redis"]} tokio = { version = "1.47.1", features = ["full"] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } url = "2.5.4" +tempfile = "3.20.0" diff --git a/payjoin-test-utils/src/lib.rs b/payjoin-test-utils/src/lib.rs index cac755082..b6532678c 100644 --- a/payjoin-test-utils/src/lib.rs +++ b/payjoin-test-utils/src/lib.rs @@ -17,9 +17,7 @@ use rcgen::Certificate; use reqwest::{Client, ClientBuilder}; use rustls::pki_types::CertificateDer; use rustls::RootCertStore; -use testcontainers_modules::redis::{Redis, REDIS_PORT}; -use testcontainers_modules::testcontainers::runners::AsyncRunner; -use testcontainers_modules::testcontainers::ContainerAsync; +use tempfile::tempdir; use tokio::net::TcpListener; use tokio::task::JoinHandle; use tracing::Level; @@ -49,8 +47,6 @@ pub fn init_tracing() { pub struct TestServices { cert: Certificate, /// redis is an implicit dependency of the directory service - #[allow(dead_code)] - redis: (u16, ContainerAsync), directory: (u16, Option>>), ohttp_relay: (u16, Option>>), http_agent: Arc, @@ -67,9 +63,8 @@ impl TestServices { let mut root_store = RootCertStore::empty(); root_store.add(CertificateDer::from(cert.cert.der().to_vec())).unwrap(); - let redis = init_redis().await; - let db_host = format!("127.0.0.1:{}", redis.0); - let directory = init_directory(db_host, cert_key).await?; + let directory = init_directory(cert_key).await?; + let gateway_origin = ohttp_relay::GatewayUri::from_str(&format!("https://localhost:{}", directory.0))?; let ohttp_relay = ohttp_relay::listen_tcp_on_free_port(gateway_origin, root_store).await?; @@ -77,7 +72,6 @@ impl TestServices { Ok(Self { cert: cert.cert, - redis, directory: (directory.0, Some(directory.1)), ohttp_relay: (ohttp_relay.0, Some(ohttp_relay.1)), http_agent, @@ -125,17 +119,7 @@ impl TestServices { } } -pub async fn init_redis() -> (u16, ContainerAsync) { - let redis_instance = Redis::default().start().await.expect("redis container should start"); - let host_port = redis_instance - .get_host_port_ipv4(REDIS_PORT) - .await - .expect("redis instance should have port"); - (host_port, redis_instance) -} - pub async fn init_directory( - db_host: String, local_cert_key: (Vec, Vec), ) -> std::result::Result< (u16, tokio::task::JoinHandle>), @@ -144,15 +128,19 @@ pub async fn init_directory( let timeout = Duration::from_secs(2); let ohttp_server = payjoin_directory::gen_ohttp_server_config()?; - println!("Database running on {db_host}"); - let db = payjoin_directory::DbPool::new(timeout, db_host).await?; let metrics = payjoin_directory::metrics::Metrics::new(); + let tempdir = tempdir()?; + let db = payjoin_directory::FilesDb::init(timeout, tempdir.path().to_path_buf()).await?; + let service = payjoin_directory::Service::new(db, ohttp_server.into(), metrics); let listener = bind_free_port().await?; let port = listener.local_addr()?.port(); - let handle = tokio::spawn(service.serve_tls(listener, local_cert_key)); + let handle = tokio::spawn(async move { + let _tempdir = tempdir; // keep the tempdir until the directory shuts down + service.serve_tls(listener, local_cert_key).await + }); Ok((port, handle)) } diff --git a/payjoin/src/directory.rs b/payjoin/src/directory.rs index 5d5ff79a6..9c1b1a9f7 100644 --- a/payjoin/src/directory.rs +++ b/payjoin/src/directory.rs @@ -19,7 +19,7 @@ pub const ENCAPSULATED_MESSAGE_BYTES: usize = 8192; /// /// Note: This implementation assumes ephemeral public keys with sufficient entropy. The short length /// is an intentional tradeoff that provides adequate practical uniqueness while reducing DoS surface. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "_core", derive(serde::Serialize, serde::Deserialize))] pub struct ShortId(pub [u8; 8]);