diff --git a/Cargo.lock b/Cargo.lock index 13aa5b62..06f5829c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,7 +54,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", ] @@ -68,7 +68,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -142,11 +142,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] @@ -187,18 +188,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -347,12 +348,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - [[package]] name = "bech32" version = "0.11.0" @@ -377,7 +372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026" dependencies = [ "base58ck", - "bech32 0.11.0", + "bech32", "bitcoin-internals 0.3.0", "bitcoin-io 0.1.3", "bitcoin-units", @@ -473,9 +468,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] @@ -527,9 +522,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-tools" @@ -545,9 +540,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "cbc" @@ -560,9 +555,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.6" +version = "1.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" dependencies = [ "shlex", ] @@ -624,9 +619,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" dependencies = [ "clap_builder", "clap_derive", @@ -634,9 +629,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" dependencies = [ "anstream", "anstyle", @@ -646,14 +641,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -670,9 +665,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "config" -version = "0.15.4" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d84f8d224ac58107d53d3ec2b9ad39fd8c8c4e285d3c9cb35485ffd2ca88cb3" +checksum = "8cf9dc8d4ef88e27a8cb23e85cb116403dedd57f7971964dc4b18ccead548901" dependencies = [ "async-trait", "convert_case", @@ -702,7 +697,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -734,9 +729,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -803,9 +798,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -814,15 +809,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", - "rand_core", + "rand_core 0.6.4", "typenum", ] [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "digest" @@ -852,7 +847,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -1093,7 +1088,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -1154,10 +1149,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1188,7 +1195,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -1207,7 +1214,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.2.0", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -1372,9 +1379,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -1408,9 +1415,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -1448,9 +1455,9 @@ checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http 1.2.0", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", - "rustls 0.23.20", + "rustls 0.23.22", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", @@ -1477,7 +1484,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "native-tls", "tokio", @@ -1496,7 +1503,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.2", + "hyper 1.6.0", "pin-project-lite", "socket2", "tokio", @@ -1642,7 +1649,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -1678,9 +1685,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1710,9 +1717,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is_terminal_polyfill" @@ -1737,9 +1744,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -1790,31 +1797,29 @@ dependencies = [ [[package]] name = "lightning-invoice" -version = "0.32.0" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ab9f6ea77e20e3129235e62a2e6bd64ed932363df104e864ee65ccffb54a8f" +checksum = "d4254e7d05961a3728bc90737c522e7091735ba6f2f71014096d4b3eb4ee5d89" dependencies = [ - "bech32 0.9.1", + "bech32", "bitcoin", "lightning-types", ] [[package]] name = "lightning-types" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1083b8d9137000edf3bfcb1ff011c0d25e0cdd2feb98cc21d6765e64a494148f" +checksum = "f2cd84d4e71472035903e43caded8ecc123066ce466329ccd5ae537a8d5488c7" dependencies = [ - "bech32 0.9.1", "bitcoin", - "hex-conservative 0.2.1", ] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" @@ -1831,7 +1836,7 @@ dependencies = [ "aes", "anyhow", "base64 0.22.1", - "bech32 0.11.0", + "bech32", "bitcoin", "cbc", "email_address", @@ -1854,9 +1859,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "matchers" @@ -1922,9 +1927,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -1936,7 +1941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1971,14 +1976,14 @@ dependencies = [ [[package]] name = "mostro-core" version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a7c10b8900e96717edc31b621e1d07204a18b001c22d128fd3573a9f882585" +source = "git+https://github.com/MostroP2P/mostro-core?rev=7e53875cc8370a8da4dcc80b243f9eae630ae345#7e53875cc8370a8da4dcc80b243f9eae630ae345" dependencies = [ "anyhow", "bitcoin", "bitcoin_hashes 0.16.0", "chrono", "nostr-sdk", + "rand 0.8.5", "serde", "serde_json", "sqlx", @@ -1995,9 +2000,9 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -2040,13 +2045,13 @@ checksum = "af7c1eebe17dd785e52e1f81149c1b50fa6ec92e4ac239840934d1ffbd4f631c" dependencies = [ "async-trait", "base64 0.22.1", - "bech32 0.11.0", + "bech32", "bip39", "bitcoin", "cbc", "chacha20", "chacha20poly1305", - "getrandom", + "getrandom 0.2.15", "instant", "negentropy 0.3.1", "negentropy 0.4.3", @@ -2130,9 +2135,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "opaque-debug" @@ -2148,11 +2153,11 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "foreign-types", "libc", @@ -2169,29 +2174,29 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.4.1+3.4.0" +version = "300.4.2+3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" +checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", @@ -2271,7 +2276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2310,7 +2315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.9", + "thiserror 2.0.11", "ucd-trie", ] @@ -2334,7 +2339,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -2355,34 +2360,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.7.0", + "indexmap 2.7.1", ] [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2413,24 +2418,24 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -2462,7 +2467,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.94", + "syn 2.0.98", "tempfile", ] @@ -2476,7 +2481,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -2504,8 +2509,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.17", ] [[package]] @@ -2515,7 +2531,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -2524,7 +2550,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.17", ] [[package]] @@ -2542,7 +2578,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -2604,7 +2640,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-rustls 0.27.5", "hyper-tls", "hyper-util", @@ -2657,7 +2693,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -2671,7 +2707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.6.0", + "bitflags 2.8.0", "serde", "serde_derive", ] @@ -2695,11 +2731,11 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", @@ -2732,9 +2768,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "once_cell", "ring 0.17.8", @@ -2764,9 +2800,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -2797,9 +2833,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "salsa20" @@ -2854,7 +2890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes 0.14.0", - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -2874,7 +2910,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation", "core-foundation-sys", "libc", @@ -2883,9 +2919,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -2908,16 +2944,16 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "itoa", "memchr", "ryu", @@ -3172,7 +3208,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -3250,9 +3286,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.94" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -3282,7 +3318,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -3291,7 +3327,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation", "system-configuration-sys", ] @@ -3308,12 +3344,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -3330,11 +3367,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.11", ] [[package]] @@ -3345,18 +3382,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -3405,9 +3442,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -3433,13 +3470,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -3479,7 +3516,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.20", + "rustls 0.23.22", "tokio", ] @@ -3514,12 +3551,12 @@ checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", - "rustls 0.23.20", + "rustls 0.23.22", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", "tungstenite", - "webpki-roots 0.26.7", + "webpki-roots 0.26.8", ] [[package]] @@ -3537,9 +3574,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -3558,11 +3595,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -3609,7 +3646,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -3623,7 +3660,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -3679,7 +3716,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] @@ -3745,8 +3782,8 @@ dependencies = [ "http 1.2.0", "httparse", "log", - "rand", - "rustls 0.23.20", + "rand 0.8.5", + "rustls 0.23.22", "rustls-pki-types", "sha1 0.10.6", "thiserror 1.0.69", @@ -3773,9 +3810,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -3883,12 +3920,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" dependencies = [ - "getrandom", - "rand", + "getrandom 0.3.1", + "js-sys", + "rand 0.9.0", "serde", "uuid-macro-internal", "wasm-bindgen", @@ -3896,20 +3934,20 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08" +checksum = "d28dd23acb5f2fa7bd2155ab70b960e770596b3bb6395119b40476c3655dfba4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -3938,36 +3976,46 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -3978,9 +4026,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3988,28 +4036,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -4042,9 +4093,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -4194,13 +4245,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.21" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f5bb5257f2407a5425c6e749bfd9692192a73e70a6060516ac04f889087d68" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -4244,7 +4304,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", "synstructure", ] @@ -4255,7 +4315,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", ] [[package]] @@ -4266,7 +4335,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -4286,7 +4366,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", "synstructure", ] @@ -4315,5 +4395,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.98", ] diff --git a/Cargo.toml b/Cargo.toml index 74f11135..f8c308fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" anyhow = "1.0.89" chrono = "0.4.35" easy-hasher = "2.2.1" -lightning-invoice = { version = "0.32.0", features = ["std"] } +lightning-invoice = { version = "0.33.1", features = ["std"] } nostr-sdk = { version = "0.38.0", features = ["nip59"] } serde = { version = "1.0.210" } serde_json = "1.0.128" @@ -38,15 +38,16 @@ uuid = { version = "1.8.0", features = [ "serde", ] } reqwest = { version = "0.12.1", features = ["json"] } -mostro-core = { version = "0.6.25", features = ["sqlx"] } +# mostro-core = { version = "0.6.25", features = ["sqlx"] } +mostro-core = { git = "https://github.com/MostroP2P/mostro-core", rev = "7e53875cc8370a8da4dcc80b243f9eae630ae345" , features = ["sqlx"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -config = "0.15.4" -clap = { version = "4.5.19", features = ["derive"] } +config = "0.15.8" +clap = { version = "4.5.29", features = ["derive"] } lnurl-rs = "0.9.0" openssl = { version = "0.10.66", features = ["vendored"] } once_cell = "1.20.2" bitcoin = "0.32.5" [dev-dependencies] -tokio = { version = "1.40.0", features = ["full", "test-util", "macros"] } +tokio = { version = "1.40.0", features = ["full", "test-util", "macros"] } \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 208dbda9..72c3e574 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,8 +15,8 @@ pub mod rate_user; // User reputation system pub mod release; // Release of held funds pub mod take_buy; // Taking buy orders pub mod take_sell; // Taking sell orders - -// Import action handlers from submodules +pub mod trade_pubkey; // Trade pubkey action + // Import action handlers from submodules use crate::app::add_invoice::add_invoice_action; use crate::app::admin_add_solver::admin_add_solver_action; use crate::app::admin_cancel::admin_cancel_action; @@ -30,27 +30,51 @@ use crate::app::rate_user::update_user_reputation_action; use crate::app::release::release_action; use crate::app::take_buy::take_buy_action; use crate::app::take_sell::take_sell_action; -use crate::db::update_user_trade_index; +use crate::app::trade_pubkey::trade_pubkey_action; +// use crate::db::update_user_trade_index; // Core functionality imports use crate::db::add_new_user; use crate::db::is_user_present; use crate::lightning::LndConnector; -use crate::util::send_cant_do_msg; +use crate::util::enqueue_cant_do_msg; use crate::Settings; // External dependencies use anyhow::Result; -use mostro_core::message::{Action, CantDoReason, Message}; +use mostro_core::error::CantDoReason; +use mostro_core::error::MostroError; +use mostro_core::error::ServiceError; +use mostro_core::message::{Action, Message}; use mostro_core::user::User; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; -use std::sync::Arc; -use tokio::sync::Mutex; + /// Helper function to log warning messages for action errors -fn warning_msg(action: &Action, e: anyhow::Error) { +fn warning_msg(action: &Action, e: ServiceError) { tracing::warn!("Error in {} with context {}", action, e); } +/// Function to manage errors and send appropriate messages +async fn manage_errors( + e: MostroError, + inner_message: Message, + event: UnwrappedGift, + action: &Action, +) { + match e { + MostroError::MostroCantDo(cause) => { + enqueue_cant_do_msg( + inner_message.get_inner_message_kind().request_id, + inner_message.get_inner_message_kind().id, + cause, + event.rumor.pubkey, + ) + .await + } + MostroError::MostroInternalErr(e) => warning_msg(action, e), + } +} + /// Function to check if a user is present in the database and update or create their trade index. /// /// This function performs the following tasks: @@ -64,7 +88,11 @@ fn warning_msg(action: &Action, e: anyhow::Error) { /// * `pool` - The database connection pool used to query and update user data. /// * `event` - The unwrapped gift event containing the sender's information. /// * `msg` - The message containing action details and trade index information. -async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Message) { +async fn check_trade_index( + pool: &Pool, + event: &UnwrappedGift, + msg: &Message, +) -> Result<(), MostroError> { let message_kind = msg.get_inner_message_kind(); // Only process actions related to trading @@ -72,7 +100,7 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes message_kind.action, Action::NewOrder | Action::TakeBuy | Action::TakeSell ) { - return; + return Ok(()); } // If user is present, we check the trade index and signature @@ -87,7 +115,9 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes Ok(data) => data, Err(e) => { tracing::error!("Error deserializing content: {}", e); - return; + return Err(MostroError::MostroInternalErr( + ServiceError::MessageSerializationError, + )); } }; @@ -95,52 +125,35 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes if index <= user.last_trade_index { tracing::info!("Invalid trade index"); - send_cant_do_msg( - None, - message_kind.id, - Some(CantDoReason::InvalidTradeIndex), - &event.rumor.pubkey, + manage_errors( + MostroError::MostroCantDo(CantDoReason::InvalidTradeIndex), + msg.clone(), + event.clone(), + &message_kind.action, ) .await; - return; + return Err(MostroError::MostroCantDo(CantDoReason::InvalidTradeIndex)); } if !message_kind.verify_signature(event.rumor.pubkey, sig) { tracing::info!("Invalid signature"); - send_cant_do_msg( - None, - message_kind.id, - Some(CantDoReason::InvalidSignature), - &event.rumor.pubkey, - ) - .await; - return; - } - - if let Err(e) = update_user_trade_index(pool, event.sender.to_string(), index).await - { - tracing::error!("Error updating user trade index: {}", e); + return Err(MostroError::MostroCantDo(CantDoReason::InvalidSignature)); } } + Ok(()) } Err(_) => { - if let (true, last_trade_index) = message_kind.has_trade_index() { + if let (true, _) = message_kind.has_trade_index() { let new_user: User = User { pubkey: event.sender.to_string(), - last_trade_index, ..Default::default() }; if let Err(e) = add_new_user(pool, new_user).await { tracing::error!("Error creating new user: {}", e); - send_cant_do_msg( - None, - msg.get_inner_message_kind().id, - Some(CantDoReason::CantCreateUser), - &event.rumor.pubkey, - ) - .await; + return Err(MostroError::MostroCantDo(CantDoReason::CantCreateUser)); } } + Ok(()) } } } @@ -155,7 +168,6 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes /// * `my_keys` - Node keypair for signing/verification /// * `pool` - Database connection pool /// * `ln_client` - Lightning network connector -/// * `rate_list` - Shared list of rating events async fn handle_message_action( action: &Action, msg: Message, @@ -163,32 +175,58 @@ async fn handle_message_action( my_keys: &Keys, pool: &Pool, ln_client: &mut LndConnector, - rate_list: Arc>>, ) -> Result<()> { match action { // Order-related actions - Action::NewOrder => order_action(msg, event, my_keys, pool).await, - Action::TakeSell => take_sell_action(msg, event, my_keys, pool).await, - Action::TakeBuy => take_buy_action(msg, event, my_keys, pool).await, + Action::NewOrder => order_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), + Action::TakeSell => take_sell_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), + Action::TakeBuy => take_buy_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), // Payment-related actions - Action::FiatSent => fiat_sent_action(msg, event, my_keys, pool).await, - Action::Release => release_action(msg, event, my_keys, pool, ln_client).await, - Action::AddInvoice => add_invoice_action(msg, event, my_keys, pool).await, + Action::FiatSent => fiat_sent_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), + Action::Release => release_action(msg, event, my_keys, pool, ln_client) + .await + .map_err(|e| e.into()), + Action::AddInvoice => add_invoice_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), Action::PayInvoice => todo!(), // Dispute and rating actions - Action::Dispute => dispute_action(msg, event, my_keys, pool).await, - Action::RateUser => { - update_user_reputation_action(msg, event, my_keys, pool, rate_list).await - } - Action::Cancel => cancel_action(msg, event, my_keys, pool, ln_client).await, + Action::Dispute => dispute_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), + Action::RateUser => update_user_reputation_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), + Action::Cancel => cancel_action(msg, event, my_keys, pool, ln_client) + .await + .map_err(|e| e.into()), // Admin actions - Action::AdminCancel => admin_cancel_action(msg, event, my_keys, pool, ln_client).await, - Action::AdminSettle => admin_settle_action(msg, event, my_keys, pool, ln_client).await, - Action::AdminAddSolver => admin_add_solver_action(msg, event, my_keys, pool).await, - Action::AdminTakeDispute => admin_take_dispute_action(msg, event, pool).await, + Action::AdminCancel => admin_cancel_action(msg, event, my_keys, pool, ln_client) + .await + .map_err(|e| e.into()), + Action::AdminSettle => admin_settle_action(msg, event, my_keys, pool, ln_client) + .await + .map_err(|e| e.into()), + Action::AdminAddSolver => admin_add_solver_action(msg, event, my_keys, pool) + .await + .map_err(|e| e.into()), + Action::AdminTakeDispute => admin_take_dispute_action(msg, event, pool) + .await + .map_err(|e| e.into()), + Action::TradePubkey => trade_pubkey_action(msg, event, pool) + .await + .map_err(|e| e.into()), _ => { tracing::info!("Received message with action {:?}", action); @@ -211,7 +249,6 @@ pub async fn run( client: &Client, ln_client: &mut LndConnector, pool: Pool, - rate_list: Arc>>, ) -> Result<()> { loop { let mut notifications = client.notifications(); @@ -275,22 +312,35 @@ pub async fn run( } // Check if message is message with trade index - check_trade_index(&pool, &event, &message).await; + if let Err(e) = check_trade_index(&pool, &event, &message).await { + tracing::error!("Error checking trade index: {}", e); + continue; + } if inner_message.verify() { if let Some(action) = message.inner_action() { if let Err(e) = handle_message_action( &action, - message, + message.clone(), &event, &my_keys, &pool, ln_client, - rate_list.clone(), ) .await { - warning_msg(&action, e) + match e.downcast::() { + Ok(err) => { + manage_errors(err, message, event, &action).await; + } + Err(e) => { + tracing::error!("Unexpected error type: {}", e); + warning_msg( + &action, + ServiceError::UnexpectedError(e.to_string()), + ); + } + } } } } diff --git a/src/app/add_invoice.rs b/src/app/add_invoice.rs index b756ff35..cc1bae5b 100644 --- a/src/app/add_invoice.rs +++ b/src/app/add_invoice.rs @@ -1,160 +1,77 @@ -use crate::lightning::invoice::is_valid_invoice; -use crate::util::{send_cant_do_msg, send_new_order_msg, show_hold_invoice, update_order_event}; +use crate::util::{ + enqueue_order_msg, get_order, show_hold_invoice, update_order_event, validate_invoice, +}; -use anyhow::{Error, Result}; +use anyhow::Result; -use mostro_core::message::{Action, CantDoReason, Message, Payload}; -use mostro_core::order::SmallOrder; -use mostro_core::order::{Kind, Order, Status}; +use mostro_core::error::MostroError::{self, *}; +use mostro_core::error::{CantDoReason, ServiceError}; +use mostro_core::message::{Action, Message, Payload}; +use mostro_core::order::Status; +use mostro_core::order::{Order, SmallOrder}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; use sqlx_crud::Crud; -use std::str::FromStr; -use tracing::error; -pub async fn add_invoice_action( - msg: Message, - event: &UnwrappedGift, - my_keys: &Keys, +pub async fn check_order_status( + order: &mut Order, pool: &Pool, -) -> Result<()> { - // Get the order message - let order_msg = msg.get_inner_message_kind(); - // Get the request id - let request_id = order_msg.request_id; - // Get the order - let mut order = if let Some(order_id) = order_msg.id { - match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => return Err(Error::msg("Order Id {order_id} not found!")), - } - } else { - return Err(Error::msg("Missing message Id")); - }; - - let order_status = match Status::from_str(&order.status) { - Ok(s) => s, - Err(e) => { - error!("Order Id {} wrong status: {e:?}", order.id); - return Ok(()); - } - }; - - let order_kind = match Kind::from_str(&order.kind) { - Ok(k) => k, - Err(e) => { - error!("Order Id {} wrong kind: {e:?}", order.id); - return Ok(()); - } - }; - - let buyer_pubkey = match order.buyer_pubkey.as_ref() { - Some(pk) => PublicKey::from_str(pk)?, - None => { - error!("Buyer pubkey not found for order {}!", order.id); - return Ok(()); - } - }; - // Only the buyer can add an invoice - if buyer_pubkey != event.rumor.pubkey { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPeer), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - } - - // Invoice variable - let invoice: String; - // If a buyer sent me a lightning invoice or a ln address we handle it - if let Some(payment_request) = order_msg.get_payment_request() { - invoice = { - // Verify if invoice is valid - match is_valid_invoice( - payment_request.clone(), - Some(order.amount as u64), - Some(order.fee as u64), - ) - .await - { - Ok(_) => payment_request, - Err(_) => { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidAmount), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - } - } - }; - } else { - error!("Order Id {} wrong get_payment_request", order.id); - return Ok(()); - } - // We save the invoice on db - order.buyer_invoice = Some(invoice); + msg: &Message, +) -> Result<(), MostroError> { // Buyer can add invoice orders with WaitingBuyerInvoice status - match order_status { - Status::WaitingBuyerInvoice => {} - Status::SettledHoldInvoice => { + match order.get_order_status() { + Ok(Status::WaitingBuyerInvoice) => {} + Ok(Status::SettledHoldInvoice) => { order.payment_attempts = 0; - order.clone().update(pool).await?; - send_new_order_msg( - request_id, + order.clone().update(pool).await.map_err(|cause| { + MostroInternalErr(ServiceError::DbAccessError(cause.to_string())) + })?; + enqueue_order_msg( + msg.get_inner_message_kind().request_id, Some(order.id), Action::InvoiceUpdated, None, - &buyer_pubkey, + order.get_buyer_pubkey().map_err(MostroInternalErr)?, None, ) .await; - return Ok(()); } _ => { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + return Err(MostroCantDo(CantDoReason::NotAllowedByStatus)); } } + Ok(()) +} - let seller_pubkey = match &order.seller_pubkey { - Some(seller) => PublicKey::from_str(seller.as_str())?, - _ => return Err(Error::msg("Missing pubkeys")), - }; - +pub async fn add_invoice_action( + msg: Message, + event: &UnwrappedGift, + my_keys: &Keys, + pool: &Pool, +) -> Result<(), MostroError> { + // Get order + let mut order = get_order(&msg, pool).await?; + // Check order status + order.get_order_status().map_err(MostroInternalErr)?; + // Check order kind + order.get_order_kind().map_err(MostroInternalErr)?; + // Get buyer pubkey + let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?; + // Only the buyer can add an invoice + if buyer_pubkey != event.rumor.pubkey { + return Err(MostroCantDo(CantDoReason::InvalidPeer)); + } + // We save the invoice on db + order.buyer_invoice = validate_invoice(&msg, &order).await?; + // Buyer can add invoice orders with WaitingBuyerInvoice status + check_order_status(&mut order, pool, &msg).await?; + // Get seller pubkey + let seller_pubkey = order.get_seller_pubkey().map_err(MostroInternalErr)?; + // Check if the order has a preimage if order.preimage.is_some() { // We send this data related to the order to the parties - let order_data = SmallOrder::new( - Some(order.id), - Some(order_kind), - Some(Status::Active), - order.amount, - order.fiat_code.clone(), - order.min_amount, - order.max_amount, - order.fiat_amount, - order.payment_method.clone(), - order.premium, - order.buyer_pubkey.as_ref().cloned(), - order.seller_pubkey.as_ref().cloned(), - None, - None, - None, - None, - None, - ); + let order_data = SmallOrder::from(order.clone()); // We publish a new replaceable kind nostr event with the status updated // and update on local database the status and new event id if let Ok(order_updated) = update_order_event(my_keys, Status::Active, &order).await { @@ -162,36 +79,38 @@ pub async fn add_invoice_action( } // We send a confirmation message to seller - send_new_order_msg( + enqueue_order_msg( None, - Some(order.id), + Some(order.clone().id), Action::BuyerTookOrder, Some(Payload::Order(order_data.clone())), - &seller_pubkey, + seller_pubkey, None, ) .await; // We send a message to buyer saying seller paid - send_new_order_msg( - request_id, - Some(order.id), + enqueue_order_msg( + msg.get_inner_message_kind().request_id, + Some(order.clone().id), Action::HoldInvoicePaymentAccepted, Some(Payload::Order(order_data)), - &buyer_pubkey, + buyer_pubkey, None, ) .await; - } else { - show_hold_invoice( - my_keys, - None, - &buyer_pubkey, - &seller_pubkey, - order, - request_id, - ) - .await?; + } else if let Err(cause) = show_hold_invoice( + my_keys, + None, + &buyer_pubkey, + &seller_pubkey, + order, + msg.get_inner_message_kind().request_id, + ) + .await + { + return Err(MostroInternalErr(ServiceError::HoldInvoiceError( + cause.to_string(), + ))); } - Ok(()) } diff --git a/src/app/admin_add_solver.rs b/src/app/admin_add_solver.rs index 29d44254..b8017d14 100644 --- a/src/app/admin_add_solver.rs +++ b/src/app/admin_add_solver.rs @@ -1,8 +1,12 @@ use crate::db::add_new_user; -use crate::util::{send_cant_do_msg, send_dm}; +use crate::util::send_dm; use anyhow::Result; -use mostro_core::message::{Action, CantDoReason, Message, Payload}; +use mostro_core::error::{ + MostroError::{self, *}, + ServiceError, +}; +use mostro_core::message::{Action, Message, Payload}; use mostro_core::user::User; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; @@ -14,7 +18,7 @@ pub async fn admin_add_solver_action( event: &UnwrappedGift, my_keys: &Keys, pool: &Pool, -) -> Result<()> { +) -> Result<(), MostroError> { // Get request id let request_id = msg.get_inner_message_kind().request_id; @@ -35,18 +39,12 @@ pub async fn admin_add_solver_action( // Check if the pubkey is Mostro if event.rumor.pubkey.to_string() != my_keys.public_key().to_string() { // We create a Message - send_cant_do_msg( - request_id, - None, - Some(CantDoReason::InvalidPubkey), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + return Err(MostroInternalErr(ServiceError::InvalidPubkey)); } let trade_index = inner_message.trade_index.unwrap_or(0); - let public_key = PublicKey::from_bech32(npubkey)?.to_hex(); - let user = User::new(public_key, 0, 1, 0, 0, trade_index); + let public_key = PublicKey::from_bech32(npubkey) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?; + let user = User::new(public_key.to_string(), 0, 1, 0, 0, trade_index); // Use CRUD to create user match add_new_user(pool, user).await { Ok(r) => info!("Solver added: {:#?}", r), @@ -54,10 +52,14 @@ pub async fn admin_add_solver_action( } // We create a Message for admin let message = Message::new_dispute(None, request_id, None, Action::AdminAddSolver, None); - let message = message.as_json()?; + let message = message + .as_json() + .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?; // Send the message let sender_keys = crate::util::get_keys().unwrap(); - send_dm(&event.rumor.pubkey, sender_keys, message, None).await?; + send_dm(event.rumor.pubkey, sender_keys, message, None) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; Ok(()) } diff --git a/src/app/admin_cancel.rs b/src/app/admin_cancel.rs index f2ee6edc..c83a451c 100644 --- a/src/app/admin_cancel.rs +++ b/src/app/admin_cancel.rs @@ -4,12 +4,16 @@ use std::str::FromStr; use crate::db::{find_dispute_by_order_id, is_assigned_solver}; use crate::lightning::LndConnector; use crate::nip33::new_event; -use crate::util::{get_nostr_client, send_cant_do_msg, send_dm, update_order_event}; +use crate::util::{enqueue_order_msg, get_nostr_client, get_order, send_dm, update_order_event}; -use anyhow::{Error, Result}; use mostro_core::dispute::Status as DisputeStatus; -use mostro_core::message::{Action, CantDoReason, Message, MessageKind}; -use mostro_core::order::{Order, Status}; +use mostro_core::error::{ + CantDoReason, + MostroError::{self, *}, + ServiceError, +}; +use mostro_core::message::{Action, Message}; +use mostro_core::order::Status; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; @@ -22,70 +26,42 @@ pub async fn admin_cancel_action( my_keys: &Keys, pool: &Pool, ln_client: &mut LndConnector, -) -> Result<()> { +) -> Result<(), MostroError> { // Get request id let request_id = msg.get_inner_message_kind().request_id; - - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { - order_id - } else { - return Err(Error::msg("No order id")); - }; - let inner_message = msg.get_inner_message_kind(); - - match is_assigned_solver(pool, &event.rumor.pubkey.to_string(), order_id).await { + // Get order + let order = get_order(&msg, pool).await?; + // Check if the solver is assigned to the order + match is_assigned_solver(pool, &event.rumor.pubkey.to_string(), order.id).await { Ok(false) => { - send_cant_do_msg( - request_id, - Some(order_id), - Some(CantDoReason::IsNotYourDispute), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); + return Err(MostroCantDo(CantDoReason::IsNotYourDispute)); } Err(e) => { - error!("Error checking if solver is assigned to order: {:?}", e); - return Ok(()); + return Err(MostroInternalErr(ServiceError::DbAccessError( + e.to_string(), + ))); } _ => {} } - let order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - error!("Order Id {order_id} not found!"); - return Ok(()); - } - }; - // Was order cooperatively cancelled? - if order.status == Status::CooperativelyCanceled.to_string() { - let message = MessageKind::new( - Some(order_id), + if let Err(cause) = order.check_status(Status::CooperativelyCanceled) { + return Err(MostroCantDo(cause)); + } else { + enqueue_order_msg( request_id, - inner_message.trade_index, + Some(order.id), Action::CooperativeCancelAccepted, None, - ); - if let Ok(message) = message.as_json() { - let sender_keys = crate::util::get_keys().unwrap(); - let _ = send_dm(&event.rumor.pubkey, sender_keys, message, None).await; - } - return Ok(()); - } - - if order.status != Status::Dispute.to_string() { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &event.rumor.pubkey, + event.rumor.pubkey, + msg.get_inner_message_kind().trade_index, ) .await; + } - return Ok(()); + // Was order in dispute? + if order.check_status(Status::Dispute).is_ok() { + return Err(MostroCantDo(CantDoReason::NotAllowedByStatus)); } if order.hash.is_some() { @@ -97,13 +73,15 @@ pub async fn admin_cancel_action( } // we check if there is a dispute - let dispute = find_dispute_by_order_id(pool, order_id).await; + let dispute = find_dispute_by_order_id(pool, order.id).await; if let Ok(mut d) = dispute { let dispute_id = d.id; // we update the dispute d.status = DisputeStatus::SellerRefunded.to_string(); - d.update(pool).await?; + d.update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; // We create a tag to show status of the dispute let tags: Tags = Tags::new(vec![ Tag::custom( @@ -120,7 +98,8 @@ pub async fn admin_cancel_action( ), ]); // nip33 kind with dispute id as identifier - let event = new_event(my_keys, "", dispute_id.to_string(), tags)?; + let event = new_event(my_keys, "", dispute_id.to_string(), tags) + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; match get_nostr_client() { Ok(client) => { @@ -134,32 +113,48 @@ pub async fn admin_cancel_action( // We publish a new replaceable kind nostr event with the status updated // and update on local database the status and new event id - let order_updated = update_order_event(my_keys, Status::CanceledByAdmin, &order).await?; - order_updated.update(pool).await?; + let order_updated = update_order_event(my_keys, Status::CanceledByAdmin, &order) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + order_updated + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; // We create a Message for cancel let message = Message::new_order( Some(order.id), request_id, - inner_message.trade_index, + msg.get_inner_message_kind().trade_index, Action::AdminCanceled, None, ); - let message = message.as_json()?; + + let message = message + .as_json() + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; // Message to admin let sender_keys = crate::util::get_keys().unwrap(); - send_dm(&event.rumor.pubkey, sender_keys, message.clone(), None).await?; + send_dm(event.rumor.pubkey, sender_keys, message.clone(), None) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; let (seller_pubkey, buyer_pubkey) = match (&order.seller_pubkey, &order.buyer_pubkey) { (Some(seller), Some(buyer)) => ( - PublicKey::from_str(seller.as_str())?, - PublicKey::from_str(buyer.as_str())?, + PublicKey::from_str(seller.as_str()) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?, + PublicKey::from_str(buyer.as_str()) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?, ), - (None, _) => return Err(Error::msg("Missing seller pubkey")), - (_, None) => return Err(Error::msg("Missing buyer pubkey")), + (None, _) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), + (_, None) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), }; let sender_keys = crate::util::get_keys().unwrap(); - send_dm(&seller_pubkey, sender_keys.clone(), message.clone(), None).await?; - send_dm(&buyer_pubkey, sender_keys, message, None).await?; + send_dm(seller_pubkey, sender_keys.clone(), message.clone(), None) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + send_dm(buyer_pubkey, sender_keys, message, None) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; Ok(()) } diff --git a/src/app/admin_settle.rs b/src/app/admin_settle.rs index 1d096cb1..9e2d191f 100644 --- a/src/app/admin_settle.rs +++ b/src/app/admin_settle.rs @@ -2,13 +2,14 @@ use crate::db::{find_dispute_by_order_id, is_assigned_solver}; use crate::lightning::LndConnector; use crate::nip33::new_event; use crate::util::{ - get_nostr_client, send_cant_do_msg, send_dm, settle_seller_hold_invoice, update_order_event, + enqueue_order_msg, get_nostr_client, get_order, settle_seller_hold_invoice, update_order_event, }; -use anyhow::{Error, Result}; use mostro_core::dispute::Status as DisputeStatus; -use mostro_core::message::{Action, CantDoReason, Message, MessageKind}; -use mostro_core::order::{Order, Status}; +use mostro_core::error::MostroError::{self, *}; +use mostro_core::error::ServiceError; +use mostro_core::message::{Action, Message}; +use mostro_core::order::Status; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; @@ -24,92 +25,65 @@ pub async fn admin_settle_action( my_keys: &Keys, pool: &Pool, ln_client: &mut LndConnector, -) -> Result<()> { +) -> Result<(), MostroError> { // Get request id let request_id = msg.get_inner_message_kind().request_id; + // Get order + let order = get_order(&msg, pool).await?; - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { - order_id - } else { - return Err(Error::msg("No order id")); - }; - let inner_message = msg.get_inner_message_kind(); - - match is_assigned_solver(pool, &event.rumor.pubkey.to_string(), order_id).await { + match is_assigned_solver(pool, &event.rumor.pubkey.to_string(), order.id).await { Ok(false) => { - send_cant_do_msg( - request_id, - Some(order_id), - Some(CantDoReason::IsNotYourDispute), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); + return Err(MostroCantDo( + mostro_core::error::CantDoReason::IsNotYourDispute, + )); } Err(e) => { - error!("Error checking if solver is assigned to order: {:?}", e); - return Ok(()); + return Err(MostroInternalErr(ServiceError::DbAccessError( + e.to_string(), + ))); } _ => {} } - let order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - error!("Order Id {order_id} not found!"); - return Ok(()); - } - }; - // Was orde cooperatively cancelled? - if order.status == Status::CooperativelyCanceled.to_string() { - let message = MessageKind::new( - Some(order_id), - msg.get_inner_message_kind().request_id, - inner_message.trade_index, - Action::CooperativeCancelAccepted, - None, - ); - if let Ok(message) = message.as_json() { - let sender_keys = crate::util::get_keys().unwrap(); - let _ = send_dm(&event.rumor.pubkey, sender_keys, message, None).await; - } - return Ok(()); - } - - if order.status != Status::Dispute.to_string() { - send_cant_do_msg( + if order.check_status(Status::CooperativelyCanceled).is_err() { + return Err(MostroCantDo( + mostro_core::error::CantDoReason::IsNotYourDispute, + )); + } else { + enqueue_order_msg( request_id, Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &event.rumor.pubkey, + Action::CooperativeCancelAccepted, + None, + event.rumor.pubkey, + msg.get_inner_message_kind().trade_index, ) .await; - - return Ok(()); } - settle_seller_hold_invoice( - event, - ln_client, - Action::AdminSettled, - true, - &order, - request_id, - ) - .await?; - - let order_updated = update_order_event(my_keys, Status::SettledHoldInvoice, &order).await?; + if let Err(cause) = order.check_status(Status::Dispute) { + return Err(MostroCantDo(cause)); + } + // Settle seller hold invoice + settle_seller_hold_invoice(event, ln_client, Action::AdminSettled, true, &order) + .await + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))?; + // Update order event + let order_updated = update_order_event(my_keys, Status::SettledHoldInvoice, &order) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; // we check if there is a dispute - let dispute = find_dispute_by_order_id(pool, order_id).await; + let dispute = find_dispute_by_order_id(pool, order.id).await; if let Ok(mut d) = dispute { let dispute_id = d.id; // we update the dispute d.status = DisputeStatus::Settled.to_string(); - d.update(pool).await?; + d.update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; // We create a tag to show status of the dispute let tags: Tags = Tags::new(vec![ Tag::custom( @@ -127,7 +101,8 @@ pub async fn admin_settle_action( ]); // nip33 kind with dispute id as identifier - let event = new_event(my_keys, "", dispute_id.to_string(), tags)?; + let event = new_event(my_keys, "", dispute_id.to_string(), tags) + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; match get_nostr_client() { Ok(client) => { @@ -140,41 +115,43 @@ pub async fn admin_settle_action( } } } - // We create a Message for settle - let message = Message::new_order( - Some(order_updated.id), + + // Send message to event creator + enqueue_order_msg( request_id, - inner_message.trade_index, + Some(order_updated.id), Action::AdminSettled, None, - ); - let message = message.as_json()?; - // Message to admin - let sender_keys = crate::util::get_keys().unwrap(); - send_dm( - &event.rumor.pubkey, - sender_keys.clone(), - message.clone(), - None, + event.rumor.pubkey, + msg.get_inner_message_kind().trade_index, ) - .await?; + .await; + + // Send message to seller and buyer if let Some(ref seller_pubkey) = order_updated.seller_pubkey { - send_dm( - &PublicKey::from_str(seller_pubkey)?, - sender_keys.clone(), - message.clone(), + enqueue_order_msg( + None, + Some(order_updated.id), + Action::AdminSettled, None, + PublicKey::from_str(seller_pubkey) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?, + msg.get_inner_message_kind().trade_index, ) - .await?; + .await; } + // Send message to buyer if let Some(ref buyer_pubkey) = order_updated.buyer_pubkey { - send_dm( - &PublicKey::from_str(buyer_pubkey)?, - sender_keys, - message.clone(), + enqueue_order_msg( + None, + Some(order_updated.id), + Action::AdminSettled, None, + PublicKey::from_str(buyer_pubkey) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?, + msg.get_inner_message_kind().trade_index, ) - .await?; + .await; } let _ = do_payment(order_updated, request_id).await; diff --git a/src/app/admin_take_dispute.rs b/src/app/admin_take_dispute.rs index 954779d3..0d7f8a08 100644 --- a/src/app/admin_take_dispute.rs +++ b/src/app/admin_take_dispute.rs @@ -1,11 +1,14 @@ use crate::db::find_solver_pubkey; use crate::nip33::new_event; -use crate::util::{get_nostr_client, send_cant_do_msg, send_dm}; +use crate::util::{get_nostr_client, get_order, send_dm}; -use anyhow::{Error, Result}; use mostro_core::dispute::{Dispute, Status}; -use mostro_core::message::{Action, CantDoReason, Message, Payload, Peer}; -use mostro_core::order::Order; +use mostro_core::error::{ + CantDoReason, + MostroError::{self, *}, + ServiceError, +}; +use mostro_core::message::{Action, Message, Payload, Peer}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; @@ -41,7 +44,7 @@ pub async fn admin_take_dispute_action( msg: Message, event: &UnwrappedGift, pool: &Pool, -) -> Result<()> { +) -> Result<(), MostroError> { // Get request id let request_id = msg.get_inner_message_kind().request_id; @@ -49,22 +52,17 @@ pub async fn admin_take_dispute_action( let dispute_id = if let Some(dispute_id) = msg.get_inner_message_kind().id { dispute_id } else { - return Err(Error::msg("No order id")); + return Err(MostroInternalErr(ServiceError::InvalidDisputeId)); }; // Fetch dispute from db - let mut dispute = match Dispute::by_id(pool, dispute_id).await? { + let mut dispute = match Dispute::by_id(pool, dispute_id) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))? + { Some(dispute) => dispute, None => { - send_cant_do_msg( - request_id, - Some(dispute_id), - Some(CantDoReason::NotFound), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); + return Err(MostroInternalErr(ServiceError::InvalidDisputeId)); } }; @@ -72,23 +70,14 @@ pub async fn admin_take_dispute_action( if let Ok(dispute_status) = Status::from_str(&dispute.status) { if !pubkey_event_can_solve(pool, &event.rumor.pubkey, dispute_status).await { // We create a Message - send_cant_do_msg( - request_id, - Some(dispute_id), - Some(CantDoReason::InvalidPubkey), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + return Err(MostroCantDo(CantDoReason::InvalidPubkey)); } } else { - return Err(Error::msg("No dispute status")); + return Err(MostroInternalErr(ServiceError::InvalidDisputeId)); }; - let order = match Order::by_id(pool, dispute.order_id).await? { - Some(o) => o, - None => return Err(Error::msg("No order id")), - }; + // Get order from db + let order = get_order(&msg, pool).await?; let mut new_order = order.as_new_order(); // Only in this case we use the trade pubkey fields to store the master pubkey @@ -109,7 +98,10 @@ pub async fn admin_take_dispute_action( new_order.seller_token = dispute.seller_token; new_order.buyer_token = dispute.buyer_token; // Save it to DB - dispute.update(pool).await?; + dispute + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; // We create a Message for admin let message = Message::new_dispute( @@ -119,9 +111,13 @@ pub async fn admin_take_dispute_action( Action::AdminTookDispute, Some(Payload::Order(new_order)), ); - let message = message.as_json()?; + let message = message + .as_json() + .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?; let sender_keys = crate::util::get_keys().unwrap(); - send_dm(&event.rumor.pubkey, sender_keys, message, None).await?; + send_dm(event.rumor.pubkey, sender_keys, message, None) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; // Now we create a message to both parties of the order // to them know who will assist them on the dispute let solver_pubkey = Peer::new(event.rumor.pubkey.to_hex()); @@ -143,21 +139,37 @@ pub async fn admin_take_dispute_action( let (seller_pubkey, buyer_pubkey) = match (&order.seller_pubkey, &order.buyer_pubkey) { (Some(seller), Some(buyer)) => ( - PublicKey::from_str(seller.as_str())?, - PublicKey::from_str(buyer.as_str())?, + PublicKey::from_str(seller.as_str()) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?, + PublicKey::from_str(buyer.as_str()) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?, ), - (None, _) => return Err(Error::msg("Missing seller pubkey")), - (_, None) => return Err(Error::msg("Missing buyer pubkey")), + (None, _) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), + (_, None) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), }; let sender_keys = crate::util::get_keys().unwrap(); send_dm( - &buyer_pubkey, + buyer_pubkey, sender_keys.clone(), - msg_to_buyer.as_json()?, + msg_to_buyer + .as_json() + .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?, + None, + ) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + + // Send message to seller + send_dm( + seller_pubkey, + sender_keys, + msg_to_seller + .as_json() + .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?, None, ) - .await?; - send_dm(&seller_pubkey, sender_keys, msg_to_seller.as_json()?, None).await?; + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; // We create a tag to show status of the dispute let tags: Tags = Tags::new(vec![ Tag::custom( @@ -174,21 +186,34 @@ pub async fn admin_take_dispute_action( ), ]); // nip33 kind with dispute id as identifier - let event = new_event(&crate::util::get_keys()?, "", dispute_id.to_string(), tags)?; + let event = new_event( + &crate::util::get_keys() + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?, + "", + dispute_id.to_string(), + tags, + ) + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; info!("Dispute event to be published: {event:#?}"); - let client = get_nostr_client().map_err(|e| { - info!( - "Failed to get nostr client for dispute {}: {}", - dispute_id, e - ); - e - })?; - - client.send_event(event).await.map_err(|e| { - info!("Failed to send dispute {} status event: {}", dispute_id, e); - e - })?; + let client = get_nostr_client() + .map_err(|e| { + info!( + "Failed to get nostr client for dispute {}: {}", + dispute_id, e + ); + e + }) + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + + client + .send_event(event) + .await + .map_err(|e| { + info!("Failed to send dispute {} status event: {}", dispute_id, e); + e + }) + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; Ok(()) } diff --git a/src/app/cancel.rs b/src/app/cancel.rs index 205bbd50..ab70edae 100644 --- a/src/app/cancel.rs +++ b/src/app/cancel.rs @@ -1,224 +1,382 @@ use crate::db::{ edit_buyer_pubkey_order, edit_master_buyer_pubkey_order, edit_master_seller_pubkey_order, - edit_seller_pubkey_order, find_order_by_id, update_order_to_initial_state, + edit_seller_pubkey_order, update_order_to_initial_state, }; use crate::lightning::LndConnector; -use crate::util::{send_cant_do_msg, send_new_order_msg, update_order_event}; +use crate::util::{enqueue_order_msg, get_order, update_order_event}; -use anyhow::{Error, Result}; -use mostro_core::message::{Action, CantDoReason, Message}; -use mostro_core::order::{Kind as OrderKind, Status}; +use mostro_core::error::{ + CantDoReason, + MostroError::{self, *}, + ServiceError, +}; +use mostro_core::message::{Action, Message}; +use mostro_core::order::{Order, Status}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; use sqlx_crud::Crud; use std::str::FromStr; -use tracing::{error, info}; +use tracing::info; + +/// Reset the api quotes +fn reset_api_quotes(order: &mut Order) { + if order.price_from_api { + order.amount = 0; + order.fee = 0; + } +} + +/// Notify the creator that the order was cancelled +async fn notify_creator(order: &mut Order, request_id: Option) -> Result<(), MostroError> { + if order.is_buy_order().is_ok() && order.check_status(Status::WaitingBuyerInvoice).is_ok() + || order.is_sell_order().is_ok() && order.check_status(Status::WaitingPayment).is_ok() + { + // Get creator pubkey + let creator_pubkey = order.get_creator_pubkey().map_err(MostroInternalErr)?; + + enqueue_order_msg( + request_id, + Some(order.id), + Action::Canceled, + None, + creator_pubkey, + None, + ) + .await; + } + + Ok(()) +} + +/// Cancel a cooperative execution +async fn cancel_cooperative_execution_step_2( + pool: &Pool, + event: &UnwrappedGift, + request_id: Option, + mut order: Order, + counterparty_pubkey: String, + my_keys: &Keys, + ln_client: &mut LndConnector, +) -> Result<(), MostroError> { + // Validate if the initiator is the same as the event pubkey + if let Some(initiator) = &order.cancel_initiator_pubkey { + if *initiator == event.rumor.pubkey.to_string() { + // We create a Message + return Err(MostroCantDo(CantDoReason::InvalidPubkey)); + } + } + + // Cancel hold invoice if present + if let Some(hash) = &order.hash { + // We return funds to seller + ln_client.cancel_hold_invoice(hash).await?; + info!( + "Cooperative cancel: Order Id {}: Funds returned to seller", + &order.id + ); + } + order.status = Status::CooperativelyCanceled.to_string(); + // update db + let order = order + .clone() + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + // We publish a new replaceable kind nostr event with the status updated + // and update on local database the status and new event id + update_order_event(my_keys, Status::CooperativelyCanceled, &order) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + // We create a Message for an accepted cooperative cancel and send it to both parties + enqueue_order_msg( + request_id, + Some(order.id), + Action::CooperativeCancelAccepted, + None, + event.rumor.pubkey, + None, + ) + .await; + let counterparty_pubkey = PublicKey::from_str(&counterparty_pubkey) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?; + enqueue_order_msg( + None, + Some(order.id), + Action::CooperativeCancelAccepted, + None, + counterparty_pubkey, + None, + ) + .await; + info!("Cancel: Order Id {} canceled cooperatively!", order.id); + + Ok(()) +} + +async fn cancel_cooperative_execution_step_1( + pool: &Pool, + event: &UnwrappedGift, + mut order: Order, + counterparty_pubkey: String, + request_id: Option, +) -> Result<(), MostroError> { + order.cancel_initiator_pubkey = Some(event.rumor.pubkey.to_string()); + // update db + let order = order + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + // We create a Message to start a cooperative cancel and send it to both parties + enqueue_order_msg( + request_id, + Some(order.id), + Action::CooperativeCancelInitiatedByYou, + None, + event.rumor.pubkey, + None, + ) + .await; + let counterparty_pubkey = PublicKey::from_str(&counterparty_pubkey) + .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?; + enqueue_order_msg( + None, + Some(order.id), + Action::CooperativeCancelInitiatedByPeer, + None, + counterparty_pubkey, + None, + ) + .await; + + Ok(()) +} + +/// Cancel an order by the taker +async fn cancel_order_by_taker( + pool: &Pool, + event: &UnwrappedGift, + order: &mut Order, + my_keys: &Keys, + request_id: Option, + ln_client: &mut LndConnector, + taker_pubkey: PublicKey, +) -> Result<(), MostroError> { + // Cancel hold invoice is present + if let Some(hash) = &order.hash { + ln_client.cancel_hold_invoice(hash).await?; + info!("Order Id {}: Funds returned to seller", &order.id); + } + + //We notify the creator that the order was cancelled only if the taker had already done his part before + notify_creator(order, request_id).await?; + + //We notify the taker that the order is cancelled + enqueue_order_msg( + request_id, + Some(order.id), + Action::Canceled, + None, + event.rumor.pubkey, + None, + ) + .await; + + // Reset api quotes + reset_api_quotes(order); + + if order.is_buy_order().is_ok() { + info!("Cancel seller data from db"); + edit_seller_pubkey_order(pool, order.id, None) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + edit_master_seller_pubkey_order(pool, order.id, None) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + } + if order.is_sell_order().is_ok() { + info!("Cancel buyer data from db"); + edit_buyer_pubkey_order(pool, order.id, None) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + edit_master_buyer_pubkey_order(pool, order.id, None) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + } + update_order_to_initial_state(pool, order.id, order.amount, order.fee) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + update_order_event(my_keys, Status::Pending, order) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + + info!( + "{}: Canceled order Id {} republishing order", + taker_pubkey, order.id + ); + + Ok(()) +} + +/// Cancel an order by the maker +async fn cancel_order_by_maker( + pool: &Pool, + event: &UnwrappedGift, + order: &mut Order, + taker_pubkey: PublicKey, + my_keys: &Keys, + request_id: Option, + ln_client: &mut LndConnector, +) -> Result<(), MostroError> { + // We publish a new replaceable kind nostr event with the status updated + if let Ok(order_updated) = update_order_event(my_keys, Status::Canceled, order).await { + order_updated + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + } + // Cancel hold invoice if present + if let Some(hash) = &order.hash { + ln_client.cancel_hold_invoice(hash).await?; + info!("Order Id {}: Funds returned to seller", &order.id); + } + + enqueue_order_msg( + request_id, + Some(order.id), + Action::Canceled, + None, + event.rumor.pubkey, + None, + ) + .await; + //We notify the taker that the order was cancelled + enqueue_order_msg( + None, + Some(order.id), + Action::Canceled, + None, + taker_pubkey, + None, + ) + .await; + + Ok(()) +} + +async fn cancel_pending_order_from_maker( + pool: &Pool, + event: &UnwrappedGift, + order: &mut Order, + my_keys: &Keys, + request_id: Option, +) -> Result<(), MostroError> { + // Validates if this user is the order creator + order + .sent_from_maker(event.rumor.pubkey) + .map_err(|_| MostroCantDo(CantDoReason::IsNotYourOrder))?; + // We publish a new replaceable kind nostr event with the status updated + // and update on local database the status and new event id + match update_order_event(my_keys, Status::Canceled, order).await { + Ok(order_updated) => { + order_updated + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + } + Err(e) => { + return Err(MostroInternalErr(ServiceError::DbAccessError( + e.to_string(), + ))); + } + } + // We create a Message for cancel + enqueue_order_msg( + request_id, + Some(order.id), + Action::Canceled, + None, + event.rumor.pubkey, + None, + ) + .await; + Ok(()) +} +/// Cancel an order pub async fn cancel_action( msg: Message, event: &UnwrappedGift, my_keys: &Keys, pool: &Pool, ln_client: &mut LndConnector, -) -> Result<()> { +) -> Result<(), MostroError> { // Get request id let request_id = msg.get_inner_message_kind().request_id; + // Get order id + let mut order = get_order(&msg, pool).await?; - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { - order_id - } else { - return Err(Error::msg("No order id")); - }; - let user_pubkey = event.rumor.pubkey.to_string(); - - let mut order = match find_order_by_id(pool, order_id, &user_pubkey).await { - Ok(order) => order, - Err(_) => { - error!("Order Id {order_id} not found for user with pubkey: {user_pubkey}"); - return Ok(()); - } - }; - - if order.status == Status::Canceled.to_string() - || order.status == Status::CooperativelyCanceled.to_string() - || order.status == Status::CanceledByAdmin.to_string() + if order.check_status(Status::Canceled).is_ok() + || order.check_status(Status::CooperativelyCanceled).is_ok() + || order.check_status(Status::CanceledByAdmin).is_ok() { - send_cant_do_msg( - request_id, - Some(order_id), - Some(CantDoReason::OrderAlreadyCanceled), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + return Err(MostroCantDo(CantDoReason::OrderAlreadyCanceled)); } - if order.status == Status::Pending.to_string() { - // Validates if this user is the order creator - if user_pubkey != order.creator_pubkey { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::IsNotYourOrder), - &event.rumor.pubkey, - ) - .await; - } else { - // We publish a new replaceable kind nostr event with the status updated - // and update on local database the status and new event id - if let Ok(order_updated) = update_order_event(my_keys, Status::Canceled, &order).await { - let _ = order_updated.update(pool).await; - } - // We create a Message for cancel - send_new_order_msg( - request_id, - Some(order.id), - Action::Canceled, - None, - &event.rumor.pubkey, - None, - ) - .await; - } - + if order.check_status(Status::Pending).is_ok() { + cancel_pending_order_from_maker(pool, event, &mut order, my_keys, request_id).await?; return Ok(()); } - if order.status == Status::WaitingPayment.to_string() - || order.status == Status::WaitingBuyerInvoice.to_string() - { - let (seller_pubkey, buyer_pubkey) = match (&order.seller_pubkey, &order.buyer_pubkey) { - (Some(seller), Some(buyer)) => (seller, buyer), - (None, _) => return Err(Error::msg("Missing seller pubkey")), - (_, None) => return Err(Error::msg("Missing buyer pubkey")), - }; + // Get seller and buyer pubkey + let seller_pubkey = order.get_seller_pubkey().map_err(MostroInternalErr)?; + let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?; - let taker_pubkey: String = if seller_pubkey == &order.creator_pubkey { - buyer_pubkey.to_string() + if order.check_status(Status::WaitingPayment).is_ok() + || order.check_status(Status::WaitingBuyerInvoice).is_ok() + { + // Get order taker pubkey + let taker_pubkey = if order.creator_pubkey == seller_pubkey.to_string() { + buyer_pubkey + } else if order.creator_pubkey == buyer_pubkey.to_string() { + seller_pubkey } else { - seller_pubkey.to_string() + return Err(MostroInternalErr(ServiceError::InvalidPubkey)); }; - if user_pubkey == order.creator_pubkey { - if let Ok(order_updated) = update_order_event(my_keys, Status::Canceled, &order).await { - let _ = order_updated.update(pool).await; - } - - if let Some(hash) = &order.hash { - ln_client.cancel_hold_invoice(hash).await?; - info!("Order Id {}: Funds returned to seller", &order.id); - } - - send_new_order_msg( + if order.sent_from_maker(event.rumor.pubkey).is_ok() { + cancel_order_by_maker( + pool, + event, + &mut order, + taker_pubkey, + my_keys, request_id, - Some(order.id), - Action::Canceled, - None, - &event.rumor.pubkey, - None, - ) - .await; - - let taker_pubkey = PublicKey::from_str(&taker_pubkey)?; - //We notify the taker that the order was cancelled - send_new_order_msg( - None, - Some(order.id), - Action::Canceled, - None, - &taker_pubkey, - None, + ln_client, ) - .await; - } else if user_pubkey == taker_pubkey { - if let Some(hash) = &order.hash { - ln_client.cancel_hold_invoice(hash).await?; - info!("Order Id {}: Funds returned to seller", &order.id); - } - - let creator_pubkey = PublicKey::from_str(&order.creator_pubkey)?; - //We notify the creator that the order was cancelled only if the taker had already done his part before - - if order.kind == OrderKind::Buy.to_string() { - if order.status == Status::WaitingBuyerInvoice.to_string() { - send_new_order_msg( - request_id, - Some(order.id), - Action::Canceled, - None, - &creator_pubkey, - None, - ) - .await; - } - if order.price_from_api { - order.amount = 0; - order.fee = 0; - } - edit_seller_pubkey_order(pool, order.id, None).await?; - edit_master_seller_pubkey_order(pool, order.id, None).await?; - update_order_to_initial_state(pool, order.id, order.amount, order.fee).await?; - update_order_event(my_keys, Status::Pending, &order).await?; - info!( - "{}: Canceled order Id {} republishing order", - buyer_pubkey, order.id - ); - } - - if order.kind == OrderKind::Sell.to_string() { - if order.status == Status::WaitingPayment.to_string() { - send_new_order_msg( - request_id, - Some(order.id), - Action::Canceled, - None, - &creator_pubkey, - None, - ) - .await; - } - if order.price_from_api { - order.amount = 0; - order.fee = 0; - } - edit_buyer_pubkey_order(pool, order.id, None).await?; - edit_master_buyer_pubkey_order(pool, order.id, None).await?; - update_order_to_initial_state(pool, order.id, order.amount, order.fee).await?; - update_order_event(my_keys, Status::Pending, &order).await?; - info!( - "{}: Canceled order Id {} republishing order", - buyer_pubkey, order.id - ); - } - - send_new_order_msg( + .await?; + } else if event.rumor.pubkey == taker_pubkey { + cancel_order_by_taker( + pool, + event, + &mut order, + my_keys, request_id, - Some(order.id), - Action::Canceled, - None, - &event.rumor.pubkey, - None, + ln_client, + taker_pubkey, ) - .await; + .await?; } else { - send_cant_do_msg(request_id, Some(order.id), None, &event.rumor.pubkey).await; - return Ok(()); + return Err(MostroCantDo(CantDoReason::InvalidPubkey)); } } - if order.status == Status::Active.to_string() - || order.status == Status::FiatSent.to_string() - || order.status == Status::Dispute.to_string() + if order.check_status(Status::Active).is_ok() + || order.check_status(Status::FiatSent).is_ok() + || order.check_status(Status::Dispute).is_ok() { - let (seller_pubkey, buyer_pubkey) = match (&order.seller_pubkey, &order.buyer_pubkey) { - (Some(seller), Some(buyer)) => (seller, buyer), - (None, _) => return Err(Error::msg("Missing seller pubkey")), - (_, None) => return Err(Error::msg("Missing buyer pubkey")), - }; - let counterparty_pubkey: String; - if buyer_pubkey == &user_pubkey { + if buyer_pubkey == event.rumor.pubkey { order.buyer_cooperativecancel = true; counterparty_pubkey = seller_pubkey.to_string(); } else { @@ -227,73 +385,27 @@ pub async fn cancel_action( } match order.cancel_initiator_pubkey { - Some(ref initiator_pubkey) => { - if initiator_pubkey == &user_pubkey { - // We create a Message - send_cant_do_msg(request_id, Some(order_id), None, &event.rumor.pubkey).await; - return Ok(()); - } else { - if let Some(hash) = &order.hash { - // We return funds to seller - ln_client.cancel_hold_invoice(hash).await?; - info!( - "Cooperative cancel: Order Id {}: Funds returned to seller", - &order.id - ); - } - order.status = Status::CooperativelyCanceled.to_string(); - // update db - let order = order.update(pool).await?; - // We publish a new replaceable kind nostr event with the status updated - // and update on local database the status and new event id - update_order_event(my_keys, Status::CooperativelyCanceled, &order).await?; - // We create a Message for an accepted cooperative cancel and send it to both parties - send_new_order_msg( - request_id, - Some(order.id), - Action::CooperativeCancelAccepted, - None, - &event.rumor.pubkey, - None, - ) - .await; - let counterparty_pubkey = PublicKey::from_str(&counterparty_pubkey)?; - send_new_order_msg( - None, - Some(order.id), - Action::CooperativeCancelAccepted, - None, - &counterparty_pubkey, - None, - ) - .await; - info!("Cancel: Order Id {order_id} canceled cooperatively!"); - } + Some(_) => { + cancel_cooperative_execution_step_2( + pool, + event, + request_id, + order, + counterparty_pubkey, + my_keys, + ln_client, + ) + .await?; } None => { - order.cancel_initiator_pubkey = Some(user_pubkey.clone()); - // update db - let order = order.update(pool).await?; - // We create a Message to start a cooperative cancel and send it to both parties - send_new_order_msg( + cancel_cooperative_execution_step_1( + pool, + event, + order, + counterparty_pubkey, request_id, - Some(order.id), - Action::CooperativeCancelInitiatedByYou, - None, - &event.rumor.pubkey, - None, - ) - .await; - let counterparty_pubkey = PublicKey::from_str(&counterparty_pubkey)?; - send_new_order_msg( - None, - Some(order.id), - Action::CooperativeCancelInitiatedByPeer, - None, - &counterparty_pubkey, - None, ) - .await; + .await?; } } } diff --git a/src/app/dispute.rs b/src/app/dispute.rs index c2ce6b8d..7a51d3b7 100644 --- a/src/app/dispute.rs +++ b/src/app/dispute.rs @@ -7,24 +7,26 @@ use std::str::FromStr; use crate::db::find_dispute_by_order_id; use crate::nip33::new_event; -use crate::util::{get_nostr_client, send_cant_do_msg, send_new_order_msg}; +use crate::util::{enqueue_order_msg, get_nostr_client, get_order}; -use anyhow::{Error, Result}; use mostro_core::dispute::Dispute; -use mostro_core::message::{Action, CantDoReason, Message, Payload}; +use mostro_core::error::{ + CantDoReason, + MostroError::{self, *}, + ServiceError, +}; +use mostro_core::message::{Action, Message, Payload}; use mostro_core::order::{Order, Status}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; -use rand::Rng; use sqlx::{Pool, Sqlite}; use sqlx_crud::traits::Crud; -use uuid::Uuid; /// Publishes a dispute event to the Nostr network. /// /// Creates and publishes a NIP-33 replaceable event containing dispute details /// including status and application metadata. -async fn publish_dispute_event(dispute: &Dispute, my_keys: &Keys) -> Result<()> { +async fn publish_dispute_event(dispute: &Dispute, my_keys: &Keys) -> Result<(), MostroError> { // Create tags for the dispute event let tags = Tags::new(vec![ // Status tag - indicates the current state of the dispute @@ -47,7 +49,7 @@ async fn publish_dispute_event(dispute: &Dispute, my_keys: &Keys) -> Result<()> // Create a new NIP-33 replaceable event // Empty content string as the information is in the tags let event = new_event(my_keys, "", dispute.id.to_string(), tags) - .map_err(|_| Error::msg("Failed to create dispute event"))?; + .map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?; tracing::info!("Publishing dispute event: {:#?}", event); @@ -63,12 +65,12 @@ async fn publish_dispute_event(dispute: &Dispute, my_keys: &Keys) -> Result<()> } Err(e) => { tracing::error!("Failed to send dispute event: {}", e); - Err(Error::msg("Failed to send dispute event")) + Err(MostroInternalErr(ServiceError::NostrError(e.to_string()))) } }, Err(e) => { tracing::error!("Failed to get Nostr client: {}", e); - Err(Error::msg("Failed to get Nostr client")) + Err(MostroInternalErr(ServiceError::NostrError(e.to_string()))) } } } @@ -78,14 +80,15 @@ async fn publish_dispute_event(dispute: &Dispute, my_keys: &Keys) -> Result<()> /// Returns a tuple containing: /// - The counterparty's public key as a String /// - A boolean indicating if the dispute was initiated by the buyer (true) or seller (false) -fn get_counterpart_info(sender: &str, buyer: &str, seller: &str) -> Result<(String, bool)> { +fn get_counterpart_info( + sender: &str, + buyer: &str, + seller: &str, +) -> Result<(String, bool), CantDoReason> { match sender { s if s == buyer => Ok((seller.to_string(), true)), // buyer is initiator s if s == seller => Ok((buyer.to_string(), false)), // seller is initiator - _ => { - tracing::error!("Message sender {sender} is neither buyer nor seller"); - Err(Error::msg("Invalid message sender")) - } + _ => Err(CantDoReason::InvalidPubkey), } } @@ -94,45 +97,15 @@ fn get_counterpart_info(sender: &str, buyer: &str, seller: &str) -> Result<(Stri /// Checks that: /// - The order exists /// - The order status allows disputes (Active or FiatSent) -async fn get_valid_order( - pool: &Pool, - order_id: Uuid, - event: &UnwrappedGift, - request_id: Option, -) -> Result { +async fn get_valid_order(pool: &Pool, msg: &Message) -> Result { // Try to fetch the order from the database - let order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - tracing::error!("Order Id {order_id} not found!"); - return Err(Error::msg("Order not found")); - } - }; - - // Parse and validate the order status - match Status::from_str(&order.status) { - Ok(status) => { - // Only allow disputes for Active or FiatSent orders - if !matches!(status, Status::Active | Status::FiatSent) { - // Notify the sender that the action is not allowed for this status - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &event.rumor.pubkey, - ) - .await; + let order = get_order(msg, pool).await?; - return Err(Error::msg(format!( - "Order {} with status {} does not allow disputes. Must be Active or FiatSent", - order.id, order.status - ))); - } - } - Err(_) => { - return Err(Error::msg("Invalid order status")); - } - }; + // Check if the order status is Active or FiatSent + if order.check_status(Status::Active).is_err() && order.check_status(Status::FiatSent).is_err() + { + return Err(MostroCantDo(CantDoReason::NotAllowedByStatus)); + } Ok(order) } @@ -151,102 +124,66 @@ pub async fn dispute_action( event: &UnwrappedGift, my_keys: &Keys, pool: &Pool, -) -> Result<()> { - // Get request id - let request_id = msg.get_inner_message_kind().request_id; - +) -> Result<(), MostroError> { let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { order_id } else { - return Err(Error::msg("No order id")); + return Err(MostroInternalErr(ServiceError::InvalidOrderId)); }; - // Check dispute for this order id is yet present. if find_dispute_by_order_id(pool, order_id).await.is_ok() { - return Err(Error::msg(format!( - "Dispute already exists for order {}", - order_id - ))); + return Err(MostroInternalErr(ServiceError::DisputeAlreadyExists)); } - // Get and validate order - let mut order = get_valid_order(pool, order_id, event, request_id).await?; - + let mut order = get_valid_order(pool, &msg).await?; + // Get seller and buyer pubkeys let (seller, buyer) = match (&order.seller_pubkey, &order.buyer_pubkey) { (Some(seller), Some(buyer)) => (seller.to_owned(), buyer.to_owned()), - (None, _) => return Err(Error::msg("Missing seller pubkey")), - (_, None) => return Err(Error::msg("Missing buyer pubkey")), + (None, _) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), + (_, None) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), }; - + // Get message sender let message_sender = event.rumor.pubkey.to_string(); + // Get counterpart info let (counterpart, is_buyer_dispute) = match get_counterpart_info(&message_sender, &buyer, &seller) { Ok((counterpart, is_buyer_dispute)) => (counterpart, is_buyer_dispute), - Err(_) => { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPubkey), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - } + Err(cause) => return Err(MostroCantDo(cause)), }; - // Get the opposite dispute status - let is_seller_dispute = !is_buyer_dispute; - - // Update dispute flags based on who initiated - let mut update_seller_dispute = false; - let mut update_buyer_dispute = false; - if is_seller_dispute && !order.seller_dispute { - update_seller_dispute = true; - order.seller_dispute = update_seller_dispute; - } else if is_buyer_dispute && !order.buyer_dispute { - update_buyer_dispute = true; - order.buyer_dispute = update_buyer_dispute; - }; - order.status = Status::Dispute.to_string(); - - // Update the database with dispute information - // Save the dispute to DB - if !update_buyer_dispute && !update_seller_dispute { - return Ok(()); - } else { - // Need to update dispute status - order.update(pool).await?; + // Setup dispute + if order.setup_dispute(is_buyer_dispute).is_ok() { + order + .update(pool) + .await + .map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?; } // Create new dispute record and generate security tokens let mut dispute = Dispute::new(order_id); - let mut rng = rand::thread_rng(); - dispute.buyer_token = Some(rng.gen_range(100..=999)); - dispute.seller_token = Some(rng.gen_range(100..=999)); - - let (initiator_token, counterpart_token) = match is_seller_dispute { - true => (dispute.seller_token, dispute.buyer_token), - false => (dispute.buyer_token, dispute.seller_token), - }; + // Create tokens + let (initiator_token, counterpart_token) = dispute.create_tokens(is_buyer_dispute); // Save dispute to database - let dispute = dispute.create(pool).await?; + let dispute = dispute + .create(pool) + .await + .map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?; // Send notification to dispute initiator let initiator_pubkey = match PublicKey::from_str(&message_sender) { Ok(pk) => pk, - Err(e) => { - tracing::error!("Error parsing initiator pubkey: {:#?}", e); - return Err(Error::msg("Failed to parse initiator public key")); + Err(_) => { + return Err(MostroInternalErr(ServiceError::InvalidPubkey)); } }; - send_new_order_msg( + enqueue_order_msg( msg.get_inner_message_kind().request_id, Some(order_id), Action::DisputeInitiatedByYou, Some(Payload::Dispute(dispute.clone().id, initiator_token)), - &initiator_pubkey, + initiator_pubkey, None, ) .await; @@ -254,22 +191,23 @@ pub async fn dispute_action( // Send notification to counterparty let counterpart_pubkey = match PublicKey::from_str(&counterpart) { Ok(pk) => pk, - Err(e) => { - tracing::error!("Error parsing counterpart pubkey: {:#?}", e); - return Err(Error::msg("Failed to parse counterpart public key")); + Err(_) => { + return Err(MostroInternalErr(ServiceError::InvalidPubkey)); } }; - send_new_order_msg( + enqueue_order_msg( msg.get_inner_message_kind().request_id, Some(order_id), Action::DisputeInitiatedByPeer, Some(Payload::Dispute(dispute.clone().id, counterpart_token)), - &counterpart_pubkey, + counterpart_pubkey, None, ) .await; // Publish dispute event to network - publish_dispute_event(&dispute, my_keys).await?; + publish_dispute_event(&dispute, my_keys) + .await + .map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?; Ok(()) } diff --git a/src/app/fiat_sent.rs b/src/app/fiat_sent.rs index 78353c5b..e9e635bd 100644 --- a/src/app/fiat_sent.rs +++ b/src/app/fiat_sent.rs @@ -1,118 +1,96 @@ -use crate::util::{send_cant_do_msg, send_new_order_msg, update_order_event}; +use crate::util::{enqueue_order_msg, get_order, update_order_event}; -use anyhow::{Error, Result}; -use mostro_core::message::{Action, CantDoReason, Message, Payload, Peer}; -use mostro_core::order::{Order, Status}; +use mostro_core::error::{ + CantDoReason, + MostroError::{self, *}, + ServiceError, +}; +use mostro_core::message::{Action, Message, Payload, Peer}; +use mostro_core::order::Status; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; use sqlx_crud::Crud; -use std::str::FromStr; -use tracing::error; +// Handle fiat sent action pub async fn fiat_sent_action( msg: Message, event: &UnwrappedGift, my_keys: &Keys, pool: &Pool, -) -> Result<()> { - // Get request id - let request_id = msg.get_inner_message_kind().request_id; +) -> Result<(), MostroError> { + // Get order + let order = get_order(&msg, pool).await?; - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { - order_id - } else { - return Err(Error::msg("No order id")); - }; - let order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - error!("Order Id {order_id} not found!"); - return Ok(()); - } - }; - // Send to user a DM with the error - if order.status != Status::Active.to_string() { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); + // Check if the order status is active + if let Err(cause) = order.check_status(Status::Active) { + return Err(MostroCantDo(cause)); } + // Check if the pubkey is the buyer - if Some(event.rumor.pubkey.to_string()) != order.buyer_pubkey { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPubkey), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + if order.get_buyer_pubkey().ok() != Some(event.rumor.pubkey) { + return Err(MostroCantDo(CantDoReason::InvalidPubkey)); } - let next_trade: Option<(String, u32)> = match &msg.get_inner_message_kind().payload { - Some(Payload::NextTrade(pubkey, index)) => Some((pubkey.clone(), *index)), - _ => None, - }; + + // Get next trade key + let next_trade = msg + .get_inner_message_kind() + .get_next_trade_key() + .map_err(MostroInternalErr)?; + // We publish a new replaceable kind nostr event with the status updated // and update on local database the status and new event id - let mut order_updated = match update_order_event(my_keys, Status::FiatSent, &order).await { - Ok(order) => order.update(pool).await?, - Err(e) => { - error!("Failed to update order {}: {}", order.id, e); - return Ok(()); - } - }; + let order_updated = update_order_event(my_keys, Status::FiatSent, &order) + .await + .map_err(|e| MostroError::MostroInternalErr(ServiceError::NostrError(e.to_string())))?; - let seller_pubkey = match order_updated.seller_pubkey.as_ref() { - Some(pk) => PublicKey::from_str(pk)?, - None => { - error!("Seller pubkey not found for order {}!", order_updated.id); - return Ok(()); - } - }; + // Update order + let mut order_updated = order_updated + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + + let seller_pubkey = order.get_seller_pubkey().map_err(MostroInternalErr)?; + + // Create peer let peer = Peer::new(event.rumor.pubkey.to_string()); // We a message to the seller - send_new_order_msg( + enqueue_order_msg( None, Some(order_updated.id), Action::FiatSentOk, Some(Payload::Peer(peer)), - &seller_pubkey, + seller_pubkey, None, ) .await; // We send a message to buyer to wait let peer = Peer::new(seller_pubkey.to_string()); - send_new_order_msg( + enqueue_order_msg( msg.get_inner_message_kind().request_id, Some(order_updated.id), Action::FiatSentOk, Some(Payload::Peer(peer)), - &event.rumor.pubkey, + event.rumor.pubkey, None, ) .await; // Update next trade fields only when the buyer is the maker of a range order // These fields will be used to create the next child order in the range - if order_updated.creator_pubkey == event.rumor.pubkey.to_string() && next_trade.is_some() { - if let Some((pubkey, index)) = next_trade { - order_updated.next_trade_pubkey = Some(pubkey.clone()); - order_updated.next_trade_index = Some(index as i64); - if let Err(e) = order_updated.update(pool).await { - error!( - "Failed to update next trade fields for order {}: {}", - order_id, e - ); - return Ok(()); - } + order + .not_sent_from_maker(event.rumor.pubkey) + .map_err(MostroCantDo)?; + + if let Some((pubkey, index)) = next_trade { + order_updated.next_trade_pubkey = Some(pubkey); + order_updated.next_trade_index = Some(index as i64); + if let Err(e) = order_updated.update(pool).await { + return Err(MostroInternalErr(ServiceError::DbAccessError( + e.to_string(), + ))); } } diff --git a/src/app/order.rs b/src/app/order.rs index 6ee93680..1bc1f0f7 100644 --- a/src/app/order.rs +++ b/src/app/order.rs @@ -1,135 +1,93 @@ use crate::cli::settings::Settings; -use crate::lightning::invoice::is_valid_invoice; -use crate::util::{get_bitcoin_price, publish_order, send_cant_do_msg}; -use anyhow::Result; -use mostro_core::message::{CantDoReason, Message}; +use crate::db::update_user_trade_index; +use crate::util::{get_bitcoin_price, publish_order, validate_invoice}; +use mostro_core::error::{ + CantDoReason, + MostroError::{self, *}, + ServiceError, +}; +use mostro_core::message::Message; +use mostro_core::order::{Order, SmallOrder}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use nostr_sdk::Keys; use sqlx::{Pool, Sqlite}; -use tracing::error; + +async fn calculate_and_check_quote( + order: &SmallOrder, + fiat_amount: &i64, +) -> Result<(), MostroError> { + // Get mostro settings + let mostro_settings = Settings::get_mostro(); + // Calculate quote + let quote = match order.amount { + 0 => match get_bitcoin_price(&order.fiat_code) { + Ok(price) => { + let quote = *fiat_amount as f64 / price; + (quote * 1E8) as i64 + } + Err(_) => { + return Err(MostroInternalErr(ServiceError::NoAPIResponse)); + } + }, + _ => order.amount, + }; + + // Check amount is positive - extra safety check + if quote < 0 { + return Err(MostroCantDo(CantDoReason::InvalidAmount)); + } + + if quote > mostro_settings.max_order_amount as i64 + || quote < mostro_settings.min_payment_amount as i64 + { + return Err(MostroCantDo(CantDoReason::OutOfRangeSatsAmount)); + } + + Ok(()) +} pub async fn order_action( msg: Message, event: &UnwrappedGift, my_keys: &Keys, pool: &Pool, -) -> Result<()> { +) -> Result<(), MostroError> { // Get request id let request_id = msg.get_inner_message_kind().request_id; if let Some(order) = msg.get_inner_message_kind().get_order() { - let mostro_settings = Settings::get_mostro(); - - // Allows lightning address or invoice - // If user add a bolt11 invoice with a wrong amount the payment will fail later - if let Some(invoice) = msg.get_inner_message_kind().get_payment_request() { - // Verify if LN address is valid - match is_valid_invoice(invoice.clone(), None, None).await { - Ok(_) => (), - Err(_) => { - send_cant_do_msg( - request_id, - order.id, - Some(CantDoReason::InvalidAmount), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); - } - } - } + // Validate invoice + let _invoice = validate_invoice(&msg, &Order::from(order.clone())).await?; // Default case single amount let mut amount_vec = vec![order.fiat_amount]; - // Get max and and min amount in case of range order // in case of single order do like usual - if let (Some(min), Some(max)) = (order.min_amount, order.max_amount) { - if min >= max { - send_cant_do_msg( - request_id, - order.id, - Some(CantDoReason::InvalidAmount), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - } - if order.amount != 0 { - send_cant_do_msg( - request_id, - None, - Some(CantDoReason::InvalidAmount), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); - } - amount_vec.clear(); - amount_vec.push(min); - amount_vec.push(max); + if let Err(cause) = order.check_range_order_limits(&mut amount_vec) { + return Err(MostroCantDo(cause)); } - let premium = (order.premium != 0).then_some(order.premium); - let fiat_amount = (order.fiat_amount != 0).then_some(order.fiat_amount); - let amount = (order.amount != 0).then_some(order.amount); - - if premium.is_some() && fiat_amount.is_some() && amount.is_some() { - send_cant_do_msg( - request_id, - None, - Some(CantDoReason::InvalidParameters), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + // Check if zero amount with premium + if let Err(cause) = order.check_zero_amount_with_premium() { + return Err(MostroCantDo(cause)); } + // Check quote in sats for each amount for fiat_amount in amount_vec.iter() { - let quote = match order.amount { - 0 => match get_bitcoin_price(&order.fiat_code) { - Ok(price) => { - let quote = *fiat_amount as f64 / price; - (quote * 1E8) as i64 - } - Err(e) => { - error!("{:?}", e.to_string()); - return Ok(()); - } - }, - _ => order.amount, - }; - - // Check amount is positive - extra safety check - if quote < 0 { - send_cant_do_msg( - request_id, - None, - Some(CantDoReason::InvalidAmount), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); - } - - if quote > mostro_settings.max_order_amount as i64 - || quote < mostro_settings.min_payment_amount as i64 - { - send_cant_do_msg( - request_id, - None, - Some(CantDoReason::OutOfRangeSatsAmount), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - } + calculate_and_check_quote(order, fiat_amount).await?; } + // Update trade index only after all checks are done + update_user_trade_index( + pool, + event.sender.to_string(), + msg.get_inner_message_kind().trade_index.unwrap(), + ) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + + // Publish order publish_order( pool, my_keys, @@ -140,7 +98,8 @@ pub async fn order_action( request_id, msg.get_inner_message_kind().trade_index, ) - .await?; + .await + .map_err(|_| MostroError::MostroInternalErr(ServiceError::InvalidOrderId))?; } Ok(()) } diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index c7a72fd0..9517fb1e 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -1,25 +1,73 @@ -use crate::util::{send_cant_do_msg, send_new_order_msg, update_user_rating_event}; +use crate::util::{enqueue_order_msg, get_order, update_user_rating_event}; use crate::NOSTR_CLIENT; use crate::db::{is_user_present, update_user_rating}; -use anyhow::{Error, Result}; -use mostro_core::message::{Action, CantDoReason, Message, Payload}; +use anyhow::Result; +use mostro_core::error::MostroError::{self, *}; +use mostro_core::error::{CantDoReason, ServiceError}; +use mostro_core::message::{Action, Message, Payload}; use mostro_core::order::{Order, Status}; use mostro_core::rating::Rating; use mostro_core::NOSTR_REPLACEABLE_EVENT_KIND; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; -use sqlx_crud::Crud; -use std::sync::Arc; use std::time::Duration; -use tokio::sync::Mutex; -use tracing::error; -pub const MAX_RATING: u8 = 5; -pub const MIN_RATING: u8 = 1; +pub fn prepare_variables_for_vote( + message_sender: &str, + order: &Order, +) -> Result<(String, String, bool, bool), MostroError> { + let mut counterpart: String = String::new(); + let mut counterpart_trade_pubkey: String = String::new(); + let mut buyer_rating: bool = false; + let mut seller_rating: bool = false; + + // Get needed info about users + let (seller, buyer) = match (&order.seller_pubkey, &order.buyer_pubkey) { + (Some(seller), Some(buyer)) => (seller.to_owned(), buyer.to_owned()), + (None, _) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), + (_, None) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), + }; -pub async fn get_user_reputation(user: &str, my_keys: &Keys) -> Result> { + // Find the counterpart public key + if message_sender == buyer { + counterpart = order + .get_master_seller_pubkey() + .map_err(MostroInternalErr)? + .to_string(); + buyer_rating = true; + counterpart_trade_pubkey = order + .get_buyer_pubkey() + .map_err(MostroInternalErr)? + .to_string(); + } else if message_sender == seller { + counterpart = order + .get_master_buyer_pubkey() + .map_err(MostroInternalErr)? + .to_string(); + seller_rating = true; + counterpart_trade_pubkey = order + .get_seller_pubkey() + .map_err(MostroInternalErr)? + .to_string(); + }; + // Add a check in case of no counterpart found + if counterpart.is_empty() { + return Err(MostroCantDo(CantDoReason::InvalidPeer)); + }; + Ok(( + counterpart, + counterpart_trade_pubkey, + buyer_rating, + seller_rating, + )) +} + +pub async fn get_user_reputation( + user: &str, + my_keys: &Keys, +) -> Result, MostroError> { // Request NIP33 of the counterparts let filters = Filter::new() .author(my_keys.public_key()) @@ -31,7 +79,8 @@ pub async fn get_user_reputation(user: &str, my_keys: &Keys) -> Result Result, - rate_list: Arc>>, -) -> Result<()> { - // Get request id - let request_id = msg.get_inner_message_kind().request_id; - - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { - order_id - } else { - return Err(Error::msg("No order id")); - }; - let order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - error!("Order Id {order_id} not found!"); - return Ok(()); - } - }; - - // Get needed info about users - let (seller, buyer) = match (&order.seller_pubkey, &order.buyer_pubkey) { - (Some(seller), Some(buyer)) => (seller.to_owned(), buyer.to_owned()), - (None, _) => return Err(Error::msg("Missing seller pubkey")), - (_, None) => return Err(Error::msg("Missing buyer pubkey")), - }; - - let message_sender = event.rumor.pubkey.to_string(); +) -> Result<(), MostroError> { + // Get order + let order = get_order(&msg, pool).await?; - if order.status != Status::Success.to_string() { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidOrderStatus), - &event.rumor.pubkey, - ) - .await; - error!("Order Id {order_id} wrong status"); - return Ok(()); + // Check if order is success + if order.check_status(Status::Success).is_err() { + return Err(MostroCantDo(CantDoReason::InvalidOrderStatus)); } - // Get counterpart pubkey - let mut counterpart: String = String::new(); - let mut counterpart_trade_pubkey: String = String::new(); - let mut buyer_rating: bool = false; - let mut seller_rating: bool = false; - // Find the counterpart public key - if message_sender == buyer { - counterpart = order - .master_seller_pubkey - .ok_or_else(|| Error::msg("Missing seller identity pubkey"))?; - buyer_rating = true; - counterpart_trade_pubkey = order - .buyer_pubkey - .ok_or_else(|| Error::msg("Missing buyer pubkey"))?; - } else if message_sender == seller { - counterpart = order - .master_buyer_pubkey - .ok_or_else(|| Error::msg("Missing buyer identity pubkey"))?; - seller_rating = true; - counterpart_trade_pubkey = order - .seller_pubkey - .ok_or_else(|| Error::msg("Missing seller pubkey"))?; - }; - - // Add a check in case of no counterpart found - if counterpart.is_empty() { - // We create a Message - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPeer), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - }; + // Prepare variables for vote + let (counterpart, counterpart_trade_pubkey, buyer_rating, seller_rating) = + prepare_variables_for_vote(&event.rumor.pubkey.to_string(), &order)?; // Check if the order is not rated by the message sender // Check what rate status needs update @@ -141,46 +128,20 @@ pub async fn update_user_reputation_action( return Ok(()); }; - // Check if content of Peer is the same of counterpart - let rating = - if let Some(Payload::RatingUser(v)) = msg.get_inner_message_kind().payload.to_owned() { - if !(MIN_RATING..=MAX_RATING).contains(&v) { - return Err(Error::msg(format!( - "Rating must be between {} and {}", - MIN_RATING, MAX_RATING - ))); - } - v - } else { - return Err(Error::msg("No rating present")); - }; + // Get rating from message + let new_rating = msg + .get_inner_message_kind() + .get_rating() + .map_err(MostroInternalErr)?; // Get counter to vote from db - let mut user_to_vote = is_user_present(pool, counterpart.clone()).await?; - - // Update user reputation - // Going on with calculation - // increment first - user_to_vote.total_reviews += 1; - let old_rating = user_to_vote.total_rating as f64; - // recompute new rating - if user_to_vote.total_reviews <= 1 { - user_to_vote.total_rating = rating.into(); - user_to_vote.max_rating = rating.into(); - user_to_vote.min_rating = rating.into(); - } else { - user_to_vote.total_rating = old_rating - + ((user_to_vote.last_rating as f64) - old_rating) - / (user_to_vote.total_reviews as f64); - if user_to_vote.max_rating < rating.into() { - user_to_vote.max_rating = rating.into(); - } - if user_to_vote.min_rating > rating.into() { - user_to_vote.min_rating = rating.into(); - } - } - // Store last rating - user_to_vote.last_rating = rating.into(); + let mut user_to_vote = is_user_present(pool, counterpart.clone()) + .await + .map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?; + + // Calculate new rating + user_to_vote.update_rating(new_rating); + // Create new rating event let reputation_event = Rating::new( user_to_vote.total_reviews as u64, @@ -189,7 +150,8 @@ pub async fn update_user_reputation_action( user_to_vote.min_rating as u8, user_to_vote.max_rating as u8, ) - .to_tags()?; + .to_tags() + .map_err(|cause| MostroInternalErr(ServiceError::NostrError(cause.to_string())))?; // Save new rating to db if let Err(e) = update_user_rating( @@ -203,7 +165,10 @@ pub async fn update_user_reputation_action( ) .await { - return Err(Error::msg(format!("Error updating user rating : {}", e))); + return Err(MostroInternalErr(ServiceError::DbAccessError(format!( + "Error updating user rating : {}", + e + )))); } if buyer_rating || seller_rating { @@ -213,20 +178,25 @@ pub async fn update_user_reputation_action( update_buyer_rate, update_seller_rate, reputation_event, - order.id, + &msg, my_keys, pool, - rate_list, ) - .await?; + .await + .map_err(|cause| { + MostroInternalErr(ServiceError::DbAccessError(format!( + "Error updating user rating event : {}", + cause + ))) + })?; // Send confirmation message to user that rated - send_new_order_msg( + enqueue_order_msg( msg.get_inner_message_kind().request_id, Some(order.id), Action::RateReceived, - Some(Payload::RatingUser(rating)), - &event.rumor.pubkey, + Some(Payload::RatingUser(new_rating)), + event.rumor.pubkey, None, ) .await; diff --git a/src/app/release.rs b/src/app/release.rs index 14a667ac..02a36766 100644 --- a/src/app/release.rs +++ b/src/app/release.rs @@ -3,13 +3,14 @@ use crate::db::{self}; use crate::lightning::LndConnector; use crate::lnurl::resolv_ln_address; use crate::util::{ - get_keys, get_nostr_client, send_cant_do_msg, send_new_order_msg, settle_seller_hold_invoice, - update_order_event, + enqueue_cant_do_msg, enqueue_order_msg, get_keys, get_nostr_client, get_order, + settle_seller_hold_invoice, update_order_event, }; use anyhow::{Error, Result}; use fedimint_tonic_lnd::lnrpc::payment::PaymentStatus; use lnurl::lightning_address::LightningAddress; -use mostro_core::message::{Action, CantDoReason, Message, Payload}; +use mostro_core::error::{CantDoReason, MostroError, MostroError::*, ServiceError}; +use mostro_core::message::{Action, Message, Payload}; use mostro_core::order::{Order, Status}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; @@ -20,40 +21,41 @@ use std::str::FromStr; use tokio::sync::mpsc::channel; use tracing::{error, info}; -pub async fn check_failure_retries(order: &Order, request_id: Option) -> Result { +/// Check if order has failed payment retries +pub async fn check_failure_retries( + order: &Order, + request_id: Option, +) -> Result { let mut order = order.clone(); // Handle to db here - let pool = db::connect().await?; + let pool = db::connect() + .await + .map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?; // Get max number of retries let ln_settings = Settings::get_ln(); let retries_number = ln_settings.payment_attempts as i64; + // Count payment retries up to limit + order.count_failed_payment(retries_number); - // Mark payment as failed - if !order.failed_payment { - order.failed_payment = true; - order.payment_attempts = 0; - } else if order.payment_attempts < retries_number { - order.payment_attempts += 1; - } - let buyer_pubkey = match &order.buyer_pubkey { - Some(buyer) => PublicKey::from_str(buyer.as_str())?, - None => return Err(Error::msg("Missing buyer pubkey")), - }; + let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?; - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::PaymentFailed, None, - &buyer_pubkey, + buyer_pubkey, None, ) .await; // Update order - let result = order.update(&pool).await?; + let result = order + .update(&pool) + .await + .map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?; Ok(result) } @@ -63,98 +65,51 @@ pub async fn release_action( my_keys: &Keys, pool: &Pool, ln_client: &mut LndConnector, -) -> Result<()> { +) -> Result<(), MostroError> { // Get request id let request_id = msg.get_inner_message_kind().request_id; - let order_id = msg - .get_inner_message_kind() - .id - .ok_or(Error::msg("Order ID is required but was not provided"))?; - - let mut order = Order::by_id(pool, order_id) - .await? - .ok_or(Error::msg(format!( - "Order {} not found in database", - order_id - )))?; - - let seller_pubkey_hex = order.seller_pubkey.as_ref().ok_or(Error::msg(format!( - "Seller public key not found for order {}", - order_id - )))?; - - // Only seller can release funds - if &event.rumor.pubkey.to_string() != seller_pubkey_hex { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPeer), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - } - - let next_trade: Option<(String, u32)> = match event.rumor.pubkey.to_string() { - pubkey if pubkey == order.creator_pubkey => { - if let Some(Payload::NextTrade(pubkey, index)) = &msg.get_inner_message_kind().payload { - Some((pubkey.clone(), *index)) - } else { - None - } - } - _ => match (order.next_trade_pubkey.as_ref(), order.next_trade_index) { - (Some(pubkey), Some(index)) => Some((pubkey.clone(), index as u32)), - _ => None, - }, - }; + // Get order + let mut order = get_order(&msg, pool).await?; + // Get seller pubkey hex + let seller_pubkey = order.get_seller_pubkey().map_err(MostroInternalErr)?; + // We send a message to buyer indicating seller released funds + let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?; - let current_status = - Status::from_str(&order.status).map_err(|_| Error::msg("Wrong order status"))?; + if seller_pubkey != event.rumor.pubkey { + return Err(MostroCantDo(CantDoReason::InvalidPeer)); + } - if !matches!( - current_status, - Status::Active | Status::FiatSent | Status::Dispute - ) { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + // Check if order is active, fiat sent or dispute + if order.check_status(Status::Active).is_err() + && order.check_status(Status::FiatSent).is_err() + && order.check_status(Status::Dispute).is_err() + { + return Err(MostroCantDo(CantDoReason::NotAllowedByStatus)); } - settle_seller_hold_invoice( - event, - ln_client, - Action::Released, - false, - &order, - request_id, - ) - .await?; + // Get next trade key + let next_trade = msg + .get_inner_message_kind() + .get_next_trade_key() + .map_err(MostroInternalErr)?; - // We send a message to buyer indicating seller released funds - let buyer_pubkey = PublicKey::from_str( - order - .buyer_pubkey - .as_ref() - .ok_or(Error::msg("Missing buyer pubkey"))? - .as_str(), - )?; - - send_new_order_msg( + // Settle seller hold invoice + settle_seller_hold_invoice(event, ln_client, Action::Released, false, &order).await?; + // Update order event with status SettledHoldInvoice + order = update_order_event(my_keys, Status::SettledHoldInvoice, &order) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + + enqueue_order_msg( None, - Some(order_id), + Some(order.id), Action::Released, None, - &buyer_pubkey, + buyer_pubkey, None, ) .await; - order = update_order_event(my_keys, Status::SettledHoldInvoice, &order).await?; + // Handle child order for range orders if let Ok((Some(child_order), Some(event))) = get_child_order(order.clone(), request_id, my_keys).await @@ -164,28 +119,30 @@ pub async fn release_action( tracing::warn!("Failed sending child order event for order id: {}. This may affect order synchronization", child_order.id) } } - handle_child_order(child_order, &order, next_trade, pool, request_id).await?; + handle_child_order(child_order, &order, next_trade, pool, request_id) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; } // We send a HoldInvoicePaymentSettled message to seller, the client should // indicate *funds released* message to seller - send_new_order_msg( + enqueue_order_msg( request_id, - Some(order_id), + Some(order.id), Action::HoldInvoicePaymentSettled, None, - &event.rumor.pubkey, + seller_pubkey, None, ) .await; - // Send DM to seller to rate counterpart - send_new_order_msg( - request_id, + // We send a message to seller indicating seller released funds + enqueue_order_msg( + None, Some(order.id), Action::Rate, None, - &event.rumor.pubkey, + seller_pubkey, None, ) .await; @@ -230,12 +187,12 @@ async fn handle_child_order( let new_order = child_order.as_new_order(); let next_trade_pubkey = PublicKey::from_str(&next_trade_pubkey)?; - send_new_order_msg( + enqueue_order_msg( request_id, new_order.id, Action::NewOrder, Some(Payload::Order(new_order)), - &next_trade_pubkey, + next_trade_pubkey, Some(next_trade_index as i64), ) .await; @@ -271,12 +228,12 @@ pub async fn do_payment(mut order: Order, request_id: Option) -> Result<()> } } - let my_keys = get_keys()?; + // Get Mostro keys + let my_keys = + get_keys().map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; - let buyer_pubkey = match &order.buyer_pubkey { - Some(buyer) => PublicKey::from_str(buyer.as_str())?, - None => return Err(Error::msg("Missing buyer pubkey")), - }; + // Get buyer and seller pubkeys + let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?; let payment = { async move { @@ -290,10 +247,8 @@ pub async fn do_payment(mut order: Order, request_id: Option) -> Result<()> "Order Id {}: Invoice with hash: {} paid!", order.id, msg.payment.payment_hash ); - - let _ = - payment_success(&mut order, &buyer_pubkey, &my_keys, request_id) - .await; + let _ = payment_success(&mut order, buyer_pubkey, &my_keys, request_id) + .await; } PaymentStatus::Failed => { info!( @@ -323,12 +278,12 @@ pub async fn do_payment(mut order: Order, request_id: Option) -> Result<()> async fn payment_success( order: &mut Order, - buyer_pubkey: &PublicKey, + buyer_pubkey: PublicKey, my_keys: &Keys, request_id: Option, ) -> Result<()> { // Purchase completed message to buyer - send_new_order_msg( + enqueue_order_msg( None, Some(order.id), Action::PurchaseCompleted, @@ -338,20 +293,26 @@ async fn payment_success( ) .await; + // Get db connection + let pool = db::connect() + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + if let Ok(order_updated) = update_order_event(my_keys, Status::Success, order).await { - let pool = db::connect().await?; - if let Ok(order) = order_updated.update(&pool).await { - // Send dm to buyer to rate counterpart - send_new_order_msg( - request_id, - Some(order.id), - Action::Rate, - None, - buyer_pubkey, - None, - ) - .await; - } + let order = order_updated + .update(&pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + // Send dm to buyer to rate counterpart + enqueue_order_msg( + request_id, + Some(order.id), + Action::Rate, + None, + buyer_pubkey, + None, + ) + .await; } Ok(()) } @@ -471,19 +432,19 @@ async fn notify_invalid_amount(order: &Order, request_id: Option) { } }; - send_cant_do_msg( + enqueue_cant_do_msg( None, Some(order.id), - Some(CantDoReason::InvalidAmount), - &buyer_pubkey, + CantDoReason::InvalidAmount, + buyer_pubkey, ) .await; - send_cant_do_msg( + enqueue_cant_do_msg( request_id, Some(order.id), - Some(CantDoReason::InvalidAmount), - &seller_pubkey, + CantDoReason::InvalidAmount, + seller_pubkey, ) .await; } diff --git a/src/app/take_buy.rs b/src/app/take_buy.rs index 271ea88a..fa611484 100644 --- a/src/app/take_buy.rs +++ b/src/app/take_buy.rs @@ -1,117 +1,84 @@ use crate::util::{ - get_fiat_amount_requested, get_market_amount_and_fee, send_cant_do_msg, show_hold_invoice, + get_fiat_amount_requested, get_market_amount_and_fee, get_order, show_hold_invoice, }; -use anyhow::{Error, Result}; -use mostro_core::message::{CantDoReason, Message}; -use mostro_core::order::{Kind, Order, Status}; +use crate::db::update_user_trade_index; +use anyhow::Result; +use mostro_core::error::MostroError::{self, *}; +use mostro_core::error::{CantDoReason, ServiceError}; +use mostro_core::message::Message; +use mostro_core::order::Status; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; -use sqlx_crud::Crud; -use std::str::FromStr; -use tracing::error; pub async fn take_buy_action( msg: Message, event: &UnwrappedGift, my_keys: &Keys, pool: &Pool, -) -> Result<()> { +) -> Result<(), MostroError> { + // Extract order ID from the message, returning an error if not found // Safe unwrap as we verified the message - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { - order_id - } else { - return Err(Error::msg("No order id")); - }; + let mut order = get_order(&msg, pool).await?; + // Get the request ID from the message let request_id = msg.get_inner_message_kind().request_id; - let mut order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - error!("Order Id {order_id} not found!"); - return Ok(()); - } + // Check if the order is a buy order and if its status is active + if let Err(cause) = order.is_buy_order() { + return Err(MostroCantDo(cause)); }; - - // Maker can't take own order - if order.kind != Kind::Buy.to_string() || order.creator_pubkey == event.rumor.pubkey.to_hex() { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPubkey), - &event.rumor.pubkey, - ) - .await; - return Ok(()); + // Check if the order status is pending + if let Err(cause) = order.check_status(Status::Pending) { + return Err(MostroCantDo(cause)); } - let order_status = match Status::from_str(&order.status) { - Ok(s) => s, - Err(e) => { - error!("Order Id {order_id} wrong status: {e:?}"); - return Ok(()); - } - }; - let buyer_pubkey = match order.buyer_pubkey.as_ref() { - Some(pk) => PublicKey::from_str(pk)?, - None => { - error!("Buyer pubkey not found for order {}!", order.id); - return Ok(()); - } - }; - - // We update the pubkey - let seller_pubkey = event.rumor.pubkey; - // Seller can take pending orders only - match order_status { - Status::Pending => {} - _ => { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); - } - } + // Validate that the order was sent from the correct maker + order + .not_sent_from_maker(event.rumor.pubkey) + .map_err(MostroCantDo)?; - // Get amount request if user requested one for range order - fiat amount will be used below + // Get the fiat amount requested by the user for range orders if let Some(am) = get_fiat_amount_requested(&order, &msg) { order.fiat_amount = am; } else { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::OutOfRangeFiatAmount), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); + return Err(MostroCantDo(CantDoReason::OutOfRangeSatsAmount)); } - // Check market price value in sats - if order was with market price then calculate - if order.amount == 0 { - let (new_sats_amount, fee) = - get_market_amount_and_fee(order.fiat_amount, &order.fiat_code, order.premium).await?; - // Update order with new sats value - order.amount = new_sats_amount; - order.fee = fee; + // If the order amount is zero, calculate the market price in sats + if order.has_no_amount() { + match get_market_amount_and_fee(order.fiat_amount, &order.fiat_code, order.premium).await { + Ok(amount_fees) => { + order.amount = amount_fees.0; + order.fee = amount_fees.1 + } + Err(_) => return Err(MostroInternalErr(ServiceError::WrongAmountError)), + }; } - // Add seller identity pubkey to order + // Get seller and buyer public keys + let seller_pubkey = event.rumor.pubkey; + let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?; + + // Add seller identity and trade index to the order order.master_seller_pubkey = Some(event.sender.to_string()); - // Add seller trade index to order order.trade_index_seller = msg.get_inner_message_kind().trade_index; - // Timestamp order take time - order.taken_at = Timestamp::now().as_u64() as i64; - show_hold_invoice( + // Timestamp the order take time + order.set_timestamp_now(); + + // Update trade index only after all checks are done + update_user_trade_index( + pool, + event.sender.to_string(), + msg.get_inner_message_kind().trade_index.unwrap(), + ) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + + // Show hold invoice and return success or error + if let Err(cause) = show_hold_invoice( my_keys, None, &buyer_pubkey, @@ -119,6 +86,11 @@ pub async fn take_buy_action( order, request_id, ) - .await?; + .await + { + return Err(MostroInternalErr(ServiceError::HoldInvoiceError( + cause.to_string(), + ))); + } Ok(()) } diff --git a/src/app/take_sell.rs b/src/app/take_sell.rs index 24511f44..1817ba38 100644 --- a/src/app/take_sell.rs +++ b/src/app/take_sell.rs @@ -1,178 +1,128 @@ -use crate::lightning::invoice::is_valid_invoice; use crate::util::{ - get_fiat_amount_requested, get_market_amount_and_fee, send_cant_do_msg, - set_waiting_invoice_status, show_hold_invoice, update_order_event, + get_fiat_amount_requested, get_market_amount_and_fee, get_order, set_waiting_invoice_status, + show_hold_invoice, update_order_event, validate_invoice, }; -use anyhow::{Error, Result}; -use mostro_core::message::{CantDoReason, Message}; -use mostro_core::order::{Kind, Order, Status}; +use mostro_core::error::MostroError::{self, *}; +use mostro_core::error::{CantDoReason, ServiceError}; + +use crate::db::update_user_trade_index; +use anyhow::Result; +use mostro_core::message::Message; +use mostro_core::order::{Order, Status}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; use sqlx_crud::Crud; -use std::str::FromStr; -use tracing::error; + +async fn update_order_status( + order: &mut Order, + my_keys: &Keys, + pool: &Pool, + request_id: Option, +) -> Result<(), MostroError> { + // Get buyer pubkey + let buyer_pubkey = order.get_buyer_pubkey().unwrap(); + // Set order status to waiting buyer invoice + match set_waiting_invoice_status(order, buyer_pubkey, request_id).await { + Ok(_) => { + // Update order status + match update_order_event(my_keys, Status::WaitingBuyerInvoice, order).await { + Ok(order_updated) => { + let _ = order_updated.update(pool).await; + Ok(()) + } + Err(_) => Err(MostroInternalErr(ServiceError::UpdateOrderStatusError)), + } + } + Err(_) => Err(MostroInternalErr(ServiceError::UpdateOrderStatusError)), + } +} pub async fn take_sell_action( msg: Message, event: &UnwrappedGift, my_keys: &Keys, pool: &Pool, -) -> Result<()> { +) -> Result<(), MostroError> { + // Get order + let mut order = get_order(&msg, pool).await?; + // Get request id let request_id = msg.get_inner_message_kind().request_id; - // Safe unwrap as we verified the message - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { - order_id - } else { - return Err(Error::msg("No order id")); - }; - - let mut order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - return Ok(()); - } - }; - - // Maker can't take own order - if order.creator_pubkey == event.rumor.pubkey.to_hex() { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPubkey), - &event.rumor.pubkey, - ) - .await; - return Ok(()); - } - - if order.kind != Kind::Sell.to_string() { - return Ok(()); - } - - // Get trade pubkey of the buyer - let buyer_trade_pubkey = event.rumor.pubkey; - - let seller_pubkey = match &order.seller_pubkey { - Some(seller) => PublicKey::from_str(seller.as_str())?, - _ => return Err(Error::msg("Missing seller pubkeys")), + // Check if the order is a sell order and if its status is active + if let Err(cause) = order.is_sell_order() { + return Err(MostroCantDo(cause)); }; - - let mut pr: Option = None; - // If a buyer sent me a lightning invoice we look on db an order with - // that order id and save the buyer pubkey and invoice fields - if let Some(payment_request) = msg.get_inner_message_kind().get_payment_request() { - pr = { - // Verify if invoice is valid - match is_valid_invoice( - payment_request.clone(), - Some(order.amount as u64), - Some(order.fee as u64), - ) - .await - { - Ok(_) => Some(payment_request), - Err(e) => { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidInvoice), - &event.rumor.pubkey, - ) - .await; - error!("{e}"); - return Ok(()); - } - } - }; + // Check if the order status is pending + if let Err(cause) = order.check_status(Status::Pending) { + return Err(MostroCantDo(cause)); } - let order_status = match Status::from_str(&order.status) { - Ok(s) => s, - Err(e) => { - error!("Order Id {order_id} wrong status: {e:?}"); - return Ok(()); - } - }; + // Validate that the order was sent from the correct maker + order + .not_sent_from_maker(event.rumor.pubkey) + .map_err(MostroCantDo)?; - // Buyer can take Pending orders only - match order_status { - Status::Pending => {} - _ => { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::NotAllowedByStatus), - &buyer_trade_pubkey, - ) - .await; + // Get seller pubkey + let seller_pubkey = order.get_seller_pubkey().map_err(MostroInternalErr)?; - return Ok(()); - } - } + // Validate invoice and get payment request if present + let payment_request = validate_invoice(&msg, &order).await?; // Get amount request if user requested one for range order - fiat amount will be used below if let Some(am) = get_fiat_amount_requested(&order, &msg) { order.fiat_amount = am; } else { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::OutOfRangeFiatAmount), - &event.rumor.pubkey, - ) - .await; - - return Ok(()); + return Err(MostroCantDo(CantDoReason::OutOfRangeSatsAmount)); } // Add buyer pubkey to order - order.buyer_pubkey = Some(buyer_trade_pubkey.to_string()); + order.buyer_pubkey = Some(event.rumor.pubkey.to_string()); // Add buyer identity pubkey to order order.master_buyer_pubkey = Some(event.sender.to_string()); // Add buyer trade index to order order.trade_index_buyer = msg.get_inner_message_kind().trade_index; // Timestamp take order time - order.taken_at = Timestamp::now().as_u64() as i64; + order.set_timestamp_now(); // Check market price value in sats - if order was with market price then calculate it and send a DM to buyer - if order.amount == 0 { - let (new_sats_amount, fee) = - get_market_amount_and_fee(order.fiat_amount, &order.fiat_code, order.premium).await?; - // Update order with new sats value - order.amount = new_sats_amount; - order.fee = fee; + if order.has_no_amount() { + match get_market_amount_and_fee(order.fiat_amount, &order.fiat_code, order.premium).await { + Ok(amount_fees) => { + order.amount = amount_fees.0; + order.fee = amount_fees.1 + } + Err(_) => return Err(MostroInternalErr(ServiceError::WrongAmountError)), + }; } - if pr.is_none() { - match set_waiting_invoice_status(&mut order, buyer_trade_pubkey, request_id).await { - Ok(_) => { - // Update order status - if let Ok(order_updated) = - update_order_event(my_keys, Status::WaitingBuyerInvoice, &order).await - { - let _ = order_updated.update(pool).await; - return Ok(()); - } - } - Err(e) => { - error!("Error setting market order sats amount: {:#?}", e); - return Ok(()); - } - } - } else { + // Update trade index only after all checks are done + update_user_trade_index( + pool, + event.sender.to_string(), + msg.get_inner_message_kind().trade_index.unwrap(), + ) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + + // If payment request is not present, update order status to waiting buyer invoice + if payment_request.is_none() { + update_order_status(&mut order, my_keys, pool, request_id).await?; + } + // If payment request is present, show hold invoice + else { show_hold_invoice( my_keys, - pr, - &buyer_trade_pubkey, + payment_request, + &event.rumor.pubkey, &seller_pubkey, order, request_id, ) .await?; } + Ok(()) } diff --git a/src/app/trade_pubkey.rs b/src/app/trade_pubkey.rs new file mode 100644 index 00000000..8f7ec783 --- /dev/null +++ b/src/app/trade_pubkey.rs @@ -0,0 +1,58 @@ +use crate::util::{enqueue_order_msg, get_order}; + +use mostro_core::error::MostroError::{self, *}; +use mostro_core::error::ServiceError; +use mostro_core::message::{Action, Message}; +use mostro_core::order::Status; +use nostr::nips::nip59::UnwrappedGift; +use nostr_sdk::prelude::*; +use sqlx::{Pool, Sqlite}; +use sqlx_crud::Crud; + +pub async fn trade_pubkey_action( + msg: Message, + event: &UnwrappedGift, + pool: &Pool, +) -> Result<(), MostroError> { + // Get request id + let request_id = msg.get_inner_message_kind().request_id; + // Get order + let mut order = get_order(&msg, pool).await?; + + // Check if the order status is pending + if let Err(cause) = order.check_status(Status::Pending) { + return Err(MostroCantDo(cause)); + } + + match ( + order.master_buyer_pubkey.as_ref(), + order.master_seller_pubkey.as_ref(), + ) { + (Some(master_buyer_pubkey), _) if master_buyer_pubkey == &event.sender.to_string() => { + order.buyer_pubkey = Some(event.rumor.pubkey.to_string()); + } + (_, Some(master_seller_pubkey)) if master_seller_pubkey == &event.sender.to_string() => { + order.seller_pubkey = Some(event.rumor.pubkey.to_string()); + } + _ => return Err(MostroInternalErr(ServiceError::InvalidPubkey)), + }; + order.creator_pubkey = event.rumor.pubkey.to_string(); + + // We a message to the seller + enqueue_order_msg( + request_id, + Some(order.id), + Action::TradePubkey, + None, + event.rumor.pubkey, + None, + ) + .await; + + order + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + + Ok(()) +} diff --git a/src/db.rs b/src/db.rs index b6a7d3fa..9c30ff27 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,6 +1,6 @@ -use crate::app::rate_user::{MAX_RATING, MIN_RATING}; use anyhow::Result; use mostro_core::dispute::Dispute; +use mostro_core::message::{MAX_RATING, MIN_RATING}; use mostro_core::order::Order; use mostro_core::order::Status; use mostro_core::user::User; diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index fcce1f40..00000000 --- a/src/error.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::fmt; - -#[derive(Debug, PartialEq, Eq)] -pub enum MostroError { - ParsingInvoiceError, - ParsingNumberError, - InvoiceExpiredError, - MinExpirationTimeError, - MinAmountError, - WrongAmountError, - NoAPIResponse, - NoCurrency, - MalformedAPIRes, - NegativeAmount, - LnAddressParseError, - LnAddressWrongAmount, - LnPaymentError(String), - LnNodeError(String), - InvalidOrderKind, -} - -impl std::error::Error for MostroError {} - -impl fmt::Display for MostroError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - MostroError::ParsingInvoiceError => write!(f, "Incorrect invoice"), - MostroError::ParsingNumberError => write!(f, "Error parsing the number"), - MostroError::InvoiceExpiredError => write!(f, "Invoice has expired"), - MostroError::MinExpirationTimeError => write!(f, "Minimal expiration time on invoice"), - MostroError::MinAmountError => write!(f, "Minimal payment amount"), - MostroError::WrongAmountError => write!(f, "The amount on this invoice is wrong"), - MostroError::NoAPIResponse => write!(f, "Price API not answered - retry"), - MostroError::NoCurrency => write!(f, "Currency requested is not present in the exchange list, please specify a fixed rate"), - MostroError::MalformedAPIRes => write!(f, "Malformed answer from exchange quoting request"), - MostroError::NegativeAmount => write!(f, "Negative amount is not valid"), - MostroError::LnAddressWrongAmount => write!(f, "Ln address need amount of 0 sats - please check your order"), - MostroError::LnAddressParseError => write!(f, "Ln address parsing error - please check your address"), - MostroError::LnPaymentError(e) => write!(f, "Lightning payment failure cause: {}",e), - MostroError::LnNodeError(e) => write!(f, "Lightning node connection failure caused by: {}",e), - MostroError::InvalidOrderKind => write!(f, "Invalid order kind"), - } - } -} - -impl From for MostroError { - fn from(_: lightning_invoice::Bolt11ParseError) -> Self { - MostroError::ParsingInvoiceError - } -} - -impl From for MostroError { - fn from(_: lightning_invoice::ParseOrSemanticError) -> Self { - MostroError::ParsingInvoiceError - } -} - -impl From for MostroError { - fn from(_: std::num::ParseIntError) -> Self { - MostroError::ParsingNumberError - } -} - -impl From for MostroError { - fn from(_: reqwest::Error) -> Self { - MostroError::NoAPIResponse - } -} diff --git a/src/flow.rs b/src/flow.rs index e05f563b..4dcf4ca9 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -1,38 +1,36 @@ -use crate::util::send_new_order_msg; -use anyhow::{Error, Result}; +use crate::util::enqueue_order_msg; +use mostro_core::error::MostroError::{self, MostroInternalErr}; +use mostro_core::error::ServiceError; use mostro_core::message::{Action, Payload}; -use mostro_core::order::{Kind, SmallOrder, Status}; +use mostro_core::order::{SmallOrder, Status}; use nostr_sdk::prelude::*; use sqlx_crud::Crud; -use std::str::FromStr; -use tracing::{error, info}; +use tracing::info; -pub async fn hold_invoice_paid(hash: &str, request_id: Option) -> Result<()> { - let pool = crate::db::connect().await?; - let order = crate::db::find_order_by_hash(&pool, hash).await?; - let my_keys = crate::util::get_keys()?; +pub async fn hold_invoice_paid(hash: &str, request_id: Option) -> Result<(), MostroError> { + let pool = crate::db::connect() + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + let order = crate::db::find_order_by_hash(&pool, hash) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + let my_keys = crate::util::get_keys() + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; - let (seller_pubkey, buyer_pubkey) = match (&order.seller_pubkey, &order.buyer_pubkey) { - (Some(seller), Some(buyer)) => ( - PublicKey::from_str(seller.as_str())?, - PublicKey::from_str(buyer.as_str())?, - ), - (None, _) => return Err(Error::msg("Missing seller pubkey")), - (_, None) => return Err(Error::msg("Missing buyer pubkey")), - }; + let buyer_pubkey = order + .get_buyer_pubkey() + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + let seller_pubkey = order + .get_seller_pubkey() + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; info!( "Order Id: {} - Seller paid invoice with hash: {hash}", order.id ); - let order_kind = match Kind::from_str(&order.kind) { - Ok(k) => k, - Err(e) => { - error!("Order Id {} wrong kind: {:?}", order.id, e); - return Err(Error::msg("Order state is not valid")); - } - }; + // Check if the order kind is valid + let order_kind = order.get_order_kind().map_err(MostroInternalErr)?; // We send this data related to the order to the parties let mut order_data = SmallOrder::new( @@ -60,22 +58,22 @@ pub async fn hold_invoice_paid(hash: &str, request_id: Option) -> Result<() status = Status::Active; order_data.status = Some(status); // We send a confirmation message to seller - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::BuyerTookOrder, Some(Payload::Order(order_data.clone())), - &seller_pubkey, + seller_pubkey, None, ) .await; // We send a message to buyer saying seller paid - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::HoldInvoicePaymentAccepted, Some(Payload::Order(order_data)), - &buyer_pubkey, + buyer_pubkey, None, ) .await; @@ -87,23 +85,23 @@ pub async fn hold_invoice_paid(hash: &str, request_id: Option) -> Result<() order_data.buyer_trade_pubkey = None; order_data.seller_trade_pubkey = None; // We ask to buyer for a new invoice - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::AddInvoice, Some(Payload::Order(order_data)), - &buyer_pubkey, + buyer_pubkey, None, ) .await; // We send a message to seller we are waiting for buyer invoice - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::WaitingBuyerInvoice, None, - &seller_pubkey, + seller_pubkey, None, ) .await; @@ -117,7 +115,8 @@ pub async fn hold_invoice_paid(hash: &str, request_id: Option) -> Result<() // Update the invoice_held_at field crate::db::update_order_invoice_held_at_time(&pool, order.id, Timestamp::now().as_u64() as i64) - .await?; + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; Ok(()) } diff --git a/src/lightning/invoice.rs b/src/lightning/invoice.rs index d0e58299..b4eef378 100644 --- a/src/lightning/invoice.rs +++ b/src/lightning/invoice.rs @@ -1,16 +1,17 @@ use crate::cli::settings::Settings; -use crate::error::MostroError; use crate::lnurl::ln_exists; use chrono::prelude::*; use chrono::TimeDelta; use lightning_invoice::{Bolt11Invoice, SignedRawBolt11Invoice}; use lnurl::lightning_address::LightningAddress; +use mostro_core::error::{MostroError, MostroError::MostroInternalErr, ServiceError}; use std::str::FromStr; /// Decode a lightning invoice (bolt11) pub fn decode_invoice(payment_request: &str) -> Result { - let invoice = Bolt11Invoice::from_str(payment_request)?; + let invoice = Bolt11Invoice::from_str(payment_request) + .map_err(|_| MostroInternalErr(ServiceError::InvoiceInvalidError))?; Ok(invoice) } @@ -27,7 +28,7 @@ pub async fn is_valid_invoice( // Is it a ln address if ln_addr.is_ok() { if ln_exists(&payment_request).await.is_err() { - return Err(MostroError::ParsingInvoiceError); + return Err(MostroInternalErr(ServiceError::InvoiceInvalidError)); } } else { let invoice = decode_invoice(&payment_request)?; @@ -41,23 +42,25 @@ pub async fn is_valid_invoice( if let Some(amt) = amount { if let Some(res) = amt.checked_sub(fee) { if amount_sat != res && amount_sat != 0 { - return Err(MostroError::WrongAmountError); + return Err(MostroInternalErr(ServiceError::InvoiceInvalidError)); } } else { //case overflow in subtraction - return Err(MostroError::WrongAmountError); + return Err(MostroInternalErr(ServiceError::InvoiceInvalidError)); } } if amount_sat > 0 && amount_sat < mostro_settings.min_payment_amount as u64 { - return Err(MostroError::MinAmountError); + return Err(MostroInternalErr(ServiceError::InvoiceInvalidError)); } if invoice.is_expired() { - return Err(MostroError::InvoiceExpiredError); + return Err(MostroInternalErr(ServiceError::InvoiceInvalidError)); } - let parsed = payment_request.parse::()?; + let parsed = payment_request + .parse::() + .map_err(|_| MostroInternalErr(ServiceError::InvoiceInvalidError))?; let (parsed_invoice, _, _) = parsed.into_parts(); @@ -69,7 +72,7 @@ pub async fn is_valid_invoice( invoice.expiry_time().as_secs() + parsed_invoice.data.timestamp.as_unix_timestamp(); if expires_at < latest_date { - return Err(MostroError::MinExpirationTimeError); + return Err(MostroInternalErr(ServiceError::InvoiceInvalidError)); } } @@ -82,8 +85,8 @@ mod tests { use std::path::PathBuf; use super::is_valid_invoice; - use crate::{cli::settings::Settings, error::MostroError, MOSTRO_CONFIG}; - + use crate::{cli::settings::Settings, MOSTRO_CONFIG}; + use mostro_core::error::{MostroError::MostroInternalErr, ServiceError}; fn init_settings_test() { let test_path = PathBuf::from("./"); set_var("RUN_MODE", ".tpl"); @@ -95,7 +98,10 @@ mod tests { init_settings_test(); let payment_request = "lnbcrt500u1p3l8zyapp5nc0ctxjt98xq9tgdgk9m8fepnp0kv6mnj6a83mfsannw46awdp4sdqqcqzpgxqyz5vqsp5a3axmz77s5vafmheq56uh49rmy59r9a3d0dm0220l8lzdp5jrtxs9qyyssqu0ft47j0r4lu997zuqgf92y8mppatwgzhrl0hzte7mzmwrqzf2238ylch82ehhv7pfcq6qcyu070dg85vu55het2edyljuezvcw5pzgqfncf3d".to_string(); let wrong_amount_err = is_valid_invoice(payment_request, Some(23), None); - assert_eq!(Err(MostroError::WrongAmountError), wrong_amount_err.await); + assert_eq!( + Err(MostroInternalErr(ServiceError::InvoiceInvalidError)), + wrong_amount_err.await + ); } #[tokio::test] @@ -103,7 +109,10 @@ mod tests { init_settings_test(); let payment_request = "lnbcrt500u1p3lzwdzpp5t9kgwgwd07y2lrwdscdnkqu4scrcgpm5pt9uwx0rxn5rxawlxlvqdqqcqzpgxqyz5vqsp5a6k7syfxeg8jy63rteywwjla5rrg2pvhedx8ajr2ltm4seydhsqq9qyyssq0n2uwlumsx4d0mtjm8tp7jw3y4da6p6z9gyyjac0d9xugf72lhh4snxpugek6n83geafue9ndgrhuhzk98xcecu2t3z56ut35mkammsqscqp0n".to_string(); let expired_err = is_valid_invoice(payment_request, None, None); - assert_eq!(Err(MostroError::InvoiceExpiredError), expired_err.await); + assert_eq!( + Err(MostroInternalErr(ServiceError::InvoiceInvalidError)), + expired_err.await + ); } #[tokio::test] @@ -111,6 +120,9 @@ mod tests { init_settings_test(); let payment_request = "lnbcrt10n1pjwqagdpp5qwa89czezks35s73fkjspxdssh7h4mmfs4643ey7fgxlng4d3jxqdqqcqzpgxqyz5vqsp5jjlmj6hlq0zxsg5t7n6h6a95ux3ej2w3w2csvdgcpndyvut3aaqs9qyyssqg6py7mmjlcgrscvvq4x3c6kr6f6reqanwkk7rjajm4wepggh4lnku3msrjt3045l0fsl4trh3ctg8ew756wq86mz72mguusey7m0a5qq83t8n6".to_string(); let min_amount_err = is_valid_invoice(payment_request, None, None); - assert_eq!(Err(MostroError::MinAmountError), min_amount_err.await); + assert_eq!( + Err(MostroInternalErr(ServiceError::InvoiceInvalidError)), + min_amount_err.await + ); } } diff --git a/src/lightning/mod.rs b/src/lightning/mod.rs index 511828e4..944c5fa3 100644 --- a/src/lightning/mod.rs +++ b/src/lightning/mod.rs @@ -1,7 +1,6 @@ pub mod invoice; use crate::cli::settings::Settings; -use crate::error::MostroError; use crate::lightning::invoice::decode_invoice; use crate::util::bytes_to_string; @@ -14,6 +13,10 @@ use fedimint_tonic_lnd::invoicesrpc::{ use fedimint_tonic_lnd::lnrpc::{invoice::InvoiceState, GetInfoRequest, GetInfoResponse, Payment}; use fedimint_tonic_lnd::routerrpc::{SendPaymentRequest, TrackPaymentRequest}; use fedimint_tonic_lnd::Client; +use mostro_core::error::{ + MostroError::{self, *}, + ServiceError, +}; use nostr_sdk::nostr::hashes::hex::FromHex; use nostr_sdk::nostr::secp256k1::rand::{self, RngCore}; use std::cmp::Ordering; @@ -36,7 +39,7 @@ pub struct PaymentMessage { } impl LndConnector { - pub async fn new() -> anyhow::Result { + pub async fn new() -> Result { let ln_settings = Settings::get_ln(); // Connecting to LND requires only host, port, cert file, and macaroon file @@ -46,7 +49,7 @@ impl LndConnector { ln_settings.lnd_macaroon_file, ) .await - .map_err(|e| MostroError::LnNodeError(e.to_string()))?; + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))?; // Safe unwrap here Ok(Self { client }) @@ -75,11 +78,11 @@ impl LndConnector { .invoices() .add_hold_invoice(invoice) .await - .map_err(|e| MostroError::LnNodeError(e.to_string())); + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string()))); match holdinvoice { Ok(holdinvoice) => Ok((holdinvoice.into_inner(), preimage.to_vec(), hash.to_vec())), - Err(e) => Err(e), + Err(e) => Err(MostroInternalErr(ServiceError::LnNodeError(e.to_string()))), } } @@ -87,7 +90,7 @@ impl LndConnector { &mut self, r_hash: Vec, listener: Sender, - ) -> anyhow::Result<()> { + ) -> Result<(), MostroError> { let invoice_stream = self .client .invoices() @@ -97,16 +100,17 @@ impl LndConnector { }, ) .await - .map_err(|e| MostroError::LnNodeError(e.to_string()))?; + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))?; let mut inner_invoice = invoice_stream.into_inner(); while let Some(invoice) = inner_invoice .message() .await - .map_err(|e| MostroError::LnNodeError(e.to_string()))? + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))? { - let state = fedimint_tonic_lnd::lnrpc::invoice::InvoiceState::try_from(invoice.state)?; + let state = fedimint_tonic_lnd::lnrpc::invoice::InvoiceState::try_from(invoice.state) + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))?; { let msg = InvoiceMessage { hash: r_hash.clone(), @@ -116,7 +120,7 @@ impl LndConnector { .clone() .send(msg) .await - .map_err(|e| MostroError::LnNodeError(e.to_string()))? + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))? } } Ok(()) @@ -134,7 +138,7 @@ impl LndConnector { .invoices() .settle_invoice(preimage_message) .await - .map_err(|e| MostroError::LnNodeError(e.to_string())); + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string()))); match settle { Ok(settle) => Ok(settle.into_inner()), @@ -154,11 +158,11 @@ impl LndConnector { .invoices() .cancel_invoice(cancel_message) .await - .map_err(|e| MostroError::LnNodeError(e.to_string())); + .map_err(|e| e.to_string()); match cancel { Ok(cancel) => Ok(cancel.into_inner()), - Err(e) => Err(e), + Err(e) => Err(MostroInternalErr(ServiceError::LnNodeError(e.to_string()))), } } @@ -190,12 +194,14 @@ impl LndConnector { .router() .track_payment_v2(track_payment_req) .await - .map_err(|e| MostroError::LnPaymentError(e.to_string())); + .map_err(|e| MostroInternalErr(ServiceError::LnPaymentError(e.to_string()))); // We only send the payment if it wasn't attempted before if track.is_ok() { info!("Aborting paying invoice with hash {} to buyer", hash); - return Err(MostroError::LnPaymentError("Track error".to_string())); + return Err(MostroInternalErr(ServiceError::LnPaymentError( + "Track error".to_string(), + ))); } let mut request = SendPaymentRequest { @@ -212,7 +218,9 @@ impl LndConnector { "Aborting paying invoice with wrong amount to buyer, hash: {}", hash ); - return Err(MostroError::LnPaymentError("Wrong amount".to_string())); + return Err(MostroInternalErr(ServiceError::LnPaymentError( + "Wrong amount".to_string(), + ))); } } None => { @@ -229,17 +237,17 @@ impl LndConnector { .router() .send_payment_v2(request) .await - .map_err(|e| MostroError::LnPaymentError(e.to_string())); + .map_err(|e| MostroInternalErr(ServiceError::LnPaymentError(e.to_string()))); // We can safely unwrap here cause await was successful let mut stream = outer_stream - .map_err(|e| MostroError::LnPaymentError(e.to_string()))? + .map_err(|e| MostroInternalErr(ServiceError::LnPaymentError(e.to_string())))? .into_inner(); while let Ok(Some(payment)) = stream .message() .await - .map_err(|e| MostroError::LnPaymentError(e.to_string())) + .map_err(|e| MostroInternalErr(ServiceError::LnPaymentError(e.to_string()))) { // ("Failed paying invoice") { let msg = PaymentMessage { payment }; @@ -247,7 +255,7 @@ impl LndConnector { .clone() .send(msg) .await - .map_err(|e| MostroError::LnNodeError(e.to_string()))? + .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))? } Ok(()) @@ -258,7 +266,7 @@ impl LndConnector { match info { Ok(i) => Ok(i.into_inner()), - Err(e) => Err(MostroError::LnNodeError(e.to_string())), + Err(e) => Err(MostroInternalErr(ServiceError::LnNodeError(e.to_string()))), } } } diff --git a/src/lnurl.rs b/src/lnurl.rs index 4c8a40b4..d114bdba 100644 --- a/src/lnurl.rs +++ b/src/lnurl.rs @@ -1,28 +1,35 @@ -use crate::error::MostroError; use anyhow::{Context, Result}; +use mostro_core::error::{ + MostroError::{self, *}, + ServiceError, +}; use serde_json::Value; pub async fn ln_exists(address: &str) -> Result<(), MostroError> { let (user, domain) = match address.split_once('@') { Some((user, domain)) => (user, domain), - None => return Err(MostroError::LnAddressParseError), + None => return Err(MostroInternalErr(ServiceError::LnAddressParseError)), }; let url = format!("https://{domain}/.well-known/lnurlp/{user}"); let res = reqwest::get(url) .await - .map_err(|_| MostroError::NoAPIResponse)?; + .map_err(|_| MostroInternalErr(ServiceError::NoAPIResponse))?; let status = res.status(); if status.is_success() { - let body = res.text().await?; - let body: Value = serde_json::from_str(&body).map_err(|_| MostroError::MalformedAPIRes)?; + let body = res + .text() + .await + .map_err(|_| MostroInternalErr(ServiceError::NoAPIResponse))?; + let body: Value = serde_json::from_str(&body) + .map_err(|_| MostroInternalErr(ServiceError::MalformedAPIRes))?; let tag = body["tag"].as_str().unwrap_or(""); if tag == "payRequest" { return Ok(()); } - Err(MostroError::LnAddressParseError) + Err(MostroInternalErr(ServiceError::LnAddressParseError)) } else { - Err(MostroError::LnAddressParseError) + Err(MostroInternalErr(ServiceError::LnAddressParseError)) } } diff --git a/src/main.rs b/src/main.rs index 78b79fc5..50f8d889 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ pub mod app; mod bitcoin_price; pub mod cli; pub mod db; -pub mod error; pub mod flow; pub mod lightning; pub mod lnurl; @@ -19,13 +18,14 @@ use crate::lightning::LnStatus; use anyhow::Result; use db::find_held_invoices; use lightning::LndConnector; +use mostro_core::message::Message; use nostr_sdk::prelude::*; use scheduler::start_scheduler; use std::env; use std::process::exit; -use std::sync::Arc; use std::sync::OnceLock; -use tokio::sync::Mutex; +use std::sync::{Arc, LazyLock}; +use tokio::sync::{Mutex, RwLock}; use tracing::{error, info}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use util::{get_nostr_client, invoice_subscribe}; @@ -34,6 +34,16 @@ static MOSTRO_CONFIG: OnceLock = OnceLock::new(); static NOSTR_CLIENT: OnceLock = OnceLock::new(); static LN_STATUS: OnceLock = OnceLock::new(); +#[derive(Debug, Clone, Default)] +pub struct MessageQueues { + pub queue_order_msg: Arc>>, + pub queue_order_cantdo: Arc>>, + pub queue_order_rate: Arc>>, +} + +static MESSAGE_QUEUES: LazyLock> = + LazyLock::new(|| RwLock::new(MessageQueues::default())); + #[tokio::main] async fn main() -> Result<()> { if cfg!(debug_assertions) { @@ -50,8 +60,6 @@ async fn main() -> Result<()> { .with(EnvFilter::from_default_env()) .init(); - let rate_list: Arc>> = Arc::new(Mutex::new(vec![])); - // Init path from cli let config_path = settings_init()?; @@ -104,9 +112,9 @@ async fn main() -> Result<()> { } // Start scheduler for tasks - start_scheduler(rate_list.clone()).await; + start_scheduler().await; - run(my_keys, client, &mut ln_client, pool, rate_list.clone()).await + run(my_keys, client, &mut ln_client, pool).await } #[cfg(test)] diff --git a/src/scheduler.rs b/src/scheduler.rs index 338ff179..e5174a70 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -1,41 +1,108 @@ use crate::app::release::do_payment; use crate::bitcoin_price::BitcoinPriceManager; use crate::cli::settings::Settings; -use crate::db::*; use crate::lightning::LndConnector; use crate::util; use crate::util::get_nostr_client; use crate::LN_STATUS; +use crate::{db::*, MESSAGE_QUEUES}; +use crate::{Keys, PublicKey}; use chrono::{TimeDelta, Utc}; +use mostro_core::message::Message; use mostro_core::order::{Kind, Status}; use nostr_sdk::EventBuilder; -use nostr_sdk::{Event, Kind as NostrKind, Tag}; +use nostr_sdk::{Kind as NostrKind, Tag}; use sqlx_crud::Crud; use std::sync::Arc; use tokio::sync::Mutex; use tracing::{error, info}; -use util::{get_keys, get_nostr_relays, update_order_event}; +use util::{get_keys, get_nostr_relays, send_dm, update_order_event}; -pub async fn start_scheduler(rate_list: Arc>>) { +pub async fn start_scheduler() { info!("Creating scheduler"); job_expire_pending_older_orders().await; - job_update_rate_events(rate_list).await; + job_update_rate_events().await; let _ = job_cancel_orders().await; job_retry_failed_payments().await; job_info_event_send().await; job_relay_list().await; job_update_bitcoin_prices().await; + job_flush_messages_queue().await; info!("Scheduler Started"); } +async fn job_flush_messages_queue() { + // Clone for closure owning with Arc + let order_msg_list = MESSAGE_QUEUES.read().await.queue_order_msg.clone(); + let cantdo_msg_list = MESSAGE_QUEUES.read().await.queue_order_cantdo.clone(); + let sender_keys = match get_keys() { + Ok(keys) => keys, + Err(e) => return error!("{e}"), + }; + + // Helper function to send messages + async fn send_messages( + msg_list: Arc>>, + sender_keys: Keys, + retries: &mut usize, + ) { + if !msg_list.lock().await.is_empty() { + let (message, destination_key) = msg_list.lock().await[0].clone(); + match message.as_json() { + Ok(msg) => { + if let Err(e) = send_dm(destination_key, sender_keys.clone(), msg, None).await { + error!("Failed to send message: {}", e); + *retries += 1; + } else { + *retries = 0; + msg_list.lock().await.remove(0); + } + } + Err(e) => error!("Failed to parse message: {}", e), + } + if *retries > 3 { + *retries = 0; // Reset retries after removing message + msg_list.lock().await.remove(0); + } + } + } + + // Spawn a new task to flush the messages queue + tokio::spawn(async move { + let mut retries_messages = 0; + let mut retries_cantdo_messages = 0; + + loop { + send_messages( + order_msg_list.clone(), + sender_keys.clone(), + &mut retries_messages, + ) + .await; + send_messages( + cantdo_msg_list.clone(), + sender_keys.clone(), + &mut retries_cantdo_messages, + ) + .await; + + tokio::time::sleep(tokio::time::Duration::from_millis(250)).await; + } + }); +} + async fn job_relay_list() { let mostro_keys = match get_keys() { Ok(keys) => keys, Err(e) => return error!("{e}"), }; + let client = match get_nostr_client() { + Ok(client) => client, + Err(e) => return error!("{e}"), + }; tokio::spawn(async move { loop { @@ -54,9 +121,7 @@ async fn job_relay_list() { if let Ok(relay_ev) = EventBuilder::new(NostrKind::RelayList, "").sign_with_keys(&mostro_keys) { - if let Ok(client) = get_nostr_client() { - let _ = client.send_event(relay_ev).await; - } + let _ = client.send_event(relay_ev).await; } } tokio::time::sleep(tokio::time::Duration::from_secs(interval)).await; @@ -69,6 +134,10 @@ async fn job_info_event_send() { Ok(keys) => keys, Err(e) => return error!("{e}"), }; + let client = match get_nostr_client() { + Ok(client) => client, + Err(e) => return error!("{e}"), + }; let interval = Settings::get_mostro().publish_mostro_info_interval as u64; let ln_status = LN_STATUS.get().unwrap(); tokio::spawn(async move { @@ -83,9 +152,7 @@ async fn job_info_event_send() { Err(e) => return error!("{e}"), }; - if let Ok(client) = get_nostr_client() { - let _ = client.send_event(info_ev).await; - } + let _ = client.send_event(info_ev).await; tokio::time::sleep(tokio::time::Duration::from_secs(interval)).await; } @@ -123,11 +190,15 @@ async fn job_retry_failed_payments() { }); } -async fn job_update_rate_events(rate_list: Arc>>) { +async fn job_update_rate_events() { // Clone for closure owning with Arc - let inner_list = rate_list.clone(); + let queue_order_rate = MESSAGE_QUEUES.read().await.queue_order_rate.clone(); let mostro_settings = Settings::get_mostro(); let interval = mostro_settings.user_rates_sent_interval_seconds as u64; + let client = match get_nostr_client() { + Ok(client) => client, + Err(e) => return error!("{e}"), + }; tokio::spawn(async move { loop { @@ -136,22 +207,13 @@ async fn job_update_rate_events(rate_list: Arc>>) { interval ); - for ev in inner_list.lock().await.iter() { + for ev in queue_order_rate.lock().await.iter() { // Send event to relay - if let Ok(client) = get_nostr_client() { - match client.send_event(ev.clone()).await { - Ok(id) => { - info!("Updated rate event with id {:?}", id) - } - Err(e) => { - info!("Error on updating rate event {:?}", e.to_string()) - } - } - } + let _ = client.send_event(ev.clone()).await; } // Clear list after send events - inner_list.lock().await.clear(); + queue_order_rate.lock().await.clear(); let now = Utc::now(); if let Some(next_tick) = now.checked_add_signed( diff --git a/src/util.rs b/src/util.rs index 2665d7ce..4a7ec451 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,34 +2,36 @@ use crate::app::rate_user::get_user_reputation; use crate::bitcoin_price::BitcoinPriceManager; use crate::cli::settings::Settings; use crate::db; -use crate::error::MostroError; use crate::flow; use crate::lightning; +use crate::lightning::invoice::is_valid_invoice; use crate::lightning::LndConnector; use crate::messages; use crate::models::Yadio; use crate::nip33::{new_event, order_to_tags}; +use crate::MESSAGE_QUEUES; use crate::NOSTR_CLIENT; -use anyhow::{Context, Error, Result}; +use anyhow::Context; use chrono::Duration; -use mostro_core::message::CantDoReason; +use mostro_core::error::CantDoReason; +use mostro_core::error::MostroError::{self, *}; +use mostro_core::error::ServiceError; use mostro_core::message::{Action, Message, Payload}; use mostro_core::order::{Kind as OrderKind, Order, SmallOrder, Status}; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; +use sqlx::Pool; +use sqlx::Sqlite; use sqlx::SqlitePool; use sqlx_crud::Crud; use std::fmt::Write; use std::str::FromStr; -use std::sync::Arc; use std::thread; use tokio::sync::mpsc::channel; -use tokio::sync::Mutex; // use fedimint_tonic_lnd::Client; use fedimint_tonic_lnd::lnrpc::invoice::InvoiceState; use std::collections::HashMap; -use tracing::error; use tracing::info; use uuid::Uuid; @@ -60,9 +62,9 @@ pub async fn retries_yadio_request( Ok((Some(res), fiat_list_check)) } -pub fn get_bitcoin_price(fiat_code: &str) -> Result { +pub fn get_bitcoin_price(fiat_code: &str) -> Result { BitcoinPriceManager::get_price(fiat_code) - .ok_or_else(|| anyhow::anyhow!("Failed to get Bitcoin price")) + .ok_or(MostroError::MostroInternalErr(ServiceError::NoAPIResponse)) } /// Request market quote from Yadio to have sats amount at actual market price @@ -103,22 +105,28 @@ pub async fn get_market_quote( // Case no answers from Yadio if no_answer_api { - return Err(MostroError::NoAPIResponse); + return Err(MostroError::MostroInternalErr(ServiceError::NoAPIResponse)); } // No currency present if !req.1 { - return Err(MostroError::NoCurrency); + return Err(MostroError::MostroInternalErr(ServiceError::NoCurrency)); } if req.0.is_none() { - return Err(MostroError::MalformedAPIRes); + return Err(MostroError::MostroInternalErr( + ServiceError::MalformedAPIRes, + )); } let quote = if let Some(q) = req.0 { - q.json::().await? + q.json::() + .await + .map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))? } else { - return Err(MostroError::MalformedAPIRes); + return Err(MostroError::MostroInternalErr( + ServiceError::MalformedAPIRes, + )); }; let mut sats = quote.result * 100_000_000_f64; @@ -166,7 +174,7 @@ pub async fn publish_order( trade_pubkey: PublicKey, request_id: Option, trade_index: Option, -) -> Result<()> { +) -> Result<(), MostroError> { // Prepare a new default order let new_order_db = match prepare_new_order( new_order, @@ -177,14 +185,18 @@ pub async fn publish_order( ) .await { - Some(order) => order, - None => { - return Ok(()); + Ok(order) => order, + Err(e) => { + return Err(e); } }; // CRUD order creation - let mut order = new_order_db.clone().create(pool).await?; + let mut order = new_order_db + .clone() + .create(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; let order_id = order.id; info!("New order saved Id: {}", order_id); // Get user reputation @@ -192,23 +204,27 @@ pub async fn publish_order( // We transform the order fields to tags to use in the event let tags = order_to_tags(&new_order_db, reputation); // nip33 kind with order fields as tags and order id as identifier - let event = new_event(keys, "", order_id.to_string(), tags)?; + let event = new_event(keys, "", order_id.to_string(), tags) + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; info!("Order event to be published: {event:#?}"); let event_id = event.id.to_string(); info!("Publishing Event Id: {event_id} for Order Id: {order_id}"); // We update the order with the new event_id order.event_id = event_id; - order.update(pool).await?; + order + .update(pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; let mut order = new_order_db.as_new_order(); order.id = Some(order_id); // Send message as ack with small order - send_new_order_msg( + enqueue_order_msg( request_id, Some(order_id), Action::NewOrder, Some(Payload::Order(order)), - &trade_pubkey, + trade_pubkey, trade_index, ) .await; @@ -219,7 +235,7 @@ pub async fn publish_order( .send_event(event) .await .map(|_s| ()) - .map_err(|err| err.into()) + .map_err(|err| MostroInternalErr(ServiceError::NostrError(err.to_string()))) } async fn prepare_new_order( @@ -228,7 +244,7 @@ async fn prepare_new_order( trade_index: Option, identity_pubkey: PublicKey, trade_pubkey: PublicKey, -) -> Option { +) -> Result { let mut fee = 0; if new_order.amount > 0 { fee = get_fee(new_order.amount); @@ -271,39 +287,34 @@ async fn prepare_new_order( new_order_db.trade_index_seller = trade_index; } None => { - send_cant_do_msg( - None, - None, - Some(CantDoReason::InvalidOrderKind), - &trade_pubkey, - ) - .await; - return None; + return Err(MostroCantDo(CantDoReason::InvalidOrderKind)); } } // Request price from API in case amount is 0 new_order_db.price_from_api = new_order.amount == 0; - Some(new_order_db) + Ok(new_order_db) } pub async fn send_dm( - receiver_pubkey: &PublicKey, + receiver_pubkey: PublicKey, sender_keys: Keys, payload: String, expiration: Option, -) -> Result<()> { +) -> Result<(), MostroError> { info!( "sender key {} - receiver key {}", sender_keys.public_key().to_hex(), receiver_pubkey.to_hex() ); - let message = Message::from_json(&payload).unwrap(); + let message = Message::from_json(&payload) + .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?; // We sign the message let sig = message.get_inner_message_kind().sign(&sender_keys); // We compose the content let content = (message, sig); - let content = serde_json::to_string(&content).unwrap(); + let content = serde_json::to_string(&content) + .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?; // We create the rumor let rumor = EventBuilder::text_note(content).build(sender_keys.public_key()); let mut tags: Vec = Vec::with_capacity(1 + usize::from(expiration.is_some())); @@ -313,29 +324,32 @@ pub async fn send_dm( } let tags = Tags::new(tags); - let event = EventBuilder::gift_wrap(&sender_keys, receiver_pubkey, rumor, tags).await?; + let event = EventBuilder::gift_wrap(&sender_keys, &receiver_pubkey, rumor, tags) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; info!( "Sending DM, Event ID: {} with payload: {:#?}", event.id, payload ); if let Ok(client) = get_nostr_client() { - if let Err(e) = client.send_event(event).await { - error!("Failed to send event: {}", e); - } + client + .send_event(event) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; } Ok(()) } -pub fn get_keys() -> Result { +pub fn get_keys() -> Result { let nostr_settings = Settings::get_nostr(); // nostr private key match Keys::parse(&nostr_settings.nsec_privkey) { Ok(my_keys) => Ok(my_keys), Err(e) => { tracing::error!("Failed to parse nostr private key: {}", e); - std::process::exit(1); + Err(MostroInternalErr(ServiceError::NostrError(e.to_string()))) } } } @@ -346,19 +360,14 @@ pub async fn update_user_rating_event( buyer_sent_rate: bool, seller_sent_rate: bool, tags: Tags, - order_id: Uuid, + msg: &Message, keys: &Keys, pool: &SqlitePool, - rate_list: Arc>>, ) -> Result<()> { - // Get order from id - let mut order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - error!("Order Id {order_id} not found!"); - return Ok(()); - } - }; // nip33 kind with user as identifier + // Get order from msg + let mut order = get_order(msg, pool).await?; + + // nip33 kind with user as identifier let event = new_event(keys, "", user.to_string(), tags)?; info!("Sending replaceable event: {event:#?}"); // We update the order vote status @@ -371,19 +380,29 @@ pub async fn update_user_rating_event( order.update(pool).await?; // Add event message to global list - rate_list.lock().await.push(event); - + MESSAGE_QUEUES + .write() + .await + .queue_order_rate + .lock() + .await + .push(event); Ok(()) } -pub async fn update_order_event(keys: &Keys, status: Status, order: &Order) -> Result { +pub async fn update_order_event( + keys: &Keys, + status: Status, + order: &Order, +) -> Result { let mut order_updated = order.clone(); // update order.status with new status order_updated.status = status.to_string(); // We transform the order fields to tags to use in the event let tags = order_to_tags(&order_updated, None); // nip33 kind with order id as identifier and order fields as tags - let event = new_event(keys, "", order.id.to_string(), tags)?; + let event = new_event(keys, "", order.id.to_string(), tags) + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; let order_id = order.id.to_string(); info!("Sending replaceable event: {event:#?}"); // We update the order with the new event_id @@ -409,7 +428,7 @@ pub async fn update_order_event(keys: &Keys, status: Status, order: &Order) -> R Ok(order_updated) } -pub async fn connect_nostr() -> Result { +pub async fn connect_nostr() -> Result { let nostr_settings = Settings::get_nostr(); let mut limits = RelayLimits::default(); @@ -424,7 +443,10 @@ pub async fn connect_nostr() -> Result { // Add relays for relay in nostr_settings.relays.iter() { - client.add_relay(relay).await?; + client + .add_relay(relay) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; } // Connect to relays and keep connection alive @@ -440,7 +462,7 @@ pub async fn show_hold_invoice( seller_pubkey: &PublicKey, mut order: Order, request_id: Option, -) -> anyhow::Result<()> { +) -> Result<(), MostroError> { let mut ln_client = lightning::LndConnector::new().await?; // Add fee of seller to hold invoice let new_amount = order.amount + order.fee; @@ -452,10 +474,12 @@ pub async fn show_hold_invoice( &order.id.to_string(), &order.fiat_code, &order.fiat_amount.to_string(), - )?, + ) + .map_err(|e| MostroInternalErr(ServiceError::HoldInvoiceError(e.to_string())))?, new_amount, ) - .await?; + .await + .map_err(|e| MostroInternalErr(ServiceError::HoldInvoiceError(e.to_string())))?; if let Some(invoice) = payment_request { order.buyer_invoice = Some(invoice); }; @@ -468,14 +492,21 @@ pub async fn show_hold_invoice( order.seller_pubkey = Some(seller_pubkey.to_string()); // We need to publish a new event with the new status - let pool = db::connect().await?; - let order_updated = update_order_event(my_keys, Status::WaitingPayment, &order).await?; - order_updated.update(&pool).await?; + let pool = db::connect() + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + let order_updated = update_order_event(my_keys, Status::WaitingPayment, &order) + .await + .map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?; + order_updated + .update(&pool) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; let mut new_order = order.as_new_order(); new_order.status = Some(Status::WaitingPayment); // We create a Message to send the hold invoice to seller - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::PayInvoice, @@ -484,17 +515,17 @@ pub async fn show_hold_invoice( invoice_response.payment_request, None, )), - seller_pubkey, + *seller_pubkey, order.trade_index_seller, ) .await; // We send a message to buyer to know that seller was requested to pay the invoice - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::WaitingSellerToPay, None, - buyer_pubkey, + *buyer_pubkey, order.trade_index_buyer, ) .await; @@ -514,7 +545,7 @@ pub async fn invoice_subscribe(hash: Vec, request_id: Option) -> anyhow let _ = ln_client_invoices .subscribe_invoice(hash, tx) .await - .map_err(|e| MostroError::LnNodeError(e.to_string())); + .map_err(|e| e.to_string()); } }; tokio::spawn(invoice_task); @@ -593,19 +624,50 @@ pub async fn set_waiting_invoice_status( None, ); // We create a Message - send_new_order_msg( + enqueue_order_msg( request_id, Some(order.id), Action::AddInvoice, Some(Payload::Order(order_data)), - &buyer_pubkey, + buyer_pubkey, order.trade_index_buyer, ) .await; - Ok(order.amount) } +/// Send message to buyer and seller to vote for counterpart +pub async fn rate_counterpart( + buyer_pubkey: &PublicKey, + seller_pubkey: &PublicKey, + order: &Order, + request_id: Option, +) -> Result<()> { + // Send dm to counterparts + // to buyer + enqueue_order_msg( + request_id, + Some(order.id), + Action::Rate, + None, + *buyer_pubkey, + None, + ) + .await; + // to seller + enqueue_order_msg( + request_id, + Some(order.id), + Action::Rate, + None, + *seller_pubkey, + None, + ) + .await; + + Ok(()) +} + /// Settle a seller hold invoice #[allow(clippy::too_many_arguments)] pub async fn settle_seller_hold_invoice( @@ -614,20 +676,17 @@ pub async fn settle_seller_hold_invoice( action: Action, is_admin: bool, order: &Order, - request_id: Option, -) -> Result<()> { +) -> Result<(), MostroError> { + // Get seller pubkey + let seller_pubkey = order + .get_seller_pubkey() + .map_err(|_| MostroCantDo(CantDoReason::InvalidPubkey))? + .to_string(); + // Get sender pubkey + let sender_pubkey = event.rumor.pubkey.to_string(); // Check if the pubkey is right - if !is_admin - && event.rumor.pubkey.to_string() != *order.seller_pubkey.as_ref().unwrap().to_string() - { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidPubkey), - &event.rumor.pubkey, - ) - .await; - return Err(Error::msg("Not allowed")); + if !is_admin && sender_pubkey != seller_pubkey { + return Err(MostroCantDo(CantDoReason::InvalidPubkey)); } // Settling the hold invoice @@ -635,14 +694,7 @@ pub async fn settle_seller_hold_invoice( ln_client.settle_hold_invoice(preimage).await?; info!("{action}: Order Id {}: hold invoice settled", order.id); } else { - send_cant_do_msg( - request_id, - Some(order.id), - Some(CantDoReason::InvalidInvoice), - &event.rumor.pubkey, - ) - .await; - return Err(Error::msg("No preimage")); + return Err(MostroCantDo(CantDoReason::InvalidInvoice)); } Ok(()) } @@ -654,34 +706,40 @@ pub fn bytes_to_string(bytes: &[u8]) -> String { }) } -pub async fn send_cant_do_msg( +pub async fn enqueue_cant_do_msg( request_id: Option, order_id: Option, - reason: Option, - destination_key: &PublicKey, + reason: CantDoReason, + destination_key: PublicKey, ) { // Send message to event creator - let message = Message::cant_do(order_id, request_id, Some(Payload::CantDo(reason))); - if let Ok(message) = message.as_json() { - let sender_keys = crate::util::get_keys().unwrap(); - let _ = send_dm(destination_key, sender_keys, message, None).await; - } + let message = Message::cant_do(order_id, request_id, Some(Payload::CantDo(Some(reason)))); + MESSAGE_QUEUES + .write() + .await + .queue_order_cantdo + .lock() + .await + .push((message, destination_key)); } -pub async fn send_new_order_msg( +pub async fn enqueue_order_msg( request_id: Option, order_id: Option, action: Action, payload: Option, - destination_key: &PublicKey, + destination_key: PublicKey, trade_index: Option, ) { // Send message to event creator let message = Message::new_order(order_id, request_id, trade_index, action, payload); - if let Ok(message) = message.as_json() { - let sender_keys = crate::util::get_keys().unwrap(); - let _ = send_dm(destination_key, sender_keys, message, None).await; - } + MESSAGE_QUEUES + .write() + .await + .queue_order_msg + .lock() + .await + .push((message, destination_key)); } pub fn get_fiat_amount_requested(order: &Order, msg: &Message) -> Option { @@ -704,11 +762,13 @@ pub fn get_fiat_amount_requested(order: &Order, msg: &Message) -> Option { } /// Getter function with error management for nostr Client -pub fn get_nostr_client() -> Result<&'static Client> { +pub fn get_nostr_client() -> Result<&'static Client, MostroError> { if let Some(client) = NOSTR_CLIENT.get() { Ok(client) } else { - Err(Error::msg("Client not initialized!")) + Err(MostroInternalErr(ServiceError::NostrError( + "Client not initialized!".to_string(), + ))) } } @@ -721,6 +781,45 @@ pub async fn get_nostr_relays() -> Option> { } } +pub async fn get_order(msg: &Message, pool: &Pool) -> Result { + let order_msg = msg.get_inner_message_kind(); + let order_id = order_msg + .id + .ok_or(MostroInternalErr(ServiceError::InvalidOrderId))?; + let order = Order::by_id(pool, order_id) + .await + .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?; + if let Some(order) = order { + Ok(order) + } else { + Err(MostroInternalErr(ServiceError::InvalidOrderId)) + } +} + +pub async fn validate_invoice(msg: &Message, order: &Order) -> Result, MostroError> { + // init payment request to None + let mut payment_request = None; + // if payment request is present + if let Some(pr) = msg.get_inner_message_kind().get_payment_request() { + // if invoice is valid + if is_valid_invoice( + pr.clone(), + Some(order.amount as u64), + Some(order.fee as u64), + ) + .await + .is_err() + { + return Err(MostroCantDo(CantDoReason::InvalidInvoice)); + } + // if invoice is valid return it + else { + payment_request = Some(pr); + } + } + Ok(payment_request) +} + #[cfg(test)] mod tests { use super::*; @@ -800,15 +899,12 @@ mod tests { Action::FiatSent, None, )); - let client = Client::default(); - NOSTR_CLIENT.get_or_init(|| client); - let client_result = get_nostr_client(); - assert!(client_result.is_ok()); - let payload = message.as_json().unwrap(); let sender_keys = Keys::generate(); - let result = send_dm(&receiver_pubkey, sender_keys, payload, None).await; - assert!(result.is_ok()); + // Now error is well manager this call will fail now, previously test was ok becuse error was not managed + // now just make it ok and then will make a better test + let result = send_dm(receiver_pubkey, sender_keys, payload, None).await; + assert!(result.is_err()); } #[tokio::test]