diff --git a/Cargo.lock b/Cargo.lock index 89eb3492..bfe6f7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aead" version = "0.4.3" @@ -13,24 +28,31 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom 0.2.5", + "cfg-if 1.0.0", + "getrandom 0.2.10", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "ansi_term" version = "0.12.1" @@ -42,9 +64,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "assert-json-diff" @@ -58,34 +80,44 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite 0.2.13", ] [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", ] [[package]] @@ -112,11 +144,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.5", + "getrandom 0.2.10", "instant", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "rand 0.8.5", - "tokio 1.25.0", + "tokio 1.32.0", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", ] [[package]] @@ -127,15 +174,15 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64-compat" @@ -146,6 +193,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bech32" version = "0.8.1" @@ -154,9 +207,9 @@ checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" [[package]] name = "bitcoin" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05bba324e6baf655b882df672453dbbc527bc938cadd27750ae510aaccc3a66a" +checksum = "d4d30fb43d287492017964a1fd7d3f82e8cc760818471c6ef2d44111e317d5c3" dependencies = [ "base64-compat", "bech32", @@ -204,6 +257,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -216,9 +278,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -231,18 +293,19 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bstr" -version = "0.2.17" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" @@ -258,15 +321,18 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -282,21 +348,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.7.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08493fa7707effc63254c66c6ea908675912493cd67952eda23c09fae2610b1" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.1.5", "zeroize", ] [[package]] name = "chacha20poly1305" -version = "0.8.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6547abe025f4027edacd9edaa357aded014eecec42a5070d9b885c3c334aba2" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" dependencies = [ "aead", "chacha20", @@ -307,19 +373,18 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ - "num-integer", "num-traits", ] [[package]] name = "chunked_transfer" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" [[package]] name = "cipher" @@ -338,7 +403,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", @@ -347,33 +412,39 @@ dependencies = [ [[package]] name = "cln-plugin" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49be99e6e5ad55d420884b5b2a68aca890bcd1a1540ed6d2892363623a60f538" +checksum = "1098794b7562120ec5caa7b768847655fd5249088676a8d8ba9110a01becf97b" dependencies = [ "anyhow", - "bytes 1.1.0", + "bytes 1.5.0", "env_logger", "futures", "log", "serde", "serde_json", - "tokio 1.25.0", + "tokio 1.32.0", "tokio-stream", - "tokio-util 0.7.0", + "tokio-util 0.7.8", ] [[package]] name = "colored" -version = "2.0.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ - "atty", + "is-terminal", "lazy_static", - "winapi 0.3.9", + "windows-sys", ] +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + [[package]] name = "convert_case" version = "0.4.0" @@ -392,24 +463,67 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -427,9 +541,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", @@ -440,29 +554,21 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] -name = "deadpool" -version = "0.9.5" +name = "der" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ - "async-trait", - "deadpool-runtime", - "num_cpus", - "retain_mut", - "tokio 1.25.0", + "const-oid", + "pem-rfc7468", + "zeroize", ] -[[package]] -name = "deadpool-runtime" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" - [[package]] name = "der-oid-macro" version = "0.5.0" @@ -471,7 +577,7 @@ checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436" dependencies = [ "num-bigint", "num-traits", - "syn", + "syn 1.0.109", ] [[package]] @@ -487,6 +593,12 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + [[package]] name = "derive_more" version = "0.99.17" @@ -497,7 +609,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] @@ -515,17 +627,25 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.4", + "const-oid", "crypto-common", + "subtle", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", ] [[package]] @@ -538,21 +658,24 @@ dependencies = [ "ed25519", "rand 0.7.3", "serde", - "sha2", + "sha2 0.9.9", "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if 1.0.0", ] @@ -570,15 +693,21 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.2.8" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -591,6 +720,23 @@ dependencies = [ "libc", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if 1.0.0", + "home", + "windows-sys", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -605,18 +751,33 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "1.7.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flume" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] [[package]] name = "fnv" @@ -641,11 +802,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ - "matches", "percent-encoding", ] @@ -661,7 +821,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fuchsia-zircon-sys", ] @@ -673,9 +833,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -688,9 +848,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -698,55 +858,66 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.1", +] + [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -755,16 +926,16 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "pin-utils", "slab", ] [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -783,20 +954,26 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "globset" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" dependencies = [ "aho-corasick", "bstr", @@ -817,7 +994,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio 0.2.25", "tokio-util 0.3.1", @@ -827,55 +1004,61 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "fnv", "futures-core", "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", - "tokio 1.25.0", - "tokio-util 0.6.9", + "tokio 1.32.0", + "tokio-util 0.7.8", "tracing", ] [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash", + "allocator-api2", ] [[package]] name = "hashlink" -version = "0.7.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown", + "hashbrown 0.14.0", ] [[package]] name = "headers" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.0", - "bitflags", - "bytes 1.1.0", + "base64 0.21.4", + "bytes 1.5.0", "headers-core", "http", - "httpdate 1.0.2", + "httpdate 1.0.3", "mime", - "sha-1", + "sha1", ] [[package]] @@ -896,6 +1079,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -907,12 +1099,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -923,6 +1112,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.11.0" @@ -933,24 +1131,33 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "home" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "winapi 0.3.9", + "windows-sys", ] [[package]] name = "http" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "fnv", - "itoa 1.0.1", + "itoa 1.0.9", ] [[package]] @@ -965,20 +1172,20 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "http", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", ] [[package]] name = "httparse" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -988,9 +1195,9 @@ checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -1024,23 +1231,23 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.18" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "futures-channel", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.21", "http", - "http-body 0.4.4", + "http-body 0.4.5", "httparse", - "httpdate 1.0.2", - "itoa 1.0.1", - "pin-project-lite 0.2.8", - "socket2 0.4.4", - "tokio 1.25.0", + "httpdate 1.0.3", + "itoa 1.0.9", + "pin-project-lite 0.2.13", + "socket2 0.4.9", + "tokio 1.32.0", "tower-service", "tracing", "want", @@ -1052,9 +1259,9 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.18", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "hyper 0.14.27", + "pin-project-lite 0.2.13", + "tokio 1.32.0", "tokio-io-timeout", ] @@ -1064,51 +1271,50 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", - "hyper 0.14.18", + "bytes 1.5.0", + "hyper 0.14.27", "native-tls", - "tokio 1.25.0", + "tokio 1.32.0", "tokio-native-tls", ] [[package]] name = "idna" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ - "cfg-if 1.0.0", + "equivalent", + "hashbrown 0.14.0", ] [[package]] -name = "io-lifetimes" -version = "1.0.3" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "libc", - "windows-sys 0.42.0", + "cfg-if 1.0.0", ] [[package]] @@ -1122,27 +1328,35 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.2.6", - "io-lifetimes", + "hermit-abi 0.3.2", "rustix", - "windows-sys 0.42.0", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] name = "itertools" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] @@ -1155,15 +1369,15 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1230,9 +1444,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures 0.2.9", +] [[package]] name = "kernel32-sys" @@ -1249,18 +1466,27 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" -version = "0.2.139" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libsqlite3-sys" -version = "0.23.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" dependencies = [ "cc", "pkg-config", @@ -1298,50 +1524,51 @@ checksum = "2f0170619152c4d6b947d5ed0de427b85691482a293e0cae52d4336a2220a776" dependencies = [ "bitcoin", "lightning", - "tokio 1.25.0", + "tokio 1.32.0", ] [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.16" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "matches" -version = "0.1.9" +name = "md-5" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest 0.10.7", +] [[package]] name = "memchr" -version = "2.4.1" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -1359,6 +1586,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.6.23" @@ -1380,14 +1616,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] @@ -1404,16 +1639,14 @@ dependencies = [ [[package]] name = "mockito" -version = "0.32.4" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa08cccbc31b07113e4322d79df4464e0ed888032fc67ec56136325cd3afecf" +checksum = "406f43768da5a859ce19bb0978fd8dc2167a7d9a52f3935c6a187242e1a4ff9f" dependencies = [ "assert-json-diff", - "async-trait", "colored", - "deadpool", "futures", - "hyper 0.14.18", + "hyper 0.14.27", "lazy_static", "log", "rand 0.8.5", @@ -1421,7 +1654,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "similar", - "tokio 1.25.0", + "tokio 1.32.0", ] [[package]] @@ -1430,7 +1663,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "encoding_rs", "futures-util", "http", @@ -1450,9 +1683,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -1468,9 +1701,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.37" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1479,9 +1712,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -1489,15 +1722,32 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1508,34 +1758,55 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.3.2", "libc", ] [[package]] name = "num_threads" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.2.0" @@ -1547,9 +1818,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -1559,11 +1830,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.40" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags", + "bitflags 2.4.0", "cfg-if 1.0.0", "foreign-types", "libc", @@ -1574,13 +1845,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] @@ -1591,11 +1862,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.74" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", @@ -1610,7 +1880,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -1620,79 +1890,94 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.6", + "parking_lot_core 0.9.8", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi 0.3.9", ] [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.42.0", + "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pem" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.0.0", ] [[package]] name = "pin-project" -version = "1.0.10" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] @@ -1703,9 +1988,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1713,11 +1998,32 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "poly1305" @@ -1725,16 +2031,16 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.9", "opaque-debug", "universal-hash", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-error" @@ -1745,7 +2051,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -1762,9 +2068,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -1775,7 +2081,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "prost-derive 0.8.0", ] @@ -1785,7 +2091,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "prost-derive 0.9.0", ] @@ -1795,9 +2101,9 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.1.0", - "heck", - "itertools", + "bytes 1.5.0", + "heck 0.3.3", + "itertools 0.10.5", "lazy_static", "log", "multimap", @@ -1816,10 +2122,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1829,10 +2135,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1841,15 +2147,15 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "prost 0.9.0", ] [[package]] name = "quote" -version = "1.0.15" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1888,7 +2194,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -1908,7 +2214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -1937,11 +2243,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.10", ] [[package]] @@ -1977,18 +2283,39 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "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 = "regex" -version = "1.7.1" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", @@ -1997,9 +2324,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "remove_dir_all" @@ -2012,32 +2339,32 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.13.0", - "bytes 1.1.0", + "base64 0.21.4", + "bytes 1.5.0", "encoding_rs", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.21", "http", - "http-body 0.4.4", - "hyper 0.14.18", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "serde", "serde_json", "serde_urlencoded", - "tokio 1.25.0", + "tokio 1.32.0", "tokio-native-tls", "tokio-socks", "tower-service", @@ -2048,12 +2375,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "retain_mut" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" - [[package]] name = "ring" version = "0.16.20" @@ -2069,21 +2390,48 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature 2.1.0", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rusqlite" -version = "0.26.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags", + "bitflags 2.4.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", - "memchr", "smallvec", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2104,16 +2452,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.6" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -2122,7 +2469,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "log", "ring", "sct", @@ -2135,36 +2482,35 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", ] [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -2178,9 +2524,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a" dependencies = [ "secp256k1-sys", "serde", @@ -2197,11 +2543,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2210,9 +2556,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -2220,38 +2566,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ - "indexmap", - "itoa 1.0.1", + "indexmap 2.0.0", + "itoa 1.0.9", "ryu", "serde", ] @@ -2263,22 +2609,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa 1.0.9", "ryu", "serde", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha1" version = "0.10.5" @@ -2286,7 +2621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.9", "digest 0.10.7", ] @@ -2298,11 +2633,22 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.9", "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures 0.2.9", + "digest 0.10.7", +] + [[package]] name = "sha3" version = "0.9.1" @@ -2317,18 +2663,28 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "1.5.0" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "signature" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] [[package]] name = "similar" @@ -2338,9 +2694,9 @@ checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "simple_logger" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75a9723083573ace81ad0cdfc50b858aa3c366c48636edb4109d73122a0c0ea" +checksum = "48047e77b528151aaf841a10a9025f9459da80ba820e425ff7eb005708a76dc7" dependencies = [ "atty", "colored", @@ -2351,15 +2707,18 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -2374,14 +2733,24 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi 0.3.9", ] +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.5.2" @@ -2393,6 +2762,237 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools 0.11.0", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e58421b6bc416714d5115a2ca953718f6c621a51b68e4f4922aea5a4391a721" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes 1.5.0", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.0.0", + "log", + "memchr", + "native-tls", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.7", + "smallvec", + "sqlformat", + "thiserror", + "tokio 1.32.0", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "208e3165167afd7f3881b16c1ef3f2af69fa75980897aac8874a0696516d12c2" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.7", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio 1.32.0", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" +dependencies = [ + "atoi", + "base64 0.21.4", + "bitflags 2.4.0", + "byteorder", + "bytes 1.5.0", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac 0.12.1", + "itoa 1.0.9", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2 0.10.7", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" +dependencies = [ + "atoi", + "base64 0.21.4", + "bitflags 2.4.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac 0.12.1", + "home", + "itoa 1.0.9", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha1", + "sha2 0.10.7", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4c21bf34c7cae5b283efb3ac1bcc7670df7561124dc2f8bdc0b59be40f79a2" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] [[package]] name = "strsim" @@ -2417,11 +3017,11 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2432,9 +3032,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2442,15 +3042,14 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.12.6" +name = "syn" +version = "2.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2465,16 +3064,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if 1.0.0", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.9", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", ] [[package]] @@ -2493,14 +3091,14 @@ dependencies = [ "prost 0.9.0", "rand 0.8.5", "rcgen", - "rusqlite", "serde", "serde_json", "simple_logger", + "sqlx", "structopt", "tempdir", "teos-common", - "tokio 1.25.0", + "tokio 1.32.0", "tokio-stream", "toml", "tonic 0.6.2", @@ -2520,7 +3118,6 @@ dependencies = [ "lightning", "prost 0.9.0", "rand 0.8.5", - "rusqlite", "serde", "serde_json", "tonic 0.6.2", @@ -2529,9 +3126,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -2547,56 +3144,68 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] name = "time" -version = "0.3.7" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ - "itoa 1.0.1", + "deranged", + "itoa 1.0.9", "libc", "num_threads", + "serde", + "time-core", "time-macros", ] +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + [[package]] name = "time-macros" -version = "0.2.3" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" @@ -2618,22 +3227,21 @@ dependencies = [ [[package]] name = "tokio" -version = "1.25.0" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", - "bytes 1.1.0", + "backtrace", + "bytes 1.5.0", "libc", - "memchr", - "mio 0.8.4", + "mio 0.8.8", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "signal-hook-registry", - "socket2 0.4.4", + "socket2 0.5.4", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -2642,29 +3250,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.32.0", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", - "tokio 1.25.0", + "tokio 1.32.0", ] [[package]] @@ -2674,7 +3282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls", - "tokio 1.25.0", + "tokio 1.32.0", "webpki", ] @@ -2687,18 +3295,18 @@ dependencies = [ "either", "futures-util", "thiserror", - "tokio 1.25.0", + "tokio 1.32.0", ] [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.32.0", ] [[package]] @@ -2709,7 +3317,7 @@ checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "tokio 1.25.0", + "tokio 1.32.0", "tungstenite", ] @@ -2729,37 +3337,37 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "futures-core", "futures-sink", "log", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.32.0", ] [[package]] name = "tokio-util" -version = "0.7.0" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "futures-core", "futures-sink", - "log", - "pin-project-lite 0.2.8", - "tokio 1.25.0", + "pin-project-lite 0.2.13", + "tokio 1.32.0", + "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -2772,23 +3380,23 @@ checksum = "796c5e1cd49905e65dd8e700d4cb1dffcbfdb4fc9d017de08c1a537afd83627c" dependencies = [ "async-stream", "async-trait", - "base64 0.13.0", - "bytes 1.1.0", + "base64 0.13.1", + "bytes 1.5.0", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.21", "http", - "http-body 0.4.4", - "hyper 0.14.18", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-timeout", "percent-encoding", "pin-project", "prost 0.8.0", "prost-derive 0.8.0", - "tokio 1.25.0", + "tokio 1.32.0", "tokio-rustls", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2804,23 +3412,23 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64 0.13.0", - "bytes 1.1.0", + "base64 0.13.1", + "bytes 1.5.0", "futures-core", "futures-util", - "h2 0.3.11", + "h2 0.3.21", "http", - "http-body 0.4.4", - "hyper 0.14.18", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-timeout", "percent-encoding", "pin-project", "prost 0.9.0", "prost-derive 0.9.0", - "tokio 1.25.0", + "tokio 1.32.0", "tokio-rustls", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2837,7 +3445,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2847,34 +3455,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99febc413f26cf855b3a309c5872edff5c31e0ffe9c2fce5681868761df36f69" dependencies = [ "base32", - "base64 0.13.0", + "base64 0.13.1", "derive_more", "ed25519-dalek", "hex", - "hmac", + "hmac 0.11.0", "rand 0.7.3", "serde", "serde_derive", - "sha2", + "sha2 0.9.9", "sha3", - "tokio 1.25.0", + "tokio 1.32.0", ] [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.9.3", "pin-project", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "rand 0.8.5", "slab", - "tokio 1.25.0", - "tokio-util 0.7.0", + "tokio 1.32.0", + "tokio-util 0.7.8", "tower-layer", "tower-service", "tracing", @@ -2882,47 +3490,47 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.13", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", ] [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -2943,9 +3551,9 @@ checksum = "ce148eae0d1a376c1b94ae651fc3261d9cb8294788b962b7382066376503a2d1" [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" @@ -2953,9 +3561,9 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "byteorder", - "bytes 1.1.0", + "bytes 1.5.0", "http", "httparse", "log", @@ -2968,57 +3576,57 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "unicode_categories" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "universal-hash" @@ -3038,13 +3646,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -3074,11 +3681,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -3088,12 +3694,12 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" dependencies = [ - "bytes 1.1.0", + "bytes 1.5.0", "futures-channel", "futures-util", "headers", "http", - "hyper 0.14.18", + "hyper 0.14.27", "log", "mime", "mime_guess", @@ -3105,10 +3711,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.25.0", + "tokio 1.32.0", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.7.0", + "tokio-util 0.7.8", "tower-service", "tracing", ] @@ -3119,12 +3725,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3133,9 +3733,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3143,24 +3743,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.36", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3170,9 +3770,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3180,22 +3780,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.36", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "watchtower-plugin" @@ -3214,15 +3814,15 @@ dependencies = [ "serde_json", "tempdir", "teos-common", - "tokio 1.25.0", + "tokio 1.32.0", "tonic 0.5.2", ] [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -3240,15 +3840,22 @@ dependencies = [ [[package]] name = "which" -version = "4.2.4" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "lazy_static", - "libc", + "home", + "once_cell", + "rustix", ] +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + [[package]] name = "winapi" version = "0.2.8" @@ -3294,111 +3901,78 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets", ] [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi 0.3.9", + "cfg-if 1.0.0", + "windows-sys", ] [[package]] @@ -3417,7 +3991,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "chrono", "data-encoding", "der-parser", @@ -3440,21 +4014,20 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.36", ] diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index be0cb489..ceceffed 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -10,7 +10,6 @@ edition = "2018" # General hex = { version = "0.4.3", features = [ "serde" ] } prost = "0.9" -rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } serde = "1.0.130" serde_json = "1.0" tonic = "0.6" diff --git a/teos-common/src/dbm.rs b/teos-common/src/dbm.rs deleted file mode 100644 index d94369f6..00000000 --- a/teos-common/src/dbm.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Logic related to a common database manager, component in charge of persisting data on disk. This is the base of more complex managers -//! that can be used by both clients and towers. -//! - -use rusqlite::ffi::{SQLITE_CONSTRAINT_FOREIGNKEY, SQLITE_CONSTRAINT_PRIMARYKEY}; -use rusqlite::{Connection, Error as SqliteError, ErrorCode, Params}; - -/// Packs the errors than can raise when interacting with the underlying database. -#[derive(Debug)] -pub enum Error { - AlreadyExists, - MissingForeignKey, - MissingField, - NotFound, - Unknown(SqliteError), -} - -pub trait DatabaseConnection { - fn get_connection(&self) -> &Connection; - fn get_mut_connection(&mut self) -> &mut Connection; -} - -pub trait DatabaseManager: Sized { - fn create_tables(&mut self, tables: Vec<&str>) -> Result<(), SqliteError>; - fn store_data(&self, query: &str, params: P) -> Result<(), Error>; - fn remove_data(&self, query: &str, params: P) -> Result<(), Error>; - fn update_data(&self, query: &str, params: P) -> Result<(), Error>; -} - -impl DatabaseManager for T { - /// Creates the database tables if not present. - fn create_tables(&mut self, tables: Vec<&str>) -> Result<(), SqliteError> { - let tx = self.get_mut_connection().transaction().unwrap(); - for table in tables.iter() { - tx.execute(table, [])?; - } - tx.commit() - } - - /// Generic method to store data into the database. - fn store_data(&self, query: &str, params: P) -> Result<(), Error> { - match self.get_connection().execute(query, params) { - Ok(_) => Ok(()), - Err(e) => match e { - SqliteError::SqliteFailure(ie, _) => match ie.code { - ErrorCode::ConstraintViolation => match ie.extended_code { - SQLITE_CONSTRAINT_FOREIGNKEY => Err(Error::MissingForeignKey), - SQLITE_CONSTRAINT_PRIMARYKEY => Err(Error::AlreadyExists), - _ => Err(Error::Unknown(e)), - }, - _ => Err(Error::Unknown(e)), - }, - _ => Err(Error::Unknown(e)), - }, - } - } - - /// Generic method to remove data from the database. - fn remove_data(&self, query: &str, params: P) -> Result<(), Error> { - match self.get_connection().execute(query, params).unwrap() { - 0 => Err(Error::NotFound), - _ => Ok(()), - } - } - - /// Generic method to update data from the database. - fn update_data(&self, query: &str, params: P) -> Result<(), Error> { - // Updating data is fundamentally the same as deleting it in terms of interface. - // A query is sent and either no row is modified or some rows are - self.remove_data(query, params) - } -} diff --git a/teos-common/src/lib.rs b/teos-common/src/lib.rs index 12a7df0d..49145da1 100644 --- a/teos-common/src/lib.rs +++ b/teos-common/src/lib.rs @@ -11,7 +11,6 @@ pub mod protos { pub mod appointment; pub mod constants; pub mod cryptography; -pub mod dbm; pub mod errors; pub mod net; pub mod receipts; diff --git a/teos/Cargo.toml b/teos/Cargo.toml index f01ab5ca..b1868083 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -14,6 +14,12 @@ path = "src/cli.rs" name = "teosd" path = "src/main.rs" +[features] +# By default, enable both SQLite snd PostgreSQL in the output binary. +default = ["sqlite", "postgres"] +sqlite = ["sqlx/sqlite"] +postgres = ["sqlx/postgres"] + [dependencies] # General hex = { version = "0.4.3", features = [ "serde" ] } @@ -21,7 +27,7 @@ home = "0.5.3" log = "0.4" prost = "0.9" rcgen = { version = "0.8", features = ["pem", "x509-parser"] } -rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } +sqlx = { version = "0.7", features = ["runtime-tokio", "tls-native-tls", "migrate", "any"] } serde = "1.0.130" serde_json = "1.0" simple_logger = "2.1.0" diff --git a/teos/build.rs b/teos/build.rs index aa9031fd..6fd333fa 100644 --- a/teos/build.rs +++ b/teos/build.rs @@ -1,4 +1,6 @@ -fn main() -> Result<(), Box> { +fn main() { + // trigger recompilation when a new migration is added without a change in the source code. + println!("cargo:rerun-if-changed=migrations"); tonic_build::configure() .extern_path(".common.teos.v2", "::teos-common::protos") .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") @@ -23,7 +25,6 @@ fn main() -> Result<(), Box> { "proto/teos/v2/user.proto", ], &["proto/teos/v2", "../teos-common/proto/"], - )?; - - Ok(()) + ) + .unwrap(); } diff --git a/teos/migrations/README.md b/teos/migrations/README.md new file mode 100644 index 00000000..4bbc7fa9 --- /dev/null +++ b/teos/migrations/README.md @@ -0,0 +1,10 @@ +# Structure: +- `postgres`: Contains `.sql` migrations for postgres databases. +- `sqlite`: Contains `.sql` migrations for sqlite databases. + + +# Migrations Extra Documentation (`migrations/*/*.md`): + +Migrations cannot be edited once applied to the database. Thus, writing/editing any comments or explanations in the `.sql` files would break the tower for users who have applied those migrations. + +Any additional comments that we need to add after a migration has been applied should be in `MID_MNAME.md` instead. diff --git a/teos/migrations/postgres/000_init.sql b/teos/migrations/postgres/000_init.sql new file mode 100644 index 00000000..5da33a11 --- /dev/null +++ b/teos/migrations/postgres/000_init.sql @@ -0,0 +1,51 @@ +-- INT is 4 bytes signed integer (i32) in PostgreSQL. +-- Many fields in here map to (u32)s in Rust, which has double the capacity of (i32)s on the positive side. +-- Some database calls might break because of this. +-- A solution for this could be either one of: +-- 1- Find a one to one mapping between the Rust (u32)s and PostgreSQL's (i32)s since they are essentially the same size. +-- 2- Use PostgreSQL's BIGINT which is equivalent to an i64. + +CREATE TABLE IF NOT EXISTS users ( + user_id BYTEA PRIMARY KEY, + available_slots BIGINT NOT NULL, + subscription_start BIGINT NOT NULL, + subscription_expiry BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS appointments ( + UUID BYTEA PRIMARY KEY, + locator BYTEA NOT NULL, + encrypted_blob BYTEA NOT NULL, + to_self_delay BIGINT NOT NULL, + user_signature TEXT NOT NULL, + start_block BIGINT NOT NULL, + user_id BYTEA NOT NULL, + FOREIGN KEY(user_id) + REFERENCES users(user_id) + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS trackers ( + UUID BYTEA PRIMARY KEY, + dispute_tx BYTEA NOT NULL, + penalty_tx BYTEA NOT NULL, + height BIGINT NOT NULL, + confirmed BIGINT NOT NULL, + FOREIGN KEY(UUID) + REFERENCES appointments(UUID) + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS last_known_block ( + id INT PRIMARY KEY, + block_hash BYTEA NOT NULL +); + +CREATE TABLE IF NOT EXISTS keys ( + id SERIAL PRIMARY KEY, + secret_key TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS locators_index ON appointments ( + locator +); diff --git a/teos/migrations/sqlite/000_init.sql b/teos/migrations/sqlite/000_init.sql new file mode 100644 index 00000000..a9ef373d --- /dev/null +++ b/teos/migrations/sqlite/000_init.sql @@ -0,0 +1,40 @@ +CREATE TABLE IF NOT EXISTS users ( + user_id INT PRIMARY KEY, + available_slots INT NOT NULL, + subscription_start INT NOT NULL, + subscription_expiry INT NOT NULL +); + +CREATE TABLE IF NOT EXISTS appointments ( + UUID INT PRIMARY KEY, + locator INT NOT NULL, + encrypted_blob BLOB NOT NULL, + to_self_delay INT NOT NULL, + user_signature BLOB NOT NULL, + start_block INT NOT NULL, + user_id INT NOT NULL, + FOREIGN KEY(user_id) + REFERENCES users(user_id) + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS trackers ( + UUID INT PRIMARY KEY, + dispute_tx BLOB NOT NULL, + penalty_tx BLOB NOT NULL, + height INT NOT NULL, + confirmed BOOL NOT NULL, + FOREIGN KEY(UUID) + REFERENCES appointments(UUID) + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS last_known_block ( + id INT PRIMARY KEY, + block_hash INT NOT NULL +); + +CREATE TABLE IF NOT EXISTS keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key INT NOT NULL +); diff --git a/teos/migrations/sqlite/001_datatypes_correction.sql b/teos/migrations/sqlite/001_datatypes_correction.sql new file mode 100644 index 00000000..3bee4f47 --- /dev/null +++ b/teos/migrations/sqlite/001_datatypes_correction.sql @@ -0,0 +1,51 @@ +-- Change `user_id` from INT to BLOB. +CREATE TABLE tmp_users ( + user_id BLOB PRIMARY KEY, + available_slots INT NOT NULL, + subscription_start INT NOT NULL, + subscription_expiry INT NOT NULL +); +INSERT INTO tmp_users SELECT * FROM users; +-- We couldn't drop `users` before copying `appointments`, as the former will cascade delete the latter. +-- Same for `trackers` and `appointments`. +-- DROP TABLE users; + +-- Change `UUID` & `locator` & `user_id` from INT to BLOB. +-- Change `user_signature` from BLOB to TEXT. +CREATE TABLE tmp_appointments ( + UUID BLOB PRIMARY KEY, + locator BLOB NOT NULL, + encrypted_blob BLOB NOT NULL, + to_self_delay INT NOT NULL, + user_signature TEXT NOT NULL, + start_block INT NOT NULL, + user_id BLOB NOT NULL, + FOREIGN KEY(user_id) + REFERENCES tmp_users(user_id) + ON DELETE CASCADE +); +INSERT INTO tmp_appointments SELECT * FROM appointments; + +-- Change `UUID` from INT to BLOB. +-- Change `confirmed` from BOOL to INT (due to https://github.com/launchbadge/sqlx/issues/2657). +CREATE TABLE tmp_trackers ( + UUID BLOB PRIMARY KEY, + dispute_tx BLOB NOT NULL, + penalty_tx BLOB NOT NULL, + height INT NOT NULL, + confirmed INT NOT NULL, + FOREIGN KEY(UUID) + REFERENCES tmp_appointments(UUID) + ON DELETE CASCADE +); +INSERT INTO tmp_trackers SELECT * FROM trackers; + +-- We can drop these now after all the data has been copied. +DROP TABLE users; +DROP TABLE appointments; +DROP TABLE trackers; + +-- Foreign key references are automatically adjusted (tmp_* -> *). +ALTER TABLE tmp_users RENAME TO users; +ALTER TABLE tmp_appointments RENAME TO appointments; +ALTER TABLE tmp_trackers RENAME TO trackers; diff --git a/teos/migrations/sqlite/002_more_datatypes_corrections.sql b/teos/migrations/sqlite/002_more_datatypes_corrections.sql new file mode 100644 index 00000000..0076dddb --- /dev/null +++ b/teos/migrations/sqlite/002_more_datatypes_corrections.sql @@ -0,0 +1,17 @@ +-- Change `block_hash` from INT to BLOB. +CREATE TABLE tmp_last_known_block ( + id INT PRIMARY KEY, + block_hash BLOB NOT NULL +); +INSERT INTO tmp_last_known_block SELECT * FROM last_known_block; +DROP TABLE last_known_block; +ALTER TABLE tmp_last_known_block RENAME TO last_known_block; + +-- Change `key` from INT to TEXT. +CREATE TABLE tmp_keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL +); +INSERT INTO tmp_keys SELECT * FROM keys; +DROP TABLE keys; +ALTER TABLE tmp_keys RENAME TO keys; diff --git a/teos/migrations/sqlite/003_keys_rename.sql b/teos/migrations/sqlite/003_keys_rename.sql new file mode 100644 index 00000000..b7943ed7 --- /dev/null +++ b/teos/migrations/sqlite/003_keys_rename.sql @@ -0,0 +1,2 @@ +-- Rename `key` to `secret_key` as the word `key` is reserved in some databases. +ALTER TABLE keys RENAME key TO secret_key; diff --git a/teos/migrations/sqlite/004_locator_index.sql b/teos/migrations/sqlite/004_locator_index.sql new file mode 100644 index 00000000..c9d3dcf0 --- /dev/null +++ b/teos/migrations/sqlite/004_locator_index.sql @@ -0,0 +1,5 @@ +-- This index greatly enhances the performance of locator based selection queries: +-- "SELECT ... FROM appointments WHERE locator = ..." +CREATE INDEX IF NOT EXISTS locators_index ON appointments ( + locator +); diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index a041ee34..221228a8 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -842,7 +842,8 @@ mod tests_methods { ); internal_api .get_watcher() - .add_dummy_tracker_to_responder(&tracker); + .add_dummy_tracker_to_responder(&tracker) + .await; // Try to add it via the http API let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index fc2085d8..a7fe3b73 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -82,7 +82,7 @@ impl PublicTowerServices for Arc { ) })?; - match self.watcher.register(user_id) { + match self.watcher.register(user_id).await { Ok(receipt) => Ok(Response::new(common_msgs::RegisterResponse { user_id: req_data.user_id, available_slots: receipt.available_slots(), @@ -116,6 +116,7 @@ impl PublicTowerServices for Arc { match self .watcher .add_appointment(appointment, req_data.signature) + .await { Ok((receipt, available_slots, subscription_expiry)) => { Ok(Response::new(common_msgs::AddAppointmentResponse { @@ -153,7 +154,11 @@ impl PublicTowerServices for Arc { let req_data = request.into_inner(); let locator = Locator::from_slice(&req_data.locator).unwrap(); - match self.watcher.get_appointment(locator, &req_data.signature) { + match self + .watcher + .get_appointment(locator, &req_data.signature) + .await + { Ok(info) => { let (appointment_data, status) = match info { AppointmentInfo::Appointment(appointment) => ( @@ -207,6 +212,7 @@ impl PublicTowerServices for Arc { let (subscription_info, locators) = self .watcher .get_subscription_info(&request.into_inner().signature) + .await .map_err(|e| match e { GetSubscriptionInfoFailure::AuthenticationFailure => Status::new( Code::Unauthenticated, @@ -244,7 +250,12 @@ impl PrivateTowerServices for Arc { let mut all_appointments = Vec::new(); - for (_, appointment) in self.watcher.get_all_watcher_appointments().into_iter() { + for (_, appointment) in self + .watcher + .get_all_watcher_appointments() + .await + .into_iter() + { all_appointments.push(common_msgs::AppointmentData { appointment_data: Some( common_msgs::appointment_data::AppointmentData::Appointment( @@ -254,7 +265,7 @@ impl PrivateTowerServices for Arc { }) } - for (_, tracker) in self.watcher.get_all_responder_trackers().into_iter() { + for (_, tracker) in self.watcher.get_all_responder_trackers().await.into_iter() { all_appointments.push(common_msgs::AppointmentData { appointment_data: Some(common_msgs::appointment_data::AppointmentData::Tracker( tracker.into(), @@ -291,6 +302,7 @@ impl PrivateTowerServices for Arc { for (_, appointment) in self .watcher .get_watcher_appointments_with_locator(locator) + .await .into_iter() { matching_appointments.push(common_msgs::AppointmentData { @@ -305,6 +317,7 @@ impl PrivateTowerServices for Arc { for (_, tracker) in self .watcher .get_responder_trackers_with_locator(locator) + .await .into_iter() { matching_appointments.push(common_msgs::AppointmentData { @@ -336,9 +349,9 @@ impl PrivateTowerServices for Arc { Ok(Response::new(msgs::GetTowerInfoResponse { tower_id: self.watcher.tower_id.to_vec(), addresses: self.get_addresses().clone(), - n_registered_users: self.watcher.get_registered_users_count() as u32, - n_watcher_appointments: self.watcher.get_appointments_count() as u32, - n_responder_trackers: self.watcher.get_trackers_count() as u32, + n_registered_users: self.watcher.get_registered_users_count().await as u32, + n_watcher_appointments: self.watcher.get_appointments_count().await as u32, + n_responder_trackers: self.watcher.get_trackers_count().await as u32, bitcoind_reachable: self.check_service_unavailable().is_ok(), })) } @@ -359,6 +372,7 @@ impl PrivateTowerServices for Arc { let user_ids = self .watcher .get_user_ids() + .await .iter() .map(|x| x.to_vec()) .collect(); @@ -386,7 +400,7 @@ impl PrivateTowerServices for Arc { ) })?; - match self.watcher.get_user_info(user_id) { + match self.watcher.get_user_info(user_id).await { Some((info, locators)) => Ok(Response::new(msgs::GetUserResponse { available_slots: info.available_slots, subscription_expiry: info.subscription_expiry, @@ -463,13 +477,18 @@ mod tests_private_api { // Add data to the Watcher so we can retrieve it later on let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); let appointment = generate_dummy_appointment(None).inner; let user_signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); internal_api .watcher .add_appointment(appointment.clone(), user_signature) + .await .unwrap(); let response = internal_api @@ -490,7 +509,7 @@ mod tests_private_api { let (internal_api, _s) = create_api().await; // Add data to the Responser so we can retrieve it later on - internal_api.watcher.add_random_tracker_to_responder(); + internal_api.watcher.add_random_tracker_to_responder().await; let response = internal_api .get_all_appointments(Request::new(())) @@ -533,12 +552,17 @@ mod tests_private_api { // Add that many appointments to the watcher. for _ in 0..appointments_to_create { let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); let appointment = generate_dummy_appointment(Some(&dispute_txid)).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); internal_api .watcher .add_appointment(appointment, signature) + .await .unwrap(); } @@ -590,7 +614,8 @@ mod tests_private_api { ); internal_api .watcher - .add_dummy_tracker_to_responder(&tracker); + .add_dummy_tracker_to_responder(&tracker) + .await; } let locator = Locator::new(dispute_tx.txid()); @@ -643,7 +668,7 @@ mod tests_private_api { // Register a user let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - internal_api.watcher.register(user_id).unwrap(); + internal_api.watcher.register(user_id).await.unwrap(); // Add data to the Watcher for _ in 0..2 { @@ -652,12 +677,13 @@ mod tests_private_api { internal_api .watcher .add_appointment(appointment.clone(), user_signature) + .await .unwrap(); } // And the Responder for _ in 0..3 { - internal_api.watcher.add_random_tracker_to_responder(); + internal_api.watcher.add_random_tracker_to_responder().await; } let response = internal_api @@ -682,7 +708,7 @@ mod tests_private_api { for _ in 0..2 { let (_, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - internal_api.watcher.register(user_id).unwrap(); + internal_api.watcher.register(user_id).await.unwrap(); users.insert(user_id.to_vec()); } @@ -715,7 +741,7 @@ mod tests_private_api { // Register a user and get it back let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - internal_api.watcher.register(user_id).unwrap(); + internal_api.watcher.register(user_id).await.unwrap(); let response = internal_api .get_user(Request::new(msgs::GetUserRequest { @@ -735,6 +761,7 @@ mod tests_private_api { internal_api .watcher .add_appointment(appointment.inner, user_signature) + .await .unwrap(); let response = internal_api @@ -898,7 +925,11 @@ mod tests_public_api { // User must be registered let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); let appointment = generate_dummy_appointment(None).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); @@ -952,7 +983,11 @@ mod tests_public_api { // User is registered but has no slots let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); let appointment = generate_dummy_appointment(None).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); @@ -981,7 +1016,11 @@ mod tests_public_api { // User is registered but subscription is expired let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); let appointment = generate_dummy_appointment(None).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); @@ -1007,7 +1046,7 @@ mod tests_public_api { let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - internal_api.watcher.register(user_id).unwrap(); + internal_api.watcher.register(user_id).await.unwrap(); // Add a tracker to the responder to simulate it being triggered. let dispute_tx = get_random_tx(); @@ -1018,7 +1057,8 @@ mod tests_public_api { ); internal_api .get_watcher() - .add_dummy_tracker_to_responder(&tracker); + .add_dummy_tracker_to_responder(&tracker) + .await; // Try to add it again using the API. let appointment = generate_dummy_appointment(Some(&dispute_tx.txid())).inner; @@ -1070,7 +1110,11 @@ mod tests_public_api { // The user must be registered let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); // Add the appointment let appointment = generate_dummy_appointment(None).inner; @@ -1078,6 +1122,7 @@ mod tests_public_api { internal_api .watcher .add_appointment(appointment.clone(), user_signature) + .await .unwrap(); // Get the appointment through the API @@ -1103,7 +1148,11 @@ mod tests_public_api { // Add a first user to link the appointment to him let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); // There's no need to add the appointment given the subscription status is checked first let appointment = generate_dummy_appointment(None).inner; @@ -1131,7 +1180,11 @@ mod tests_public_api { // The user is registered but the appointment does not exist let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); // Try to get the appointment through the API let appointment = generate_dummy_appointment(None).inner; @@ -1158,7 +1211,11 @@ mod tests_public_api { // Register the user let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); // There s no need to add the appointment given the subscription status is checked first. let appointment = generate_dummy_appointment(None).inner; @@ -1209,7 +1266,11 @@ mod tests_public_api { // The user must be registered let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); // Get the subscription info though the API let message = "get subscription info".to_string(); @@ -1256,7 +1317,11 @@ mod tests_public_api { // The user is registered but the subscription has expired let (user_sk, user_pk) = get_random_keypair(); - internal_api.watcher.register(UserId(user_pk)).unwrap(); + internal_api + .watcher + .register(UserId(user_pk)) + .await + .unwrap(); // Try to get the subscription info though the API let message = "get subscription info".to_string(); diff --git a/teos/src/async_listener.rs b/teos/src/async_listener.rs new file mode 100644 index 00000000..4243a1fa --- /dev/null +++ b/teos/src/async_listener.rs @@ -0,0 +1,128 @@ +//! Contains the [AsyncListen] trait that's analogous to the [chain::Listen] from LDK but runs +//! inside an asynchronous context. + +use crate::dbm::DBM; + +use std::marker::{Send, Sync}; +use std::sync::Arc; + +use bitcoin::{Block, BlockHeader}; +use lightning::chain; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + +/// A trait similar to LDK's [chain::Listen] but runs asynchronously. +#[tonic::async_trait] +pub trait AsyncListen: Send + Sync { + async fn block_connected(&self, block: &Block, height: u32); + async fn block_disconnected(&self, header: &BlockHeader, height: u32); +} + +#[tonic::async_trait] +impl AsyncListen for Arc { + async fn block_connected(&self, block: &Block, height: u32) { + (**self).block_connected(block, height).await; + } + + async fn block_disconnected(&self, header: &BlockHeader, height: u32) { + (**self).block_disconnected(header, height).await; + } +} + +#[tonic::async_trait] +impl AsyncListen for (F, S) { + async fn block_connected(&self, block: &Block, height: u32) { + self.0.block_connected(block, height).await; + self.1.block_connected(block, height).await; + } + + async fn block_disconnected(&self, header: &BlockHeader, height: u32) { + self.0.block_disconnected(header, height).await; + self.1.block_disconnected(header, height).await; + } +} + +#[derive(Debug)] +enum BlockListenerAction { + BlockConnected(Block, u32), + BlockDisconnected(BlockHeader, u32), +} + +/// A helper struct that wraps a listener that implements [AsyncListen] and feeds it connected and disconnected +/// blocks received from an [UnboundedReceiver] in the background. +pub struct AsyncBlockListener { + listener: L, + dbm: Arc, + rx: UnboundedReceiver, +} + +impl AsyncBlockListener { + /// Takes a `listener` that implements [AsyncListen] and returns a listener that implements [chain::Listen]. + /// + /// These two listeners are connected. That is, blocks connected-to/disconnected-from the [chain::Listen] + /// listener are forwarded to the [AsyncListen] listener. + /// + /// The [AsyncListen] listener will be actively listening for actions in a background tokio task. + pub fn wrap_listener(listener: L, dbm: Arc) -> SyncBlockListener { + let (tx, rx) = unbounded_channel(); + let actor = AsyncBlockListener { listener, dbm, rx }; + actor.run_actor_in_bg(); + SyncBlockListener { tx } + } + + /// Spawns a forever living task that listens for [BlockListenerAction] and feeds them to the + /// listener in an asynchronous context. + fn run_actor_in_bg(mut self) { + tokio::spawn(async move { + while let Some(action) = self.rx.recv().await { + match action { + BlockListenerAction::BlockConnected(block, height) => { + self.listener.block_connected(&block, height).await; + // We can update the last known block after all the listeners have received it. + self.dbm + .store_last_known_block(&block.block_hash()) + .await + .unwrap(); + } + BlockListenerAction::BlockDisconnected(header, height) => { + self.listener.block_disconnected(&header, height).await; + } + } + } + }); + } +} + +/// A block listener that implements the sync [chain::Listen] trait. All it does is forward the blocks received +/// to another (async) block listener through an [UnboundedSender]. +pub struct SyncBlockListener { + tx: UnboundedSender, +} + +impl chain::Listen for SyncBlockListener { + fn block_connected(&self, block: &Block, height: u32) { + self.tx + .send(BlockListenerAction::BlockConnected(block.clone(), height)) + .unwrap(); + } + + fn block_disconnected(&self, header: &BlockHeader, height: u32) { + self.tx + .send(BlockListenerAction::BlockDisconnected(*header, height)) + .unwrap(); + } + + fn filtered_block_connected( + &self, + header: &BlockHeader, + txdata: &chain::transaction::TransactionData, + height: u32, + ) { + let block = Block { + header: *header, + txdata: txdata.iter().map(|&(_, tx)| tx.clone()).collect(), + }; + self.tx + .send(BlockListenerAction::BlockConnected(block, height)) + .unwrap(); + } +} diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index 35534013..4b255249 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -11,8 +11,6 @@ use lightning::chain; use lightning_block_sync::poll::{ChainTip, Poll, ValidatedBlockHeader}; use lightning_block_sync::{BlockSourceErrorKind, Cache, SpvClient}; -use crate::dbm::DBM; - /// Component in charge of monitoring the chain for new blocks. /// /// Takes care of polling `bitcoind` for new tips and hand it to subscribers. @@ -28,8 +26,6 @@ where spv_client: SpvClient<'a, P, C, L>, /// The lat known block header by the [ChainMonitor]. last_known_block_header: ValidatedBlockHeader, - /// A [DBM] (database manager) instance. Used to persist block data into disk. - dbm: Arc>, /// The time between polls. polling_delta: time::Duration, /// A signal from the main thread indicating the tower is shuting down. @@ -49,7 +45,6 @@ where pub async fn new( spv_client: SpvClient<'a, P, C, L>, last_known_block_header: ValidatedBlockHeader, - dbm: Arc>, polling_delta_sec: u16, shutdown_signal: Listener, bitcoind_reachable: Arc<(Mutex, Condvar)>, @@ -57,7 +52,6 @@ where ChainMonitor { spv_client, last_known_block_header, - dbm, polling_delta: time::Duration::from_secs(polling_delta_sec as u64), shutdown_signal, bitcoind_reachable, @@ -75,11 +69,6 @@ where ChainTip::Better(new_best) => { log::debug!("Updating best tip: {}", new_best.header.block_hash()); self.last_known_block_header = new_best; - self.dbm - .lock() - .unwrap() - .store_last_known_block(&new_best.header.block_hash()) - .unwrap(); } ChainTip::Worse(worse) => { // This would happen both if a block has less chainwork than the previous one, or if it has the same chainwork @@ -179,7 +168,6 @@ mod tests { let mut chain = Blockchain::default().with_height(START_HEIGHT); let tip = chain.tip(); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let (_, shutdown_signal) = triggered::trigger(); let listener = DummyListener::new(); @@ -189,7 +177,7 @@ mod tests { let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); let mut cm = - ChainMonitor::new(spv_client, tip, dbm, 1, shutdown_signal, bitcoind_reachable).await; + ChainMonitor::new(spv_client, tip, 1, shutdown_signal, bitcoind_reachable).await; // If there's no new block nothing gets connected nor disconnected cm.poll_best_tip().await; @@ -203,7 +191,6 @@ mod tests { let new_tip = chain.tip(); let old_tip = chain.at_height(START_HEIGHT - 1); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let (_, shutdown_signal) = triggered::trigger(); let listener = DummyListener::new(); @@ -212,21 +199,14 @@ mod tests { let spv_client = SpvClient::new(old_tip, poller, cache, &listener); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let mut cm = ChainMonitor::new( - spv_client, - old_tip, - dbm, - 1, - shutdown_signal, - bitcoind_reachable, - ) - .await; + let mut cm = + ChainMonitor::new(spv_client, old_tip, 1, shutdown_signal, bitcoind_reachable).await; // If a new (best) block gets mined, it should be connected cm.poll_best_tip().await; assert_eq!(cm.last_known_block_header, new_tip); assert_eq!( - cm.dbm.lock().unwrap().load_last_known_block().unwrap(), + cm.last_known_block_header.header.block_hash(), new_tip.deref().header.block_hash() ); assert!(listener @@ -242,7 +222,6 @@ mod tests { let best_tip = chain.tip(); chain.disconnect_tip(); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let (_, shutdown_signal) = triggered::trigger(); let listener = DummyListener::new(); @@ -251,20 +230,12 @@ mod tests { let spv_client = SpvClient::new(best_tip, poller, cache, &listener); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let mut cm = ChainMonitor::new( - spv_client, - best_tip, - dbm, - 1, - shutdown_signal, - bitcoind_reachable, - ) - .await; + let mut cm = + ChainMonitor::new(spv_client, best_tip, 1, shutdown_signal, bitcoind_reachable).await; // If a new (worse, just one) block gets mined, nothing gets connected nor disconnected cm.poll_best_tip().await; assert_eq!(cm.last_known_block_header, best_tip); - assert!(cm.dbm.lock().unwrap().load_last_known_block().is_none()); assert!(listener.connected_blocks.borrow().is_empty()); assert!(listener.disconnected_blocks.borrow().is_empty()); } @@ -281,7 +252,6 @@ mod tests { let new_best = chain.tip(); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let (_, shutdown_signal) = triggered::trigger(); let listener = DummyListener::new(); @@ -290,21 +260,14 @@ mod tests { let spv_client = SpvClient::new(old_best, poller, cache, &listener); let bitcoind_reachable = Arc::new((Mutex::new(true), Condvar::new())); - let mut cm = ChainMonitor::new( - spv_client, - old_best, - dbm, - 1, - shutdown_signal, - bitcoind_reachable, - ) - .await; + let mut cm = + ChainMonitor::new(spv_client, old_best, 1, shutdown_signal, bitcoind_reachable).await; // If a a reorg is found (tip is disconnected and a new best is found), both data should be connected and disconnected cm.poll_best_tip().await; assert_eq!(cm.last_known_block_header, new_best); assert_eq!( - cm.dbm.lock().unwrap().load_last_known_block().unwrap(), + cm.last_known_block_header.header.block_hash(), new_best.deref().header.block_hash() ); assert_eq!(*listener.connected_blocks.borrow(), new_blocks); @@ -320,7 +283,6 @@ mod tests { let chain_offline = chain.unreachable.clone(); let tip = chain.tip(); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let (_, shutdown_signal) = triggered::trigger(); let listener = DummyListener::new(); @@ -332,7 +294,6 @@ mod tests { let mut cm = ChainMonitor::new( spv_client, tip, - dbm, 1, shutdown_signal, bitcoind_reachable.clone(), diff --git a/teos/src/config.rs b/teos/src/config.rs index 5436725f..6597dbc4 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -86,6 +86,10 @@ pub struct Opt { #[structopt(long, default_value = "~/.teos")] pub data_dir: String, + /// Database connection string [default: managed (managed SQLite database)] + #[structopt(long)] + pub database_url: Option, + /// Runs teos in debug mode #[structopt(long)] pub debug: bool, @@ -140,6 +144,9 @@ pub struct Config { pub btc_rpc_connect: String, pub btc_rpc_port: u16, + // Database + pub database_url: String, + // Flags pub debug: bool, pub deps_debug: bool, @@ -193,6 +200,9 @@ impl Config { if options.btc_rpc_port.is_some() { self.btc_rpc_port = options.btc_rpc_port.unwrap(); } + if options.database_url.is_some() { + self.database_url = options.database_url.unwrap(); + } if options.tor_control_port.is_some() { self.tor_control_port = options.tor_control_port.unwrap(); } @@ -254,7 +264,7 @@ impl Config { pub fn log_non_default_options(&self) { let json_default_config = serde_json::json!(&Config::default()); let json_config = serde_json::json!(&self); - let sensitive_args = ["btc_rpc_user", "btc_rpc_password"]; + let sensitive_args = ["btc_rpc_user", "btc_rpc_password", "database_url"]; for (key, value) in json_config.as_object().unwrap().iter() { if *value != json_default_config[key] { @@ -293,7 +303,7 @@ impl Default for Config { btc_rpc_password: String::new(), btc_rpc_connect: "localhost".into(), btc_rpc_port: 0, - + database_url: "managed".into(), debug: false, deps_debug: false, overwrite_key: false, @@ -329,7 +339,7 @@ mod tests { btc_rpc_connect: None, btc_rpc_port: None, data_dir: String::from("~/.teos"), - + database_url: None, debug: false, deps_debug: false, overwrite_key: false, diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index ca6dabff..7f3af054 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -1,757 +1,629 @@ //! Logic related to the tower database manager (DBM), component in charge of persisting data on disk. -//! +#![allow(dead_code)] use std::collections::HashMap; -use std::iter::FromIterator; -use std::path::PathBuf; use std::str::FromStr; -use rusqlite::limits::Limit; -use rusqlite::{params, params_from_iter, Connection, Error as SqliteError}; - -use bitcoin::consensus; use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; -use bitcoin::BlockHash; +use bitcoin::{consensus, BlockHash}; +use sqlx::any::{install_drivers, AnyRow}; +use sqlx::{AnyPool, Error, Row}; use teos_common::appointment::{Appointment, Locator}; -use teos_common::dbm::{DatabaseConnection, DatabaseManager, Error}; use teos_common::UserId; use crate::extended_appointment::{ExtendedAppointment, UUID}; use crate::gatekeeper::UserInfo; use crate::responder::{ConfirmationStatus, PenaltySummary, TransactionTracker}; +use crate::watcher::Breach; -const TABLES: [&str; 6] = [ - "CREATE TABLE IF NOT EXISTS users ( - user_id INT PRIMARY KEY, - available_slots INT NOT NULL, - subscription_start INT NOT NULL, - subscription_expiry INT NOT NULL -)", - "CREATE TABLE IF NOT EXISTS appointments ( - UUID INT PRIMARY KEY, - locator INT NOT NULL, - encrypted_blob BLOB NOT NULL, - to_self_delay INT NOT NULL, - user_signature BLOB NOT NULL, - start_block INT NOT NULL, - user_id INT NOT NULL, - FOREIGN KEY(user_id) - REFERENCES users(user_id) - ON DELETE CASCADE -)", - "CREATE TABLE IF NOT EXISTS trackers ( - UUID INT PRIMARY KEY, - dispute_tx BLOB NOT NULL, - penalty_tx BLOB NOT NULL, - height INT NOT NULL, - confirmed BOOL NOT NULL, - FOREIGN KEY(UUID) - REFERENCES appointments(UUID) - ON DELETE CASCADE -)", - "CREATE TABLE IF NOT EXISTS last_known_block ( - id INT PRIMARY KEY, - block_hash INT NOT NULL -)", - "CREATE TABLE IF NOT EXISTS keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key INT NOT NULL -)", - "CREATE INDEX IF NOT EXISTS locators_index ON appointments ( - locator -)", -]; +#[cfg(not(test))] +/// The maximum number of bind variables per SQL query. +/// `32766` for SQLite & `65535` for PostgreSQL. Picking `32766` for both since it's big enough already. +const SQL_VARIABLE_LIMIT: usize = 32766; +#[cfg(test)] +/// The maximum number of bind variables per SQL query. Set to `10` for testing. +const SQL_VARIABLE_LIMIT: usize = 10; + +/// Checks if the database type (`$db_type`) matches with the connection string (`$connection_string`). +/// If the connection string matches the database type, this macro does: +/// - Try to install the driver for the passed database type. +/// - Try to connect to the database. +/// - Try to run the migrations specified in `$migrations_path`. +/// - Return [Ok(DBM)] if everything succeeds, [Err(String)] otherwise explaining the error. +macro_rules! return_db_if_matching { + ($connection_string:expr, $db_type:tt, $migrations_path:literal) => { + if $connection_string.starts_with(concat!(stringify!($db_type), ":")) { + install_drivers(&[sqlx::$db_type::any::DRIVER]) + // Just log the warning but try to connect anyways. `install_drivers` might fail if the driver is already installed. + .map_err(|e| log::error!("Failed to install database drivers for {}: {e}", stringify!($db_type))) + .ok(); + let pool = AnyPool::connect($connection_string) + .await + .map_err(|e| format!("Couldn't connect to {}: {e}", $connection_string))?; + sqlx::migrate!($migrations_path) + .run(&pool) + .await + .map_err(|e| format!("Failed to run database migrations: {e}"))?; + return Ok(Self { pool }); + } + } +} /// Component in charge of interacting with the underlying database. -/// -/// Currently works for `SQLite`. `PostgreSQL` should also be added in the future. #[derive(Debug)] pub struct DBM { - /// The underlying database connection. - connection: Connection, -} - -impl DatabaseConnection for DBM { - fn get_connection(&self) -> &Connection { - &self.connection - } - - fn get_mut_connection(&mut self) -> &mut Connection { - &mut self.connection - } + // TODO: Add MySQL support. The queries used right now isn't MySQL-compatible. + pool: AnyPool, } impl DBM { /// Creates a new [DBM] instance. - pub fn new(db_path: PathBuf) -> Result { - let connection = Connection::open(db_path)?; - connection.execute("PRAGMA foreign_keys=1;", [])?; - let mut dbm = Self { connection }; - dbm.create_tables(Vec::from_iter(TABLES))?; + pub async fn new(connection_string: &str) -> Result { + #[cfg(not(any(feature = "sqlite", feature = "postgres")))] + compile_error!("Can't compile with no database drivers."); + + // Note that sqlx initiates `PRAGMA foreign_keys = ON` connections by default. + #[cfg(feature = "sqlite")] + return_db_if_matching!(connection_string, sqlite, "migrations/sqlite"); + + #[cfg(feature = "postgres")] + return_db_if_matching!(connection_string, postgres, "migrations/postgres"); - Ok(dbm) + let supported_dbs = vec![ + #[cfg(feature = "sqlite")] + "SQLite", + #[cfg(feature = "postgres")] + "PostgreSQL", + ]; + + Err(format!( + "The database connection string ({connection_string}) is invalid or isn't supported. Supported databases are: {supported_dbs:?}." + )) } /// Stores a user ([UserInfo]) into the database. - pub(crate) fn store_user(&self, user_id: UserId, user_info: &UserInfo) -> Result<(), Error> { - let query = - "INSERT INTO users (user_id, available_slots, subscription_start, subscription_expiry) VALUES (?1, ?2, ?3, ?4)"; - - match self.store_data( - query, - params![ - user_id.to_vec(), - user_info.available_slots, - user_info.subscription_start, - user_info.subscription_expiry, - ], - ) { - Ok(x) => { - log::debug!("User successfully stored: {user_id}"); - Ok(x) - } - Err(e) => { - log::error!("Couldn't store user: {user_id}. Error: {e:?}"); - Err(e) - } - } + pub(crate) async fn store_user( + &self, + user_id: UserId, + user_info: &UserInfo, + ) -> Result<(), Error> { + let sql = "INSERT INTO users (user_id, available_slots, subscription_start, subscription_expiry) VALUES ($1, $2, $3, $4)"; + sqlx::query(sql) + .bind(user_id.to_vec()) + .bind(user_info.available_slots as i64) + .bind(user_info.subscription_start as i64) + .bind(user_info.subscription_expiry as i64) + .execute(&self.pool) + .await + .map(|_| ()) } /// Updates an existing user ([UserInfo]) in the database. - pub(crate) fn update_user(&self, user_id: UserId, user_info: &UserInfo) { - let query = - "UPDATE users SET available_slots=(?1), subscription_start=(?2), subscription_expiry=(?3) WHERE user_id=(?4)"; - match self.update_data( - query, - params![ - user_info.available_slots, - user_info.subscription_start, - user_info.subscription_expiry, - user_id.to_vec(), - ], - ) { - Ok(_) => { - log::debug!("User's info successfully updated: {user_id}"); - } - Err(_) => { - log::error!("User not found, data cannot be updated: {user_id}"); - } - } + pub(crate) async fn update_user( + &self, + user_id: UserId, + user_info: &UserInfo, + ) -> Result<(), Error> { + let sql = "UPDATE users SET available_slots=($1), subscription_start=($2), subscription_expiry=($3) WHERE user_id=($4)"; + let updated_rows = sqlx::query(sql) + .bind(user_info.available_slots as i64) + .bind(user_info.subscription_start as i64) + .bind(user_info.subscription_expiry as i64) + .bind(user_id.to_vec()) + .execute(&self.pool) + .await? + .rows_affected(); + + (updated_rows != 0).then_some(()).ok_or(Error::RowNotFound) } /// Loads the associated locators ([Locator]) of a given user ([UserId]). - pub(crate) fn load_user_locators(&self, user_id: UserId) -> Vec { - let mut stmt = self - .connection - .prepare("SELECT locator FROM appointments WHERE user_id=(?)") - .unwrap(); + pub(crate) async fn load_user_locators(&self, user_id: UserId) -> Vec { + sqlx::query("SELECT locator FROM appointments WHERE user_id=($1)") + .bind(user_id.to_vec()) + .map(|row: AnyRow| Locator::from_slice(row.get("locator")).unwrap()) + .fetch_all(&self.pool) + .await + .unwrap() + } - stmt.query_map([user_id.to_vec()], |row| { - let raw_locator: Vec = row.get(0).unwrap(); - let locator = Locator::from_slice(&raw_locator).unwrap(); - Ok(locator) + /// Loads all users from the database. + pub(crate) async fn load_all_users(&self) -> HashMap { + sqlx::query( + "SELECT user_id, available_slots, subscription_start, subscription_expiry FROM users", + ) + .map(|row: AnyRow| { + ( + UserId::from_slice(row.get("user_id")).unwrap(), + UserInfo::new( + row.get::("available_slots") as u32, + row.get::("subscription_start") as u32, + row.get::("subscription_expiry") as u32, + ), + ) }) + .fetch_all(&self.pool) + .await .unwrap() - .map(|res| res.unwrap()) + .into_iter() .collect() } - /// Loads all users from the database. - pub(crate) fn load_all_users(&self) -> HashMap { - let mut users = HashMap::new(); - let mut stmt = self - .connection - .prepare("SELECT user_id, available_slots, subscription_start, subscription_expiry FROM users") - .unwrap(); - let mut rows = stmt.query([]).unwrap(); - - while let Ok(Some(row)) = rows.next() { - let raw_userid: Vec = row.get(0).unwrap(); - let user_id = UserId::from_slice(&raw_userid).unwrap(); - let slots = row.get(1).unwrap(); - let start = row.get(2).unwrap(); - let expiry = row.get(3).unwrap(); - - users.insert(user_id, UserInfo::new(slots, start, expiry)); - } - - users - } - /// Removes some users from the database in batch. - pub(crate) fn batch_remove_users(&mut self, users: &Vec) -> usize { - let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; - let tx = self.connection.transaction().unwrap(); - let iter = users - .iter() - .map(|uuid| uuid.to_vec()) - .collect::>>(); - - for chunk in iter.chunks(limit) { - let query = "DELETE FROM users WHERE user_id IN ".to_owned(); - let placeholders = format!("(?{})", (", ?").repeat(chunk.len() - 1)); - - match tx.execute(&format!("{query}{placeholders}"), params_from_iter(chunk)) { - Ok(_) => log::debug!("Users deletion added to db transaction"), - Err(e) => log::error!("Couldn't add deletion query to transaction. Error: {e:?}"), + pub(crate) async fn batch_remove_users(&self, users: Vec) -> usize { + let users: Vec<_> = users.iter().map(|uuid| uuid.to_vec()).collect(); + + for chunk in users.chunks(SQL_VARIABLE_LIMIT) { + let str_indices: Vec<_> = (1..=chunk.len()).map(|i| i.to_string()).collect(); + let placeholders = format!("${}", str_indices.join(", $")); + let sql = format!("DELETE FROM users WHERE user_id IN ({placeholders})"); + let mut query = sqlx::query(&sql); + for user_id in chunk { + query = query.bind(user_id); } + query.execute(&self.pool).await.unwrap(); } - match tx.commit() { - Ok(_) => log::debug!("Users successfully deleted"), - Err(e) => log::error!("Couldn't delete users. Error: {e:?}"), - } - - (users.len() as f64 / limit as f64).ceil() as usize + (users.len() as f64 / SQL_VARIABLE_LIMIT as f64).ceil() as usize } /// Get the number of stored appointments. - pub(crate) fn get_appointments_count(&self) -> usize { - let mut stmt = self - .connection - .prepare("SELECT COUNT(*) FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL") - .unwrap(); - stmt.query_row([], |row| row.get(0)).unwrap() + pub(crate) async fn get_appointments_count(&self) -> usize { + let sql = "SELECT COUNT(*) FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL"; + sqlx::query(sql) + .map(|row: AnyRow| row.get::(0) as usize) + .fetch_one(&self.pool) + .await + .unwrap() } /// Get the number of stored trackers. - pub(crate) fn get_trackers_count(&self) -> usize { - let mut stmt = self - .connection - .prepare("SELECT COUNT(*) FROM trackers") - .unwrap(); - stmt.query_row([], |row| row.get(0)).unwrap() + pub(crate) async fn get_trackers_count(&self) -> usize { + sqlx::query("SELECT COUNT(*) FROM trackers") + .map(|row: AnyRow| row.get::(0) as usize) + .fetch_one(&self.pool) + .await + .unwrap() } /// Stores an [Appointment] into the database. - pub(crate) fn store_appointment( + pub(crate) async fn store_appointment( &self, uuid: UUID, appointment: &ExtendedAppointment, ) -> Result<(), Error> { - let query = "INSERT INTO appointments (UUID, locator, encrypted_blob, to_self_delay, user_signature, start_block, user_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"; - match self.store_data( - query, - params![ - uuid.to_vec(), - appointment.locator().to_vec(), - appointment.encrypted_blob(), - appointment.to_self_delay(), - appointment.user_signature, - appointment.start_block, - appointment.user_id.to_vec(), - ], - ) { - Ok(x) => { - log::debug!("Appointment successfully stored: {uuid}"); - Ok(x) - } - Err(e) => { - log::error!("Couldn't store appointment: {uuid}. Error: {e:?}"); - Err(e) - } - } + let sql = "INSERT INTO appointments (UUID, locator, encrypted_blob, to_self_delay, user_signature, start_block, user_id) VALUES ($1, $2, $3, $4, $5, $6, $7)"; + sqlx::query(sql) + .bind(uuid.to_vec()) + .bind(appointment.locator().to_vec()) + .bind(appointment.encrypted_blob()) + .bind(appointment.to_self_delay() as i64) + .bind(&appointment.user_signature) + .bind(appointment.start_block as i64) + .bind(appointment.user_id.to_vec()) + .execute(&self.pool) + .await + .map(|_| ()) } /// Updates an existing [Appointment] in the database. - pub(crate) fn update_appointment( + pub(crate) async fn update_appointment( &self, uuid: UUID, appointment: &ExtendedAppointment, ) -> Result<(), Error> { // DISCUSS: Check what fields we'd like to make updatable. e_blob and signature are the obvious, to_self_delay and start_block may not be necessary (or even risky) - let query = - "UPDATE appointments SET encrypted_blob=(?1), to_self_delay=(?2), user_signature=(?3), start_block=(?4) WHERE UUID=(?5)"; - match self.update_data( - query, - params![ - appointment.encrypted_blob(), - appointment.to_self_delay(), - appointment.user_signature, - appointment.start_block, - uuid.to_vec(), - ], - ) { - Ok(_) => { - log::debug!("Appointment successfully updated: {uuid}"); - Ok(()) - } - Err(e) => { - log::error!("Appointment not found, data cannot be updated: {uuid}. Error: {e:?}"); - Err(e) - } - } + let sql = "UPDATE appointments SET encrypted_blob=($1), to_self_delay=($2), user_signature=($3), start_block=($4) WHERE UUID=($5)"; + let updated_rows = sqlx::query(sql) + .bind(appointment.encrypted_blob()) + .bind(appointment.to_self_delay() as i64) + .bind(&appointment.user_signature) + .bind(appointment.start_block as i64) + .bind(uuid.to_vec()) + .execute(&self.pool) + .await? + .rows_affected(); + + (updated_rows != 0).then_some(()).ok_or(Error::RowNotFound) } /// Loads an [Appointment] from the database. - pub(crate) fn load_appointment(&self, uuid: UUID) -> Option { - let key = uuid.to_vec(); - let mut stmt = self - .connection - .prepare( - "SELECT locator, encrypted_blob, to_self_delay, user_signature, start_block, user_id - FROM appointments WHERE UUID=(?)" - ) - .unwrap(); - - stmt.query_row([key], |row| { - let raw_locator: Vec = row.get(0).unwrap(); - let encrypted_blob = row.get(1).unwrap(); - let to_self_delay = row.get(2).unwrap(); - let user_signature = row.get(3).unwrap(); - let start_block = row.get(4).unwrap(); - let raw_userid: Vec = row.get(5).unwrap(); - - let locator = Locator::from_slice(&raw_locator).unwrap(); - let user_id = UserId::from_slice(&raw_userid).unwrap(); - let appointment = Appointment::new(locator, encrypted_blob, to_self_delay); - Ok(ExtendedAppointment::new( - appointment, - user_id, - user_signature, - start_block, - )) - }) - .ok() + pub(crate) async fn load_appointment(&self, uuid: UUID) -> Option { + let sql = "SELECT locator, encrypted_blob, to_self_delay, user_id, user_signature, start_block FROM appointments WHERE UUID=($1)"; + sqlx::query(sql) + .bind(uuid.to_vec()) + .map(|row: AnyRow| { + ExtendedAppointment::new( + Appointment::new( + Locator::from_slice(row.get("locator")).unwrap(), + row.get("encrypted_blob"), + row.get::("to_self_delay") as u32, + ), + UserId::from_slice(row.get("user_id")).unwrap(), + row.get("user_signature"), + row.get::("start_block") as u32, + ) + }) + .fetch_one(&self.pool) + .await + .ok() } /// Check if an appointment with `uuid` exists. - pub(crate) fn appointment_exists(&self, uuid: UUID) -> bool { - self.connection - .prepare("SELECT UUID FROM appointments WHERE UUID=(?)") - .unwrap() - .exists([uuid.to_vec()]) - .unwrap() + pub(crate) async fn appointment_exists(&self, uuid: UUID) -> bool { + sqlx::query("SELECT UUID FROM appointments WHERE UUID=($1)") + .bind(uuid.to_vec()) + .fetch_one(&self.pool) + .await + .is_ok() } /// Loads appointments from the database. If a locator is given, this method loads only the appointments /// matching this locator. If no locator is given, all the appointments in the database would be returned. - pub(crate) fn load_appointments( + pub(crate) async fn load_appointments( &self, locator: Option, ) -> HashMap { - let mut appointments = HashMap::new(); + let mut sql = "SELECT a.UUID, a.locator, a.encrypted_blob, a.to_self_delay, a.user_id, a.user_signature, a.start_block FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL".to_string(); - let mut sql = - "SELECT a.UUID, a.locator, a.encrypted_blob, a.to_self_delay, a.user_signature, a.start_block, a.user_id - FROM appointments as a LEFT JOIN trackers as t ON a.UUID=t.UUID WHERE t.UUID IS NULL".to_string(); // If a locator was passed, filter based on it. - if locator.is_some() { - sql.push_str(" AND a.locator=(?)"); - } - let mut stmt = self.connection.prepare(&sql).unwrap(); - - let mut rows = if let Some(locator) = locator { - stmt.query([locator.to_vec()]).unwrap() + let query = if let Some(locator) = locator { + sql.push_str(" AND a.locator=($1)"); + sqlx::query(&sql).bind(locator.to_vec()) } else { - stmt.query([]).unwrap() + sqlx::query(&sql) }; - while let Ok(Some(row)) = rows.next() { - let raw_uuid: Vec = row.get(0).unwrap(); - let uuid = UUID::from_slice(&raw_uuid[0..20]).unwrap(); - let raw_locator: Vec = row.get(1).unwrap(); - let locator = Locator::from_slice(&raw_locator).unwrap(); - let raw_userid: Vec = row.get(6).unwrap(); - let user_id = UserId::from_slice(&raw_userid).unwrap(); - - let appointment = Appointment::new(locator, row.get(2).unwrap(), row.get(3).unwrap()); - - appointments.insert( - uuid, - ExtendedAppointment::new( - appointment, - user_id, - row.get(4).unwrap(), - row.get(5).unwrap(), - ), - ); - } - - appointments + query + .map(|row: AnyRow| { + ( + UUID::from_slice(row.get(0)).unwrap(), + ExtendedAppointment::new( + Appointment::new( + Locator::from_slice(row.get(1)).unwrap(), + row.get(2), + row.get::(3) as u32, + ), + UserId::from_slice(row.get(4)).unwrap(), + row.get(5), + row.get::(6) as u32, + ), + ) + }) + .fetch_all(&self.pool) + .await + .unwrap() + .into_iter() + .collect() } /// Gets the length of an appointment (the length of `appointment.encrypted_blob`). - pub(crate) fn get_appointment_length(&self, uuid: UUID) -> Option { - let mut stmt = self - .connection - .prepare("SELECT length(encrypted_blob) FROM appointments WHERE UUID=(?)") - .unwrap(); - - stmt.query_row([uuid.to_vec()], |row| row.get(0)).ok() + pub(crate) async fn get_appointment_length(&self, uuid: UUID) -> Result { + sqlx::query("SELECT length(encrypted_blob) FROM appointments WHERE UUID=($1)") + .bind(uuid.to_vec()) + .map(|row: AnyRow| row.get::(0) as usize) + .fetch_one(&self.pool) + .await } /// Gets the [`UserId`] of the owner of the appointment along with the appointment /// length (same as [DBM::get_appointment_length]) for `uuid`. - pub(crate) fn get_appointment_user_and_length(&self, uuid: UUID) -> Option<(UserId, usize)> { - let mut stmt = self - .connection - .prepare("SELECT user_id, length(encrypted_blob) FROM appointments WHERE UUID=(?)") - .unwrap(); - - stmt.query_row([uuid.to_vec()], |row| { - let raw_userid: Vec = row.get(0).unwrap(); - let length = row.get(1).unwrap(); - Ok((UserId::from_slice(&raw_userid).unwrap(), length)) - }) - .ok() + pub(crate) async fn get_appointment_user_and_length( + &self, + uuid: UUID, + ) -> Result<(UserId, usize), Error> { + sqlx::query("SELECT user_id, length(encrypted_blob) FROM appointments WHERE UUID=($1)") + .bind(uuid.to_vec()) + .map(|row: AnyRow| { + ( + UserId::from_slice(row.get("user_id")).unwrap(), + row.get::(1) as usize, + ) + }) + .fetch_one(&self.pool) + .await } /// Removes an [Appointment] from the database. - pub(crate) fn remove_appointment(&self, uuid: UUID) { - let query = "DELETE FROM appointments WHERE UUID=(?)"; - match self.remove_data(query, params![uuid.to_vec()]) { - Ok(_) => { - log::debug!("Appointment successfully removed: {uuid}"); - } - Err(_) => { - log::error!("Appointment not found, data cannot be removed: {uuid}"); - } + pub(crate) async fn remove_appointment(&self, uuid: UUID) { + if let Err(e) = sqlx::query("DELETE FROM appointments WHERE UUID=($1)") + .bind(uuid.to_vec()) + .execute(&self.pool) + .await + { + log::error!("Failed to remove appointment ({uuid}) due to: {e}") } } /// Removes some appointments from the database in batch and updates the associated users /// (giving back freed appointment slots) in one transaction so that the deletion and the /// update is atomic. - pub(crate) fn batch_remove_appointments( - &mut self, - appointments: &Vec, - updated_users: &HashMap, + pub(crate) async fn batch_remove_appointments( + &self, + appointments: Vec, + updated_users: HashMap, ) -> usize { - let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; - let tx = self.connection.transaction().unwrap(); - let iter = appointments - .iter() - .map(|uuid| uuid.to_vec()) - .collect::>>(); - - for chunk in iter.chunks(limit) { - let query = "DELETE FROM appointments WHERE UUID IN ".to_owned(); - let placeholders = format!("(?{})", (", ?").repeat(chunk.len() - 1)); - - match tx.execute(&format!("{query}{placeholders}"), params_from_iter(chunk)) { - Ok(_) => log::debug!("Appointments deletion added to db transaction"), - Err(e) => log::error!("Couldn't add deletion query to transaction. Error: {e:?}"), + let uuids: Vec<_> = appointments.iter().map(|uuid| uuid.to_vec()).collect(); + let mut tx = self.pool.begin().await.unwrap(); + + for chunk in uuids.chunks(SQL_VARIABLE_LIMIT) { + let str_indices: Vec<_> = (1..=chunk.len()).map(|i| i.to_string()).collect(); + let placeholders = format!("${}", str_indices.join(", $")); + let sql = format!("DELETE FROM appointments WHERE UUID IN ({placeholders})"); + let mut query = sqlx::query(&sql); + for uuid in chunk { + query = query.bind(uuid); } + query.execute(&mut *tx).await.unwrap(); } - for (id, info) in updated_users.iter() { - let query = "UPDATE users SET available_slots=(?1) WHERE user_id=(?2)"; - match tx.execute(query, params![info.available_slots, id.to_vec(),]) { - Ok(_) => log::debug!("User update added to db transaction"), - Err(e) => log::error!("Couldn't add update query to transaction. Error: {e:?}"), - }; + for (user_id, info) in updated_users.iter() { + sqlx::query("UPDATE users SET available_slots=($1) WHERE user_id=($2)") + .bind(info.available_slots as i64) + .bind(user_id.to_vec()) + .execute(&mut *tx) + .await + .unwrap(); } - match tx.commit() { - Ok(_) => log::debug!("Appointments successfully deleted"), - Err(e) => log::error!("Couldn't delete appointments. Error: {e:?}"), + if let Err(e) = tx.commit().await { + log::error!("Failed to remove appointments in batch: {e}") } - (appointments.len() as f64 / limit as f64).ceil() as usize + (uuids.len() as f64 / SQL_VARIABLE_LIMIT as f64).ceil() as usize } /// Loads the [`UUID`]s of appointments triggered by `locator`. - pub(crate) fn load_uuids(&self, locator: Locator) -> Vec { - let mut stmt = self - .connection - .prepare("SELECT UUID from appointments WHERE locator=(?)") - .unwrap(); - - stmt.query_map([locator.to_vec()], |row| { - let raw_uuid: Vec = row.get(0).unwrap(); - let uuid = UUID::from_slice(&raw_uuid).unwrap(); - Ok(uuid) - }) - .unwrap() - .map(|uuid_res| uuid_res.unwrap()) - .collect() + pub(crate) async fn load_uuids(&self, locator: Locator) -> Vec { + sqlx::query("SELECT UUID from appointments WHERE locator=($1)") + .bind(locator.to_vec()) + // NOTE: For some reason, indexing using the string "UUID" fails on PostgreSQL. + // Using numerical index for interoperability with SQLite. + .map(|row: AnyRow| UUID::from_slice(row.get(0)).unwrap()) + .fetch_all(&self.pool) + .await + .unwrap() } /// Filters the given set of [`Locator`]s by including only the ones which trigger any of our stored appointments. - pub(crate) fn batch_check_locators_exist(&self, locators: Vec<&Locator>) -> Vec { + pub(crate) async fn batch_check_locators_exist(&self, locators: Vec<&Locator>) -> Vec { let mut registered_locators = Vec::new(); - let locators: Vec> = locators.iter().map(|l| l.to_vec()).collect(); - let limit = self.connection.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; - - for chunk in locators.chunks(limit) { - let query = "SELECT locator FROM appointments WHERE locator IN ".to_owned(); - let placeholders = format!("(?{})", (", ?").repeat(chunk.len() - 1)); - - let mut stmt = self - .connection - .prepare(&format!("{query}{placeholders}")) - .unwrap(); - let known_locators = stmt - .query_map(params_from_iter(chunk), |row| { - let raw_locator: Vec = row.get(0).unwrap(); - let locator = Locator::from_slice(&raw_locator).unwrap(); - Ok(locator) - }) - .unwrap() - .map(|locator_res| locator_res.unwrap()); - registered_locators.extend(known_locators); + let locators: Vec<_> = locators.iter().map(|l| l.to_vec()).collect(); + + for chunk in locators.chunks(SQL_VARIABLE_LIMIT) { + let str_indices: Vec<_> = (1..=chunk.len()).map(|i| i.to_string()).collect(); + let placeholders = format!("${}", str_indices.join(", $")); + let sql = format!("SELECT locator FROM appointments WHERE locator IN ({placeholders})"); + let mut query = sqlx::query(&sql); + for locator in chunk { + query = query.bind(locator); + } + registered_locators.extend( + query + .map(|row: AnyRow| Locator::from_slice(row.get("locator")).unwrap()) + .fetch_all(&self.pool) + .await + .unwrap(), + ) } registered_locators } /// Stores a [TransactionTracker] into the database. - pub(crate) fn store_tracker( + pub(crate) async fn store_tracker( &self, uuid: UUID, tracker: &TransactionTracker, ) -> Result<(), Error> { - let (height, confirmed) = tracker.status.to_db_data().ok_or(Error::MissingField)?; - - let query = - "INSERT INTO trackers (UUID, dispute_tx, penalty_tx, height, confirmed) VALUES (?1, ?2, ?3, ?4, ?5)"; - match self.store_data( - query, - params![ - uuid.to_vec(), - consensus::serialize(&tracker.dispute_tx), - consensus::serialize(&tracker.penalty_tx), - height, - confirmed, - ], - ) { - Ok(x) => { - log::debug!("Tracker successfully stored: {uuid}"); - Ok(x) - } - Err(e) => { - log::error!("Couldn't store tracker: {uuid}. Error: {e:?}"); - Err(e) - } - } + let (height, confirmed) = tracker + .status + .to_db_data() + .ok_or(Error::Decode("Tracker status isn't storable".into()))?; + let sql = "INSERT INTO trackers (UUID, dispute_tx, penalty_tx, height, confirmed) VALUES ($1, $2, $3, $4, $5)"; + sqlx::query(sql) + .bind(uuid.to_vec()) + .bind(consensus::serialize(&tracker.dispute_tx)) + .bind(consensus::serialize(&tracker.penalty_tx)) + .bind(height as i64) + .bind(confirmed as i64) + .execute(&self.pool) + .await + .map(|_| ()) } /// Updates the tracker status in the database. /// /// The only updatable fields are `height` and `confirmed`. - pub(crate) fn update_tracker_status( + pub(crate) async fn update_tracker_status( &self, uuid: UUID, status: &ConfirmationStatus, ) -> Result<(), Error> { - let (height, confirmed) = status.to_db_data().ok_or(Error::MissingField)?; + let (height, confirmed) = status + .to_db_data() + .ok_or(Error::Decode("Tracker status isn't storable".into()))?; + let updated_rows = + sqlx::query("UPDATE trackers SET height=($1), confirmed=($2) WHERE UUID=($3)") + .bind(height as i64) + .bind(confirmed as i64) + .bind(uuid.to_vec()) + .execute(&self.pool) + .await? + .rows_affected(); - let query = "UPDATE trackers SET height=(?1), confirmed=(?2) WHERE UUID=(?3)"; - match self.update_data(query, params![height, confirmed, uuid.to_vec(),]) { - Ok(x) => { - log::debug!("Tracker successfully updated: {uuid}"); - Ok(x) - } - Err(e) => { - log::error!("Couldn't update tracker: {uuid}. Error: {e:?}"); - Err(e) - } - } + (updated_rows != 0).then_some(()).ok_or(Error::RowNotFound) } /// Loads a [TransactionTracker] from the database. - pub(crate) fn load_tracker(&self, uuid: UUID) -> Option { - let key = uuid.to_vec(); - let mut stmt = self - .connection.prepare( - "SELECT t.dispute_tx, t.penalty_tx, t.height, t.confirmed, a.user_id - FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID WHERE t.UUID=(?)" - ) - .unwrap(); - - stmt.query_row([key], |row| { - let raw_dispute_tx: Vec = row.get(0).unwrap(); - let raw_penalty_tx: Vec = row.get(1).unwrap(); - let height: u32 = row.get(2).unwrap(); - let confirmed: bool = row.get(3).unwrap(); - let raw_userid: Vec = row.get(4).unwrap(); - - let dispute_tx = consensus::deserialize(&raw_dispute_tx).unwrap(); - let penalty_tx = consensus::deserialize(&raw_penalty_tx).unwrap(); - let user_id = UserId::from_slice(&raw_userid).unwrap(); - - Ok(TransactionTracker { - dispute_tx, - penalty_tx, - status: ConfirmationStatus::from_db_data(height, confirmed), - user_id, + pub(crate) async fn load_tracker(&self, uuid: UUID) -> Option { + let sql = "SELECT t.dispute_tx, t.penalty_tx, a.user_id, t.height, t.confirmed FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID WHERE t.UUID=($1)"; + sqlx::query(sql) + .bind(uuid.to_vec()) + .map(|row: AnyRow| { + TransactionTracker::new( + Breach::new( + consensus::deserialize(row.get(0)).unwrap(), + consensus::deserialize(row.get(1)).unwrap(), + ), + UserId::from_slice(row.get(2)).unwrap(), + ConfirmationStatus::from_db_data( + row.get::(3) as u32, + row.get::(4) != 0, + ), + ) }) - }) - .ok() + .fetch_one(&self.pool) + .await + .ok() } /// Check if a tracker with `uuid` exists. - pub(crate) fn tracker_exists(&self, uuid: UUID) -> bool { - self.connection - .prepare("SELECT UUID FROM trackers WHERE UUID=(?)") - .unwrap() - .exists([uuid.to_vec()]) - .unwrap() + pub(crate) async fn tracker_exists(&self, uuid: UUID) -> bool { + sqlx::query("SELECT UUID FROM trackers WHERE UUID=($1)") + .bind(uuid.to_vec()) + .fetch_one(&self.pool) + .await + .is_ok() } /// Loads trackers from the database. If a locator is given, this method loads only the trackers /// matching this locator. If no locator is given, all the trackers in the database would be returned. - pub(crate) fn load_trackers( + pub(crate) async fn load_trackers( &self, locator: Option, ) -> HashMap { - let mut trackers = HashMap::new(); + let mut sql = "SELECT t.UUID, t.dispute_tx, t.penalty_tx, a.user_id, t.height, t.confirmed FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID".to_string(); - let mut sql = "SELECT t.UUID, t.dispute_tx, t.penalty_tx, t.height, t.confirmed, a.user_id - FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID" - .to_string(); // If a locator was passed, filter based on it. - if locator.is_some() { - sql.push_str(" WHERE a.locator=(?)"); - } - let mut stmt = self.connection.prepare(&sql).unwrap(); - - let mut rows = if let Some(locator) = locator { - stmt.query([locator.to_vec()]).unwrap() + let query = if let Some(locator) = locator { + sql.push_str(" WHERE a.locator=($1)"); + sqlx::query(&sql).bind(locator.to_vec()) } else { - stmt.query([]).unwrap() + sqlx::query(&sql) }; - while let Ok(Some(row)) = rows.next() { - let raw_uuid: Vec = row.get(0).unwrap(); - let uuid = UUID::from_slice(&raw_uuid[0..20]).unwrap(); - let raw_dispute_tx: Vec = row.get(1).unwrap(); - let dispute_tx = consensus::deserialize(&raw_dispute_tx).unwrap(); - let raw_penalty_tx: Vec = row.get(2).unwrap(); - let penalty_tx = consensus::deserialize(&raw_penalty_tx).unwrap(); - let height: u32 = row.get(3).unwrap(); - let confirmed: bool = row.get(4).unwrap(); - let raw_userid: Vec = row.get(5).unwrap(); - let user_id = UserId::from_slice(&raw_userid).unwrap(); - - trackers.insert( - uuid, - TransactionTracker { - dispute_tx, - penalty_tx, - status: ConfirmationStatus::from_db_data(height, confirmed), - user_id, - }, - ); - } - - trackers + query + .map(|row: AnyRow| { + ( + UUID::from_slice(row.get(0)).unwrap(), + TransactionTracker::new( + Breach::new( + consensus::deserialize(row.get(1)).unwrap(), + consensus::deserialize(row.get(2)).unwrap(), + ), + UserId::from_slice(row.get(3)).unwrap(), + ConfirmationStatus::from_db_data( + row.get::(4) as u32, + row.get::(5) != 0, + ), + ), + ) + }) + .fetch_all(&self.pool) + .await + .unwrap() + .into_iter() + .collect() } /// Loads trackers with the given confirmation status. /// /// Note that for [`ConfirmationStatus::InMempoolSince(height)`] variant, this pulls trackers /// with `h <= height` and not just `h = height`. - pub(crate) fn load_trackers_with_confirmation_status( + pub(crate) async fn load_trackers_with_confirmation_status( &self, status: ConfirmationStatus, ) -> Result, Error> { - let (height, confirmed) = status.to_db_data().ok_or(Error::MissingField)?; - let sql = format!( - "SELECT UUID FROM trackers WHERE confirmed=(?1) AND height{}(?2)", - if confirmed { "=" } else { "<=" } - ); - let mut stmt = self.connection.prepare(&sql).unwrap(); + let (height, confirmed) = status + .to_db_data() + .ok_or(Error::Decode("Tracker status isn't storable".into()))?; - Ok(stmt - .query_map(params![confirmed, height], |row| { - let raw_uuid: Vec = row.get(0).unwrap(); - let uuid = UUID::from_slice(&raw_uuid).unwrap(); - Ok(uuid) - }) - .unwrap() - .map(|uuid_res| uuid_res.unwrap()) - .collect()) + sqlx::query(&format!( + "SELECT UUID FROM trackers WHERE confirmed=($1) AND height{}($2)", + if confirmed { "=" } else { "<=" } + )) + .bind(confirmed as i64) + .bind(height as i64) + .map(|row: AnyRow| UUID::from_slice(row.get(0)).unwrap()) + .fetch_all(&self.pool) + .await } /// Loads the transaction IDs of all the penalties and their status from the database. - pub(crate) fn load_penalties_summaries(&self) -> HashMap { - let mut summaries = HashMap::new(); - - let mut stmt = self - .connection - .prepare( - "SELECT t.UUID, t.penalty_tx, t.height, t.confirmed - FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID", - ) - .unwrap(); - let mut rows = stmt.query([]).unwrap(); - - while let Ok(Some(row)) = rows.next() { - let raw_uuid: Vec = row.get(0).unwrap(); - let raw_penalty_tx: Vec = row.get(1).unwrap(); - let height: u32 = row.get(2).unwrap(); - let confirmed: bool = row.get(3).unwrap(); - - // DISCUSS: Should we store the txids to avoid pulling raw txs and deserializing then hashing them. - let penalty_txid = consensus::deserialize::(&raw_penalty_tx) - .unwrap() - .txid(); - summaries.insert( - UUID::from_slice(&raw_uuid).unwrap(), - PenaltySummary::new( - penalty_txid, - ConfirmationStatus::from_db_data(height, confirmed), - ), - ); - } - summaries + pub(crate) async fn load_penalties_summaries(&self) -> HashMap { + let sql = "SELECT t.UUID, t.penalty_tx, t.height, t.confirmed FROM trackers as t INNER JOIN appointments as a ON t.UUID=a.UUID"; + sqlx::query(sql) + .map(|row: AnyRow| { + ( + UUID::from_slice(row.get(0)).unwrap(), + PenaltySummary::new( + consensus::deserialize::(row.get(1)) + .unwrap() + .txid(), + ConfirmationStatus::from_db_data( + row.get::(2) as u32, + row.get::(3) != 0, + ), + ), + ) + }) + .fetch_all(&self.pool) + .await + .unwrap() + .into_iter() + .collect() } /// Stores the last known block into the database. - pub(crate) fn store_last_known_block(&self, block_hash: &BlockHash) -> Result<(), Error> { - let query = "INSERT OR REPLACE INTO last_known_block (id, block_hash) VALUES (0, ?)"; - self.store_data(query, params![block_hash.to_vec()]) + pub(crate) async fn store_last_known_block(&self, block_hash: &BlockHash) -> Result<(), Error> { + let sql = "INSERT INTO last_known_block (id, block_hash) VALUES (0, $1) ON CONFLICT (id) DO UPDATE SET block_hash = excluded.block_hash"; + sqlx::query(sql) + .bind(block_hash.to_vec()) + .execute(&self.pool) + .await + .map(|_| ()) } /// Loads the last known block from the database. - pub fn load_last_known_block(&self) -> Option { - let mut stmt = self - .connection - .prepare("SELECT block_hash FROM last_known_block WHERE id=0") - .unwrap(); - - stmt.query_row([], |row| { - let raw_hash: Vec = row.get(0).unwrap(); - Ok(BlockHash::from_slice(&raw_hash).unwrap()) - }) - .ok() + pub async fn load_last_known_block(&self) -> Option { + sqlx::query("SELECT block_hash FROM last_known_block WHERE id=0") + .map(|row: AnyRow| BlockHash::from_slice(row.get("block_hash")).unwrap()) + .fetch_one(&self.pool) + .await + .ok() } /// Stores the tower secret key into the database. /// /// When a new key is generated, old keys are not overwritten but are not retrievable from the API either. - pub fn store_tower_key(&self, sk: &SecretKey) -> Result<(), Error> { - let query = "INSERT INTO keys (key) VALUES (?)"; - self.store_data(query, params![sk.display_secret().to_string()]) + pub async fn store_tower_key(&self, sk: &SecretKey) -> Result<(), Error> { + sqlx::query("INSERT INTO keys (secret_key) VALUES ($1)") + .bind(sk.display_secret().to_string()) + .execute(&self.pool) + .await + .map(|_| ()) } /// Loads the last known tower secret key from the database. /// /// Loads the key with higher id from the database. Old keys are not overwritten just in case a recovery is needed, /// but they are not accessible from the API either. - pub fn load_tower_key(&self) -> Option { - let mut stmt = self - .connection - .prepare( - "SELECT key FROM keys WHERE id = (SELECT seq FROM sqlite_sequence WHERE name=(?))", - ) - .unwrap(); - - stmt.query_row(["keys"], |row| { - let sk: String = row.get(0).unwrap(); - Ok(SecretKey::from_str(&sk).unwrap()) - }) - .ok() + pub async fn load_tower_key(&self) -> Option { + sqlx::query("SELECT secret_key FROM keys WHERE id = (SELECT MAX(id) from keys)") + .map(|row: AnyRow| SecretKey::from_str(row.get("secret_key")).unwrap()) + .fetch_one(&self.pool) + .await + .ok() } } #[cfg(test)] mod tests { use super::*; + use std::collections::HashSet; use std::iter::FromIterator; @@ -766,141 +638,173 @@ mod tests { }; impl DBM { - pub(crate) fn in_memory() -> Result { - let connection = Connection::open_in_memory()?; - connection.execute("PRAGMA foreign_keys=1;", [])?; - let mut dbm = Self { connection }; - dbm.create_tables(Vec::from_iter(TABLES))?; - - Ok(dbm) + #[cfg(feature = "sqlite")] + async fn memory() -> Self { + use sqlx::any::AnyPoolOptions; + install_drivers(&[sqlx::sqlite::any::DRIVER]).ok(); + let pool = AnyPoolOptions::new() + // Each new connection actually creates a brand new DB: https://github.com/launchbadge/sqlx/issues/2510 + // So make sure the pool doesn't create more than one connection. + .max_connections(1) + .connect("sqlite::memory:") + .await + .unwrap(); + sqlx::migrate!("migrations/sqlite") + .run(&pool) + .await + .unwrap(); + Self { pool } } - pub(crate) fn load_user(&self, user_id: UserId) -> Option { - let key = user_id.to_vec(); - let mut stmt = self - .connection - .prepare( - "SELECT available_slots, subscription_start, subscription_expiry - FROM users WHERE user_id=(?)", - ) - .unwrap(); - stmt.query_row([&key], |row| { - let slots = row.get(0).unwrap(); - let start = row.get(1).unwrap(); - let expiry = row.get(2).unwrap(); - Ok(UserInfo::new(slots, start, expiry)) - }) - .ok() + #[cfg(feature = "postgres")] + async fn postgres() -> Self { + let dbm = async { + return_db_if_matching!( + "postgres://user:pass@localhost/teos", + postgres, + "migrations/postgres" + ); + Err("Unreachable (the macro above will always match)".to_string()) + } + .await + .unwrap(); + // The DBM could have been used in a previous test, so make sure it is clear. + dbm.clear_db().await; + dbm } - } - #[test] - fn test_create_tables() { - let connection = Connection::open_in_memory().unwrap(); - let mut dbm = DBM { connection }; - dbm.create_tables(Vec::from_iter(TABLES)).unwrap(); - } + /// Clears all the DB tables. To be used to emulate starting a brand new database. + async fn clear_db(&self) { + let tables_to_clear = [ + "users", + "appointments", + "trackers", + "last_known_block", + "keys", + ]; + for table in tables_to_clear { + sqlx::query(&format!("DELETE FROM {table}")) + .execute(&self.pool) + .await + .unwrap(); + } + } - #[test] - fn test_store_load_user() { - let dbm = DBM::in_memory().unwrap(); + #[allow(unreachable_code)] + /// Returns a new [DBM], preferring sqlite over postgres if available. + pub(crate) async fn test_db() -> Self { + #[cfg(feature = "sqlite")] + return Self::memory().await; - let user_id = get_random_user_id(); - let mut user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + // WARNING: When running the tests on PostgreSQL set the environment variable RUST_TEST_THREADS=1. + // Otherwise tests will run on parallel and contaminate the database. + #[cfg(feature = "postgres")] + return Self::postgres().await; - assert!(matches!(dbm.store_user(user_id, &user), Ok { .. })); - assert_eq!(dbm.load_user(user_id).unwrap(), user); + panic!("No database driver available. Make sure you compile with sqlite and/or postgres features enabled.") + } - // User info should be updatable but only via the update_user method - user = UserInfo::new(AVAILABLE_SLOTS * 2, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - assert!(matches!( - dbm.store_user(user_id, &user), - Err(Error::AlreadyExists) - )); + pub(crate) async fn load_user(&self, user_id: UserId) -> Option { + let sql = "SELECT available_slots, subscription_start, subscription_expiry FROM users WHERE user_id=($1)"; + sqlx::query(sql) + .bind(user_id.to_vec()) + .map(|row: AnyRow| { + UserInfo::new( + row.get::("available_slots") as u32, + row.get::("subscription_start") as u32, + row.get::("subscription_expiry") as u32, + ) + }) + .fetch_one(&self.pool) + .await + .ok() + } } - #[test] - fn test_load_nonexistent_user() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_user() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); - assert!(dbm.load_user(user_id).is_none()); + let user_info = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + + assert!(dbm.load_user(user_id).await.is_none()); + + dbm.store_user(user_id, &user_info).await.unwrap(); + assert_eq!(dbm.load_user(user_id).await, Some(user_info)); + + // Store an existing user should error (should use update_user). + dbm.store_user(user_id, &user_info).await.unwrap_err(); } - #[test] - fn test_update_user() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_update_user() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); - let mut user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + let mut user_info = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); - assert_eq!(dbm.load_user(user_id).unwrap(), user); + dbm.store_user(user_id, &user_info).await.unwrap(); - user.available_slots *= 2; - dbm.update_user(user_id, &user); - assert_eq!(dbm.load_user(user_id).unwrap(), user); + user_info.available_slots *= 2; + dbm.update_user(user_id, &user_info).await.unwrap(); + assert_eq!(dbm.load_user(user_id).await, Some(user_info)); } - #[test] - fn test_load_user_locators() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_user_locators() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); - let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + let user_info = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + dbm.store_user(user_id, &user_info).await.unwrap(); let mut locators = HashSet::new(); // Add some appointments to the user for _ in 0..10 { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); locators.insert(appointment.locator()); } - assert_eq!(dbm.load_user(user_id).unwrap(), user); + assert_eq!(dbm.load_user(user_id).await, Some(user_info)); assert_eq!( - HashSet::from_iter(dbm.load_user_locators(user_id)), + HashSet::from_iter(dbm.load_user_locators(user_id).await), locators ); } - #[test] - fn test_load_all_users() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_all_users() { + let dbm = DBM::test_db().await; let mut users = HashMap::new(); for i in 1..11 { let user_id = get_random_user_id(); - let user = UserInfo::new( + let user_info = UserInfo::new( AVAILABLE_SLOTS + i, SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - users.insert(user_id, user); - dbm.store_user(user_id, &user).unwrap(); + users.insert(user_id, user_info); + dbm.store_user(user_id, &user_info).await.unwrap(); } - assert_eq!(dbm.load_all_users(), users); + assert_eq!(dbm.load_all_users().await, users); } - #[test] - fn test_batch_remove_users() { - let mut dbm = DBM::in_memory().unwrap(); - - // Set a limit value for the maximum number of variables in SQLite so we can - // test splitting big queries into chunks. - let limit = 10; - dbm.connection - .set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, limit); + #[tokio::test] + async fn test_batch_remove_users() { + let dbm = DBM::test_db().await; let mut to_be_deleted = Vec::new(); let mut rest = HashSet::new(); - for i in 1..100 { + + for i in 0..SQL_VARIABLE_LIMIT * 3 { let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); if i % 2 == 0 { to_be_deleted.push(user_id); @@ -909,16 +813,18 @@ mod tests { } } - // Check that the db transaction had 5 (100/2*10) queries on it - assert_eq!(dbm.batch_remove_users(&to_be_deleted), 5); + // SQL_VARIABLE_LIMIT is 10 for tests, + // Check that deletion had `ceil(10 * 3 / 2) / 10` (2) queries on it + assert_eq!(dbm.batch_remove_users(to_be_deleted).await, 2); + // Check user data was deleted - assert_eq!(rest, dbm.load_all_users().keys().cloned().collect()); + assert_eq!(rest, dbm.load_all_users().await.keys().cloned().collect()); } - #[test] - fn test_batch_remove_users_cascade() { + #[tokio::test] + async fn test_batch_remove_users_cascade() { // Test that removing users cascade deleted appointments and trackers - let mut dbm = DBM::in_memory().unwrap(); + let dbm = DBM::test_db().await; let uuid = generate_uuid(); let appointment = generate_dummy_appointment(None); // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. @@ -927,44 +833,38 @@ mod tests { // Add the user and link an appointment (this is usually done once the appointment) // is added after the user creation, but for the test purpose it can be done all at once. let info = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(appointment.user_id, &info).unwrap(); + dbm.store_user(appointment.user_id, &info).await.unwrap(); // Appointment only - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Ok { .. } - )); + dbm.store_appointment(uuid, &appointment).await.unwrap(); - dbm.batch_remove_users(&vec![appointment.user_id]); - assert!(dbm.load_user(appointment.user_id).is_none()); - assert!(dbm.load_appointment(uuid).is_none()); + dbm.batch_remove_users(vec![appointment.user_id]).await; + assert!(dbm.load_user(appointment.user_id).await.is_none()); + assert!(dbm.load_appointment(uuid).await.is_none()); // Appointment + Tracker - dbm.store_user(appointment.user_id, &info).unwrap(); - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Ok { .. } - )); - assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); + dbm.store_user(appointment.user_id, &info).await.unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); - dbm.batch_remove_users(&vec![appointment.user_id]); - assert!(dbm.load_user(appointment.user_id).is_none()); - assert!(dbm.load_appointment(uuid).is_none()); - assert!(dbm.load_tracker(uuid).is_none()); + dbm.batch_remove_users(vec![appointment.user_id]).await; + assert!(dbm.load_user(appointment.user_id).await.is_none()); + assert!(dbm.load_appointment(uuid).await.is_none()); + assert!(dbm.load_tracker(uuid).await.is_none()); } - #[test] - fn test_batch_remove_nonexistent_users() { - let mut dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_batch_remove_nonexistent_users() { + let dbm = DBM::test_db().await; let users = (0..10).map(|_| get_random_user_id()).collect(); - // Test it does not fail even if the user does not exist (it will log though) - dbm.batch_remove_users(&users); + // Test it does not fail even if the user does not exist + dbm.batch_remove_users(users).await; } - #[test] - fn test_get_appointments_trackers_count() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_get_appointments_trackers_count() { + let dbm = DBM::test_db().await; let n_users = 100; let n_app_per_user = 4; let n_trk_per_user = 6; @@ -972,124 +872,171 @@ mod tests { for _ in 0..n_users { let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); // These are un-triggered appointments. for _ in 0..n_app_per_user { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); } // And these are triggered ones (trackers). for _ in 0..n_trk_per_user { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); let tracker = get_random_tracker(user_id, ConfirmationStatus::ConfirmedIn(42)); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); } } - assert_eq!(dbm.get_appointments_count(), n_users * n_app_per_user); - assert_eq!(dbm.get_trackers_count(), n_users * n_trk_per_user); + assert_eq!(dbm.get_appointments_count().await, n_users * n_app_per_user); + assert_eq!(dbm.get_trackers_count().await, n_users * n_trk_per_user); } - #[test] - fn test_store_load_appointment() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_load_appointment() { + let dbm = DBM::test_db().await; // In order to add an appointment we need the associated user to be present let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Ok { .. } - )); - assert_eq!(dbm.load_appointment(uuid).unwrap(), appointment); + dbm.store_appointment(uuid, &appointment).await.unwrap(); + + assert_eq!(dbm.load_appointment(uuid).await.unwrap(), appointment); // Appointment info should be updatable but only via the update_appointment method - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Err(Error::AlreadyExists) - )); + assert!(dbm + .store_appointment(uuid, &appointment) + .await + .unwrap_err() + .into_database_error() + .unwrap() + .is_unique_violation()) } - #[test] - fn test_store_appointment_missing_user() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_appointment_missing_user() { + let dbm = DBM::test_db().await; let uuid = generate_uuid(); let appointment = generate_dummy_appointment(None); - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Err(Error::MissingForeignKey) - )); - assert!((dbm.load_tracker(uuid).is_none())); + assert!(dbm + .store_appointment(uuid, &appointment) + .await + .unwrap_err() + .into_database_error() + .unwrap() + .is_foreign_key_violation()); + assert!((dbm.load_tracker(uuid).await.is_none())); } - #[test] - fn test_load_nonexistent_appointment() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_update_appointment() { + let dbm = DBM::test_db().await; + + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + dbm.store_user(user_id, &user).await.unwrap(); + + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).await.unwrap(); + + // Modify the appointment and update it + let mut modified_appointment = appointment; + modified_appointment.inner.encrypted_blob.reverse(); + + // Not all fields are updatable, create another appointment modifying fields that cannot be + let mut another_modified_appointment = modified_appointment.clone(); + another_modified_appointment.user_id = get_random_user_id(); + + // Check how only the modifiable fields have been updated + dbm.update_appointment(uuid, &another_modified_appointment) + .await + .unwrap(); + assert_eq!( + dbm.load_appointment(uuid).await.unwrap(), + modified_appointment + ); + assert_ne!( + dbm.load_appointment(uuid).await.unwrap(), + another_modified_appointment + ); + } + + #[tokio::test] + async fn test_load_nonexistent_appointment() { + let dbm = DBM::test_db().await; let uuid = generate_uuid(); - assert!(dbm.load_appointment(uuid).is_none()); + assert!(dbm.load_appointment(uuid).await.is_none()); } - #[test] - fn test_appointment_exists() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_appointment_exists() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - assert!(!dbm.appointment_exists(uuid)); + assert!(!dbm.appointment_exists(uuid).await); - dbm.store_user(user_id, &user).unwrap(); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); - assert!(dbm.appointment_exists(uuid)); + assert!(dbm.appointment_exists(uuid).await); } - #[test] - fn test_update_appointment() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_get_appointment_length() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + + dbm.store_user(user_id, &user).await.unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); + + assert_eq!( + dbm.get_appointment_length(uuid).await.unwrap(), + appointment.inner.encrypted_blob.len() + ); assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Ok { .. } + dbm.get_appointment_length(generate_uuid()).await, + Err(Error::RowNotFound) )); + } - // Modify the appointment and update it - let mut modified_appointment = appointment; - modified_appointment.inner.encrypted_blob.reverse(); + #[tokio::test] + async fn test_get_appointment_user_and_length() { + let dbm = DBM::test_db().await; - // Not all fields are updatable, create another appointment modifying fields that cannot be - let mut another_modified_appointment = modified_appointment.clone(); - another_modified_appointment.user_id = get_random_user_id(); + let user_id = get_random_user_id(); + let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); + let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - // Check how only the modifiable fields have been updated - dbm.update_appointment(uuid, &another_modified_appointment) - .unwrap(); - assert_eq!(dbm.load_appointment(uuid).unwrap(), modified_appointment); - assert_ne!( - dbm.load_appointment(uuid).unwrap(), - another_modified_appointment + dbm.store_user(user_id, &user).await.unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); + + assert_eq!( + dbm.get_appointment_user_and_length(uuid).await.unwrap(), + (user_id, appointment.encrypted_blob().len()) ); + assert!(matches!( + dbm.get_appointment_user_and_length(generate_uuid()).await, + Err(Error::RowNotFound) + )); } - #[test] - fn test_load_all_appointments() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_all_appointments() { + let dbm = DBM::test_db().await; let mut appointments = HashMap::new(); for i in 1..11 { @@ -1099,35 +1046,35 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); appointments.insert(uuid, appointment); } - assert_eq!(dbm.load_appointments(None), appointments); + assert_eq!(dbm.load_appointments(None).await, appointments); // If an appointment has an associated tracker, it should not be loaded since it is seen // as a triggered appointment let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. let tracker = get_random_tracker(user_id, ConfirmationStatus::InMempoolSince(100)); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); // We should get all the appointments back except from the triggered one - assert_eq!(dbm.load_appointments(None), appointments); + assert_eq!(dbm.load_appointments(None).await, appointments); } - #[test] - fn test_load_appointments_with_locator() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_appointments_with_locator() { + let dbm = DBM::test_db().await; let mut appointments = HashMap::new(); let dispute_tx = get_random_tx(); let dispute_txid = dispute_tx.txid(); @@ -1140,91 +1087,66 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); // Let some appointments belong to a specific dispute tx and some with random ones. // We will use the locator for that dispute tx to query these appointments. if i % 2 == 0 { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // Store the appointments made using our dispute tx. appointments.insert(uuid, appointment); } else { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); } } // Validate that no other appointments than the ones with our locator are returned. - assert_eq!(dbm.load_appointments(Some(locator)), appointments); + assert_eq!(dbm.load_appointments(Some(locator)).await, appointments); // If an appointment has an associated tracker, it should not be loaded since it is seen // as a triggered appointment let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); // Generate an appointment for our dispute tx, thus it gets the same locator as the ones generated above. let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. let tracker = get_random_tracker(user_id, ConfirmationStatus::InMempoolSince(100)); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); // We should get all the appointments matching our locator back except from the triggered one - assert_eq!(dbm.load_appointments(Some(locator)), appointments); + assert_eq!(dbm.load_appointments(Some(locator)).await, appointments); } - #[test] - fn test_get_appointment_length() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_remove_appointment() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); - let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - - dbm.store_user(user_id, &user).unwrap(); - dbm.store_appointment(uuid, &appointment).unwrap(); - - assert_eq!( - dbm.get_appointment_length(uuid).unwrap(), - appointment.inner.encrypted_blob.len() + let user = UserInfo::new( + AVAILABLE_SLOTS + 123, + SUBSCRIPTION_START, + SUBSCRIPTION_EXPIRY, ); - assert!(dbm.get_appointment_length(generate_uuid()).is_none()); - } + dbm.store_user(user_id, &user).await.unwrap(); - #[test] - fn test_get_appointment_user_and_length() { - let dbm = DBM::in_memory().unwrap(); - - let user_id = get_random_user_id(); - let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); + dbm.store_appointment(uuid, &appointment).await.unwrap(); - dbm.store_user(user_id, &user).unwrap(); - dbm.store_appointment(uuid, &appointment).unwrap(); - - assert_eq!( - dbm.get_appointment_user_and_length(uuid).unwrap(), - (user_id, appointment.encrypted_blob().len()) - ); - assert!(dbm - .get_appointment_user_and_length(generate_uuid()) - .is_none()); + dbm.remove_appointment(uuid).await; + assert!(!dbm.appointment_exists(uuid).await) } - #[test] - fn test_batch_remove_appointments() { - let mut dbm = DBM::in_memory().unwrap(); - - // Set a limit value for the maximum number of variables in SQLite so we can - // test splitting big queries into chunks. - let limit = 10; - dbm.connection - .set_limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER, limit); + #[tokio::test] + async fn test_batch_remove_appointments() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); let mut user = UserInfo::new( @@ -1232,14 +1154,14 @@ mod tests { SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let mut rest = HashSet::new(); for i in 1..6 { let mut to_be_deleted = Vec::new(); - for j in 0..limit * 2 * i { + for j in 0..SQL_VARIABLE_LIMIT * 2 * i { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); if j % 2 == 0 { to_be_deleted.push(uuid); @@ -1255,21 +1177,25 @@ mod tests { // Check that the db transaction had i queries on it assert_eq!( - dbm.batch_remove_appointments(&to_be_deleted, &updated_users), - i as usize + dbm.batch_remove_appointments(to_be_deleted, updated_users) + .await, + i ); // Check appointment data was deleted and users properly updated - assert_eq!(rest, dbm.load_appointments(None).keys().cloned().collect()); assert_eq!( - dbm.load_user(user_id).unwrap().available_slots, + rest, + dbm.load_appointments(None).await.keys().cloned().collect() + ); + assert_eq!( + dbm.load_user(user_id).await.unwrap().available_slots, user.available_slots ); } } - #[test] - fn test_batch_remove_appointments_cascade() { - let mut dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_batch_remove_appointments_cascade() { + let dbm = DBM::test_db().await; let uuid = generate_uuid(); let appointment = generate_dummy_appointment(None); // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. @@ -1278,47 +1204,45 @@ mod tests { let info = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); // Add the user b/c of FK restrictions - dbm.store_user(appointment.user_id, &info).unwrap(); + dbm.store_user(appointment.user_id, &info).await.unwrap(); + println!("{}", appointment.inner.to_self_delay); // Appointment only - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Ok { .. } - )); + dbm.store_appointment(uuid, &appointment).await.unwrap(); dbm.batch_remove_appointments( - &vec![uuid], - &HashMap::from_iter([(appointment.user_id, info)]), - ); - assert!(dbm.load_appointment(uuid).is_none()); + vec![uuid], + HashMap::from_iter([(appointment.user_id, info)]), + ) + .await; + assert!(dbm.load_appointment(uuid).await.is_none()); // Appointment + Tracker - assert!(matches!( - dbm.store_appointment(uuid, &appointment), - Ok { .. } - )); - assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); + dbm.store_appointment(uuid, &appointment).await.unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); dbm.batch_remove_appointments( - &vec![uuid], - &HashMap::from_iter([(appointment.user_id, info)]), - ); - assert!(dbm.load_appointment(uuid).is_none()); - assert!(dbm.load_tracker(uuid).is_none()); + vec![uuid], + HashMap::from_iter([(appointment.user_id, info)]), + ) + .await; + assert!(dbm.load_appointment(uuid).await.is_none()); + assert!(dbm.load_tracker(uuid).await.is_none()); } - #[test] - fn test_batch_remove_nonexistent_appointments() { - let mut dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_batch_remove_nonexistent_appointments() { + let dbm = DBM::test_db().await; let appointments = (0..10).map(|_| generate_uuid()).collect(); - // Test it does not fail even if the user does not exist (it will log though) - dbm.batch_remove_appointments(&appointments, &HashMap::new()); + // Test it does not fail even if the user does not exist + dbm.batch_remove_appointments(appointments, HashMap::new()) + .await; } - #[test] - fn test_load_uuids() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_uuids() { + let dbm = DBM::test_db().await; let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); let dispute_tx = get_random_tx(); @@ -1328,11 +1252,11 @@ mod tests { // Add ten appointments triggered by the same locator. for _ in 0..10 { let user_id = get_random_user_id(); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); uuids.insert(uuid); } @@ -1340,23 +1264,23 @@ mod tests { // Add ten more appointments triggered by different locators. for _ in 0..10 { let user_id = get_random_user_id(); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let dispute_txid = get_random_tx().txid(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); } assert_eq!( - HashSet::from_iter(dbm.load_uuids(Locator::new(dispute_txid))), + HashSet::from_iter(dbm.load_uuids(Locator::new(dispute_txid)).await), uuids ); } - #[test] - fn test_batch_check_locators_exist() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_batch_check_locators_exist() { + let dbm = DBM::test_db().await; // Generate `n_app` appointments which we will store in the DB. let n_app = 100; let appointments: Vec<_> = (0..n_app) @@ -1366,12 +1290,13 @@ mod tests { // Register all the users beforehand. for user_id in appointments.iter().map(|a| a.user_id) { let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); } // Store all the `n_app` appointments. for appointment in appointments.iter() { dbm.store_appointment(appointment.uuid(), appointment) + .await .unwrap(); } @@ -1389,55 +1314,58 @@ mod tests { .collect(); assert_eq!( - HashSet::from_iter(dbm.batch_check_locators_exist(all_locators)), + HashSet::from_iter(dbm.batch_check_locators_exist(all_locators).await), known_locators ); } - #[test] - fn test_store_load_tracker() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_load_tracker() { + let dbm = DBM::test_db().await; // In order to add a tracker we need the associated appointment to be present (which // at the same time requires an associated user to be present) let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. let tracker = get_random_tracker(user_id, ConfirmationStatus::ConfirmedIn(21)); - assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); - assert_eq!(dbm.load_tracker(uuid).unwrap(), tracker); + dbm.store_tracker(uuid, &tracker).await.unwrap(); + assert_eq!(dbm.load_tracker(uuid).await.unwrap(), tracker); } - #[test] - fn test_store_duplicate_tracker() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_duplicate_tracker() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. let tracker = get_random_tracker(user_id, ConfirmationStatus::InMempoolSince(42)); - assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); + dbm.store_tracker(uuid, &tracker).await.unwrap(); // Try to store it again, but it shouldn't go through - assert!(matches!( - dbm.store_tracker(uuid, &tracker), - Err(Error::AlreadyExists) - )); + assert!(dbm + .store_tracker(uuid, &tracker) + .await + .unwrap_err() + .into_database_error() + .unwrap() + .is_unique_violation()); } - #[test] - fn test_store_tracker_missing_appointment() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_tracker_missing_appointment() { + let dbm = DBM::test_db().await; let uuid = generate_uuid(); let user_id = get_random_user_id(); @@ -1445,31 +1373,36 @@ mod tests { // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. let tracker = get_random_tracker(user_id, ConfirmationStatus::InMempoolSince(42)); - assert!(matches!( - dbm.store_tracker(uuid, &tracker), - Err(Error::MissingForeignKey) - )); + // Try to store the tracker with no appointment for it + assert!(dbm + .store_tracker(uuid, &tracker) + .await + .unwrap_err() + .into_database_error() + .unwrap() + .is_foreign_key_violation()) } - #[test] - fn test_update_tracker_status() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_update_tracker_status() { + let dbm = DBM::test_db().await; let user_id = get_random_user_id(); let user = UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); let tracker = get_random_tracker(user_id, ConfirmationStatus::InMempoolSince(42)); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); // Update the status and check if it's actually updated. dbm.update_tracker_status(uuid, &ConfirmationStatus::ConfirmedIn(100)) + .await .unwrap(); assert_eq!( - dbm.load_tracker(uuid).unwrap().status, + dbm.load_tracker(uuid).await.unwrap().status, ConfirmationStatus::ConfirmedIn(100) ); @@ -1478,22 +1411,23 @@ mod tests { dbm.update_tracker_status( uuid, &ConfirmationStatus::Rejected(rpc_errors::RPC_VERIFY_REJECTED) - ), - Err(Error::MissingField) + ) + .await, + Err(Error::Decode(..)) )); } - #[test] - fn test_load_nonexistent_tracker() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_nonexistent_tracker() { + let dbm = DBM::test_db().await; let uuid = generate_uuid(); - assert!(dbm.load_tracker(uuid).is_none()); + assert!(dbm.load_tracker(uuid).await.is_none()); } - #[test] - fn test_load_all_trackers() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_all_trackers() { + let dbm = DBM::test_db().await; let mut trackers = HashMap::new(); for i in 1..11 { @@ -1503,23 +1437,23 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // The confirmation status doesn't really matter here, it can be any of {ConfirmedIn, InMempoolSince}. let tracker = get_random_tracker(user_id, ConfirmationStatus::InMempoolSince(42)); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); trackers.insert(uuid, tracker); } - assert_eq!(dbm.load_trackers(None), trackers); + assert_eq!(dbm.load_trackers(None).await, trackers); } - #[test] - fn test_load_trackers_with_locator() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_trackers_with_locator() { + let dbm = DBM::test_db().await; let mut trackers = HashMap::new(); let dispute_tx = get_random_tx(); let dispute_txid = dispute_tx.txid(); @@ -1533,7 +1467,7 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let tracker = get_random_tracker(user_id, status); // Let some trackers belong to our dispute tx and some belong to random ones. @@ -1546,16 +1480,16 @@ mod tests { } else { generate_dummy_appointment_with_user(user_id, None) }; - dbm.store_appointment(uuid, &appointment).unwrap(); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); } - assert_eq!(dbm.load_trackers(Some(locator)), trackers); + assert_eq!(dbm.load_trackers(Some(locator)).await, trackers); } - #[test] - fn test_load_trackers_with_confirmation_status_in_mempool() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_trackers_with_confirmation_status_in_mempool() { + let dbm = DBM::test_db().await; let n_trackers = 100; let mut tracker_statuses = HashMap::new(); @@ -1567,10 +1501,10 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // Some trackers confirmed and some aren't. let status = if i % 2 == 0 { @@ -1580,7 +1514,7 @@ mod tests { }; let tracker = get_random_tracker(user_id, status); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); tracker_statuses.insert(uuid, status); } @@ -1600,6 +1534,7 @@ mod tests { dbm.load_trackers_with_confirmation_status(ConfirmationStatus::InMempoolSince( i )) + .await .unwrap() ), in_mempool_since_i, @@ -1607,9 +1542,9 @@ mod tests { } } - #[test] - fn test_load_trackers_with_confirmation_status_confirmed() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_trackers_with_confirmation_status_confirmed() { + let dbm = DBM::test_db().await; let n_blocks = 100; let n_trackers = 30; let mut tracker_statuses = HashMap::new(); @@ -1624,10 +1559,10 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); // Some trackers confirmed and some aren't. let status = if j % 2 == 0 { @@ -1637,7 +1572,7 @@ mod tests { }; let tracker = get_random_tracker(user_id, status); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); tracker_statuses.insert(uuid, status); } } @@ -1655,6 +1590,7 @@ mod tests { assert_eq!( HashSet::from_iter( dbm.load_trackers_with_confirmation_status(ConfirmationStatus::ConfirmedIn(i)) + .await .unwrap() ), confirmed_in_i, @@ -1662,26 +1598,27 @@ mod tests { } } - #[test] - fn test_load_trackers_with_confirmation_status_bad_status() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_trackers_with_confirmation_status_bad_status() { + let dbm = DBM::test_db().await; assert!(matches!( dbm.load_trackers_with_confirmation_status(ConfirmationStatus::Rejected( rpc_errors::RPC_VERIFY_REJECTED - )), - Err(Error::MissingField) + )) + .await, + Err(Error::Decode(..)) )); - assert!(matches!( - dbm.load_trackers_with_confirmation_status(ConfirmationStatus::IrrevocablyResolved), - Err(Error::MissingField) + dbm.load_trackers_with_confirmation_status(ConfirmationStatus::IrrevocablyResolved) + .await, + Err(Error::Decode(..)) )); } - #[test] - fn test_load_penalties_summaries() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_load_penalties_summaries() { + let dbm = DBM::test_db().await; let n_trackers = 100; let mut penalties_summaries = HashMap::new(); @@ -1692,10 +1629,10 @@ mod tests { SUBSCRIPTION_START + i, SUBSCRIPTION_EXPIRY + i, ); - dbm.store_user(user_id, &user).unwrap(); + dbm.store_user(user_id, &user).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); - dbm.store_appointment(uuid, &appointment).unwrap(); + dbm.store_appointment(uuid, &appointment).await.unwrap(); let status = if i % 2 == 0 { ConfirmationStatus::InMempoolSince(i) @@ -1704,45 +1641,45 @@ mod tests { }; let tracker = get_random_tracker(user_id, status); - dbm.store_tracker(uuid, &tracker).unwrap(); + dbm.store_tracker(uuid, &tracker).await.unwrap(); penalties_summaries .insert(uuid, PenaltySummary::new(tracker.penalty_tx.txid(), status)); } - assert_eq!(dbm.load_penalties_summaries(), penalties_summaries); + assert_eq!(dbm.load_penalties_summaries().await, penalties_summaries); } - #[test] - fn test_store_load_last_known_block() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_load_last_known_block() { + let dbm = DBM::test_db().await; let mut block_hash = BlockHash::from_slice(&get_random_bytes(32)).unwrap(); - dbm.store_last_known_block(&block_hash).unwrap(); - assert_eq!(dbm.load_last_known_block().unwrap(), block_hash); + dbm.store_last_known_block(&block_hash).await.unwrap(); + assert_eq!(dbm.load_last_known_block().await.unwrap(), block_hash); // Update with a new hash to check it can be done block_hash = BlockHash::from_slice(&get_random_bytes(32)).unwrap(); - dbm.store_last_known_block(&block_hash).unwrap(); - assert_eq!(dbm.load_last_known_block().unwrap(), block_hash); + dbm.store_last_known_block(&block_hash).await.unwrap(); + assert_eq!(dbm.load_last_known_block().await.unwrap(), block_hash); } - #[test] - fn test_store_load_nonexistent_last_known_block() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_load_nonexistent_last_known_block() { + let dbm = DBM::test_db().await; - assert!(dbm.load_last_known_block().is_none()); + assert!(dbm.load_last_known_block().await.is_none()); } - #[test] - fn test_store_load_tower_key() { - let dbm = DBM::in_memory().unwrap(); + #[tokio::test] + async fn test_store_load_tower_key() { + let dbm = DBM::test_db().await; - assert!(dbm.load_tower_key().is_none()); + assert!(dbm.load_tower_key().await.is_none()); for _ in 0..7 { let sk = get_random_keypair().0; - dbm.store_tower_key(&sk).unwrap(); - assert_eq!(dbm.load_tower_key().unwrap(), sk); + dbm.store_tower_key(&sk).await.unwrap(); + assert_eq!(dbm.load_tower_key().await.unwrap(), sk); } } } diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index e5d490e3..5b874fc2 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -1,9 +1,10 @@ //! Logic related to the Gatekeeper, the component in charge of managing access to the tower resources. -use lightning::chain; use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; + +use tokio::sync::Mutex; use teos_common::appointment::{compute_appointment_slots, Locator}; use teos_common::constants::ENCRYPTED_BLOB_MAX_SIZE; @@ -11,6 +12,7 @@ use teos_common::cryptography; use teos_common::receipts::RegistrationReceipt; use teos_common::UserId; +use crate::async_listener::AsyncListen; use crate::dbm::DBM; use crate::extended_appointment::{ExtendedAppointment, UUID}; @@ -69,20 +71,20 @@ pub struct Gatekeeper { expiry_delta: u32, /// Map of users registered within the tower. registered_users: Mutex>, - /// A [DBM] (database manager) instance. Used to persist appointment data into disk. - dbm: Arc>, + /// A [DBM] (database manager) instance. Used to persist user data into disk. + dbm: Arc, } impl Gatekeeper { /// Creates a new [Gatekeeper] instance. - pub fn new( + pub async fn new( last_known_block_height: u32, subscription_slots: u32, subscription_duration: u32, expiry_delta: u32, - dbm: Arc>, + dbm: Arc, ) -> Self { - let registered_users = dbm.lock().unwrap().load_all_users(); + let registered_users = dbm.load_all_users().await; Gatekeeper { last_known_block_height: AtomicU32::new(last_known_block_height), subscription_slots, @@ -94,36 +96,34 @@ impl Gatekeeper { } /// Returns whether the [Gatekeeper] has been created from scratch (fresh) or from backed-up data. - pub fn is_fresh(&self) -> bool { - self.registered_users.lock().unwrap().is_empty() + pub async fn is_fresh(&self) -> bool { + self.registered_users.lock().await.is_empty() } /// Ges the number of users currently registered to the tower. - pub(crate) fn get_registered_users_count(&self) -> usize { - self.registered_users.lock().unwrap().len() + pub(crate) async fn get_registered_users_count(&self) -> usize { + self.registered_users.lock().await.len() } /// Gets the list of all registered user ids. - pub(crate) fn get_user_ids(&self) -> Vec { - self.registered_users - .lock() - .unwrap() - .keys() - .cloned() - .collect() + pub(crate) async fn get_user_ids(&self) -> Vec { + self.registered_users.lock().await.keys().cloned().collect() } /// Gets the data held by the tower about a given user. - pub(crate) fn get_user_info(&self, user_id: UserId) -> Option<(UserInfo, Vec)> { - let info = self.registered_users.lock().unwrap().get(&user_id).cloned(); - info.map(|info| (info, self.dbm.lock().unwrap().load_user_locators(user_id))) + pub(crate) async fn get_user_info(&self, user_id: UserId) -> Option<(UserInfo, Vec)> { + let info = self.registered_users.lock().await.get(&user_id).cloned(); + if let Some(info) = info { + return Some((info, self.dbm.load_user_locators(user_id).await)); + } + None } /// Authenticates a user. /// /// User authentication is performed using ECRecover against fixed messages (one for each command). /// Notice all interaction with the tower should be guarded by this. - pub(crate) fn authenticate_user( + pub(crate) async fn authenticate_user( &self, message: &[u8], signature: &str, @@ -133,7 +133,7 @@ impl Gatekeeper { .map_err(|_| AuthenticationFailure("Wrong message or signature."))?, ); - if self.registered_users.lock().unwrap().contains_key(&user_id) { + if self.registered_users.lock().await.contains_key(&user_id) { Ok(user_id) } else { Err(AuthenticationFailure("User not found.")) @@ -141,14 +141,14 @@ impl Gatekeeper { } /// Adds a new user to the tower (or updates its subscription if already registered). - pub(crate) fn add_update_user( + pub(crate) async fn add_update_user( &self, user_id: UserId, ) -> Result { let block_count = self.last_known_block_height.load(Ordering::Acquire); // TODO: For now, new calls to `add_update_user` add subscription_slots to the current count and reset the expiry time - let mut registered_users = self.registered_users.lock().unwrap(); + let mut registered_users = self.registered_users.lock().await; let user_info = match registered_users.get_mut(&user_id) { // User already exists, updating the info Some(user_info) => { @@ -160,7 +160,7 @@ impl Gatekeeper { .subscription_expiry .checked_add(self.subscription_duration) .unwrap_or(u32::MAX); - self.dbm.lock().unwrap().update_user(user_id, user_info); + self.dbm.update_user(user_id, user_info).await.ok(); user_info } @@ -171,11 +171,7 @@ impl Gatekeeper { block_count, block_count + self.subscription_duration, ); - self.dbm - .lock() - .unwrap() - .store_user(user_id, &user_info) - .unwrap(); + self.dbm.store_user(user_id, &user_info).await.unwrap(); registered_users.insert(user_id, user_info); registered_users.get_mut(&user_id).unwrap() @@ -191,21 +187,16 @@ impl Gatekeeper { } /// Adds an appointment to a given user, or updates it if already present in the system (and belonging to the requester). - pub(crate) fn add_update_appointment( + pub(crate) async fn add_update_appointment( &self, user_id: UserId, uuid: UUID, appointment: &ExtendedAppointment, ) -> Result { // For updates, the difference between the existing appointment size and the update is computed. - let mut registered_users = self.registered_users.lock().unwrap(); + let mut registered_users = self.registered_users.lock().await; let user_info = registered_users.get_mut(&user_id).unwrap(); - let used_blob_size = self - .dbm - .lock() - .unwrap() - .get_appointment_length(uuid) - .unwrap_or(0); + let used_blob_size = self.dbm.get_appointment_length(uuid).await.unwrap_or(0); let used_slots = compute_appointment_slots(used_blob_size, ENCRYPTED_BLOB_MAX_SIZE); let required_slots = @@ -217,7 +208,7 @@ impl Gatekeeper { // than the old appointment user_info.available_slots = (user_info.available_slots as i64 - diff) as u32; - self.dbm.lock().unwrap().update_user(user_id, user_info); + self.dbm.update_user(user_id, user_info).await.ok(); Ok(user_info.available_slots) } else { @@ -226,11 +217,11 @@ impl Gatekeeper { } /// Checks whether a subscription has expired. - pub(crate) fn has_subscription_expired( + pub(crate) async fn has_subscription_expired( &self, user_id: UserId, ) -> Result<(bool, u32), AuthenticationFailure<'_>> { - self.registered_users.lock().unwrap().get(&user_id).map_or( + self.registered_users.lock().await.get(&user_id).map_or( Err(AuthenticationFailure("User not found.")), |user_info| { Ok(( @@ -244,15 +235,14 @@ impl Gatekeeper { /// Gets a map of outdated users. Outdated users are those whose subscription has expired and the renewal grace period /// has already passed ([expiry_delta](Self::expiry_delta)). - pub(crate) fn get_outdated_users(&self, block_height: u32) -> Vec { - self.registered_users - .lock() - .unwrap() - .iter() + pub(crate) async fn get_outdated_users(&self, block_height: u32) -> Vec { + let registered_users = self.registered_users.lock().await.clone(); + registered_users + .into_iter() // NOTE: Ideally there won't be a user with `block_height > subscription_expiry + expiry_delta`, but // this might happen if we skip a couple of block connections due to a force update. .filter(|(_, info)| block_height >= info.subscription_expiry + self.expiry_delta) - .map(|(user_id, _)| *user_id) + .map(|(user_id, _)| user_id) .collect() } @@ -262,15 +252,17 @@ impl Gatekeeper { /// /// DISCUSS: When `refund` is `false` we don't give back the slots to the user for the deleted appointments. /// This is to discourage misbehavior (sending bad appointments, either non-decryptable or rejected by the network). - pub(crate) fn delete_appointments(&self, appointments: Vec, refund: bool) { - let mut dbm = self.dbm.lock().unwrap(); - + pub(crate) async fn delete_appointments(&self, appointments: Vec, refund: bool) { let updated_users = if refund { let mut updated_users = HashMap::new(); - let mut registered_users = self.registered_users.lock().unwrap(); + let mut registered_users = self.registered_users.lock().await; // Give back the consumed slots to each user. for uuid in appointments.iter() { - let (user_id, blob_size) = dbm.get_appointment_user_and_length(*uuid).unwrap(); + let (user_id, blob_size) = self + .dbm + .get_appointment_user_and_length(*uuid) + .await + .unwrap(); registered_users.get_mut(&user_id).unwrap().available_slots += compute_appointment_slots(blob_size, ENCRYPTED_BLOB_MAX_SIZE); updated_users.insert(user_id, registered_users[&user_id]); @@ -284,38 +276,36 @@ impl Gatekeeper { // An optimization for the case when only one appointment is being deleted without refunding. // This avoids creating a DB transaction for a single query. if appointments.len() == 1 && updated_users.is_empty() { - dbm.remove_appointment(appointments[0]) + self.dbm.remove_appointment(appointments[0]).await; } else { - dbm.batch_remove_appointments(&appointments, &updated_users); + self.dbm + .batch_remove_appointments(appointments, updated_users) + .await; } } } -impl chain::Listen for Gatekeeper { +#[tonic::async_trait] +impl AsyncListen for Gatekeeper { /// Handles the monitoring process by the [Gatekeeper]. /// /// This is mainly used to keep track of time and expire / outdate subscriptions when needed. - fn filtered_block_connected( - &self, - header: &bitcoin::BlockHeader, - _: &chain::transaction::TransactionData, - height: u32, - ) { - log::info!("New block received: {}", header.block_hash()); + async fn block_connected(&self, block: &bitcoin::Block, height: u32) { + log::info!("New block received: {}", block.header.block_hash()); // Expired user deletion is delayed. Users are deleted when their subscription is outdated, not expired. - let outdated_users = self.get_outdated_users(height); + let outdated_users = self.get_outdated_users(height).await; if !outdated_users.is_empty() { // Remove the outdated users from memory first. { - let mut registered_users = self.registered_users.lock().unwrap(); + let mut registered_users = self.registered_users.lock().await; // Removing each outdated user in a loop is more efficient than retaining non-outdated users // because retaining would loop over all the available users which is always more than the outdated ones. for outdated_user in outdated_users.iter() { registered_users.remove(outdated_user); } } - self.dbm.lock().unwrap().batch_remove_users(&outdated_users); + self.dbm.batch_remove_users(outdated_users).await; } // Update last known block height @@ -324,7 +314,7 @@ impl chain::Listen for Gatekeeper { } /// Handles reorgs in the [Gatekeeper]. Simply updates the last_known_block_height. - fn block_disconnected(&self, header: &bitcoin::BlockHeader, height: u32) { + async fn block_disconnected(&self, header: &bitcoin::BlockHeader, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); // There's nothing to be done here but updating the last known block self.last_known_block_height @@ -337,7 +327,6 @@ mod tests { use super::*; use crate::test_utils::{generate_dummy_appointment_with_user, get_random_tracker, Blockchain}; - use lightning::chain::Listen; use teos_common::cryptography::{get_random_bytes, get_random_keypair}; use teos_common::test_utils::get_random_user_id; @@ -353,9 +342,10 @@ mod tests { self.subscription_slots == other.subscription_slots && self.subscription_duration == other.subscription_duration && self.expiry_delta == other.expiry_delta - && *self.registered_users.lock().unwrap() == *other.registered_users.lock().unwrap() && self.last_known_block_height.load(Ordering::Relaxed) == other.last_known_block_height.load(Ordering::Relaxed) + && *self.registered_users.try_lock().unwrap() + == *other.registered_users.try_lock().unwrap() } } impl Eq for Gatekeeper {} @@ -365,24 +355,24 @@ mod tests { &self.registered_users } - pub(crate) fn add_outdated_user(&self, user_id: UserId, outdates_at: u32) { - self.add_update_user(user_id).unwrap(); - let mut registered_users = self.registered_users.lock().unwrap(); + pub(crate) async fn add_outdated_user(&self, user_id: UserId, outdates_at: u32) { + self.add_update_user(user_id).await.unwrap(); + let mut registered_users = self.registered_users.lock().await; let user = registered_users.get_mut(&user_id).unwrap(); user.subscription_expiry = outdates_at - self.expiry_delta; } } - fn init_gatekeeper(chain: &Blockchain) -> Gatekeeper { - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); - Gatekeeper::new(chain.get_block_count(), SLOTS, DURATION, EXPIRY_DELTA, dbm) + async fn init_gatekeeper(chain: &Blockchain) -> Gatekeeper { + let dbm = Arc::new(DBM::test_db().await); + Gatekeeper::new(chain.get_block_count(), SLOTS, DURATION, EXPIRY_DELTA, dbm).await } - #[test] - fn test_new() { + #[tokio::test] + async fn test_new() { // A fresh gatekeeper has no associated data let chain = Blockchain::default().with_height(START_HEIGHT); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); + let dbm = Arc::new(DBM::test_db().await); let gatekeeper = Gatekeeper::new( chain.get_block_count(), @@ -390,38 +380,39 @@ mod tests { DURATION, EXPIRY_DELTA, dbm.clone(), - ); - assert!(gatekeeper.is_fresh()); + ) + .await; + assert!(gatekeeper.is_fresh().await); // If we add some users and appointments to the system and create a new Gatekeeper reusing the same db // (as if simulating a bootstrap from existing data), the data should be properly loaded. for _ in 0..10 { let user_id = get_random_user_id(); - gatekeeper.add_update_user(user_id).unwrap(); + gatekeeper.add_update_user(user_id).await.unwrap(); let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); // Add the appointment to the database. This is normally done by the Watcher. gatekeeper .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); } // Create a new GK reusing the same DB and check that the data is loaded let another_gk = - Gatekeeper::new(chain.get_block_count(), SLOTS, DURATION, EXPIRY_DELTA, dbm); - assert!(!another_gk.is_fresh()); + Gatekeeper::new(chain.get_block_count(), SLOTS, DURATION, EXPIRY_DELTA, dbm).await; + assert!(!another_gk.is_fresh().await); assert_eq!(gatekeeper, another_gk); } - #[test] - fn test_authenticate_user() { - let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)); + #[tokio::test] + async fn test_authenticate_user() { + let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)).await; // Authenticate user returns the UserId if the user is found in the system, or an AuthenticationError otherwise. @@ -429,7 +420,7 @@ mod tests { let message = "message".as_bytes(); let wrong_signature = "signature"; assert_eq!( - gatekeeper.authenticate_user(message, wrong_signature), + gatekeeper.authenticate_user(message, wrong_signature).await, Err(AuthenticationFailure("Wrong message or signature.")) ); @@ -437,33 +428,33 @@ mod tests { let (user_sk, user_pk) = get_random_keypair(); let signature = cryptography::sign(message, &user_sk).unwrap(); assert_eq!( - gatekeeper.authenticate_user(message, &signature), + gatekeeper.authenticate_user(message, &signature).await, Err(AuthenticationFailure("User not found.")) ); // Last, let's add the user to the Gatekeeper and try again. let user_id = UserId(user_pk); - gatekeeper.add_update_user(user_id).unwrap(); + gatekeeper.add_update_user(user_id).await.unwrap(); assert_eq!( - gatekeeper.authenticate_user(message, &signature), + gatekeeper.authenticate_user(message, &signature).await, Ok(user_id) ); } - #[test] - fn test_add_update_user() { + #[tokio::test] + async fn test_add_update_user() { let mut chain = Blockchain::default().with_height(START_HEIGHT); - let gatekeeper = init_gatekeeper(&chain); + let gatekeeper = init_gatekeeper(&chain).await; // add_update_user adds a user to the system if it is not still registered, otherwise it add slots to the user subscription // and refreshes the subscription expiry. Slots are added up to u32:MAX, further call will return an MaxSlotsReached error. // Let's start by adding new user let user_id = get_random_user_id(); - let receipt = gatekeeper.add_update_user(user_id).unwrap(); + let receipt = gatekeeper.add_update_user(user_id).await.unwrap(); // The data should have been also added to the database assert_eq!( - gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(), + gatekeeper.dbm.load_user(user_id).await.unwrap(), UserInfo::new( receipt.available_slots(), receipt.subscription_start(), @@ -476,7 +467,7 @@ mod tests { gatekeeper .last_known_block_height .store(chain.get_block_count(), Ordering::Relaxed); - let updated_receipt = gatekeeper.add_update_user(user_id).unwrap(); + let updated_receipt = gatekeeper.add_update_user(user_id).await.unwrap(); assert_eq!(updated_receipt.available_slots(), SLOTS * 2); assert_eq!( @@ -486,7 +477,7 @@ mod tests { // Data in the database should have been updated too assert_eq!( - gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(), + gatekeeper.dbm.load_user(user_id).await.unwrap(), UserInfo::new( updated_receipt.available_slots(), updated_receipt.subscription_start(), @@ -498,19 +489,19 @@ mod tests { gatekeeper .registered_users .lock() - .unwrap() + .await .get_mut(&user_id) .unwrap() .available_slots = u32::MAX; assert!(matches!( - gatekeeper.add_update_user(user_id), + gatekeeper.add_update_user(user_id).await, Err(MaxSlotsReached) )); // Data in the database remains untouched assert_eq!( - gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(), + gatekeeper.dbm.load_user(user_id).await.unwrap(), UserInfo::new( updated_receipt.available_slots(), updated_receipt.subscription_start(), @@ -519,56 +510,57 @@ mod tests { ); } - #[test] - fn test_add_update_appointment() { - let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)); + #[tokio::test] + async fn test_add_update_appointment() { + let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)).await; // if a given appointment is not associated with a given user, add_update_appointment adds the appointment user appointments alongside the number os slots it consumes. If the appointment // is already associated with the user, it will update it (both data and slot count). // Let's first add the a user to the Gatekeeper (inputs are always sanitized here, so we don't need tests for non-registered users) let user_id = get_random_user_id(); - gatekeeper.add_update_user(user_id).unwrap(); + gatekeeper.add_update_user(user_id).await.unwrap(); // Now let's add a new appointment let slots_before = gatekeeper .registered_users .lock() - .unwrap() + .await .get(&user_id) .unwrap() .available_slots; let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); let available_slots = gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); // Simulate the watcher adding the appointment in the database. gatekeeper .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); - let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + let (_, user_locators) = gatekeeper.get_user_info(user_id).await.unwrap(); assert!(user_locators.contains(&appointment.locator())); assert_eq!(slots_before, available_slots + 1); // Slots should have been updated in the database too. - let mut loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); + let mut loaded_user = gatekeeper.dbm.load_user(user_id).await.unwrap(); assert_eq!(loaded_user.available_slots, available_slots); // Adding the exact same appointment should leave the slots count unchanged. // We don't really need to update the appointment in the DB since it's the very same appointment. let mut updated_slot_count = gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); - let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + let (_, user_locators) = gatekeeper.get_user_info(user_id).await.unwrap(); assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots); - loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); + loaded_user = gatekeeper.dbm.load_user(user_id).await.unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); // If we add an update to an existing appointment with a bigger data blob (modulo ENCRYPTED_BLOB_MAX_SIZE), additional slots should be taken @@ -576,59 +568,59 @@ mod tests { bigger_appointment.inner.encrypted_blob = get_random_bytes(ENCRYPTED_BLOB_MAX_SIZE + 1); updated_slot_count = gatekeeper .add_update_appointment(user_id, uuid, &bigger_appointment) + .await .unwrap(); // Simulate the watcher updating the appointment in the database. gatekeeper .dbm - .lock() - .unwrap() .update_appointment(uuid, &bigger_appointment) + .await .unwrap(); - let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + let (_, user_locators) = gatekeeper.get_user_info(user_id).await.unwrap(); assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots - 1); - loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); + loaded_user = gatekeeper.dbm.load_user(user_id).await.unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); // Adding back a smaller update (modulo ENCRYPTED_BLOB_MAX_SIZE) should reduce the count updated_slot_count = gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); // Simulate the watcher updating the appointment in the database. gatekeeper .dbm - .lock() - .unwrap() .update_appointment(uuid, &appointment) + .await .unwrap(); - let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + let (_, user_locators) = gatekeeper.get_user_info(user_id).await.unwrap(); assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots); - loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); + loaded_user = gatekeeper.dbm.load_user(user_id).await.unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); // Adding an appointment with a different uuid should not count as an update let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); updated_slot_count = gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); // Simulate the watcher adding the appointment in the database. gatekeeper .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); - let (_, user_locators) = gatekeeper.get_user_info(user_id).unwrap(); + let (_, user_locators) = gatekeeper.get_user_info(user_id).await.unwrap(); assert!(user_locators.contains(&appointment.locator())); assert_eq!(updated_slot_count, available_slots - 1); - loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); + loaded_user = gatekeeper.dbm.load_user(user_id).await.unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); // Finally, trying to add an appointment when the user has no enough slots should fail @@ -636,35 +628,37 @@ mod tests { gatekeeper .registered_users .lock() - .unwrap() + .await .get_mut(&user_id) .unwrap() .available_slots = 0; assert!(matches!( - gatekeeper.add_update_appointment(user_id, uuid, &appointment), + gatekeeper + .add_update_appointment(user_id, uuid, &appointment) + .await, Err(NotEnoughSlots) )); // The entry in the database should remain unchanged in this case - loaded_user = gatekeeper.dbm.lock().unwrap().load_user(user_id).unwrap(); + loaded_user = gatekeeper.dbm.load_user(user_id).await.unwrap(); assert_eq!(loaded_user.available_slots, updated_slot_count); } - #[test] - fn test_has_subscription_expired() { - let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)); + #[tokio::test] + async fn test_has_subscription_expired() { + let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)).await; // If the user is not registered, querying for a subscription expiry check should return an error let user_id = get_random_user_id(); assert!(matches!( - gatekeeper.has_subscription_expired(user_id), + gatekeeper.has_subscription_expired(user_id).await, Err(AuthenticationFailure { .. }) )); // If the user is registered and the subscription is active we should get (false, expiry) - gatekeeper.add_update_user(user_id).unwrap(); + gatekeeper.add_update_user(user_id).await.unwrap(); assert_eq!( - gatekeeper.has_subscription_expired(user_id), + gatekeeper.has_subscription_expired(user_id).await, Ok((false, DURATION + START_HEIGHT as u32)) ); @@ -673,41 +667,45 @@ mod tests { gatekeeper .registered_users .lock() - .unwrap() + .await .get_mut(&user_id) .unwrap() .subscription_expiry = expiry; assert_eq!( - gatekeeper.has_subscription_expired(user_id), + gatekeeper.has_subscription_expired(user_id).await, Ok((true, expiry)) ); } - #[test] - fn test_get_outdated_users() { + #[tokio::test] + async fn test_get_outdated_users() { let start_height = START_HEIGHT as u32 + EXPIRY_DELTA; - let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(start_height as usize)); + let gatekeeper = + init_gatekeeper(&Blockchain::default().with_height(start_height as usize)).await; // Initially, there are not outdated users, so querying any block height should return an empty map for i in 0..start_height { - assert_eq!(gatekeeper.get_outdated_users(i), vec![]); + assert_eq!(gatekeeper.get_outdated_users(i).await, vec![]); } // Adding a user whose subscription is outdated should return an entry let user_id = get_random_user_id(); - gatekeeper.add_update_user(user_id).unwrap(); + gatekeeper.add_update_user(user_id).await.unwrap(); // Check that data is not yet outdated - assert_eq!(gatekeeper.get_outdated_users(start_height), vec![]); + assert_eq!(gatekeeper.get_outdated_users(start_height).await, vec![]); // Add an outdated user and check again - gatekeeper.add_outdated_user(user_id, start_height); - assert_eq!(gatekeeper.get_outdated_users(start_height), vec![user_id]); + gatekeeper.add_outdated_user(user_id, start_height).await; + assert_eq!( + gatekeeper.get_outdated_users(start_height).await, + vec![user_id] + ); } - #[test] - fn test_delete_appointments_without_refund() { - let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)); + #[tokio::test] + async fn test_delete_appointments_without_refund() { + let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)).await; let n_users = 100; let n_apps = 10; let mut uuids_to_delete = Vec::new(); @@ -717,18 +715,18 @@ mod tests { for _ in 0..n_users { let user_id = get_random_user_id(); - gatekeeper.add_update_user(user_id).unwrap(); + gatekeeper.add_update_user(user_id).await.unwrap(); for i in 0..n_apps { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); // Add the appointment to the database. This is normally done by the Watcher. gatekeeper .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); if i % 2 == 0 { uuids_to_delete.push(uuid); @@ -739,47 +737,48 @@ mod tests { if i % 5 == 0 { gatekeeper .dbm - .lock() - .unwrap() .store_tracker( uuid, &get_random_tracker(user_id, ConfirmationStatus::ConfirmedIn(42)), ) + .await .unwrap(); trackers.push(uuid); } } - users_info.insert(user_id, gatekeeper.get_user_info(user_id).unwrap().0); + users_info.insert(user_id, gatekeeper.get_user_info(user_id).await.unwrap().0); } // Delete these appointments without refunding their owners. - gatekeeper.delete_appointments(uuids_to_delete.clone(), false); + gatekeeper + .delete_appointments(uuids_to_delete.clone(), false) + .await; for uuid in uuids_to_delete.clone() { - assert!(!gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!gatekeeper.dbm.appointment_exists(uuid).await); } for uuid in rest { - assert!(gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(gatekeeper.dbm.appointment_exists(uuid).await); } for uuid in trackers { if uuids_to_delete.contains(&uuid) { // The tracker should be deleted as well. - assert!(!gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + assert!(!gatekeeper.dbm.tracker_exists(uuid).await); } else { - assert!(gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + assert!(gatekeeper.dbm.tracker_exists(uuid).await); } } for (user_id, user_info_before_deletion) in users_info { // Since `refund` was false, the users' slots should not have changed after deleting appointments. - let (user_info_after_deletion, _) = gatekeeper.get_user_info(user_id).unwrap(); + let (user_info_after_deletion, _) = gatekeeper.get_user_info(user_id).await.unwrap(); assert_eq!(user_info_after_deletion, user_info_before_deletion); } } - #[test] - fn test_delete_appointments_with_refund() { - let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)); + #[tokio::test] + async fn test_delete_appointments_with_refund() { + let gatekeeper = init_gatekeeper(&Blockchain::default().with_height(START_HEIGHT)).await; let n_users = 100; let n_apps = 10; let mut uuids_to_delete = Vec::new(); @@ -789,20 +788,24 @@ mod tests { for _ in 0..n_users { let user_id = get_random_user_id(); - gatekeeper.add_update_user(user_id).unwrap(); - let mut user_remaining_slots = - gatekeeper.get_user_info(user_id).unwrap().0.available_slots; + gatekeeper.add_update_user(user_id).await.unwrap(); + let mut user_remaining_slots = gatekeeper + .get_user_info(user_id) + .await + .unwrap() + .0 + .available_slots; for i in 0..n_apps { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); // Add the appointment to the database. This is normally done by the Watcher. gatekeeper .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); if i % 2 == 0 { // We don't reduce the remaining slots for the appointments which are @@ -819,12 +822,11 @@ mod tests { if i % 5 == 0 { gatekeeper .dbm - .lock() - .unwrap() .store_tracker( uuid, &get_random_tracker(user_id, ConfirmationStatus::ConfirmedIn(42)), ) + .await .unwrap(); trackers.push(uuid); } @@ -833,40 +835,46 @@ mod tests { } // Delete these appointments and refund their owners their slots back. - gatekeeper.delete_appointments(uuids_to_delete.clone(), true); + gatekeeper + .delete_appointments(uuids_to_delete.clone(), true) + .await; for uuid in uuids_to_delete.clone() { - assert!(!gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!gatekeeper.dbm.appointment_exists(uuid).await); } for uuid in rest { - assert!(gatekeeper.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(gatekeeper.dbm.appointment_exists(uuid).await); } for uuid in trackers { if uuids_to_delete.contains(&uuid) { // The tracker should be deleted as well. - assert!(!gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + assert!(!gatekeeper.dbm.tracker_exists(uuid).await); } else { - assert!(gatekeeper.dbm.lock().unwrap().tracker_exists(uuid)); + assert!(gatekeeper.dbm.tracker_exists(uuid).await); } } for (user_id, correct_remaining_slots) in users_remaining_slots { - let remaining_slots_from_db = - gatekeeper.get_user_info(user_id).unwrap().0.available_slots; + let remaining_slots_from_db = gatekeeper + .get_user_info(user_id) + .await + .unwrap() + .0 + .available_slots; assert_eq!(remaining_slots_from_db, correct_remaining_slots); assert_eq!( - gatekeeper.registered_users.lock().unwrap()[&user_id].available_slots, + gatekeeper.registered_users.lock().await[&user_id].available_slots, correct_remaining_slots ); } } - #[test] - fn test_filtered_block_connected() { + #[tokio::test] + async fn test_block_connected() { // block_connected in the Gatekeeper is used to keep track of time in order to manage the users' subscription expiry. // Remove users that get outdated at the new block's height from registered_users and the database. let mut chain = Blockchain::default().with_height(START_HEIGHT); - let gatekeeper = init_gatekeeper(&chain); + let gatekeeper = init_gatekeeper(&chain).await; // Check that users are outdated when the expected height if hit let user1_id = get_random_user_id(); @@ -874,20 +882,24 @@ mod tests { let user3_id = get_random_user_id(); for user_id in &[user1_id, user2_id, user3_id] { - gatekeeper.add_outdated_user(*user_id, chain.tip().height + 1) + gatekeeper + .add_outdated_user(*user_id, chain.tip().height + 1) + .await } // Connect a new block. Outdated users are deleted - gatekeeper.block_connected(&chain.generate(None), chain.get_block_count()); + gatekeeper + .block_connected(&chain.generate(None), chain.get_block_count()) + .await; // Check that users have been removed from registered_users and the database for user_id in &[user1_id, user2_id, user3_id] { assert!(!gatekeeper .registered_users .lock() - .unwrap() + .await .contains_key(user_id)); - assert!(gatekeeper.dbm.lock().unwrap().load_user(*user_id).is_none()); + assert!(gatekeeper.dbm.load_user(*user_id).await.is_none()); } // Check that the last_known_block_header has been properly updated @@ -897,17 +909,19 @@ mod tests { ); } - #[test] - fn test_block_disconnected() { + #[tokio::test] + async fn test_block_disconnected() { // Block disconnected simply updates the last known block let chain = Blockchain::default().with_height(START_HEIGHT); - let gatekeeper = init_gatekeeper(&chain); + let gatekeeper = init_gatekeeper(&chain).await; let height = chain.get_block_count(); let last_known_block_header = chain.tip(); let prev_block_header = chain.at_height((height - 1) as usize); - gatekeeper.block_disconnected(&last_known_block_header.header, height); + gatekeeper + .block_disconnected(&last_known_block_header.header, height) + .await; assert_eq!( gatekeeper.last_known_block_height.load(Ordering::Relaxed), prev_block_header.height diff --git a/teos/src/lib.rs b/teos/src/lib.rs index 2e87bcf1..4999f542 100644 --- a/teos/src/lib.rs +++ b/teos/src/lib.rs @@ -8,6 +8,7 @@ pub mod protos { tonic::include_proto!("teos.v2"); } pub mod api; +pub mod async_listener; pub mod bitcoin_cli; pub mod carrier; pub mod chain_monitor; diff --git a/teos/src/main.rs b/teos/src/main.rs index bdd30e06..1e13056d 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -20,6 +20,7 @@ use lightning_block_sync::{BlockSource, BlockSourceError, SpvClient, UnboundedCa use teos::api::internal::InternalAPI; use teos::api::{http, tor::TorAPI}; +use teos::async_listener::AsyncBlockListener; use teos::bitcoin_cli::BitcoindClient; use teos::carrier::Carrier; use teos::chain_monitor::ChainMonitor; @@ -57,9 +58,9 @@ where Ok(last_n_blocks) } -fn create_new_tower_keypair(db: &DBM) -> (SecretKey, PublicKey) { +async fn create_new_tower_keypair(dbm: &DBM) -> (SecretKey, PublicKey) { let (sk, pk) = get_random_keypair(); - db.store_tower_key(&sk).unwrap(); + dbm.store_tower_key(&sk).await.unwrap(); (sk, pk) } @@ -122,22 +123,33 @@ async fn main() { conf.log_non_default_options(); } - let dbm = Arc::new(Mutex::new( - DBM::new(path_network.join("teos_db.sql3")).unwrap(), - )); + let dbm = if conf.database_url == "managed" { + DBM::new(&format!( + "sqlite://{}", + path_network + // rwc = Read + Write + Create (creates the database file if not found) + .join("teos_db.sql3?mode=rwc") + .to_str() + .expect("Path to the sqlite DB contains non-UTF-8 characters.") + )) + .await + .unwrap() + } else { + DBM::new(&conf.database_url).await.unwrap() + }; + let dbm = Arc::new(dbm); // Load tower secret key or create a fresh one if none is found. If overwrite key is set, create a new // key straightaway let (tower_sk, tower_pk) = { - let locked_db = dbm.lock().unwrap(); if conf.overwrite_key { log::info!("Overwriting tower keys"); - create_new_tower_keypair(&locked_db) - } else if let Some(sk) = locked_db.load_tower_key() { + create_new_tower_keypair(&dbm).await + } else if let Some(sk) = dbm.load_tower_key().await { (sk, PublicKey::from_secret_key(&Secp256k1::new(), &sk)) } else { log::info!("Tower keys not found. Creating a fresh set"); - create_new_tower_keypair(&locked_db) + create_new_tower_keypair(&dbm).await } }; log::info!("tower_id: {tower_pk}"); @@ -182,7 +194,7 @@ async fn main() { ); let mut derefed = bitcoin_cli.deref(); // Load last known block from DB if found. Poll it from Bitcoind otherwise. - let last_known_block = dbm.lock().unwrap().load_last_known_block(); + let last_known_block = dbm.load_last_known_block().await; let tip = if let Some(block_hash) = last_known_block { let mut last_known_header = derefed .get_header(&block_hash, None) @@ -259,13 +271,16 @@ async fn main() { }; // Build components - let gatekeeper = Arc::new(Gatekeeper::new( - tip.height, - conf.subscription_slots, - conf.subscription_duration, - conf.expiry_delta, - dbm.clone(), - )); + let gatekeeper = Arc::new( + Gatekeeper::new( + tip.height, + conf.subscription_slots, + conf.subscription_duration, + conf.expiry_delta, + dbm.clone(), + ) + .await, + ); let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); let (responder, watcher) = { @@ -297,7 +312,7 @@ async fn main() { (responder, watcher) }; - if watcher.is_fresh() & responder.is_fresh() & gatekeeper.is_fresh() { + if watcher.is_fresh().await & responder.is_fresh().await & gatekeeper.is_fresh().await { log::info!("Fresh bootstrap"); } else { log::info!("Bootstrapping from backed up data"); @@ -311,13 +326,17 @@ async fn main() { // The ordering here actually matters. Listeners are called by order, and we want the gatekeeper to be called // first so it updates the users' states and both the Watcher and the Responder operate only on registered users. - let listener = &(gatekeeper, &(watcher.clone(), responder)); + let listeners = (gatekeeper, (watcher.clone(), responder)); + + // This spawns a separate async actor in the background that will be fed new blocks from a sync block listener. + // In this way we can have our components listen to blocks in an async manner from the async actor. + let listener = AsyncBlockListener::wrap_listener(listeners, dbm); + let cache = &mut UnboundedCache::new(); - let spv_client = SpvClient::new(tip, poller, cache, listener); + let spv_client = SpvClient::new(tip, poller, cache, &listener); let mut chain_monitor = ChainMonitor::new( spv_client, tip, - dbm, conf.polling_delta, shutdown_signal_cm, bitcoind_reachable.clone(), diff --git a/teos/src/responder.rs b/teos/src/responder.rs index b7412cca..a6d52379 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -4,14 +4,14 @@ use std::collections::HashSet; use std::sync::{Arc, Mutex}; use bitcoin::{consensus, BlockHash}; -use bitcoin::{BlockHeader, Transaction, Txid}; -use lightning::chain; +use bitcoin::{Block, BlockHeader, Transaction, Txid}; use lightning_block_sync::poll::ValidatedBlock; use teos_common::constants; use teos_common::protos as common_msgs; use teos_common::UserId; +use crate::async_listener::AsyncListen; use crate::carrier::Carrier; use crate::dbm::DBM; use crate::extended_appointment::UUID; @@ -130,7 +130,7 @@ pub struct Responder { /// A [Gatekeeper] instance. Data regarding users is requested to it. gatekeeper: Arc, /// A [DBM] (database manager) instance. Used to persist tracker data into disk. - dbm: Arc>, + dbm: Arc, /// A list of all the reorged trackers that might need to be republished after reorg resolution. reorged_trackers: Mutex>, } @@ -142,7 +142,7 @@ impl Responder { last_known_block_height: u32, carrier: Carrier, gatekeeper: Arc, - dbm: Arc>, + dbm: Arc, ) -> Self { Responder { carrier: Mutex::new(carrier), @@ -154,13 +154,14 @@ impl Responder { } /// Returns whether the [Responder] has been created from scratch (fresh) or from backed-up data. - pub fn is_fresh(&self) -> bool { - self.get_trackers_count() == 0 + pub async fn is_fresh(&self) -> bool { + self.get_trackers_count().await == 0 } /// Gets the total number of trackers in the [Responder]. - pub(crate) fn get_trackers_count(&self) -> usize { - self.dbm.lock().unwrap().get_trackers_count() + pub(crate) async fn get_trackers_count(&self) -> usize { + // replace with a pull from the database. + self.dbm.get_trackers_count().await } /// Checks whether the [Responder] has gone through a reorg and some transactions should to be resent. @@ -172,27 +173,29 @@ impl Responder { /// /// Breaches can either be added to the [Responder] in the form of a [TransactionTracker] if the [penalty transaction](Breach::penalty_tx) /// is accepted by the `bitcoind` or rejected otherwise. - pub(crate) fn handle_breach( + pub(crate) async fn handle_breach( &self, uuid: UUID, breach: Breach, user_id: UserId, ) -> ConfirmationStatus { - let mut carrier = self.carrier.lock().unwrap(); - let tx_index = self.tx_index.lock().unwrap(); - - // Check whether the transaction is in mempool or part of our internal txindex. Send it to our node otherwise. - let status = if let Some(block_hash) = tx_index.get(&breach.penalty_tx.txid()) { - ConfirmationStatus::ConfirmedIn(tx_index.get_height(block_hash).unwrap() as u32) - } else if carrier.in_mempool(&breach.penalty_tx.txid()) { - // If it's in mempool we assume it was just included - ConfirmationStatus::InMempoolSince(carrier.block_height()) - } else { - carrier.send_transaction(&breach.penalty_tx) + let status = { + let mut carrier = self.carrier.lock().unwrap(); + let tx_index = self.tx_index.lock().unwrap(); + + // Check whether the transaction is in mempool or part of our internal txindex. Send it to our node otherwise. + if let Some(block_hash) = tx_index.get(&breach.penalty_tx.txid()) { + ConfirmationStatus::ConfirmedIn(tx_index.get_height(block_hash).unwrap() as u32) + } else if carrier.in_mempool(&breach.penalty_tx.txid()) { + // If it's in mempool we assume it was just included + ConfirmationStatus::InMempoolSince(carrier.block_height()) + } else { + carrier.send_transaction(&breach.penalty_tx) + } }; if status.accepted() { - self.add_tracker(uuid, breach, user_id, status); + self.add_tracker(uuid, breach, user_id, status).await; } status @@ -206,7 +209,7 @@ impl Responder { /// /// Some transaction may already be confirmed by the time the tower tries to send them to the network. If that's the case, /// the [Responder] will simply continue tracking the job until its completion. - pub(crate) fn add_tracker( + pub(crate) async fn add_tracker( &self, uuid: UUID, breach: Breach, @@ -215,9 +218,8 @@ impl Responder { ) { if self .dbm - .lock() - .unwrap() .store_tracker(uuid, &TransactionTracker::new(breach, user_id, status)) + .await .is_ok() { log::info!("New tracker added (uuid={uuid})"); @@ -229,8 +231,8 @@ impl Responder { } /// Checks whether a given tracker can be found in the [Responder]. - pub(crate) fn has_tracker(&self, uuid: UUID) -> bool { - self.dbm.lock().unwrap().tracker_exists(uuid) + pub(crate) async fn has_tracker(&self, uuid: UUID) -> bool { + self.dbm.tracker_exists(uuid).await } /// Checks the confirmation count for the [TransactionTracker]s. @@ -238,21 +240,25 @@ impl Responder { /// For unconfirmed transactions, it checks whether they have been confirmed or keep missing confirmations. /// For confirmed transactions, nothing is done until they are completed (confirmation count reaches [IRREVOCABLY_RESOLVED](constants::IRREVOCABLY_RESOLVED)) /// Returns the set of completed trackers or [None] if none were completed. - fn check_confirmations(&self, txids: HashSet, current_height: u32) -> Option> { + async fn check_confirmations( + &self, + txids: HashSet, + current_height: u32, + ) -> Option> { let mut completed_trackers = Vec::new(); - let mut reorged_trackers = self.reorged_trackers.lock().unwrap(); - let dbm = self.dbm.lock().unwrap(); - for (uuid, penalty_summary) in dbm.load_penalties_summaries() { + for (uuid, penalty_summary) in self.dbm.load_penalties_summaries().await { if txids.contains(&penalty_summary.penalty_txid) { // First confirmation was received - dbm.update_tracker_status(uuid, &ConfirmationStatus::ConfirmedIn(current_height)) + self.dbm + .update_tracker_status(uuid, &ConfirmationStatus::ConfirmedIn(current_height)) + .await .unwrap(); // Remove that uuid from reorged trackers if it was confirmed. - reorged_trackers.remove(&uuid); + self.reorged_trackers.lock().unwrap().remove(&uuid); // TODO: We won't need this check when we persist the correct tracker status // in the DB after migrations are supported. - } else if reorged_trackers.contains(&uuid) { + } else if self.reorged_trackers.lock().unwrap().contains(&uuid) { // Don't consider reorged trackers since they have wrong DB status. continue; } else if let ConfirmationStatus::ConfirmedIn(h) = penalty_summary.status { @@ -282,19 +288,22 @@ impl Responder { /// It tries to publish the dispute and penalty transactions of reorged trackers to the blockchain. /// /// Returns a vector of rejected trackers during rebroadcast if any were rejected, [None] otherwise. - fn handle_reorged_txs(&self, height: u32) -> Option> { + async fn handle_reorged_txs(&self, height: u32) -> Option> { // NOTE: We are draining the reorged trackers set, meaning that we won't try sending these disputes again. let reorged_trackers: Vec = self.reorged_trackers.lock().unwrap().drain().collect(); - let mut carrier = self.carrier.lock().unwrap(); - let dbm = self.dbm.lock().unwrap(); let mut rejected = Vec::new(); // Republish all the dispute transactions of the reorged trackers. for uuid in reorged_trackers { - let tracker = dbm.load_tracker(uuid).unwrap(); + let tracker = self.dbm.load_tracker(uuid).await.unwrap(); let dispute_txid = tracker.dispute_tx.txid(); // Try to publish the dispute transaction. - let should_publish_penalty = match carrier.send_transaction(&tracker.dispute_tx) { + let should_publish_penalty = match self + .carrier + .lock() + .unwrap() + .send_transaction(&tracker.dispute_tx) + { ConfirmationStatus::InMempoolSince(_) => { log::info!( "Reorged dispute tx (txid={}) is in the mempool now", @@ -325,15 +334,20 @@ impl Responder { if should_publish_penalty { // Try to rebroadcast the penalty tx. - if let ConfirmationStatus::Rejected(_) = - carrier.send_transaction(&tracker.penalty_tx) - { + let status = self + .carrier + .lock() + .unwrap() + .send_transaction(&tracker.penalty_tx); + if let ConfirmationStatus::Rejected(_) = status { rejected.push(uuid) } else { // The penalty might actually be confirmed (ConfirmationStatus::IrrevocablyResolved) since bitcoind // is fully synced with the stronger chain already, but we won't know which block was it confirmed in. // We should see the tracker appear in the blockchain in the next couple of connected blocks. - dbm.update_tracker_status(uuid, &ConfirmationStatus::InMempoolSince(height)) + self.dbm + .update_tracker_status(uuid, &ConfirmationStatus::InMempoolSince(height)) + .await .unwrap() } } else { @@ -350,9 +364,7 @@ impl Responder { /// fess and needs to be bumped, but there is not much we can do until anchors). /// /// Returns a vector of rejected trackers during rebroadcast if any were rejected, [None] otherwise. - fn rebroadcast_stale_txs(&self, height: u32) -> Option> { - let dbm = self.dbm.lock().unwrap(); - let mut carrier = self.carrier.lock().unwrap(); + async fn rebroadcast_stale_txs(&self, height: u32) -> Option> { let mut rejected = Vec::new(); // Retry sending trackers which have been in the mempool since more than `CONFIRMATIONS_BEFORE_RETRY` blocks. @@ -361,17 +373,23 @@ impl Responder { // NOTE: Ideally this will only pull UUIDs which have been in mempool since `CONFIRMATIONS_BEFORE_RETRY`, but // might also return ones which have been there for a longer period. This can only happen if the tower missed // a couple of block connections due to a force update. - for uuid in dbm + for uuid in self + .dbm .load_trackers_with_confirmation_status(stale_confirmation_status) + .await .unwrap() { - let tracker = dbm.load_tracker(uuid).unwrap(); + let tracker = self.dbm.load_tracker(uuid).await.unwrap(); log::warn!( "Penalty transaction has missed many confirmations: {}", tracker.penalty_tx.txid() ); // Rebroadcast the penalty transaction. - let status = carrier.send_transaction(&tracker.penalty_tx); + let status = self + .carrier + .lock() + .unwrap() + .send_transaction(&tracker.penalty_tx); if let ConfirmationStatus::Rejected(_) = status { rejected.push(uuid); } else { @@ -379,7 +397,7 @@ impl Responder { // Sending it will yield `ConfirmationStatus::IrrevocablyResolved` which would panic here. // We might want to replace `ConfirmationStatus::IrrevocablyResolved` variant with // `ConfirmationStatus::ConfirmedIn(height - IRREVOCABLY_RESOLVED) - dbm.update_tracker_status(uuid, &status).unwrap(); + self.dbm.update_tracker_status(uuid, &status).await.unwrap(); } } @@ -388,7 +406,8 @@ impl Responder { } /// Listen implementation by the [Responder]. Handles monitoring and reorgs. -impl chain::Listen for Responder { +#[tonic::async_trait] +impl AsyncListen for Responder { /// Handles the monitoring process by the [Responder]. /// /// Watching is performed in a per-block basis. A [TransactionTracker] is tracked until: @@ -399,24 +418,23 @@ impl chain::Listen for Responder { /// Every time a block is received the tracking conditions are checked against the monitored [TransactionTracker]s and /// data deletion is performed accordingly. Moreover, lack of confirmations is check for the tracked transactions and /// rebroadcasting is performed for those that have missed too many. - fn filtered_block_connected( - &self, - header: &BlockHeader, - txdata: &chain::transaction::TransactionData, - height: u32, - ) { - log::info!("New block received: {}", header.block_hash()); + async fn block_connected(&self, block: &Block, height: u32) { + log::info!("New block received: {}", block.header.block_hash()); self.carrier.lock().unwrap().update_height(height); - let txs = txdata + let txs = block + .txdata .iter() - .map(|(_, tx)| (tx.txid(), header.block_hash())) + .map(|tx| (tx.txid(), block.header.block_hash())) .collect(); - self.tx_index.lock().unwrap().update(*header, &txs); + self.tx_index.lock().unwrap().update(block.header, &txs); // Delete trackers completed at this height - if let Some(trackers) = self.check_confirmations(txs.keys().cloned().collect(), height) { - self.gatekeeper.delete_appointments(trackers, true); + if let Some(trackers) = self + .check_confirmations(txs.keys().cloned().collect(), height) + .await + { + self.gatekeeper.delete_appointments(trackers, true).await; } let mut trackers_to_delete = Vec::new(); @@ -424,19 +442,20 @@ impl chain::Listen for Responder { // We will need to update those trackers that have been reorged. if self.coming_from_reorg() { // Handle reorged transactions. This clears `self.reorged_trackers`. - if let Some(trackers) = self.handle_reorged_txs(height) { + if let Some(trackers) = self.handle_reorged_txs(height).await { trackers_to_delete.extend(trackers); } } // Rebroadcast those transactions that need to - if let Some(trackers) = self.rebroadcast_stale_txs(height) { + if let Some(trackers) = self.rebroadcast_stale_txs(height).await { trackers_to_delete.extend(trackers); } if !trackers_to_delete.is_empty() { self.gatekeeper - .delete_appointments(trackers_to_delete, false); + .delete_appointments(trackers_to_delete, false) + .await; } // Remove all receipts created in this block @@ -444,7 +463,7 @@ impl chain::Listen for Responder { } /// Handles reorgs in the [Responder]. - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + async fn block_disconnected(&self, header: &BlockHeader, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); // Update the carrier and our tx_index. self.carrier.lock().unwrap().update_height(height); @@ -456,20 +475,21 @@ impl chain::Listen for Responder { // TODO: Not only confirmed trackers need to be marked as reorged, but trackers that hasn't confirmed but their // dispute did confirm in the reorged block. We can pull dispute txids of non confirmed penalties and get their // confirmation block from our tx_index. - self.reorged_trackers.lock().unwrap().extend( - self.dbm - .lock() - .unwrap() - .load_trackers_with_confirmation_status(ConfirmationStatus::ConfirmedIn(height)) - .unwrap(), - ); + let reorged_trackers = self + .dbm + .load_trackers_with_confirmation_status(ConfirmationStatus::ConfirmedIn(height)) + .await + .unwrap(); + self.reorged_trackers + .lock() + .unwrap() + .extend(reorged_trackers); } } #[cfg(test)] mod tests { use super::*; - use lightning::chain::Listen; use teos_common::appointment::Locator; use std::collections::HashMap; @@ -501,49 +521,45 @@ mod tests { impl PartialEq for Responder { fn eq(&self, other: &Self) -> bool { // Same in-memory data. - *self.reorged_trackers.lock().unwrap() == *other.reorged_trackers.lock().unwrap() && - *self.tx_index.lock().unwrap() == *other.tx_index.lock().unwrap() && - // && Same DB data. - self.get_trackers() == other.get_trackers() + *self.reorged_trackers.lock().unwrap() == *other.reorged_trackers.lock().unwrap() + && *self.tx_index.lock().unwrap() == *other.tx_index.lock().unwrap() } } impl Eq for Responder {} impl Responder { - pub(crate) fn get_trackers(&self) -> HashMap { - self.dbm.lock().unwrap().load_trackers(None) - } - pub(crate) fn get_carrier(&self) -> &Mutex { &self.carrier } - pub(crate) fn add_random_tracker(&self, status: ConfirmationStatus) -> TransactionTracker { + pub(crate) async fn add_random_tracker( + &self, + status: ConfirmationStatus, + ) -> TransactionTracker { let user_id = get_random_user_id(); let tracker = get_random_tracker(user_id, status); - self.add_dummy_tracker(&tracker); + self.add_dummy_tracker(&tracker).await; tracker } - pub(crate) fn add_dummy_tracker(&self, tracker: &TransactionTracker) { + pub(crate) async fn add_dummy_tracker(&self, tracker: &TransactionTracker) { let (_, appointment) = generate_dummy_appointment_with_user( tracker.user_id, Some(&tracker.dispute_tx.txid()), ); - store_appointment_and_its_user(&self.dbm.lock().unwrap(), &appointment); + store_appointment_and_its_user(&self.dbm, &appointment).await; self.dbm - .lock() - .unwrap() .store_tracker(appointment.uuid(), tracker) + .await .unwrap(); } - fn store_dummy_appointment_to_db(&self) -> (UserId, UUID) { + async fn store_dummy_appointment_to_db(&self) -> (UserId, UUID) { let appointment = generate_dummy_appointment(None); let (uuid, user_id) = (appointment.uuid(), appointment.user_id); // Store the appointment and the user to the DB. - store_appointment_and_its_user(&self.dbm.lock().unwrap(), &appointment); + store_appointment_and_its_user(&self.dbm, &appointment).await; (user_id, uuid) } } @@ -551,7 +567,7 @@ mod tests { async fn create_responder( chain: &mut Blockchain, gatekeeper: Arc, - dbm: Arc>, + dbm: Arc, query: MockedServerQuery, ) -> (Responder, BitcoindStopper) { let height = if chain.tip().height < IRREVOCABLY_RESOLVED { @@ -572,7 +588,7 @@ mod tests { async fn init_responder_with_chain_and_dbm( mocked_query: MockedServerQuery, chain: &mut Blockchain, - dbm: Arc>, + dbm: Arc, ) -> (Responder, BitcoindStopper) { let gk = Gatekeeper::new( chain.get_block_count(), @@ -580,12 +596,13 @@ mod tests { DURATION, EXPIRY_DELTA, dbm.clone(), - ); + ) + .await; create_responder(chain, Arc::new(gk), dbm, mocked_query).await } async fn init_responder(mocked_query: MockedServerQuery) -> (Responder, BitcoindStopper) { - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); + let dbm = Arc::new(DBM::test_db().await); let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); init_responder_with_chain_and_dbm(mocked_query, &mut chain, dbm).await } @@ -628,29 +645,31 @@ mod tests { async fn test_responder_new() { // A fresh responder has no associated data let mut chain = Blockchain::default().with_height(START_HEIGHT); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); + let dbm = Arc::new(DBM::test_db().await); let (responder, _s) = init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm.clone()) .await; - assert!(responder.is_fresh()); + assert!(responder.is_fresh().await); // If we add some trackers to the system and create a new Responder reusing the same db // (as if simulating a bootstrap from existing data), the data should be properly loaded. for i in 0..10 { - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; let breach = get_random_breach(); let s = if i % 2 == 0 { ConfirmationStatus::InMempoolSince(i) } else { ConfirmationStatus::ConfirmedIn(i) }; - responder.add_tracker(uuid, breach.clone(), user_id, s); + responder + .add_tracker(uuid, breach.clone(), user_id, s) + .await; } // Create a new Responder reusing the same DB and check that the data is loaded let (another_r, _) = init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm).await; - assert!(!responder.is_fresh()); + assert!(!responder.is_fresh().await); assert_eq!(responder, another_r); } @@ -659,14 +678,14 @@ mod tests { let start_height = START_HEIGHT as u32; let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; let breach = get_random_breach(); assert_eq!( - responder.handle_breach(uuid, breach, user_id), + responder.handle_breach(uuid, breach, user_id).await, ConfirmationStatus::InMempoolSince(start_height) ); - let tracker = responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(); + let tracker = responder.dbm.load_tracker(uuid).await.unwrap(); assert_eq!( tracker.status, ConfirmationStatus::InMempoolSince(start_height) @@ -676,14 +695,11 @@ mod tests { // passed twice, the receipt corresponding to the first breach will be handed back. let another_breach = get_random_breach(); assert_eq!( - responder.handle_breach(uuid, another_breach, user_id), + responder.handle_breach(uuid, another_breach, user_id).await, ConfirmationStatus::InMempoolSince(start_height) ); // Getting the tracker should return the old one. - assert_eq!( - tracker, - responder.dbm.lock().unwrap().load_tracker(uuid).unwrap() - ); + assert_eq!(tracker, responder.dbm.load_tracker(uuid).await.unwrap()); } #[tokio::test] @@ -691,14 +707,14 @@ mod tests { let start_height = START_HEIGHT as u32; let (responder, _s) = init_responder(MockedServerQuery::InMempoool).await; - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; let breach = get_random_breach(); assert_eq!( - responder.handle_breach(uuid, breach, user_id), + responder.handle_breach(uuid, breach, user_id).await, ConfirmationStatus::InMempoolSince(start_height) ); - let tracker = responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(); + let tracker = responder.dbm.load_tracker(uuid).await.unwrap(); assert_eq!( tracker.status, ConfirmationStatus::InMempoolSince(start_height) @@ -709,7 +725,7 @@ mod tests { async fn test_handle_breach_accepted_in_txindex() { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; let breach = get_random_breach(); let penalty_txid = breach.penalty_tx.txid(); @@ -730,10 +746,10 @@ mod tests { .unwrap() as u32; assert_eq!( - responder.handle_breach(uuid, breach, user_id), + responder.handle_breach(uuid, breach, user_id).await, ConfirmationStatus::ConfirmedIn(target_height) ); - let tracker = responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(); + let tracker = responder.dbm.load_tracker(uuid).await.unwrap(); assert_eq!( tracker.status, ConfirmationStatus::ConfirmedIn(target_height) @@ -752,10 +768,10 @@ mod tests { let breach = get_random_breach(); assert_eq!( - responder.handle_breach(uuid, breach, user_id), + responder.handle_breach(uuid, breach, user_id).await, ConfirmationStatus::Rejected(rpc_errors::RPC_VERIFY_ERROR) ); - assert!(!responder.has_tracker(uuid)); + assert!(!responder.has_tracker(uuid).await); } #[tokio::test] @@ -763,18 +779,20 @@ mod tests { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; let start_height = START_HEIGHT as u32; - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; let mut breach = get_random_breach(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(start_height), - ); + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::InMempoolSince(start_height), + ) + .await; // Check that the data has been added to the responder. assert_eq!( - responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), + responder.dbm.load_tracker(uuid).await.unwrap(), TransactionTracker::new( breach, user_id, @@ -784,17 +802,19 @@ mod tests { // Adding a confirmed tracker should result in the same but with the height being set. - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; breach = get_random_breach(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn(start_height - 1), - ); + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::ConfirmedIn(start_height - 1), + ) + .await; assert_eq!( - responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), + responder.dbm.load_tracker(uuid).await.unwrap(), TransactionTracker::new( breach.clone(), user_id, @@ -803,16 +823,18 @@ mod tests { ); // Adding another breach with the same penalty transaction (but different uuid) - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn(start_height), - ); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::ConfirmedIn(start_height), + ) + .await; assert_eq!( - responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), + responder.dbm.load_tracker(uuid).await.unwrap(), TransactionTracker::new( breach, user_id, @@ -829,20 +851,25 @@ mod tests { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Add a new tracker - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; let breach = get_random_breach(); - responder.add_tracker( - uuid, - breach, - user_id, - ConfirmationStatus::ConfirmedIn(START_HEIGHT as u32), - ); + responder + .add_tracker( + uuid, + breach, + user_id, + ConfirmationStatus::ConfirmedIn(START_HEIGHT as u32), + ) + .await; - assert!(responder.has_tracker(uuid)); + assert!(responder.has_tracker(uuid).await); // Delete the tracker and check again. - responder.gatekeeper.delete_appointments(vec![uuid], false); - assert!(!responder.has_tracker(uuid)); + responder + .gatekeeper + .delete_appointments(vec![uuid], false) + .await; + assert!(!responder.has_tracker(uuid).await); } #[tokio::test] @@ -852,21 +879,23 @@ mod tests { let (responder, _s) = init_responder(MockedServerQuery::Regular).await; // Store the user and the appointment in the database so we can add the tracker later on (due to FK restrictions) - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; // Data should not be there before adding it - assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); + assert!(responder.dbm.load_tracker(uuid).await.is_none()); // Data should be there now let breach = get_random_breach(); - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(start_height), - ); + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::InMempoolSince(start_height), + ) + .await; assert_eq!( - responder.dbm.lock().unwrap().load_tracker(uuid).unwrap(), + responder.dbm.load_tracker(uuid).await.unwrap(), TransactionTracker::new( breach, user_id, @@ -875,8 +904,11 @@ mod tests { ); // After deleting the data it should be gone - responder.gatekeeper.delete_appointments(vec![uuid], false); - assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); + responder + .gatekeeper + .delete_appointments(vec![uuid], false) + .await; + assert!(responder.dbm.load_tracker(uuid).await.is_none()); } #[tokio::test] @@ -892,47 +924,55 @@ mod tests { let mut txids = HashSet::new(); for i in 0..40 { - let (user_id, uuid) = responder.store_dummy_appointment_to_db(); + let (user_id, uuid) = responder.store_dummy_appointment_to_db().await; let breach = get_random_breach(); match i % 4 { 0 => { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(21), - ); + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::InMempoolSince(21), + ) + .await; in_mempool.insert(uuid); } 1 => { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::InMempoolSince(i), - ); + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::InMempoolSince(i), + ) + .await; just_confirmed.insert(uuid); txids.insert(breach.penalty_tx.txid()); } 2 => { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn(42), - ); + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::ConfirmedIn(42), + ) + .await; confirmed.insert(uuid); } _ => { - responder.add_tracker( - uuid, - breach.clone(), - user_id, - ConfirmationStatus::ConfirmedIn( - target_height - constants::IRREVOCABLY_RESOLVED, - ), - ); + responder + .add_tracker( + uuid, + breach.clone(), + user_id, + ConfirmationStatus::ConfirmedIn( + target_height - constants::IRREVOCABLY_RESOLVED, + ), + ) + .await; completed.insert(uuid); } } @@ -941,19 +981,18 @@ mod tests { // The trackers that were completed should be returned assert_eq!( completed, - HashSet::from_iter(responder.check_confirmations(txids, target_height).unwrap()) + HashSet::from_iter( + responder + .check_confirmations(txids, target_height) + .await + .unwrap() + ) ); // The ones in mempool should still be there (at the same height) for uuid in in_mempool { assert_eq!( - responder - .dbm - .lock() - .unwrap() - .load_tracker(uuid) - .unwrap() - .status, + responder.dbm.load_tracker(uuid).await.unwrap().status, ConfirmationStatus::InMempoolSince(21) ); } @@ -961,13 +1000,7 @@ mod tests { // The ones that just got confirmed should have been flagged so (at this height) for uuid in just_confirmed { assert_eq!( - responder - .dbm - .lock() - .unwrap() - .load_tracker(uuid) - .unwrap() - .status, + responder.dbm.load_tracker(uuid).await.unwrap().status, ConfirmationStatus::ConfirmedIn(target_height) ); } @@ -975,13 +1008,7 @@ mod tests { // The ones that were already confirmed but have not reached the end should remain the same for uuid in confirmed { assert_eq!( - responder - .dbm - .lock() - .unwrap() - .load_tracker(uuid) - .unwrap() - .status, + responder.dbm.load_tracker(uuid).await.unwrap().status, ConfirmationStatus::ConfirmedIn(42) ); } @@ -995,26 +1022,21 @@ mod tests { for _ in 0..10 { let uuid = responder .add_random_tracker(ConfirmationStatus::ConfirmedIn(42)) + .await .uuid(); responder.reorged_trackers.lock().unwrap().insert(uuid); trackers.push(uuid); } let height = 100; - assert!(responder.handle_reorged_txs(height).is_none()); + assert!(responder.handle_reorged_txs(height).await.is_none()); // The reorged trackers buffer should be empty after this. assert!(responder.reorged_trackers.lock().unwrap().is_empty()); // And all the reorged trackers should have in mempool since `height` status. for uuid in trackers { assert_eq!( - responder - .dbm - .lock() - .unwrap() - .load_tracker(uuid) - .unwrap() - .status, + responder.dbm.load_tracker(uuid).await.unwrap().status, ConfirmationStatus::InMempoolSince(height) ); } @@ -1032,13 +1054,14 @@ mod tests { for _ in 0..n_trackers { let uuid = responder .add_random_tracker(ConfirmationStatus::ConfirmedIn(42)) + .await .uuid(); responder.reorged_trackers.lock().unwrap().insert(uuid); trackers.insert(uuid); } let height = 100; - let rejected = HashSet::from_iter(responder.handle_reorged_txs(height).unwrap()); + let rejected = HashSet::from_iter(responder.handle_reorged_txs(height).await.unwrap()); // All the trackers should be returned as rejected. assert_eq!(trackers, rejected); // The reorged trackers buffer should be empty after this. @@ -1047,13 +1070,7 @@ mod tests { // And all the reorged trackers statuses should be untouched. for uuid in trackers { assert_eq!( - responder - .dbm - .lock() - .unwrap() - .load_tracker(uuid) - .unwrap() - .status, + responder.dbm.load_tracker(uuid).await.unwrap().status, ConfirmationStatus::ConfirmedIn(42) ); } @@ -1072,21 +1089,15 @@ mod tests { ConfirmationStatus::InMempoolSince(i) }; - let uuid = responder.add_random_tracker(status).uuid(); + let uuid = responder.add_random_tracker(status).await.uuid(); statues.insert(uuid, status); } // There should be no rejected tx. - assert!(responder.rebroadcast_stale_txs(height).is_none()); + assert!(responder.rebroadcast_stale_txs(height).await.is_none()); for (uuid, former_status) in statues { - let status = responder - .dbm - .lock() - .unwrap() - .load_tracker(uuid) - .unwrap() - .status; + let status = responder.dbm.load_tracker(uuid).await.unwrap().status; if let ConfirmationStatus::InMempoolSince(h) = former_status { if height - h >= CONFIRMATIONS_BEFORE_RETRY as u32 { // Transactions which stayed for more than `CONFIRMATIONS_BEFORE_RETRY` should have been rebroadcasted. @@ -1118,13 +1129,13 @@ mod tests { ConfirmationStatus::InMempoolSince(i) }; - let uuid = responder.add_random_tracker(status).uuid(); + let uuid = responder.add_random_tracker(status).await.uuid(); statues.insert(uuid, status); } // `rebroadcast_stale_txs` will broadcast txs which has been in mempool since `CONFIRMATIONS_BEFORE_RETRY` or more // blocks. Since our backend rejects all the txs, all these broadcasted txs should be returned from this method (rejected). - let rejected = HashSet::from_iter(responder.rebroadcast_stale_txs(height).unwrap()); + let rejected = HashSet::from_iter(responder.rebroadcast_stale_txs(height).await.unwrap()); let should_reject: HashSet<_> = statues .iter() .filter_map(|(&uuid, &status)| { @@ -1138,27 +1149,24 @@ mod tests { assert_eq!(should_reject, rejected); for (uuid, former_status) in statues { - let status = responder - .dbm - .lock() - .unwrap() - .load_tracker(uuid) - .unwrap() - .status; + let status = responder.dbm.load_tracker(uuid).await.unwrap().status; // All tracker statues shouldn't change since the submitted ones were all rejected. assert_eq!(status, former_status); } } #[tokio::test] - async fn test_filtered_block_connected() { - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); + async fn test_block_connected() { let start_height = START_HEIGHT * 2; let mut chain = Blockchain::default().with_height(start_height); - let (responder, _s) = - init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm).await; + let (responder, _s) = init_responder_with_chain_and_dbm( + MockedServerQuery::Regular, + &mut chain, + Arc::new(DBM::test_db().await), + ) + .await; - // filtered_block_connected is used to keep track of the confirmation received (or missed) by the trackers the Responder + // block_connected is used to keep track of the confirmation received (or missed) by the trackers the Responder // is keeping track of. // // If there are any trackers, the Responder will: @@ -1175,7 +1183,7 @@ mod tests { let mut users = Vec::new(); for _ in 0..21 { let user_id = get_random_user_id(); - responder.gatekeeper.add_update_user(user_id).unwrap(); + responder.gatekeeper.add_update_user(user_id).await.unwrap(); users.push(user_id); } @@ -1191,12 +1199,12 @@ mod tests { responder .gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); responder .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); // Trackers complete in the next block. @@ -1204,7 +1212,9 @@ mod tests { let status = ConfirmationStatus::ConfirmedIn( target_block_height - constants::IRREVOCABLY_RESOLVED, ); - responder.add_tracker(uuid, breach.clone(), user_id, status); + responder + .add_tracker(uuid, breach.clone(), user_id, status) + .await; completed_trackers.push(TransactionTracker::new(breach, user_id, status)); } @@ -1218,24 +1228,27 @@ mod tests { responder .gatekeeper .add_update_appointment(user_id, uuid, &appointment) + .await .unwrap(); responder .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); let breach = Breach::new(dispute_tx, get_random_tx()); let status = ConfirmationStatus::InMempoolSince(target_block_height - 1); - responder.add_tracker(uuid, breach.clone(), user_id, status); + responder + .add_tracker(uuid, breach.clone(), user_id, status) + .await; outdated_trackers.push(TransactionTracker::new(breach, user_id, status)); } // Outdate this user so their trackers are deleted responder .gatekeeper - .add_outdated_user(user_id, target_block_height); + .add_outdated_user(user_id, target_block_height) + .await; } // CONFIRMATIONS SETUP @@ -1243,6 +1256,7 @@ mod tests { responder .gatekeeper .add_update_user(standalone_user_id) + .await .unwrap(); let mut missed_confirmation_trackers = Vec::new(); @@ -1254,18 +1268,20 @@ mod tests { responder .gatekeeper .add_update_appointment(standalone_user_id, uuid, &appointment) + .await .unwrap(); responder .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); let breach = Breach::new(dispute_tx, get_random_tx()); let status = ConfirmationStatus::InMempoolSince(target_block_height - 1); - responder.add_tracker(uuid, breach.clone(), standalone_user_id, status); + responder + .add_tracker(uuid, breach.clone(), standalone_user_id, status) + .await; if i % 2 == 0 { just_confirmed_trackers.push(TransactionTracker::new( breach, @@ -1290,19 +1306,21 @@ mod tests { responder .gatekeeper .add_update_appointment(standalone_user_id, uuid, &appointment) + .await .unwrap(); responder .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); let breach = Breach::new(dispute_tx, get_random_tx()); let status = ConfirmationStatus::InMempoolSince( target_block_height - CONFIRMATIONS_BEFORE_RETRY as u32, ); - responder.add_tracker(uuid, breach.clone(), standalone_user_id, status); + responder + .add_tracker(uuid, breach.clone(), standalone_user_id, status) + .await; trackers_to_rebroadcast.push(TransactionTracker::new( breach, standalone_user_id, @@ -1328,8 +1346,8 @@ mod tests { )); let height = chain.get_block_count(); // We connect the gatekeeper first so it deletes the outdated users. - responder.gatekeeper.block_connected(&block, height); - responder.block_connected(&block, height); + responder.gatekeeper.block_connected(&block, height).await; + responder.block_connected(&block, height).await; // CARRIER CHECKS assert!(responder @@ -1348,28 +1366,23 @@ mod tests { // COMPLETED TRACKERS CHECKS // Data should have been removed for tracker in completed_trackers { - assert!(responder - .dbm - .lock() - .unwrap() - .load_tracker(tracker.uuid()) - .is_none()); - let (_, user_locators) = responder.gatekeeper.get_user_info(tracker.user_id).unwrap(); + assert!(responder.dbm.load_tracker(tracker.uuid()).await.is_none()); + let (_, user_locators) = responder + .gatekeeper + .get_user_info(tracker.user_id) + .await + .unwrap(); assert!(!user_locators.contains(&tracker.locator())); } // OUTDATED TRACKERS CHECKS // Data should have been removed (tracker not found nor the user) for tracker in outdated_trackers { - assert!(responder - .dbm - .lock() - .unwrap() - .load_tracker(tracker.uuid()) - .is_none()); + assert!(responder.dbm.load_tracker(tracker.uuid()).await.is_none()); assert!(responder .gatekeeper .get_user_info(tracker.user_id) + .await .is_none()); } @@ -1379,9 +1392,8 @@ mod tests { assert_eq!( responder .dbm - .lock() - .unwrap() .load_tracker(tracker.uuid()) + .await .unwrap() .status, ConfirmationStatus::ConfirmedIn(target_block_height) @@ -1391,9 +1403,8 @@ mod tests { assert_eq!( responder .dbm - .lock() - .unwrap() .load_tracker(tracker.uuid()) + .await .unwrap() .status, ConfirmationStatus::InMempoolSince(target_block_height - 1) @@ -1405,9 +1416,8 @@ mod tests { assert_eq!( responder .dbm - .lock() - .unwrap() .load_tracker(tracker.uuid()) + .await .unwrap() .status, ConfirmationStatus::InMempoolSince(target_block_height), @@ -1417,14 +1427,17 @@ mod tests { #[tokio::test] async fn test_block_disconnected() { - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); let mut chain = Blockchain::default().with_height_and_txs(START_HEIGHT, 10); - let (responder, _s) = - init_responder_with_chain_and_dbm(MockedServerQuery::Regular, &mut chain, dbm).await; + let (responder, _s) = init_responder_with_chain_and_dbm( + MockedServerQuery::Regular, + &mut chain, + Arc::new(DBM::test_db().await), + ) + .await; // Add user to the database let user_id = get_random_user_id(); - responder.gatekeeper.add_update_user(user_id).unwrap(); + responder.gatekeeper.add_update_user(user_id).await.unwrap(); let mut reorged = Vec::new(); let block_range = START_HEIGHT - 10..START_HEIGHT; @@ -1436,25 +1449,28 @@ mod tests { generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); responder .dbm - .lock() - .unwrap() .store_appointment(uuid, &appointment) + .await .unwrap(); let breach = Breach::new(dispute_tx, get_random_tx()); - responder.add_tracker( - uuid, - breach, - user_id, - ConfirmationStatus::ConfirmedIn(i as u32), - ); + responder + .add_tracker( + uuid, + breach, + user_id, + ConfirmationStatus::ConfirmedIn(i as u32), + ) + .await; reorged.push(uuid); } // Check that trackers are flagged as reorged if the height they were included at gets disconnected for (i, uuid) in block_range.clone().zip(reorged.iter()).rev() { // The header doesn't really matter, just the height - responder.block_disconnected(&chain.tip().header, i as u32); + responder + .block_disconnected(&chain.tip().header, i as u32) + .await; // Check that the proper tracker gets reorged at the proper height assert!(responder.reorged_trackers.lock().unwrap().contains(uuid)); // Check that the carrier block_height has been updated @@ -1467,7 +1483,9 @@ mod tests { } // But should be clear after the first block connection - responder.block_connected(&chain.generate(None), block_range.start as u32); + responder + .block_connected(&chain.generate(None), block_range.start as u32) + .await; assert!(responder.reorged_trackers.lock().unwrap().is_empty()); } } diff --git a/teos/src/test_utils.rs b/teos/src/test_utils.rs index 4dba0a93..c2bb86cd 100644 --- a/teos/src/test_utils.rs +++ b/teos/src/test_utils.rs @@ -341,14 +341,16 @@ pub(crate) fn get_random_tracker( TransactionTracker::new(breach, user_id, status) } -pub(crate) fn store_appointment_and_its_user(dbm: &DBM, appointment: &ExtendedAppointment) { +pub(crate) async fn store_appointment_and_its_user(dbm: &DBM, appointment: &ExtendedAppointment) { dbm.store_user( appointment.user_id, &UserInfo::new(AVAILABLE_SLOTS, SUBSCRIPTION_START, SUBSCRIPTION_EXPIRY), ) + .await // It's ok if the user is already stored. .ok(); dbm.store_appointment(appointment.uuid(), appointment) + .await .unwrap(); } @@ -392,7 +394,7 @@ pub(crate) fn create_carrier(query: MockedServerQuery, height: u32) -> (Carrier, pub(crate) async fn create_responder( chain: &mut Blockchain, gatekeeper: Arc, - dbm: Arc>, + dbm: Arc, server_url: &str, ) -> Responder { let height = chain.tip().height; @@ -413,7 +415,7 @@ pub(crate) async fn create_watcher( responder: Arc, gatekeeper: Arc, bitcoind_mock: BitcoindMock, - dbm: Arc>, + dbm: Arc, ) -> (Watcher, BitcoindStopper) { let last_n_blocks = get_last_n_blocks(chain, 6).await; @@ -471,14 +473,17 @@ pub(crate) async fn create_api_with_config( let bitcoind_mock = BitcoindMock::new(MockOptions::default()); let mut chain = Blockchain::default().with_height(START_HEIGHT); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); - let gk = Arc::new(Gatekeeper::new( - chain.get_block_count(), - api_config.slots, - api_config.duration, - EXPIRY_DELTA, - dbm.clone(), - )); + let dbm = Arc::new(DBM::test_db().await); + let gk = Arc::new( + Gatekeeper::new( + chain.get_block_count(), + api_config.slots, + api_config.duration, + EXPIRY_DELTA, + dbm.clone(), + ) + .await, + ); let responder = create_responder(&mut chain, gk.clone(), dbm.clone(), bitcoind_mock.url()).await; let (watcher, stopper) = create_watcher( diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index 90606fcf..fd6ca6e4 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -5,8 +5,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use bitcoin::secp256k1::SecretKey; -use bitcoin::{BlockHeader, Transaction}; -use lightning::chain; +use bitcoin::{Block, BlockHeader, Transaction}; use lightning_block_sync::poll::ValidatedBlock; use teos_common::appointment::{Appointment, Locator}; @@ -14,6 +13,7 @@ use teos_common::cryptography; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; +use crate::async_listener::AsyncListen; use crate::dbm::DBM; use crate::extended_appointment::{ExtendedAppointment, UUID}; use crate::gatekeeper::{Gatekeeper, MaxSlotsReached, UserInfo}; @@ -109,7 +109,7 @@ pub struct Watcher { /// The tower identifier. pub tower_id: TowerId, /// A [DBM] (database manager) instance. Used to persist appointment data into disk. - dbm: Arc>, + dbm: Arc, } impl Watcher { @@ -121,7 +121,7 @@ impl Watcher { last_known_block_height: u32, signing_key: SecretKey, tower_id: TowerId, - dbm: Arc>, + dbm: Arc, ) -> Self { Watcher { locator_cache: Mutex::new(TxIndex::new(last_n_blocks, last_known_block_height)), @@ -135,14 +135,17 @@ impl Watcher { } /// Returns whether the [Watcher] has been created from scratch (fresh) or from backed-up data. - pub fn is_fresh(&self) -> bool { - self.get_appointments_count() == 0 + pub async fn is_fresh(&self) -> bool { + self.get_appointments_count().await == 0 } /// Registers a new user within the [Watcher]. This request is passed to the [Gatekeeper], who is in /// charge of managing users. - pub(crate) fn register(&self, user_id: UserId) -> Result { - let mut receipt = self.gatekeeper.add_update_user(user_id)?; + pub(crate) async fn register( + &self, + user_id: UserId, + ) -> Result { + let mut receipt = self.gatekeeper.add_update_user(user_id).await?; receipt.sign(&self.signing_key); Ok(receipt) @@ -159,7 +162,7 @@ impl Watcher { /// If an appointment is accepted, an [ExtendedAppointment] (constructed from the [Appointment]) will be persisted on disk. /// In case the locator for the given appointment can be found in the cache (meaning the appointment has been /// triggered recently) the data will be passed to the [Responder] straightaway (modulo it being valid). - pub(crate) fn add_appointment( + pub(crate) async fn add_appointment( &self, appointment: Appointment, user_signature: String, @@ -167,10 +170,14 @@ impl Watcher { let user_id = self .gatekeeper .authenticate_user(&appointment.to_vec(), &user_signature) + .await .map_err(|_| AddAppointmentFailure::AuthenticationFailure)?; - let (has_subscription_expired, expiry) = - self.gatekeeper.has_subscription_expired(user_id).unwrap(); + let (has_subscription_expired, expiry) = self + .gatekeeper + .has_subscription_expired(user_id) + .await + .unwrap(); if has_subscription_expired { return Err(AddAppointmentFailure::SubscriptionExpired(expiry)); @@ -185,7 +192,7 @@ impl Watcher { let uuid = extended_appointment.uuid(); - if self.responder.has_tracker(uuid) { + if self.responder.has_tracker(uuid).await { log::info!("Tracker for {uuid} already found in Responder"); return Err(AddAppointmentFailure::AlreadyTriggered); } @@ -195,25 +202,28 @@ impl Watcher { let available_slots = self .gatekeeper .add_update_appointment(user_id, uuid, &extended_appointment) + .await .map_err(|_| AddAppointmentFailure::NotEnoughSlots)?; // FIXME: There's an edge case here if store_triggered_appointment is called and bitcoind is unreachable. // This will hang, the request will timeout but be accepted. However, the user will not be handed the receipt. // This could be fixed adding a thread to take care of storing while the main thread returns the receipt. // Not fixing this atm since working with threads that call self.method is surprisingly non-trivial. - match self + let dispute_tx = self .locator_cache .lock() .unwrap() .get(&extended_appointment.locator()) - { + .cloned(); + match dispute_tx { // Appointments that were triggered in blocks held in the cache Some(dispute_tx) => { - self.store_triggered_appointment(uuid, &extended_appointment, user_id, dispute_tx); + self.store_triggered_appointment(uuid, &extended_appointment, user_id, &dispute_tx) + .await; } // Regular appointments that have not been triggered (or, at least, not recently) None => { - self.store_appointment(uuid, &extended_appointment); + self.store_appointment(uuid, &extended_appointment).await; } }; @@ -227,21 +237,23 @@ impl Watcher { } /// Stores an appointment in the database (or updates it if it already exists). - fn store_appointment( + async fn store_appointment( &self, uuid: UUID, appointment: &ExtendedAppointment, ) -> StoredAppointment { - let dbm = self.dbm.lock().unwrap(); - if dbm.appointment_exists(uuid) { + if self.dbm.appointment_exists(uuid).await { log::debug!( "User {} is updating appointment {uuid}", appointment.user_id ); - dbm.update_appointment(uuid, appointment).unwrap(); + self.dbm + .update_appointment(uuid, appointment) + .await + .unwrap(); StoredAppointment::Update } else { - dbm.store_appointment(uuid, appointment).unwrap(); + self.dbm.store_appointment(uuid, appointment).await.unwrap(); StoredAppointment::New } } @@ -250,7 +262,7 @@ impl Watcher { /// /// If the appointment is rejected by the [Responder] (i.e. for being invalid), the data is wiped /// from the database but the slot is not freed. - fn store_triggered_appointment( + async fn store_triggered_appointment( &self, uuid: UUID, appointment: &ExtendedAppointment, @@ -266,21 +278,22 @@ impl Watcher { // Data needs to be added the database straightaway since appointments are // FKs to trackers. If handle breach fails, data will be deleted later. self.dbm - .lock() - .unwrap() .store_appointment(uuid, appointment) + .await // TODO: Don't unwrap, or better, make this insertion atomic with the // `responder.has_tracker` that might cause the unwrap in the first place. // ref: https://github.com/talaia-labs/rust-teos/pull/190#discussion_r1218235632 .unwrap(); - if let ConfirmationStatus::Rejected(reason) = self.responder.handle_breach( - uuid, - Breach::new(dispute_tx.clone(), penalty_tx), - user_id, - ) { + if let ConfirmationStatus::Rejected(reason) = self + .responder + .handle_breach(uuid, Breach::new(dispute_tx.clone(), penalty_tx), user_id) + .await + { + // DISCUSS: We could either free the slots or keep it occupied as if this was misbehavior. + // Keeping it for now. log::warn!("Appointment bounced in the Responder. Reason: {reason:?}"); - self.gatekeeper.delete_appointments(vec![uuid], false); + self.gatekeeper.delete_appointments(vec![uuid], false).await; TriggeredAppointment::Rejected } else { log::info!("Appointment went straight to the Responder"); @@ -309,7 +322,7 @@ impl Watcher { /// - The user subscription has not expired /// - The appointment belongs to the user /// - The appointment exists within the system (either in the [Watcher] or the [Responder]) - pub(crate) fn get_appointment( + pub(crate) async fn get_appointment( &self, locator: Locator, user_signature: &str, @@ -319,27 +332,27 @@ impl Watcher { let user_id = self .gatekeeper .authenticate_user(message.as_bytes(), user_signature) + .await .map_err(|_| GetAppointmentFailure::AuthenticationFailure)?; - let (has_subscription_expired, expiry) = - self.gatekeeper.has_subscription_expired(user_id).unwrap(); + let (has_subscription_expired, expiry) = self + .gatekeeper + .has_subscription_expired(user_id) + .await + .unwrap(); if has_subscription_expired { return Err(GetAppointmentFailure::SubscriptionExpired(expiry)); } let uuid = UUID::new(locator, user_id); - let dbm = self.dbm.lock().unwrap(); - dbm.load_tracker(uuid) - .map(AppointmentInfo::Tracker) - .or_else(|| { - dbm.load_appointment(uuid) - .map(|ext_app| AppointmentInfo::Appointment(ext_app.inner)) - }) - .ok_or_else(|| { - log::info!("Cannot find {locator}"); - GetAppointmentFailure::NotFound - }) + if let Some(tracker) = self.dbm.load_tracker(uuid).await { + Ok(AppointmentInfo::Tracker(tracker)) + } else if let Some(appointment) = self.dbm.load_appointment(uuid).await { + Ok(AppointmentInfo::Appointment(appointment.inner)) + } else { + Err(GetAppointmentFailure::NotFound) + } } /// Gets a map of breaches provided a map between locators and transactions. @@ -347,15 +360,14 @@ impl Watcher { /// The provided map if intersected with the map of all locators monitored by [Watcher] and the result /// is considered the list of all breaches. This is queried on a per-block basis with all the /// `(locator, transaction)` pairs computed from the transaction data. - fn get_breaches( + async fn get_breaches( &self, locator_tx_map: HashMap, ) -> HashMap { let breaches: HashMap = self .dbm - .lock() - .unwrap() .batch_check_locators_exist(locator_tx_map.keys().collect()) + .await .iter() .map(|locator| (*locator, locator_tx_map[locator].clone())) .collect(); @@ -375,21 +387,24 @@ impl Watcher { /// If the decryption fails for some appointments or if it succeeds but they get rejected when sent to the network, /// they are marked as an invalid breaches and returned. /// [None] is returned if none of these breaches are invalid. - fn handle_breaches(&self, breaches: HashMap) -> Option> { + async fn handle_breaches(&self, breaches: HashMap) -> Option> { let mut invalid_breaches = Vec::new(); for (locator, dispute_tx) in breaches.into_iter() { - // WARNING(deadlock): Don't lock `self.dbm` over the loop since `Responder::handle_breach` uses it as well. - let uuids = self.dbm.lock().unwrap().load_uuids(locator); + let uuids = self.dbm.load_uuids(locator).await; for uuid in uuids { - let appointment = self.dbm.lock().unwrap().load_appointment(uuid).unwrap(); + let appointment = self.dbm.load_appointment(uuid).await.unwrap(); match cryptography::decrypt(appointment.encrypted_blob(), &dispute_tx.txid()) { Ok(penalty_tx) => { - if let ConfirmationStatus::Rejected(_) = self.responder.handle_breach( - uuid, - Breach::new(dispute_tx.clone(), penalty_tx), - appointment.user_id, - ) { + if let ConfirmationStatus::Rejected(_) = self + .responder + .handle_breach( + uuid, + Breach::new(dispute_tx.clone(), penalty_tx), + appointment.user_id, + ) + .await + { invalid_breaches.push(uuid); } } @@ -403,59 +418,59 @@ impl Watcher { (!invalid_breaches.is_empty()).then_some(invalid_breaches) } - /// Ges the number of users currently registered with the tower. - pub(crate) fn get_registered_users_count(&self) -> usize { - self.gatekeeper.get_registered_users_count() + /// Gets the number of users currently registered with the tower. + pub(crate) async fn get_registered_users_count(&self) -> usize { + self.gatekeeper.get_registered_users_count().await } /// Gets the total number of appointments excluding trackers. - pub(crate) fn get_appointments_count(&self) -> usize { - self.dbm.lock().unwrap().get_appointments_count() + pub(crate) async fn get_appointments_count(&self) -> usize { + self.dbm.get_appointments_count().await } /// Gets the total number of trackers in the [Responder]. - pub(crate) fn get_trackers_count(&self) -> usize { - self.responder.get_trackers_count() + pub(crate) async fn get_trackers_count(&self) -> usize { + self.responder.get_trackers_count().await } /// Gets all the appointments stored in the [Watcher] (from the database). - pub(crate) fn get_all_watcher_appointments(&self) -> HashMap { - self.dbm.lock().unwrap().load_appointments(None) + pub(crate) async fn get_all_watcher_appointments(&self) -> HashMap { + self.dbm.load_appointments(None).await } /// Gets all the appointments matching a specific locator from the [Watcher] (from the database). - pub(crate) fn get_watcher_appointments_with_locator( + pub(crate) async fn get_watcher_appointments_with_locator( &self, locator: Locator, ) -> HashMap { - self.dbm.lock().unwrap().load_appointments(Some(locator)) + self.dbm.load_appointments(Some(locator)).await } /// Gets all the trackers stored in the [Responder] (from the database). - pub(crate) fn get_all_responder_trackers(&self) -> HashMap { - self.dbm.lock().unwrap().load_trackers(None) + pub(crate) async fn get_all_responder_trackers(&self) -> HashMap { + self.dbm.load_trackers(None).await } /// Gets all the trackers matching s specific locator from the [Responder] (from the database). - pub(crate) fn get_responder_trackers_with_locator( + pub(crate) async fn get_responder_trackers_with_locator( &self, locator: Locator, ) -> HashMap { - self.dbm.lock().unwrap().load_trackers(Some(locator)) + self.dbm.load_trackers(Some(locator)).await } /// Gets the list of all registered user ids. - pub(crate) fn get_user_ids(&self) -> Vec { - self.gatekeeper.get_user_ids() + pub(crate) async fn get_user_ids(&self) -> Vec { + self.gatekeeper.get_user_ids().await } /// Gets the data held by the tower about a given user. - pub(crate) fn get_user_info(&self, user_id: UserId) -> Option<(UserInfo, Vec)> { - self.gatekeeper.get_user_info(user_id) + pub(crate) async fn get_user_info(&self, user_id: UserId) -> Option<(UserInfo, Vec)> { + self.gatekeeper.get_user_info(user_id).await } /// Gets information about a user's subscription. - pub(crate) fn get_subscription_info( + pub(crate) async fn get_subscription_info( &self, signature: &str, ) -> Result<(UserInfo, Vec), GetSubscriptionInfoFailure> { @@ -464,22 +479,27 @@ impl Watcher { let user_id = self .gatekeeper .authenticate_user(message.as_bytes(), signature) + .await .map_err(|_| GetSubscriptionInfoFailure::AuthenticationFailure)?; - let (has_subscription_expired, expiry) = - self.gatekeeper.has_subscription_expired(user_id).unwrap(); + let (has_subscription_expired, expiry) = self + .gatekeeper + .has_subscription_expired(user_id) + .await + .unwrap(); if has_subscription_expired { return Err(GetSubscriptionInfoFailure::SubscriptionExpired(expiry)); } - let (subscription_info, locators) = self.gatekeeper.get_user_info(user_id).unwrap(); + let (subscription_info, locators) = self.gatekeeper.get_user_info(user_id).await.unwrap(); Ok((subscription_info, locators)) } } /// Listen implementation by the [Watcher]. Handles monitoring and reorgs. -impl chain::Listen for Watcher { +#[tonic::async_trait] +impl AsyncListen for Watcher { /// Handles the monitoring process by the [Watcher]. /// /// Watching is performed in a per-block basis. Therefore, a breach is only considered (and detected) if seen @@ -491,27 +511,28 @@ impl chain::Listen for Watcher { /// /// This also takes care of updating the [LocatorCache] and removing outdated data from the [Watcher] when /// told by the [Gatekeeper]. - fn filtered_block_connected( - &self, - header: &BlockHeader, - txdata: &chain::transaction::TransactionData, - height: u32, - ) { - log::info!("New block received: {}", header.block_hash()); + async fn block_connected(&self, block: &Block, height: u32) { + log::info!("New block received: {}", block.header.block_hash()); - let locator_tx_map = txdata + let locator_tx_map = block + .txdata .iter() - .map(|(_, tx)| (Locator::new(tx.txid()), (*tx).clone())) + .map(|tx| (Locator::new(tx.txid()), (*tx).clone())) .collect(); self.locator_cache .lock() .unwrap() - .update(*header, &locator_tx_map); + .update(block.header, &locator_tx_map); // Get the breaches found in this block, handle them, and delete invalid ones. - if let Some(invalid_breaches) = self.handle_breaches(self.get_breaches(locator_tx_map)) { - self.gatekeeper.delete_appointments(invalid_breaches, false); + if let Some(invalid_breaches) = self + .handle_breaches(self.get_breaches(locator_tx_map).await) + .await + { + self.gatekeeper + .delete_appointments(invalid_breaches, false) + .await; } // Update last known block @@ -522,7 +543,7 @@ impl chain::Listen for Watcher { /// Handle reorgs in the [Watcher]. /// /// Fixes the [LocatorCache] by removing the disconnected data and updates the last_known_block_height. - fn block_disconnected(&self, header: &BlockHeader, height: u32) { + async fn block_disconnected(&self, header: &BlockHeader, height: u32) { log::warn!("Block disconnected: {}", header.block_hash()); self.locator_cache .lock() @@ -539,7 +560,7 @@ mod tests { use std::collections::HashSet; use std::iter::FromIterator; use std::ops::Deref; - use std::sync::{Arc, Mutex}; + use std::sync::Arc; use crate::dbm::DBM; use crate::responder::ConfirmationStatus; @@ -553,49 +574,50 @@ mod tests { use bitcoin::secp256k1::{PublicKey, Secp256k1}; - use lightning::chain::Listen; - impl PartialEq for Watcher { fn eq(&self, other: &Self) -> bool { // Same in-memory data. - self.last_known_block_height.load(Ordering::Relaxed) == other.last_known_block_height.load(Ordering::Relaxed) && - *self.locator_cache.lock().unwrap() == *other.locator_cache.lock().unwrap() && - // && Same DB data. - self.get_all_watcher_appointments() == other.get_all_watcher_appointments() + self.last_known_block_height.load(Ordering::Relaxed) + == other.last_known_block_height.load(Ordering::Relaxed) + && *self.locator_cache.lock().unwrap() == *other.locator_cache.lock().unwrap() } } impl Eq for Watcher {} impl Watcher { - pub(crate) fn add_dummy_tracker_to_responder(&self, tracker: &TransactionTracker) { - self.responder.add_dummy_tracker(tracker) + pub(crate) async fn add_dummy_tracker_to_responder(&self, tracker: &TransactionTracker) { + self.responder.add_dummy_tracker(tracker).await } - pub(crate) fn add_random_tracker_to_responder(&self) -> TransactionTracker { + pub(crate) async fn add_random_tracker_to_responder(&self) -> TransactionTracker { // The confirmation status can be whatever here. Using the most common. self.responder .add_random_tracker(ConfirmationStatus::ConfirmedIn(100)) + .await } } async fn init_watcher(chain: &mut Blockchain) -> (Watcher, BitcoindStopper) { - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); + let dbm = Arc::new(DBM::test_db().await); init_watcher_with_db(chain, dbm).await } async fn init_watcher_with_db( chain: &mut Blockchain, - dbm: Arc>, + dbm: Arc, ) -> (Watcher, BitcoindStopper) { let bitcoind_mock = BitcoindMock::new(MockOptions::default()); - let gk = Arc::new(Gatekeeper::new( - chain.get_block_count(), - SLOTS, - DURATION, - EXPIRY_DELTA, - dbm.clone(), - )); + let gk = Arc::new( + Gatekeeper::new( + chain.get_block_count(), + SLOTS, + DURATION, + EXPIRY_DELTA, + dbm.clone(), + ) + .await, + ); let responder = create_responder(chain, gk.clone(), dbm.clone(), bitcoind_mock.url()).await; create_watcher( chain, @@ -628,13 +650,13 @@ mod tests { async fn test_new() { // A fresh watcher has no associated data let mut chain = Blockchain::default().with_height(START_HEIGHT); - let dbm = Arc::new(Mutex::new(DBM::in_memory().unwrap())); + let dbm = Arc::new(DBM::test_db().await); let (watcher, _s) = init_watcher_with_db(&mut chain, dbm.clone()).await; - assert!(watcher.is_fresh()); + assert!(watcher.is_fresh().await); let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); // If we add some appointments to the system and create a new Watcher reusing the same db // (as if simulating a bootstrap from existing data), the data should be properly loaded. @@ -643,12 +665,13 @@ mod tests { let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); watcher .add_appointment(appointment.clone(), user_sig.clone()) + .await .unwrap(); } - // Create a new Responder reusing the same DB and check that the data is loaded + // Create a new watcher reusing the same DB and check that the data is loaded let (another_w, _as) = init_watcher_with_db(&mut chain, dbm).await; - assert!(!another_w.is_fresh()); + assert!(!another_w.is_fresh().await); assert_eq!(watcher, another_w); } @@ -663,7 +686,7 @@ mod tests { let (_, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - let receipt = watcher.register(user_id).unwrap(); + let receipt = watcher.register(user_id).await.unwrap(); assert_eq!(receipt.user_id(), user_id); assert_eq!(receipt.available_slots(), SLOTS); @@ -700,7 +723,7 @@ mod tests { )); let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); let appointment = generate_dummy_appointment(None).inner; let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); @@ -708,6 +731,7 @@ mod tests { for _ in 0..2 { let (receipt, slots, expiry) = watcher .add_appointment(appointment.clone(), user_sig.clone()) + .await .unwrap(); assert_appointment_added(slots, SLOTS - 1, expiry, receipt, &user_sig, tower_id); @@ -716,18 +740,19 @@ mod tests { // Add the same appointment but for another user let (user2_sk, user2_pk) = get_random_keypair(); let user2_id = UserId(user2_pk); - watcher.register(user2_id).unwrap(); + watcher.register(user2_id).await.unwrap(); let user2_sig = cryptography::sign(&appointment.to_vec(), &user2_sk).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(appointment.clone(), user2_sig.clone()) + .await .unwrap(); assert_appointment_added(slots, SLOTS - 1, expiry, receipt, &user2_sig, tower_id); // There should be now two appointments in the Watcher - assert_eq!(watcher.get_appointments_count(), 2); - assert_eq!(watcher.responder.get_trackers_count(), 0); + assert_eq!(watcher.get_appointments_count().await, 2); + assert_eq!(watcher.responder.get_trackers_count().await, 0); // If an appointment is already in the Responder, it should bounce let dispute_tx = get_random_tx(); @@ -737,27 +762,33 @@ mod tests { cryptography::sign(&triggered_appointment.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(triggered_appointment.inner.clone(), signature.clone()) + .await .unwrap(); assert_appointment_added(slots, SLOTS - 2, expiry, receipt, &signature, tower_id); - assert_eq!(watcher.get_appointments_count(), 3); - assert_eq!(watcher.responder.get_trackers_count(), 0); + assert_eq!(watcher.get_appointments_count().await, 3); + assert_eq!(watcher.responder.get_trackers_count().await, 0); let breach = Breach::new(dispute_tx, get_random_tx()); - watcher.responder.add_tracker( - uuid, - breach, - user_id, - ConfirmationStatus::InMempoolSince(chain.get_block_count()), - ); - let receipt = watcher.add_appointment(triggered_appointment.inner, signature); + watcher + .responder + .add_tracker( + uuid, + breach, + user_id, + ConfirmationStatus::InMempoolSince(chain.get_block_count()), + ) + .await; + let receipt = watcher + .add_appointment(triggered_appointment.inner, signature) + .await; assert!(matches!( receipt, Err(AddAppointmentFailure::AlreadyTriggered) )); - assert_eq!(watcher.get_appointments_count(), 2); - assert_eq!(watcher.responder.get_trackers_count(), 1); + assert_eq!(watcher.get_appointments_count().await, 2); + assert_eq!(watcher.responder.get_trackers_count().await, 1); // If the trigger is already in the cache, the appointment will go straight to the Responder let dispute_tx = tip_txs.last().unwrap(); @@ -766,14 +797,15 @@ mod tests { let user_sig = cryptography::sign(&appointment_in_cache.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(appointment_in_cache.inner, user_sig.clone()) + .await .unwrap(); // The appointment should have been accepted, slots should have been decreased, and a new tracker should be found in the Responder assert_appointment_added(slots, SLOTS - 3, expiry, receipt, &user_sig, tower_id); - assert_eq!(watcher.get_appointments_count(), 2); - assert_eq!(watcher.responder.get_trackers_count(), 2); + assert_eq!(watcher.get_appointments_count().await, 2); + assert_eq!(watcher.responder.get_trackers_count().await, 2); // Data should be in the database - assert!(watcher.responder.has_tracker(uuid)); + assert!(watcher.responder.has_tracker(uuid).await); // If an appointment is rejected by the Responder, it is considered misbehavior and the slot count is kept // Wrong penalty @@ -784,14 +816,15 @@ mod tests { let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(invalid_appointment.inner, user_sig.clone()) + .await .unwrap(); assert_appointment_added(slots, SLOTS - 4, expiry, receipt, &user_sig, tower_id); - assert_eq!(watcher.get_appointments_count(), 2); - assert_eq!(watcher.responder.get_trackers_count(), 2); + assert_eq!(watcher.get_appointments_count().await, 2); + assert_eq!(watcher.responder.get_trackers_count().await, 2); // Data should not be in the database - assert!(!watcher.responder.has_tracker(uuid)); - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.responder.has_tracker(uuid).await); + assert!(!watcher.dbm.appointment_exists(uuid).await); // Transaction rejected // Update the Responder with a new Carrier @@ -807,14 +840,15 @@ mod tests { let user_sig = cryptography::sign(&invalid_appointment.inner.to_vec(), &user_sk).unwrap(); let (receipt, slots, expiry) = watcher .add_appointment(invalid_appointment.inner, user_sig.clone()) + .await .unwrap(); assert_appointment_added(slots, SLOTS - 5, expiry, receipt, &user_sig, tower_id); - assert_eq!(watcher.get_appointments_count(), 2); - assert_eq!(watcher.responder.get_trackers_count(), 2); + assert_eq!(watcher.get_appointments_count().await, 2); + assert_eq!(watcher.responder.get_trackers_count().await, 2); // Data should not be in the database - assert!(!watcher.responder.has_tracker(uuid)); - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.responder.has_tracker(uuid).await); + assert!(!watcher.dbm.appointment_exists(uuid).await); // FAIL cases (non-registered, subscription expired and not enough slots) @@ -823,11 +857,11 @@ mod tests { let user3_sig = String::from_utf8((0..65).collect()).unwrap(); assert!(matches!( - watcher.add_appointment(appointment, user3_sig), + watcher.add_appointment(appointment, user3_sig).await, Err(AddAppointmentFailure::AuthenticationFailure) )); // Data should not be in the database - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.dbm.appointment_exists(uuid).await); // If the user has no enough slots, the appointment is rejected. We do not test all possible cases since updates are // already tested int he Gatekeeper. Testing that it is rejected if the condition is met should suffice. @@ -835,7 +869,7 @@ mod tests { .gatekeeper .get_registered_users() .lock() - .unwrap() + .await .get_mut(&user_id) .unwrap() .available_slots = 0; @@ -844,26 +878,27 @@ mod tests { let signature = cryptography::sign(&appointment.inner.to_vec(), &user_sk).unwrap(); assert!(matches!( - watcher.add_appointment(appointment.inner, signature), + watcher.add_appointment(appointment.inner, signature).await, Err(AddAppointmentFailure::NotEnoughSlots) )); // Data should not be in the database - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.dbm.appointment_exists(uuid).await); // If the user subscription has expired, the appointment should be rejected. watcher .gatekeeper - .add_outdated_user(user2_id, START_HEIGHT as u32); + .add_outdated_user(user2_id, START_HEIGHT as u32) + .await; let (uuid, appointment) = generate_dummy_appointment_with_user(user2_id, None); let signature = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); assert!(matches!( - watcher.add_appointment(appointment.inner, signature), + watcher.add_appointment(appointment.inner, signature).await, Err(AddAppointmentFailure::SubscriptionExpired { .. }) )); // Data should not be in the database - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.dbm.appointment_exists(uuid).await); } #[tokio::test] @@ -874,7 +909,7 @@ mod tests { // Register the user let (_, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); let dispute_txid = get_random_tx().txid(); let (uuid, appointment) = @@ -882,11 +917,11 @@ mod tests { // Storing a new appointment should return New assert_eq!( - watcher.store_appointment(uuid, &appointment), + watcher.store_appointment(uuid, &appointment).await, StoredAppointment::New, ); assert_eq!( - watcher.get_all_watcher_appointments(), + watcher.get_all_watcher_appointments().await, HashMap::from_iter([(uuid, appointment)]) ); @@ -896,11 +931,11 @@ mod tests { generate_dummy_appointment_with_user(user_id, Some(&dispute_txid)); assert_eq!(new_uuid, uuid); assert_eq!( - watcher.store_appointment(uuid, &appointment), + watcher.store_appointment(uuid, &appointment).await, StoredAppointment::Update, ); assert_eq!( - watcher.get_all_watcher_appointments(), + watcher.get_all_watcher_appointments().await, HashMap::from_iter([(uuid, appointment)]) ); } @@ -913,7 +948,7 @@ mod tests { // Register the user let (_, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); let dispute_tx = get_random_tx(); let (uuid, appointment) = @@ -921,12 +956,14 @@ mod tests { // Valid triggered appointments should be accepted by the Responder assert_eq!( - watcher.store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx), + watcher + .store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx) + .await, TriggeredAppointment::Accepted, ); // In this case the appointment is kept in the Responder and, therefore, in the database - assert!(watcher.responder.has_tracker(uuid)); - assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(watcher.responder.has_tracker(uuid).await); + assert!(watcher.dbm.appointment_exists(uuid).await); // A properly formatted but invalid transaction should be rejected by the Responder // Update the Responder with a new Carrier that will reject the transaction @@ -939,24 +976,28 @@ mod tests { let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, Some(&dispute_tx.txid())); assert_eq!( - watcher.store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx), + watcher + .store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx) + .await, TriggeredAppointment::Rejected, ); // In this case the appointment is not kept in the Responder nor in the database - assert!(!watcher.responder.has_tracker(uuid)); - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.responder.has_tracker(uuid).await); + assert!(!watcher.dbm.appointment_exists(uuid).await); // Invalid triggered appointments should not be passed to the Responder // Use a dispute_tx that does not match the appointment to replicate a decryption error // (the same applies to invalid formatted transactions) let (uuid, appointment) = generate_dummy_appointment_with_user(user_id, None); assert_eq!( - watcher.store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx), + watcher + .store_triggered_appointment(uuid, &appointment, user_id, &dispute_tx) + .await, TriggeredAppointment::Invalid, ); // The appointment is not kept anywhere - assert!(!watcher.responder.has_tracker(uuid)); - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.responder.has_tracker(uuid).await); + assert!(!watcher.dbm.appointment_exists(uuid).await); } #[tokio::test] @@ -970,25 +1011,29 @@ mod tests { // If the user cannot be properly identified, the request will fail. This can be simulated by providing a wrong signature let wrong_sig = String::from_utf8((0..65).collect()).unwrap(); assert!(matches!( - watcher.get_appointment(appointment.locator, &wrong_sig), + watcher + .get_appointment(appointment.locator, &wrong_sig) + .await, Err(GetAppointmentFailure::AuthenticationFailure) )); // If the user does exist and there's an appointment with the given locator belonging to him, it will be returned let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); watcher .add_appointment( appointment.clone(), cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(), ) + .await .unwrap(); let message = format!("get appointment {}", appointment.locator); let signature = cryptography::sign(message.as_bytes(), &user_sk).unwrap(); let info = watcher .get_appointment(appointment.locator, &signature) + .await .unwrap(); match info { @@ -1008,13 +1053,15 @@ mod tests { let status = ConfirmationStatus::InMempoolSince(chain.get_block_count()); watcher .responder - .add_tracker(uuid, breach.clone(), user_id, status); + .add_tracker(uuid, breach.clone(), user_id, status) + .await; let tracker = TransactionTracker::new(breach, user_id, status); let tracker_message = format!("get appointment {}", appointment.locator); let tracker_signature = cryptography::sign(tracker_message.as_bytes(), &user_sk).unwrap(); let info = watcher .get_appointment(appointment.locator, &tracker_signature) + .await .unwrap(); match info { @@ -1028,21 +1075,26 @@ mod tests { // NotFound should be returned. let (user2_sk, user2_pk) = get_random_keypair(); let user2_id = UserId(user2_pk); - watcher.register(user2_id).unwrap(); + watcher.register(user2_id).await.unwrap(); let signature2 = cryptography::sign(message.as_bytes(), &user2_sk).unwrap(); assert!(matches!( - watcher.get_appointment(appointment.locator, &signature2), + watcher + .get_appointment(appointment.locator, &signature2) + .await, Err(GetAppointmentFailure::NotFound { .. }) )); // If the user subscription has expired, the request will fail watcher .gatekeeper - .add_outdated_user(user_id, START_HEIGHT as u32); + .add_outdated_user(user_id, START_HEIGHT as u32) + .await; assert!(matches!( - watcher.get_appointment(appointment.locator, &signature), + watcher + .get_appointment(appointment.locator, &signature) + .await, Err(GetAppointmentFailure::SubscriptionExpired { .. }) )); } @@ -1060,7 +1112,7 @@ mod tests { let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); // Add some of them to the Watcher let mut breaches = HashMap::new(); @@ -1069,13 +1121,16 @@ mod tests { if i % 2 == 0 { let appointment = generate_dummy_appointment(Some(&tx.txid())).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); - watcher.add_appointment(appointment, signature).unwrap(); + watcher + .add_appointment(appointment, signature) + .await + .unwrap(); breaches.insert(*l, tx.clone()); } } // Check that breaches are correctly detected - assert_eq!(watcher.get_breaches(locator_tx_map), breaches); + assert_eq!(watcher.get_breaches(locator_tx_map).await, breaches); } #[tokio::test] @@ -1091,16 +1146,19 @@ mod tests { let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); // Let the watcher track these breaches. for (_, tx) in breaches.iter() { let appointment = generate_dummy_appointment(Some(&tx.txid())).inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); - watcher.add_appointment(appointment, signature).unwrap(); + watcher + .add_appointment(appointment, signature) + .await + .unwrap(); } - assert!(watcher.handle_breaches(breaches).is_none()) + assert!(watcher.handle_breaches(breaches).await.is_none()) } #[tokio::test] @@ -1116,7 +1174,7 @@ mod tests { let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); let mut rejected = HashSet::new(); // Let the watcher track these breaches. @@ -1130,12 +1188,15 @@ mod tests { rejected.insert(uuid); }; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); - watcher.add_appointment(appointment, signature).unwrap(); + watcher + .add_appointment(appointment, signature) + .await + .unwrap(); } assert_eq!( rejected, - HashSet::from_iter(watcher.handle_breaches(breaches).unwrap()) + HashSet::from_iter(watcher.handle_breaches(breaches).await.unwrap()) ); } @@ -1159,7 +1220,7 @@ mod tests { let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); let mut uuids = HashSet::new(); // Let the watcher track these breaches. @@ -1168,13 +1229,16 @@ mod tests { generate_dummy_appointment_with_user(user_id, Some(&tx.txid())); let appointment = appointment.inner; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); - watcher.add_appointment(appointment, signature).unwrap(); + watcher + .add_appointment(appointment, signature) + .await + .unwrap(); uuids.insert(uuid); } assert_eq!( uuids, - HashSet::from_iter(watcher.handle_breaches(breaches).unwrap()) + HashSet::from_iter(watcher.handle_breaches(breaches).await.unwrap()) ); } @@ -1191,7 +1255,7 @@ mod tests { let (user_sk, user_pk) = get_random_keypair(); let user_id = UserId(user_pk); - watcher.register(user_id).unwrap(); + watcher.register(user_id).await.unwrap(); let mut rejected_breaches = HashSet::new(); // Let the watcher track these breaches. @@ -1205,17 +1269,20 @@ mod tests { rejected_breaches.insert(uuid); }; let signature = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); - watcher.add_appointment(appointment, signature).unwrap(); + watcher + .add_appointment(appointment, signature) + .await + .unwrap(); } assert_eq!( rejected_breaches, - HashSet::from_iter(watcher.handle_breaches(breaches).unwrap()) + HashSet::from_iter(watcher.handle_breaches(breaches).await.unwrap()) ); } #[tokio::test] - async fn test_filtered_block_connected() { + async fn test_block_connected() { let mut chain = Blockchain::default().with_height(START_HEIGHT); let (watcher, _s) = init_watcher(&mut chain).await; @@ -1228,7 +1295,9 @@ mod tests { watcher.last_known_block_height.load(Ordering::Relaxed), chain.get_block_count() ); - watcher.block_connected(&chain.generate(None), chain.get_block_count()); + watcher + .block_connected(&chain.generate(None), chain.get_block_count()) + .await; assert_eq!( watcher.last_known_block_height.load(Ordering::Relaxed), chain.get_block_count() @@ -1248,8 +1317,8 @@ mod tests { let user_id = UserId(user_pk); let (user2_sk, user2_pk) = get_random_keypair(); let user2_id = UserId(user2_pk); - watcher.register(user_id).unwrap(); - watcher.register(user2_id).unwrap(); + watcher.register(user_id).await.unwrap(); + watcher.register(user2_id).await.unwrap(); let appointment = generate_dummy_appointment(None).inner; let uuid1 = UUID::new(appointment.locator, user_id); @@ -1258,41 +1327,49 @@ mod tests { let user_sig = cryptography::sign(&appointment.to_vec(), &user_sk).unwrap(); watcher .add_appointment(appointment.clone(), user_sig) + .await .unwrap(); let user2_sig = cryptography::sign(&appointment.to_vec(), &user2_sk).unwrap(); - watcher.add_appointment(appointment, user2_sig).unwrap(); + watcher + .add_appointment(appointment, user2_sig) + .await + .unwrap(); // Outdate the first user's registration. watcher .gatekeeper - .add_outdated_user(user_id, chain.get_block_count()); + .add_outdated_user(user_id, chain.get_block_count()) + .await; // Both appointments can be found before mining a block, only the user's 2 can be found afterwards for &uuid in &[uuid1, uuid2] { - assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(watcher.dbm.appointment_exists(uuid).await); } // We always need to connect the gatekeeper first so it cleans up outdated users and their data. let block = chain.generate(None); watcher .gatekeeper - .block_connected(&block, chain.get_block_count()); - watcher.block_connected(&block, chain.get_block_count()); + .block_connected(&block, chain.get_block_count()) + .await; + watcher + .block_connected(&block, chain.get_block_count()) + .await; // uuid1 and user1 should have been deleted while uuid2 and user2 still exists. - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid1)); + assert!(!watcher.dbm.appointment_exists(uuid1).await); assert!(!watcher .gatekeeper .get_registered_users() .lock() - .unwrap() + .await .contains_key(&user_id)); - assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid2)); + assert!(watcher.dbm.appointment_exists(uuid2).await); assert!(watcher .gatekeeper .get_registered_users() .lock() - .unwrap() + .await .contains_key(&user2_id)); // Check triggers. Add a new appointment and trigger it with valid data. @@ -1300,18 +1377,24 @@ mod tests { let (uuid, appointment) = generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); - watcher.add_appointment(appointment.inner, sig).unwrap(); + watcher + .add_appointment(appointment.inner, sig) + .await + .unwrap(); - assert!(watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(watcher.dbm.appointment_exists(uuid).await); let block = chain.generate(Some(vec![dispute_tx])); watcher .gatekeeper - .block_connected(&block, chain.get_block_count()); - watcher.block_connected(&block, chain.get_block_count()); + .block_connected(&block, chain.get_block_count()) + .await; + watcher + .block_connected(&block, chain.get_block_count()) + .await; // Data should have been kept in the database - assert!(watcher.responder.has_tracker(uuid)); + assert!(watcher.responder.has_tracker(uuid).await); // Checks invalid triggers. Add a new appointment and trigger it with invalid data. let dispute_tx = get_random_tx(); @@ -1320,24 +1403,33 @@ mod tests { // Modify the encrypted blob so the data is invalid. appointment.inner.encrypted_blob.reverse(); let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); - watcher.add_appointment(appointment.inner, sig).unwrap(); + watcher + .add_appointment(appointment.inner, sig) + .await + .unwrap(); let block = chain.generate(Some(vec![dispute_tx])); watcher .gatekeeper - .block_connected(&block, chain.get_block_count()); - watcher.block_connected(&block, chain.get_block_count()); + .block_connected(&block, chain.get_block_count()) + .await; + watcher + .block_connected(&block, chain.get_block_count()) + .await; // Data should have been wiped from the database - assert!(!watcher.responder.has_tracker(uuid)); - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.responder.has_tracker(uuid).await); + assert!(!watcher.dbm.appointment_exists(uuid).await); // Check triggering with a valid formatted transaction but that is rejected by the Responder. let dispute_tx = get_random_tx(); let (uuid, appointment) = generate_dummy_appointment_with_user(user2_id, Some(&dispute_tx.txid())); let sig = cryptography::sign(&appointment.inner.to_vec(), &user2_sk).unwrap(); - watcher.add_appointment(appointment.inner, sig).unwrap(); + watcher + .add_appointment(appointment.inner, sig) + .await + .unwrap(); // Set the carrier response // Both non-decryptable blobs and blobs with invalid transactions will yield an invalid trigger. @@ -1350,12 +1442,15 @@ mod tests { let block = chain.generate(Some(vec![dispute_tx])); watcher .gatekeeper - .block_connected(&block, chain.get_block_count()); - watcher.block_connected(&block, chain.get_block_count()); + .block_connected(&block, chain.get_block_count()) + .await; + watcher + .block_connected(&block, chain.get_block_count()) + .await; // Data should have been wiped from the database - assert!(!watcher.responder.has_tracker(uuid)); - assert!(!watcher.dbm.lock().unwrap().appointment_exists(uuid)); + assert!(!watcher.responder.has_tracker(uuid).await); + assert!(!watcher.dbm.appointment_exists(uuid).await); } #[tokio::test] @@ -1374,7 +1469,9 @@ mod tests { .blocks() .contains(&last_block_header.block_hash())); - watcher.block_disconnected(&last_block_header, start_height); + watcher + .block_disconnected(&last_block_header, start_height) + .await; assert_eq!( watcher.last_known_block_height.load(Ordering::Relaxed), diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index 976f8e9d..6e02d8e9 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -18,7 +18,7 @@ hex = { version = "0.4.3", features = [ "serde" ] } home = "0.5.3" reqwest = { version = "0.11", features = [ "blocking", "json", "socks" ] } log = "0.4.16" -rusqlite = { version = "0.26.0", features = [ "bundled", "limits" ] } +rusqlite = { version = "0.29.0", features = [ "bundled", "limits" ] } serde = "1.0.130" serde_json = { version = "1.0", features = [ "preserve_order" ] } tonic = { version = "^0.5", features = [ "tls", "transport" ] } diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index bf271bb8..09197aa6 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -3,12 +3,12 @@ use std::iter::FromIterator; use std::path::PathBuf; use std::str::FromStr; -use rusqlite::{params, Connection, Error as SqliteError}; +use rusqlite::ffi::{SQLITE_CONSTRAINT_FOREIGNKEY, SQLITE_CONSTRAINT_PRIMARYKEY}; +use rusqlite::{params, Connection, Error as SqliteError, ErrorCode, Params}; use bitcoin::secp256k1::SecretKey; use teos_common::appointment::{Appointment, Locator}; -use teos_common::dbm::{DatabaseConnection, DatabaseManager, Error}; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; @@ -83,6 +83,16 @@ const TABLES: [&str; 8] = [ )", ]; +/// Packs the errors than can raise when interacting with the underlying database. +#[derive(Debug)] +pub enum Error { + AlreadyExists, + MissingForeignKey, + MissingField, + NotFound, + Unknown(SqliteError), +} + /// Component in charge of interacting with the underlying database. /// /// Currently works for `SQLite`. `PostgreSQL` should also be added in the future. @@ -92,7 +102,7 @@ pub struct DBM { connection: Connection, } -impl DatabaseConnection for DBM { +impl DBM { fn get_connection(&self) -> &Connection { &self.connection } @@ -100,6 +110,49 @@ impl DatabaseConnection for DBM { fn get_mut_connection(&mut self) -> &mut Connection { &mut self.connection } + + /// Creates the database tables if not present. + fn create_tables(&mut self, tables: Vec<&str>) -> Result<(), SqliteError> { + let tx = self.get_mut_connection().transaction().unwrap(); + for table in tables.iter() { + tx.execute(table, [])?; + } + tx.commit() + } + + /// Generic method to store data into the database. + fn store_data(&self, query: &str, params: P) -> Result<(), Error> { + match self.get_connection().execute(query, params) { + Ok(_) => Ok(()), + Err(e) => match e { + SqliteError::SqliteFailure(ie, _) => match ie.code { + ErrorCode::ConstraintViolation => match ie.extended_code { + SQLITE_CONSTRAINT_FOREIGNKEY => Err(Error::MissingForeignKey), + SQLITE_CONSTRAINT_PRIMARYKEY => Err(Error::AlreadyExists), + _ => Err(Error::Unknown(e)), + }, + _ => Err(Error::Unknown(e)), + }, + _ => Err(Error::Unknown(e)), + }, + } + } + + /// Generic method to remove data from the database. + fn remove_data(&self, query: &str, params: P) -> Result<(), Error> { + match self.get_connection().execute(query, params).unwrap() { + 0 => Err(Error::NotFound), + _ => Ok(()), + } + } + + #[allow(dead_code)] + /// Generic method to update data from the database. + fn update_data(&self, query: &str, params: P) -> Result<(), Error> { + // Updating data is fundamentally the same as deleting it in terms of interface. + // A query is sent and either no row is modified or some rows are + self.remove_data(query, params) + } } impl DBM { diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index ecf26db0..713bde09 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -8,10 +8,10 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use teos_common::appointment::{Appointment, Locator}; use teos_common::cryptography; -use teos_common::dbm::Error as DBError; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::{TowerId, UserId}; +use crate::dbm::Error as DBError; use crate::dbm::DBM; use crate::net::ProxyInfo; use crate::retrier::RetrierStatus;