diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 90fd989c..b646bcb3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,35 @@ pub struct Responder { ## Test Coverage Tests should be provided to cover both positive and negative conditions. Tests should cover both the proper execution as well as all the covered error paths. PR with no proper test coverage will not be merged. -## Signing Commits +## Git conventions + +### Commits, titles, and descriptions + +- Changes must be split logically in commits, such that a commit is self-contained +- In general terms, all commits need to pass the test suite. There may be some exceptions to this rule if the change you are working on touches several components of the codebase and it makes more sense to split the change by component (or group of components) +- Commit titles need to be short and explanatory. If we are, for instance, adding an RPC command to the backend, "Adds command X to the backend" will be a good short description, "Add command" or "Fix #123" where #123 is an issue referencing this feature **IS NOT** +- Descriptions can be provided to give more context about what has been fixed and how + +### Pull requests + +- Pull request titles need to be explanatory, in the same way, commits titles were. If a PR includes a single commit, they can share the title, otherwise, a general title of what we are trying to achieve is required. **DO NOT REFERENCE ISSUES IN PULL REQUEST TITLES**, save that for the PR description +- PR descriptions need to guide the reviewer into what has been changed. You can reference issues here. If the PR is a fix of a simple issue, "Fix #123" may suffice, however, if it involves several changes, a proper explanation of both what has been fixed and how is due. These are two good examples of PR descriptions, both long and short: [188](https://github.com/talaia-labs/rust-teos/pull/188), [194](https://github.com/talaia-labs/rust-teos/pull/194) +- **WE DO NOT PILE "fix" COMMITS IN A PULL REQUEST**, that is, if some fixes are requested by reviewers, or something was missing from our original approach, it needs to be squashed. Do **NOT** do this: + + ``` + 886b0ff Adds X functionality to component Y + 801ff5d Fixes the previous commit because Z + 67ac345 Addresses review comments + 7dc7fcd Updates X because G was missing + b60999c Adds missing test + ... + ``` + +- Create a new branch to work on your pull request. **DO NOT** work from the master branch of your fork* +- **DO NOT** merge master into your branch, rebase master instead* + + \* If you're not sure how to handle this, check external documentation on how to manage multiple remotes for the same repository. + +### Signing Commits We require that all commits to be merged into master are signed. You can enable commit signing on GitHub by following [Signing commits](https://help.github.com/en/github/authenticating-to-github/signing-commits). diff --git a/Cargo.lock b/Cargo.lock index 0a9edf9c..93020933 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,15 +46,6 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - [[package]] name = "assert-json-diff" version = "2.0.2" @@ -65,128 +56,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "async-channel" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "num_cpus", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" -dependencies = [ - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2 0.4.4", - "waker-fn", - "winapi 0.3.9", -] - -[[package]] -name = "async-lock" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-object-pool" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" -dependencies = [ - "async-std", -] - -[[package]] -name = "async-process" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" -dependencies = [ - "async-io", - "blocking", - "cfg-if 1.0.0", - "event-listener", - "futures-lite", - "libc", - "once_cell", - "signal-hook", - "winapi 0.3.9", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite 0.2.8", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-stream" version = "0.3.2" @@ -208,12 +77,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-task" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" - [[package]] name = "async-trait" version = "0.1.52" @@ -225,12 +88,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atomic-waker" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" - [[package]] name = "atty" version = "0.2.14" @@ -259,7 +116,7 @@ dependencies = [ "instant", "pin-project-lite 0.2.8", "rand 0.8.5", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -283,38 +140,12 @@ dependencies = [ "byteorder", ] -[[package]] -name = "basic-cookies" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38" -dependencies = [ - "lalrpop", - "lalrpop-util", - "regex", -] - [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitcoin" version = "0.28.1" @@ -392,20 +223,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "blocking" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" -dependencies = [ - "async-channel", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "once_cell", -] - [[package]] name = "bstr" version = "0.2.17" @@ -449,18 +266,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cc" version = "1.0.73" @@ -557,7 +362,7 @@ dependencies = [ "log", "serde", "serde_json", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-stream", "tokio-util 0.7.0", ] @@ -573,15 +378,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "concurrent-queue" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" -dependencies = [ - "cache-padded", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -613,22 +409,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" -dependencies = [ - "cfg-if 1.0.0", - "once_cell", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.3" @@ -649,47 +429,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ctor" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "curl" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2 0.4.4", - "winapi 0.3.9", -] - -[[package]] -name = "curl-sys" -version = "0.4.55+curl-7.83.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi 0.3.9", -] - [[package]] name = "curve25519-dalek" version = "3.2.1" @@ -709,6 +448,25 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "deadpool" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +dependencies = [ + "async-trait", + "deadpool-runtime", + "num_cpus", + "retain_mut", + "tokio 1.25.0", +] + +[[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" @@ -746,12 +504,6 @@ dependencies = [ "syn", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.9.0" @@ -771,27 +523,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi 0.3.9", -] - [[package]] name = "ed25519" version = "1.5.2" @@ -821,15 +552,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "ena" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" -dependencies = [ - "log", -] - [[package]] name = "encoding_rs" version = "0.8.31" @@ -873,12 +595,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1007,21 +723,6 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite 0.2.8", - "waker-fn", -] - [[package]] name = "futures-macro" version = "0.3.21" @@ -1108,18 +809,6 @@ dependencies = [ "regex", ] -[[package]] -name = "gloo-timers" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.2.7" @@ -1154,7 +843,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-util 0.6.9", "tracing", ] @@ -1307,34 +996,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "httpmock" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c159c4fc205e6c1a9b325cb7ec135d13b5f47188ce175dabb76ec847f331d9bd" -dependencies = [ - "assert-json-diff", - "async-object-pool", - "async-trait", - "base64", - "basic-cookies", - "crossbeam-utils", - "form_urlencoded", - "futures-util", - "hyper 0.14.18", - "isahc", - "lazy_static", - "levenshtein", - "log", - "regex", - "serde", - "serde_json", - "serde_regex", - "similar", - "tokio 1.20.1", - "url", -] - [[package]] name = "humantime" version = "2.1.0" @@ -1383,7 +1044,7 @@ dependencies = [ "itoa 1.0.1", "pin-project-lite 0.2.8", "socket2 0.4.4", - "tokio 1.20.1", + "tokio 1.25.0", "tower-service", "tracing", "want", @@ -1397,7 +1058,7 @@ checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper 0.14.18", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-io-timeout", ] @@ -1410,7 +1071,7 @@ dependencies = [ "bytes 1.1.0", "hyper 0.14.18", "native-tls", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-native-tls", ] @@ -1481,33 +1142,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itertools" version = "0.10.3" @@ -1577,7 +1211,7 @@ dependencies = [ "jsonrpc-server-utils", "log", "net2", - "parking_lot", + "parking_lot 0.11.2", "unicase", ] @@ -1614,75 +1248,18 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "lalrpop" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823" -dependencies = [ - "ascii-canvas", - "atty", - "bit-set", - "diff", - "ena", - "itertools", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4" -dependencies = [ - "regex", -] - [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "libsqlite3-sys" version = "0.23.2" @@ -1694,18 +1271,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "lightning" version = "0.0.108" @@ -1737,7 +1302,7 @@ checksum = "2f0170619152c4d6b947d5ed0de427b85691482a293e0cae52d4336a2220a776" dependencies = [ "bitcoin", "lightning", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -1762,7 +1327,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if 1.0.0", - "value-bag", ] [[package]] @@ -1842,6 +1406,28 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "mockito" +version = "0.32.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa08cccbc31b07113e4322d79df4464e0ed888032fc67ec56136325cd3afecf" +dependencies = [ + "assert-json-diff", + "async-trait", + "colored", + "deadpool", + "futures", + "hyper 0.14.18", + "lazy_static", + "log", + "rand 0.8.5", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio 1.25.0", +] + [[package]] name = "multimap" version = "0.8.3" @@ -1895,12 +1481,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "nom" version = "7.1.1" @@ -2026,12 +1606,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - [[package]] name = "parking_lot" version = "0.11.2" @@ -2040,7 +1614,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.6", ] [[package]] @@ -2057,6 +1641,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.42.0", +] + [[package]] name = "pem" version = "1.1.0" @@ -2082,21 +1679,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" - [[package]] name = "pin-project" version = "1.0.10" @@ -2141,19 +1723,6 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" -[[package]] -name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "log", - "wepoll-ffi", - "winapi 0.3.9", -] - [[package]] name = "poly1305" version = "0.7.2" @@ -2171,12 +1740,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2431,21 +1994,11 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom 0.2.5", - "redox_syscall", -] - [[package]] name = "regex" -version = "1.5.6" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -2454,9 +2007,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -2494,7 +2047,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-native-tls", "tokio-socks", "tower-service", @@ -2505,6 +2058,12 @@ 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" @@ -2580,12 +2139,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "rustversion" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" - [[package]] name = "ryu" version = "1.0.9" @@ -2710,16 +2263,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2781,16 +2324,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2808,9 +2341,9 @@ checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" [[package]] name = "similar" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "simple_logger" @@ -2825,29 +2358,12 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "siphasher" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" - [[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.8.0" @@ -2881,19 +2397,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "string_cache" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26" -dependencies = [ - "lazy_static", - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - [[package]] name = "strsim" version = "0.8.0" @@ -3000,7 +2503,7 @@ dependencies = [ "structopt", "tempdir", "teos-common", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-stream", "toml", "tonic 0.6.2", @@ -3027,17 +2530,6 @@ dependencies = [ "tonic-build", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi 0.3.9", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -3094,15 +2586,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinyvec" version = "1.5.1" @@ -3138,9 +2621,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.20.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes 1.1.0", @@ -3148,12 +2631,12 @@ dependencies = [ "memchr", "mio 0.8.4", "num_cpus", - "once_cell", + "parking_lot 0.12.1", "pin-project-lite 0.2.8", "signal-hook-registry", "socket2 0.4.4", "tokio-macros", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -3163,7 +2646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3184,7 +2667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3194,7 +2677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls", - "tokio 1.20.1", + "tokio 1.25.0", "webpki", ] @@ -3207,7 +2690,7 @@ dependencies = [ "either", "futures-util", "thiserror", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3218,7 +2701,7 @@ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3230,7 +2713,7 @@ dependencies = [ "futures-util", "log", "pin-project", - "tokio 1.20.1", + "tokio 1.25.0", "tungstenite", ] @@ -3259,7 +2742,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3273,7 +2756,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.2.8", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3306,7 +2789,7 @@ dependencies = [ "pin-project", "prost 0.8.0", "prost-derive 0.8.0", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-rustls", "tokio-stream", "tokio-util 0.6.9", @@ -3338,7 +2821,7 @@ dependencies = [ "pin-project", "prost 0.9.0", "prost-derive 0.9.0", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-rustls", "tokio-stream", "tokio-util 0.6.9", @@ -3378,7 +2861,7 @@ dependencies = [ "serde_derive", "sha2", "sha3", - "tokio 1.20.1", + "tokio 1.25.0", ] [[package]] @@ -3394,7 +2877,7 @@ dependencies = [ "pin-project-lite 0.2.8", "rand 0.8.5", "slab", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-util 0.7.0", "tower-layer", "tower-service", @@ -3584,16 +3067,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "value-bag" -version = "1.0.0-alpha.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" -dependencies = [ - "ctor", - "version_check", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -3612,12 +3085,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "want" version = "0.3.0" @@ -3650,7 +3117,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.20.1", + "tokio 1.25.0", "tokio-stream", "tokio-tungstenite", "tokio-util 0.6.9", @@ -3751,15 +3218,15 @@ dependencies = [ "cln-plugin", "hex", "home", - "httpmock", "log", + "mockito", "reqwest", "rusqlite", "serde", "serde_json", "tempdir", "teos-common", - "tokio 1.20.1", + "tokio 1.25.0", "tonic 0.5.2", ] @@ -3783,15 +3250,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "which" version = "4.2.4" diff --git a/README.md b/README.md index 4213e2db..9b5f7548 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -**THIS IS CURRENTLY WIP** - # The Eye of Satoshi (rust-teos) The Eye of Satoshi is a Lightning watchtower compliant with [BOLT13](https://github.com/sr-gi/bolt13), written in Rust. +[![discord](https://img.shields.io/discord/991334710611550208?logo=discord&style=plastic)](https://discord.gg/EyVbrNMDUP) +[![build](https://img.shields.io/github/actions/workflow/status/talaia-labs/rust-teos/build.yaml?logo=github&style=plastic)](https://github.com/talaia-labs/rust-teos/actions/workflows/build.yaml) +[![release](https://img.shields.io/github/v/release/talaia-labs/rust-teos?style=plastic)](https://github.com/talaia-labs/rust-teos/releases/latest) + + `rust-teos` consists of two main crates: - `teos`: including the tower's main functionality (server-side) and a CLI. Compiling this crate will generate two binaries: `teosd` and `teos-cli`. diff --git a/teos-common/src/net/http.rs b/teos-common/src/net/http.rs index 86aa3df8..f982bd98 100644 --- a/teos-common/src/net/http.rs +++ b/teos-common/src/net/http.rs @@ -3,6 +3,7 @@ pub enum Endpoint { AddAppointment, GetAppointment, GetSubscriptionInfo, + Ping, } impl std::fmt::Display for Endpoint { @@ -15,6 +16,7 @@ impl std::fmt::Display for Endpoint { Endpoint::AddAppointment => "add_appointment", Endpoint::GetAppointment => "get_appointment", Endpoint::GetSubscriptionInfo => "get_subscription_info", + Endpoint::Ping => "ping", } ) } diff --git a/teos/src/api/http.rs b/teos/src/api/http.rs index 6fd696cf..fa65ccfb 100644 --- a/teos/src/api/http.rs +++ b/teos/src/api/http.rs @@ -217,6 +217,14 @@ async fn get_subscription_info( Ok(reply::with_status(body, status)) } +async fn ping(addr: Option) -> Result { + log::debug!( + "Received a ping request from {}", + addr.map_or("an unknown address".to_owned(), |a| a.to_string()) + ); + Ok(reply::reply()) +} + fn router( grpc_conn: PublicTowerServicesClient, ) -> impl Filter + Clone { @@ -251,10 +259,16 @@ fn router( .and(with_grpc(grpc_conn)) .and_then(get_subscription_info); + let ping = warp::get() + .and(warp::path(Endpoint::Ping.to_string())) + .and(warp::addr::remote()) + .and_then(ping); + register .or(add_appointment) .or(get_appointment) .or(get_subscription_info) + .or(ping) .recover(handle_rejection) } @@ -290,12 +304,12 @@ async fn handle_rejection(err: Rejection) -> Result { pub async fn serve( http_bind: SocketAddr, - grpc_bind: String, + grpc_bind: SocketAddr, service_ready: Trigger, shutdown_signal: Listener, ) { let grpc_conn = loop { - match PublicTowerServicesClient::connect(grpc_bind.clone()).await { + match PublicTowerServicesClient::connect(format!("http://{grpc_bind}")).await { Ok(conn) => break conn, Err(_) => { log::error!("Cannot connect to the gRPC server. Retrying shortly"); @@ -615,7 +629,7 @@ mod tests_failures { .reply(&router(grpc_conn)) .await; - assert_eq!(res.status(), StatusCode::NOT_FOUND); + assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); } #[tokio::test] diff --git a/teos/src/chain_monitor.rs b/teos/src/chain_monitor.rs index 0119267e..35534013 100644 --- a/teos/src/chain_monitor.rs +++ b/teos/src/chain_monitor.rs @@ -264,10 +264,7 @@ mod tests { // 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!(matches!( - cm.dbm.lock().unwrap().load_last_known_block(), - Err { .. } - )); + 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()); } diff --git a/teos/src/cli.rs b/teos/src/cli.rs index 5c20f00c..dbc345df 100644 --- a/teos/src/cli.rs +++ b/teos/src/cli.rs @@ -27,7 +27,7 @@ async fn main() { let command = opt.command.clone(); // Load conf (from file or defaults) and patch it with the command line parameters received (if any) - let mut conf = config::from_file::(path.join("teos.toml")); + let mut conf = config::from_file::(&path.join("teos.toml")); conf.patch_with_options(opt); let key = fs::read(&path.join("client-key.pem")) @@ -56,8 +56,8 @@ async fn main() { }) .connect() .await - .unwrap_or_else(|e| { - eprintln!("Could not connect to tower: {e:?}"); + .unwrap_or_else(|_| { + eprintln!("Could not connect to tower. Is teosd running?"); std::process::exit(1); }); diff --git a/teos/src/config.rs b/teos/src/config.rs index 1669b25b..5436725f 100644 --- a/teos/src/config.rs +++ b/teos/src/config.rs @@ -1,6 +1,6 @@ //! Logic related to the tower configuration and command line parameter parsing. -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; use structopt::StructOpt; @@ -16,7 +16,7 @@ pub fn data_dir_absolute_path(data_dir: String) -> PathBuf { } } -pub fn from_file(path: PathBuf) -> T { +pub fn from_file(path: &PathBuf) -> T { match std::fs::read(path) { Ok(file_content) => toml::from_slice::(&file_content).map_or_else( |e| { @@ -102,6 +102,11 @@ pub struct Opt { #[structopt(long)] pub tor_support: bool, + /// Forces the tower to run even if the underlying chain has gone too far out of sync. This can only happen + /// if the node is being run in pruned mode. + #[structopt(long)] + pub force_update: bool, + /// Tor control port [default: 9051] #[structopt(long)] pub tor_control_port: Option, @@ -117,7 +122,7 @@ pub struct Opt { /// - Defaults /// - Configuration file /// - Command line options -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] #[serde(default)] pub struct Config { // API @@ -139,6 +144,7 @@ pub struct Config { pub debug: bool, pub deps_debug: bool, pub overwrite_key: bool, + pub force_update: bool, // General pub subscription_slots: u32, @@ -198,6 +204,7 @@ impl Config { self.debug |= options.debug; self.deps_debug |= options.deps_debug; self.overwrite_key = options.overwrite_key; + self.force_update = options.force_update; } /// Verifies that [Config] is properly built. @@ -242,6 +249,27 @@ impl Config { pub fn is_default(&self) -> bool { self == &Config::default() } + + /// Logs non-default options. + 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"]; + + for (key, value) in json_config.as_object().unwrap().iter() { + if *value != json_default_config[key] { + log::info!( + "Custom config arg: {}: {}", + key, + if sensitive_args.contains(&key.as_str()) { + "****".to_owned() + } else { + value.to_string() + } + ); + } + } + } } impl Default for Config { @@ -269,6 +297,7 @@ impl Default for Config { debug: false, deps_debug: false, overwrite_key: false, + force_update: false, subscription_slots: 10000, subscription_duration: 4320, expiry_delta: 6, @@ -304,6 +333,7 @@ mod tests { debug: false, deps_debug: false, overwrite_key: false, + force_update: false, } } } diff --git a/teos/src/dbm.rs b/teos/src/dbm.rs index 5cbdc339..1abc177e 100644 --- a/teos/src/dbm.rs +++ b/teos/src/dbm.rs @@ -286,7 +286,7 @@ impl DBM { } /// Loads an [Appointment] from the database. - pub(crate) fn load_appointment(&self, uuid: UUID) -> Result { + pub(crate) fn load_appointment(&self, uuid: UUID) -> Option { let key = uuid.to_vec(); let mut stmt = self .connection @@ -314,7 +314,7 @@ impl DBM { start_block, )) }) - .map_err(|_| Error::NotFound) + .ok() } /// Check if an appointment with `uuid` exists. @@ -375,14 +375,14 @@ impl DBM { // TODO: Optimize me by storing the appointment length to avoid pulling heavy `encrypted_blob`. /// Gets the length of an appointment (the length of `appointment.encrypted_blob`). - pub(crate) fn get_appointment_length(&self, uuid: UUID) -> Result { + pub(crate) fn get_appointment_length(&self, uuid: UUID) -> Option { let appointment = self.load_appointment(uuid)?; let blob_size = appointment.inner.encrypted_blob.len(); - Ok(blob_size) + Some(blob_size) } /// Gets the [`UserId`] of the owner of the appointment with the passed `uuid`. - pub(crate) fn get_appointment_user(&self, uuid: UUID) -> Result { + pub(crate) fn get_appointment_user(&self, uuid: UUID) -> Option { let mut stmt = self .connection .prepare("SELECT user_id FROM appointments WHERE UUID=(?)") @@ -392,7 +392,7 @@ impl DBM { let raw_userid: Vec = row.get(0).unwrap(); Ok(UserId::from_slice(&raw_userid).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } /// Removes an [Appointment] from the database. @@ -547,7 +547,7 @@ impl DBM { } /// Loads a [TransactionTracker] from the database. - pub(crate) fn load_tracker(&self, uuid: UUID) -> Result { + pub(crate) fn load_tracker(&self, uuid: UUID) -> Option { let key = uuid.to_vec(); let mut stmt = self .connection.prepare( @@ -574,7 +574,7 @@ impl DBM { user_id, }) }) - .map_err(|_| Error::NotFound) + .ok() } pub(crate) fn tracker_exists(&self, uuid: UUID) -> bool { @@ -701,7 +701,7 @@ impl DBM { } /// Loads the last known block from the database. - pub fn load_last_known_block(&self) -> Result { + pub fn load_last_known_block(&self) -> Option { let mut stmt = self .connection .prepare("SELECT block_hash FROM last_known_block WHERE id=0") @@ -711,7 +711,7 @@ impl DBM { let raw_hash: Vec = row.get(0).unwrap(); Ok(BlockHash::from_slice(&raw_hash).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } /// Stores the tower secret key into the database. @@ -726,7 +726,7 @@ impl DBM { /// /// 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) -> Result { + pub fn load_tower_key(&self) -> Option { let mut stmt = self .connection .prepare( @@ -738,7 +738,7 @@ impl DBM { let sk: String = row.get(0).unwrap(); Ok(SecretKey::from_str(&sk).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } } @@ -767,7 +767,7 @@ mod tests { Ok(dbm) } - pub(crate) fn load_user(&self, user_id: UserId) -> Result { + pub(crate) fn load_user(&self, user_id: UserId) -> Option { let key = user_id.to_vec(); let mut stmt = self .connection @@ -776,16 +776,13 @@ mod tests { FROM users WHERE user_id=(?)", ) .unwrap(); - let user = 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)) - }) - .map_err(|_| Error::NotFound)?; - - Ok(user) + 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() } } @@ -819,7 +816,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let user_id = get_random_user_id(); - assert!(matches!(dbm.load_user(user_id), Err(Error::NotFound))); + assert!(dbm.load_user(user_id).is_none()); } #[test] @@ -931,11 +928,8 @@ mod tests { )); dbm.batch_remove_users(&vec![appointment.user_id]); - assert!(matches!( - dbm.load_user(appointment.user_id), - Err(Error::NotFound) - )); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); + assert!(dbm.load_user(appointment.user_id).is_none()); + assert!(dbm.load_appointment(uuid).is_none()); // Appointment + Tracker dbm.store_user(appointment.user_id, &info).unwrap(); @@ -946,12 +940,9 @@ mod tests { assert!(matches!(dbm.store_tracker(uuid, &tracker), Ok { .. })); dbm.batch_remove_users(&vec![appointment.user_id]); - assert!(matches!( - dbm.load_user(appointment.user_id), - Err(Error::NotFound) - )); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!(dbm.load_user(appointment.user_id).is_none()); + assert!(dbm.load_appointment(uuid).is_none()); + assert!(dbm.load_tracker(uuid).is_none()); } #[test] @@ -1029,7 +1020,7 @@ mod tests { dbm.store_appointment(uuid, &appointment), Err(Error::MissingForeignKey) )); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!((dbm.load_tracker(uuid).is_none())); } #[test] @@ -1037,7 +1028,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let uuid = generate_uuid(); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); + assert!(dbm.load_appointment(uuid).is_none()); } #[test] @@ -1194,10 +1185,7 @@ mod tests { dbm.get_appointment_length(uuid).unwrap(), appointment.inner.encrypted_blob.len() ); - assert!(matches!( - dbm.get_appointment_length(generate_uuid()), - Err(Error::NotFound) - )); + assert!(dbm.get_appointment_length(generate_uuid()).is_none()); } #[test] @@ -1212,10 +1200,7 @@ mod tests { dbm.store_appointment(uuid, &appointment).unwrap(); assert_eq!(dbm.get_appointment_user(uuid).unwrap(), user_id); - assert!(matches!( - dbm.get_appointment_user(generate_uuid()), - Err(Error::NotFound) - )); + assert!(dbm.get_appointment_user(generate_uuid()).is_none()); } #[test] @@ -1292,7 +1277,7 @@ mod tests { &vec![uuid], &HashMap::from_iter([(appointment.user_id, info.clone())]), ); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); + assert!(dbm.load_appointment(uuid).is_none()); // Appointment + Tracker assert!(matches!( @@ -1305,8 +1290,8 @@ mod tests { &vec![uuid], &HashMap::from_iter([(appointment.user_id, info)]), ); - assert!(matches!(dbm.load_appointment(uuid), Err(Error::NotFound))); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!(dbm.load_appointment(uuid).is_none()); + assert!(dbm.load_tracker(uuid).is_none()); } #[test] @@ -1490,7 +1475,7 @@ mod tests { let dbm = DBM::in_memory().unwrap(); let uuid = generate_uuid(); - assert!(matches!(dbm.load_tracker(uuid), Err(Error::NotFound))); + assert!(dbm.load_tracker(uuid).is_none()); } #[test] @@ -1681,14 +1666,14 @@ mod tests { fn test_store_load_nonexistent_last_known_block() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!(dbm.load_last_known_block(), Err(Error::NotFound))); + assert!(dbm.load_last_known_block().is_none()); } #[test] fn test_store_load_tower_key() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!(dbm.load_tower_key(), Err(Error::NotFound))); + assert!(dbm.load_tower_key().is_none()); for _ in 0..7 { let sk = get_random_keypair().0; dbm.store_tower_key(&sk).unwrap(); diff --git a/teos/src/gatekeeper.rs b/teos/src/gatekeeper.rs index 4adb168e..5edb3b93 100644 --- a/teos/src/gatekeeper.rs +++ b/teos/src/gatekeeper.rs @@ -330,7 +330,6 @@ mod tests { 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::dbm::Error as DBError; use teos_common::test_utils::get_random_user_id; use crate::responder::ConfirmationStatus; @@ -882,10 +881,7 @@ mod tests { .lock() .unwrap() .contains_key(user_id)); - assert!(matches!( - gatekeeper.dbm.lock().unwrap().load_user(*user_id), - Err(DBError::NotFound) - )); + assert!(gatekeeper.dbm.lock().unwrap().load_user(*user_id).is_none()); } // Check that the last_known_block_header has been properly updated diff --git a/teos/src/main.rs b/teos/src/main.rs index b83aeaec..583d53ab 100644 --- a/teos/src/main.rs +++ b/teos/src/main.rs @@ -11,12 +11,12 @@ use tonic::transport::{Certificate, Server, ServerTlsConfig}; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; -use bitcoincore_rpc::{Auth, Client}; +use bitcoincore_rpc::{Auth, Client, RpcApi}; use lightning_block_sync::init::validate_best_block_header; use lightning_block_sync::poll::{ ChainPoller, Poll, Validate, ValidatedBlock, ValidatedBlockHeader, }; -use lightning_block_sync::{BlockSource, SpvClient, UnboundedCache}; +use lightning_block_sync::{BlockSource, BlockSourceError, SpvClient, UnboundedCache}; use teos::api::internal::InternalAPI; use teos::api::{http, tor::TorAPI}; @@ -41,7 +41,7 @@ async fn get_last_n_blocks( poller: &mut ChainPoller, mut last_known_block: ValidatedBlockHeader, n: usize, -) -> Vec +) -> Result, BlockSourceError> where B: DerefMut + Sized + Send + Sync, T: BlockSource, @@ -49,15 +49,12 @@ where let mut last_n_blocks = Vec::with_capacity(n); for _ in 0..n { log::debug!("Fetching block #{}", last_known_block.height); - let block = poller.fetch_block(&last_known_block).await.unwrap(); - last_known_block = poller - .look_up_previous_header(&last_known_block) - .await - .unwrap(); + let block = poller.fetch_block(&last_known_block).await?; + last_known_block = poller.look_up_previous_header(&last_known_block).await?; last_n_blocks.push(block); } - last_n_blocks + Ok(last_n_blocks) } fn create_new_tower_keypair(db: &DBM) -> (SecretKey, PublicKey) { @@ -70,7 +67,7 @@ fn create_new_tower_keypair(db: &DBM) -> (SecretKey, PublicKey) { async fn main() { let opt = Opt::from_args(); let path = config::data_dir_absolute_path(opt.data_dir.clone()); - + let conf_file_path = path.join("teos.toml"); // Create data dir if it does not exist fs::create_dir_all(&path).unwrap_or_else(|e| { eprintln!("Cannot create data dir: {e:?}"); @@ -78,7 +75,7 @@ async fn main() { }); // Load conf (from file or defaults) and patch it with the command line parameters received (if any) - let mut conf = config::from_file::(path.join("teos.toml")); + let mut conf = config::from_file::(&conf_file_path); let is_default = conf.is_default(); conf.patch_with_options(opt); conf.verify().unwrap_or_else(|e| { @@ -104,18 +101,27 @@ async fn main() { .init() .unwrap(); - if is_default { - log::info!("Loading default configuration") - } else { - log::info!("Loading configuration from file") - } - // Create network dir let path_network = path.join(conf.btc_network.clone()); fs::create_dir_all(&path_network).unwrap_or_else(|e| { eprintln!("Cannot create network dir: {e:?}"); std::process::exit(1); }); + + // Log default data dir + log::info!("Default data directory: {:?}", &path); + + // Log datadir path + log::info!("Using data directory: {:?}", &path_network); + + // Log config file path based on whether the config file is found or not + if is_default { + log::info!("Config file: {:?} (not found, skipping)", &conf_file_path); + } else { + log::info!("Config file: {:?}", &conf_file_path); + conf.log_non_default_options(); + } + let dbm = Arc::new(Mutex::new( DBM::new(path_network.join("teos_db.sql3")).unwrap(), )); @@ -127,14 +133,11 @@ async fn main() { 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() { + (sk, PublicKey::from_secret_key(&Secp256k1::new(), &sk)) } else { - match locked_db.load_tower_key() { - Ok(sk) => (sk, PublicKey::from_secret_key(&Secp256k1::new(), &sk)), - Err(_) => { - log::info!("Tower keys not found. Creating a fresh set"); - create_new_tower_keypair(&locked_db) - } - } + log::info!("Tower keys not found. Creating a fresh set"); + create_new_tower_keypair(&locked_db) } }; log::info!("tower_id: {tower_pk}"); @@ -180,15 +183,54 @@ 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 tip = if let Ok(block_hash) = last_known_block { - derefed + let tip = if let Some(block_hash) = last_known_block { + let mut last_known_header = derefed .get_header(&block_hash, None) .await .unwrap() .validate(block_hash) - .unwrap() + .unwrap(); + + log::info!( + "Last known block: {} (height: {})", + last_known_header.header.block_hash(), + last_known_header.height + ); + + // If we are running in pruned mode some data may be missing (if we happen to have been offline for a while) + if let Some(prune_height) = rpc.get_blockchain_info().unwrap().prune_height { + if last_known_header.height - IRREVOCABLY_RESOLVED + 1 < prune_height as u32 { + log::warn!( + "Cannot load blocks in the range {}-{}. Chain has gone too far out of sync", + last_known_header.height - IRREVOCABLY_RESOLVED + 1, + last_known_header.height + ); + if conf.force_update { + log::info!("Forcing a backend update"); + // We want to grab the first IRREVOCABLY_RESOLVED we know about for the initial cache + // So we can perform transitions from there onwards. + let target_height = prune_height + IRREVOCABLY_RESOLVED as u64; + let target_hash = rpc.get_block_hash(target_height).unwrap(); + last_known_header = derefed + .get_header( + &rpc.get_block_hash(target_height).unwrap(), + Some(target_height as u32), + ) + .await + .unwrap() + .validate(target_hash) + .unwrap(); + } else { + log::error!( + "The underlying chain has gone too far out of sync. The tower block cache cannot be initialized. Run with --forceupdate to force update. THIS WILL, POTENTIALLY, MAKE THE TOWER MISS SOME OF ITS APPOINTMENTS" + ); + std::process::exit(1); + } + } + } + last_known_header } else { - validate_best_block_header(&mut derefed).await.unwrap() + validate_best_block_header(&derefed).await.unwrap() }; // DISCUSS: This is not really required (and only triggered in regtest). This is only in place so the caches can be @@ -203,7 +245,11 @@ async fn main() { std::process::exit(1); } - log::info!("Last known block: {}", tip.header.block_hash()); + log::info!( + "Current chain tip: {} (height: {})", + tip.header.block_hash(), + tip.height + ); // This is how chain poller names bitcoin networks. let btc_network = match conf.btc_network.as_str() { @@ -224,7 +270,13 @@ async fn main() { let mut poller = ChainPoller::new(&mut derefed, Network::from_str(btc_network).unwrap()); let (responder, watcher) = { let last_n_blocks = - get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize).await; + get_last_n_blocks(&mut poller, tip, IRREVOCABLY_RESOLVED as usize).await.unwrap_or_else(|e| { + // I'm pretty sure this can only happen if we are pulling blocks from the target to the prune height, and by the time we get to + // the end at least one has been pruned. + log::error!("Couldn't load the latest {IRREVOCABLY_RESOLVED} blocks. Please try again (Error: {})", e.into_inner()); + std::process::exit(1); + } + ); let responder = Arc::new(Responder::new( &last_n_blocks, @@ -252,7 +304,7 @@ async fn main() { } let (shutdown_trigger, shutdown_signal_rpc_api) = triggered::trigger(); - let shutdown_signal_internal_rpc_api = shutdown_signal_rpc_api.clone(); + let shutdown_signal_internal_api = shutdown_signal_rpc_api.clone(); let shutdown_signal_http = shutdown_signal_rpc_api.clone(); let shutdown_signal_cm = shutdown_signal_rpc_api.clone(); let shutdown_signal_tor = shutdown_signal_rpc_api.clone(); @@ -304,24 +356,20 @@ async fn main() { None }; - let rpc_api = Arc::new(InternalAPI::new( + let internal_api = Arc::new(InternalAPI::new( watcher, addresses, bitcoind_reachable.clone(), shutdown_trigger, )); - let internal_rpc_api = rpc_api.clone(); + let internal_api_cloned = internal_api.clone(); let rpc_api_addr = format!("{}:{}", conf.rpc_bind, conf.rpc_port) .parse() .unwrap(); - let internal_rpc_api_addr = format!("{}:{}", conf.internal_api_bind, conf.internal_api_port) + let internal_api_addr = format!("{}:{}", conf.internal_api_bind, conf.internal_api_port) .parse() .unwrap(); - let internal_rpc_api_uri = format!( - "http://{}:{}", - conf.internal_api_bind, conf.internal_api_port - ); // Generate mtls certificates to data directory so the admin can securely connect // to the server to perform administrative tasks. @@ -339,7 +387,7 @@ async fn main() { Server::builder() .tls_config(tls) .expect("couldn't configure tls") - .add_service(PrivateTowerServicesServer::new(rpc_api)) + .add_service(PrivateTowerServicesServer::new(internal_api)) .serve_with_shutdown(rpc_api_addr, shutdown_signal_rpc_api) .await .unwrap(); @@ -347,8 +395,8 @@ async fn main() { let public_api_task = task::spawn(async move { Server::builder() - .add_service(PublicTowerServicesServer::new(internal_rpc_api)) - .serve_with_shutdown(internal_rpc_api_addr, shutdown_signal_internal_rpc_api) + .add_service(PublicTowerServicesServer::new(internal_api_cloned)) + .serve_with_shutdown(internal_api_addr, shutdown_signal_internal_api) .await .unwrap(); }); @@ -356,7 +404,7 @@ async fn main() { let (http_service_ready, ready_signal_http) = triggered::trigger(); let http_api_task = task::spawn(http::serve( http_api_addr, - internal_rpc_api_uri, + internal_api_addr, http_service_ready, shutdown_signal_http, )); diff --git a/teos/src/responder.rs b/teos/src/responder.rs index bfd76ebb..276b04b9 100644 --- a/teos/src/responder.rs +++ b/teos/src/responder.rs @@ -804,7 +804,7 @@ mod tests { let (user_id, uuid) = responder.store_dummy_appointment_to_db(); // Data should not be there before adding it - assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_err()); + assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); // Data should be there now let breach = get_random_breach(); @@ -825,7 +825,7 @@ 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_err()); + assert!(responder.dbm.lock().unwrap().load_tracker(uuid).is_none()); } #[tokio::test] @@ -1267,7 +1267,7 @@ mod tests { .lock() .unwrap() .load_tracker(tracker.uuid()) - .is_err()); + .is_none()); let (_, user_locators) = responder.gatekeeper.get_user_info(tracker.user_id).unwrap(); assert!(!user_locators.contains(&tracker.locator())); } @@ -1280,7 +1280,7 @@ mod tests { .lock() .unwrap() .load_tracker(tracker.uuid()) - .is_err()); + .is_none()); assert!(responder .gatekeeper .get_user_info(tracker.user_id) diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index e523241c..cfeaf670 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -335,11 +335,11 @@ impl Watcher { let dbm = self.dbm.lock().unwrap(); dbm.load_tracker(uuid) .map(AppointmentInfo::Tracker) - .or_else(|_| { + .or_else(|| { dbm.load_appointment(uuid) .map(|ext_app| AppointmentInfo::Appointment(ext_app.inner)) }) - .map_err(|_| { + .ok_or_else(|| { log::info!("Cannot find {locator}"); GetAppointmentFailure::NotFound }) @@ -476,7 +476,6 @@ impl Watcher { } let (subscription_info, locators) = self.gatekeeper.get_user_info(user_id).unwrap(); - Ok((subscription_info, locators)) } } diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index 199cb8f9..976f8e9d 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -32,5 +32,5 @@ cln-plugin = "0.1.2" teos-common = { path = "../teos-common" } [dev-dependencies] -httpmock = "0.6" +mockito = "0.32.4" tempdir = "0.3.7" \ No newline at end of file diff --git a/watchtower-plugin/README.md b/watchtower-plugin/README.md index 1810653f..0f9ec7ea 100644 --- a/watchtower-plugin/README.md +++ b/watchtower-plugin/README.md @@ -12,6 +12,7 @@ The plugin has the following methods: - `gettowerinfo `: gets all the locally stored data about a given tower. - `retrytower `: tries to send pending appointment to a (previously) unreachable tower. - `abandontower `: deletes all data associated with a given tower. +- `pingtower `: Polls the tower to check if it is online. - `listtowers`: lists all registered towers. - `getappointment `: queries a given tower about an appointment. - `getsubscriptioninfo `: gets the subscription information by querying the tower. diff --git a/watchtower-plugin/src/constants.rs b/watchtower-plugin/src/constants.rs index 1b14803d..90c50d8b 100644 --- a/watchtower-plugin/src/constants.rs +++ b/watchtower-plugin/src/constants.rs @@ -44,6 +44,8 @@ pub const RPC_RETRY_TOWER_DESC: &str = "Retries to send pending appointment to an unreachable tower"; pub const RPC_ABANDON_TOWER: &str = "abandontower"; pub const RPC_ABANDON_TOWER_DESC: &str = "Forgets about a tower and wipes all local data"; +pub const RPC_PING: &str = "pingtower"; +pub const RPC_PING_DESC: &str = "Polls the tower to check if it is online"; /// Collections of hook names diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index ec03886a..bf271bb8 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -125,7 +125,7 @@ impl DBM { /// /// 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_client_key(&self) -> Result { + pub fn load_client_key(&self) -> Option { let mut stmt = self .connection .prepare( @@ -137,7 +137,7 @@ impl DBM { let sk: String = row.get(0).unwrap(); Ok(SecretKey::from_str(&sk).unwrap()) }) - .map_err(|_| Error::NotFound) + .ok() } /// Stores a tower record into the database alongside the corresponding registration receipt. @@ -171,7 +171,7 @@ impl DBM { /// Tower records are composed from the tower information and the appointment data. The latter is split in: /// accepted appointments (represented by appointment receipts), pending appointments and invalid appointments. /// In the case that the tower has misbehaved, then a misbehaving proof is also attached to the record. - pub fn load_tower_record(&self, tower_id: TowerId) -> Result { + pub fn load_tower_record(&self, tower_id: TowerId) -> Option { let mut stmt = self .connection .prepare("SELECT t.net_addr, t.available_slots, r.subscription_start, r.subscription_expiry @@ -197,16 +197,16 @@ impl DBM { self.load_appointments(tower_id, AppointmentStatus::Invalid), )) }) - .map_err(|_| Error::NotFound)?; + .ok()?; - if let Ok(proof) = self.load_misbehaving_proof(tower_id) { + if let Some(proof) = self.load_misbehaving_proof(tower_id) { tower.status = TowerStatus::Misbehaving; tower.set_misbehaving_proof(proof); } else if !tower.pending_appointments.is_empty() { tower.status = TowerStatus::TemporaryUnreachable; } - Ok(tower) + Some(tower) } /// Loads the latest registration receipt for a given tower. @@ -216,7 +216,7 @@ impl DBM { &self, tower_id: TowerId, user_id: UserId, - ) -> Result { + ) -> Option { let mut stmt = self .connection .prepare( @@ -228,20 +228,17 @@ impl DBM { ) .unwrap(); - let receipt = stmt - .query_row([tower_id.to_vec()], |row| { - let slots: u32 = row.get(0).unwrap(); - let start: u32 = row.get(1).unwrap(); - let expiry: u32 = row.get(2).unwrap(); - let signature: String = row.get(3).unwrap(); + stmt.query_row([tower_id.to_vec()], |row| { + let slots: u32 = row.get(0).unwrap(); + let start: u32 = row.get(1).unwrap(); + let expiry: u32 = row.get(2).unwrap(); + let signature: String = row.get(3).unwrap(); - Ok(RegistrationReceipt::with_signature( - user_id, slots, start, expiry, signature, - )) - }) - .map_err(|_| Error::NotFound)?; - - Ok(receipt) + Ok(RegistrationReceipt::with_signature( + user_id, slots, start, expiry, signature, + )) + }) + .ok() } /// Removes a tower record from the database. @@ -333,7 +330,7 @@ impl DBM { &self, tower_id: TowerId, locator: Locator, - ) -> Result { + ) -> Option { let mut stmt = self .connection .prepare("SELECT start_block, user_signature, tower_signature FROM appointment_receipts WHERE tower_id = ?1 and locator = ?2") @@ -350,7 +347,7 @@ impl DBM { tower_sig, )) }) - .map_err(|_| Error::NotFound) + .ok() } /// Loads the appointment receipts associated to a given tower. @@ -406,7 +403,7 @@ impl DBM { } /// Loads an appointment from the database. - pub fn load_appointment(&self, locator: Locator) -> Result { + pub fn load_appointment(&self, locator: Locator) -> Option { let mut stmt = self .connection .prepare("SELECT encrypted_blob, to_self_delay FROM appointments WHERE locator = ?") @@ -418,7 +415,7 @@ impl DBM { Ok(Appointment::new(locator, encrypted_blob, to_self_delay)) }) - .map_err(|_| Error::NotFound) + .ok() } /// Stores an appointment into the database. @@ -598,7 +595,7 @@ impl DBM { } /// Loads the misbehaving proof for a given tower from the database (if found). - fn load_misbehaving_proof(&self, tower_id: TowerId) -> Result { + fn load_misbehaving_proof(&self, tower_id: TowerId) -> Option { let mut misbehaving_stmt = self .connection .prepare("SELECT locator, recovered_id FROM misbehaving_proofs WHERE tower_id = ?") @@ -633,7 +630,7 @@ impl DBM { .unwrap(); MisbehaviorProof::new(locator, receipt, recovered_id) }) - .map_err(|_| Error::NotFound) + .ok() } /// Checks whether a misbehaving proof exists for a given tower. @@ -792,10 +789,7 @@ mod tests { // If the tower does not exists, `load_tower` will fail. let tower_id = get_random_user_id(); - assert!(matches!( - dbm.load_tower_record(tower_id), - Err(Error::NotFound) - )); + assert!(dbm.load_tower_record(tower_id).is_none()); } #[test] @@ -917,10 +911,9 @@ mod tests { // If there is no appointment receipt for the given (locator, tower_id) pair, Error::NotFound is returned // Try first with both being unknown - assert!(matches!( - dbm.load_appointment_receipt(tower_id, appointment.locator), - Err(Error::NotFound) - )); + assert!(dbm + .load_appointment_receipt(tower_id, appointment.locator) + .is_none()); // Add the tower but not the appointment and try again let net_addr = "talaia.watch"; @@ -928,10 +921,9 @@ mod tests { dbm.store_tower_record(tower_id, net_addr, &receipt) .unwrap(); - assert!(matches!( - dbm.load_appointment_receipt(tower_id, appointment.locator), - Err(Error::NotFound) - )); + assert!(dbm + .load_appointment_receipt(tower_id, appointment.locator) + .is_none()); // Add both let tower_summary = TowerSummary::new( @@ -1045,7 +1037,7 @@ mod tests { let locator = generate_random_appointment(None).locator; let loaded_appointment = dbm.load_appointment(locator); - assert!(matches!(loaded_appointment, Err(Error::NotFound))); + assert!(loaded_appointment.is_none()); } #[test] @@ -1284,10 +1276,7 @@ mod tests { #[test] fn test_store_load_non_existing_misbehaving_proof() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!( - dbm.load_misbehaving_proof(get_random_user_id()), - Err(Error::NotFound) - )); + assert!(dbm.load_misbehaving_proof(get_random_user_id()).is_none()); } #[test] @@ -1340,7 +1329,7 @@ mod tests { fn test_store_load_client_key() { let dbm = DBM::in_memory().unwrap(); - assert!(matches!(dbm.load_client_key(), Err(Error::NotFound))); + assert!(dbm.load_client_key().is_none()); for _ in 0..7 { let sk = get_random_keypair().0; dbm.store_client_key(&sk).unwrap(); diff --git a/watchtower-plugin/src/main.rs b/watchtower-plugin/src/main.rs index f7c0fbf4..5778e8d0 100755 --- a/watchtower-plugin/src/main.rs +++ b/watchtower-plugin/src/main.rs @@ -20,7 +20,8 @@ use teos_common::{cryptography, errors}; use watchtower_plugin::convert::{CommitmentRevocation, GetAppointmentParams, RegisterParams}; use watchtower_plugin::net::http::{ - self, post_request, process_post_response, AddAppointmentError, ApiResponse, RequestError, + self, get_request, post_request, process_post_response, AddAppointmentError, ApiResponse, + RequestError, }; use watchtower_plugin::net::ProxyInfo; use watchtower_plugin::retrier::RetryManager; @@ -136,11 +137,13 @@ async fn get_registration_receipt( let tower_id = TowerId::try_from(v).map_err(|x| anyhow!(x))?; let state = plugin.state().lock().unwrap(); - let response = state.get_registration_receipt(tower_id).map_err(|_| { - anyhow!("Cannot find {tower_id} within the known towers. Have you registered?") - })?; - - Ok(json!(response)) + if let Some(response) = state.get_registration_receipt(tower_id) { + Ok(json!(response)) + } else { + Err(anyhow!( + "Cannot find {tower_id} within the known towers. Have you registered?" + )) + } } /// Gets the subscription information directly form the tower. @@ -244,24 +247,20 @@ async fn get_appointment_receipt( let params = GetAppointmentParams::try_from(v).map_err(|x| anyhow!(x))?; let state = plugin.state().lock().unwrap(); - let response = state - .get_appointment_receipt(params.tower_id, params.locator) - .map_err(|_| { - if state.towers.contains_key(¶ms.tower_id) { - anyhow!( - "Cannot find {} within {}. Did you send that appointment?", - params.locator, - params.tower_id - ) - } else { - anyhow!( - "Cannot find {} within the known towers. Have you registered?", - params.tower_id - ) - } - })?; - - Ok(json!(response)) + if let Some(r) = state.get_appointment_receipt(params.tower_id, params.locator) { + Ok(json!(r)) + } else if state.towers.contains_key(¶ms.tower_id) { + Err(anyhow!( + "Cannot find {} within {}. Did you send that appointment?", + params.locator, + params.tower_id + )) + } else { + Err(anyhow!( + "Cannot find {} within the known towers. Have you registered?", + params.tower_id + )) + } } /// Lists all the registered towers. @@ -283,15 +282,50 @@ async fn get_tower_info( ) -> Result { let state = plugin.state().lock().unwrap(); let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; - let tower_info = state.load_tower_info(tower_id).map_err(|_| { - anyhow!("Cannot find {tower_id} within the known towers. Have you registered?") - })?; - // Notice we need to check the status in memory since we cannot distinguish between unreachable and temporary unreachable - // by just checking the data in the database. - Ok(json!( - tower_info.with_status(state.get_tower_status(&tower_id).unwrap()) - )) + if let Some(tower_info) = state.load_tower_info(tower_id) { + // Notice we need to check the status in memory since we cannot distinguish between unreachable and temporary unreachable + // by just checking the data in the database. + Ok(json!( + tower_info.with_status(state.get_tower_status(&tower_id).unwrap()) + )) + } else { + Err(anyhow!( + "Cannot find {tower_id} within the known towers. Have you registered?", + )) + } +} + +async fn ping( + plugin: Plugin>>, + v: serde_json::Value, +) -> Result { + let (tower_net_addr, proxy) = { + // Check if the tower_id is known to the plugin + let tower_id = TowerId::try_from(v).map_err(|e| anyhow!(e))?; + let state = plugin.state().lock().unwrap(); + ( + state + .towers + .get(&tower_id) + .ok_or(anyhow!("Unknown tower_id"))? + .net_addr + .clone(), + state.proxy.clone(), + ) + }; + let response = get_request(&tower_net_addr, Endpoint::Ping, &proxy) + .await + .map_err(to_cln_error)?; + + if response.status().is_success() { + Ok(json!("Tower is reachable")) + } else { + Err(anyhow!(format!( + "Tower cannot be reached (Error: {})", + response.status() + ))) + } } /// Triggers a manual retry of a tower, tries to send all pending appointments to it. @@ -555,6 +589,7 @@ async fn main() -> Result<(), Error> { constants::RPC_GET_TOWER_INFO_DESC, get_tower_info, ) + .rpcmethod(constants::RPC_PING, constants::RPC_PING_DESC, ping) .rpcmethod( constants::RPC_RETRY_TOWER, constants::RPC_RETRY_TOWER_DESC, diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 23448a59..1a93633d 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -1,4 +1,4 @@ -use reqwest::Response; +use reqwest::{Method, Response}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use teos_common::appointment::Appointment; @@ -154,12 +154,13 @@ pub async fn send_appointment( } } -/// Generic function to post different types of requests to the tower. -pub async fn post_request( +/// A generic function to send a request to a tower. +async fn request( tower_net_addr: &NetAddr, endpoint: Endpoint, - data: S, proxy: &Option, + method: Method, + data: Option, ) -> Result { let client = if let Some(proxy) = proxy { if proxy.always_use || tower_net_addr.is_onion() { @@ -183,23 +184,42 @@ pub async fn post_request( reqwest::Client::new() }; - client - .post(format!("{}{}", tower_net_addr.net_addr(), endpoint.path())) - .json(&data) - .send() - .await - .map_err(|e| { - log::debug!("An error ocurred when sending data to the tower: {e}"); - if e.is_connect() | e.is_timeout() { - RequestError::ConnectionError( - "Cannot connect to the tower. Connection refused".to_owned(), - ) - } else { - RequestError::Unexpected( - "Unexpected error ocurred (see logs for more info)".to_owned(), - ) - } - }) + let mut request_builder = client.request( + method, + format!("{}{}", tower_net_addr.net_addr(), endpoint.path()), + ); + + if let Some(data) = data { + request_builder = request_builder.json(&data); + } + + request_builder.send().await.map_err(|e| { + log::debug!("An error ocurred when sending data to the tower: {e}"); + if e.is_connect() | e.is_timeout() { + RequestError::ConnectionError( + "Cannot connect to the tower. Connection refused".to_owned(), + ) + } else { + RequestError::Unexpected("Unexpected error ocurred (see logs for more info)".to_owned()) + } + }) +} + +pub async fn post_request( + tower_net_addr: &NetAddr, + endpoint: Endpoint, + data: S, + proxy: &Option, +) -> Result { + request(tower_net_addr, endpoint, proxy, Method::POST, Some(data)).await +} + +pub async fn get_request( + tower_net_addr: &NetAddr, + endpoint: Endpoint, + proxy: &Option, +) -> Result { + request::<()>(tower_net_addr, endpoint, proxy, Method::GET, None).await } /// Generic function to process the response of a given post request. @@ -218,7 +238,6 @@ pub async fn process_post_response( #[cfg(test)] mod tests { use super::*; - use httpmock::prelude::*; use serde_json::json; use crate::test_utils::get_dummy_add_appointment_response; @@ -253,24 +272,25 @@ mod tests { let mut registration_receipt = get_random_registration_receipt(); registration_receipt.sign(&tower_sk); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::Register.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(registration_receipt)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(registration_receipt).to_string()) + .create_async() + .await; let receipt = register( TowerId(tower_pk), registration_receipt.user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, ) .await .unwrap(); - api_mock.assert(); + api_mock.assert_async().await; assert_eq!(receipt, registration_receipt); } @@ -290,24 +310,25 @@ mod tests { #[tokio::test] async fn test_register_deserialize_error() { - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::Register.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!([])); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!([]).to_string()) + .create_async() + .await; let error = register( get_random_user_id(), get_random_user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, ) .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; assert!(matches!(error, RequestError::DeserializeError { .. })) } @@ -322,17 +343,18 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; let (response, receipt) = add_appointment( TowerId(tower_pk), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &appointment, appointment_receipt.user_signature(), @@ -340,7 +362,7 @@ mod tests { .await .unwrap(); - api_mock.assert(); + api_mock.assert_async().await; assert_eq!(response, add_appointment_response.available_slots); assert_eq!(receipt, appointment_receipt); } @@ -354,17 +376,18 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; let (response, receipt) = send_appointment( TowerId(tower_pk), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &appointment, appointment_receipt.user_signature(), @@ -372,7 +395,7 @@ mod tests { .await .unwrap(); - api_mock.assert(); + api_mock.assert_async().await; assert_eq!(response, add_appointment_response); assert_eq!(receipt, appointment_receipt); } @@ -386,18 +409,19 @@ mod tests { let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &appointment_receipt); - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; let tower_id = get_random_user_id(); let error = send_appointment( tower_id, - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &appointment, appointment_receipt.user_signature(), @@ -405,7 +429,7 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; if let AddAppointmentError::SignatureError(proof) = error { assert_eq!( MisbehaviorProof::new( @@ -441,17 +465,18 @@ mod tests { #[tokio::test] async fn test_send_appointment_deserialize_error() { - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!([])); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!([]).to_string()) + .create_async() + .await; let error = send_appointment( get_random_user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &generate_random_appointment(None), "user_sig", @@ -459,7 +484,7 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; if let AddAppointmentError::RequestError(e) = error { assert!(matches!(e, RequestError::DeserializeError { .. })) } else { @@ -474,17 +499,18 @@ mod tests { error_code: 1, }; - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .header("content-type", "application/json") - .json_body(json!(api_error)); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body(json!(api_error).to_string()) + .create_async() + .await; let error = send_appointment( get_random_user_id(), - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), &None, &generate_random_appointment(None), "user_sig", @@ -492,60 +518,160 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; assert!(matches!(error, AddAppointmentError::ApiError { .. })); } + #[tokio::test] + async fn test_request() { + let mut server = mockito::Server::new_async().await; + + // Test with POST + let api_mock_post = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; + + let response_post = request( + &NetAddr::new(server.url()), + Endpoint::Register, + &None, + Method::POST, + Some(json!("")), + ) + .await; + + api_mock_post.assert_async().await; + assert!(matches!(response_post, Ok(Response { .. }))); + + // Test with GET + let api_mock_get = server + .mock("GET", Endpoint::Ping.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; + + let response_get = request::<()>( + &NetAddr::new(server.url()), + Endpoint::Ping, + &None, + Method::GET, + None, + ) + .await; + + api_mock_get.assert_async().await; + assert!(matches!(response_get, Ok(Response { .. }))); + } + + #[tokio::test] + async fn test_request_connection_error() { + assert!(request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Register, + &None, + Method::POST, + Some(json!("")), + ) + .await + .unwrap_err() + .is_connection()); + + assert!(request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Ping, + &None, + Method::GET, + None::<&str>, + ) + .await + .unwrap_err() + .is_connection()); + } + + #[tokio::test] + async fn test_get_request() { + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("GET", Endpoint::Ping.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; + let response = get_request(&NetAddr::new(server.url()), Endpoint::Ping, &None).await; + + api_mock.assert_async().await; + + assert!(matches!(response, Ok(Response { .. }))); + } + + #[tokio::test] + async fn test_get_request_connection_error() { + assert!(get_request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Ping, + &None, + ) + .await + .unwrap_err() + .is_connection()); + } + #[tokio::test] async fn test_post_request() { - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST); - then.status(200).header("content-type", "application/json"); - }); + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::Register.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; let response = post_request( - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), Endpoint::Register, json!(""), &None, ) - .await - .unwrap(); + .await; - api_mock.assert(); - assert!(matches!(response, Response { .. })); + api_mock.assert_async().await; + assert!(matches!(response, Ok(Response { .. }))); } #[tokio::test] async fn test_post_request_connection_error() { - assert!(matches!( - post_request( - &NetAddr::new("http://unreachable_url".to_owned()), - Endpoint::Register, - json!(""), - &None, - ) - .await - .unwrap_err(), - RequestError::ConnectionError { .. } - )); + assert!(post_request( + &NetAddr::new("http://unreachable_url".to_owned()), + Endpoint::Register, + json!(""), + &None, + ) + .await + .unwrap_err() + .is_connection()); } #[tokio::test] async fn test_process_post_response_json_error() { // `process_post_response` is a pass-trough function that maps json deserialization errors from `post_request`. // So just testing that specific case should be enough. - let server = MockServer::start(); - let api_mock = server.mock(|when, then| { - when.method(POST); - then.status(200).header("content-type", "application/json"); - }); + + let mut server = mockito::Server::new_async().await; + let api_mock = server + .mock("POST", Endpoint::GetAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .create_async() + .await; // Any expected response work here as long as it cannot be properly deserialized let error = process_post_response::>( post_request( - &NetAddr::new(server.base_url()), + &NetAddr::new(server.url()), Endpoint::GetAppointment, json!(""), &None, @@ -555,7 +681,7 @@ mod tests { .await .unwrap_err(); - api_mock.assert(); + api_mock.assert_async().await; assert!(matches!(error, RequestError::DeserializeError { .. })); } } diff --git a/watchtower-plugin/src/retrier.rs b/watchtower-plugin/src/retrier.rs index b980a403..0920e366 100644 --- a/watchtower-plugin/src/retrier.rs +++ b/watchtower-plugin/src/retrier.rs @@ -576,13 +576,13 @@ impl Retrier { mod tests { use super::*; - use httpmock::prelude::*; use serde_json::json; use tempdir::TempDir; use tokio::sync::mpsc::unbounded_channel; use teos_common::errors; use teos_common::net::http::Endpoint; + use teos_common::protos::AddAppointmentRequest; use teos_common::receipts::{AppointmentReceipt, RegistrationReceipt}; use teos_common::test_utils::{ generate_random_appointment, get_random_registration_receipt, get_random_user_id, @@ -600,6 +600,18 @@ mod tests { const MAX_INTERVAL_TIME: u16 = 1; const MAX_RUN_TIME: f64 = 0.2; + macro_rules! wait_until { + () => {}; + ($cond:expr $(,)?) => { + loop { + if $cond { + break; + } + tokio::time::sleep(Duration::from_secs_f64(0.1)).await; + } + }; + } + impl Retrier { fn empty(wt_client: Arc>, tower_id: TowerId) -> Self { Self { @@ -612,15 +624,14 @@ mod tests { } #[tokio::test] - // TODO: It'll be nice to toggle the mock on and off instead of having it always on. Not sure MockServer allows that though: - // https://github.com/alexliesenfeld/httpmock/issues/67 async fn test_manage_retry_reachable() { let tmp_path = TempDir::new(&format!("watchtower_{}", get_random_user_id())).unwrap(); let (tx, rx) = unbounded_channel(); let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -629,7 +640,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -647,13 +658,17 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |_| { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(add_appointment_response).to_string().into() + }) + .create_async() + .await; // Start the task and send the tower to the channel for retry tx.send((tower_id, RevocationData::Fresh(appointment.locator))) @@ -681,23 +696,23 @@ mod tests { .unwrap() .is_running()); - // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; - - let state = wt_client.lock().unwrap(); - assert_eq!( - state.get_tower_status(&tower_id).unwrap(), - TowerStatus::Reachable - ); - assert!(!state.retriers.contains_key(&tower_id)); - assert!(!state - .towers - .get(&tower_id) + wait_until!(wt_client + .lock() .unwrap() - .pending_appointments - .contains(&appointment.locator)); + .get_retrier_status(&tower_id) + .is_none()); - api_mock.assert(); + { + let state = wt_client.lock().unwrap(); + assert!(state.get_tower_status(&tower_id).unwrap().is_reachable()); + assert!(!state + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .contains(&appointment.locator)); + } + api_mock.assert_async().await; task.abort(); } @@ -760,25 +775,23 @@ mod tests { .is_running()); // Wait until the task gives up and check again (this gives up due to accumulation of transient errors, so the retiers will be idle). - // Notice we'd normally wait for MAX_ELAPSED_TIME + MAX_RUN_TIME (the maximum time a Retrier can be working plus the marginal time of the last retry). - // However, we've already waited for MAX_RUN_TIME right before to check the tower was temporary unreachable, so we don't need to account for that again. - tokio::time::sleep(Duration::from_secs(MAX_ELAPSED_TIME as u64)).await; - assert!(wt_client + wait_until!(wt_client .lock() .unwrap() - .get_tower_status(&tower_id) + .get_retrier_status(&tower_id) .unwrap() - .is_unreachable()); + .is_idle()); + assert!(wt_client .lock() .unwrap() - .get_retrier_status(&tower_id) + .get_tower_status(&tower_id) .unwrap() - .is_idle()); + .is_unreachable()); // Add a proper server and check that the auto-retry works // Prepare the mock response - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; let mut add_appointment_receipt = AppointmentReceipt::new( cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), 42, @@ -786,12 +799,13 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; // Update the tower details wt_client @@ -799,7 +813,7 @@ mod tests { .unwrap() .add_update_tower( tower_id, - &server.base_url(), + &server.url(), &get_registration_receipt_from_previous(&receipt), ) .unwrap(); @@ -824,8 +838,7 @@ mod tests { .pending_appointments .contains(&appointment.locator)); assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); - - api_mock.assert(); + api_mock.assert_async().await; task.abort(); } @@ -837,7 +850,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (_, tower_pk) = cryptography::get_random_keypair(); @@ -846,7 +859,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -857,16 +870,21 @@ mod tests { .add_pending_appointment(tower_id, &appointment); // Prepare the mock response - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(ApiError { + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body_from_request(|_| { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(ApiError { error: "error_msg".to_owned(), error_code: 1, - })); - }); + }) + .to_string() + .into() + }) + .create_async() + .await; // Start the task and send the tower to the channel for retry tx.send((tower_id, RevocationData::Fresh(appointment.locator))) @@ -895,16 +913,18 @@ mod tests { .is_running()); // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; - assert_eq!( - wt_client - .lock() - .unwrap() - .get_tower_status(&tower_id) - .unwrap(), - TowerStatus::Reachable - ); - assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); + + assert!(wt_client + .lock() + .unwrap() + .get_tower_status(&tower_id) + .unwrap() + .is_reachable()); assert!(!wt_client .lock() .unwrap() @@ -921,7 +941,7 @@ mod tests { .unwrap() .invalid_appointments .contains(&appointment.locator)); - api_mock.assert(); + api_mock.assert_async().await; task.abort(); } @@ -933,7 +953,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (_, tower_pk) = cryptography::get_random_keypair(); @@ -942,7 +962,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -961,13 +981,16 @@ mod tests { add_appointment_receipt.sign(&cryptography::get_random_keypair().0); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |_| { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(add_appointment_response).to_string().into() + }) + .create_async() + .await; // Start the task and send the tower to the channel for retry tx.send((tower_id, RevocationData::Fresh(appointment.locator))) @@ -995,19 +1018,21 @@ mod tests { .unwrap() .is_running()); - // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(HALF_API_DELAY + MAX_RUN_TIME)).await; + // Wait until the tower is no longer being retried. + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); + + // The tower should have a misbehaving status. assert!(wt_client .lock() .unwrap() .get_tower_status(&tower_id) .unwrap() .is_misbehaving()); - - // Retriers are wiped every polling interval, so we'll need to wait a bit more to check it - tokio::time::sleep(Duration::from_secs(POLLING_TIME)).await; - assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); - api_mock.assert(); + api_mock.assert_async().await; task.abort(); } @@ -1019,7 +1044,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let server = mockito::Server::new_async().await; // Add a tower with pending appointments let (_, tower_pk) = cryptography::get_random_keypair(); @@ -1028,7 +1053,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Remove the tower (to simulate it has been abandoned) @@ -1049,7 +1074,6 @@ mod tests { .manage_retry() .await }); - assert!(!wt_client.lock().unwrap().towers.contains_key(&tower_id)); task.abort(); @@ -1062,7 +1086,7 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), tx.clone()).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // Add a tower with pending appointments let (tower_sk, tower_pk) = cryptography::get_random_keypair(); @@ -1073,7 +1097,7 @@ mod tests { wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), ®istration_receipt) + .add_update_tower(tower_id, &server.url(), ®istration_receipt) .unwrap(); // Add appointment to pending @@ -1083,7 +1107,11 @@ mod tests { .unwrap() .add_pending_appointment(tower_id, &appointment); - // Mock the add_appointment response (this is right, so after the re-registration the appointments are accepted) + // Mock the registration and add_appointment response (this is right, so after the re-registration the appointments are accepted) + let mut re_registration_receipt = + get_registration_receipt_from_previous(®istration_receipt); + re_registration_receipt.sign(&tower_sk); + let mut add_appointment_receipt = AppointmentReceipt::new( cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), 42, @@ -1091,24 +1119,25 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let add_appointment_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); - // Mock the re-registration - let mut re_registration_receipt = - get_registration_receipt_from_previous(®istration_receipt); - re_registration_receipt.sign(&tower_sk); - let register_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::Register.path()); - then.status(200) - .delay(Duration::from_secs_f64(API_DELAY)) - .header("content-type", "application/json") - .json_body(json!(re_registration_receipt)); - }); + let api_mock = server + .mock("POST", mockito::Matcher::Any) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |request| { + let response = if request.path() == Endpoint::Register.path().as_str() { + std::thread::sleep(Duration::from_secs_f64(API_DELAY)); + json!(re_registration_receipt).to_string() + } else if request.path() == Endpoint::AddAppointment.path().as_str() { + json!(add_appointment_response).to_string() + } else { + panic!("Wrong endpoint hit") + }; + response.into() + }) + .create_async() + .await + .expect(2); // Set the status as SubscriptionError so we simulate the retrier faced this in a previous round wt_client @@ -1143,16 +1172,20 @@ mod tests { .is_running()); // Wait for the remaining time and re-check - tokio::time::sleep(Duration::from_secs_f64(MAX_RUN_TIME + HALF_API_DELAY)).await; - let state = wt_client.lock().unwrap(); - assert!(!state.retriers.contains_key(&tower_id)); + wait_until!(wt_client + .lock() + .unwrap() + .get_retrier_status(&tower_id) + .is_none()); - let tower = state.towers.get(&tower_id).unwrap(); - assert!(tower.status.is_reachable()); - assert!(tower.pending_appointments.is_empty()); + { + let state = wt_client.lock().unwrap(); + let tower = state.towers.get(&tower_id).unwrap(); + assert!(tower.status.is_reachable()); + assert!(tower.pending_appointments.is_empty()); + } + api_mock.assert_async().await; - register_mock.assert(); - add_appointment_mock.assert(); task.abort(); } @@ -1216,12 +1249,12 @@ mod tests { // With the retrier idling all fresh data sent to it will be stored but it won't trigger a retry. // (we can check the data was stored later on) - let new_appointment = generate_random_appointment(None); + let appointment2 = generate_random_appointment(None); wt_client .lock() .unwrap() - .add_pending_appointment(tower_id, &new_appointment); - tx.send((tower_id, RevocationData::Fresh(new_appointment.locator))) + .add_pending_appointment(tower_id, &appointment2); + tx.send((tower_id, RevocationData::Fresh(appointment2.locator))) .unwrap(); { @@ -1232,22 +1265,40 @@ mod tests { assert_eq!(tower.status, TowerStatus::Unreachable); } - let mut add_appointment_receipt = AppointmentReceipt::new( + // Create the receipts, the responses and set the mocks + let mut appointment_receipt = AppointmentReceipt::new( cryptography::sign(&appointment.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), 42, ); + let mut appointment2_receipt = AppointmentReceipt::new( + cryptography::sign(&appointment2.to_vec(), &wt_client.lock().unwrap().user_sk).unwrap(), + 42, + ); + appointment_receipt.sign(&tower_sk); + appointment2_receipt.sign(&tower_sk); // Mock a proper response - let server = MockServer::start(); - add_appointment_receipt.sign(&tower_sk); - let add_appointment_response = - get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let mut server = mockito::Server::new_async().await; + + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body_from_request(move |request| { + let body = serde_json::from_slice::(request.body().unwrap()) + .unwrap(); + + let response = if body.appointment.unwrap().locator == appointment.locator.to_vec() + { + get_dummy_add_appointment_response(appointment.locator, &appointment_receipt) + } else { + get_dummy_add_appointment_response(appointment2.locator, &appointment2_receipt) + }; + json!(response).to_string().into() + }) + .expect(2) + .create_async() + .await; // Patch the tower address wt_client @@ -1256,7 +1307,7 @@ mod tests { .towers .get_mut(&tower_id) .unwrap() - .set_net_addr(server.base_url()); + .set_net_addr(server.url()); // Check pending data is still there now, and is it not once the retrier succeeds assert_eq!( @@ -1274,24 +1325,19 @@ mod tests { // Send a retry flag to the retrier to force a retry. tx.send((tower_id, RevocationData::None)).unwrap(); + // After retrying the pending pool has been emptied, meaning that both appointments went trough tokio::time::sleep(Duration::from_secs_f64(POLLING_TIME as f64 + MAX_RUN_TIME)).await; - // FIXME: Here we should be able to check this, however, due to httpmock limitations, we cannot return a response based on the request. - // Therefore, both requests will be responded with the same data. Given pending_appointments is a HashSet, we cannot even know which request - // will be sent first (sets are initialized with a random state, which decided the order or iteration). - // https://github.com/alexliesenfeld/httpmock/issues/49 - // assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); - // assert!(wt_client - // .lock() - // .unwrap() - // .towers - // .get(&tower_id) - // .unwrap() - // .pending_appointments - // .is_empty()); - - // This is not much tbh, but looks like its the best we can do at the moment without experiencing random errors. - // Depending on what appointment is sent first the api will be hit either one or two times. - assert!(api_mock.hits() >= 1 && api_mock.hits() <= 2); + assert!(!wt_client.lock().unwrap().retriers.contains_key(&tower_id)); + assert!(wt_client + .lock() + .unwrap() + .towers + .get(&tower_id) + .unwrap() + .pending_appointments + .is_empty()); + api_mock.assert_async().await; + task.abort(); } @@ -1303,14 +1349,14 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -1328,18 +1374,19 @@ mod tests { add_appointment_receipt.sign(&tower_sk); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; // Since we are retrying manually, we need to add the data to pending appointments manually too let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); let r = retrier.run().await; assert_eq!(r, Ok(())); - api_mock.assert(); + api_mock.assert_async().await; } #[tokio::test] @@ -1350,14 +1397,14 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // If there are no pending appointments the method will simply return @@ -1373,14 +1420,14 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); // Add appointment to pending @@ -1398,12 +1445,13 @@ mod tests { add_appointment_receipt.sign(&cryptography::get_random_keypair().0); let add_appointment_response = get_dummy_add_appointment_response(appointment.locator, &add_appointment_receipt); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(200) - .header("content-type", "application/json") - .json_body(json!(add_appointment_response)); - }); + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(json!(add_appointment_response).to_string()) + .create_async() + .await; // Since we are retrying manually, we need to add the data to pending appointments manually too let retrier = Retrier::new(wt_client, tower_id, HashSet::from([appointment.locator])); @@ -1412,7 +1460,7 @@ mod tests { r, Err(Error::Permanent(RetryError::Misbehaving { .. },)) )); - api_mock.assert(); + api_mock.assert_async().await; } #[tokio::test] @@ -1454,25 +1502,29 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .header("content-type", "application/json") - .json_body(json!(ApiError { + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body( + json!(ApiError { error: "error_msg".to_owned(), error_code: errors::INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR, - })); - }); + }) + .to_string(), + ) + .create_async() + .await; // Add some pending appointments and try again (with an unreachable tower). let appointment = generate_random_appointment(None); @@ -1492,7 +1544,7 @@ mod tests { .. }) )); - api_mock.assert(); + api_mock.assert_async().await; } #[tokio::test] @@ -1503,25 +1555,29 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); + let mut server = mockito::Server::new_async().await; // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, &server.url(), &receipt) .unwrap(); - let api_mock = server.mock(|when, then| { - when.method(POST).path(Endpoint::AddAppointment.path()); - then.status(400) - .header("content-type", "application/json") - .json_body(json!(ApiError { + let api_mock = server + .mock("POST", Endpoint::AddAppointment.path().as_str()) + .with_status(400) + .with_header("content-type", "application/json") + .with_body( + json!(ApiError { error: "error_msg".to_owned(), error_code: 1, - })); - }); + }) + .to_string(), + ) + .create_async() + .await; // Add some pending appointments and try again (with an unreachable tower). let appointment = generate_random_appointment(None); @@ -1538,8 +1594,6 @@ mod tests { ); let r = retrier.run().await; - assert_eq!(r, Ok(())); - api_mock.assert(); assert!(wt_client .lock() .unwrap() @@ -1548,6 +1602,8 @@ mod tests { .unwrap() .invalid_appointments .contains(&appointment.locator)); + assert!(r.is_ok()); + api_mock.assert_async().await; } #[tokio::test] @@ -1558,14 +1614,13 @@ mod tests { let wt_client = Arc::new(Mutex::new( WTClient::new(tmp_path.path().to_path_buf(), unbounded_channel().0).await, )); - let server = MockServer::start(); // The tower we'd like to retry sending appointments to has to exist within the plugin let receipt = get_random_registration_receipt(); wt_client .lock() .unwrap() - .add_update_tower(tower_id, &server.base_url(), &receipt) + .add_update_tower(tower_id, "http://tower.adrress", &receipt) .unwrap(); // Remove the tower (to simulate it has been abandoned) diff --git a/watchtower-plugin/src/wt_client.rs b/watchtower-plugin/src/wt_client.rs index 315d9aa2..ecf26db0 100644 --- a/watchtower-plugin/src/wt_client.rs +++ b/watchtower-plugin/src/wt_client.rs @@ -95,17 +95,17 @@ impl WTClient { }); let dbm = DBM::new(&data_dir.join("watchtowers_db.sql3")).unwrap(); - let (user_sk, user_id) = match dbm.load_client_key() { - Ok(sk) => ( + + let (user_sk, user_id) = if let Some(sk) = dbm.load_client_key() { + ( sk, UserId(PublicKey::from_secret_key(&Secp256k1::new(), &sk)), - ), - Err(_) => { - log::info!("Watchtower client keys not found. Creating a fresh set"); - let (sk, pk) = cryptography::get_random_keypair(); - dbm.store_client_key(&sk).unwrap(); - (sk, UserId(pk)) - } + ) + } else { + log::info!("Watchtower client keys not found. Creating a fresh set"); + let (sk, pk) = cryptography::get_random_keypair(); + dbm.store_client_key(&sk).unwrap(); + (sk, UserId(pk)) }; let towers = dbm.load_towers(); @@ -180,15 +180,12 @@ impl WTClient { } /// Gets the latest registration receipt of a given tower. - pub fn get_registration_receipt( - &self, - tower_id: TowerId, - ) -> Result { + pub fn get_registration_receipt(&self, tower_id: TowerId) -> Option { self.dbm.load_registration_receipt(tower_id, self.user_id) } /// Loads a tower record from the database. - pub fn load_tower_info(&self, tower_id: TowerId) -> Result { + pub fn load_tower_info(&self, tower_id: TowerId) -> Option { self.dbm.load_tower_record(tower_id) } @@ -240,7 +237,7 @@ impl WTClient { &self, tower_id: TowerId, locator: Locator, - ) -> Result { + ) -> Option { self.dbm.load_appointment_receipt(tower_id, locator) } @@ -810,10 +807,7 @@ mod tests { // Remove the tower and check it is not there anymore wt_client.remove_tower(tower_id).unwrap(); - assert!(matches!( - wt_client.load_tower_info(tower_id), - Err(DBError::NotFound) - )); + assert!(wt_client.load_tower_info(tower_id).is_none()); assert!(!wt_client.towers.contains_key(&tower_id)); // Try again but this time with an associated appointment to check that it also gets removed @@ -836,10 +830,7 @@ mod tests { // Remove and check both the tower and the appointment wt_client.remove_tower(tower_id).unwrap(); - assert!(matches!( - wt_client.load_tower_info(tower_id), - Err(DBError::NotFound) - )); + assert!(wt_client.load_tower_info(tower_id).is_none()); assert!(!wt_client.towers.contains_key(&tower_id)); assert!(!wt_client.dbm.appointment_receipt_exists(locator, tower_id)); } @@ -890,10 +881,7 @@ mod tests { // Remove tower1 and check that the appointment receipt can still be found for tower2 wt_client.remove_tower(tower1_id).unwrap(); - assert!(matches!( - wt_client.load_tower_info(tower1_id), - Err(DBError::NotFound) - )); + assert!(wt_client.load_tower_info(tower1_id).is_none()); assert!(!wt_client.dbm.appointment_receipt_exists(locator, tower1_id)); assert!(wt_client.dbm.appointment_receipt_exists(locator, tower2_id));