diff --git a/.gitignore b/.gitignore index 65420fe..f5f61bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ target -database.redb -database-ah-usdc.redb -database-ah-usdt.redb +*.db + +# IDE +.vscode +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9c1e692..1ec9841 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,32 +2,13 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "gimli 0.27.3", -] - -[[package]] -name = "addr2line" -version = "0.21.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ - "gimli 0.28.1", + "gimli", ] [[package]] @@ -36,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aead" version = "0.5.2" @@ -46,17 +33,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.11" @@ -81,77 +57,54 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -159,150 +112,24 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" - -[[package]] -name = "ark-bls12-377" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" -dependencies = [ - "ark-ec", - "ark-ff", - "ark-std", -] - -[[package]] -name = "ark-bls12-381" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" -dependencies = [ - "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", -] - -[[package]] -name = "ark-ec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" -dependencies = [ - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "derivative", - "hashbrown 0.13.2", - "itertools 0.10.5", - "num-traits", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" -dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", - "derivative", - "digest 0.10.7", - "itertools 0.10.5", - "num-bigint", - "num-traits", - "paste", - "rustc_version", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-poly" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" -dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", - "derivative", - "hashbrown 0.13.2", -] - -[[package]] -name = "ark-serialize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" -dependencies = [ - "ark-serialize-derive", - "ark-std", - "digest 0.10.7", - "num-bigint", -] - -[[package]] -name = "ark-serialize-derive" -version = "0.4.2" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] -name = "ark-std" -version = "0.4.0" +name = "approx" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", - "rand", ] -[[package]] -name = "array-bytes" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f840fb7195bcfc5e17ea40c26e5ce6d5b9ce5d584466e17703209657e459ae0" - [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -315,157 +142,21 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-channel" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" -dependencies = [ - "concurrent-queue", - "event-listener 5.2.0", - "event-listener-strategy 0.5.0", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" -dependencies = [ - "async-lock 3.3.0", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc19683171f287921f2405677dd2ed2549c3b3bda697a563ebc3a121ace2aba1" -dependencies = [ - "async-lock 3.3.0", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" -dependencies = [ - "async-lock 3.3.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 0.38.32", - "slab", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", - "pin-project-lite", -] - -[[package]] -name = "async-net" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" -dependencies = [ - "async-io", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-process" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451e3cf68011bd56771c79db04a9e333095ab6349f7e47592b788e9b98720cc8" -dependencies = [ - "async-channel", - "async-io", - "async-lock 3.3.0", - "async-signal", - "blocking", - "cfg-if", - "event-listener 5.2.0", - "futures-lite", - "rustix 0.38.32", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-signal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" -dependencies = [ - "async-io", - "async-lock 2.8.0", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 0.38.32", - "signal-hook-registry", - "slab", - "windows-sys 0.48.0", -] - -[[package]] -name = "async-task" -version = "4.7.0" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] -[[package]] -name = "atomic-take" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" - [[package]] name = "atomic-waker" version = "1.1.2" @@ -474,9 +165,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -488,10 +179,10 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "http-body-util", - "hyper 1.2.0", + "hyper", "hyper-util", "itoa", "matchit", @@ -504,12 +195,11 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.0", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -521,8 +211,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -530,41 +220,52 @@ dependencies = [ "sync_wrapper 0.1.2", "tower-layer", "tower-service", - "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "addr2line 0.21.0", - "cc", + "addr2line", "cfg-if", "libc", - "miniz_oxide", - "object 0.32.2", + "miniz_oxide 0.8.0", + "object", "rustc-demangle", + "windows-targets", ] [[package]] -name = "base58" +name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] -name = "base64" -version = "0.13.1" +name = "base58" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -581,34 +282,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bip39" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" -dependencies = [ - "bitcoin_hashes", - "rand", - "rand_core 0.6.4", - "serde", - "unicode-normalization", -] - -[[package]] -name = "bitcoin_hashes" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" - [[package]] name = "bitflags" version = "1.3.2" @@ -617,9 +290,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -633,15 +306,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest 0.10.7", -] - [[package]] name = "blake2-rfc" version = "0.2.18" @@ -659,76 +323,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", - "arrayvec 0.7.4", - "constant_time_eq 0.3.0", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", ] [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] -name = "block-buffer" -version = "0.10.4" +name = "bumpalo" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "blocking" -version = "1.5.1" +name = "byte-slice-cast" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" -dependencies = [ - "async-channel", - "async-lock 3.3.0", - "async-task", - "fastrand", - "futures-io", - "futures-lite", - "piper", - "tracing", -] - -[[package]] -name = "bounded-collections" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca548b6163b872067dc5eb82fd130c56881435e30367d2073594a3d9744120dd" -dependencies = [ - "log", - "parity-scale-codec", - "scale-info", - "serde", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] -name = "byte-slice-cast" -version = "1.2.2" +name = "bytemuck" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -738,15 +362,26 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.0.90" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bcde016d64c21da4be18b655631e5ab6d3107607e71a73a9f53eb48aae23fb" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" @@ -755,57 +390,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chacha20" -version = "0.9.1" +name = "clap" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", + "clap_builder", + "clap_derive", ] [[package]] -name = "chrono" -version = "0.4.37" +name = "clap_builder" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "windows-targets 0.52.4", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "cipher" -version = "0.4.4" +name = "clap_derive" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ - "crypto-common", - "inout", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] -name = "colorchoice" -version = "1.0.0" +name = "clap_lex" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] -name = "common-path" -version = "1.0.0" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "concurrent-queue" -version = "2.4.0" +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "crossbeam-utils", + "bytes", + "memchr", ] [[package]] @@ -815,28 +458,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "const_format" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +dependencies = [ + "const_format_proc_macros", +] [[package]] -name = "constant_time_eq" -version = "0.3.0" +name = "const_format_proc_macros" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] [[package]] -name = "constcat" -version = "0.3.1" +name = "constant_time_eq" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] -name = "convert_case" -version = "0.4.0" +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" @@ -850,60 +501,42 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpp_demangle" -version = "0.3.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" -dependencies = [ - "cfg-if", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] -[[package]] -name = "cranelift-entity" -version = "0.95.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" -dependencies = [ - "serde", -] - [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] -name = "crossbeam-queue" -version = "0.3.11" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -912,61 +545,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.8.0" +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", + "rand_core", "subtle", + "zeroize", ] [[package]] -name = "crypto-mac" -version = "0.11.0" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "subtle", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", + "rand_core", + "typenum", ] [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", + "digest", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -980,445 +591,251 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] [[package]] -name = "darling" -version = "0.14.4" +name = "der" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", + "const-oid", + "zeroize", ] [[package]] -name = "darling" -version = "0.20.8" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "darling_core 0.20.8", - "darling_macro 0.20.8", + "powerfmt", ] [[package]] -name = "darling_core" -version = "0.14.4" +name = "derive_more" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ - "fnv", - "ident_case", "proc-macro2", "quote", - "strsim", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] -name = "darling_core" -version = "0.20.8" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.55", + "block-buffer", + "const-oid", + "crypto-common", + "subtle", ] [[package]] -name = "darling_macro" -version = "0.14.4" +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] -name = "darling_macro" -version = "0.20.8" +name = "ed25519" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "darling_core 0.20.8", - "quote", - "syn 2.0.55", + "signature", ] [[package]] -name = "der" -version = "0.7.8" +name = "ed25519-zebra" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "const-oid", + "curve25519-dalek", + "ed25519", + "hashbrown", + "hex", + "rand_core", + "sha2", "zeroize", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", ] [[package]] -name = "derive-syn-parse" -version = "0.1.5" +name = "encoding_rs" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79116f119dd1dba1abf1f3405f03b9b0e79a27a3883864bfebded8a3dc768cd" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "cfg-if", ] [[package]] -name = "derive_more" -version = "0.99.17" +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "digest" -version = "0.9.0" +name = "external-memory-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf172ba7bfe5412e03c4dfd7d8e4b5f1e6cd0b7087fd61fa274b73f87ad94854" + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fdeflate" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ - "generic-array", + "simd-adler32", ] [[package]] -name = "digest" -version = "0.10.7" +name = "ff" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "block-buffer 0.10.4", - "crypto-common", + "rand_core", "subtle", ] [[package]] -name = "docify" -version = "0.2.7" +name = "fiat-crypto" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc4fd38aaa9fb98ac70794c82a00360d1e165a87fbf96a8a91f9dfc602aaee2" -dependencies = [ - "docify_macros", -] +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] -name = "docify_macros" -version = "0.2.7" +name = "find-crate" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63fa215f3a0d40fb2a221b3aa90d8e1fbb8379785a990cb60d62ac71ebdc6460" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ - "common-path", - "derive-syn-parse", - "once_cell", - "proc-macro2", - "quote", - "regex", - "syn 2.0.55", - "termcolor", "toml", - "walkdir", ] [[package]] -name = "downcast-rs" -version = "1.2.0" +name = "fixed-hash" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] [[package]] -name = "dyn-clonable" -version = "0.9.0" +name = "flate2" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ - "dyn-clonable-impl", - "dyn-clone", + "crc32fast", + "miniz_oxide 0.8.0", ] [[package]] -name = "dyn-clonable-impl" -version = "0.9.0" +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "foreign-types-shared", ] [[package]] -name = "dyn-clone" -version = "1.0.17" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "ed25519" -version = "2.2.3" +name = "form_urlencoded" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "pkcs8", - "signature", + "percent-encoding", ] [[package]] -name = "ed25519-dalek" -version = "2.1.1" +name = "frame-metadata" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" dependencies = [ - "curve25519-dalek 4.1.2", - "ed25519", + "cfg-if", + "parity-scale-codec", + "scale-info", "serde", - "sha2 0.10.8", - "subtle", - "zeroize", ] [[package]] -name = "ed25519-zebra" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" -dependencies = [ - "curve25519-dalek 3.2.0", - "hashbrown 0.12.3", - "hex", - "rand_core 0.6.4", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "ed25519-zebra" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" -dependencies = [ - "curve25519-dalek 4.1.2", - "ed25519", - "hashbrown 0.14.3", - "hex", - "rand_core 0.6.4", - "sha2 0.10.8", - "zeroize", -] - -[[package]] -name = "either" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - -[[package]] -name = "env_filter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - -[[package]] -name = "environmental" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.8" +name = "fs2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" -dependencies = [ - "event-listener 5.2.0", - "pin-project-lite", -] - -[[package]] -name = "expander" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e83c02035136f1592a47964ea60c05a50e4ed8b5892cfac197063850898d4d" -dependencies = [ - "blake2", - "fs-err", - "prettier-please", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fastrand" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" - -[[package]] -name = "fiat-crypto" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "frame-metadata" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" -dependencies = [ - "cfg-if", - "parity-scale-codec", - "scale-info", -] - -[[package]] -name = "frame-metadata" -version = "16.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" -dependencies = [ - "cfg-if", - "parity-scale-codec", - "scale-info", - "serde", -] - -[[package]] -name = "fs-err" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", + "winapi", ] [[package]] @@ -1467,7 +884,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -1476,19 +892,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.30" @@ -1497,7 +900,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] [[package]] @@ -1536,6 +939,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1544,13 +956,14 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -1564,87 +977,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" dependencies = [ "rand", - "rand_core 0.6.4", + "rand_core", ] [[package]] name = "gimli" -version = "0.27.3" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + +[[package]] +name = "git2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" dependencies = [ - "fallible-iterator", - "indexmap 1.9.3", - "stable_deref_trait", + "bitflags 2.6.0", + "libc", + "libgit2-sys", + "log", + "url", ] [[package]] -name = "gimli" -version = "0.28.1" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] [[package]] name = "h2" -version = "0.3.25" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.2.6", + "http", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "hash-db" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" - -[[package]] -name = "hash256-std-hasher" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" -dependencies = [ - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.11", -] - [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash", "allocator-api2", - "serde", ] [[package]] @@ -1653,6 +1045,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1665,51 +1063,20 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.0", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array", - "hmac 0.8.1", + "digest", ] [[package]] name = "http" -version = "0.2.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1717,55 +1084,33 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.1.0" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "fnv", - "itoa", + "http", ] [[package]] -name = "http-body" -version = "0.4.6" +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "http 0.2.12", + "futures-util", + "http", + "http-body", "pin-project-lite", ] [[package]] -name = "http-body" -version = "1.0.0" +name = "httparse" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" -dependencies = [ - "bytes", - "http 1.1.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" -dependencies = [ - "bytes", - "futures-core", - "http 1.1.0", - "http-body 1.0.0", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -1773,116 +1118,80 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" -version = "0.14.28" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", - "http 0.2.12", - "http-body 0.4.6", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] -name = "hyper" -version = "1.2.0" +name = "hyper-rustls" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ - "bytes", - "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", "tokio", + "tokio-rustls", + "tower-service", ] [[package]] -name = "hyper-rustls" -version = "0.24.2" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.28", - "log", - "rustls 0.21.10", - "rustls-native-certs 0.6.3", + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", "tokio", - "tokio-rustls 0.24.1", + "tokio-native-tls", + "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", + "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.2.0", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.5.0" @@ -1894,21 +1203,25 @@ dependencies = [ ] [[package]] -name = "impl-codec" -version = "0.6.0" +name = "image" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ - "parity-scale-codec", + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", ] [[package]] -name = "impl-serde" -version = "0.4.0" +name = "impl-codec" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "serde", + "parity-scale-codec", ] [[package]] @@ -1924,45 +1237,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", ] [[package]] -name = "indexmap-nostd" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" - -[[package]] -name = "inout" -version = "0.1.3" +name = "indoc" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -1977,33 +1270,22 @@ dependencies = [ ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "ipnet" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] -name = "itertools" -version = "0.10.5" +name = "is_debug" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" [[package]] -name = "itertools" -version = "0.12.1" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -2012,74 +1294,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "js-sys" -version = "0.3.69" +name = "jni" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" dependencies = [ - "wasm-bindgen", + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", ] [[package]] -name = "jsonrpsee" -version = "0.21.0" +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9579d0ca9fb30da026bac2f0f7d9576ec93489aeb7cd4971dd5b4617d82c79b2" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ - "jsonrpsee-client-transport 0.21.0", - "jsonrpsee-core 0.21.0", - "jsonrpsee-http-client", - "jsonrpsee-types 0.21.0", + "libc", ] [[package]] -name = "jsonrpsee" -version = "0.22.3" +name = "js-sys" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cdbb7cb6f3ba28f5b212dd250ab4483105efc3e381f5c8bb90340f14f0a2cc3" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ - "jsonrpsee-core 0.22.3", - "jsonrpsee-types 0.22.3", - "jsonrpsee-ws-client", + "wasm-bindgen", ] [[package]] -name = "jsonrpsee-client-transport" -version = "0.21.0" +name = "jsonrpsee" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9f9ed46590a8d5681975f126e22531698211b926129a40a2db47cbca429220" +checksum = "62b089779ad7f80768693755a031cc14a7766aba707cbe886674e3f79e9b7e47" dependencies = [ - "futures-util", - "http 0.2.12", - "jsonrpsee-core 0.21.0", - "pin-project", - "rustls-native-certs 0.7.0", - "rustls-pki-types", - "soketto", - "thiserror", - "tokio", - "tokio-rustls 0.25.0", - "tokio-util", - "tracing", - "url", + "jsonrpsee-core", + "jsonrpsee-types", + "jsonrpsee-ws-client", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab2e14e727d2faf388c99d9ca5210566ed3b044f07d92c29c3611718d178380" +checksum = "08163edd8bcc466c33d79e10f695cdc98c00d1e6ddfb95cec41b6b0279dd5432" dependencies = [ + "base64", "futures-util", - "http 0.2.12", - "jsonrpsee-core 0.22.3", + "http", + "jsonrpsee-core", "pin-project", - "rustls-native-certs 0.7.0", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "soketto", "thiserror", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", "tokio-util", "tracing", "url", @@ -2087,41 +1367,16 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "776d009e2f591b78c038e0d053a796f94575d66ca4e77dd84bfc5e81419e436c" -dependencies = [ - "anyhow", - "async-lock 3.3.0", - "async-trait", - "beef", - "futures-timer", - "futures-util", - "hyper 0.14.28", - "jsonrpsee-types 0.21.0", - "pin-project", - "rustc-hash", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "jsonrpsee-core" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71962a1c49af43adf81d337e4ebc93f3c915faf6eccaa14d74e255107dfd7723" +checksum = "79712302e737d23ca0daa178e752c9334846b08321d439fd89af9a384f8c830b" dependencies = [ "anyhow", - "async-lock 3.3.0", "async-trait", "beef", "futures-timer", "futures-util", - "jsonrpsee-types 0.22.3", + "jsonrpsee-types", "pin-project", "rustc-hash", "serde", @@ -2132,47 +1387,14 @@ dependencies = [ "tracing", ] -[[package]] -name = "jsonrpsee-http-client" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b7de9f3219d95985eb77fd03194d7c1b56c19bce1abfcc9d07462574b15572" -dependencies = [ - "async-trait", - "hyper 0.14.28", - "hyper-rustls", - "jsonrpsee-core 0.21.0", - "jsonrpsee-types 0.21.0", - "serde", - "serde_json", - "thiserror", - "tokio", - "tower", - "tracing", - "url", -] - -[[package]] -name = "jsonrpsee-types" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3266dfb045c9174b24c77c2dfe0084914bb23a6b2597d70c9dc6018392e1cd1b" -dependencies = [ - "anyhow", - "beef", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "jsonrpsee-types" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e53c72de6cd2ad6ac1aa6e848206ef8b736f92ed02354959130373dfa5b3cbd" +checksum = "d9c465fbe385238e861fdc4d1c85e04ada6c1fd246161d26385c1b311724d2af" dependencies = [ - "anyhow", "beef", + "http", "serde", "serde_json", "thiserror", @@ -2180,51 +1402,65 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8a07ab8da9a283b906f6735ddd17d3680158bb72259e853441d1dd0167079ec" +checksum = "1c28759775f5cb2f1ea9667672d3fe2b0e701d1f4b7b67954e60afe7fd058b5e" dependencies = [ - "http 0.2.12", - "jsonrpsee-client-transport 0.22.3", - "jsonrpsee-core 0.22.3", - "jsonrpsee-types 0.22.3", + "http", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", "url", ] [[package]] -name = "kalatori" -version = "0.1.3" +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ - "anyhow", - "axum", - "env_logger", - "hex", - "log", - "reconnecting-jsonrpsee-ws-client", - "redb", - "serde", - "serde_json", - "subxt", - "tokio", - "tokio-util", + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", ] [[package]] -name = "kalatori-ah" -version = "0.1.3" +name = "kalatori" +version = "0.2.0-rc4" dependencies = [ - "anyhow", + "ahash", "axum", - "env_logger", + "axum-macros", + "clap", + "frame-metadata", + "futures", "hex", - "log", - "reconnecting-jsonrpsee-ws-client", - "redb", + "indoc", + "jsonrpsee", + "lazy_static", + "mnemonic-external", + "names", + "parity-scale-codec", + "primitive-types", + "reqwest", + "scale-info", "serde", "serde_json", - "subxt", + "shadow-rs", + "sled", + "sp-crypto-hashing", + "substrate-constructor", + "substrate-crypto-light", + "substrate_parser", + "thiserror", "tokio", "tokio-util", + "toml_edit", + "tracing", + "tracing-subscriber", + "ureq", + "zeroize", ] [[package]] @@ -2238,87 +1474,54 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libsecp256k1" -version = "0.7.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "arrayref", - "base64 0.13.1", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand", - "serde", - "sha2 0.9.9", - "typenum", + "spin", ] [[package]] -name = "libsecp256k1-core" -version = "0.3.0" +name = "libc" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" +name = "libgit2-sys" +version = "0.16.2+1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" dependencies = [ - "libsecp256k1-core", + "cc", + "libc", + "libz-sys", + "pkg-config", ] [[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" +name = "libz-sys" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ - "libsecp256k1-core", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - -[[package]] -name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2326,33 +1529,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "lru" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" -dependencies = [ - "hashbrown 0.14.3", -] - -[[package]] -name = "mach" -version = "0.3.2" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matchers" -version = "0.0.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] @@ -2365,36 +1550,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "memfd" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" -dependencies = [ - "rustix 0.38.32", -] - -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memory-db" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" -dependencies = [ - "hash-db", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "merlin" @@ -2404,7 +1562,7 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core 0.6.4", + "rand_core", "zeroize", ] @@ -2415,79 +1573,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "miniz_oxide" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "no-std-net" -version = "0.6.0" +name = "mnemonic-external" +version = "0.1.0" +source = "git+https://github.com/Alzymologist/mnemonic-external#ac8b4f027adec591ae16a4e284ca25c0ffda6c3d" +dependencies = [ + "bitvec", + "sha2", + "thiserror", +] + +[[package]] +name = "names" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" +checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" +dependencies = [ + "rand", +] [[package]] -name = "nodrop" -version = "0.1.14" +name = "native-tls" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] [[package]] -name = "nohash-hasher" -version = "0.2.0" +name = "nodrop" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] -name = "nom" -version = "7.1.3" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "memchr", - "minimal-lexical", + "overload", + "winapi", ] [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] -name = "num-format" -version = "0.4.4" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec 0.7.4", - "itoa", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" @@ -2499,85 +1681,120 @@ dependencies = [ ] [[package]] -name = "num-rational" -version = "0.4.1" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "num-bigint", - "num-integer", - "num-traits", ] [[package]] -name = "num-traits" -version = "0.2.18" +name = "num_threads" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ - "autocfg", + "libc", ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "object" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ - "hermit-abi", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", "libc", + "once_cell", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "object" -version = "0.30.4" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "crc32fast", - "hashbrown 0.13.2", - "indexmap 1.9.3", - "memchr", + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] -name = "object" -version = "0.32.2" +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ - "memchr", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "palette" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "8f9cd68f7112581033f157e56c77ac4a5538ec5836a2e39284e65bd7d7275e49" +dependencies = [ + "approx", + "num-traits", + "palette_derive", +] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "palette_derive" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "05eedf46a8e7c27f74af0c9cfcdb004ceca158cb1b918c6f68f8d7a549b3e427" +dependencies = [ + "find-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "parity-scale-codec" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitvec", "byte-slice-cast", - "bytes", "impl-trait-for-tuples", "parity-scale-codec-derive", "serde", @@ -2585,58 +1802,62 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] -name = "parking" -version = "2.2.0" +name = "parking_lot" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.10", ] [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", + "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "windows-targets 0.48.5", + "winapi", ] [[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "pbkdf2" -version = "0.8.0" +name = "parking_lot_core" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "crypto-mac 0.11.0", + "cfg-if", + "libc", + "redox_syscall 0.5.4", + "smallvec", + "windows-targets", ] [[package]] @@ -2645,7 +1866,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] @@ -2671,14 +1892,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2686,17 +1907,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -2708,51 +1918,49 @@ dependencies = [ ] [[package]] -name = "platforms" -version = "3.4.0" +name = "pkg-config" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "polling" -version = "3.6.0" +name = "plot_icon" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "6b92a0e6ac9a5007559362f0f41a4feaa3021a2ac07c54b8788fad354137b5fa" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 0.38.32", - "tracing", - "windows-sys 0.52.0", + "blake2-rfc", + "image", + "palette", + "png", ] [[package]] -name = "poly1305" -version = "0.8.0" +name = "png" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.4", ] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "prettier-please" -version = "0.2.0" +name = "ppv-lite86" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "proc-macro2", - "syn 2.0.55", + "zerocopy", ] [[package]] @@ -2763,86 +1971,32 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "impl-serde", - "scale-info", "uint", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -2861,7 +2015,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2871,15 +2025,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" @@ -2889,71 +2037,34 @@ dependencies = [ "getrandom", ] -[[package]] -name = "reconnecting-jsonrpsee-ws-client" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea5cf7b021db88f1af45a9b2ecdbe5bc1c5cbebc146632269d572cdd435f5cf" -dependencies = [ - "futures", - "jsonrpsee 0.22.3", - "serde_json", - "subxt", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tracing", -] - -[[package]] -name = "redb" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7f82ecd6ba647a39dd1a7172b8a1cd9453c0adee6da20cb553d83a9a460fa5" -dependencies = [ - "libc", -] - [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags 1.3.2", ] [[package]] -name = "ref-cast" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.22" +name = "redox_syscall" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -2967,13 +2078,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -2984,30 +2095,83 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] -name = "ring" -version = "0.17.8" +name = "reqwest" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -3023,86 +2187,49 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.36.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] - -[[package]] -name = "rustix" -version = "0.38.32" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.10" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.2", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.1", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", @@ -3110,44 +2237,52 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64 0.21.7", + "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "rustls-platform-verifier" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" dependencies = [ - "ring", - "untrusted", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -3156,26 +2291,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "ruzstd" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" -dependencies = [ - "byteorder", - "derive_more", - "twox-hash", -] +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -3186,78 +2310,11 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scale-bits" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" -dependencies = [ - "parity-scale-codec", - "scale-info", - "serde", -] - -[[package]] -name = "scale-decode" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" -dependencies = [ - "derive_more", - "parity-scale-codec", - "primitive-types", - "scale-bits", - "scale-decode-derive", - "scale-info", - "smallvec", -] - -[[package]] -name = "scale-decode-derive" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3475108a1b62c7efd1b5c65974f30109a598b2f45f23c9ae030acb9686966db" -dependencies = [ - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "scale-encode" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" -dependencies = [ - "derive_more", - "parity-scale-codec", - "primitive-types", - "scale-bits", - "scale-encode-derive", - "scale-info", - "smallvec", -] - -[[package]] -name = "scale-encode-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" -dependencies = [ - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "scale-info" -version = "2.11.1" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788745a868b0e751750388f4e6546eb921ef714a4317fa6954f7cde114eb2eb7" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "bitvec", "cfg-if", @@ -3269,67 +2326,23 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.1" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc2f4e8bc344b9fc3d5f74f72c2e55bfc38d28dc2ebc69c194a3df424e4d9ac" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "scale-typegen" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00860983481ac590ac87972062909bef0d6a658013b592ccc0f2feb272feab11" -dependencies = [ - "proc-macro2", - "quote", - "scale-info", - "syn 2.0.55", - "thiserror", -] - -[[package]] -name = "scale-value" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58223c7691bf0bd46b43c9aea6f0472d1067f378d574180232358d7c6e0a8089" -dependencies = [ - "base58", - "blake2", - "derive_more", - "either", - "frame-metadata 15.1.0", - "parity-scale-codec", - "scale-bits", - "scale-decode", - "scale-encode", - "scale-info", - "serde", - "yap", -] - [[package]] name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "schnellru" -version = "0.2.1" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "ahash 0.8.11", - "cfg-if", - "hashbrown 0.13.2", + "windows-sys 0.59.0", ] [[package]] @@ -3340,13 +2353,13 @@ checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ "aead", "arrayref", - "arrayvec 0.7.4", - "curve25519-dalek 4.1.2", + "arrayvec 0.7.6", + "curve25519-dalek", "getrandom_or_panic", "merlin", - "rand_core 0.6.4", + "rand_core", "serde_bytes", - "sha2 0.10.8", + "sha2", "subtle", "zeroize", ] @@ -3358,60 +2371,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "secp256k1" -version = "0.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" -dependencies = [ - "cc", -] - -[[package]] -name = "secrecy" -version = "0.8.0" +name = "sec1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", "zeroize", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -3419,46 +2410,47 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -3475,9 +2467,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -3495,29 +2487,14 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.9.9" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest", ] [[package]] @@ -3528,7 +2505,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -3537,525 +2514,151 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] [[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "simple-mermaid" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smol" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e635339259e51ef85ac7aa29a1cd991b957047507288697a690e80ab97d07cad" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock 3.3.0", - "async-net", - "async-process", - "blocking", - "futures-lite", -] - -[[package]] -name = "smoldot" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d1eaa97d77be4d026a1e7ffad1bb3b78448763b357ea6f8188d3e6f736a9b9" -dependencies = [ - "arrayvec 0.7.4", - "async-lock 3.3.0", - "atomic-take", - "base64 0.21.7", - "bip39", - "blake2-rfc", - "bs58", - "chacha20", - "crossbeam-queue", - "derive_more", - "ed25519-zebra 4.0.3", - "either", - "event-listener 4.0.3", - "fnv", - "futures-lite", - "futures-util", - "hashbrown 0.14.3", - "hex", - "hmac 0.12.1", - "itertools 0.12.1", - "libm", - "libsecp256k1", - "merlin", - "no-std-net", - "nom", - "num-bigint", - "num-rational", - "num-traits", - "pbkdf2 0.12.2", - "pin-project", - "poly1305", - "rand", - "rand_chacha", - "ruzstd", - "schnorrkel", - "serde", - "serde_json", - "sha2 0.10.8", - "sha3", - "siphasher", - "slab", - "smallvec", - "soketto", - "twox-hash", - "wasmi", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "smoldot-light" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" -dependencies = [ - "async-channel", - "async-lock 3.3.0", - "base64 0.21.7", - "blake2-rfc", - "derive_more", - "either", - "event-listener 4.0.3", - "fnv", - "futures-channel", - "futures-lite", - "futures-util", - "hashbrown 0.14.3", - "hex", - "itertools 0.12.1", - "log", - "lru", - "no-std-net", - "parking_lot", - "pin-project", - "rand", - "rand_chacha", - "serde", - "serde_json", - "siphasher", - "slab", - "smol", - "smoldot", - "zeroize", -] - -[[package]] -name = "socket2" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "soketto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" -dependencies = [ - "base64 0.13.1", - "bytes", - "futures", - "httparse", - "log", - "rand", - "sha-1", -] - -[[package]] -name = "sp-application-crypto" -version = "30.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4fe7a9b7fa9da76272b201e2fb3c7900d97d32a46b66af9a04dad457f73c71" -dependencies = [ - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-std", -] - -[[package]] -name = "sp-arithmetic" -version = "23.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f42721f072b421f292a072e8f52a3b3c0fbc27428f0c9fe24067bc47046bad63" -dependencies = [ - "integer-sqrt", - "num-traits", - "parity-scale-codec", - "scale-info", - "serde", - "sp-std", - "static_assertions", -] - -[[package]] -name = "sp-core" -version = "28.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f230cb12575455070da0fc174815958423a0b9a641d5e304a9457113c7cb4007" -dependencies = [ - "array-bytes", - "bip39", - "bitflags 1.3.2", - "blake2", - "bounded-collections", - "bs58", - "dyn-clonable", - "ed25519-zebra 3.1.0", - "futures", - "hash-db", - "hash256-std-hasher", - "impl-serde", - "itertools 0.10.5", - "libsecp256k1", - "log", - "merlin", - "parity-scale-codec", - "parking_lot", - "paste", - "primitive-types", - "rand", - "scale-info", - "schnorrkel", - "secp256k1", - "secrecy", - "serde", - "sp-core-hashing", - "sp-debug-derive", - "sp-externalities", - "sp-runtime-interface", - "sp-std", - "sp-storage", - "ss58-registry", - "substrate-bip39", - "thiserror", - "tracing", - "w3f-bls", - "zeroize", -] - -[[package]] -name = "sp-core-hashing" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f4990add7b2cefdeca883c0efa99bb4d912cb2196120e1500c0cc099553b0" -dependencies = [ - "blake2b_simd", - "byteorder", - "digest 0.10.7", - "sha2 0.10.8", - "sha3", - "twox-hash", -] - -[[package]] -name = "sp-debug-derive" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "sp-externalities" -version = "0.25.0" +name = "shadow-rs" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63867ec85950ced90d4ab1bba902a47db1b1efdf2829f653945669b2bb470a9c" +checksum = "1d75516bdaee8f640543ad1f6e292448c23ce57143f812c3736ab4b0874383df" dependencies = [ - "environmental", - "parity-scale-codec", - "sp-std", - "sp-storage", + "const_format", + "git2", + "is_debug", + "time", ] [[package]] -name = "sp-io" -version = "30.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55f26d89feedaf0faf81688b6e1e1e81329cd8b4c6a4fd6c5b97ed9dd068b8a" -dependencies = [ - "bytes", - "ed25519-dalek", - "libsecp256k1", - "log", - "parity-scale-codec", - "rustversion", - "secp256k1", - "sp-core", - "sp-externalities", - "sp-keystore", - "sp-runtime-interface", - "sp-state-machine", - "sp-std", - "sp-tracing", - "sp-trie", - "tracing", - "tracing-core", -] - -[[package]] -name = "sp-keystore" -version = "0.34.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96806a28a62ed9ddecd0b28857b1344d029390f7c5c42a2ff9199cbf5638635c" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "parity-scale-codec", - "parking_lot", - "sp-core", - "sp-externalities", - "thiserror", + "lazy_static", ] [[package]] -name = "sp-panic-handler" -version = "13.0.0" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f5a17a0a11de029a8b811cb6e8b32ce7e02183cc04a3e965c383246798c416" -dependencies = [ - "backtrace", - "lazy_static", - "regex", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "sp-runtime" -version = "31.0.1" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3bb49a4475d390198dfd3d41bef4564ab569fbaf1b5e38ae69b35fc01199d91" -dependencies = [ - "docify", - "either", - "hash256-std-hasher", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "paste", - "rand", - "scale-info", - "serde", - "simple-mermaid", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-std", - "sp-weights", +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", ] [[package]] -name = "sp-runtime-interface" -version = "24.0.0" +name = "signature" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66b66d8cec3d785fa6289336c1d9cbd4305d5d84f7134378c4d79ed7983e6fb" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "primitive-types", - "sp-externalities", - "sp-runtime-interface-proc-macro", - "sp-std", - "sp-storage", - "sp-tracing", - "sp-wasm-interface", - "static_assertions", + "digest", + "rand_core", ] [[package]] -name = "sp-runtime-interface-proc-macro" -version = "17.0.0" +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfaf6e85b2ec12a4b99cd6d8d57d083e30c94b7f1b0d8f93547121495aae6f0c" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "Inflector", - "expander", - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.55", + "autocfg", ] [[package]] -name = "sp-state-machine" -version = "0.35.0" +name = "sled" +version = "0.34.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718c779ad1d6fcc0be64c7ce030b33fa44b5c8914b3a1319ef63bb5f27fb98df" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" dependencies = [ - "hash-db", + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", "log", - "parity-scale-codec", - "parking_lot", - "rand", - "smallvec", - "sp-core", - "sp-externalities", - "sp-panic-handler", - "sp-std", - "sp-trie", - "thiserror", - "tracing", - "trie-db", + "parking_lot 0.11.2", ] [[package]] -name = "sp-std" -version = "14.0.0" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "sp-storage" -version = "19.0.0" +name = "socket2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb92d7b24033a8a856d6e20dd980b653cbd7af7ec471cc988b1b7c1d2e3a32b" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ - "impl-serde", - "parity-scale-codec", - "ref-cast", - "serde", - "sp-debug-derive", - "sp-std", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "sp-tracing" -version = "16.0.0" +name = "soketto" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" +checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" dependencies = [ - "parity-scale-codec", - "sp-std", - "tracing", - "tracing-core", - "tracing-subscriber", + "base64", + "bytes", + "futures", + "httparse", + "log", + "rand", + "sha1", ] [[package]] -name = "sp-trie" -version = "29.0.0" +name = "sp-arithmetic" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4d24d84a0beb44a71dcac1b41980e1edf7fb722c7f3046710136a283cd479b" +checksum = "910c07fa263b20bf7271fdd4adcb5d3217dfdac14270592e0780223542e7e114" dependencies = [ - "ahash 0.8.11", - "hash-db", - "lazy_static", - "memory-db", - "nohash-hasher", + "integer-sqrt", + "num-traits", "parity-scale-codec", - "parking_lot", - "rand", "scale-info", - "schnellru", - "sp-core", - "sp-externalities", "sp-std", - "thiserror", - "tracing", - "trie-db", - "trie-root", + "static_assertions", ] [[package]] -name = "sp-wasm-interface" -version = "20.0.0" +name = "sp-crypto-hashing" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" dependencies = [ - "anyhow", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "sp-std", - "wasmtime", + "blake2b_simd", + "byteorder", + "digest", + "sha2", + "sha3", + "twox-hash", ] [[package]] -name = "sp-weights" -version = "27.0.0" +name = "sp-std" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e874bdf9dd3fd3242f5b7867a4eaedd545b02f29041a46d222a9d9d5caaaa5c" -dependencies = [ - "bounded-collections", - "parity-scale-codec", - "scale-info", - "serde", - "smallvec", - "sp-arithmetic", - "sp-debug-derive", - "sp-std", -] +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" [[package]] name = "spin" @@ -4073,27 +2676,6 @@ dependencies = [ "der", ] -[[package]] -name = "ss58-registry" -version = "1.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4743ce898933fbff7bbf414f497c459a782d496269644b3d650a398ae6a487ba" -dependencies = [ - "Inflector", - "num-format", - "proc-macro2", - "quote", - "serde", - "serde_json", - "unicode-xid", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -4102,132 +2684,75 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "substrate-bip39" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7590dc041b9bc2825e52ce5af8416c73dbe9d0654402bfd4b4941938b94d8f" -dependencies = [ - "hmac 0.11.0", - "pbkdf2 0.8.0", - "schnorrkel", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "subtle" -version = "2.5.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "subxt" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3323d5c27898b139d043dc1ee971f602f937b99354ee33ee933bd90e0009fbd" +name = "substrate-constructor" +version = "0.1.0" +source = "git+https://github.com/Alzymologist/substrate-constructor#b32ba7a2a85a890d8911356123f8b9d2ed941392" dependencies = [ - "async-trait", - "base58", - "blake2", - "derivative", - "either", - "frame-metadata 16.0.0", - "futures", + "bitvec", + "external-memory-tools", + "frame-metadata", "hex", - "impl-serde", - "instant", - "jsonrpsee 0.21.0", + "num-bigint", "parity-scale-codec", "primitive-types", - "scale-bits", - "scale-decode", - "scale-encode", "scale-info", - "scale-value", - "serde", - "serde_json", - "sp-core", - "sp-core-hashing", - "sp-runtime", - "subxt-lightclient", - "subxt-macro", - "subxt-metadata", + "sp-arithmetic", + "sp-crypto-hashing", + "substrate-crypto-light", + "substrate_parser", "thiserror", - "tokio-util", - "tracing", - "url", ] [[package]] -name = "subxt-codegen" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0e58c3f88651cff26aa52bae0a0a85f806a2e923a20eb438c16474990743ea" +name = "substrate-crypto-light" +version = "0.1.0" +source = "git+https://github.com/Alzymologist/substrate-crypto-light#ed4b83b5fc67cf06ebca009791b3513674045f80" dependencies = [ - "frame-metadata 16.0.0", - "heck", - "hex", - "jsonrpsee 0.21.0", + "base58", + "blake2b_simd", + "ed25519-zebra", + "hmac", + "k256", + "lazy_static", "parity-scale-codec", - "proc-macro2", - "quote", - "scale-info", - "scale-typegen", - "subxt-metadata", - "syn 2.0.55", - "thiserror", - "tokio", -] - -[[package]] -name = "subxt-lightclient" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecec7066ba7bc0c3608fcd1d0c7d9584390990cd06095b6ae4f114f74c4b8550" -dependencies = [ - "futures", - "futures-util", - "serde", - "serde_json", - "smoldot-light", + "pbkdf2", + "rand_core", + "regex", + "schnorrkel", + "sha2", "thiserror", - "tokio", - "tokio-stream", - "tracing", + "zeroize", ] [[package]] -name = "subxt-macro" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365251668613323064803427af8c7c7bc366cd8b28e33639640757669dafebd5" +name = "substrate_parser" +version = "0.6.1" +source = "git+https://github.com/Alzymologist/substrate-parser#c227d40952e3d11b384d30e294f08be2abd1ad43" dependencies = [ - "darling 0.20.8", + "bitvec", + "external-memory-tools", + "frame-metadata", + "hex", + "num-bigint", "parity-scale-codec", - "proc-macro-error", - "quote", - "scale-typegen", - "subxt-codegen", - "syn 2.0.55", + "plot_icon", + "primitive-types", + "scale-info", + "sp-arithmetic", + "sp-crypto-hashing", + "substrate-crypto-light", ] [[package]] -name = "subxt-metadata" -version = "0.34.0" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02aca8d39a1f6c55fff3a8fd81557d30a610fedc1cef03f889a81bc0f8f0b52" -dependencies = [ - "frame-metadata 16.0.0", - "parity-scale-codec", - "scale-info", - "sp-core-hashing", - "thiserror", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -4242,9 +2767,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -4259,49 +2784,71 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] -name = "tap" -version = "1.0.1" +name = "system-configuration" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] [[package]] -name = "target-lexicon" -version = "0.12.14" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] -name = "termcolor" -version = "1.4.1" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ - "winapi-util", + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] [[package]] @@ -4314,11 +2861,44 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -4331,71 +2911,59 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", - "parking_lot", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", -] - -[[package]] -name = "tokio-retry" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" -dependencies = [ - "pin-project", - "rand", - "tokio", + "syn 2.0.77", ] [[package]] -name = "tokio-rustls" -version = "0.24.1" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "rustls 0.21.10", + "native-tls", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.22.3", + "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -4404,87 +2972,49 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "futures-util", - "hashbrown 0.14.3", + "hashbrown", "pin-project-lite", - "slab", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.8.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.9", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.9" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ - "indexmap 2.2.6", + "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow", ] [[package]] @@ -4500,20 +3030,19 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -4521,7 +3050,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4535,7 +3063,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] [[package]] @@ -4545,72 +3073,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.2.25" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "ansi_term", - "chrono", - "lazy_static", "matchers", + "nu-ansi-term", + "once_cell", "regex", - "serde", - "serde_json", "sharded-slab", "smallvec", "thread_local", + "time", "tracing", "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "trie-db" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff28e0f815c2fea41ebddf148e008b077d2faddb026c9555b29696114d602642" -dependencies = [ - "hash-db", - "hashbrown 0.13.2", - "log", - "rustc-hex", - "smallvec", -] - -[[package]] -name = "trie-root" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" -dependencies = [ - "hash-db", ] [[package]] @@ -4626,7 +3106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest", "rand", "static_assertions", ] @@ -4657,34 +3137,24 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "universal-hash" -version = "0.5.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "untrusted" @@ -4692,11 +3162,25 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64", + "log", + "once_cell", + "serde", + "serde_json", + "url", +] + [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -4705,45 +3189,21 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "valuable" -version = "0.1.0" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "w3f-bls" -version = "0.1.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" -dependencies = [ - "ark-bls12-377", - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-serialize", - "ark-serialize-derive", - "arrayref", - "constcat", - "digest 0.10.7", - "rand", - "rand_chacha", - "rand_core 0.6.4", - "sha2 0.10.8", - "sha3", - "thiserror", - "zeroize", -] +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -4772,34 +3232,47 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4807,203 +3280,40 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "wasmi" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" -dependencies = [ - "smallvec", - "spin", - "wasmi_arena", - "wasmi_core", - "wasmparser-nostd", -] - -[[package]] -name = "wasmi_arena" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" - -[[package]] -name = "wasmi_core" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" -dependencies = [ - "downcast-rs", - "libm", - "num-traits", - "paste", -] - -[[package]] -name = "wasmparser" -version = "0.102.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" -dependencies = [ - "indexmap 1.9.3", - "url", -] - -[[package]] -name = "wasmparser-nostd" -version = "0.100.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" -dependencies = [ - "indexmap-nostd", -] - -[[package]] -name = "wasmtime" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f907fdead3153cb9bfb7a93bbd5b62629472dc06dee83605358c64c52ed3dda9" -dependencies = [ - "anyhow", - "bincode", - "cfg-if", - "indexmap 1.9.3", - "libc", - "log", - "object 0.30.4", - "once_cell", - "paste", - "psm", - "serde", - "target-lexicon", - "wasmparser", - "wasmtime-environ", - "wasmtime-jit", - "wasmtime-runtime", - "windows-sys 0.45.0", -] - -[[package]] -name = "wasmtime-asm-macros" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b9daa7c14cd4fa3edbf69de994408d5f4b7b0959ac13fa69d465f6597f810d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "wasmtime-environ" -version = "8.0.1" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a990198cee4197423045235bf89d3359e69bd2ea031005f4c2d901125955c949" -dependencies = [ - "anyhow", - "cranelift-entity", - "gimli 0.27.3", - "indexmap 1.9.3", - "log", - "object 0.30.4", - "serde", - "target-lexicon", - "thiserror", - "wasmparser", - "wasmtime-types", -] - -[[package]] -name = "wasmtime-jit" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de48df552cfca1c9b750002d3e07b45772dd033b0b206d5c0968496abf31244" -dependencies = [ - "addr2line 0.19.0", - "anyhow", - "bincode", - "cfg-if", - "cpp_demangle", - "gimli 0.27.3", - "log", - "object 0.30.4", - "rustc-demangle", - "serde", - "target-lexicon", - "wasmtime-environ", - "wasmtime-jit-icache-coherence", - "wasmtime-runtime", - "windows-sys 0.45.0", -] +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] -name = "wasmtime-jit-debug" -version = "8.0.1" +name = "web-sys" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ - "once_cell", -] - -[[package]] -name = "wasmtime-jit-icache-coherence" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecae978b13f7f67efb23bd827373ace4578f2137ec110bbf6a4a7cde4121bbd" -dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "wasmtime-runtime" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658cf6f325232b6760e202e5255d823da5e348fdea827eff0a2a22319000b441" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "indexmap 1.9.3", - "libc", - "log", - "mach", - "memfd", - "memoffset", - "paste", - "rand", - "rustix 0.36.17", - "wasmtime-asm-macros", - "wasmtime-environ", - "wasmtime-jit-debug", - "windows-sys 0.45.0", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wasmtime-types" -version = "8.0.1" +name = "webpki-roots" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" dependencies = [ - "cranelift-entity", - "serde", - "thiserror", - "wasmparser", + "rustls-pki-types", ] [[package]] @@ -5024,11 +3334,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -5038,30 +3348,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-registry" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ - "windows-targets 0.52.4", + "windows-result", + "windows-strings", + "windows-targets", ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-result" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-strings" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-targets 0.48.5", + "windows-result", + "windows-targets", ] [[package]] @@ -5070,194 +3383,87 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" - -[[package]] -name = "winnow" -version = "0.5.40" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -5271,49 +3477,32 @@ dependencies = [ "tap", ] -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek 4.1.2", - "rand_core 0.6.4", - "serde", - "zeroize", -] - -[[package]] -name = "yap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" - [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -5326,5 +3515,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.77", ] diff --git a/Cargo.toml b/Cargo.toml index 63a1d95..27d5954 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kalatori" authors = ["Alzymologist Oy "] -version = "0.1.3" +version = "0.2.0-rc4" edition = "2021" description = "A gateway daemon for Kalatori." license = "GPL-3.0-or-later" @@ -10,36 +10,76 @@ readme = true keywords = ["substrate", "blockchain", "finance", "service", "middleware"] categories = ["finance"] -[workspace] -members = ["kalatori-ah"] - [dependencies] +axum = { version = "0.7", default-features = false, features = [ + "tokio", + "http1", + "query", + "json", + "matched-path", +] } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "smallvec", + "ansi", + "env-filter", + "time", +] } +ureq = { version = "2", default-features = false, features = ["json"] } +names = { version = "0.14", default-features = false } +tokio-util = { version = "0.7", features = ["rt"] } tokio = { version = "1", features = ["full"] } -tokio-util = { version = "0.7", features = ["full"] } -anyhow = "1" -env_logger = "0.11" -log = "0.4" -subxt = { version = "0.34", features = ["substrate-compat"] } -axum = "0.7" -serde = "1" -redb = "1" -serde_json = "1" +serde = { version = "1", features = ["derive"] } +tracing = "0.1" +scale-info = "2" +axum-macros = "0.4" +primitive-types = { version = "0.12", features = ["codec"] } +jsonrpsee = { version = "0.23", features = ["ws-client"] } +thiserror = "1" +frame-metadata = "16" hex = "0.4" -reconnecting-jsonrpsee-ws-client = { version = "0.3", features = ["subxt"] } +parity-scale-codec = "3" +serde_json = "1" +sp-crypto-hashing = "0.1" +toml_edit = { version = "0.22", features = ["serde"]} +sled = "0.34" +zeroize = "1.7" +clap = { version = "4", features = ["derive", "cargo", "string", "env"] } +shadow-rs = { version = "0.28", default-features = false } +futures = "0.3" +ahash = "0.8" + +substrate_parser = { git = "https://github.com/Alzymologist/substrate-parser" } +substrate-constructor = { git = "https://github.com/Alzymologist/substrate-constructor" } +mnemonic-external = { git = "https://github.com/Alzymologist/mnemonic-external" } +substrate-crypto-light = { git = "https://github.com/Alzymologist/substrate-crypto-light" } + +[build-dependencies] +shadow-rs = { version = "0.28", default-features = false, features = ["git2"] } + +[dev-dependencies] +reqwest = { version = "0.12", features = ["json"] } +lazy_static = "1" +indoc = "2" [profile.release] strip = true lto = true codegen-units = 1 +panic = "abort" [lints.rust] future_incompatible = "warn" let_underscore = "warn" rust_2018_idioms = "warn" unused = "warn" +# TODO: https://github.com/rust-lang/cargo/issues/12918 +rust-2024-compatibility = { level = "warn", priority = -1 } [lints.clippy] shadow_reuse = "warn" shadow_same = "warn" shadow_unrelated = "warn" cargo_common_metadata = "warn" +arithmetic_side_effects = "warn" +# TODO: https://github.com/rust-lang/cargo/issues/12918 +pedantic = { level = "warn", priority = -1 } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..51ceaac --- /dev/null +++ b/build.rs @@ -0,0 +1,44 @@ +use shadow_rs::{ + SdResult, BUILD_OS, BUILD_TARGET, BUILD_TARGET_ARCH, CARGO_MANIFEST_DIR, CARGO_TREE, + CARGO_VERSION, COMMIT_AUTHOR, COMMIT_DATE, COMMIT_DATE_2822, COMMIT_DATE_3339, COMMIT_EMAIL, + COMMIT_HASH, GIT_CLEAN, GIT_STATUS_FILE, LAST_TAG, PKG_DESCRIPTION, PKG_VERSION_MAJOR, + PKG_VERSION_MINOR, PKG_VERSION_PATCH, PKG_VERSION_PRE, TAG, +}; + +fn main() -> SdResult<()> { + shadow_rs::new_deny( + [ + BUILD_OS, + BUILD_TARGET, + BUILD_TARGET_ARCH, + CARGO_MANIFEST_DIR, + CARGO_TREE, + CARGO_VERSION, + COMMIT_AUTHOR, + COMMIT_DATE, + COMMIT_DATE_2822, + COMMIT_DATE_3339, + COMMIT_EMAIL, + COMMIT_HASH, + GIT_CLEAN, + GIT_STATUS_FILE, + LAST_TAG, + PKG_DESCRIPTION, + PKG_VERSION_MAJOR, + PKG_VERSION_MINOR, + PKG_VERSION_PATCH, + PKG_VERSION_PRE, + TAG, + "BUILD_TIME_2822", + "BUILD_RUST_CHANNEL", + "PROJECT_NAME", + // Required for undeniable `CLAP_LONG_VERSION`. + + // BRANCH, + // PKG_VERSION, + // RUST_CHANNEL, + // "BUILD_TIME", + ] + .into(), + ) +} diff --git a/chopsticks/pd-ah.yml b/chopsticks/pd-ah.yml index 8e28c18..bbdb482 100644 --- a/chopsticks/pd-ah.yml +++ b/chopsticks/pd-ah.yml @@ -1,4 +1,4 @@ -endpoint: wss://polkadot-asset-hub-rpc.polkadot.io +endpoint: wss://statemint.api.onfinality.io/public-ws import-storage: System: diff --git a/configs/chopsticks.toml b/configs/chopsticks.toml new file mode 100644 index 0000000..e832b2b --- /dev/null +++ b/configs/chopsticks.toml @@ -0,0 +1,18 @@ +account-lifetime = 86400000 # 1 day. +depth = 3600000 # 1 hour. +debug = true +in-memory-db = true + +[[chain]] +name = "statemint" +endpoints = [ + "ws://localhost:8000" +] + +[[chain.asset]] +name = "USDC" +id = 1337 + +[[chain.asset]] +name = "USDT" +id = 1984 diff --git a/configs/kusama.toml b/configs/kusama.toml new file mode 100644 index 0000000..e50b237 --- /dev/null +++ b/configs/kusama.toml @@ -0,0 +1,11 @@ +account-lifetime = 604800000 # 1 week. +depth = 86400000 # 1 day. + +[[chain]] +name = "kusama" +native-token = "KSM" +decimals = 12 +endpoints = [ + "wss://kusama-rpc.polkadot.io", + "wss://1rpc.io/ksm", +] diff --git a/configs/polkadot.toml b/configs/polkadot.toml new file mode 100644 index 0000000..518bbe1 --- /dev/null +++ b/configs/polkadot.toml @@ -0,0 +1,27 @@ +account-lifetime = 604800000 # 1 week. +debug = true +depth = 86400000 # 1 day. + +[[chain]] +name = "polkadot" +native-token = "DOT" +decimals = 10 +endpoints = [ + "wss://rpc.polkadot.io", + "wss://1rpc.io/dot", +] + +[[chain]] +name = "statemint" +endpoints = [ + "wss://polkadot-asset-hub-rpc.polkadot.io", + "wss://statemint-rpc.dwellir.com", +] + +[[chain.asset]] +name = "USDC" +id = 1337 + +[[chain.asset]] +name = "USDt" +id = 1984 diff --git a/configs/testnets.toml b/configs/testnets.toml new file mode 100644 index 0000000..223773a --- /dev/null +++ b/configs/testnets.toml @@ -0,0 +1,55 @@ +account-lifetime = 86400000 # 1 day. +depth = 3600000 # 1 hour. +debug = true +in-memory-db = true + +[[chain]] +name = "westend" +native-token = "WND" +decimals = 12 +endpoints = [ + "wss://westend-rpc.polkadot.io", + "wss://westend-rpc.dwellir.com", +] + +[[chain]] +name = "westmint" +native-token = "WND AH" +decimals = 12 +endpoints = [ + "wss://westend-asset-hub-rpc.polkadot.io", + "wss://westmint-rpc.dwellir.com", +] + +[[chain.asset]] +name = "JOE" +id = 8 + +[[chain.asset]] +name = "TEST" +id = 1234 + +[[chain]] +name = "rococo" +native-token = "ROC" +decimals = 12 +endpoints = [ + "wss://rococo-rpc.polkadot.io", +] + +[[chain]] +name = "assethub-rococo" +native-token = "ROC AH" +decimals = 12 +endpoints = [ + "wss://rococo-asset-hub-rpc.polkadot.io", + "wss://rococo-asset-hub-rpc.dwellir.com", +] + +[[chain.asset]] +name = "USDT" +id = 1984 + +[[chain.asset]] +name = "TUSDT" +id = 7777 diff --git a/docs/structure.md b/docs/structure.md new file mode 100644 index 0000000..89ef24a --- /dev/null +++ b/docs/structure.md @@ -0,0 +1,176 @@ +The daemon consists of 5 modules: + +* `callback.rs` +* `database.rs` +* `main.rs` +* `rpc.rs` +* `server.rs` + +## `main.rs` + +Everything starts here. Previously, the start logic was in `lib.rs` because this allowed to add documentation directly to the source code (e.g., for the server API), but since Markdown isn't sufficient for complex descriptions & generating the documentation with `rustdoc` just to read it is inconvenient for those who won't build the daemon from the source, it was decided to use only `main.rs` while keeping the documentation elsewhere. + +At the start, the daemon reads the following environment variables: + +* `KALATORI_CONFIG` + +The path to a config file. + +* `KALATORI_LOG` + +[Filter directives for the logger.](https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/filter/struct.EnvFilter.html) + +* `KALATORI_SEED` + +The seed phrase that new accounts will be derived from. + +* `KALATORI_OLD_SEED_*` + +Old seed phrases for existing accounts. Used for the unimplemented seed rotation logic. + +* `KALATORI_RECIPIENT` + +The recipient account address in the SS58 format. + +* `KALATORI_REMARK` + +The arbitrary string to be passed with the server info from the server API. + +Then the daemon parses a config file. The config format can be found in at the end of `main.rs`. Examples are in the [configs](../configs) directory. + +## `server.rs` + +Contains the server & server API implementation using the `axum` framework. Currently, everything is hardcoded to USDC from the Polkadot Asset Hub parachain. + +The amount parameter in API is treated as `f64` during a de- and serialization due to restrictions on the frontend side. This approach is error-prone because `f64` is insufficient to hold `u128` (that's the actual type of the amount parameter) leading to the loss of last digits during a parsing/formatting. Moreover, the converting between `f64` & `u128` suffers from rounding errors that could unexpectedly mutate a lenghty number during a roundtrip. An example of such a number can be found in the test at the end of `main.rs`. A proposed solution to this problem was to create a custom (de)serializer that'd parse/format floats in the amount parameter directly to/from `u128` skipping the erroneous parts with `f64`, but as it's a quite long task, it was decided to stick with the default (de)serializer with `f64` and hope to get no lenghty amounts. + +## `callback.rs` + +Contains a simple function to make callbacks with the order info. To do this, it was decided to use the `ureq` crate, a blocking HTTP-client. To prevent blocking the async executor, every callback is spawned in the Tokio's blocking threadpool. Since we don't need any complex HTTP 1 features as well as HTTP 2/3, `ureq` is a good choice for a rather dependencies-heavy crate that the daemon is. + +## `rps.rs` + +The most complex module of the daemon where all the crucial blockchain-related logic happens. In the first version of daemon, only one chain & currency (a native token or an asset) were supported in 2 separate binaries. With the requirement to support both a native token & multiple assets within a single daemon, a new architecture was needed. Since from Substrate RPC's POV there's no difference between relaychains & parachains, it was decided to make the support of any number of chains with the parallel processing, different configurations & currencies, not just a pair of a relaychain with a native token & a parachain with assets in 2 threads. The config file was introduced to process chain configurations from a much more convenient format than a cluster of environment variables. For now, the daemon can parse this config and prepare chains & their currencies for the block parsing loop. The block parsing loop isn't implemented. + +### Assumptions + +The current Substrate RPC API & pallets used by the daemon, unfortunately, don't have any strict standard, which all nodes & chains follow, and the common implementations of RPC API & pallets lack of some essential features. This creates restrictions & assumptions for which nodes & chains the daemon can be used with. Of course, our first priority is and will be the support of the Polkadot relaychain & the Polkadot Asset Hub parachain with the support of USDT & USDC assets on that parachain, but in the future, we'll try to lift the following limitations and support a broader range of nodes & chains along with the evolution of the Substrate ecosystem & its standards. + +#### [`ChargeAssetTxPayment`] + +One of difficult parts in the chain preparation is to determine & dynamically process the asset type in [`ChargeAssetTxPayment`], the signed extension that's used to pay transaction fees in an asset instead of a native token. Kusama, Westend & Rococo Asset Hub parachains have the [`MultiLocation`] struct as the asset type, and only Polkadot Asset Hub has the `u32` type as its asset type. There's also a possibility of other types, but the daemon supports only these 2 because they're the most common. The daemon chooses between them by looking up the properties of [`ChargeAssetTxPayment`] in the metadata returned from an RPC server. To avoid depending on the `staging-xcm` crate, the daemon in `asset.rs` has the copy of [`MultiLocation`] from this crate with custom trait implementations to process it along with `u32`. + +#### Transfers + +All extrinsics sent by the daemon are transfer transactions. Despite they themselves are quite unsophisticated, the whole process of *the approximate fee calculation to subtract it from the transfer amount and then, in case of a fail because a real fee was higher than the daemon estimated, the resending of the same transaction after another never accurate calculation* does sound like a really hard path. + +We tried to use the [`transfer_allow_death`] call in the Balances pallet for all transfers because we thought that a transfer fee would never be greater than the existential deposit but that isn't true for all chains (e.g., it's true for Polkadot but not for Kusama), and unlikely will be a standard, so we've stuck with the [`transfer_all`] call for transfers to both the beneficiary & overpayers accounts. + +The Assets pallet doesn't have a call similar to Balances's [`transfer_all`], so the daemon uses [`transfer`] assuming that a fee for that call wouldn't be higher than asset's [`min_balance`]. For now, that's true for USDT & USDC on Polkadot Asset Hub, but **if one these or another assets won't meet this criteria, the daemon won't be able to work with them**. We plan to propose and hopefully include some kind of the `transfer_all` call in Assets pallet to subsequently eliminate this restriction. + +#### Transactions + +The daemon uses the `author_submit_extrinsic` method to submit transactions. Another option to do this is the `author_submit_and_watch_extrinsic` method, it was considered more reliable, albeit more heavy & restricted, because it provides a subscription to a status of every submitted transaction, but its main problem is there's no way to resume a subscription (e.g., in case of an abnormal daemon shutdown or a loss of a connection with an RPC server). To track a transactions status, the daemon will use the [CheckMortality](https://docs.rs/frame-system/32/frame_system/struct.CheckMortality.html) signed extension. It's used to add mortality to a transaction, mortality measured with blocks, so the daemon can calculate a block number on which a transaction would be dead (invalid) and wait for it. If the transaction hasn't appeared before its death block, the transaction considered lost. It might happen for a variety of reasons that the daemon doesn't have the control of, e.g., a connection loss, faulty RPC server, unusual chain's runtime implementation. + +Unresolved questions: + +* What should the daemon do in the case of a lost transaction? + +One of expected behaviours would be to send it again, but it's unclear how many attempts should be made before giving up. + +* How should the transaction mortality affect account lifetimes in the database? (See the description of the `"hitlist"` database table.) + +## `database.rs` + +To store data, the daemon uses the `redb` crate, a key-value store with tables. The daemon database consists of the following tables: + +* `"root"` +* `"keys"` +* `"chains"` +* `"invoices"` +* `"accounts*"` +* `"transactions*"` +* `"hit_list*"` + +### `"root"` + +Contains the database format version & the daemon info. The daemon info tracks the information about chains & keys the daemon is using. + +#### Chains + +Chains are stored as `Map`. `String` is a arbitrary name of a chain from the daemon config. Chain properties consists of a chain's hash, genesis hash, kind, and assets. Chain hash is used as an internal key to a chain in other tables instead of a string from the config to enable the renaming of a chain without changing its internal key. A genesis hash is used to check whether given RPC endpoints represent a right chain. A kind means whether a chain operates with [`MultiLocation`] or `u32` as the asset type (if a chain doesn't have assets, `u32` is the default). + +#### Keys + +Accounts for new invoices are derived from the current key. If the database has no invoices, the current key can be replaced with another one from the `KALATORI_SEED` environment variable. If the database has invoices, the current key can be replaced in the same way, but the previous current key's seed must be moved to a new `KALATORI_OLD_SEED_*` variable. Then the daemon will match old seeds with public keys stored in the database. This is used for the key rotation logic. + +### `"keys"` + +The contuation of the above paragraph. Keys are also stored here to track a number of account created with a certain key. Once the number reaches 0, its key is deleted from the database, and the daemon won't require its seed in `KALATORI_OLD_SEED_*` variables. + +This data is stored in separated database because the `"root"` table stores data in encoded form requiring a full decode & encode roundtrip for each slot change. + +### `"chains"` + +Used to store the last saved block for each chain. + +### `"invoices"` + +Invoices are stored as `Map`. The key has an arbitrary format, and set by the server module that in turn receives it from the frontend. + +An invoice contains: + +* The public key & the hard derivation joint. + +The payment account address can be generated from them. + +In the first version of the daemon, the database didn't store the invoice's derivation joint and calculated it from the invoice's string key received from the server module. There was a very small chance of the hash collision that could lead to, e.g., returning information about already paid invoice instead of creating a new one, so it was decided to use a calculated derivation joint as the initial hash, check if the database contains an account from the joint, and if not, create an account from the initial joint, or calculate an unoccupied joint and create an account with it. + +* The payment status. + +It's unclear how to properly separate unpaid accounts for the unpaid account checking on the daemon startup. There are a few possible options: iterate over all invoices filtering unpaid ones out; storing paid & unpaid invoices in separate tables; storing only unpaid invoices' keys in a separate table. + +* The creation timestamp. + +* The price. + +Must be checked to be greater than its currency's existential deposit. + +* The callback. + +Used to send invoice statuses back to the frontend. Ignored if empty. + +* The message. + +A message with an arbitrary human-readable format. Usually contains error messages. + +* Transactions. + +Finalized & pending transfer transactions. Each transaction contains the recipient, the amount, the string with the hex-encoded transaction, and the currency info (since each invoice has only 1 currency, it's unclear why the server API needs the currency info for every invoice's transaction). + +> The next tables have `*` at the end of their name. It's the placeholder for a chain hash. The daemon creates a set of these table for each chain. + +### `"accounts*"` + +Stores account addresses with currency info (`Map<(Option, AccountId), InvoiceKey>`) for the block processing loop. What's notably 1 account address can be used for different currencies in case of an aforementioned hash collision because while invoices are created for 1 currency, their account's transactions don't depend on currencies, so the daemon should be able to process multi-currency accounts without conflicts. + +### `"transactions*"` + +Stores info about pending transfer transactions with their death block as the key. + +### `"hit_list*"` + +The content of this table is in progress. It should be used to track lifetimes of invoices to remove them from the database, thereby maintaining the stable element search time and avoiding the unused accounts tracking for overpayments. Currently, this table looks like `Map, Account)>`, but this layout is insufficient because of the following + +Unresolved questions: + +* On a startup, the daemon simply removes all dead invoices. But consider the following scenario: the daemon shuts down (due to a crash or manually) after creating an invoice, invoice's account receives money, and after a time longer than the invoice's lifetime, the daemon starts up and immediately deletes the dead but paid invoice. How to avoid this situation? Should the daemon check on startup all unpaid invoices, and renew lifetimes of just paid ones? What about overpayments on paid invoices? Should the daemon renew lifetimes of all invoices then? + +* How transaction lifetimes should affect ones of invoices? Currently, if some transaction appears in a block after its invoice's lifetime, the transaction is ignored because its invoice have been deleted. This applies to both incoming & outgoing refund/withdrawal transactions. Lifetimes of transactions aren't fixed values, their upper bound depends on the chains' `BlockHashCount` runtime parameter that can be different on each chain and modified on a runtime upgrade, and the lower bound depends on a block producing/finalization algorithm and properties of a connection with an RPC server. + +[`MultiLocation`]: https://docs.rs/staging-xcm/11/staging_xcm/v3/struct.MultiLocation.html +[`transfer_all`]: https://docs.rs/pallet-balances/33/pallet_balances/pallet/struct.Pallet.html#method.transfer_all +[`transfer_allow_death`]: https://docs.rs/pallet-balances/33/pallet_balances/pallet/struct.Pallet.html#method.transfer_allow_death +[`transfer`]: https://docs.rs/pallet-assets/33/pallet_assets/pallet/struct.Pallet.html#method.transfer +[`min_balance`]: https://docs.rs/pallet-assets/33/src/pallet_assets/types.rs.html#66 +[`ChargeAssetTxPayment`]: https://docs.rs/pallet-asset-tx-payment/32/pallet_asset_tx_payment/struct.ChargeAssetTxPayment.html diff --git a/kalatori-ah/Cargo.toml b/kalatori-ah/Cargo.toml deleted file mode 100644 index 0b01f2a..0000000 --- a/kalatori-ah/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "kalatori-ah" -authors = ["Alzymologist Oy "] -version = "0.1.3" -edition = "2021" -description = "A gateway daemon for Kalatori." -license = "GPL-3.0-or-later" -repository = "https://github.com/Alzymologist/Kalatori-backend" -readme = true -keywords = ["substrate", "blockchain", "finance", "service", "middleware"] -categories = ["finance"] - -[dependencies] -tokio = { version = "1", features = ["full"] } -tokio-util = { version = "0.7", features = ["full"] } -anyhow = "1" -env_logger = "0.11" -log = "0.4" -subxt = { version = "0.34", features = ["substrate-compat"] } -axum = "0.7" -serde = "1" -redb = "1" -serde_json = "1" -hex = "0.4" -reconnecting-jsonrpsee-ws-client = { version = "0.3", features = ["subxt"] } - -[profile.release] -strip = true -lto = true -codegen-units = 1 - -[lints.rust] -future_incompatible = "warn" -let_underscore = "warn" -rust_2018_idioms = "warn" -unused = "warn" - -[lints.clippy] -shadow_reuse = "warn" -shadow_same = "warn" -shadow_unrelated = "warn" -cargo_common_metadata = "warn" diff --git a/kalatori-ah/src/database.rs b/kalatori-ah/src/database.rs deleted file mode 100644 index 6cbe14d..0000000 --- a/kalatori-ah/src/database.rs +++ /dev/null @@ -1,380 +0,0 @@ -use crate::{ - rpc::{ChainProperties, EndpointProperties}, - Account, Balance, BlockNumber, RuntimeConfig, Version, DATABASE_VERSION, OVERRIDE_RPC, SEED, -}; -use anyhow::{Context, Result}; -use redb::{ - backends::InMemoryBackend, AccessGuard, ReadOnlyTable, ReadableTable, RedbValue, Table, - TableDefinition, TableHandle, TypeName, -}; -use std::sync::Arc; -use subxt::{ - ext::{ - codec::{Compact, Decode, Encode}, - sp_core::{ - crypto::Ss58Codec, - sr25519::{Pair, Public}, - DeriveJunction, Pair as _, - }, - }, - tx::PairSigner, -}; -use tokio::{ - sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}, - task, -}; - -type Order = [u8; 32]; - -pub const MODULE: &str = module_path!(); - -// Tables - -const ROOT: TableDefinition<'_, &str, Vec> = TableDefinition::new("root"); -const INVOICES: TableDefinition<'_, &[u8; 32], Invoice> = TableDefinition::new("invoices"); - -// Keys - -// The database version must be stored in a separate slot to be used by the not implemented yet -// database migration logic. -const DB_VERSION_KEY: &str = "db_version"; -const DAEMON_INFO: &str = "daemon_info"; -const LAST_BLOCK: &str = "last_block"; - -// Slots - -#[derive(Debug, Encode, Decode)] -#[codec(crate = subxt::ext::codec)] -pub struct Invoice { - pub recipient: Account, - pub order: Order, - pub status: InvoiceStatus, -} - -impl Invoice { - pub fn signer(&self, pair: &Pair) -> Result> { - let invoice_pair = pair - .derive( - [self.recipient.clone().into(), self.order] - .map(DeriveJunction::Hard) - .into_iter(), - None, - ) - .context("failed to derive an invoice key pair")? - .0; - - Ok(PairSigner::new(invoice_pair)) - } -} - -#[derive(Debug, Encode, Decode)] -#[codec(crate = subxt::ext::codec)] -pub enum InvoiceStatus { - Unpaid(Balance), - Paid(Balance), -} - -impl RedbValue for Invoice { - type SelfType<'a> = Self; - - type AsBytes<'a> = Vec; - - fn fixed_width() -> Option { - None - } - - fn from_bytes<'a>(mut data: &'a [u8]) -> Self::SelfType<'_> - where - Self: 'a, - { - Self::decode(&mut data).unwrap() - } - - fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'a>) -> Self::AsBytes<'_> { - value.encode() - } - - fn type_name() -> TypeName { - TypeName::new(stringify!(Invoice)) - } -} - -#[derive(Encode, Decode)] -#[codec(crate = subxt::ext::codec)] -struct DaemonInfo { - rpc: String, - key: Public, -} - -pub struct Database { - db: redb::Database, - properties: Arc>, - pair: Pair, - rpc: String, - destination: Option, -} - -impl Database { - pub fn initialise( - path_option: Option, - override_rpc: bool, - pair: Pair, - EndpointProperties { url, chain }: EndpointProperties, - destination: Option, - ) -> Result<(Arc, Option)> { - let public = pair.public(); - let public_formatted = public.to_ss58check_with_version( - task::block_in_place(|| chain.blocking_read()).address_format, - ); - let given_rpc = url.get(); - - let mut database = if let Some(path) = path_option { - log::info!("Creating/Opening the database at \"{path}\"."); - - redb::Database::create(path) - } else { - log::warn!( - "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" - ); - - redb::Database::builder().create_with_backend(InMemoryBackend::new()) - }.context("failed to create/open the database")?; - - let tx = database - .begin_write() - .context("failed to begin a write transaction")?; - let mut table = tx - .open_table(ROOT) - .with_context(|| format!("failed to open the `{}` table", ROOT.name()))?; - drop( - tx.open_table(INVOICES) - .with_context(|| format!("failed to open the `{}` table", INVOICES.name()))?, - ); - - let last_block = match ( - get_slot(&table, DB_VERSION_KEY)?, - get_slot(&table, DAEMON_INFO)?, - get_slot(&table, LAST_BLOCK)?, - ) { - (None, None, None) => { - table - .insert( - DB_VERSION_KEY, - Compact(DATABASE_VERSION).encode(), - ) - .context("failed to insert the database version")?; - insert_daemon_info(&mut table, given_rpc.clone(), public)?; - - None - } - (Some(encoded_db_version), Some(daemon_info), last_block_option) => { - let Compact::(db_version) = - decode_slot(&encoded_db_version, DB_VERSION_KEY)?; - let DaemonInfo { rpc: db_rpc, key } = decode_slot(&daemon_info, DAEMON_INFO)?; - - if db_version != DATABASE_VERSION { - anyhow::bail!( - "database contains an unsupported database version (\"{db_version}\"), expected \"{DATABASE_VERSION}\"" - ); - } - - if public != key { - anyhow::bail!( - "public key from `{SEED}` doesn't equal the one from the database (\"{public_formatted}\")" - ); - } - - if given_rpc != db_rpc { - if override_rpc { - log::warn!( - "The saved RPC endpoint ({db_rpc:?}) differs from the given one ({given_rpc:?}) and will be overwritten by it because `{OVERRIDE_RPC}` is set." - ); - - insert_daemon_info(&mut table, given_rpc.clone(), public)?; - } else { - anyhow::bail!( - "database contains a different RPC endpoint address ({db_rpc:?}), expected {given_rpc:?}" - ); - } - } else if override_rpc { - log::warn!( - "`{OVERRIDE_RPC}` is set but the saved RPC endpoint ({db_rpc:?}) equals to the given one." - ); - } - - if let Some(encoded_last_block) = last_block_option { - Some(decode_slot::>(&encoded_last_block, LAST_BLOCK)?.0) - } else { - None - } - } - _ => anyhow::bail!( - "database was found but it doesn't contain `{DB_VERSION_KEY:?}` and/or `{DAEMON_INFO:?}`, maybe it was created by another program" - ), - }; - - drop(table); - - tx.commit().context("failed to commit a transaction")?; - - let compacted = database - .compact() - .context("failed to compact the database")?; - - if compacted { - log::debug!("The database was successfully compacted."); - } else { - log::debug!("The database doesn't need the compaction."); - } - - log::info!("Public key from the given seed: \"{public_formatted}\"."); - - Ok(( - Arc::new(Self { - db: database, - properties: chain, - pair, - rpc: given_rpc, - destination, - }), - last_block, - )) - } - - pub fn rpc(&self) -> &str { - &self.rpc - } - - pub fn destination(&self) -> &Option { - &self.destination - } - - pub fn write(&self) -> Result> { - self.db - .begin_write() - .map(WriteTransaction) - .context("failed to begin a write transaction for the database") - } - - pub fn read(&self) -> Result> { - self.db - .begin_read() - .map(ReadTransaction) - .context("failed to begin a read transaction for the database") - } - - pub async fn properties(&self) -> RwLockReadGuard<'_, ChainProperties> { - self.properties.read().await - } - - pub async fn properties_write(&self) -> RwLockWriteGuard<'_, ChainProperties> { - self.properties.write().await - } - - pub fn pair(&self) -> &Pair { - &self.pair - } -} - -pub struct ReadTransaction<'db>(redb::ReadTransaction<'db>); - -impl ReadTransaction<'_> { - pub fn invoices(&self) -> Result> { - self.0 - .open_table(INVOICES) - .map(ReadInvoices) - .with_context(|| format!("failed to open the `{}` table", INVOICES.name())) - } -} - -pub struct ReadInvoices<'tx>(ReadOnlyTable<'tx, &'static [u8; 32], Invoice>); - -impl ReadInvoices<'_> { - pub fn get(&self, account: &Account) -> Result>> { - self.0 - .get(AsRef::<[u8; 32]>::as_ref(account)) - .context("failed to get an invoice from the database") - } - - pub fn try_iter( - &self, - ) -> Result, AccessGuard<'_, Invoice>)>>> - { - self.0 - .iter() - .context("failed to get the invoices iterator") - .map(|iter| iter.map(|item| item.context("failed to get an invoice from the iterator"))) - } -} - -pub struct WriteTransaction<'db>(redb::WriteTransaction<'db>); - -impl<'db> WriteTransaction<'db> { - pub fn root(&self) -> Result> { - self.0 - .open_table(ROOT) - .map(Root) - .with_context(|| format!("failed to open the `{}` table", ROOT.name())) - } - - pub fn invoices(&self) -> Result> { - self.0 - .open_table(INVOICES) - .map(WriteInvoices) - .with_context(|| format!("failed to open the `{}` table", INVOICES.name())) - } - - pub fn commit(self) -> Result<()> { - self.0 - .commit() - .context("failed to commit a write transaction in the database") - } -} - -pub struct WriteInvoices<'db, 'tx>(Table<'db, 'tx, &'static [u8; 32], Invoice>); - -impl WriteInvoices<'_, '_> { - pub fn save( - &mut self, - account: &Account, - invoice: &Invoice, - ) -> Result>> { - self.0 - .insert(AsRef::<[u8; 32]>::as_ref(account), invoice) - .context("failed to save an invoice in the database") - } -} - -pub struct Root<'db, 'tx>(Table<'db, 'tx, &'static str, Vec>); - -impl Root<'_, '_> { - pub fn save_last_block(&mut self, number: BlockNumber) -> Result<()> { - self.0 - .insert(LAST_BLOCK, Compact(number).encode()) - .context("context")?; - - Ok(()) - } -} - -fn get_slot(table: &Table<'_, '_, &str, Vec>, key: &str) -> Result>> { - table - .get(key) - .map(|slot_option| slot_option.map(|slot| slot.value().clone())) - .with_context(|| format!("failed to get the {key:?} slot")) -} - -fn decode_slot(mut slot: &[u8], key: &str) -> Result { - T::decode(&mut slot).with_context(|| format!("failed to decode the {key:?} slot")) -} - -fn insert_daemon_info( - table: &mut Table<'_, '_, &str, Vec>, - rpc: String, - key: Public, -) -> Result<()> { - table - .insert(DAEMON_INFO, DaemonInfo { rpc, key }.encode()) - .map(|_| ()) - .context("failed to insert the daemon info") -} diff --git a/kalatori-ah/src/lib.rs b/kalatori-ah/src/lib.rs deleted file mode 100644 index 2ccdd81..0000000 --- a/kalatori-ah/src/lib.rs +++ /dev/null @@ -1,322 +0,0 @@ -use anyhow::{Context, Error, Result}; -use database::Database; -use env_logger::{Builder, Env}; -use environment_variables::{ - DATABASE, DESTINATION, HOST, IN_MEMORY_DB, LOG, LOG_STYLE, OVERRIDE_RPC, RPC, SEED, USD_ASSET, -}; -use log::LevelFilter; -use rpc::Processor; -use serde::Deserialize; -use std::{ - env::{self, VarError}, - future::Future, -}; -use subxt::{ - config::{DefaultExtrinsicParams, Header}, - ext::{ - codec::{Decode, Encode}, - scale_decode::DecodeAsType, - scale_encode::EncodeAsType, - sp_core::{crypto::AccountId32, Pair}, - }, - Config, PolkadotConfig, -}; -use tokio::{ - signal, - sync::mpsc::{self, UnboundedSender}, -}; -use tokio_util::{sync::CancellationToken, task::TaskTracker}; - -mod database; -mod rpc; - -pub mod server; - -pub mod environment_variables { - pub const HOST: &str = "KALATORI_HOST"; - pub const SEED: &str = "KALATORI_SEED"; - pub const LOG: &str = "KALATORI_LOG"; - pub const LOG_STYLE: &str = "KALATORI_LOG_STYLE"; - pub const DATABASE: &str = "KALATORI_DATABASE"; - pub const RPC: &str = "KALATORI_RPC"; - pub const OVERRIDE_RPC: &str = "KALATORI_OVERRIDE_RPC"; - pub const IN_MEMORY_DB: &str = "KALATORI_IN_MEMORY_DB"; - pub const DESTINATION: &str = "KALATORI_DESTINATION"; - pub const USD_ASSET: &str = "KALATORI_USD_ASSET"; -} - -pub const DEFAULT_RPC: &str = "wss://westend-asset-hub-rpc.polkadot.io"; -pub const DATABASE_VERSION: Version = 0; -// Expected USD(C/T) fee (0.03) -pub const EXPECTED_USDX_FEE: Balance = 30000; - -const USDT_ID: u32 = 1984; -const USDC_ID: u32 = 1337; -// https://github.com/paritytech/polkadot-sdk/blob/7c9fd83805cc446983a7698c7a3281677cf655c8/substrate/client/cli/src/config.rs#L50 -const SCANNER_TO_LISTENER_SWITCH_POINT: BlockNumber = 512; - -#[derive(Clone, Copy)] -enum Usd { - T, - C, -} - -impl Usd { - fn id(self) -> u32 { - match self { - Usd::T => USDT_ID, - Usd::C => USDC_ID, - } - } -} - -type OnlineClient = subxt::OnlineClient; -type Account = ::AccountId; -type BlockNumber = <::Header as Header>::Number; -type Hash = ::Hash; -// https://github.com/paritytech/polkadot-sdk/blob/a3dc2f15f23b3fd25ada62917bfab169a01f2b0d/substrate/bin/node/primitives/src/lib.rs#L43 -type Balance = u128; -// https://github.com/paritytech/subxt/blob/f06a95d687605bf826db9d83b2932a73a57b169f/subxt/src/config/signed_extensions.rs#L71 -type Nonce = u64; -// https://github.com/dtolnay/semver/blob/f9cc2df9415c880bd3610c2cdb6785ac7cad31ea/src/lib.rs#L163-L165 -type Version = u64; -// https://github.com/serde-rs/json/blob/0131ac68212e8094bd14ee618587d731b4f9a68b/src/number.rs#L29 -type Decimals = u64; - -struct RuntimeConfig; - -impl Config for RuntimeConfig { - type Hash = ::Hash; - type AccountId = AccountId32; - type Address = ::Address; - type Signature = ::Signature; - type Hasher = ::Hasher; - type Header = ::Header; - type ExtrinsicParams = DefaultExtrinsicParams; - type AssetId = u32; -} - -#[derive(EncodeAsType, Encode, Decode, DecodeAsType, Clone, Debug, Deserialize, PartialEq)] -#[encode_as_type(crate_path = "subxt::ext::scale_encode")] -#[decode_as_type(crate_path = "subxt::ext::scale_decode")] -#[codec(crate = subxt::ext::codec)] -struct MultiLocation { - /// The number of parent junctions at the beginning of this `MultiLocation`. - parents: u8, - /// The interior (i.e. non-parent) junctions that this `MultiLocation` contains. - interior: Junctions, -} - -#[derive(EncodeAsType, Encode, Decode, DecodeAsType, Clone, Debug, Deserialize, PartialEq)] -#[encode_as_type(crate_path = "subxt::ext::scale_encode")] -#[decode_as_type(crate_path = "subxt::ext::scale_decode")] -#[codec(crate = subxt::ext::codec)] -enum Junctions { - /// The interpreting consensus system. - #[codec(index = 0)] - Here, - /// A relative path comprising 2 junctions. - #[codec(index = 2)] - X2(Junction, Junction), -} - -#[derive(EncodeAsType, Encode, Decode, DecodeAsType, Clone, Debug, Deserialize, PartialEq)] -#[encode_as_type(crate_path = "subxt::ext::scale_encode")] -#[decode_as_type(crate_path = "subxt::ext::scale_decode")] -#[codec(crate = subxt::ext::codec)] -enum Junction { - /// An instanced, indexed pallet that forms a constituent part of the context. - /// - /// Generally used when the context is a Frame-based chain. - #[codec(index = 4)] - PalletInstance(u8), - /// A non-descript index within the context location. - /// - /// Usage will vary widely owing to its generality. - /// - /// NOTE: Try to avoid using this and instead use a more specific item. - #[codec(index = 5)] - GeneralIndex(#[codec(compact)] u128), -} - -#[doc(hidden)] -#[allow(clippy::too_many_lines)] -#[tokio::main] -pub async fn main() -> Result<()> { - let mut builder = Builder::new(); - - if cfg!(debug_assertions) { - builder.filter_level(LevelFilter::Debug) - } else { - builder - .filter_level(LevelFilter::Off) - .filter_module(server::MODULE, LevelFilter::Info) - .filter_module(rpc::MODULE, LevelFilter::Info) - .filter_module(database::MODULE, LevelFilter::Info) - .filter_module(env!("CARGO_PKG_NAME"), LevelFilter::Info) - } - .parse_env(Env::new().filter(LOG).write_style(LOG_STYLE)) - .init(); - - let host = env::var(HOST) - .with_context(|| format!("`{HOST}` isn't set"))? - .parse() - .with_context(|| format!("failed to convert `{HOST}` to a socket address"))?; - - let usd_asset = match env::var(USD_ASSET) - .with_context(|| format!("`{USD_ASSET}` isn't set"))? - .as_str() - { - "USDC" => Usd::C, - "USDT" => Usd::T, - _ => anyhow::bail!("{USD_ASSET} must equal USDC or USDT"), - }; - - let pair = Pair::from_string( - &env::var(SEED).with_context(|| format!("`{SEED}` isn't set"))?, - None, - ) - .with_context(|| format!("failed to generate a key pair from `{SEED}`"))?; - - let endpoint = env::var(RPC).or_else(|error| { - if error == VarError::NotPresent { - log::debug!( - "`{RPC}` isn't present, using the default value instead: \"{DEFAULT_RPC}\"." - ); - - Ok(DEFAULT_RPC.into()) - } else { - Err(error).context(format!("failed to read `{RPC}`")) - } - })?; - - let override_rpc = env::var_os(OVERRIDE_RPC).is_some(); - - let database_path = if env::var_os(IN_MEMORY_DB).is_none() { - Some(env::var(DATABASE).or_else(|error| { - if error == VarError::NotPresent { - let default_v = match usd_asset { - Usd::C => "database-ah-usdc.redb", - Usd::T => "database-ah-usdt.redb", - }; - - log::debug!( - "`{DATABASE}` isn't present, using the default value instead: \"{default_v}\"." - ); - - Ok(default_v.into()) - } else { - Err(error).context(format!("failed to read `{DATABASE}`")) - } - })?) - } else { - if env::var_os(DATABASE).is_some() { - log::warn!( - "`{IN_MEMORY_DB}` is set along with `{DATABASE}`. The latter will be ignored." - ); - } - - None - }; - - let destination = match env::var(DESTINATION) { - Ok(destination) => Ok(Some( - AccountId32::try_from(hex::decode(&destination[2..])?.as_ref()) - .map_err(|()| anyhow::anyhow!("unknown destination address length"))?, - )), - Err(VarError::NotPresent) => Ok(None), - Err(error) => Err(error).context(format!("failed to read `{DESTINATION}`")), - }?; - - log::info!( - "Kalatori {} by {} is starting...", - env!("CARGO_PKG_VERSION"), - env!("CARGO_PKG_AUTHORS") - ); - - let shutdown_notification = CancellationToken::new(); - let (error_tx, mut error_rx) = mpsc::unbounded_channel(); - - let (api_config, endpoint_properties, updater) = - rpc::prepare(endpoint, shutdown_notification.clone(), usd_asset) - .await - .context("failed to prepare the node module")?; - - let (database, last_saved_block) = Database::initialise( - database_path, - override_rpc, - pair, - endpoint_properties, - destination, - ) - .context("failed to initialise the database module")?; - - let processor = Processor::new(api_config, database.clone(), shutdown_notification.clone()) - .context("failed to initialise the RPC module")?; - - let server = server::new(shutdown_notification.clone(), host, database) - .await - .context("failed to initialise the server module")?; - - let task_tracker = TaskTracker::new(); - - task_tracker.close(); - - task_tracker.spawn(shutdown( - shutdown_listener(shutdown_notification.clone()), - error_tx.clone(), - )); - task_tracker.spawn(shutdown(updater.ignite(), error_tx.clone())); - task_tracker.spawn(shutdown( - processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), - error_tx, - )); - task_tracker.spawn(server); - - while let Some(error) = error_rx.recv().await { - log::error!("Received a fatal error!\n{error:?}"); - - if !shutdown_notification.is_cancelled() { - log::info!("Initialising the shutdown..."); - - shutdown_notification.cancel(); - } - } - - task_tracker.wait().await; - - log::info!("Goodbye!"); - - Ok(()) -} - -async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result<&'static str> { - tokio::select! { - biased; - signal = signal::ctrl_c() => { - signal.context("failed to listen for the shutdown signal")?; - - // Print shutdown log messages on the next line after the Control-C command. - println!(); - - log::info!("Received the shutdown signal. Initialising the shutdown..."); - - shutdown_notification.cancel(); - - Ok("The shutdown signal listener is shut down.") - } - () = shutdown_notification.cancelled() => { - Ok("The shutdown signal listener is shut down.") - } - } -} - -async fn shutdown( - task: impl Future>, - error_tx: UnboundedSender, -) { - match task.await { - Ok(shutdown_message) => log::info!("{shutdown_message}"), - Err(error) => error_tx.send(error).unwrap(), - } -} diff --git a/kalatori-ah/src/main.rs b/kalatori-ah/src/main.rs deleted file mode 100644 index 252df04..0000000 --- a/kalatori-ah/src/main.rs +++ /dev/null @@ -1,5 +0,0 @@ -use anyhow::Result; - -fn main() -> Result<()> { - kalatori_ah::main() -} diff --git a/kalatori-ah/src/rpc.rs b/kalatori-ah/src/rpc.rs deleted file mode 100644 index 5626ab7..0000000 --- a/kalatori-ah/src/rpc.rs +++ /dev/null @@ -1,1279 +0,0 @@ -use crate::{ - database::{Database, Invoice, InvoiceStatus, ReadInvoices}, - shutdown, Account, Balance, BlockNumber, Decimals, Hash, Nonce, OnlineClient, RuntimeConfig, - Usd, EXPECTED_USDX_FEE, SCANNER_TO_LISTENER_SWITCH_POINT, -}; -use anyhow::{Context, Result}; -use reconnecting_jsonrpsee_ws_client::ClientBuilder; -use serde::{Deserialize, Deserializer}; -use std::{ - collections::{hash_map::Entry, HashMap}, - error::Error, - fmt::{self, Arguments, Display, Formatter, Write}, - sync::Arc, -}; -use subxt::{ - backend::{ - legacy::{LegacyBackend, LegacyRpcMethods}, - rpc::{RpcClient, RpcSubscription}, - Backend, BackendExt, RuntimeVersion, - }, - blocks::{Block, BlocksClient}, - config::{DefaultExtrinsicParamsBuilder, Header}, - constants::ConstantsClient, - dynamic::{self, Value}, - error::RpcError, - ext::{ - futures::TryFutureExt, - scale_decode::DecodeAsType, - scale_value::{self, At}, - sp_core::{ - crypto::{AccountId32, Ss58AddressFormat}, - sr25519::Pair, - }, - }, - storage::{Storage, StorageClient}, - tx::{PairSigner, SubmittableExtrinsic, TxClient}, - Config, Metadata, -}; -use tokio::sync::{mpsc::UnboundedSender, RwLock}; -use tokio_util::{sync::CancellationToken, task::TaskTracker}; - -pub const MODULE: &str = module_path!(); - -const MAX_BLOCK_NUMBER_ERROR: &str = "block number type overflow is occurred"; -const BLOCK_NONCE_ERROR: &str = "failed to fetch an account nonce by the scanner client"; - -// Pallets - -const SYSTEM: &str = "System"; -const UTILITY: &str = "Utility"; -const ASSETS: &str = "Assets"; - -async fn fetch_best_block(methods: &LegacyRpcMethods) -> Result { - methods - .chain_get_block_hash(None) - .await - .context("failed to get the best block hash")? - .context("received nothing after requesting the best block hash") -} - -async fn fetch_api_runtime( - methods: &LegacyRpcMethods, - backend: &impl Backend, -) -> Result<(Metadata, RuntimeVersion)> { - let best_block = fetch_best_block(methods).await?; - - Ok(( - fetch_metadata(backend, best_block) - .await - .context("failed to fetch metadata")?, - methods - .state_get_runtime_version(Some(best_block)) - .await - .map(|runtime_version| RuntimeVersion { - spec_version: runtime_version.spec_version, - transaction_version: runtime_version.transaction_version, - }) - .context("failed to fetch the runtime version")?, - )) -} - -async fn fetch_min_balance( - storage_finalized: Storage, - usd_asset: &Usd, -) -> Result { - const ASSET: &str = "Asset"; - const MIN_BALANCE: &str = "min_balance"; - - let asset_info = storage_finalized - .fetch(&dynamic::storage(ASSETS, ASSET, vec![usd_asset.id()])) - .await - .context("failed to fetch asset info from the chain")? - .context("received nothing after fetching asset info from the chain")? - .to_value() - .context("failed to decode account info")?; - let encoded_min_balance = asset_info - .at(MIN_BALANCE) - .with_context(|| format!("{MIN_BALANCE} field wasn't found in asset info"))?; - - encoded_min_balance.as_u128().with_context(|| { - format!("expected `u128` as the type of the min balance, got {encoded_min_balance}") - }) -} - -async fn fetch_metadata(backend: &impl Backend, at: Hash) -> Result { - const LATEST_SUPPORTED_METADATA_VERSION: u32 = 15; - - backend - .metadata_at_version(LATEST_SUPPORTED_METADATA_VERSION, at) - .or_else(|error| async { - if let subxt::Error::Rpc(RpcError::ClientError(_)) | subxt::Error::Other(_) = error { - backend.legacy_metadata(at).await - } else { - Err(error) - } - }) - .await - .map_err(Into::into) -} - -async fn fetch_decimals( - storage: Storage, - usd_asset: &Usd, -) -> Result { - const METADATA: &str = "Metadata"; - const DECIMALS: &str = "decimals"; - - let asset_metadata = storage - .fetch(&dynamic::storage(ASSETS, METADATA, vec![usd_asset.id()])) - .await - .context("failed to fetch asset info from the chain")? - .context("received nothing after fetching asset info from the chain")? - .to_value() - .context("failed to decode account info")?; - let encoded_decimals = asset_metadata - .at(DECIMALS) - .with_context(|| format!("{DECIMALS} field wasn't found in asset info"))?; - - encoded_decimals - .as_u128() - .map(|num| num.try_into().expect("must be less than u64")) - .with_context(|| { - format!("expected `u128` as the type of the min balance, got {encoded_decimals}") - }) -} - -fn fetch_constant( - constants: &ConstantsClient, - constant: (&str, &str), -) -> Result { - constants - .at(&dynamic::constant(constant.0, constant.1)) - .with_context(|| format!("failed to get the constant {constant:?}"))? - .as_type() - .with_context(|| format!("failed to decode the constant {constant:?}")) -} - -pub struct ChainProperties { - pub address_format: Ss58AddressFormat, - pub existential_deposit: Balance, - pub block_hash_count: BlockNumber, - pub decimals: Decimals, - pub usd_asset: Usd, -} - -impl ChainProperties { - fn fetch_only_constants( - existential_deposit: Balance, - decimals: Decimals, - constants: &ConstantsClient, - usd_asset: Usd, - ) -> Result { - const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); - const BLOCK_HASH_COUNT: (&str, &str) = (SYSTEM, "BlockHashCount"); - - Ok(Self { - address_format: Ss58AddressFormat::custom(fetch_constant(constants, ADDRESS_PREFIX)?), - existential_deposit, - decimals, - block_hash_count: fetch_constant(constants, BLOCK_HASH_COUNT)?, - usd_asset, - }) - } -} - -pub struct ApiConfig { - api: Arc, - methods: Arc>, - backend: Arc>, -} - -pub struct EndpointProperties { - pub url: CheckedUrl, - pub chain: Arc>, -} - -pub struct CheckedUrl(String); - -impl CheckedUrl { - pub fn get(self) -> String { - self.0 - } -} - -pub async fn prepare( - url: String, - shutdown_notification: CancellationToken, - usd_asset: Usd, -) -> Result<(ApiConfig, EndpointProperties, Updater)> { - // TODO: - // The current reconnecting client implementation automatically restores all subscriptions, - // including unrecoverable ones, losing all notifications! For now, it shouldn't affect the - // daemon, but may in the future, so we should consider creating our own implementation. - let rpc = RpcClient::new( - ClientBuilder::new() - .build(url.clone()) - .await - .context("failed to construct the RPC client")?, - ); - - log::info!("Connected to an RPC server at \"{url}\"."); - - let methods = Arc::new(LegacyRpcMethods::new(rpc.clone())); - let backend = Arc::new(LegacyBackend::new(rpc)); - - let (metadata, runtime_version) = fetch_api_runtime(&methods, &*backend) - .await - .context("failed to fetch the runtime of the API client")?; - let genesis_hash = methods - .genesis_hash() - .await - .context("failed to get the genesis hash")?; - let api = Arc::new( - OnlineClient::from_backend_with(genesis_hash, runtime_version, metadata, backend.clone()) - .context("failed to construct the API client")?, - ); - let constants = api.constants(); - - let min_balance = fetch_min_balance( - OnlineClient::from_url(url.clone()) - .await? - .storage() - .at_latest() - .await?, - &usd_asset, - ) - .await?; - let decimals = fetch_decimals(api.storage().at_latest().await?, &usd_asset).await?; - let properties = - ChainProperties::fetch_only_constants(min_balance, decimals, &constants, usd_asset)?; - - log::info!( - "Chain properties:\n\ - Address format: \"{}\" ({}).\n\ - Decimal places number: {}.\n\ - Existential deposit: {}.\n\ - USD asset: {} ({}).\n\ - Block hash count: {}.", - properties.address_format, - properties.address_format.prefix(), - decimals, - properties.existential_deposit, - match properties.usd_asset { - Usd::C => "USDC", - Usd::T => "USDT", - }, - properties.usd_asset.id(), - properties.block_hash_count - ); - - let arc_properties = Arc::new(RwLock::const_new(properties)); - - Ok(( - ApiConfig { - api: api.clone(), - methods: methods.clone(), - backend: backend.clone(), - }, - EndpointProperties { - url: CheckedUrl(url), - chain: arc_properties.clone(), - }, - Updater { - methods, - backend, - api, - constants, - shutdown_notification, - properties: arc_properties, - }, - )) -} - -pub struct Updater { - methods: Arc>, - backend: Arc>, - api: Arc, - constants: ConstantsClient, - shutdown_notification: CancellationToken, - properties: Arc>, -} - -impl Updater { - pub async fn ignite(self) -> Result<&'static str> { - loop { - let mut updates = self - .backend - .stream_runtime_version() - .await - .context("failed to get the runtime updates stream")?; - - if let Some(current_runtime_version_result) = updates.next().await { - let current_runtime_version = current_runtime_version_result - .context("failed to decode the current runtime version")?; - - // The updates stream is always returns the current runtime version in the first - // item. We don't skip it though because during a connection loss the runtime can be - // updated, hence this condition will catch this. - if self.api.runtime_version() != current_runtime_version { - self.process_update() - .await - .context("failed to process the first API client update")?; - } - - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Ok("The API client updater is shut down."); - } - runtime_version = updates.next() => { - if runtime_version.is_some() { - self.process_update() - .await - .context( - "failed to process an update for the API client" - )?; - } else { - break; - } - } - } - } - } - - log::warn!( - "Lost the connection while listening the endpoint for API client runtime updates. Retrying..." - ); - } - } - - async fn process_update(&self) -> Result<()> { - // We don't use the runtime version from the updates stream because it doesn't provide the - // best block hash, so we fetch it ourselves (in `fetch_api_runtime`) and use it to make sure - // that metadata & the runtime version are from the same block. - let (metadata, runtime_version) = fetch_api_runtime(&self.methods, &*self.backend) - .await - .context("failed to fetch a new runtime for the API client")?; - - self.api.set_metadata(metadata); - self.api.set_runtime_version(runtime_version); - - let mut current_properties = self.properties.write().await; - let new_properties = ChainProperties::fetch_only_constants( - current_properties.existential_deposit, - current_properties.decimals, - &self.constants, - current_properties.usd_asset, - )?; - - let mut changed = String::new(); - let mut add_change = |message: Arguments<'_>| { - changed.write_fmt(message).unwrap(); - }; - - if new_properties.address_format != current_properties.address_format { - add_change(format_args!( - "\nOld {value}: \"{}\" ({}). New {value}: \"{}\" ({}).", - current_properties.address_format, - current_properties.address_format.prefix(), - new_properties.address_format, - new_properties.address_format.prefix(), - value = "address format", - )); - } - - if new_properties.existential_deposit != current_properties.existential_deposit { - add_change(format_args!( - "\nOld {value}: {}. New {value}: {}.", - current_properties.existential_deposit, - new_properties.existential_deposit, - value = "existential deposit" - )); - } - - if new_properties.decimals != current_properties.decimals { - add_change(format_args!( - "\nOld {value}: {}. New {value}: {}.", - current_properties.decimals, - new_properties.decimals, - value = "decimal places number" - )); - } - - if new_properties.block_hash_count != current_properties.block_hash_count { - add_change(format_args!( - "\nOld {value}: {}. New {value}: {}.", - current_properties.block_hash_count, - new_properties.block_hash_count, - value = "block hash count" - )); - } - - if !changed.is_empty() { - *current_properties = new_properties; - - log::warn!("The chain properties has been changed:{changed}"); - } - - log::info!("A runtime update has been found and applied for the API client."); - - Ok(()) - } -} - -#[derive(Debug)] -struct Shutdown; - -impl Error for Shutdown {} - -// Not used, but required for the `anyhow::Context` trait. -impl Display for Shutdown { - fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { - unimplemented!() - } -} - -struct Api { - tx: TxClient, - blocks: BlocksClient, -} - -struct Scanner { - client: OnlineClient, - blocks: BlocksClient, - storage: StorageClient, -} - -struct ProcessorFinalized { - database: Arc, - client: OnlineClient, - backend: Arc>, - methods: Arc>, - shutdown_notification: CancellationToken, -} - -impl ProcessorFinalized { - async fn finalized_head_number_and_hash(&self) -> Result<(BlockNumber, Hash)> { - let head_hash = self - .methods - .chain_get_finalized_head() - .await - .context("failed to get the finalized head hash")?; - let head = self - .methods - .chain_get_block(Some(head_hash)) - .await - .context("failed to get the finalized head")? - .context("received nothing after requesting the finalized head")?; - - Ok((head.block.header.number, head_hash)) - } - - pub async fn ignite(self) -> Result<&'static str> { - self.execute().await.or_else(|error| { - error - .downcast() - .map(|Shutdown| "The RPC module is shut down.") - }) - } - - async fn execute(mut self) -> Result<&'static str> { - let write_tx = self.database.write()?; - let mut write_invoices = write_tx.invoices()?; - let (mut finalized_number, finalized_hash) = self.finalized_head_number_and_hash().await?; - - self.set_client_metadata(finalized_hash).await?; - - // TODO: - // Design a new DB format to store unpaid accounts in a separate table. - - for invoice_result in self.database.read()?.invoices()?.try_iter()? { - let invoice = invoice_result?; - - match invoice.1.value().status { - InvoiceStatus::Unpaid(price) => { - if self - .balance(finalized_hash, &Account::from(*invoice.0.value())) - .await? - >= price - { - let mut changed_invoice = invoice.1.value(); - - changed_invoice.status = InvoiceStatus::Paid(price); - - log::debug!("background scan {changed_invoice:?}"); - - write_invoices - .save(&Account::from(*invoice.0.value()), &changed_invoice)?; - } - } - InvoiceStatus::Paid(_) => continue, - } - } - - drop(write_invoices); - - write_tx.commit()?; - - let mut subscription = self.finalized_heads().await?; - - loop { - self.process_finalized_heads(subscription, &mut finalized_number) - .await?; - - log::warn!("Lost the connection while processing finalized heads. Retrying..."); - - subscription = self - .finalized_heads() - .await - .context("failed to update the subscription while processing finalized heads")?; - } - } - - async fn process_skipped( - &self, - next_unscanned: &mut BlockNumber, - head: BlockNumber, - ) -> Result<()> { - for skipped_number in *next_unscanned..head { - if self.shutdown_notification.is_cancelled() { - return Err(Shutdown.into()); - } - - let skipped_hash = self - .methods - .chain_get_block_hash(Some(skipped_number.into())) - .await - .context("failed to get the hash of a skipped block")? - .context("received nothing after requesting the hash of a skipped block")?; - - self.process_block(skipped_number, skipped_hash).await?; - } - - *next_unscanned = head; - - Ok(()) - } - - async fn process_finalized_heads( - &mut self, - mut subscription: RpcSubscription<::Header>, - next_unscanned: &mut BlockNumber, - ) -> Result<()> { - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Err(Shutdown.into()); - } - head_result_option = subscription.next() => { - if let Some(head_result) = head_result_option { - let head = head_result.context( - "received an error from the RPC client while processing finalized heads" - )?; - - self - .process_skipped(next_unscanned, head.number) - .await - .context("failed to process a skipped gap in the listening mode")?; - self.process_block(head.number, head.hash()).await?; - - *next_unscanned = head.number - .checked_add(1) - .context(MAX_BLOCK_NUMBER_ERROR)?; - } else { - break; - } - } - } - } - - Ok(()) - } - - async fn finalized_heads(&self) -> Result::Header>> { - self.methods - .chain_subscribe_finalized_heads() - .await - .context("failed to subscribe to finalized heads") - } - - async fn process_block(&self, number: BlockNumber, hash: Hash) -> Result<()> { - log::debug!("background block {number}"); - - let block = self - .client - .blocks() - .at(hash) - .await - .context("failed to obtain a block for processing")?; - let events = block - .events() - .await - .context("failed to obtain block events")?; - - let read_tx = self.database.read()?; - let read_invoices = read_tx.invoices()?; - - let mut update = false; - let mut invoices_changes = HashMap::new(); - - for event_result in events.iter() { - const UPDATE: &str = "CodeUpdated"; - const TRANSFERRED: &str = "Transferred"; - const ASSET_MIN_BALANCE_CHANGED: &str = "AssetMinBalanceChanged"; - const METADATA_SET: &str = "MetadataSet"; - - let event = event_result.context("failed to decode an event")?; - let metadata = event.event_metadata(); - - match (metadata.pallet.name(), &*metadata.variant.name) { - (SYSTEM, UPDATE) => update = true, - (ASSETS, TRANSFERRED) => Transferred::deserialize( - event - .field_values() - .context("failed to decode event's fields")?, - ) - .context("failed to deserialize a transfer event")? - .process( - &mut invoices_changes, - &read_invoices, - self.database.properties().await.usd_asset, - )?, - (ASSETS, ASSET_MIN_BALANCE_CHANGED) => { - let mut props = self.database.properties_write().await; - let new_min_balance = fetch_min_balance( - self.client.storage().at_latest().await?, - &props.usd_asset, - ) - .await?; - - props.existential_deposit = new_min_balance; - } - (ASSETS, METADATA_SET) => { - let props = self.database.properties_write().await; - let new_decimals = - fetch_decimals(self.client.storage().at_latest().await?, &props.usd_asset) - .await?; - - if props.decimals != new_decimals { - anyhow::bail!("decimals have been changed: {new_decimals}"); - } - } - _ => {} - } - } - - let write_tx = self.database.write()?; - let mut write_invoices = write_tx.invoices()?; - - for (invoice, mut changes) in invoices_changes { - log::debug!("final loop acc : {invoice}; changes: {changes:?}"); - if let InvoiceStatus::Unpaid(price) = changes.invoice.status { - let balance = self.balance(hash, &invoice).await?; - - log::debug!("unpaid acc balance: {balance}; price: {price}"); - - if balance >= price { - changes.invoice.status = InvoiceStatus::Paid(price); - - write_invoices.save(&invoice, &changes.invoice)?; - } - } - } - - drop(write_invoices); - - write_tx.commit()?; - - if update { - self.set_client_metadata(hash) - .await - .context("failed to update metadata in the finalized client")?; - - log::info!("A metadata update has been found and applied for the finalized client."); - } - - Ok(()) - } - - async fn set_client_metadata(&self, at: Hash) -> Result<()> { - let metadata = fetch_metadata(&*self.backend, at) - .await - .context("failed to fetch metadata for the scanner client")?; - - self.client.set_metadata(metadata); - - Ok(()) - } - - async fn balance(&self, hash: Hash, account: &Account) -> Result { - const ACCOUNT: &str = "Account"; - const BALANCE: &str = "balance"; - - if let Some(account_info) = self - .client - .storage() - .at(hash) - .fetch(&dynamic::storage( - ASSETS, - ACCOUNT, - vec![ - Value::from(self.database.properties().await.usd_asset.id()), - Value::from_bytes(AsRef::<[u8; 32]>::as_ref(account)), - ], - )) - .await - .context("failed to fetch account info from the chain")? - { - let decoded_account_info = account_info - .to_value() - .context("failed to decode account info")?; - let encoded_balance = decoded_account_info - .at(BALANCE) - .with_context(|| format!("{BALANCE} field wasn't found in account info"))?; - - encoded_balance.as_u128().with_context(|| { - format!("expected `u128` as the type of a balance, got {encoded_balance}") - }) - } else { - Ok(0) - } - } -} - -pub struct Processor { - api: Api, - scanner: Scanner, - methods: Arc>, - database: Arc, - backend: Arc>, - shutdown_notification: CancellationToken, -} - -impl Processor { - pub fn new( - ApiConfig { - api, - methods, - backend, - }: ApiConfig, - database: Arc, - shutdown_notification: CancellationToken, - ) -> Result { - let scanner = OnlineClient::from_backend_with( - api.genesis_hash(), - api.runtime_version(), - api.metadata(), - backend.clone(), - ) - .context("failed to initialize the scanner client")?; - - Ok(Processor { - api: Api { - tx: api.tx(), - blocks: api.blocks(), - }, - scanner: Scanner { - blocks: scanner.blocks(), - storage: scanner.storage(), - client: scanner, - }, - methods, - database, - shutdown_notification, - backend, - }) - } - - pub async fn ignite( - self, - latest_saved_block: Option, - task_tracker: TaskTracker, - error_tx: UnboundedSender, - ) -> Result<&'static str> { - self.execute(latest_saved_block, task_tracker, error_tx) - .await - .or_else(|error| { - error - .downcast() - .map(|Shutdown| "The RPC module is shut down.") - }) - } - - async fn execute( - mut self, - latest_saved_block: Option, - task_tracker: TaskTracker, - error_tx: UnboundedSender, - ) -> Result<&'static str> { - task_tracker.spawn(shutdown( - ProcessorFinalized { - database: self.database.clone(), - client: self.scanner.client.clone(), - backend: self.backend.clone(), - methods: self.methods.clone(), - shutdown_notification: self.shutdown_notification.clone(), - } - .ignite(), - error_tx, - )); - - let (mut head_number, head_hash) = self - .finalized_head_number_and_hash() - .await - .context("failed to get the chain head")?; - - let mut next_unscanned_number; - let mut subscription; - - if let Some(latest_saved) = latest_saved_block { - let latest_saved_hash = self - .methods - .chain_get_block_hash(Some(latest_saved.into())) - .await - .context("failed to get the hash of the last saved block")? - .context("received nothing after requesting the hash of the last saved block")?; - - self.set_scanner_metadata(latest_saved_hash).await?; - - next_unscanned_number = latest_saved - .checked_add(1) - .context(MAX_BLOCK_NUMBER_ERROR)?; - - let mut unscanned_amount = head_number.saturating_sub(next_unscanned_number); - - if unscanned_amount >= SCANNER_TO_LISTENER_SWITCH_POINT { - log::info!( - "Detected {unscanned_amount} unscanned blocks! Catching up may take a while." - ); - - while unscanned_amount >= SCANNER_TO_LISTENER_SWITCH_POINT { - self.process_skipped(&mut next_unscanned_number, head_number) - .await - .context("failed to process a skipped gap in the scanning mode")?; - - (head_number, _) = self - .finalized_head_number_and_hash() - .await - .context("failed to get a new chain head")?; - unscanned_amount = head_number.saturating_sub(next_unscanned_number); - } - - log::info!( - "Scanning of skipped blocks has been completed! Switching to the listening mode..." - ); - } - - subscription = self.finalized_heads().await?; - } else { - self.set_scanner_metadata(head_hash).await?; - - next_unscanned_number = head_number.checked_add(1).context(MAX_BLOCK_NUMBER_ERROR)?; - subscription = self.finalized_heads().await?; - } - - // Skip all already scanned blocks in cases like the first startup (we always skip the first - // block to fetch right metadata), an instant daemon restart, or a connection to a lagging - // endpoint. - 'skipping: loop { - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Err(Shutdown.into()); - } - header_result_option = subscription.next() => { - if let Some(header_result) = header_result_option { - let header = header_result.context( - "received an error from the RPC client while skipping saved finalized heads" - )?; - - if header.number >= next_unscanned_number { - break 'skipping; - } - } else { - break; - } - } - } - } - - log::warn!("Lost the connection while skipping already scanned blocks. Retrying..."); - - subscription = self - .finalized_heads() - .await - .context("failed to update the subscription while skipping scanned blocks")?; - } - - loop { - self.process_finalized_heads(subscription, &mut next_unscanned_number) - .await?; - - log::warn!("Lost the connection while processing finalized heads. Retrying..."); - - subscription = self - .finalized_heads() - .await - .context("failed to update the subscription while processing finalized heads")?; - } - } - - async fn finalized_head_number_and_hash(&self) -> Result<(BlockNumber, Hash)> { - let head_hash = self - .methods - .chain_get_finalized_head() - .await - .context("failed to get the finalized head hash")?; - let head = self - .methods - .chain_get_block(Some(head_hash)) - .await - .context("failed to get the finalized head")? - .context("received nothing after requesting the finalized head")?; - - Ok((head.block.header.number, head_hash)) - } - - async fn set_scanner_metadata(&self, at: Hash) -> Result<()> { - let metadata = fetch_metadata(&*self.backend, at) - .await - .context("failed to fetch metadata for the scanner client")?; - - self.scanner.client.set_metadata(metadata); - - Ok(()) - } - - async fn finalized_heads(&self) -> Result::Header>> { - self.methods - .chain_subscribe_finalized_heads() - .await - .context("failed to subscribe to finalized heads") - } - - async fn process_skipped( - &self, - next_unscanned: &mut BlockNumber, - head: BlockNumber, - ) -> Result<()> { - for skipped_number in *next_unscanned..head { - if self.shutdown_notification.is_cancelled() { - return Err(Shutdown.into()); - } - - let skipped_hash = self - .methods - .chain_get_block_hash(Some(skipped_number.into())) - .await - .context("failed to get the hash of a skipped block")? - .context("received nothing after requesting the hash of a skipped block")?; - - self.process_block(skipped_number, skipped_hash).await?; - } - - *next_unscanned = head; - - Ok(()) - } - - async fn process_finalized_heads( - &mut self, - mut subscription: RpcSubscription<::Header>, - next_unscanned: &mut BlockNumber, - ) -> Result<()> { - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Err(Shutdown.into()); - } - head_result_option = subscription.next() => { - if let Some(head_result) = head_result_option { - let head = head_result.context( - "received an error from the RPC client while processing finalized heads" - )?; - - self - .process_skipped(next_unscanned, head.number) - .await - .context("failed to process a skipped gap in the listening mode")?; - self.process_block(head.number, head.hash()).await?; - - *next_unscanned = head.number - .checked_add(1) - .context(MAX_BLOCK_NUMBER_ERROR)?; - } else { - break; - } - } - } - } - - Ok(()) - } - - async fn process_block(&self, number: BlockNumber, hash: Hash) -> Result<()> { - log::info!("Processing the block: {number}."); - - let block = self - .scanner - .blocks - .at(hash) - .await - .context("failed to obtain a block for processing")?; - let events = block - .events() - .await - .context("failed to obtain block events")?; - - let read_tx = self.database.read()?; - let read_invoices = read_tx.invoices()?; - - let mut update = false; - let mut invoices_changes = HashMap::new(); - - for event_result in events.iter() { - const UPDATE: &str = "CodeUpdated"; - const TRANSFERRED: &str = "Transferred"; - let event = event_result.context("failed to decode an event")?; - let metadata = event.event_metadata(); - - match (metadata.pallet.name(), &*metadata.variant.name) { - (SYSTEM, UPDATE) => update = true, - (ASSETS, TRANSFERRED) => Transferred::deserialize( - event - .field_values() - .context("failed to decode event's fields")?, - ) - .context("failed to deserialize a transfer event")? - .process( - &mut invoices_changes, - &read_invoices, - self.database.properties().await.usd_asset, - )?, - _ => {} - } - } - - for (invoice, changes) in invoices_changes { - let price = match changes.invoice.status { - InvoiceStatus::Unpaid(price) | InvoiceStatus::Paid(price) => price, - }; - - self.process_unpaid(&block, changes, hash, invoice, price) - .await - .context("failed to process an unpaid invoice")?; - } - - if update { - self.set_scanner_metadata(hash) - .await - .context("failed to update metadata in the scanner client")?; - - log::info!("A metadata update has been found and applied for the scanner client."); - } - - let write_tx = self.database.write()?; - - write_tx.root()?.save_last_block(number)?; - write_tx.commit()?; - - Ok(()) - } - - async fn balance(&self, hash: Hash, account: &Account) -> Result { - const ACCOUNT: &str = "Account"; - const BALANCE: &str = "balance"; - - if let Some(account_info) = self - .scanner - .storage - .at(hash) - .fetch(&dynamic::storage( - ASSETS, - ACCOUNT, - vec![ - Value::from(self.database.properties().await.usd_asset.id()), - Value::from_bytes(AsRef::<[u8; 32]>::as_ref(account)), - ], - )) - .await - .context("failed to fetch account info from the chain")? - { - let decoded_account_info = account_info - .to_value() - .context("failed to decode account info")?; - let encoded_balance = decoded_account_info - .at(BALANCE) - .with_context(|| format!("{BALANCE} field wasn't found in account info"))?; - - encoded_balance.as_u128().with_context(|| { - format!("expected `u128` as the type of a balance, got {encoded_balance}") - }) - } else { - Ok(0) - } - } - - async fn batch_transfer( - &self, - nonce: Nonce, - block_hash_count: BlockNumber, - signer: &PairSigner, - transfers: Vec, - ) -> Result> { - const FORCE_BATCH: &str = "force_batch"; - - let call = dynamic::tx(UTILITY, FORCE_BATCH, vec![Value::from(transfers)]); - let (number, hash) = self - .finalized_head_number_and_hash() - .await - .context("failed to get the chain head while constructing a transaction")?; - let extensions = DefaultExtrinsicParamsBuilder::new() - .mortal_unchecked(number.into(), hash, block_hash_count.into()) - .tip_of(0, self.database.properties().await.usd_asset.id()); - - self.api - .tx - .create_signed_with_nonce(&call, signer, nonce, extensions.build()) - .context("failed to create a transfer transaction") - } - - async fn current_nonce(&self, account: &Account) -> Result { - self.api - .blocks - .at(fetch_best_block(&self.methods).await?) - .await - .context("failed to obtain the best block for fetching an account nonce")? - .account_nonce(account) - .await - .context("failed to fetch an account nonce by the API client") - } - - async fn process_unpaid( - &self, - block: &Block, - mut changes: InvoiceChanges, - hash: Hash, - invoice: Account, - price: Balance, - ) -> Result<()> { - let balance = self.balance(hash, &invoice).await?; - - if let Some(_remaining) = balance.checked_sub(price) { - changes.invoice.status = InvoiceStatus::Paid(price); - - let block_nonce = block - .account_nonce(&invoice) - .await - .context(BLOCK_NONCE_ERROR)?; - let current_nonce = self.current_nonce(&invoice).await?; - - if current_nonce <= block_nonce { - let properties = self.database.properties().await; - let block_hash_count = properties.block_hash_count; - let signer = changes.invoice.signer(self.database.pair())?; - - let transfers = vec![construct_transfer( - &changes.invoice.recipient, - price - EXPECTED_USDX_FEE, - self.database.properties().await.usd_asset, - )]; - let tx = self - .batch_transfer(current_nonce, block_hash_count, &signer, transfers.clone()) - .await?; - - self.methods - .author_submit_extrinsic(tx.encoded()) - .await - .context("failed to submit an extrinsic") - .unwrap(); - } - } - - Ok(()) - } -} - -fn construct_transfer(to: &Account, amount: Balance, usd_asset: Usd) -> Value { - const TRANSFER_KEEP_ALIVE: &str = "transfer"; - - dbg!(amount); - - dynamic::tx( - ASSETS, - TRANSFER_KEEP_ALIVE, - vec![ - usd_asset.id().into(), - scale_value::value!(Id(Value::from_bytes(to))), - amount.into(), - ], - ) - .into_value() -} - -#[derive(Debug)] -struct InvoiceChanges { - invoice: Invoice, - incoming: HashMap, -} - -#[derive(Deserialize, Debug)] -struct Transferred { - asset_id: u32, - // The implementation of `Deserialize` for `AccountId32` works only with strings. - #[serde(deserialize_with = "account_deserializer")] - from: AccountId32, - #[serde(deserialize_with = "account_deserializer")] - to: AccountId32, - amount: Balance, -} - -fn account_deserializer<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId32::new(address.0)) -} - -impl Transferred { - fn process( - self, - invoices_changes: &mut HashMap, - invoices: &ReadInvoices<'_>, - usd_asset: Usd, - ) -> Result<()> { - log::debug!("Transferred event: {self:?}"); - - if self.from == self.to || self.amount == 0 || self.asset_id != usd_asset.id() { - return Ok(()); - } - - match invoices_changes.entry(self.to) { - Entry::Occupied(entry) => { - entry - .into_mut() - .incoming - .entry(self.from) - .and_modify(|amount| *amount = amount.saturating_add(self.amount)) - .or_insert(self.amount); - } - Entry::Vacant(entry) => { - if let (None, Some(encoded_invoice)) = - (invoices.get(&self.from)?, invoices.get(entry.key())?) - { - entry.insert(InvoiceChanges { - invoice: encoded_invoice.value(), - incoming: [(self.from, self.amount)].into(), - }); - } - } - } - - Ok(()) - } -} diff --git a/kalatori-ah/src/server.rs b/kalatori-ah/src/server.rs deleted file mode 100644 index bc0d134..0000000 --- a/kalatori-ah/src/server.rs +++ /dev/null @@ -1,208 +0,0 @@ -use crate::{ - database::{Database, Invoice, InvoiceStatus}, - Account, -}; -use anyhow::{Context, Result}; -use axum::{ - extract::{Path, State}, - routing::get, - Json, Router, -}; -use serde::Serialize; -use std::{future::Future, net::SocketAddr, sync::Arc}; -use subxt::ext::sp_core::{hexdisplay::HexDisplay, DeriveJunction, Pair}; -use tokio::net::TcpListener; -use tokio_util::sync::CancellationToken; - -pub(crate) const MODULE: &str = module_path!(); - -#[derive(Serialize)] -#[serde(untagged)] -pub enum Response { - Error(Error), - Success(Success), -} - -#[derive(Serialize)] -pub struct Error { - error: String, - wss: String, - mul: u64, - version: String, -} - -#[derive(Serialize)] -pub struct Success { - pay_account: String, - price: f64, - recipient: String, - order: String, - wss: String, - mul: u64, - result: String, - version: String, -} - -pub(crate) async fn new( - shutdown_notification: CancellationToken, - host: SocketAddr, - database: Arc, -) -> Result>> { - let app = Router::new() - .route( - "/recipient/:recipient/order/:order/price/:price", - get(handler_recip), - ) - .route("/order/:order/price/:price", get(handler)) - .with_state(database); - - let listener = TcpListener::bind(host) - .await - .context("failed to bind the TCP listener")?; - - log::info!("The server is listening on {host:?}."); - - Ok(async move { - axum::serve(listener, app) - .with_graceful_shutdown(shutdown_notification.cancelled_owned()) - .await - .context("failed to fire up the server")?; - - Ok("The server module is shut down.") - }) -} - -async fn handler_recip( - State(database): State>, - Path((recipient, order, price)): Path<(String, String, f64)>, -) -> Json { - let wss = database.rpc().to_string(); - let mul = database.properties().await.decimals; - - match abcd(database, Some(recipient), order, price).await { - Ok(re) => Response::Success(re), - Err(error) => Response::Error(Error { - wss, - mul, - version: env!("CARGO_PKG_VERSION").into(), - error: error.to_string(), - }), - } - .into() -} - -async fn handler( - State(database): State>, - Path((order, price)): Path<(String, f64)>, -) -> Json { - let wss = database.rpc().to_string(); - let mul = database.properties().await.decimals; - let recipient = database - .destination() - .as_ref() - .map(|d| format!("0x{}", HexDisplay::from(AsRef::<[u8; 32]>::as_ref(&d)))); - - match abcd(database, recipient, order, price).await { - Ok(re) => Response::Success(re), - Err(error) => Response::Error(Error { - wss, - mul, - version: env!("CARGO_PKG_VERSION").into(), - error: error.to_string(), - }), - } - .into() -} - -async fn abcd( - database: Arc, - recipient_option: Option, - order: String, - price_f64: f64, -) -> Result { - let recipient = recipient_option.context("destionation address isn't set")?; - let decoded_recip = hex::decode(&recipient[2..])?; - let recipient_account = Account::try_from(decoded_recip.as_ref()) - .map_err(|()| anyhow::anyhow!("Unknown address length"))?; - let properties = database.properties().await; - #[allow(clippy::cast_precision_loss)] - let mul = 10u128.pow(properties.decimals.try_into()?) as f64; - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - let price = (price_f64 * mul).round() as u128; - let order_encoded = DeriveJunction::hard(&order).unwrap_inner(); - let invoice_account: Account = database - .pair() - .derive( - [ - DeriveJunction::Hard(<[u8; 32]>::from(recipient_account.clone())), - DeriveJunction::Hard(order_encoded), - ] - .into_iter(), - None, - )? - .0 - .public() - .into(); - - if let Some(encoded_invoice) = database.read()?.invoices()?.get(&invoice_account)? { - let invoice = encoded_invoice.value(); - - if let InvoiceStatus::Unpaid(saved_price) = invoice.status { - if saved_price != price { - anyhow::bail!("The invoice was created with different price ({price})."); - } - } - - Ok(Success { - pay_account: format!("0x{}", HexDisplay::from(&invoice_account.as_ref())), - price: match invoice.status { - InvoiceStatus::Unpaid(invoice_price) | InvoiceStatus::Paid(invoice_price) => { - convert(properties.decimals, invoice_price)? - } - }, - wss: database.rpc().to_string(), - mul: properties.decimals, - recipient, - order, - result: match invoice.status { - InvoiceStatus::Unpaid(_) => "waiting", - InvoiceStatus::Paid(_) => "paid", - } - .into(), - version: env!("CARGO_PKG_VERSION").into(), - }) - } else { - let tx = database.write()?; - - tx.invoices()?.save( - &invoice_account, - &Invoice { - recipient: recipient_account, - order: order_encoded, - status: InvoiceStatus::Unpaid(price), - }, - )?; - - tx.commit()?; - - Ok(Success { - pay_account: format!("0x{}", HexDisplay::from(&invoice_account.as_ref())), - price: price_f64, - wss: database.rpc().to_string(), - mul: properties.decimals, - recipient, - order, - version: env!("CARGO_PKG_VERSION").into(), - result: "waiting".into(), - }) - } -} - -fn convert(dec: u64, num: u128) -> Result { - #[allow(clippy::cast_precision_loss)] - let numfl = num as f64; - #[allow(clippy::cast_precision_loss)] - let mul = 10u128.pow(dec.try_into()?) as f64; - - Ok(numfl / mul) -} diff --git a/rustfmt.toml b/rustfmt.toml index 5a08f28..94c6a85 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,3 @@ use_try_shorthand = true use_field_init_shorthand = true newline_style = "Unix" -force_explicit_abi = false diff --git a/shoot.sh b/shoot.sh index 976dcae..6e78389 100755 --- a/shoot.sh +++ b/shoot.sh @@ -1 +1 @@ -curl 127.0.0.1:16726/order/1337/price/5 +curl 127.0.0.1:16726/order/1111/price/30 diff --git a/src/callback.rs b/src/callback.rs new file mode 100644 index 0000000..96bfd30 --- /dev/null +++ b/src/callback.rs @@ -0,0 +1,14 @@ +use crate::definitions::api_v2::OrderStatus; +use tokio::task; + +pub const MODULE: &str = module_path!(); + +pub async fn callback(path: String, order_status: OrderStatus) { + let req = ureq::post(&path); + + task::spawn_blocking(move || { + let _d = req.send_json(order_status).unwrap(); + }) + .await + .unwrap(); +} diff --git a/src/chain/definitions.rs b/src/chain/definitions.rs new file mode 100644 index 0000000..cc7f8ab --- /dev/null +++ b/src/chain/definitions.rs @@ -0,0 +1,192 @@ +//! Common objects for chain interaction system + +use jsonrpsee::ws_client::WsClient; +use primitive_types::H256; +use substrate_crypto_light::common::{AccountId32, AsBase58}; +use tokio::sync::oneshot; + +use crate::{ + chain::{ + rpc::{asset_balance_at_account, system_balance_at_account}, + tracker::ChainWatcher, + }, + definitions::{ + api_v2::{OrderInfo, Timestamp}, + Balance, + }, + error::{ChainError, NotHex}, + utils::unhex, +}; + +/// Abstraction to distinguish block hash from many other H256 things +#[derive(Debug, Clone)] +pub struct BlockHash(pub primitive_types::H256); + +impl BlockHash { + /// Convert block hash to RPC-friendly format + pub fn to_string(&self) -> String { + format!("0x{}", hex::encode(&self.0)) + } + + /// Convert string returned by RPC to typesafe block + /// + /// TODO: integrate nicely with serde + pub fn from_str(s: &str) -> Result { + let block_hash_raw = unhex(&s, NotHex::BlockHash)?; + Ok(BlockHash(H256( + block_hash_raw + .try_into() + .map_err(|_| ChainError::BlockHashLength)?, + ))) + } +} + +#[derive(Debug)] +pub struct EventFilter<'a> { + pub pallet: &'a str, + pub optional_event_variant: Option<&'a str>, +} +/* +#[derive(Debug)] +struct ChainProperties { + specs: ShortSpecs, + metadata: RuntimeMetadataV15, + existential_deposit: Option, + assets_pallet: Option, + block_hash_count: BlockNumber, + account_lifetime: BlockNumber, + depth: Option, +} + +#[derive(Debug)] +struct AssetsPallet { + multi_location: Option, + assets: HashMap, +} + +#[derive(Debug)] +struct AssetProperties { + min_balance: Balance, + decimals: Decimals, +} + +#[derive(Debug)] +pub struct Currency { + chain: String, + asset: Option, +} + +#[derive(Debug)] +pub struct ConnectedChain { + rpc: String, + client: WsClient, + genesis: BlockHash, + properties: ChainProperties, +} + +*/ +pub enum ChainRequest { + WatchAccount(WatchAccount), + Reap(WatchAccount), + Shutdown(oneshot::Sender<()>), +} + +#[derive(Debug)] +pub struct WatchAccount { + pub id: String, + pub address: AccountId32, + pub currency: String, + pub amount: Balance, + pub recipient: AccountId32, + pub res: oneshot::Sender>, + pub death: Timestamp, +} + +impl WatchAccount { + pub fn new( + id: String, + order: OrderInfo, + recipient: AccountId32, + res: oneshot::Sender>, + ) -> Result { + Ok(WatchAccount { + id, + address: AccountId32::from_base58_string(&order.payment_account) + .map_err(ChainError::InvoiceAccount)? + .0, + currency: order.currency.currency, + amount: Balance::parse(order.amount, order.currency.decimals), + recipient, + res, + death: order.death, + }) + } +} + +pub enum ChainTrackerRequest { + WatchAccount(WatchAccount), + NewBlock(String), + Reap(WatchAccount), + Shutdown(oneshot::Sender<()>), +} + +#[derive(Clone, Debug)] +pub struct Invoice { + pub id: String, + pub address: AccountId32, + pub currency: String, + pub amount: Balance, + pub recipient: AccountId32, + pub death: Timestamp, +} + +impl Invoice { + pub fn from_request(watch_account: WatchAccount) -> Self { + drop(watch_account.res.send(Ok(()))); + Invoice { + id: watch_account.id, + address: watch_account.address, + currency: watch_account.currency, + amount: watch_account.amount, + recipient: watch_account.recipient, + death: watch_account.death, + } + } + + pub async fn balance( + &self, + client: &WsClient, + chain_watcher: &ChainWatcher, + block: &BlockHash, + ) -> Result { + let currency = chain_watcher + .assets + .get(&self.currency) + .ok_or(ChainError::InvalidCurrency(self.currency.clone()))?; + if let Some(asset_id) = currency.asset_id { + let balance = asset_balance_at_account( + client, + &block, + &chain_watcher.metadata, + &self.address, + asset_id, + ) + .await?; + Ok(balance) + } else { + let balance = + system_balance_at_account(client, &block, &chain_watcher.metadata, &self.address) + .await?; + Ok(balance) + } + } + + pub async fn check( + &self, + client: &WsClient, + chain_watcher: &ChainWatcher, + block: &BlockHash, + ) -> Result { + Ok(self.balance(client, chain_watcher, block).await? >= self.amount) + } +} diff --git a/src/chain/mod.rs b/src/chain/mod.rs new file mode 100644 index 0000000..41abf67 --- /dev/null +++ b/src/chain/mod.rs @@ -0,0 +1,188 @@ +//! Everything related to actual interaction with blockchain + +use std::collections::HashMap; + +use substrate_crypto_light::common::AccountId32; +use tokio::{ + sync::{mpsc, oneshot}, + time::{timeout, Duration}, +}; +use tokio_util::sync::CancellationToken; + +use crate::{ + definitions::{ + api_v2::{OrderInfo, Timestamp}, + Chain, + }, + error::{ChainError, Error}, + Signer, State, TaskTracker, +}; + +pub mod definitions; +pub mod payout; +pub mod rpc; +pub mod tracker; +pub mod utils; + +use definitions::{ChainRequest, ChainTrackerRequest, WatchAccount}; +use tracker::start_chain_watch; + +/// Logging filter +pub const MODULE: &str = module_path!(); + +/// Wait this long before forgetting about stuck chain watcher +const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(120000); + +/// RPC server handle +#[derive(Clone, Debug)] +pub struct ChainManager { + pub tx: tokio::sync::mpsc::Sender, +} + +impl ChainManager { + /// Run once to start all chain connections; this should be very robust, if manager fails + /// - all modules should be restarted probably. + pub fn ignite( + chain: Vec, + state: State, + signer: Signer, + task_tracker: TaskTracker, + cancellation_token: CancellationToken, + ) -> Result { + let (tx, mut rx) = mpsc::channel(1024); + + let mut watch_chain = HashMap::new(); + + let mut currency_map = HashMap::new(); + + // start network monitors + for c in chain { + if c.endpoints.is_empty() { + return Err(Error::EmptyEndpoints(c.name)); + } + let (chain_tx, chain_rx) = mpsc::channel(1024); + watch_chain.insert(c.name.clone(), chain_tx.clone()); + + // this MUST assert that there are no duplicates in requested assets + if let Some(ref a) = c.native_token { + if let Some(_) = currency_map.insert(a.name.clone(), c.name.clone()) { + return Err(Error::DuplicateCurrency(a.name.clone())); + } + } + for a in &c.asset { + if let Some(_) = currency_map.insert(a.name.clone(), c.name.clone()) { + return Err(Error::DuplicateCurrency(a.name.clone())); + } + } + + start_chain_watch( + c, + chain_tx.clone(), + chain_rx, + state.interface(), + signer.interface(), + task_tracker.clone(), + cancellation_token.clone(), + ); + } + + task_tracker + .clone() + .spawn("Blockchain connections manager", async move { + + // start requests engine + while let Some(request) = rx.recv().await { + match request { + ChainRequest::WatchAccount(request) => { + if let Some(chain) = currency_map.get(&request.currency) { + if let Some(receiver) = watch_chain.get(chain) { + let _unused = receiver + .send(ChainTrackerRequest::WatchAccount(request)) + .await; + } else { + let _unused = request + .res + .send(Err(ChainError::InvalidChain(chain.to_string()))); + } + } else { + let _unused = request + .res + .send(Err(ChainError::InvalidCurrency(request.currency))); + } + } + ChainRequest::Reap(request) => { + if let Some(chain) = currency_map.get(&request.currency) { + if let Some(receiver) = watch_chain.get(chain) { + let _unused = + receiver.send(ChainTrackerRequest::Reap(request)).await; + } else { + let _unused = request + .res + .send(Err(ChainError::InvalidChain(chain.to_string()))); + } + } else { + let _unused = request + .res + .send(Err(ChainError::InvalidCurrency(request.currency))); + } + } + ChainRequest::Shutdown(res) => { + for (name, chain) in watch_chain.drain() { + let (tx, rx) = oneshot::channel(); + if chain.send(ChainTrackerRequest::Shutdown(tx)).await.is_ok() { + if timeout(SHUTDOWN_TIMEOUT, rx).await.is_err() { + tracing::error!("Chain monitor for {name} took too much time to wind down, probably it was frozen. Discarding it."); + }; + } + } + let _ = res.send(()); + break; + } + } + } + + Ok("Chain manager is shutting down".into()) + }); + + Ok(Self { tx }) + } + + pub async fn add_invoice( + &self, + id: String, + order: OrderInfo, + recipient: AccountId32, + ) -> Result<(), ChainError> { + let (res, rx) = oneshot::channel(); + self.tx + .send(ChainRequest::WatchAccount(WatchAccount::new( + id, order, recipient, res, + )?)) + .await + .map_err(|_| ChainError::MessageDropped)?; + rx.await.map_err(|_| ChainError::MessageDropped)? + } + + pub async fn reap( + &self, + id: String, + order: OrderInfo, + recipient: AccountId32, + ) -> Result<(), ChainError> { + let (res, rx) = oneshot::channel(); + self.tx + .send(ChainRequest::Reap(WatchAccount::new( + id, order, recipient, res, + )?)) + .await + .map_err(|_| ChainError::MessageDropped)?; + rx.await.map_err(|_| ChainError::MessageDropped)? + } + + pub async fn shutdown(&self) -> () { + let (tx, rx) = oneshot::channel(); + let _unused = self.tx.send(ChainRequest::Shutdown(tx)).await; + let _ = rx.await; + () + } +} diff --git a/src/chain/payout.rs b/src/chain/payout.rs new file mode 100644 index 0000000..9a7627a --- /dev/null +++ b/src/chain/payout.rs @@ -0,0 +1,118 @@ +//! Separate engine for payout process. +//! +//! This is so unimportant for smooth SALES process, that it should be given the lowest possible +//! priority, optimized for lazy and very delayed process, and in some cases might be disabeled +//! altogether (TODO) + +use crate::{ + chain::{ + definitions::Invoice, + rpc::{block_hash, current_block_number, send_stuff}, + tracker::ChainWatcher, + utils::{ + construct_batch_transaction, construct_single_asset_transfer_call, + construct_single_balance_transfer_call, AssetTransferConstructor, + BalanceTransferConstructor, + }, + }, + definitions::api_v2::TokenKind, + error::ChainError, + signer::Signer, + state::State, +}; + +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::ws_client::WsClientBuilder; +use substrate_constructor::fill_prepare::{SpecialTypeToFill, TypeContentToFill}; + +/// Single function that should completely handle payout attmept. Just do not call anything else. +/// +/// TODO: make this an additional runner independent from chain monitors +pub async fn payout( + rpc: String, + order: Invoice, + state: State, + chain: ChainWatcher, + signer: Signer, +) -> Result<(), ChainError> { + // TODO: make this retry and rotate RPCs maybe + // + // after some retries record a failure + if let Ok(client) = WsClientBuilder::default().build(rpc).await { + let block = block_hash(&client, None).await?; // TODO should retry instead + let block_number = current_block_number(&client, &chain.metadata, &block).await?; + let balance = order.balance(&client, &chain, &block).await?; // TODO same + let loss_tolerance = 10000; // TODO: replace with multiple of existential + let manual_intervention_amount = 1000000000000; + let currency = chain + .assets + .get(&order.currency) + .ok_or(ChainError::InvalidCurrency(order.currency))?; + + // Payout operation logic + let transactions = match balance.0 - order.amount.0 { + a if (0..=loss_tolerance).contains(&a) => match currency.kind { + TokenKind::Balances => { + let balance_transfer_constructor = BalanceTransferConstructor { + amount: order.amount.0, + to_account: &order.recipient, + is_clearing: true, + }; + vec![construct_single_balance_transfer_call( + &chain.metadata, + &balance_transfer_constructor, + )?] + } + TokenKind::Asset => { + let asset_transfer_constructor = AssetTransferConstructor { + asset_id: currency.asset_id.ok_or(ChainError::AssetId)?, + amount: order.amount.0, + to_account: &order.recipient, + }; + vec![construct_single_asset_transfer_call( + &chain.metadata, + &asset_transfer_constructor, + )?] + } + }, + a if (loss_tolerance..=manual_intervention_amount).contains(&a) => { + tracing::warn!("Overpayments not handled yet"); + return Ok(()); //TODO + } + _ => { + tracing::error!("Balance is out of range: {balance:?}"); + return Ok(()); //TODO + } + }; + + let mut batch_transaction = construct_batch_transaction( + &chain.metadata, + chain.genesis_hash, + order.address, + &transactions, + block, + block_number, + 0, + )?; + + let sign_this = batch_transaction + .sign_this() + .ok_or(ChainError::TransactionNotSignable(format!( + "{batch_transaction:?}" + )))?; + + let signature = signer.sign(order.id, sign_this).await?; + + batch_transaction.signature.content = + TypeContentToFill::SpecialType(SpecialTypeToFill::SignatureSr25519(Some(signature))); + + let extrinsic = batch_transaction + .send_this_signed::<(), RuntimeMetadataV15>(&chain.metadata)? + .ok_or(ChainError::NothingToSend)?; + + send_stuff(&client, &format!("0x{}", hex::encode(extrinsic))).await?; + + // TODO obvious + } + Ok(()) +} diff --git a/src/chain/rpc.rs b/src/chain/rpc.rs new file mode 100644 index 0000000..e1a678a --- /dev/null +++ b/src/chain/rpc.rs @@ -0,0 +1,776 @@ +//! Blockchain operations that actually require calling the chain + +use crate::{ + chain::{ + definitions::{BlockHash, EventFilter}, + utils::{ + asset_balance_query, block_number_query, events_entry_metadata, hashed_key_element, + system_balance_query, system_properties_to_short_specs, + }, + }, + definitions::api_v2::CurrencyProperties, + definitions::{ + api_v2::{AssetId, TokenKind}, + Balance, + }, + error::{ChainError, NotHex}, + utils::unhex, +}; +use frame_metadata::{ + v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, + RuntimeMetadata, +}; +use jsonrpsee::core::client::{ClientT, Subscription, SubscriptionClientT}; +use jsonrpsee::rpc_params; +use jsonrpsee::ws_client::WsClient; +use parity_scale_codec::{DecodeAll, Encode}; +use scale_info::{form::PortableForm, PortableRegistry, TypeDef, TypeDefPrimitive}; +use serde::Deserialize; +use serde_json::{Number, Value}; +use sp_crypto_hashing::twox_128; +use std::{collections::HashMap, fmt::Debug}; +use substrate_crypto_light::common::AccountId32; +use substrate_parser::{ + cards::{Event, ParsedData, Sequence}, + decode_all_as_type, decode_as_storage_entry, + special_indicators::SpecialtyUnsignedInteger, + storage_data::{KeyData, KeyPart}, + AsMetadata, ResolveType, ShortSpecs, +}; + +const MAX_BLOCK_NUMBER_ERROR: &str = "block number type overflow is occurred"; +const BLOCK_NONCE_ERROR: &str = "failed to fetch an account nonce by the scanner client"; + +const CHARGE_ASSET_TX_PAYMENT: &str = "ChargeAssetTxPayment"; + +/// To prevent infinite loop while scanning for keys if the node decides to misbehave, limit number +/// of pages +/// +/// TODO: add more timeouts +const MAX_KEY_PAGES: usize = 256; + +// Pallets + +const SYSTEM: &str = "System"; +const BALANCES: &str = "Balances"; +const UTILITY: &str = "Utility"; +const ASSETS: &str = "Assets"; +const BABE: &str = "Babe"; +const TRANSFER: &str = "Transfer"; + +// Runtime APIs + +const AURA: &str = "AuraApi"; + +/// Fetch some runtime version identifier. +/// +/// This does not have to be typesafe or anything; this could be used only to check if returned +/// value changes - and reboot the whole connection then, regardless of nature of change. +pub async fn runtime_version_identifier( + client: &WsClient, + block: &BlockHash, +) -> Result { + let value = client + .request("state_getRuntimeVersion", rpc_params![block.to_string()]) + .await?; + Ok(value) +} + +pub async fn subscribe_blocks(client: &WsClient) -> Result, ChainError> { + Ok(client + .subscribe( + "chain_subscribeFinalizedHeads", + rpc_params![], + "chain_unsubscribeFinalizedHeads", + ) + .await?) +} + +pub async fn get_value_from_storage( + client: &WsClient, + whole_key: &str, + block: &BlockHash, +) -> Result { + let value: Value = client + .request( + "state_getStorage", + rpc_params![whole_key, block.to_string()], + ) + .await?; + Ok(value) +} + +pub async fn get_keys_from_storage( + client: &WsClient, + prefix: &str, + storage_name: &str, + block: &BlockHash, +) -> Result, ChainError> { + let mut keys_vec = Vec::new(); + let storage_key_prefix = format!( + "0x{}{}", + hex::encode(twox_128(prefix.as_bytes())), + hex::encode(twox_128(storage_name.as_bytes())) + ); + + let count = 10; // TODO make full scan just in case + let mut start_key: Option = None; // Start from the beginning + + let params_template = vec![ + serde_json::to_value(storage_key_prefix.clone()).unwrap(), + serde_json::to_value(count).unwrap(), + ]; + + for i in 0..MAX_KEY_PAGES { + let mut params = params_template.clone(); + if let Some(ref start_key) = start_key { + params.push(serde_json::to_value(start_key.clone()).unwrap()); + } + + params.push(serde_json::to_value(block.to_string()).unwrap()); + if let Ok(keys) = client.request("state_getKeysPaged", params).await { + if let Value::Array(ref keys_inside) = keys { + if keys_inside.len() == 0 { + return Ok(keys_vec); + } + if let Some(last) = keys_inside.last() { + if let Value::String(key_string) = last { + start_key = Some(key_string.clone()) + } else { + return Ok(keys_vec); + } + } else { + return Ok(keys_vec); + } + } else { + return Ok(keys_vec); + }; + keys_vec.push(keys); + } else { + return Ok(keys_vec); + } + } + + Ok(keys_vec) +} + +/// fetch genesis hash, must be a hexadecimal string transformable into +/// H256 format +pub async fn genesis_hash(client: &WsClient) -> Result { + let genesis_hash_request: Value = client + .request( + "chain_getBlockHash", + rpc_params![Value::Number(Number::from(0u8))], + ) + .await + .map_err(ChainError::Client)?; + match genesis_hash_request { + Value::String(x) => BlockHash::from_str(&x), + _ => return Err(ChainError::GenesisHashFormat), + } +} + +/// fetch block hash, to request later the metadata and specs for +/// the same block +pub async fn block_hash( + client: &WsClient, + number: Option, +) -> Result { + let rpc_params = if let Some(a) = number { + rpc_params![a] + } else { + rpc_params![] + }; + let block_hash_request: Value = client + .request("chain_getBlockHash", rpc_params) + .await + .map_err(ChainError::Client)?; + match block_hash_request { + Value::String(x) => BlockHash::from_str(&x), + _ => return Err(ChainError::BlockHashFormat), + } +} + +/// fetch metadata at known block +pub async fn metadata( + client: &WsClient, + block: &BlockHash, +) -> Result { + let metadata_request: Value = client + .request( + "state_call", + rpc_params![ + "Metadata_metadata_at_version", + "0x0f000000", + block.to_string() + ], + ) + .await + .map_err(ChainError::Client)?; + match metadata_request { + Value::String(x) => { + let metadata_request_raw = unhex(&x, NotHex::Metadata)?; + let maybe_metadata_raw = Option::>::decode_all(&mut &metadata_request_raw[..]) + .map_err(|_| ChainError::RawMetadataNotDecodeable)?; + if let Some(meta_v15_bytes) = maybe_metadata_raw { + if meta_v15_bytes.starts_with(b"meta") { + match RuntimeMetadata::decode_all(&mut &meta_v15_bytes[4..]) { + Ok(RuntimeMetadata::V15(runtime_metadata_v15)) => { + return Ok(runtime_metadata_v15) + } + Ok(_) => return Err(ChainError::NoMetadataV15), + Err(_) => return Err(ChainError::MetadataNotDecodeable), + } + } else { + return Err(ChainError::NoMetaPrefix); + } + } else { + return Err(ChainError::NoMetadataV15); + } + } + _ => return Err(ChainError::MetadataFormat), + }; +} + +// fetch specs at known block +pub async fn specs( + client: &WsClient, + metadata: &RuntimeMetadataV15, + block: &BlockHash, +) -> Result { + let specs_request: Value = client + .request("system_properties", rpc_params![block.to_string()]) + .await?; + match specs_request { + Value::Object(properties) => system_properties_to_short_specs(&properties, &metadata), + _ => return Err(ChainError::PropertiesFormat), + } +} + +pub async fn next_block_number(blocks: &mut Subscription) -> Result { + match blocks.next().await { + Some(Ok(a)) => Ok(a.number), + Some(Err(e)) => Err(e.into()), + None => Err(ChainError::BlockSubscriptionTerminated), + } +} + +pub async fn next_block( + client: &WsClient, + blocks: &mut Subscription, +) -> Result { + block_hash(&client, Some(next_block_number(blocks).await?)).await +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct BlockHead { + //digest: Value, + //extrinsics_root: String, + pub number: String, + //parent_hash: String, + //state_root: String, +} + +/// Get all sufficient assets from a chain +pub async fn assets_set_at_block( + client: &WsClient, + block: &BlockHash, + metadata_v15: &RuntimeMetadataV15, + rpc_url: &str, + specs: ShortSpecs, +) -> Result, ChainError> { + let mut assets_set = HashMap::new(); + let chain_name = + >::spec_name_version(metadata_v15)?.spec_name; + assets_set.insert( + specs.unit, + CurrencyProperties { + chain_name: chain_name.clone(), + kind: TokenKind::Balances, + decimals: specs.decimals, + rpc_url: rpc_url.to_owned(), + asset_id: None, + ss58: specs.base58prefix, + }, + ); + + let mut assets_asset_storage_metadata = None; + let mut assets_metadata_storage_metadata = None; + + for pallet in metadata_v15.pallets.iter() { + if let Some(storage) = &pallet.storage { + if storage.prefix == "Assets" { + for entry in storage.entries.iter() { + if entry.name == "Asset" { + assets_asset_storage_metadata = Some(entry); + } + if entry.name == "Metadata" { + assets_metadata_storage_metadata = Some(entry); + } + if assets_asset_storage_metadata.is_some() + && assets_metadata_storage_metadata.is_some() + { + break; + } + } + break; + } + } + } + + if let (Some(assets_asset_storage_metadata), Some(assets_metadata_storage_metadata)) = ( + assets_asset_storage_metadata, + assets_metadata_storage_metadata, + ) { + let available_keys_assets_asset_vec = + get_keys_from_storage(client, "Assets", "Asset", block).await?; + for available_keys_assets_asset in available_keys_assets_asset_vec { + if let Value::Array(ref keys_array) = available_keys_assets_asset { + for key in keys_array.iter() { + if let Value::String(string_key) = key { + let value_fetch = get_value_from_storage(client, string_key, block).await?; + if let Value::String(ref string_value) = value_fetch { + let key_data = unhex(string_key, NotHex::StorageKey)?; + let value_data = unhex(string_value, NotHex::StorageValue)?; + let storage_entry = + decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( + &key_data.as_ref(), + &value_data.as_ref(), + &mut (), + assets_asset_storage_metadata, + &metadata_v15.types, + )?; + let asset_id = { + if let KeyData::SingleHash { content } = storage_entry.key { + if let KeyPart::Parsed(extended_data) = content { + if let ParsedData::PrimitiveU32 { + value, + specialty: _, + } = extended_data.data + { + Ok(value) + } else { + Err(ChainError::AssetIdFormat) + } + } else { + Err(ChainError::AssetKeyEmpty) + } + } else { + Err(ChainError::AssetKeyNotSingleHash) + } + }?; + let mut verified_sufficient = false; + if let ParsedData::Composite(fields) = storage_entry.value.data { + for field_data in fields.iter() { + if let Some(field_name) = &field_data.field_name { + if field_name == "is_sufficient" { + if let ParsedData::PrimitiveBool(is_it) = + field_data.data.data + { + verified_sufficient = is_it; + } + break; + } + } + } + } + if verified_sufficient { + match &assets_metadata_storage_metadata.ty { + StorageEntryType::Plain(_) => { + return Err(ChainError::AssetMetadataPlain) + } + StorageEntryType::Map { + hashers, + key: key_ty, + value: value_ty, + } => { + if hashers.len() == 1 { + let hasher = &hashers[0]; + match metadata_v15 + .types + .resolve_ty(key_ty.id, &mut ())? + .type_def + { + TypeDef::Primitive(TypeDefPrimitive::U32) => { + let key_assets_metadata = format!( + "0x{}{}{}", + hex::encode(twox_128("Assets".as_bytes())), + hex::encode(twox_128( + "Metadata".as_bytes() + )), + hex::encode(hashed_key_element( + &asset_id.encode(), + hasher + )) + ); + let value_fetch = get_value_from_storage( + client, + &key_assets_metadata, + block, + ) + .await?; + if let Value::String(ref string_value) = + value_fetch + { + let value_data = unhex( + string_value, + NotHex::StorageValue, + )?; + let value = decode_all_as_type::< + &[u8], + (), + RuntimeMetadataV15, + >( + value_ty, + &value_data.as_ref(), + &mut (), + &metadata_v15.types, + )?; + + let mut name = None; + let mut symbol = None; + let mut decimals = None; + + if let ParsedData::Composite(fields) = + value.data + { + for field_data in fields.iter() { + if let Some(field_name) = + &field_data.field_name + { + match field_name.as_str() { + "name" => match &field_data.data.data { + ParsedData::Text{text, specialty: _} => { + name = Some(text.to_owned()); + }, + ParsedData::Sequence(sequence) => { + if let Sequence::U8(bytes) = &sequence.data { + if let Ok(name_from_bytes) = String::from_utf8(bytes.to_owned()) { + name = Some(name_from_bytes); + } + } + } + ParsedData::Composite(fields) => { + if fields.len() == 1 { + match &fields[0].data.data { + ParsedData::Text{text, specialty: _} => { + name = Some(text.to_owned()); + }, + ParsedData::Sequence(sequence) => { + if let Sequence::U8(bytes) = &sequence.data { + if let Ok(name_from_bytes) = String::from_utf8(bytes.to_owned()) { + name = Some(name_from_bytes); + } + } + }, + _ => {}, + } + } + }, + _ => {}, + }, + "symbol" => match &field_data.data.data { + ParsedData::Text{text, specialty: _} => { + symbol = Some(text.to_owned()); + }, + ParsedData::Sequence(sequence) => { + if let Sequence::U8(bytes) = &sequence.data { + if let Ok(symbol_from_bytes) = String::from_utf8(bytes.to_owned()) { + symbol = Some(symbol_from_bytes); + } + } + } + ParsedData::Composite(fields) => { + if fields.len() == 1 { + match &fields[0].data.data { + ParsedData::Text{text, specialty: _} => { + symbol = Some(text.to_owned()); + }, + ParsedData::Sequence(sequence) => { + if let Sequence::U8(bytes) = &sequence.data { + if let Ok(symbol_from_bytes) = String::from_utf8(bytes.to_owned()) { + symbol = Some(symbol_from_bytes); + } + } + }, + _ => {}, + } + } + }, + _ => {}, + }, + "decimals" => { + if let ParsedData::PrimitiveU8{value, specialty: _} = field_data.data.data { + decimals = Some(value); + } + }, + _ => {}, + } + } + if name.is_some() + && symbol.is_some() + && decimals.is_some() + { + break; + } + } + if let (Some(symbol), Some(decimals)) = + (symbol, decimals) + { + assets_set.insert( + symbol, + CurrencyProperties { + chain_name: chain_name + .clone(), + kind: TokenKind::Asset, + decimals, + rpc_url: rpc_url + .to_string(), + asset_id: Some(asset_id), + ss58: specs.base58prefix, + }, + ); + } + } else { + return Err( + ChainError::AssetMetadataUnexpected, + ); + } + } + } + + _ => return Err(ChainError::AssetMetadataType), + } + } else { + return Err(ChainError::AssetMetadataMapSize); + } + } + } + } + } + } + } + } + } + } + Ok(assets_set) +} + +pub async fn asset_balance_at_account( + client: &WsClient, + block: &BlockHash, + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, + asset_id: AssetId, +) -> Result { + let query = asset_balance_query(metadata_v15, account_id, asset_id)?; + + let value_fetch = get_value_from_storage(client, &query.key, block).await?; + if let Value::String(ref string_value) = value_fetch { + let value_data = unhex(string_value, NotHex::StorageValue)?; + let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + &query.value_ty, + &value_data.as_ref(), + &mut (), + &metadata_v15.types, + )?; + if let ParsedData::Composite(fields) = value.data { + for field in fields.iter() { + if let ParsedData::PrimitiveU128 { + value, + specialty: SpecialtyUnsignedInteger::Balance, + } = field.data.data + { + return Ok(Balance(value)); + } + } + Err(ChainError::AssetBalanceNotFound) + } else { + Err(ChainError::AssetBalanceFormat) + } + } else { + Err(ChainError::StorageValueFormat(value_fetch)) + } +} + +pub async fn system_balance_at_account( + client: &WsClient, + block: &BlockHash, + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, +) -> Result { + let query = system_balance_query(metadata_v15, account_id)?; + + let value_fetch = get_value_from_storage(client, &query.key, block).await?; + if let Value::String(ref string_value) = value_fetch { + let value_data = unhex(string_value, NotHex::StorageValue)?; + let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + &query.value_ty, + &value_data.as_ref(), + &mut (), + &metadata_v15.types, + )?; + if let ParsedData::Composite(fields) = value.data { + for field in fields.iter() { + if field.field_name == Some("data".to_string()) { + if let ParsedData::Composite(inner_fields) = &field.data.data { + for inner_field in inner_fields.iter() { + if inner_field.field_name == Some("free".to_string()) { + if let ParsedData::PrimitiveU128 { + value, + specialty: SpecialtyUnsignedInteger::Balance, + } = inner_field.data.data + { + return Ok(Balance(value)); + } + } + } + } + } + } + } + } + + Err(ChainError::BalanceNotFound) +} + +pub async fn transfer_events( + client: &WsClient, + block: &BlockHash, + metadata_v15: &RuntimeMetadataV15, +) -> Result, ChainError> { + let events_entry_metadata = events_entry_metadata(&metadata_v15)?; + + events_at_block( + &client, + block, + Some(EventFilter { + pallet: BALANCES, + optional_event_variant: Some(TRANSFER), + }), + events_entry_metadata, + &metadata_v15.types, + ) + .await +} + +async fn events_at_block( + client: &WsClient, + block: &BlockHash, + optional_filter: Option>, + events_entry_metadata: &StorageEntryMetadata, + types: &PortableRegistry, +) -> Result, ChainError> { + let keys_from_storage_vec = get_keys_from_storage(client, "System", "Events", block).await?; + let mut out = Vec::new(); + for keys_from_storage in keys_from_storage_vec { + match keys_from_storage { + Value::Array(ref keys_array) => { + for key in keys_array { + if let Value::String(key) = key { + let data_from_storage = get_value_from_storage(client, &key, block).await?; + let key_bytes = unhex(&key, NotHex::StorageValue)?; + let value_bytes = + if let Value::String(data_from_storage) = data_from_storage { + unhex(&data_from_storage, NotHex::StorageValue)? + } else { + return Err(ChainError::StorageValueFormat(data_from_storage)); + }; + let storage_data = + decode_as_storage_entry::<&[u8], (), RuntimeMetadataV15>( + &key_bytes.as_ref(), + &value_bytes.as_ref(), + &mut (), + events_entry_metadata, + types, + ) + .expect("RAM stored metadata access"); + if let ParsedData::SequenceRaw(sequence_raw) = storage_data.value.data { + for sequence_element in sequence_raw.data { + if let ParsedData::Composite(event_record) = sequence_element { + for event_record_element in event_record { + if event_record_element.field_name + == Some("event".to_string()) + { + if let ParsedData::Event(Event(ref event)) = + event_record_element.data.data + { + if let Some(ref filter) = optional_filter { + if let Some(event_variant) = + filter.optional_event_variant + { + if event.pallet_name == filter.pallet + && event.variant_name == event_variant + { + out.push(Event(event.to_owned())); + } + } else if event.pallet_name == filter.pallet { + out.push(Event(event.to_owned())); + } + } else { + out.push(Event(event.to_owned())); + } + } + } + } + } + } + } + } + } + } + _ => { + tracing::warn!("{keys_from_storage}"); + } + } + } + return Ok(out); +} + +pub async fn current_block_number( + client: &WsClient, + metadata: &RuntimeMetadataV15, + block: &BlockHash, +) -> Result { + let block_number_query = block_number_query(metadata)?; + let fetched_value = get_value_from_storage(client, &block_number_query.key, block).await?; + if let Value::String(hex_data) = fetched_value { + let value_data = unhex(&hex_data, NotHex::StorageValue)?; + let value = decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + &block_number_query.value_ty, + &value_data.as_ref(), + &mut (), + &metadata.types, + )?; + if let ParsedData::PrimitiveU32 { + value, + specialty: _, + } = value.data + { + Ok(value) + } else { + Err(ChainError::BlockNumberFormat) + } + } else { + Err(ChainError::StorageValueFormat(fetched_value)) + } +} + +pub async fn get_nonce( + client: &WsClient, + account_id: &str, +) -> Result<(), Box> { + let rpc_params = rpc_params![account_id]; + println!("{rpc_params:?}"); + let nonce: Value = client.request("account_nextIndex", rpc_params).await?; + println!("{nonce:?}"); + Ok(()) +} + +pub async fn send_stuff(client: &WsClient, data: &str) -> Result { + let rpc_params = rpc_params![data]; + Ok(client + .request("author_submitAndWatchExtrinsic", rpc_params) + .await?) +} diff --git a/src/chain/tracker.rs b/src/chain/tracker.rs new file mode 100644 index 0000000..c90c494 --- /dev/null +++ b/src/chain/tracker.rs @@ -0,0 +1,303 @@ +//! A tracker that follows individual chain + +use std::{collections::HashMap, time::SystemTime}; + +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use serde_json::Value; +use substrate_parser::{AsMetadata, ShortSpecs}; +use tokio::{ + sync::mpsc, + time::{timeout, Duration}, +}; +use tokio_util::sync::CancellationToken; + +use crate::{ + chain::{ + definitions::{BlockHash, ChainTrackerRequest, EventFilter, Invoice}, + payout::payout, + rpc::{ + assets_set_at_block, block_hash, genesis_hash, metadata, next_block, next_block_number, + runtime_version_identifier, specs, subscribe_blocks, transfer_events, + }, + utils::{events_entry_metadata, was_balance_received_at_account}, + }, + definitions::{api_v2::CurrencyProperties, Chain}, + error::ChainError, + signer::Signer, + state::State, + TaskTracker, +}; + +#[allow(clippy::too_many_lines)] +pub fn start_chain_watch( + chain: Chain, + chain_tx: mpsc::Sender, + mut chain_rx: mpsc::Receiver, + state: State, + signer: Signer, + task_tracker: TaskTracker, + cancellation_token: CancellationToken, +) { + task_tracker + .clone() + .spawn(format!("Chain {} watcher", chain.name.clone()), async move { + let watchdog = 120000; + let mut watched_accounts = HashMap::new(); + let mut shutdown = false; + // TODO: random pick instead + for endpoint in chain.endpoints.iter().cycle() { + // not restarting chain if shutdown is in progress + if shutdown || cancellation_token.is_cancelled() { + break; + } + if let Ok(client) = WsClientBuilder::default().build(endpoint).await { + // prepare chain + let watcher = match ChainWatcher::prepare_chain(&client, chain.clone(), &mut watched_accounts, endpoint, chain_tx.clone(), state.interface(), task_tracker.clone()) + .await + { + Ok(a) => a, + Err(e) => { + tracing::warn!( + "Failed to connect to chain {}, due to {} switching RPC server...", + chain.name, + e + ); + continue; + } + }; + + + // fulfill requests + while let Ok(Some(request)) = + timeout(Duration::from_millis(watchdog), chain_rx.recv()).await + { + match request { + ChainTrackerRequest::NewBlock(block) => { + // TODO: hide this under rpc module + let block = match block_hash(&client, Some(block)).await { + Ok(a) => a, + Err(e) => { + tracing::info!( + "Failed to receive block in chain {}, due to {}; Switching RPC server...", + chain.name, + e + ); + break; + }, + }; + + tracing::debug!("Block hash {} from {}", block.to_string(), chain.name); + + if watcher.version != runtime_version_identifier(&client, &block).await? { + tracing::info!("Different runtime version reported! Restarting connection..."); + break; + } + + match transfer_events( + &client, + &block, + &watcher.metadata, + ) + .await { + Ok(events) => { + let mut id_remove_list = Vec::new(); + let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis() as u64; + + for (id, invoice) in &watched_accounts { + if events.iter().any(|event| was_balance_received_at_account(&invoice.address, &event.0.fields)) { + match invoice.check(&client, &watcher, &block).await { + Ok(true) => { + state.order_paid(id.clone()).await; + id_remove_list.push(id.to_owned()); + } + Ok(false) => (), + Err(e) => { + tracing::warn!("account fetch error: {0:?}", e); + } + } + } + + if invoice.death.0 >= now { + match invoice.check(&client, &watcher, &block).await { + Ok(paid) => { + if paid { + state.order_paid(id.clone()).await; + } + + id_remove_list.push(id.to_owned()); + } + Err(e) => { + tracing::warn!("account fetch error: {0:?}", e); + } + } + } + } + for id in id_remove_list { + watched_accounts.remove(&id); + } + }, + Err(e) => { + tracing::warn!("Events fetch error {e} at {}", chain.name); + break; + }, + } + tracing::debug!("Block {} from {} processed successfully", block.to_string(), chain.name); + } + ChainTrackerRequest::WatchAccount(request) => { + watched_accounts.insert(request.id.clone(), Invoice::from_request(request)); + } + ChainTrackerRequest::Reap(request) => { + let id = request.id.clone(); + let rpc = endpoint.clone(); + let reap_state_handle = state.interface(); + let watcher_for_reaper = watcher.clone(); + let signer_for_reaper = signer.interface(); + task_tracker.clone().spawn(format!("Initiate payout for order {}", id.clone()), async move { + payout(rpc, Invoice::from_request(request), reap_state_handle, watcher_for_reaper, signer_for_reaper).await; + Ok(format!("Payout attempt for order {id} terminated").into()) + }); + } + ChainTrackerRequest::Shutdown(res) => { + shutdown = true; + let _ = res.send(()); + break; + } + } + } + } + } + Ok(format!("Chain {} monitor shut down", chain.name).into()) + }); +} + +#[derive(Debug, Clone)] +pub struct ChainWatcher { + pub genesis_hash: BlockHash, + pub metadata: RuntimeMetadataV15, + pub specs: ShortSpecs, + pub assets: HashMap, + version: Value, +} + +impl ChainWatcher { + pub async fn prepare_chain( + client: &WsClient, + chain: Chain, + watched_accounts: &mut HashMap, + rpc_url: &str, + chain_tx: mpsc::Sender, + state: State, + task_tracker: TaskTracker, + ) -> Result { + let genesis_hash = genesis_hash(&client).await?; + let mut blocks = subscribe_blocks(&client).await?; + let block = next_block(client, &mut blocks).await?; + let version = runtime_version_identifier(client, &block).await?; + let metadata = metadata(&client, &block).await?; + let name = >::spec_name_version(&metadata)?.spec_name; + if name != chain.name { + return Err(ChainError::WrongNetwork { + expected: chain.name, + actual: name, + rpc: rpc_url.to_string(), + }); + }; + let specs = specs(&client, &metadata, &block).await?; + let mut assets = + assets_set_at_block(&client, &block, &metadata, rpc_url, specs.clone()).await?; + + // TODO: make this verbosity less annoying + tracing::info!( + "chain {} requires native token {:?} and {:?}", + &chain.name, + &chain.native_token, + &chain.asset + ); + // Remove unwanted assets + assets.retain(|name, properties| { + tracing::info!( + "chain {} has token {} with properties {:?}", + &chain.name, + &name, + &properties + ); + if let Some(native_token) = &chain.native_token { + (native_token.name == *name) && (native_token.decimals == specs.decimals) + } else { + chain + .asset + .iter() + .any(|a| (a.name == *name) && (Some(a.id) == properties.asset_id)) + } + }); + + // Deduplication is done on chain manager level; + // Check that we have same number of assets as requested (we've checked that we have only + // wanted ones and performed deduplication before) + // + // This is probably an optimisation, but I don't have time to analyse perfirmance right + // now, it's just simpler to implement + // + // TODO: maybe check if at least one endpoint responds with proper assets and if not, shut + // down + if assets.len() != chain.asset.len() + if chain.native_token.is_some() { 1 } else { 0 } { + return Err(ChainError::AssetsInvalid(chain.name)); + } + // this MUST assert that assets match exactly before reporting it + + state.connect_chain(assets.clone()).await; + + let chain = ChainWatcher { + genesis_hash, + metadata, + specs, + assets, + version, + }; + + // check monitored accounts + let mut id_remove_list = Vec::new(); + for (id, account) in watched_accounts.iter() { + match account.check(client, &chain, &block).await { + Ok(true) => { + state.order_paid(id.clone()).await; + id_remove_list.push(id.to_owned()); + } + Ok(false) => (), + Err(e) => { + tracing::warn!("account fetch error: {0}", e); + } + } + } + for id in id_remove_list { + watched_accounts.remove(&id); + } + + let rpc = rpc_url.to_string(); + task_tracker.spawn(format!("watching blocks at {rpc}"), async move { + loop { + match next_block_number(&mut blocks).await { + Ok(block) => { + tracing::debug!("received block {block} from {rpc}"); + if let Err(e) = chain_tx.send(ChainTrackerRequest::NewBlock(block)).await { + tracing::warn!( + "Block watch internal communication error: {e} at {rpc}" + ); + break; + } + } + Err(e) => { + tracing::warn! {"Block watch error: {e} at {rpc}"}; + break; + } + } + } + // this should reset chain monitor on timeout; + // but if this breaks, it means that the latter is already down either way + Ok(format!("Block watch at {rpc} stopped").into()) + }); + + Ok(chain) + } +} diff --git a/src/chain/utils.rs b/src/chain/utils.rs new file mode 100644 index 0000000..60f548f --- /dev/null +++ b/src/chain/utils.rs @@ -0,0 +1,1189 @@ +//! Utils to process chain data without accessing the chain + +use crate::{chain::definitions::BlockHash, definitions::api_v2::AssetId, error::ChainError}; +use frame_metadata::{ + v14::StorageHasher, + v15::{RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType}, +}; +use parity_scale_codec::Encode; +use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive}; +use serde_json::{Map, Value}; +use sp_crypto_hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; +use substrate_constructor::{ + fill_prepare::{ + prepare_type, EraToFill, PrimitiveToFill, RegularPrimitiveToFill, SpecialTypeToFill, + SpecialtyUnsignedToFill, TransactionToFill, TypeContentToFill, TypeToFill, UnsignedToFill, + VariantSelector, DEFAULT_PERIOD, + }, + finalize::Finalize, + storage_query::{ + EntrySelector, EntrySelectorFunctional, FinalizedStorageQuery, StorageEntryTypeToFill, + StorageSelector, StorageSelectorFunctional, + }, +}; +use substrate_crypto_light::common::AccountId32; +use substrate_parser::{ + cards::{ExtendedData, FieldData, ParsedData}, + decode_all_as_type, + decoding_sci::Ty, + propagated::Propagated, + special_indicators::SpecialtyUnsignedInteger, + ResolveType, ShortSpecs, +}; + +pub struct AssetTransferConstructor<'a> { + pub asset_id: u32, + pub amount: u128, + pub to_account: &'a AccountId32, +} + +pub fn construct_single_asset_transfer_call( + metadata: &RuntimeMetadataV15, + asset_transfer_constructor: &AssetTransferConstructor, +) -> Result { + let mut call = prepare_type::<(), RuntimeMetadataV15>( + &Ty::Symbol(&metadata.extrinsic.call_ty), + &mut (), + &metadata.types, + Propagated::new(), + )?; + + if let TypeContentToFill::Variant(ref mut pallet_selector) = call.content { + let mut index_assets_in_pallets = None; + + for (index_pallet, variant_pallet) in pallet_selector.available_variants.iter().enumerate() + { + if variant_pallet.name == "Assets" { + index_assets_in_pallets = Some(index_pallet); + break; + } + } + + if let Some(index_assets_in_pallets) = index_assets_in_pallets { + *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &pallet_selector.available_variants, + &mut (), + &metadata.types, + index_assets_in_pallets, + )?; + + if pallet_selector.selected.fields_to_fill.len() == 1 { + if let TypeContentToFill::Variant(ref mut method_selector) = + pallet_selector.selected.fields_to_fill[0] + .type_to_fill + .content + { + let mut index_transfer_in_methods = None; + + for (index_method, variant_method) in + method_selector.available_variants.iter().enumerate() + { + if variant_method.name.as_str() == "transfer" { + index_transfer_in_methods = Some(index_method); + break; + } + } + + if let Some(index_transfer_in_methods) = index_transfer_in_methods { + *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &method_selector.available_variants, + &mut (), + &metadata.types, + index_transfer_in_methods, + )?; + + for field in method_selector.selected.fields_to_fill.iter_mut() { + if let Some(ref mut field_name) = field.field_name { + match field_name.as_str() { + "target" => { + if let TypeContentToFill::Variant(ref mut dest_selector) = + field.type_to_fill.content + { + let mut index_account_id_in_dest_selector = None; + + for (index, dest_variant) in + dest_selector.available_variants.iter().enumerate() + { + if dest_variant.name == "Id" { + index_account_id_in_dest_selector = Some(index); + break; + } + } + + if let Some(index_account_id_in_dest_selector) = + index_account_id_in_dest_selector + { + *dest_selector = VariantSelector::new_at::< + (), + RuntimeMetadataV15, + >( + &dest_selector.available_variants, + &mut (), + &metadata.types, + index_account_id_in_dest_selector, + )?; + + if dest_selector.selected.fields_to_fill.len() == 1 + { + if let TypeContentToFill::SpecialType( + SpecialTypeToFill::AccountId32( + ref mut account_to_fill, + ), + ) = dest_selector.selected.fields_to_fill[0] + .type_to_fill + .content + { + *account_to_fill = Some( + asset_transfer_constructor + .to_account + .to_owned(), + ) + } + } + } + } + } + "id" => match field.type_to_fill.content { + TypeContentToFill::Primitive( + PrimitiveToFill::CompactUnsigned( + SpecialtyUnsignedToFill { + content: UnsignedToFill::U32(ref mut value), + specialty: SpecialtyUnsignedInteger::None, + }, + ), + ) => { + *value = Some(asset_transfer_constructor.asset_id); + } + TypeContentToFill::Primitive( + PrimitiveToFill::Unsigned(SpecialtyUnsignedToFill { + content: UnsignedToFill::U32(ref mut value), + specialty: SpecialtyUnsignedInteger::None, + }), + ) => { + *value = Some(asset_transfer_constructor.asset_id); + } + _ => {} + }, + "amount" => match field.type_to_fill.content { + TypeContentToFill::Primitive( + PrimitiveToFill::CompactUnsigned( + SpecialtyUnsignedToFill { + content: UnsignedToFill::U128(ref mut value), + specialty: SpecialtyUnsignedInteger::Balance, + }, + ), + ) => { + *value = Some(asset_transfer_constructor.amount); + } + TypeContentToFill::Primitive( + PrimitiveToFill::Unsigned(SpecialtyUnsignedToFill { + content: UnsignedToFill::U128(ref mut value), + specialty: SpecialtyUnsignedInteger::Balance, + }), + ) => { + *value = Some(asset_transfer_constructor.amount); + } + _ => {} + }, + _ => {} + } + } + } + } + } + } + } + } + Ok(CallToFill(call)) +} + +pub struct BalanceTransferConstructor<'a> { + pub amount: u128, + pub to_account: &'a AccountId32, + pub is_clearing: bool, +} + +#[derive(Clone, Debug)] +pub struct CallToFill(pub TypeToFill); + +pub fn construct_batch_transaction( + metadata: &RuntimeMetadataV15, + genesis_hash: BlockHash, + author: AccountId32, + call_set: &[CallToFill], + block: BlockHash, + block_number: u32, + nonce: u32, +) -> Result { + let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash.0)?; + + // deal with author + match transaction_to_fill.author.content { + TypeContentToFill::Composite(ref mut fields_to_fill) => { + if fields_to_fill.len() == 1 { + if let TypeContentToFill::SpecialType(SpecialTypeToFill::AccountId32(ref mut a)) = + fields_to_fill[0].type_to_fill.content + { + *a = Some(author); + } + } + } + TypeContentToFill::SpecialType(SpecialTypeToFill::AccountId32(ref mut a)) => { + *a = Some(author); + } + TypeContentToFill::Variant(ref mut variant_selector) => { + let mut index_account_id = None; + + for (index, variant_id) in variant_selector.available_variants.iter().enumerate() { + if variant_id.name == "Id" { + index_account_id = Some(index); + break; + } + } + + if let Some(index_account_id) = index_account_id { + *variant_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &variant_selector.available_variants, + &mut (), + &metadata.types, + index_account_id, + )?; + + if variant_selector.selected.fields_to_fill.len() == 1 { + if let TypeContentToFill::SpecialType(SpecialTypeToFill::AccountId32( + ref mut a, + )) = variant_selector.selected.fields_to_fill[0] + .type_to_fill + .content + { + *a = Some(author); + } + } + } + } + _ => {} + } + + // deal with call + transaction_to_fill.call = construct_batch_call(metadata, call_set)?.0; + + // set era to mortal + for ext in transaction_to_fill.extensions.iter_mut() { + match ext.content { + TypeContentToFill::Composite(ref mut fields) => { + if fields.len() == 1 { + if let TypeContentToFill::SpecialType(SpecialTypeToFill::Era(ref mut era)) = + fields[0].type_to_fill.content + { + *era = EraToFill::Mortal { + period_entry: DEFAULT_PERIOD, + block_number_entry: None, + }; + break; + } + } + } + TypeContentToFill::SpecialType(SpecialTypeToFill::Era(ref mut era)) => { + *era = EraToFill::Mortal { + period_entry: DEFAULT_PERIOD, + block_number_entry: None, + }; + break; + } + _ => {} + } + } + + transaction_to_fill.populate_block_info(Some(block.0), Some(block_number.into())); + transaction_to_fill.populate_nonce(nonce); + + for ext in transaction_to_fill.extensions.iter_mut() { + if ext.finalize().is_none() { + println!("{ext:?}"); + } + } + + Ok(transaction_to_fill) +} + +pub fn construct_batch_call( + metadata: &RuntimeMetadataV15, + call_set: &[CallToFill], +) -> Result { + let mut call = prepare_type::<(), RuntimeMetadataV15>( + &Ty::Symbol(&metadata.extrinsic.call_ty), + &mut (), + &metadata.types, + Propagated::new(), + )?; + + if let TypeContentToFill::Variant(ref mut pallet_selector) = call.content { + let mut index_utility_in_pallets = None; + + for (index_pallet, variant_pallet) in pallet_selector.available_variants.iter().enumerate() + { + if variant_pallet.name == "Utility" { + index_utility_in_pallets = Some(index_pallet); + break; + } + } + + if let Some(index_utility_in_pallets) = index_utility_in_pallets { + *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &pallet_selector.available_variants, + &mut (), + &metadata.types, + index_utility_in_pallets, + )?; + + if pallet_selector.selected.fields_to_fill.len() == 1 { + if let TypeContentToFill::Variant(ref mut method_selector) = + pallet_selector.selected.fields_to_fill[0] + .type_to_fill + .content + { + let mut index_batch_all_in_methods = None; + + for (index_method, variant_method) in + method_selector.available_variants.iter().enumerate() + { + if variant_method.name == "batch_all" { + index_batch_all_in_methods = Some(index_method); + break; + } + } + + if let Some(index_batch_all_in_methods) = index_batch_all_in_methods { + *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &method_selector.available_variants, + &mut (), + &metadata.types, + index_batch_all_in_methods, + )?; + + if method_selector.selected.fields_to_fill.len() == 1 + && method_selector.selected.fields_to_fill[0].field_name + == Some("calls".to_string()) + { + if let TypeContentToFill::SequenceRegular(ref mut calls_sequence) = + method_selector.selected.fields_to_fill[0] + .type_to_fill + .content + { + calls_sequence.content = call_set + .iter() + .map(|call| call.0.content.to_owned()) + .collect(); + } + } + } + } + } + } + } + Ok(CallToFill(call)) +} + +pub fn construct_single_balance_transfer_call( + metadata: &RuntimeMetadataV15, + balance_transfer_constructor: &BalanceTransferConstructor, +) -> Result { + let mut call = prepare_type::<(), RuntimeMetadataV15>( + &Ty::Symbol(&metadata.extrinsic.call_ty), + &mut (), + &metadata.types, + Propagated::new(), + )?; + + if let TypeContentToFill::Variant(ref mut pallet_selector) = call.content { + let mut index_balances_in_pallets = None; + + for (index_pallet, variant_pallet) in pallet_selector.available_variants.iter().enumerate() + { + if variant_pallet.name == "Balances" { + index_balances_in_pallets = Some(index_pallet); + break; + } + } + + if let Some(index_balances_in_pallets) = index_balances_in_pallets { + *pallet_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &pallet_selector.available_variants, + &mut (), + &metadata.types, + index_balances_in_pallets, + )?; + + if pallet_selector.selected.fields_to_fill.len() == 1 { + if let TypeContentToFill::Variant(ref mut method_selector) = + pallet_selector.selected.fields_to_fill[0] + .type_to_fill + .content + { + let mut index_transfer_in_methods = None; + + for (index_method, variant_method) in + method_selector.available_variants.iter().enumerate() + { + match variant_method.name.as_str() { + "transfer_keep_alive" => { + if !balance_transfer_constructor.is_clearing { + index_transfer_in_methods = Some(index_method) + } + } + "transfer_all" => { + if balance_transfer_constructor.is_clearing { + index_transfer_in_methods = Some(index_method) + } + } + _ => {} + } + if index_transfer_in_methods.is_some() { + break; + } + } + + if let Some(index_transfer_in_methods) = index_transfer_in_methods { + *method_selector = VariantSelector::new_at::<(), RuntimeMetadataV15>( + &method_selector.available_variants, + &mut (), + &metadata.types, + index_transfer_in_methods, + )?; + + for field in method_selector.selected.fields_to_fill.iter_mut() { + if let Some(ref mut field_name) = field.field_name { + match field_name.as_str() { + "dest" => { + if let TypeContentToFill::Variant(ref mut dest_selector) = + field.type_to_fill.content + { + let mut index_account_id_in_dest_selector = None; + + for (index, dest_variant) in + dest_selector.available_variants.iter().enumerate() + { + if dest_variant.name == "Id" { + index_account_id_in_dest_selector = Some(index); + break; + } + } + + if let Some(index_account_id_in_dest_selector) = + index_account_id_in_dest_selector + { + *dest_selector = VariantSelector::new_at::< + (), + RuntimeMetadataV15, + >( + &dest_selector.available_variants, + &mut (), + &metadata.types, + index_account_id_in_dest_selector, + )?; + + if dest_selector.selected.fields_to_fill.len() == 1 + { + if let TypeContentToFill::SpecialType( + SpecialTypeToFill::AccountId32( + ref mut account_to_fill, + ), + ) = dest_selector.selected.fields_to_fill[0] + .type_to_fill + .content + { + *account_to_fill = Some( + balance_transfer_constructor + .to_account + .to_owned(), + ) + } + } + } + } + } + "keep_alive" => { + if let TypeContentToFill::Primitive( + PrimitiveToFill::Regular(RegularPrimitiveToFill::Bool( + ref mut keep_alive_bool, + )), + ) = field.type_to_fill.content + { + *keep_alive_bool = Some(false); + } + } + "value" => match field.type_to_fill.content { + TypeContentToFill::Primitive( + PrimitiveToFill::CompactUnsigned( + SpecialtyUnsignedToFill { + content: UnsignedToFill::U128(ref mut value), + specialty: SpecialtyUnsignedInteger::Balance, + }, + ), + ) => { + *value = Some(balance_transfer_constructor.amount); + } + TypeContentToFill::Primitive( + PrimitiveToFill::Unsigned(SpecialtyUnsignedToFill { + content: UnsignedToFill::U128(ref mut value), + specialty: SpecialtyUnsignedInteger::Balance, + }), + ) => { + *value = Some(balance_transfer_constructor.amount); + } + _ => {} + }, + _ => {} + } + } + } + } + } + } + } + } + Ok(CallToFill(call)) +} + +pub fn block_number_query( + metadata_v15: &RuntimeMetadataV15, +) -> Result { + let storage_selector = StorageSelector::init(&mut (), metadata_v15)?; + + if let StorageSelector::Functional(mut storage_selector_functional) = storage_selector { + let mut index_system_in_pallet_selector = None; + + for (index, pallet) in storage_selector_functional + .available_pallets + .iter() + .enumerate() + { + if pallet.prefix == "System" { + index_system_in_pallet_selector = Some(index); + break; + } + } + + if let Some(index_system_in_pallet_selector) = index_system_in_pallet_selector { + // System - Number (current block number) + storage_selector_functional = + StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &storage_selector_functional.available_pallets, + &mut (), + &metadata_v15.types, + index_system_in_pallet_selector, + )?; + + if let EntrySelector::Functional(ref mut entry_selector_functional) = + storage_selector_functional.query.entry_selector + { + let mut entry_index = None; + for (index, entry) in entry_selector_functional + .available_entries + .iter() + .enumerate() + { + if entry.name == "Number" { + entry_index = Some(index); + break; + } + } + if let Some(entry_index) = entry_index { + *entry_selector_functional = + EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &entry_selector_functional.available_entries, + &mut (), + &metadata_v15.types, + entry_index, + )?; + + Ok(storage_selector_functional + .query + .finalize() + .transpose() + .ok_or(ChainError::StorageQuery)??) + } else { + Err(ChainError::NoBlockNumberDefinition) + } + } else { + Err(ChainError::NoStorageInSystem) + } + } else { + Err(ChainError::NoSystem) + } + } else { + Err(ChainError::NoStorage) + } +} + +pub fn events_entry_metadata( + metadata: &RuntimeMetadataV15, +) -> Result<&StorageEntryMetadata, ChainError> { + let mut found_events_entry_metadata = None; + for pallet in &metadata.pallets { + if let Some(storage) = &pallet.storage { + if storage.prefix == "System" { + for entry in &storage.entries { + if entry.name == "Events" { + found_events_entry_metadata = Some(entry); + break; + } + } + } + } + } + match found_events_entry_metadata { + Some(a) => Ok(a), + None => Err(ChainError::EventsNonexistant), + } +} + +pub fn was_balance_received_at_account( + known_account: &AccountId32, + balance_transfer_event_fields: &[FieldData], +) -> bool { + let mut found_receiver = None; + for field in balance_transfer_event_fields.iter() { + if let Some(ref field_name) = field.field_name { + if field_name == "to" { + if let ParsedData::Id(ref account_id32) = field.data.data { + if found_receiver.is_none() { + found_receiver = Some(account_id32); + } else { + found_receiver = None; + break; + } + } + } + } + } + if let Some(receiver) = found_receiver { + receiver.0 == known_account.0 + } else { + false + } +} + +pub fn asset_balance_query( + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, + asset_id: AssetId, +) -> Result { + let storage_selector = StorageSelector::init(&mut (), metadata_v15)?; + + if let StorageSelector::Functional(mut storage_selector_functional) = storage_selector { + let mut index_assets_in_pallet_selector: Option = None; + + for (index, pallet) in storage_selector_functional + .available_pallets + .iter() + .enumerate() + { + match pallet.prefix.as_str() { + "Assets" => index_assets_in_pallet_selector = Some(index), + _ => {} + } + if index_assets_in_pallet_selector.is_some() { + break; + } + } + if let Some(index_assets_in_pallet_selector) = index_assets_in_pallet_selector { + storage_selector_functional = + StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &storage_selector_functional.available_pallets, + &mut (), + &metadata_v15.types, + index_assets_in_pallet_selector, + )?; + if let EntrySelector::Functional(ref mut entry_selector_functional) = + storage_selector_functional.query.entry_selector + { + let mut entry_index = None; + for (index, entry) in entry_selector_functional + .available_entries + .iter() + .enumerate() + { + if entry.name == "Account" { + entry_index = Some(index); + break; + } + } + if let Some(entry_index) = entry_index { + *entry_selector_functional = + EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &entry_selector_functional.available_entries, + &mut (), + &metadata_v15.types, + entry_index, + )?; + if let StorageEntryTypeToFill::Map { + hashers: _, + ref mut key_to_fill, + value: _, + } = entry_selector_functional.selected_entry.type_to_fill + { + if let TypeContentToFill::Tuple(ref mut set) = key_to_fill.content { + for ty in set.iter_mut() { + match ty.content { + TypeContentToFill::SpecialType( + SpecialTypeToFill::AccountId32(ref mut account_to_fill), + ) => *account_to_fill = Some(*account_id), + TypeContentToFill::Primitive( + PrimitiveToFill::CompactUnsigned( + ref mut specialty_unsigned_to_fill, + ), + ) => { + if let UnsignedToFill::U32(ref mut u) = + specialty_unsigned_to_fill.content + { + *u = Some(asset_id); + } + } + TypeContentToFill::Primitive(PrimitiveToFill::Unsigned( + ref mut specialty_unsigned_to_fill, + )) => { + if let UnsignedToFill::U32(ref mut u) = + specialty_unsigned_to_fill.content + { + *u = Some(asset_id); + } + } + _ => {} + } + } + } + } + } + } + } + + storage_selector_functional + .query + .finalize()? + .ok_or(ChainError::StorageQuery) + } else { + Err(ChainError::StorageQuery) + } +} + +pub fn system_balance_query( + metadata_v15: &RuntimeMetadataV15, + account_id: &AccountId32, +) -> Result { + let storage_selector = StorageSelector::init(&mut (), metadata_v15)?; + let mut index_system_in_pallet_selector = None; + + if let StorageSelector::Functional(mut storage_selector_functional) = storage_selector { + for (index, pallet) in storage_selector_functional + .available_pallets + .iter() + .enumerate() + { + match pallet.prefix.as_str() { + "System" => index_system_in_pallet_selector = Some(index), + _ => {} + } + if index_system_in_pallet_selector.is_some() { + break; + } + } + if let Some(index_system_in_pallet_selector) = index_system_in_pallet_selector { + storage_selector_functional = + StorageSelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &storage_selector_functional.available_pallets, + &mut (), + &metadata_v15.types, + index_system_in_pallet_selector, + )?; + + if let EntrySelector::Functional(ref mut entry_selector_functional) = + storage_selector_functional.query.entry_selector + { + let mut entry_index = None; + for (index, entry) in entry_selector_functional + .available_entries + .iter() + .enumerate() + { + if entry.name == "Account" { + entry_index = Some(index); + break; + } + } + if let Some(entry_index) = entry_index { + *entry_selector_functional = + EntrySelectorFunctional::new_at::<(), RuntimeMetadataV15>( + &entry_selector_functional.available_entries, + &mut (), + &metadata_v15.types, + entry_index, + )?; + if let StorageEntryTypeToFill::Map { + hashers: _, + ref mut key_to_fill, + value: _, + } = entry_selector_functional.selected_entry.type_to_fill + { + if let TypeContentToFill::SpecialType(SpecialTypeToFill::AccountId32( + ref mut account_to_fill, + )) = key_to_fill.content + { + *account_to_fill = Some(*account_id) + } + } + } + } + } + storage_selector_functional + .query + .finalize()? + .ok_or(ChainError::StorageQuery) + } else { + Err(ChainError::StorageQuery) + } +} + +pub fn hashed_key_element(data: &[u8], hasher: &StorageHasher) -> Vec { + match hasher { + StorageHasher::Blake2_128 => blake2_128(data).to_vec(), + StorageHasher::Blake2_256 => blake2_256(data).to_vec(), + StorageHasher::Blake2_128Concat => [blake2_128(data).to_vec(), data.to_vec()].concat(), + StorageHasher::Twox128 => twox_128(data).to_vec(), + StorageHasher::Twox256 => twox_256(data).to_vec(), + StorageHasher::Twox64Concat => [twox_64(data).to_vec(), data.to_vec()].concat(), + StorageHasher::Identity => data.to_vec(), + } +} + +pub fn whole_key_u32_value( + prefix: &str, + storage_name: &str, + metadata_v15: &RuntimeMetadataV15, + entered_data: u32, +) -> Result { + for pallet in metadata_v15.pallets.iter() { + if let Some(storage) = &pallet.storage { + if storage.prefix == prefix { + for entry in storage.entries.iter() { + if entry.name == storage_name { + match &entry.ty { + StorageEntryType::Plain(_) => { + return Err(ChainError::StorageEntryNotMap) + } + StorageEntryType::Map { + hashers, + key: key_ty, + value: _, + } => { + if hashers.len() == 1 { + let hasher = &hashers[0]; + match metadata_v15 + .types + .resolve_ty(key_ty.id, &mut ())? + .type_def + { + TypeDef::Primitive(TypeDefPrimitive::U32) => { + return Ok(format!( + "0x{}{}{}", + hex::encode(twox_128(prefix.as_bytes())), + hex::encode(twox_128(storage_name.as_bytes())), + hex::encode(hashed_key_element( + &entered_data.encode(), + hasher + )) + )) + } + _ => return Err(ChainError::StorageKeyNotU32), + } + } else { + return Err(ChainError::StorageEntryMapMultiple); + } + } + } + } + } + return Err(ChainError::StorageKeyNotFound(storage_name.to_string())); + } + } + } + Err(ChainError::NoPallet) +} + +pub fn decimals(x: &Map) -> Result { + match x.get("tokenDecimals") { + // decimals info is fetched in `system_properties` rpc call + Some(a) => match a { + // fetched decimals value is a number + Value::Number(b) => match b.as_u64() { + // number is integer and could be represented as `u64` (the only + // suitable interpretation available for `Number`) + Some(c) => match c.try_into() { + // this `u64` fits into `u8` that decimals is supposed to be + Ok(d) => Ok(d), + + // this `u64` does not fit into `u8`, this is an error + Err(_) => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), + }, + + // number could not be represented as `u64`, this is an error + None => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), + }, + + // fetched decimals is an array + Value::Array(b) => { + // array with only one element + if b.len() == 1 { + // this element is a number, process same as + // `Value::Number(_)` + if let Value::Number(c) = &b[0] { + match c.as_u64() { + // number is integer and could be represented as + // `u64` (the only suitable interpretation available + // for `Number`) + Some(d) => match d.try_into() { + // this `u64` fits into `u8` that decimals is + // supposed to be + Ok(f) => Ok(f), + + // this `u64` does not fit into `u8`, this is an + // error + Err(_) => { + Err(ChainError::DecimalsFormatNotSupported(a.to_string())) + } + }, + + // number could not be represented as `u64`, this is + // an error + None => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), + } + } else { + // element is not a number, this is an error + Err(ChainError::DecimalsFormatNotSupported(a.to_string())) + } + } else { + // decimals are an array with more than one element + Err(ChainError::DecimalsFormatNotSupported(a.to_string())) + } + } + + // unexpected decimals format + _ => Err(ChainError::DecimalsFormatNotSupported(a.to_string())), + }, + + // decimals are missing + None => Err(ChainError::NoDecimals), + } +} + +pub fn optional_prefix_from_meta(metadata: &RuntimeMetadataV15) -> Option { + let mut base58_prefix_data = None; + for pallet in &metadata.pallets { + if pallet.name == "System" { + for system_constant in &pallet.constants { + if system_constant.name == "SS58Prefix" { + base58_prefix_data = Some((&system_constant.value, &system_constant.ty)); + break; + } + } + break; + } + } + if let Some((value, ty_symbol)) = base58_prefix_data { + match decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + ty_symbol, + &value.as_ref(), + &mut (), + &metadata.types, + ) { + Ok(extended_data) => match extended_data.data { + ParsedData::PrimitiveU8 { + value, + specialty: _, + } => Some(value.into()), + ParsedData::PrimitiveU16 { + value, + specialty: _, + } => Some(value), + ParsedData::PrimitiveU32 { + value, + specialty: _, + } => value.try_into().ok(), + ParsedData::PrimitiveU64 { + value, + specialty: _, + } => value.try_into().ok(), + ParsedData::PrimitiveU128 { + value, + specialty: _, + } => value.try_into().ok(), + _ => None, + }, + Err(_) => None, + } + } else { + None + } +} + +pub fn fetch_constant( + metadata: &RuntimeMetadataV15, + pallet_name: &str, + constant_name: &str, +) -> Option { + let mut found = None; + for pallet in &metadata.pallets { + if pallet.name == pallet_name { + for constant in &pallet.constants { + if constant.name == constant_name { + found = Some((&constant.value, &constant.ty)); + break; + } + } + break; + } + } + if let Some((value, ty_symbol)) = found { + decode_all_as_type::<&[u8], (), RuntimeMetadataV15>( + ty_symbol, + &value.as_ref(), + &mut (), + &metadata.types, + ) + .ok() + } else { + None + } +} + +pub fn system_properties_to_short_specs( + system_properties: &Map, + metadata: &RuntimeMetadataV15, +) -> Result { + let optional_prefix_from_meta = optional_prefix_from_meta(metadata); + let base58prefix = base58prefix(system_properties, optional_prefix_from_meta)?; + let decimals = decimals(system_properties)?; + let unit = unit(system_properties)?; + Ok(ShortSpecs { + base58prefix, + decimals, + unit, + }) +} + +pub fn pallet_index(metadata: &RuntimeMetadataV15, name: &str) -> Option { + for pallet in &metadata.pallets { + if pallet.name == name { + return Some(pallet.index); + } + } + return None; +} + +pub fn storage_key(prefix: &str, storage_name: &str) -> String { + format!( + "0x{}{}", + hex::encode(twox_128(prefix.as_bytes())), + hex::encode(twox_128(storage_name.as_bytes())) + ) +} + +pub fn base58prefix( + x: &Map, + optional_prefix_from_meta: Option, +) -> Result { + let base58prefix: u16 = match x.get("ss58Format") { + // base58 prefix is fetched in `system_properties` rpc call + Some(a) => match a { + // base58 prefix value is a number + Value::Number(b) => match b.as_u64() { + // number is integer and could be represented as `u64` (the only + // suitable interpretation available for `Number`) + Some(c) => match c.try_into() { + // this `u64` fits into `u16` that base58 prefix is supposed + // to be + Ok(d) => match optional_prefix_from_meta { + // base58 prefix was found in `SS58Prefix` constant of + // the network metadata + // + // check that the prefixes match + Some(prefix_from_meta) => { + if prefix_from_meta == d { + d + } else { + return Err(ChainError::Base58PrefixMismatch { + specs: d, + meta: prefix_from_meta, + }); + } + } + + // no base58 prefix was found in the network metadata + None => d, + }, + + // `u64` value does not fit into `u16` base58 prefix format, + // this is an error + Err(_) => { + return Err(ChainError::Base58PrefixFormatNotSupported(a.to_string())) + } + }, + + // base58 prefix value could not be presented as `u64` number, + // this is an error + None => return Err(ChainError::Base58PrefixFormatNotSupported(a.to_string())), + }, + + // base58 prefix value is not a number, this is an error + _ => return Err(ChainError::Base58PrefixFormatNotSupported(a.to_string())), + }, + + // no base58 prefix fetched in `system_properties` rpc call + None => match optional_prefix_from_meta { + // base58 prefix was found in `SS58Prefix` constant of the network + // metadata + Some(prefix_from_meta) => prefix_from_meta, + + // no base58 prefix at all, this is an error + None => return Err(ChainError::NoBase58Prefix), + }, + }; + Ok(base58prefix) +} + +pub fn unit(x: &Map) -> Result { + match x.get("tokenSymbol") { + // unit info is fetched in `system_properties` rpc call + Some(a) => match a { + // fetched unit value is a `String` + Value::String(b) => { + // definitive unit found + Ok(b.to_string()) + } + + // fetched an array of units + Value::Array(b) => { + // array with a single element + if b.len() == 1 { + // single `String` element array, process same as `String` + if let Value::String(c) = &b[0] { + // definitive unit found + Ok(c.to_string()) + } else { + // element is not a `String`, this is an error + Err(ChainError::UnitFormatNotSupported(a.to_string())) + } + } else { + // units are an array with more than one element + Err(ChainError::UnitFormatNotSupported(a.to_string())) + } + } + + // unexpected unit format + _ => Err(ChainError::UnitFormatNotSupported(a.to_string())), + }, + + // unit missing + None => Err(ChainError::NoUnit), + } +} diff --git a/src/database.rs b/src/database.rs index b06187b..8c232a3 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,80 +1,143 @@ +//! Database server module +//! +//! We do not need concurrency here, as this is our actual source of truth for legally binging +//! commercial offers and contracts, hence causality is a must. Care must be taken that no threads +//! are spawned here other than main database server thread that does everything in series. + use crate::{ - rpc::{ChainProperties, EndpointProperties}, - Account, Balance, BlockNumber, RuntimeConfig, Version, DATABASE_VERSION, OVERRIDE_RPC, SEED, -}; -use anyhow::{Context, Result}; -use redb::{ - backends::InMemoryBackend, AccessGuard, ReadOnlyTable, ReadableTable, RedbValue, Table, - TableDefinition, TableHandle, TypeName, -}; -use std::sync::Arc; -use subxt::{ - ext::{ - codec::{Compact, Decode, Encode}, - sp_core::{ - crypto::Ss58Codec, - sr25519::{Pair, Public}, - DeriveJunction, Pair as _, + definitions::{ + api_v2::{ + AssetId, BlockNumber, CurrencyInfo, CurrencyProperties, OrderCreateResponse, OrderInfo, + OrderQuery, PaymentStatus, ServerInfo, ServerStatus, Timestamp, WithdrawalStatus, }, + Balance, Nonce, Version, }, - tx::PairSigner, + error::{DbError, Error}, + TaskTracker, }; -use tokio::{ - sync::{RwLock, RwLockReadGuard}, - task, +use parity_scale_codec::{Compact, Decode, Encode}; +use serde::Deserialize; +use std::{ + collections::HashMap, + fs::File, + io::ErrorKind, + time::{Duration, Instant, SystemTime}, }; - -type Order = [u8; 32]; +use substrate_crypto_light::common::AccountId32; +use tokio::sync::{mpsc, oneshot}; pub const MODULE: &str = module_path!(); +const DB_VERSION: Version = 0; + // Tables +/* +const ROOT: TableDefinition<'_, &str, &[u8]> = TableDefinition::new("root"); +const KEYS: TableDefinition<'_, PublicSlot, U256Slot> = TableDefinition::new("keys"); +const CHAINS: TableDefinition<'_, ChainHash, BlockNumber> = TableDefinition::new("chains"); +const INVOICES: TableDefinition<'_, InvoiceKey, Invoice> = TableDefinition::new("invoices"); +*/ +const ACCOUNTS: &str = "accounts"; + +//type ACCOUNTS_KEY = (Option, Account); +//type ACCOUNTS_VALUE = InvoiceKey; + +const TRANSACTIONS: &str = "transactions"; -const ROOT: TableDefinition<'_, &str, Vec> = TableDefinition::new("root"); -const INVOICES: TableDefinition<'_, &[u8; 32], Invoice> = TableDefinition::new("invoices"); +//type TRANSACTIONS_KEY = BlockNumber; +//type TRANSACTIONS_VALUE = (Account, Nonce, Transfer); -// Keys +const HIT_LIST: &str = "hit_list"; + +//type HIT_LIST_KEY = BlockNumber; +//type HIT_LIST_VALUE = (Option, Account); + +// `ROOT` keys // The database version must be stored in a separate slot to be used by the not implemented yet // database migration logic. const DB_VERSION_KEY: &str = "db_version"; const DAEMON_INFO: &str = "daemon_info"; -const LAST_BLOCK: &str = "last_block"; + +const ORDERS: &[u8] = b"orders"; // Slots -#[derive(Debug, Encode, Decode)] -#[codec(crate = subxt::ext::codec)] -pub struct Invoice { - pub recipient: Account, - pub order: Order, - pub status: InvoiceStatus, +type InvoiceKey = &'static [u8]; +type U256Slot = [u64; 4]; +type BlockHash = [u8; 32]; +type ChainHash = [u8; 32]; +type PublicSlot = [u8; 32]; +type BalanceSlot = u128; +type Derivation = [u8; 32]; +pub type Account = [u8; 32]; +/* +#[derive(Encode, Decode)] +enum ChainKind { + Id(Vec>), + MultiLocation(Vec>), } -impl Invoice { - pub fn signer(&self, pair: &Pair) -> Result> { - let invoice_pair = pair - .derive( - [self.recipient.clone().into(), self.order] - .map(DeriveJunction::Hard) - .into_iter(), - None, - ) - .context("failed to derive an invoice key pair")? - .0; - - Ok(PairSigner::new(invoice_pair)) - } +#[derive(Encode, Decode)] +struct DaemonInfo { + chains: Vec<(String, ChainProperties)>, + current_key: PublicSlot, + old_keys_death_timestamps: Vec<(PublicSlot, Timestamp)>, } -#[derive(Debug, Encode, Decode)] -#[codec(crate = subxt::ext::codec)] -pub enum InvoiceStatus { - Unpaid(Balance), - Paid(Balance), +#[derive(Encode, Decode)] +struct ChainProperties { + genesis: BlockHash, + hash: ChainHash, + kind: ChainKind, } -impl RedbValue for Invoice { +#[derive(Encode, Decode)] +struct Transfer(Option>, #[codec(compact)] BalanceSlot); + +#[derive(Encode, Decode, Debug)] +struct Invoice { + derivation: (PublicSlot, Derivation), + paid: bool, + #[codec(compact)] + timestamp: Timestamp, + #[codec(compact)] + price: BalanceSlot, + callback: String, + message: String, + transactions: TransferTxs, +} + +#[derive(Encode, Decode, Debug)] +enum TransferTxs { + Asset { + #[codec(compact)] + id: AssetId, + // transactions: TransferTxsAsset, + }, + Native { + recipient: Account, + encoded: Vec, + exact_amount: Option>, + }, +} + +// #[derive(Encode, Decode, Debug)] +// struct TransferTxsAsset { +// recipient: Account, +// encoded: Vec, +// #[codec(compact)] +// amount: BalanceSlot, +// } + +#[derive(Encode, Decode, Debug)] +struct TransferTx { + recipient: Account, + exact_amount: Option>, +} +*/ +/* +impl Value for Invoice { type SelfType<'a> = Self; type AsBytes<'a> = Vec; @@ -83,7 +146,7 @@ impl RedbValue for Invoice { None } - fn from_bytes<'a>(mut data: &'a [u8]) -> Self::SelfType<'_> + fn from_bytes<'a>(mut data: &[u8]) -> Self::SelfType<'_> where Self: 'a, { @@ -97,280 +160,572 @@ impl RedbValue for Invoice { fn type_name() -> TypeName { TypeName::new(stringify!(Invoice)) } -} +}*/ -#[derive(Encode, Decode)] -#[codec(crate = subxt::ext::codec)] -struct DaemonInfo { - rpc: String, - key: Public, +pub struct ConfigWoChains { + pub recipient: AccountId32, + pub debug: Option, + pub remark: Option, + //pub depth: Option, } +/// Database server handle +#[derive(Clone, Debug)] pub struct Database { - db: redb::Database, - properties: Arc>, - pair: Pair, - rpc: String, - destination: Option, + tx: mpsc::Sender, } impl Database { - pub fn initialise( + pub fn init( path_option: Option, - override_rpc: bool, - pair: Pair, - EndpointProperties { url, chain }: EndpointProperties, - destination: Option, - ) -> Result<(Arc, Option)> { - let public = pair.public(); - let public_formatted = public.to_ss58check_with_version( - task::block_in_place(|| chain.blocking_read()).address_format, - ); - let given_rpc = url.get(); - - let mut database = if let Some(path) = path_option { - log::info!("Creating/Opening the database at \"{path}\"."); - - redb::Database::create(path) + task_tracker: TaskTracker, + account_lifetime: Timestamp, + ) -> Result { + let (tx, mut rx) = tokio::sync::mpsc::channel(1024); + let database = if let Some(path) = path_option { + tracing::info!("Creating/Opening the database at {path:?}."); + + sled::open(path).map_err(DbError::DbStartError)? } else { - log::warn!( + // TODO + /* + tracing::warn!( "The in-memory backend for the database is selected. All saved data will be deleted after the shutdown!" - ); - - redb::Database::builder().create_with_backend(InMemoryBackend::new()) - }.context("failed to create/open the database")?; - - let tx = database - .begin_write() - .context("failed to begin a write transaction")?; - let mut table = tx - .open_table(ROOT) - .with_context(|| format!("failed to open the `{}` table", ROOT.name()))?; - drop( - tx.open_table(INVOICES) - .with_context(|| format!("failed to open the `{}` table", INVOICES.name()))?, - ); - - let last_block = match ( - get_slot(&table, DB_VERSION_KEY)?, - get_slot(&table, DAEMON_INFO)?, - get_slot(&table, LAST_BLOCK)?, - ) { - (None, None, None) => { - table - .insert( - DB_VERSION_KEY, - Compact(DATABASE_VERSION).encode(), - ) - .context("failed to insert the database version")?; - insert_daemon_info(&mut table, given_rpc.clone(), public)?; - - None - } - (Some(encoded_db_version), Some(daemon_info), last_block_option) => { - let Compact::(db_version) = - decode_slot(&encoded_db_version, DB_VERSION_KEY)?; - let DaemonInfo { rpc: db_rpc, key } = decode_slot(&daemon_info, DAEMON_INFO)?; - - if db_version != DATABASE_VERSION { - anyhow::bail!( - "database contains an unsupported database version (\"{db_version}\"), expected \"{DATABASE_VERSION}\"" - ); - } - - if public != key { - anyhow::bail!( - "public key from `{SEED}` doesn't equal the one from the database (\"{public_formatted}\")" - ); - } - - if given_rpc != db_rpc { - if override_rpc { - log::warn!( - "The saved RPC endpoint ({db_rpc:?}) differs from the given one ({given_rpc:?}) and will be overwritten by it because `{OVERRIDE_RPC}` is set." - ); - - insert_daemon_info(&mut table, given_rpc.clone(), public)?; - } else { - anyhow::bail!( - "database contains a different RPC endpoint address ({db_rpc:?}), expected {given_rpc:?}" - ); + );*/ + sled::open("temp.db").map_err(DbError::DbStartError)? + }; + let orders = database.open_tree(ORDERS).map_err(DbError::DbStartError)?; + + task_tracker.spawn("Database server", async move { + // No process forking beyond this point! + while let Some(request) = rx.recv().await { + match request { + DbRequest::ActiveOrderList(res) => { + let _unused = res.send(Ok(orders + .iter() + .filter_map(|a| a.ok()) + .filter_map(|(a, b)| { + match (String::decode(&mut &a[..]), OrderInfo::decode(&mut &b[..])) + { + (Ok(a), Ok(b)) => Some((a, b)), + _ => None, + } + }) + .filter(|(a, b)| b.payment_status == PaymentStatus::Pending) + .collect())); + } + DbRequest::CreateOrder(request) => { + let _unused = request.res.send(create_order( + request.order, + request.query, + request.currency, + request.payment_account, + &orders, + account_lifetime, + )); + } + DbRequest::ReadOrder(request) => { + let _unused = request.res.send(read_order(request.order, &orders)); + } + DbRequest::MarkPaid(request) => { + let _unused = request.res.send(mark_paid(request.order, &orders)); + } + DbRequest::MarkWithdrawn(request) => { + let _unused = request.res.send(mark_withdrawn(request.order, &orders)); + } + DbRequest::MarkStuck(request) => { + let _unused = request.res.send(mark_stuck(request.order, &orders)); } - } else if override_rpc { - log::warn!( - "`{OVERRIDE_RPC}` is set but the saved RPC endpoint ({db_rpc:?}) equals to the given one." - ); - } - - if let Some(encoded_last_block) = last_block_option { - Some(decode_slot::>(&encoded_last_block, LAST_BLOCK)?.0) - } else { - None - } + DbRequest::Shutdown(res) => { + let _ = res.send(()); + break; + } + }; } - _ => anyhow::bail!( - "database was found but it doesn't contain `{DB_VERSION_KEY:?}` and/or `{DAEMON_INFO:?}`, maybe it was created by another program" - ), - }; - - drop(table); - tx.commit().context("failed to commit a transaction")?; + drop(database.flush()); - let compacted = database - .compact() - .context("failed to compact the database")?; + Ok("Database server is shutting down".into()) + }); - if compacted { - log::debug!("The database was successfully compacted."); - } else { - log::debug!("The database doesn't need the compaction."); - } + Ok(Self { tx }) + } - log::info!("Public key from the given seed: \"{public_formatted}\"."); - - Ok(( - Arc::new(Self { - db: database, - properties: chain, - pair, - rpc: given_rpc, - destination, - }), - last_block, - )) + pub async fn order_list(&self) -> Result, DbError> { + let (res, rx) = oneshot::channel(); + let _unused = self.tx.send(DbRequest::ActiveOrderList(res)).await; + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub fn rpc(&self) -> &str { - &self.rpc + pub async fn create_order( + &self, + order: String, + query: OrderQuery, + currency: CurrencyInfo, + payment_account: String, + ) -> Result { + let (res, rx) = oneshot::channel(); + let _unused = self + .tx + .send(DbRequest::CreateOrder(CreateOrder { + order, + query, + currency, + payment_account, + res, + })) + .await; + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub fn destination(&self) -> &Option { - &self.destination + pub async fn read_order(&self, order: String) -> Result, DbError> { + let (res, rx) = oneshot::channel(); + let _unused = self + .tx + .send(DbRequest::ReadOrder(ReadOrder { order, res })) + .await; + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub fn write(&self) -> Result> { - self.db - .begin_write() - .map(WriteTransaction) - .context("failed to begin a write transaction for the database") + pub async fn mark_paid(&self, order: String) -> Result { + let (res, rx) = oneshot::channel(); + let _unused = self + .tx + .send(DbRequest::MarkPaid(MarkPaid { order, res })) + .await; + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub fn read(&self) -> Result> { - self.db - .begin_read() - .map(ReadTransaction) - .context("failed to begin a read transaction for the database") + pub async fn mark_withdrawn(&self, order: String) -> Result<(), DbError> { + let (res, rx) = oneshot::channel(); + let _unused = self + .tx + .send(DbRequest::MarkWithdrawn(ModifyOrder { order, res })) + .await; + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub async fn properties(&self) -> RwLockReadGuard<'_, ChainProperties> { - self.properties.read().await + pub async fn mark_stuck(&self, order: String) -> Result<(), DbError> { + let (res, rx) = oneshot::channel(); + let _unused = self + .tx + .send(DbRequest::MarkStuck(ModifyOrder { order, res })) + .await; + rx.await.map_err(|_| DbError::DbEngineDown)? } - pub fn pair(&self) -> &Pair { - &self.pair + pub async fn shutdown(&self) { + let (tx, rx) = oneshot::channel(); + let _unused = self.tx.send(DbRequest::Shutdown(tx)).await; + let _ = rx.await; } } -pub struct ReadTransaction<'db>(redb::ReadTransaction<'db>); +enum DbRequest { + CreateOrder(CreateOrder), + ActiveOrderList(oneshot::Sender, DbError>>), + ReadOrder(ReadOrder), + MarkPaid(MarkPaid), + MarkWithdrawn(ModifyOrder), + MarkStuck(ModifyOrder), + Shutdown(oneshot::Sender<()>), +} -impl ReadTransaction<'_> { - pub fn invoices(&self) -> Result> { - self.0 - .open_table(INVOICES) - .map(ReadInvoices) - .with_context(|| format!("failed to open the `{}` table", INVOICES.name())) - } +pub struct CreateOrder { + pub order: String, + pub query: OrderQuery, + pub currency: CurrencyInfo, + pub payment_account: String, + pub res: oneshot::Sender>, } -pub struct ReadInvoices<'tx>(ReadOnlyTable<'tx, &'static [u8; 32], Invoice>); +pub struct ReadOrder { + pub order: String, + pub res: oneshot::Sender, DbError>>, +} -impl ReadInvoices<'_> { - pub fn get(&self, account: &Account) -> Result>> { - self.0 - .get(AsRef::<[u8; 32]>::as_ref(account)) - .context("failed to get an invoice from the database") - } +pub struct ModifyOrder { + pub order: String, + pub res: oneshot::Sender>, +} - pub fn try_iter( - &self, - ) -> Result, AccessGuard<'_, Invoice>)>>> - { - self.0 - .iter() - .context("failed to get the invoices iterator") - .map(|iter| iter.map(|item| item.context("failed to get an invoice from the iterator"))) - } +pub struct MarkPaid { + pub order: String, + pub res: oneshot::Sender>, } -pub struct WriteTransaction<'db>(redb::WriteTransaction<'db>); +fn calculate_death_ts(account_lifetime: Timestamp) -> Timestamp { + let start = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; -impl<'db> WriteTransaction<'db> { - pub fn root(&self) -> Result> { - self.0 - .open_table(ROOT) - .map(Root) - .with_context(|| format!("failed to open the `{}` table", ROOT.name())) - } + Timestamp(start + account_lifetime.0) +} - pub fn invoices(&self) -> Result> { - self.0 - .open_table(INVOICES) - .map(WriteInvoices) - .with_context(|| format!("failed to open the `{}` table", INVOICES.name())) - } +fn create_order( + order: String, + query: OrderQuery, + currency: CurrencyInfo, + payment_account: String, + orders: &sled::Tree, + account_lifetime: Timestamp, +) -> Result { + Ok(if let Some(record) = orders.get(&order)? { + let mut old_order_info = OrderInfo::decode(&mut &record[..])?; + match old_order_info.payment_status { + PaymentStatus::Pending => { + let death = calculate_death_ts(account_lifetime); + + old_order_info.death = death; + + drop(orders.insert(order.encode(), old_order_info.encode())?); + OrderCreateResponse::Modified(old_order_info) + } + PaymentStatus::Paid => OrderCreateResponse::Collision(old_order_info), + } + } else { + let death = calculate_death_ts(account_lifetime); + let order_info_new = OrderInfo::new(query, currency, payment_account, death); - pub fn commit(self) -> Result<()> { - self.0 - .commit() - .context("failed to commit a write transaction in the database") - } + orders.insert(order.encode(), order_info_new.encode())?; + OrderCreateResponse::New(order_info_new) + }) } -pub struct WriteInvoices<'db, 'tx>(Table<'db, 'tx, &'static [u8; 32], Invoice>); - -impl WriteInvoices<'_, '_> { - pub fn save( - &mut self, - account: &Account, - invoice: &Invoice, - ) -> Result>> { - self.0 - .insert(AsRef::<[u8; 32]>::as_ref(account), invoice) - .context("failed to save an invoice in the database") +fn read_order(order: String, orders: &sled::Tree) -> Result, DbError> { + if let Some(order) = orders.get(order)? { + Ok(Some(OrderInfo::decode(&mut &order[..])?)) + } else { + Ok(None) } } -pub struct Root<'db, 'tx>(Table<'db, 'tx, &'static str, Vec>); - -impl Root<'_, '_> { - pub fn save_last_block(&mut self, number: BlockNumber) -> Result<()> { - self.0 - .insert(LAST_BLOCK, Compact(number).encode()) - .context("context")?; - - Ok(()) +fn mark_paid(order: String, orders: &sled::Tree) -> Result { + if let Some(order_info) = orders.get(order.clone())? { + let mut order_info = OrderInfo::decode(&mut &order_info[..])?; + if order_info.payment_status == PaymentStatus::Pending { + order_info.payment_status = PaymentStatus::Paid; + orders.insert(order.encode(), order_info.encode())?; + Ok(order_info) + } else { + Err(DbError::AlreadyPaid(order)) + } + } else { + Err(DbError::OrderNotFound(order)) } } - -fn get_slot(table: &Table<'_, '_, &str, Vec>, key: &str) -> Result>> { - table - .get(key) - .map(|slot_option| slot_option.map(|slot| slot.value().clone())) - .with_context(|| format!("failed to get the {key:?} slot")) +fn mark_withdrawn(order: String, orders: &sled::Tree) -> Result<(), DbError> { + if let Some(order_info) = orders.get(order.clone())? { + let mut order_info = OrderInfo::decode(&mut &order_info[..])?; + if order_info.payment_status == PaymentStatus::Paid { + if order_info.withdrawal_status == WithdrawalStatus::Waiting { + order_info.withdrawal_status = WithdrawalStatus::Completed; + orders.insert(order.encode(), order_info.encode())?; + Ok(()) + } else { + Err(DbError::WithdrawalWasAttempted(order)) + } + } else { + Err(DbError::NotPaid(order)) + } + } else { + Err(DbError::OrderNotFound(order)) + } } +fn mark_stuck(order: String, orders: &sled::Tree) -> Result<(), DbError> { + if let Some(order_info) = orders.get(order.clone())? { + let mut order_info = OrderInfo::decode(&mut &order_info[..])?; + if order_info.payment_status == PaymentStatus::Paid { + if order_info.withdrawal_status == WithdrawalStatus::Waiting { + order_info.withdrawal_status = WithdrawalStatus::Failed; + orders.insert(order.encode(), order_info.encode())?; + Ok(()) + } else { + Err(DbError::WithdrawalWasAttempted(order)) + } + } else { + Err(DbError::NotPaid(order)) + } + } else { + Err(DbError::OrderNotFound(order)) + } +} +//impl StateInterface { +/* + Ok(( + OrderStatus { + order, + payment_status: if invoice.paid { + PaymentStatus::Paid + } else { + PaymentStatus::Pending + }, + message: String::new(), + recipient: state.0.recipient.to_ss58check(), + server_info: state.server_info(), + order_info: OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + amount: invoice.amount.format(6), + currency: CurrencyInfo { + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Asset, + decimals: 6, + rpc_url: state.rpc.clone(), + asset_id: Some(1337), + }, + callback: invoice.callback.clone(), + transactions: vec![], + payment_account: invoice.paym_acc.to_ss58check(), + }, + }, + OrderSuccess::Found, + )) +} else { + Ok(( + OrderStatus { + order, + payment_status: PaymentStatus::Unknown, +message: String::new(), + recipient: state.0.recipient.to_ss58check(), + server_info: state.server_info(), + order_info: OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + amount: 0f64, + currency: CurrencyInfo { + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Asset, + decimals: 6, + rpc_url: state.rpc.clone(), + asset_id: Some(1337), + }, + callback: String::new(), + transactions: vec![], + payment_account: String::new(), + }, + }, + OrderSuccess::Found, + )) +}*/ + +/* + * +let pay_acc: AccountId = state + .0 + .pair + .derive(vec![DeriveJunction::hard(order.clone())].into_iter(), None) + .unwrap() + .0 + .public() + .into(); + + * */ + +/*( + OrderStatus { + order, + payment_status: PaymentStatus::Pending, + message: String::new(), + recipient: state.0.recipient.to_ss58check(), + server_info: state.server_info(), + order_info: OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + amount, + currency: CurrencyInfo { + currency: "USDC".into(), + chain_name: "assethub-polkadot".into(), + kind: TokenKind::Asset, + decimals: 6, + rpc_url: state.rpc.clone(), + asset_id: Some(1337), + }, + callback, + transactions: vec![], + payment_account: pay_acc.to_ss58check(), + }, + }, + OrderSuccess::Created, +))*/ -fn decode_slot(mut slot: &[u8], key: &str) -> Result { - T::decode(&mut slot).with_context(|| format!("failed to decode the {key:?} slot")) +/* + ServerStatus { + description: state.server_info(), + supported_currencies: state.currencies.clone(), + } +*/ +/* +#[derive(Deserialize, Debug)] +pub struct Invoicee { + pub callback: String, + pub amount: Balance, + pub paid: bool, + pub paym_acc: Account, } +*/ +/* + +*/ +/* + pub fn server_info(&self) -> ServerInfo { + ServerInfo { + version: env!("CARGO_PKG_VERSION"), + instance_id: String::new(), + debug: self.debug, + kalatori_remark: self.remark.clone(), + } + } +*/ +/* + pub fn currency_properties(&self, currency_name: &str) -> Result<&CurrencyProperties, ErrorDb> { + self.currencies + .get(currency_name) + .ok_or(ErrorDb::CurrencyKeyNotFound) + } -fn insert_daemon_info( - table: &mut Table<'_, '_, &str, Vec>, - rpc: String, - key: Public, -) -> Result<()> { - table - .insert(DAEMON_INFO, DaemonInfo { rpc, key }.encode()) - .map(|_| ()) - .context("failed to insert the daemon info") + pub fn currency_info(&self, currency_name: &str) -> Result { + let currency = self.currency_properties(currency_name)?; + Ok(CurrencyInfo { + currency: currency_name.to_string(), + chain_name: currency.chain_name.clone(), + kind: currency.kind, + decimals: currency.decimals, + rpc_url: currency.rpc_url.clone(), + asset_id: currency.asset_id, + }) + } +*/ +// pub fn rpc(&self) -> &str { +// &self.rpc +// } + +// pub fn destination(&self) -> &Option { +// &self.destination +// } + +// pub fn write(&self) -> Result> { +// self.db +// .begin_write() +// .map(WriteTransaction) +// .context("failed to begin a write transaction for the database") +// } + +// pub fn read(&self) -> Result> { +// self.db +// .begin_read() +// .map(ReadTransaction) +// .context("failed to begin a read transaction for the database") +// } + +// pub async fn properties(&self) -> RwLockReadGuard<'_, ChainProperties> { +// self.properties.read().await +// } + +// pub fn pair(&self) -> &Pair { +// &self.pair +// } + +/* +pub struct ReadTransaction(redb::ReadTransaction); + +impl ReadTransaction { + pub fn invoices(&self) -> Result { + self.0 + .open_table(INVOICES) + .map(ReadInvoices) + .with_context(|| format!("failed to open the `{}` table", INVOICES.name())) + } } + +pub struct ReadInvoices<'a>(ReadOnlyTable<&'a [u8], Invoice>); + +impl <'a> ReadInvoices<'a> { + pub fn get(&self, account: &Account) -> Result>> { + self.0 + .get(&*account) + .context("failed to get an invoice from the database") + } +*/ +// pub fn try_iter( +// &self, +// ) -> Result, AccessGuard<'_, Invoice>)>>> +// { +// self.0 +// .iter() +// .context("failed to get the invoices iterator") +// .map(|iter| iter.map(|item| item.context("failed to get an invoice from the iterator"))) +// } +// } + +// pub struct WriteTransaction<'db>(redb::WriteTransaction<'db>); + +// impl<'db> WriteTransaction<'db> { +// pub fn root(&self) -> Result> { +// self.0 +// .open_table(ROOT) +// .map(Root) +// .with_context(|| format!("failed to open the `{}` table", ROOT.name())) +// } + +// pub fn invoices(&self) -> Result> { +// self.0 +// .open_table(INVOICES) +// .map(WriteInvoices) +// .with_context(|| format!("failed to open the `{}` table", INVOICES.name())) +// } + +// pub fn commit(self) -> Result<()> { +// self.0 +// .commit() +// .context("failed to commit a write transaction in the database") +// } +// } + +// pub struct WriteInvoices<'db, 'tx>(Table<'db, 'tx, &'static [u8; 32], Invoice>); + +// impl WriteInvoices<'_, '_> { +// pub fn save( +// &mut self, +// account: &Account, +// invoice: &Invoice, +// ) -> Result>> { +// self.0 +// .insert(AsRef::<[u8; 32]>::as_ref(account), invoice) +// .context("failed to save an invoice in the database") +// } +// } + +// pub struct Root<'db, 'tx>(Table<'db, 'tx, &'static str, Vec>); + +// impl Root<'_, '_> { +// pub fn save_last_block(&mut self, number: BlockNumber) -> Result<()> { +// self.0 +// .insert(LAST_BLOCK, Compact(number).encode()) +// .context("context")?; + +// Ok(()) +// } +// } + +// fn get_slot(table: &Table<'_, &str, Vec>, key: &str) -> Result>> { +// table +// .get(key) +// .map(|slot_option| slot_option.map(|slot| slot.value().clone())) +// .with_context(|| format!("failed to get the {key:?} slot")) +// } + +// fn decode_slot(mut slot: &[u8], key: &str) -> Result { +// T::decode(&mut slot).with_context(|| format!("failed to decode the {key:?} slot")) +// } + +// fn insert_daemon_info( +// table: &mut Table<'_, '_, &str, Vec>, +// rpc: String, +// key: Public, +// ) -> Result<()> { +// table +// .insert(DAEMON_INFO, DaemonInfo { rpc, key }.encode()) +// .map(|_| ()) +// .context("failed to insert the daemon info") +// } diff --git a/src/definitions.rs b/src/definitions.rs new file mode 100644 index 0000000..979cb7e --- /dev/null +++ b/src/definitions.rs @@ -0,0 +1,332 @@ +//! Deaf and dumb object definitions + +use std::ops::{Deref, Sub}; + +use serde::Deserialize; + +pub type Version = u64; +pub type Nonce = u32; + +pub type PalletIndex = u8; + +pub type Entropy = Vec; // TODO: maybe enforce something here + +#[derive(Clone, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Chain { + pub name: String, + pub endpoints: Vec, + #[serde(flatten)] + pub native_token: Option, + #[serde(default)] + pub asset: Vec, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct NativeToken { + pub name: String, + pub decimals: api_v2::Decimals, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct AssetInfo { + pub name: String, + pub id: api_v2::AssetId, +} + +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct Balance(pub u128); + +impl Deref for Balance { + type Target = u128; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Sub for Balance { + type Output = Self; + + fn sub(self, r: Self) -> Self { + Balance(self.0 - r.0) + } +} + +impl Balance { + pub fn format(&self, decimals: api_v2::Decimals) -> f64 { + #[allow(clippy::cast_precision_loss)] + let float = **self as f64; + + float / decimal_exponent_product(decimals) + } + + pub fn parse(float: f64, decimals: api_v2::Decimals) -> Self { + let parsed_float = (float * decimal_exponent_product(decimals)).round(); + + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + Self(parsed_float as _) + } +} + +pub fn decimal_exponent_product(decimals: api_v2::Decimals) -> f64 { + 10f64.powi(decimals.into()) +} + +/// Self-sufficient schemas used by Api v2.0.0 +pub mod api_v2 { + use std::collections::HashMap; + + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize, Serializer}; + + pub const AMOUNT: &str = "amount"; + pub const CURRENCY: &str = "currency"; + pub const CALLBACK: &str = "callback"; + + pub type AssetId = u32; + pub type Decimals = u8; + pub type BlockNumber = u64; + pub type ExtrinsicIndex = u32; + + #[derive(Encode, Decode, Debug, Clone, Copy, Serialize, Deserialize)] + pub struct Timestamp(pub u64); + + #[derive(Debug)] + pub struct OrderQuery { + pub order: String, + pub amount: f64, + pub callback: String, + pub currency: String, + } + + #[derive(Debug)] + pub enum OrderResponse { + NewOrder(OrderStatus), + FoundOrder(OrderStatus), + ModifiedOrder(OrderStatus), + CollidedOrder(OrderStatus), + NotFound, + } + + #[derive(Debug, Serialize)] + pub struct OrderStatus { + pub order: String, + pub message: String, + pub recipient: String, + pub server_info: ServerInfo, + #[serde(flatten)] + pub order_info: OrderInfo, + pub payment_page: String, + pub redirect_url: String, + } + + #[derive(Clone, Debug, Serialize, Encode, Decode)] + pub struct OrderInfo { + pub withdrawal_status: WithdrawalStatus, + pub payment_status: PaymentStatus, + pub amount: f64, + pub currency: CurrencyInfo, + pub callback: String, + pub transactions: Vec, + pub payment_account: String, + pub death: Timestamp, + } + + impl OrderInfo { + pub fn new( + query: OrderQuery, + currency: CurrencyInfo, + payment_account: String, + death: Timestamp, + ) -> Self { + OrderInfo { + withdrawal_status: WithdrawalStatus::Waiting, + payment_status: PaymentStatus::Pending, + amount: query.amount, + currency, + callback: query.callback, + transactions: Vec::new(), + payment_account, + death, + } + } + } + + pub enum OrderCreateResponse { + New(OrderInfo), + Modified(OrderInfo), + Collision(OrderInfo), + } + + #[derive(Clone, Debug, Serialize, Decode, Encode, PartialEq)] + #[serde(rename_all = "lowercase")] + pub enum PaymentStatus { + Pending, + Paid, + } + + #[derive(Clone, Debug, Serialize, Decode, Encode, PartialEq)] + #[serde(rename_all = "lowercase")] + pub enum WithdrawalStatus { + Waiting, + Failed, + Completed, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct ServerStatus { + pub server_info: ServerInfo, + pub supported_currencies: HashMap, + } + + #[derive(Debug, Serialize)] + struct ServerHealth { + server_info: ServerInfo, + connected_rpcs: Vec, + status: Health, + } + + #[derive(Debug, Serialize)] + struct RpcInfo { + rpc_url: String, + chain_name: String, + status: Health, + } + + #[derive(Debug, Serialize)] + #[serde(rename_all = "lowercase")] + enum Health { + Ok, + Degraded, + Critical, + } + + #[derive(Clone, Debug, Serialize, Decode, Encode)] + pub struct CurrencyInfo { + pub currency: String, + pub chain_name: String, + pub kind: TokenKind, + pub decimals: Decimals, + pub rpc_url: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub asset_id: Option, + // #[serde(skip_serializing)] + pub ss58: u16, + } + + impl CurrencyInfo { + pub fn properties(&self) -> CurrencyProperties { + CurrencyProperties { + chain_name: self.chain_name.clone(), + kind: self.kind, + decimals: self.decimals, + rpc_url: self.rpc_url.clone(), + asset_id: self.asset_id, + ss58: self.ss58, + } + } + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct CurrencyProperties { + pub chain_name: String, + pub kind: TokenKind, + pub decimals: Decimals, + pub rpc_url: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub asset_id: Option, + // #[serde(skip_serializing)] + pub ss58: u16, + } + + impl CurrencyProperties { + pub fn info(&self, currency: String) -> CurrencyInfo { + CurrencyInfo { + currency, + chain_name: self.chain_name.clone(), + kind: self.kind, + decimals: self.decimals, + rpc_url: self.rpc_url.clone(), + asset_id: self.asset_id, + ss58: self.ss58, + } + } + } + + #[derive(Clone, Copy, Debug, Serialize, Decode, Encode, Deserialize, PartialEq)] + #[serde(rename_all = "lowercase")] + pub enum TokenKind { + Asset, + Balances, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct ServerInfo { + pub version: String, + pub instance_id: String, + pub debug: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub kalatori_remark: Option, + } + + #[derive(Clone, Debug, Serialize, Decode, Encode)] + pub struct TransactionInfo { + #[serde(skip_serializing_if = "Option::is_none", flatten)] + finalized_tx: Option, // Clearly undefined in v2.1 - TODO + transaction_bytes: String, + sender: String, + recipient: String, + #[serde(serialize_with = "amount_serializer")] + amount: Amount, + currency: CurrencyInfo, + status: TxStatus, + } + + #[derive(Clone, Debug, Serialize, Decode, Encode)] + struct FinalizedTx { + block_number: BlockNumber, + position_in_block: ExtrinsicIndex, + timestamp: String, + } + + #[derive(Clone, Debug, Decode, Encode)] + enum Amount { + All, + Exact(f64), + } + + fn amount_serializer(amount: &Amount, serializer: S) -> Result { + match amount { + Amount::All => serializer.serialize_str("all"), + Amount::Exact(exact) => exact.serialize(serializer), + } + } + + #[derive(Clone, Debug, Serialize, Decode, Encode)] + #[serde(rename_all = "lowercase")] + enum TxStatus { + Pending, + Finalized, + Failed, + } +} + +#[cfg(test)] +#[test] +#[allow( + clippy::inconsistent_digit_grouping, + clippy::unreadable_literal, + clippy::float_cmp +)] + +fn balance_insufficient_precision() { + const DECIMALS: api_v2::Decimals = 10; + + let float = 931395.862219815_3; + let parsed = Balance::parse(float, DECIMALS); + + assert_eq!(*parsed, 931395_862219815_2); + assert_eq!(parsed.format(DECIMALS), 931395.862219815_1); +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b38ef4b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,595 @@ +use crate::{ + arguments::{OLD_SEED, SEED}, + definitions::api_v2::OrderStatus, +}; +use frame_metadata::v15::RuntimeMetadataV15; +use jsonrpsee::core::ClientError; +use mnemonic_external::error::ErrorWordList; +use parity_scale_codec::Error as ScaleError; +use serde_json::Error as JsonError; +use serde_json::Value; +use sled::Error as DatabaseError; +use std::{borrow::Cow, io::Error as IoError, net::SocketAddr}; +use substrate_constructor::error::{ErrorFixMe, StorageRegistryError}; +use substrate_crypto_light::error::Error as CryptoError; +use substrate_parser::error::{MetaVersionErrorPallets, ParserError, RegistryError, StorageError}; +use thiserror::Error; +use tokio::task::JoinError; +use toml_edit::de::Error as TomlError; +use tracing_subscriber::filter::ParseError; + +pub use pretty_cause::PrettyCause; + +#[derive(Debug, Error)] +pub enum Error { + #[error("failed to read a seed environment variable")] + SeedEnv(#[from] SeedEnvError), + + #[error("failed to read the config file at {0:?}")] + ConfigFileRead(String, #[source] IoError), + + #[error("failed to parse the config")] + ConfigFileParse(#[from] TomlError), + + #[error("failed to parse the config parameter `{0}`")] + ConfigParse(&'static str), + + #[error("chain {0:?} doesn't have any `endpoints` in the config")] + EmptyEndpoints(String), + + #[error("RPC server error is occurred")] + Chain(#[from] ChainError), + + #[error("database error is occurred")] + Db(#[from] DbError), + + #[error("order error is occurred")] + Order(#[from] OrderError), + + #[error("daemon server error is occurred")] + Server(#[from] ServerError), + + #[error("signer error is occurred")] + Signer(#[from] SignerError), + + #[error("failed to listen for the shutdown signal")] + ShutdownSignal(#[source] IoError), + + #[error("failed to initialize the asynchronous runtime")] + Runtime(#[source] IoError), + + #[error("failed to parse given filter directives for the logger ({0:?})")] + LoggerDirectives(String, #[source] ParseError), + + #[error("receiver account couldn't be parsed")] + RecipientAccount(#[from] CryptoError), + + #[error("fatal error is occurred")] + Fatal, + + #[error("found duplicate config record for the token {0:?}")] + DuplicateCurrency(String), +} + +#[derive(Debug, Error)] +pub enum SeedEnvError { + #[error("one of the `{OLD_SEED}*` variables has an invalid Unicode key")] + InvalidUnicodeOldSeedKey, + #[error("`{0}` variable contains an invalid Unicode text")] + InvalidUnicodeValue(Cow<'static, str>), + #[error("`{SEED}` isn't present")] + SeedNotPresent, +} + +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum ChainError { + // TODO: this should be prevented by typesafety + #[error("asset ID is missing")] + AssetId, + + #[error("asset ID isn't `u32`")] + AssetIdFormat, + + #[error("invalid assets for the chain {0:?}")] + AssetsInvalid(String), + + #[error("asset key has no parceable part")] + AssetKeyEmpty, + + #[error("asset key isn't single hash")] + AssetKeyNotSingleHash, + + #[error("asset metadata isn't a map")] + AssetMetadataPlain, + + #[error("unexpected assets metadata value structure")] + AssetMetadataUnexpected, + + #[error("wrong data type")] + AssetMetadataType, + + #[error("expected a map with a single entry, got multiple entries")] + AssetMetadataMapSize, + + #[error("asset balance format is unexpected")] + AssetBalanceFormat, + + #[error("no balance field in an asset record")] + AssetBalanceNotFound, + + #[error("format of the fetched Base58 prefix {0:?} isn't supported")] + Base58PrefixFormatNotSupported(String), + + #[error("Base58 prefixes in metadata ({meta:?}) and specs ({specs:?}) do not match.")] + Base58PrefixMismatch { specs: u16, meta: u16 }, + + #[error("unexpected block number format")] + BlockNumberFormat, + + #[error("unexpected block hash format")] + BlockHashFormat, + + #[error("unexpected block hash length")] + BlockHashLength, + + #[error("WS client error is occurred")] + Client(#[from] ClientError), + + #[error("threading error is occurred")] + Tokio(#[from] JoinError), + + #[error("format of fetched decimals ({0}) isn't supported")] + DecimalsFormatNotSupported(String), + + #[error("unexpected genesis hash format")] + GenesisHashFormat, + + #[error("...")] + MetadataFormat, + + #[error("...")] + MetadataNotDecodeable, + + #[error("no Base58 prefix is fetched as system properties or found in metadata")] + NoBase58Prefix, + + #[error("block number definition isn't found")] + NoBlockNumberDefinition, + + #[error("no decimals value is fetched")] + NoDecimals, + + #[error("metadata v15 isn't available through RPC")] + NoMetadataV15, + + #[error("metadata must start with the `meta` prefix")] + NoMetaPrefix, + + #[error("pallet isn't found")] + NoPallet, + + #[error("no pallets with a storage found")] + NoStorage, + + #[error("\"System\" pallet isn't found")] + NoSystem, + + #[error("no storage variants in the \"System\" pallet")] + NoStorageInSystem, + + #[error("no unit value is fetched")] + NoUnit, + + #[error("...")] + PropertiesFormat, + + #[error("...")] + RawMetadataNotDecodeable, + + #[error("format of the fetched unit ({0}) isn't supported")] + UnitFormatNotSupported(String), + + #[error("unexpected storage value format for the key \"{0:?}\"")] + StorageValueFormat(Value), + + //#[error("Chain returned zero for block time")] + //ZeroBlockTime, + + //#[error("Runtime api call response should be String, but received {0:?}")] + //StateCallResponse(Value), + + //#[error("Could not fetch BABE expected block time")] + //BabeExpectedBlockTime, + + //#[error("Aura slot duration could not be parsed as u64")] + //AuraSlotDurationFormat, + #[error("internal error is occurred")] // TODO this should be replaced by specific errors + Util(#[from] UtilError), + + #[error("invoice account couldn't be parsed")] + InvoiceAccount(#[from] CryptoError), + + #[error("chain {0:?} isn't found")] + InvalidChain(String), + + #[error("currency {0:?} isn't found")] + InvalidCurrency(String), + + #[error("chain manager dropped a message, probably due to a chain disconnect; maybe it should be sent again")] + MessageDropped, + + #[error("block subscription is terminated")] + BlockSubscriptionTerminated, + + #[error("metadata error is occurred")] + MetaVersionErrorPallets(#[from] MetaVersionErrorPallets), + + #[error("storage registry error is occurred")] + StorageRegistryError(#[from] StorageRegistryError), + + #[error("balance wasn't found")] + BalanceNotFound, + + #[error("storage query couldn't be formed")] + StorageQuery, + + #[error("events couldn't be fetched")] + EventsMissing, + + #[error("no events in this chain")] + EventsNonexistant, + + #[error("substrate parser error is occurred")] + ParserError(#[from] ParserError<()>), + + #[error("storage entry decoding error is occurred")] + StorageDecodeError(#[from] StorageError<()>), + + #[error("type registry error is occurred")] + RegistryError(#[from] RegistryError<()>), + + #[error("substrate constructor error is occurred")] + SubstrateConstructor(#[from] ErrorFixMe<(), RuntimeMetadataV15>), + + #[error("transaction isn't ready to be signed: {0:?}")] + TransactionNotSignable(String), + + #[error("signing was failed")] + Signer(#[from] SignerError), + + #[error("transaction couldn't be completed")] + NothingToSend, + + #[error("storage entry isn't a map")] + StorageEntryNotMap, + + #[error("storage entry map has more than one record")] + StorageEntryMapMultiple, + + #[error("storage key {0:?} isn't found")] + StorageKeyNotFound(String), + + #[error("storage key isn't `u32`")] + StorageKeyNotU32, + + #[error( + "RPC runs on an unexpected network: instead of {expected:?}, found {actual:?} at {rpc:?}" + )] + WrongNetwork { + expected: String, + actual: String, + rpc: String, + }, + + #[error("failed to parse JSON data from a block stream")] + Serde(#[from] JsonError), +} + +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum DbError { + #[error("currency key isn't found")] + CurrencyKeyNotFound, + + #[error("database engine isn't running")] + DbEngineDown, + + #[error("database internal error is occurred")] + DbInternalError(#[from] DatabaseError), + + #[error("failed to start the database service")] + DbStartError(DatabaseError), + + #[error("operating system related I/O error is occurred")] + IoError(#[from] IoError), + + #[error("database storage decoding error is occurred")] + CodecError(#[from] ScaleError), + + #[error("order {0:?} isn't found")] + OrderNotFound(String), + + #[error("order {0:?} was already paid")] + AlreadyPaid(String), + + #[error("order {0:?} isn't paid yet")] + NotPaid(String), + + #[error("there was already an attempt to withdraw order {0:?}")] + WithdrawalWasAttempted(String), +} + +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum OrderError { + #[error("invoice amount is less than the existential deposit")] + LessThanExistentialDeposit(f64), + + #[error("unknown currency")] + UnknownCurrency, + + #[error("order parameter is missing: {0:?}")] + MissingParameter(String), + + #[error("order parameter invalid: {0:?}")] + InvalidParameter(String), + + #[error("internal error is occurred")] + InternalError, +} + +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum ForceWithdrawalError { + #[error("order parameter is missing: {0:?}")] + MissingParameter(String), + + #[error("order parameter is invalid: {0:?}")] + InvalidParameter(String), + + #[error("withdrawal was failed: \"{0:?}\"")] + WithdrawalError(Box), +} + +#[derive(Debug, thiserror::Error)] +#[allow(clippy::module_name_repetitions)] +pub enum ServerError { + #[error("failed to bind the TCP listener to \"{0:?}\"")] + TcpListenerBind(SocketAddr), + + #[error("internal threading error is occurred")] + ThreadError, +} + +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum UtilError { + #[error("...")] + NotHex(NotHex), +} + +#[derive(Debug, Error)] +#[allow(clippy::module_name_repetitions)] +pub enum SignerError { + #[error("failed to read {0:?}")] + Env(String), + + #[error("signer is down")] + SignerDown, + + #[error("seed phrase is invalid")] + InvalidSeed(#[from] ErrorWordList), + + #[error("derivation was failed")] + InvalidDerivation(#[from] CryptoError), +} + +#[derive(Debug, Eq, PartialEq, thiserror::Error)] +pub enum NotHex { + #[error("block hash string isn't a valid hexadecimal")] + BlockHash, + + #[error("encoded metadata string isn't a valid hexadecimal")] + Metadata, + + #[error("encoded storage key string isn't a valid hexadecimal")] + StorageKey, + + #[error("encoded storage value string isn't a valid hexadecimal")] + StorageValue, +} + +mod pretty_cause { + use std::{ + error::Error, + fmt::{Display, Formatter, Result}, + }; + + const OVERLOAD: u16 = 9999; + + pub struct Wrapper<'a, T>(&'a T); + + pub trait PrettyCause { + fn pretty_cause(&self) -> Wrapper<'_, T>; + } + + impl PrettyCause for T { + fn pretty_cause(&self) -> Wrapper<'_, T> { + Wrapper(self) + } + } + + impl Display for Wrapper<'_, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let Some(cause) = self.0.source() else { + // If an error has no source, print nothing. + return Ok(()); + }; + + f.write_str("\n\nCaused by:")?; + + let Some(mut another_cause) = cause.source() else { + // If an error's source error has no source, print a cause in one line. + + f.write_str(" ")?; + + Display::fmt(&cause, f)?; + + return f.write_str("."); + }; + + // Otherwise, print a numbered list of error sources. + + let mut number = 0u16; + + print_cause(f, cause, number)?; + + loop { + if number == OVERLOAD { + break; + } + + number = number.saturating_add(1); + + print_cause(f, another_cause, number)?; + + if let Some(one_more_cause) = another_cause.source() { + another_cause = one_more_cause; + } else { + return Ok(()); + } + } + + loop { + print_cause(f, another_cause, shadow_rs::formatcp!(">{OVERLOAD}"))?; + + if let Some(one_more_cause) = another_cause.source() { + another_cause = one_more_cause; + } else { + break Ok(()); + } + } + } + } + + fn print_cause( + f: &mut Formatter<'_>, + cause: &(impl Error + ?Sized), + number: impl Display, + ) -> Result { + f.write_str("\n")?; + + write!(f, "{number:>5}")?; + + f.write_str(": ")?; + + Display::fmt(cause, f)?; + + f.write_str(".") + } + + #[cfg(test)] + mod tests { + use super::{PrettyCause, OVERLOAD}; + use std::{ + error::Error, + fmt::{Debug, Display, Formatter, Result, Write}, + }; + + #[test] + fn empty() { + assert!(TestError::empty().pretty_cause().to_string().is_empty()); + } + + #[test] + fn single() { + const MESSAGE: &str = "\n\nCaused by: TestError(0)."; + + assert_eq!(TestError::nested(1).pretty_cause().to_string(), MESSAGE); + } + + #[test] + fn multiple() { + const MESSAGE: &str = indoc::indoc! {" + \n\nCaused by: + 0: TestError(2). + 1: TestError(1). + 2: TestError(0)." + }; + + assert_eq!(TestError::nested(3).pretty_cause().to_string(), MESSAGE); + } + + #[test] + fn overload() { + let message = TestError::nested(OVERLOAD + 5).pretty_cause().to_string(); + let mut expected_message = String::with_capacity(message.len()); + + expected_message.push_str("\n\nCaused by:"); + + for number in 0..=OVERLOAD { + write!( + expected_message, + "\n{number:>5}: {}.", + TestError { + source: None, + number: OVERLOAD + 4 - number + } + ) + .unwrap(); + } + + expected_message.push_str(indoc::indoc! {" + \n>9999: TestError(3). + >9999: TestError(2). + >9999: TestError(1). + >9999: TestError(0)." + }); + + assert_eq!(message, expected_message); + } + + #[derive(Debug)] + struct TestError { + source: Option>, + number: u16, + } + + impl TestError { + fn empty() -> Self { + Self { + source: None, + number: 0, + } + } + + fn nested(nest: u16) -> Self { + let mut e = Self::empty(); + + for _ in 0..nest { + e = Self { + number: e.number.saturating_add(1), + source: Some(e.into()), + }; + } + + e + } + } + + impl Display for TestError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_tuple(stringify!(TestError)) + .field(&self.number) + .finish() + } + } + + impl Error for TestError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.source.as_ref().map(|e| e as _) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2c5fc9e..7f5d5d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,266 +1 @@ -use anyhow::{Context, Error, Result}; -use database::Database; -use env_logger::{Builder, Env}; -use environment_variables::{ - DATABASE, DECIMALS, DESTINATION, HOST, IN_MEMORY_DB, LOG, LOG_STYLE, OVERRIDE_RPC, RPC, SEED, -}; -use log::LevelFilter; -use rpc::Processor; -use std::{ - env::{self, VarError}, - future::Future, -}; -use subxt::{ - config::{ - signed_extensions::{ - AnyOf, ChargeTransactionPayment, CheckGenesis, CheckMortality, CheckNonce, - CheckSpecVersion, CheckTxVersion, - }, - Header, - }, - ext::sp_core::{crypto::AccountId32, Pair}, - Config, PolkadotConfig, -}; -use tokio::{ - signal, - sync::mpsc::{self, UnboundedSender}, -}; -use tokio_util::{sync::CancellationToken, task::TaskTracker}; - -mod database; -mod rpc; - -pub mod server; - -pub mod environment_variables { - pub const HOST: &str = "KALATORI_HOST"; - pub const SEED: &str = "KALATORI_SEED"; - pub const LOG: &str = "KALATORI_LOG"; - pub const LOG_STYLE: &str = "KALATORI_LOG_STYLE"; - pub const DATABASE: &str = "KALATORI_DATABASE"; - pub const RPC: &str = "KALATORI_RPC"; - pub const OVERRIDE_RPC: &str = "KALATORI_OVERRIDE_RPC"; - pub const IN_MEMORY_DB: &str = "KALATORI_IN_MEMORY_DB"; - pub const DECIMALS: &str = "KALATORI_DECIMALS"; - pub const DESTINATION: &str = "KALATORI_DESTINATION"; -} - -pub const DEFAULT_RPC: &str = "wss://westend-rpc.polkadot.io"; -pub const DEFAULT_DATABASE: &str = "database.redb"; -pub const DATABASE_VERSION: Version = 0; - -// https://github.com/paritytech/polkadot-sdk/blob/7c9fd83805cc446983a7698c7a3281677cf655c8/substrate/client/cli/src/config.rs#L50 -const SCANNER_TO_LISTENER_SWITCH_POINT: BlockNumber = 512; - -type OnlineClient = subxt::OnlineClient; -type Account = ::AccountId; -type BlockNumber = <::Header as Header>::Number; -type Hash = ::Hash; -// https://github.com/paritytech/polkadot-sdk/blob/a3dc2f15f23b3fd25ada62917bfab169a01f2b0d/substrate/bin/node/primitives/src/lib.rs#L43 -type Balance = u128; -// https://github.com/paritytech/subxt/blob/f06a95d687605bf826db9d83b2932a73a57b169f/subxt/src/config/signed_extensions.rs#L71 -type Nonce = u64; -// https://github.com/dtolnay/semver/blob/f9cc2df9415c880bd3610c2cdb6785ac7cad31ea/src/lib.rs#L163-L165 -type Version = u64; -// https://github.com/serde-rs/json/blob/0131ac68212e8094bd14ee618587d731b4f9a68b/src/number.rs#L29 -type Decimals = u64; - -struct RuntimeConfig; - -impl Config for RuntimeConfig { - type Hash = ::Hash; - type AccountId = AccountId32; - type Address = ::Address; - type Signature = ::Signature; - type Hasher = ::Hasher; - type Header = ::Header; - type ExtrinsicParams = AnyOf< - Self, - ( - CheckTxVersion, - CheckSpecVersion, - CheckNonce, - CheckGenesis, - CheckMortality, - ChargeTransactionPayment, - ), - >; - type AssetId = ::AssetId; -} - -#[doc(hidden)] -#[allow(clippy::too_many_lines)] -#[tokio::main] -pub async fn main() -> Result<()> { - let mut builder = Builder::new(); - - if cfg!(debug_assertions) { - builder.filter_level(LevelFilter::Debug) - } else { - builder - .filter_level(LevelFilter::Off) - .filter_module(server::MODULE, LevelFilter::Info) - .filter_module(rpc::MODULE, LevelFilter::Info) - .filter_module(database::MODULE, LevelFilter::Info) - .filter_module(env!("CARGO_PKG_NAME"), LevelFilter::Info) - } - .parse_env(Env::new().filter(LOG).write_style(LOG_STYLE)) - .init(); - - let host = env::var(HOST) - .with_context(|| format!("`{HOST}` isn't set"))? - .parse() - .with_context(|| format!("failed to convert `{HOST}` to a socket address"))?; - - let pair = Pair::from_string( - &env::var(SEED).with_context(|| format!("`{SEED}` isn't set"))?, - None, - ) - .with_context(|| format!("failed to generate a key pair from `{SEED}`"))?; - - let endpoint = env::var(RPC).or_else(|error| { - if error == VarError::NotPresent { - log::debug!( - "`{RPC}` isn't present, using the default value instead: \"{DEFAULT_RPC}\"." - ); - - Ok(DEFAULT_RPC.into()) - } else { - Err(error).context(format!("failed to read `{RPC}`")) - } - })?; - - let override_rpc = env::var_os(OVERRIDE_RPC).is_some(); - - let database_path = if env::var_os(IN_MEMORY_DB).is_none() { - Some(env::var(DATABASE).or_else(|error| { - if error == VarError::NotPresent { - log::debug!( - "`{DATABASE}` isn't present, using the default value instead: \"{DEFAULT_DATABASE}\"." - ); - - Ok(DEFAULT_DATABASE.into()) - } else { - Err(error).context(format!("failed to read `{DATABASE}`")) - } - })?) - } else { - if env::var_os(DATABASE).is_some() { - log::warn!( - "`{IN_MEMORY_DB}` is set along with `{DATABASE}`. The latter will be ignored." - ); - } - - None - }; - - let decimals = match env::var(DECIMALS) { - Ok(decimals) => decimals - .parse() - .map(Some) - .with_context(|| format!("failed to convert `{DECIMALS}` to a socket address")), - Err(VarError::NotPresent) => Ok(None), - Err(error) => Err(error).context(format!("failed to read `{DECIMALS}`")), - }?; - - let destination = match env::var(DESTINATION) { - Ok(destination) => Ok(Some( - AccountId32::try_from(hex::decode(&destination[2..])?.as_ref()) - .map_err(|()| anyhow::anyhow!("unknown destination address length"))?, - )), - Err(VarError::NotPresent) => Ok(None), - Err(error) => Err(error).context(format!("failed to read `{DESTINATION}`")), - }?; - - log::info!( - "Kalatori {} by {} is starting...", - env!("CARGO_PKG_VERSION"), - env!("CARGO_PKG_AUTHORS") - ); - - let shutdown_notification = CancellationToken::new(); - let (error_tx, mut error_rx) = mpsc::unbounded_channel(); - - let (api_config, endpoint_properties, updater) = - rpc::prepare(endpoint, decimals, shutdown_notification.clone()) - .await - .context("failed to prepare the node module")?; - - let (database, last_saved_block) = Database::initialise( - database_path, - override_rpc, - pair, - endpoint_properties, - destination, - ) - .context("failed to initialise the database module")?; - - let processor = Processor::new(api_config, database.clone(), shutdown_notification.clone()) - .context("failed to initialise the RPC module")?; - - let server = server::new(shutdown_notification.clone(), host, database) - .await - .context("failed to initialise the server module")?; - - let task_tracker = TaskTracker::new(); - - task_tracker.close(); - - task_tracker.spawn(shutdown( - shutdown_listener(shutdown_notification.clone()), - error_tx.clone(), - )); - task_tracker.spawn(shutdown(updater.ignite(), error_tx.clone())); - task_tracker.spawn(shutdown( - processor.ignite(last_saved_block, task_tracker.clone(), error_tx.clone()), - error_tx, - )); - task_tracker.spawn(server); - - while let Some(error) = error_rx.recv().await { - log::error!("Received a fatal error!\n{error:?}"); - - if !shutdown_notification.is_cancelled() { - log::info!("Initialising the shutdown..."); - - shutdown_notification.cancel(); - } - } - - task_tracker.wait().await; - - log::info!("Goodbye!"); - - Ok(()) -} - -async fn shutdown_listener(shutdown_notification: CancellationToken) -> Result<&'static str> { - tokio::select! { - biased; - signal = signal::ctrl_c() => { - signal.context("failed to listen for the shutdown signal")?; - - // Print shutdown log messages on the next line after the Control-C command. - println!(); - - log::info!("Received the shutdown signal. Initialising the shutdown..."); - - shutdown_notification.cancel(); - - Ok("The shutdown signal listener is shut down.") - } - () = shutdown_notification.cancelled() => { - Ok("The shutdown signal listener is shut down.") - } - } -} - -async fn shutdown( - task: impl Future>, - error_tx: UnboundedSender, -) { - match task.await { - Ok(shutdown_message) => log::info!("{shutdown_message}"), - Err(error) => error_tx.send(error).unwrap(), - } -} +pub mod definitions; diff --git a/src/main.rs b/src/main.rs index 5bef903..625229a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,711 @@ -use anyhow::Result; +use clap::Parser; +use std::{borrow::Cow, future::Future, process::ExitCode, str, sync::Arc}; +use substrate_crypto_light::common::{AccountId32, AsBase58}; +use tokio::{ + runtime::Runtime, + sync::{ + mpsc::{self, UnboundedReceiver, UnboundedSender}, + oneshot, RwLock, + }, + task::JoinHandle, +}; +use tokio_util::{sync::CancellationToken, task}; +use tracing::Level; -fn main() -> Result<()> { - kalatori::main() +mod callback; +mod chain; +mod database; +mod definitions; +mod error; +mod server; +mod signer; +mod state; +mod utils; + +use arguments::{CliArgs, Config, SeedEnvVars, DATABASE_DEFAULT}; +use chain::ChainManager; +use database::ConfigWoChains; +use error::{Error, PrettyCause}; +use shutdown::{ShutdownNotification, ShutdownReason}; +use signer::Signer; +use state::State; + +fn main() -> ExitCode { + let shutdown_notification = ShutdownNotification::new(); + + // Sets the panic hook to print directly to the standard error because the logger isn't + // initialized yet. + shutdown::set_panic_hook(|panic| eprintln!("{panic}"), shutdown_notification.clone()); + + match try_main(shutdown_notification) { + Ok(failed) => match *failed.blocking_read() { + ShutdownReason::UserRequested => { + tracing::info!("Goodbye!"); + + ExitCode::SUCCESS + } + ShutdownReason::UnrecoverableError => { + tracing::error!("Badbye! The daemon's shut down with errors."); + + ExitCode::FAILURE + } + }, + Err(error) => { + let print = |message| { + if tracing::event_enabled!(Level::ERROR) { + tracing::error!("{message}"); + } else { + eprintln!("{message}"); + } + }; + + print(format_args!( + "Badbye! The daemon's got a fatal error:\n {error}.{}", + error.pretty_cause() + )); + + ExitCode::FAILURE + } + } +} + +fn try_main( + shutdown_notification: ShutdownNotification, +) -> Result>, Error> { + let cli_args = CliArgs::parse(); + + logger::initialize(cli_args.log)?; + shutdown::set_panic_hook( + |panic| tracing::error!("{panic}"), + shutdown_notification.clone(), + ); + + let seed_env_vars = SeedEnvVars::parse()?; + let config = Config::parse(cli_args.config)?; + + Runtime::new() + .map_err(Error::Runtime)? + .block_on(async_try_main( + shutdown_notification, + cli_args.recipient, + cli_args.remark, + config, + seed_env_vars, + )) +} + +async fn async_try_main( + shutdown_notification: ShutdownNotification, + recipient_string: String, + remark: Option, + config: Config, + seed_env_vars: SeedEnvVars, +) -> Result>, Error> { + let database_path = if config.in_memory_db { + if config.database.is_some() { + tracing::warn!( + "`database` is set in the config but ignored because `in_memory_db` is \"true\"" + ); + } + + None + } else { + Some(config.database.unwrap_or_else(|| DATABASE_DEFAULT.into())) + }; + + let instance_id = String::from("TODO: add unique ID and save it in db"); + + // Start services + + tracing::info!( + "Kalatori {} by {} is starting on {}...", + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_AUTHORS"), + config.host, + ); + + let (task_tracker, error_rx) = TaskTracker::new(); + + let recipient = AccountId32::from_base58_string(&recipient_string) + .map_err(Error::RecipientAccount)? + .0; + + let signer = Signer::init(recipient, task_tracker.clone(), seed_env_vars.seed)?; + + let db = + database::Database::init(database_path, task_tracker.clone(), config.account_lifetime)?; + + let (cm_tx, cm_rx) = oneshot::channel(); + + let state = State::initialise( + signer.interface(), + ConfigWoChains { + recipient, + debug: config.debug, + remark, + //depth: config.depth, + }, + db, + cm_rx, + instance_id, + task_tracker.clone(), + shutdown_notification.token.clone(), + )?; + + cm_tx + .send(ChainManager::ignite( + config.chain, + state.interface(), + signer.interface(), + task_tracker.clone(), + shutdown_notification.token.clone(), + )?) + .map_err(|_| Error::Fatal)?; + + let server = server::new( + shutdown_notification.token.clone(), + config.host, + state.interface(), + ) + .await?; + + task_tracker.spawn("the server module", server); + + let shutdown_completed = CancellationToken::new(); + let mut shutdown_listener = tokio::spawn(shutdown::listener( + shutdown_notification.token.clone(), + shutdown_completed.clone(), + )); + + // Main loop + + Ok(tokio::select! { + biased; + reason = task_tracker.wait_with_notification(error_rx, shutdown_notification) => { + shutdown_completed.cancel(); + shutdown_listener.await.expect("shutdown listener shouldn't panic")?; + + reason + } + error = &mut shutdown_listener => { + return Err( + error + .expect("shutdown listener shouldn't panic") + .expect_err("shutdown listener should only complete on errors here") + ); + } + }) +} + +#[derive(Clone)] +struct TaskTracker { + inner: task::TaskTracker, + error_tx: UnboundedSender<(Cow<'static, str>, Error)>, +} + +impl TaskTracker { + fn new() -> (Self, UnboundedReceiver<(Cow<'static, str>, Error)>) { + let (error_tx, error_rx) = mpsc::unbounded_channel(); + let inner = task::TaskTracker::new(); + + inner.close(); + + (Self { inner, error_tx }, error_rx) + } + + fn spawn( + &self, + name: impl Into> + Send + 'static, + task: impl Future, Error>> + Send + 'static, + ) -> JoinHandle<()> { + let error_tx = self.error_tx.clone(); + + self.inner.spawn(async move { + match task.await { + Ok(shutdown_message) => { + if !shutdown_message.is_empty() { + tracing::info!("{shutdown_message}"); + } + } + Err(error) => error_tx.send((name.into(), error)).unwrap(), + } + }) + } + + async fn wait_with_notification( + self, + mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, + shutdown_notification: ShutdownNotification, + ) -> Arc> { + // `self` holds the last `error_tx`, so we need to drop it; otherwise it'll create a + // deadlock on `error_rx.recv()`. + drop(self.error_tx); + + let mut failed = false; + + while let Some((from, error)) = error_rx.recv().await { + tracing::error!( + "Received a fatal error from {from}:\n {error:?}.{}", + error.pretty_cause() + ); + + if failed || !shutdown_notification.is_ignited() { + tracing::info!("Initialising the shutdown..."); + + failed = true; + + shutdown_notification.ignite().await; + } + } + + self.inner.wait().await; + + shutdown_notification.reason + } + + /* async fn try_wait( + self, + mut error_rx: UnboundedReceiver<(Cow<'static, str>, Error)>, + ) -> Result<(), Error> { + // `self` holds the last `error_tx`, so we need to drop it; otherwise it'll create a + // deadlock on `error_rx.recv()`. + drop(self.error_tx); + + if let Some((from, error)) = error_rx.recv().await { + return Err(error)?; + } + + self.inner.wait().await; + + Ok(()) + } */ +} + +mod arguments { + use crate::{ + definitions::{api_v2::Timestamp, Chain}, + error::SeedEnvError, + logger, Error, + }; + use ahash::AHashMap; + use clap::{Arg, ArgAction, Parser}; + use serde::Deserialize; + use std::{ + env, fs, + net::{IpAddr, Ipv4Addr, SocketAddr}, + str, + }; + use toml_edit::de; + + shadow_rs::shadow!(shadow); + + use shadow::{BUILD_TIME_3339, RUST_VERSION, SHORT_COMMIT}; + + macro_rules! env_var_prefix { + ($var:literal) => { + concat!("KALATORI_", $var) + }; + } + + pub const SEED: &str = env_var_prefix!("SEED"); + pub const OLD_SEED: &str = env_var_prefix!("OLD_SEED_"); + + const SOCKET_DEFAULT: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 16726); + pub const DATABASE_DEFAULT: &str = "kalatori.db"; + + #[derive(Parser)] + #[command( + about, + disable_help_flag(true), + arg( + Arg::new("help") + .short('h') + .long("help") + .action(ArgAction::Help) + .help("Print this text.") + ), + after_help(concat!( + "`SEED` is a required environment variable.\n\nMore documentation can be found at ", + env!("CARGO_PKG_REPOSITORY"), + ".\n\nCopyright (C) 2024 ", + clap::crate_authors!() + )), + disable_version_flag(true), + version, + arg( + Arg::new("version") + .short('V') + .long("version") + .action(ArgAction::Version) + .help("Print the daemon version (and its build metadata).") + ), + long_version(shadow_rs::formatcp!( + "{} ({SHORT_COMMIT})\n\nBuilt on {}\nwith {RUST_VERSION}.", + clap::crate_version!(), + // Replaces the local offset part with the "00:00" (aka "Z") UTC offset. + shadow_rs::str_splice!( + BUILD_TIME_3339, + // TODO: Use `checked_sub()` with `expect()` instead. + // https://github.com/rust-lang/rust/issues/67441 + BUILD_TIME_3339.len().saturating_sub(6).., + "Z" + ).output, + )), + )] + pub struct CliArgs { + #[arg( + short, + long, + env(env_var_prefix!("CONFIG")), + value_name("PATH"), + default_value("configs/polkadot.toml") + )] + pub config: String, + + #[arg( + short, + long, + env(env_var_prefix!("LOG")), + value_name("DIRECTIVES"), + default_value(logger::default_filter()), + default_missing_value(""), + num_args(0..=1), + require_equals(true), + )] + pub log: String, + + #[arg(long, env(env_var_prefix!("REMARK")), visible_alias("rmrk"), value_name("STRING"))] + pub remark: Option, + + #[arg(short, long, env(env_var_prefix!("RECIPIENT")), value_name("HEX/SS58 ADDRESS"))] + pub recipient: String, + } + + pub struct SeedEnvVars { + pub seed: String, + pub old_seeds: AHashMap, + } + + impl SeedEnvVars { + pub fn parse() -> Result { + const SEED_BYTES: &[u8] = SEED.as_bytes(); + + let mut seed_option = None; + let mut old_seeds = AHashMap::new(); + + for (raw_key, raw_value) in env::vars_os() { + match raw_key.as_encoded_bytes() { + SEED_BYTES => { + env::remove_var(raw_key); + + seed_option = { + Some( + raw_value + .into_string() + .map_err(|_| SeedEnvError::InvalidUnicodeValue(SEED.into()))?, + ) + }; + } + raw_key_bytes => { + // TODO: Use `OsStr::slice_encoded_bytes()` instead. + // https://github.com/rust-lang/rust/issues/118485 + if let Some(stripped_raw_key) = + raw_key_bytes.strip_prefix(OLD_SEED.as_bytes()) + { + env::remove_var(&raw_key); + + let key = str::from_utf8(stripped_raw_key) + .map_err(|_| SeedEnvError::InvalidUnicodeOldSeedKey)?; + let value = + raw_value.to_str().ok_or(SeedEnvError::InvalidUnicodeValue( + format!("{OLD_SEED}{key}").into(), + ))?; + + old_seeds.insert(key.into(), value.into()); + } + } + } + } + + Ok(Self { + seed: seed_option.ok_or(SeedEnvError::SeedNotPresent)?, + old_seeds, + }) + } + } + + /// User-supplied settings through the config file. + #[derive(Deserialize)] + #[serde(rename_all = "kebab-case")] + pub struct Config { + pub account_lifetime: Timestamp, + #[serde(default = "default_host")] + pub host: SocketAddr, + pub database: Option, + pub debug: Option, + #[serde(default)] + pub in_memory_db: bool, + pub chain: Vec, + } + + impl Config { + pub fn parse(path: String) -> Result { + let unparsed_config = + fs::read_to_string(&path).map_err(|e| Error::ConfigFileRead(path, e))?; + + de::from_str(&unparsed_config).map_err(Into::into) + } + } + + fn default_host() -> SocketAddr { + SOCKET_DEFAULT + } +} + +mod logger { + use crate::{callback, chain, database, server, Error}; + use tracing_subscriber::{fmt::time::UtcTime, EnvFilter}; + + const TARGETS: &[&str] = &[ + callback::MODULE, + database::MODULE, + chain::MODULE, + server::MODULE, + env!("CARGO_PKG_NAME"), + ]; + const COMMA: &str = ","; + const INFO: &str = "=info"; + const OFF: &str = "off"; + + pub fn initialize(directives: String) -> Result<(), Error> { + let filter = + EnvFilter::try_new(&directives).map_err(|e| Error::LoggerDirectives(directives, e))?; + + tracing_subscriber::fmt() + .with_timer(UtcTime::rfc_3339()) + .with_env_filter(filter) + .init(); + + Ok(()) + } + + fn default_filter_capacity() -> usize { + OFF.len().saturating_add( + TARGETS + .iter() + .map(|module| { + COMMA + .len() + .saturating_add(module.len()) + .saturating_add(INFO.len()) + }) + .sum(), + ) + } + + pub fn default_filter() -> String { + let mut filter = String::with_capacity(default_filter_capacity()); + + filter.push_str(OFF); + + for target in TARGETS { + filter.push_str(COMMA); + filter.push_str(target); + filter.push_str(INFO); + } + + filter + } + + #[cfg(test)] + mod tests { + use tracing_subscriber::EnvFilter; + + #[test] + fn default_filter_capacity() { + assert_eq!( + super::default_filter().len(), + super::default_filter_capacity() + ); + } + + #[test] + fn default_filter_is_valid() { + assert!(EnvFilter::try_new(super::default_filter()).is_ok()); + } + } +} + +mod shutdown { + use crate::Error; + use std::{ + fmt::{Display, Formatter, Result as FmtResult}, + io::Result as IoResult, + panic::{self, PanicInfo}, + process, + sync::Arc, + time::Duration, + }; + use tokio::{signal, sync::RwLock, time}; + use tokio_util::sync::CancellationToken; + + #[derive(Clone)] + #[allow(clippy::module_name_repetitions)] + pub struct ShutdownNotification { + pub token: CancellationToken, + pub reason: Arc>, + } + + impl ShutdownNotification { + pub fn new() -> Self { + Self { + token: CancellationToken::new(), + reason: Arc::new(RwLock::new(ShutdownReason::UserRequested)), + } + } + + pub fn is_ignited(&self) -> bool { + self.token.is_cancelled() + } + + pub async fn ignite(&self) { + *self.reason.write().await = ShutdownReason::UnrecoverableError; + self.token.cancel(); + } + } + + pub async fn listener( + shutdown_notification: CancellationToken, + shutdown_completed: CancellationToken, + ) -> Result<(), Error> { + const TIP_TIMEOUT_SECS: u64 = 30; + + tokio::select! { + biased; + result = signal::ctrl_c() => { + process_signal(result)?; + + tracing::info!("Received the shutdown signal. Initialising the shutdown..."); + + shutdown_notification.cancel(); + } + () = shutdown_notification.cancelled() => {} + } + + let shutdown_completed_clone = shutdown_completed.clone(); + let tip = tokio::spawn(async move { + tokio::select! { + biased; + () = shutdown_completed_clone.cancelled() => {} + () = time::sleep(Duration::from_secs(TIP_TIMEOUT_SECS)) => { + tracing::warn!( + "Send the shutdown signal one more time to kill the daemon instead of waiting for the graceful shutdown." + ); + } + } + }); + + tokio::select! { + biased; + () = shutdown_completed.cancelled() => {} + result = signal::ctrl_c() => { + process_signal(result)?; + + tracing::info!("Received the second shutdown signal. Killing the daemon..."); + + // TODO: Use `ExitCode::exit_process()` instead. + // https://github.com/rust-lang/rust/issues/97100 + process::abort() + } + } + + tip.await.expect("tip task shouldn't panic"); + + tracing::info!("The shutdown signal listener is shut down."); + + Ok(()) + } + + fn process_signal(result: IoResult<()>) -> Result<(), Error> { + result.map_err(Error::ShutdownSignal)?; + + // Print shutdown log messages on the next line after the Control-C command. + println!(); + + Ok(()) + } + + #[derive(Clone, Copy)] + #[allow(clippy::module_name_repetitions)] + pub enum ShutdownReason { + UserRequested, + UnrecoverableError, + } + + pub fn set_panic_hook( + print: impl Fn(PrettyPanic<'_>) + Send + Sync + 'static, + shutdown_notification: ShutdownNotification, + ) { + panic::set_hook(Box::new(move |panic_info| { + let reason = *shutdown_notification.reason.blocking_read(); + + let first = match reason { + ShutdownReason::UserRequested => false, + ShutdownReason::UnrecoverableError => { + *shutdown_notification.reason.blocking_write() = + ShutdownReason::UnrecoverableError; + + true + } + }; + + print(PrettyPanic { panic_info, first }); + + shutdown_notification.token.cancel(); + })); + } + + pub struct PrettyPanic<'a> { + panic_info: &'a PanicInfo<'a>, + first: bool, + } + + // It looks like it's impossible to acquire `PanicInfo` outside of `panic::set_hook`, which + // could alter execution of other unit tests, so, without mocking the `panic_info` field, + // there's no way to test the `Display`ing. + impl Display for PrettyPanic<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.write_str("A panic detected")?; + + if let Some(location) = self.panic_info.location() { + f.write_str(" at ")?; + location.fmt(f)?; + } + + let payload = self.panic_info.payload(); + let message_option = match payload.downcast_ref() { + Some(string) => Some(*string), + None => payload.downcast_ref::().map(|string| &string[..]), + }; + + if let Some(panic_message) = message_option { + f.write_str(":\n ")?; + f.write_str(panic_message)?; + } + + f.write_str(".")?; + + // Print the report request only on the first panic. + + if self.first { + f.write_str(concat!( + "\n\nThis is a bug. Please report it at ", + env!("CARGO_PKG_REPOSITORY"), + "/issues." + ))?; + } + + Ok(()) + } + } } diff --git a/src/rpc.rs b/src/rpc.rs deleted file mode 100644 index 0011868..0000000 --- a/src/rpc.rs +++ /dev/null @@ -1,1199 +0,0 @@ -use crate::{ - database::{Database, Invoice, InvoiceStatus, ReadInvoices}, - shutdown, Account, Balance, BlockNumber, Decimals, Hash, Nonce, OnlineClient, RuntimeConfig, - DECIMALS, SCANNER_TO_LISTENER_SWITCH_POINT, -}; -use anyhow::{Context, Result}; -use reconnecting_jsonrpsee_ws_client::ClientBuilder; -use serde::{Deserialize, Deserializer}; -use std::{ - collections::{hash_map::Entry, HashMap}, - error::Error, - fmt::{self, Arguments, Display, Formatter, Write}, - sync::Arc, -}; -use subxt::{ - backend::{ - legacy::{LegacyBackend, LegacyRpcMethods}, - rpc::{RpcClient, RpcSubscription}, - Backend, BackendExt, RuntimeVersion, - }, - blocks::{Block, BlocksClient}, - config::{ - signed_extensions::{ChargeTransactionPaymentParams, CheckMortalityParams}, - Header, - }, - constants::ConstantsClient, - dynamic::{self, Value}, - error::RpcError, - ext::{ - futures::TryFutureExt, - scale_decode::DecodeAsType, - scale_value::{self, At}, - sp_core::{ - crypto::{AccountId32, Ss58AddressFormat}, - sr25519::Pair, - }, - }, - storage::StorageClient, - tx::{PairSigner, SubmittableExtrinsic, TxClient}, - Config, Metadata, -}; -use tokio::sync::{mpsc::UnboundedSender, RwLock}; -use tokio_util::{sync::CancellationToken, task::TaskTracker}; - -pub const MODULE: &str = module_path!(); - -const MAX_BLOCK_NUMBER_ERROR: &str = "block number type overflow is occurred"; -const BLOCK_NONCE_ERROR: &str = "failed to fetch an account nonce by the scanner client"; - -// Pallets - -const SYSTEM: &str = "System"; -const BALANCES: &str = "Balances"; -const UTILITY: &str = "Utility"; - -async fn fetch_best_block(methods: &LegacyRpcMethods) -> Result { - methods - .chain_get_block_hash(None) - .await - .context("failed to get the best block hash")? - .context("received nothing after requesting the best block hash") -} - -async fn fetch_api_runtime( - methods: &LegacyRpcMethods, - backend: &impl Backend, -) -> Result<(Metadata, RuntimeVersion)> { - let best_block = fetch_best_block(methods).await?; - - Ok(( - fetch_metadata(backend, best_block) - .await - .context("failed to fetch metadata")?, - methods - .state_get_runtime_version(Some(best_block)) - .await - .map(|runtime_version| RuntimeVersion { - spec_version: runtime_version.spec_version, - transaction_version: runtime_version.transaction_version, - }) - .context("failed to fetch the runtime version")?, - )) -} - -async fn fetch_metadata(backend: &impl Backend, at: Hash) -> Result { - const LATEST_SUPPORTED_METADATA_VERSION: u32 = 15; - - backend - .metadata_at_version(LATEST_SUPPORTED_METADATA_VERSION, at) - .or_else(|error| async { - if let subxt::Error::Rpc(RpcError::ClientError(_)) | subxt::Error::Other(_) = error { - backend.legacy_metadata(at).await - } else { - Err(error) - } - }) - .await - .map_err(Into::into) -} - -fn fetch_constant( - constants: &ConstantsClient, - constant: (&str, &str), -) -> Result { - constants - .at(&dynamic::constant(constant.0, constant.1)) - .with_context(|| format!("failed to get the constant {constant:?}"))? - .as_type() - .with_context(|| format!("failed to decode the constant {constant:?}")) -} - -pub struct ChainProperties { - pub address_format: Ss58AddressFormat, - pub existential_deposit: Balance, - pub decimals: Decimals, - pub block_hash_count: BlockNumber, -} - -impl ChainProperties { - fn fetch_only_constants( - constants: &ConstantsClient, - decimals: Decimals, - ) -> Result { - const ADDRESS_PREFIX: (&str, &str) = (SYSTEM, "SS58Prefix"); - const EXISTENTIAL_DEPOSIT: (&str, &str) = (BALANCES, "ExistentialDeposit"); - const BLOCK_HASH_COUNT: (&str, &str) = (SYSTEM, "BlockHashCount"); - - Ok(Self { - address_format: Ss58AddressFormat::custom(fetch_constant(constants, ADDRESS_PREFIX)?), - existential_deposit: fetch_constant(constants, EXISTENTIAL_DEPOSIT)?, - block_hash_count: fetch_constant(constants, BLOCK_HASH_COUNT)?, - decimals, - }) - } - - async fn fetch( - constants: &ConstantsClient, - methods: &LegacyRpcMethods, - ) -> Result { - const DECIMALS_KEY: &str = "tokenDecimals"; - - let system_properties = methods - .system_properties() - .await - .context("failed to get the chain system properties")?; - let encoded_decimals = system_properties - .get(DECIMALS_KEY) - .with_context(|| format!( - "{DECIMALS_KEY:?} wasn't found in a response of the `system_properties` RPC call, set `{DECIMALS}` to set the decimal places number manually" - ))?; - let decimals = encoded_decimals - .as_u64() - .with_context(|| format!( - "failed to decode the decimal places number, expected a positive integer, got \"{encoded_decimals}\"" - ))?; - - Self::fetch_only_constants(constants, decimals) - } -} - -pub struct ApiConfig { - api: Arc, - methods: Arc>, - backend: Arc>, -} - -pub struct EndpointProperties { - pub url: CheckedUrl, - pub chain: Arc>, -} - -pub struct CheckedUrl(String); - -impl CheckedUrl { - pub fn get(self) -> String { - self.0 - } -} - -pub async fn prepare( - url: String, - decimals_option: Option, - shutdown_notification: CancellationToken, -) -> Result<(ApiConfig, EndpointProperties, Updater)> { - // TODO: - // The current reconnecting client implementation automatically restores all subscriptions, - // including unrecoverable ones, losing all notifications! For now, it shouldn't affect the - // daemon, but may in the future, so we should consider creating our own implementation. - let rpc = RpcClient::new( - ClientBuilder::new() - .build(url.clone()) - .await - .context("failed to construct the RPC client")?, - ); - - log::info!("Connected to an RPC server at \"{url}\"."); - - let methods = Arc::new(LegacyRpcMethods::new(rpc.clone())); - let backend = Arc::new(LegacyBackend::new(rpc)); - - let (metadata, runtime_version) = fetch_api_runtime(&methods, &*backend) - .await - .context("failed to fetch the runtime of the API client")?; - let genesis_hash = methods - .genesis_hash() - .await - .context("failed to get the genesis hash")?; - let api = Arc::new( - OnlineClient::from_backend_with(genesis_hash, runtime_version, metadata, backend.clone()) - .context("failed to construct the API client")?, - ); - let constants = api.constants(); - - let (properties_result, decimals_set) = if let Some(decimals) = decimals_option { - ( - ChainProperties::fetch_only_constants(&constants, decimals), - true, - ) - } else { - (ChainProperties::fetch(&constants, &methods).await, false) - }; - let properties = properties_result?; - - log::info!( - "Chain properties:\n\ - Decimal places number: {}.\n\ - Address format: \"{}\" ({}).\n\ - Existential deposit: {}.\n\ - Block hash count: {}.", - properties.decimals, - properties.address_format, - properties.address_format.prefix(), - properties.existential_deposit, - properties.block_hash_count - ); - - let arc_properties = Arc::new(RwLock::const_new(properties)); - - Ok(( - ApiConfig { - api: api.clone(), - methods: methods.clone(), - backend: backend.clone(), - }, - EndpointProperties { - url: CheckedUrl(url), - chain: arc_properties.clone(), - }, - Updater { - methods, - backend, - api, - constants, - shutdown_notification, - properties: arc_properties, - decimals_set, - }, - )) -} - -pub struct Updater { - methods: Arc>, - backend: Arc>, - api: Arc, - constants: ConstantsClient, - shutdown_notification: CancellationToken, - properties: Arc>, - decimals_set: bool, -} - -impl Updater { - pub async fn ignite(self) -> Result<&'static str> { - loop { - let mut updates = self - .backend - .stream_runtime_version() - .await - .context("failed to get the runtime updates stream")?; - - if let Some(current_runtime_version_result) = updates.next().await { - let current_runtime_version = current_runtime_version_result - .context("failed to decode the current runtime version")?; - - // The updates stream is always returns the current runtime version in the first - // item. We don't skip it though because during a connection loss the runtime can be - // updated, hence this condition will catch this. - if self.api.runtime_version() != current_runtime_version { - self.process_update() - .await - .context("failed to process the first API client update")?; - } - - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Ok("The API client updater is shut down."); - } - runtime_version = updates.next() => { - if runtime_version.is_some() { - self.process_update() - .await - .context( - "failed to process an update for the API client" - )?; - } else { - break; - } - } - } - } - } - - log::warn!( - "Lost the connection while listening the endpoint for API client runtime updates. Retrying..." - ); - } - } - - async fn process_update(&self) -> Result<()> { - // We don't use the runtime version from the updates stream because it doesn't provide the - // best block hash, so we fetch it ourselves (in `fetch_api_runtime`) and use it to make sure - // that metadata & the runtime version are from the same block. - let (metadata, runtime_version) = fetch_api_runtime(&self.methods, &*self.backend) - .await - .context("failed to fetch a new runtime for the API client")?; - - self.api.set_metadata(metadata); - self.api.set_runtime_version(runtime_version); - - let (mut current_properties, new_properties_result) = if self.decimals_set { - let current_properties = self.properties.write().await; - let new_properties_result = - ChainProperties::fetch_only_constants(&self.constants, current_properties.decimals); - - (current_properties, new_properties_result) - } else { - ( - self.properties.write().await, - ChainProperties::fetch(&self.constants, &self.methods).await, - ) - }; - let new_properties = new_properties_result?; - - let mut changed = String::new(); - let mut add_change = |message: Arguments<'_>| { - changed.write_fmt(message).unwrap(); - }; - - if new_properties.address_format != current_properties.address_format { - add_change(format_args!( - "\nOld {value}: \"{}\" ({}). New {value}: \"{}\" ({}).", - current_properties.address_format, - current_properties.address_format.prefix(), - new_properties.address_format, - new_properties.address_format.prefix(), - value = "address format", - )); - } - - if new_properties.existential_deposit != current_properties.existential_deposit { - add_change(format_args!( - "\nOld {value}: {}. New {value}: {}.", - current_properties.existential_deposit, - new_properties.existential_deposit, - value = "existential deposit" - )); - } - - if new_properties.decimals != current_properties.decimals { - add_change(format_args!( - "\nOld {value}: {}. New {value}: {}.", - current_properties.decimals, - new_properties.decimals, - value = "decimal places number" - )); - } - - if new_properties.block_hash_count != current_properties.block_hash_count { - add_change(format_args!( - "\nOld {value}: {}. New {value}: {}.", - current_properties.block_hash_count, - new_properties.block_hash_count, - value = "block hash count" - )); - } - - if !changed.is_empty() { - *current_properties = new_properties; - - log::warn!("The chain properties has been changed:{changed}"); - } - - log::info!("A runtime update has been found and applied for the API client."); - - Ok(()) - } -} - -#[derive(Debug)] -struct Shutdown; - -impl Error for Shutdown {} - -// Not used, but required for the `anyhow::Context` trait. -impl Display for Shutdown { - fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { - unimplemented!() - } -} - -struct Api { - tx: TxClient, - blocks: BlocksClient, -} - -struct Scanner { - client: OnlineClient, - blocks: BlocksClient, - storage: StorageClient, -} - -struct ProcessorFinalized { - database: Arc, - client: OnlineClient, - backend: Arc>, - methods: Arc>, - shutdown_notification: CancellationToken, -} - -impl ProcessorFinalized { - async fn finalized_head_number_and_hash(&self) -> Result<(BlockNumber, Hash)> { - let head_hash = self - .methods - .chain_get_finalized_head() - .await - .context("failed to get the finalized head hash")?; - let head = self - .methods - .chain_get_block(Some(head_hash)) - .await - .context("failed to get the finalized head")? - .context("received nothing after requesting the finalized head")?; - - Ok((head.block.header.number, head_hash)) - } - - pub async fn ignite(self) -> Result<&'static str> { - self.execute().await.or_else(|error| { - error - .downcast() - .map(|Shutdown| "The RPC module is shut down.") - }) - } - - async fn execute(mut self) -> Result<&'static str> { - let write_tx = self.database.write()?; - let mut write_invoices = write_tx.invoices()?; - let (mut finalized_number, finalized_hash) = self.finalized_head_number_and_hash().await?; - - self.set_client_metadata(finalized_hash).await?; - - // TODO: - // Design a new DB format to store unpaid accounts in a separate table. - - for invoice_result in self.database.read()?.invoices()?.try_iter()? { - let invoice = invoice_result?; - - match invoice.1.value().status { - InvoiceStatus::Unpaid(price) => { - if self - .balance(finalized_hash, &Account::from(*invoice.0.value())) - .await? - >= price - { - let mut changed_invoice = invoice.1.value(); - - changed_invoice.status = InvoiceStatus::Paid(price); - - log::debug!("background scan {changed_invoice:?}"); - - write_invoices - .save(&Account::from(*invoice.0.value()), &changed_invoice)?; - } - } - InvoiceStatus::Paid(_) => continue, - } - } - - drop(write_invoices); - - write_tx.commit()?; - - let mut subscription = self.finalized_heads().await?; - - loop { - self.process_finalized_heads(subscription, &mut finalized_number) - .await?; - - log::warn!("Lost the connection while processing finalized heads. Retrying..."); - - subscription = self - .finalized_heads() - .await - .context("failed to update the subscription while processing finalized heads")?; - } - } - - async fn process_skipped( - &self, - next_unscanned: &mut BlockNumber, - head: BlockNumber, - ) -> Result<()> { - for skipped_number in *next_unscanned..head { - if self.shutdown_notification.is_cancelled() { - return Err(Shutdown.into()); - } - - let skipped_hash = self - .methods - .chain_get_block_hash(Some(skipped_number.into())) - .await - .context("failed to get the hash of a skipped block")? - .context("received nothing after requesting the hash of a skipped block")?; - - self.process_block(skipped_number, skipped_hash).await?; - } - - *next_unscanned = head; - - Ok(()) - } - - async fn process_finalized_heads( - &mut self, - mut subscription: RpcSubscription<::Header>, - next_unscanned: &mut BlockNumber, - ) -> Result<()> { - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Err(Shutdown.into()); - } - head_result_option = subscription.next() => { - if let Some(head_result) = head_result_option { - let head = head_result.context( - "received an error from the RPC client while processing finalized heads" - )?; - - self - .process_skipped(next_unscanned, head.number) - .await - .context("failed to process a skipped gap in the listening mode")?; - self.process_block(head.number, head.hash()).await?; - - *next_unscanned = head.number - .checked_add(1) - .context(MAX_BLOCK_NUMBER_ERROR)?; - } else { - break; - } - } - } - } - - Ok(()) - } - - async fn finalized_heads(&self) -> Result::Header>> { - self.methods - .chain_subscribe_finalized_heads() - .await - .context("failed to subscribe to finalized heads") - } - - async fn process_block(&self, number: BlockNumber, hash: Hash) -> Result<()> { - log::debug!("background block {number}"); - - let block = self - .client - .blocks() - .at(hash) - .await - .context("failed to obtain a block for processing")?; - let events = block - .events() - .await - .context("failed to obtain block events")?; - - let read_tx = self.database.read()?; - let read_invoices = read_tx.invoices()?; - - let mut update = false; - let mut invoices_changes = HashMap::new(); - - for event_result in events.iter() { - const UPDATE: &str = "CodeUpdated"; - const TRANSFER: &str = "Transfer"; - - let event = event_result.context("failed to decode an event")?; - let metadata = event.event_metadata(); - - match (metadata.pallet.name(), &*metadata.variant.name) { - (SYSTEM, UPDATE) => update = true, - (BALANCES, TRANSFER) => Transfer::deserialize( - event - .field_values() - .context("failed to decode event's fields")?, - ) - .context("failed to deserialize a transfer event")? - .process(&mut invoices_changes, &read_invoices)?, - _ => {} - } - } - - let write_tx = self.database.write()?; - let mut write_invoices = write_tx.invoices()?; - - for (invoice, mut changes) in invoices_changes { - if let InvoiceStatus::Unpaid(price) = changes.invoice.status { - let balance = self.balance(hash, &invoice).await?; - - if balance >= price { - changes.invoice.status = InvoiceStatus::Paid(price); - - write_invoices.save(&invoice, &changes.invoice)?; - } - } - } - - drop(write_invoices); - - write_tx.commit()?; - - if update { - self.set_client_metadata(hash) - .await - .context("failed to update metadata in the finalized client")?; - - log::info!("A metadata update has been found and applied for the finalized client."); - } - - Ok(()) - } - - async fn set_client_metadata(&self, at: Hash) -> Result<()> { - let metadata = fetch_metadata(&*self.backend, at) - .await - .context("failed to fetch metadata for the scanner client")?; - - self.client.set_metadata(metadata); - - Ok(()) - } - - async fn balance(&self, hash: Hash, account: &Account) -> Result { - const ACCOUNT: &str = "Account"; - const ACCOUNT_BALANCES: &str = "data"; - const FREE_BALANCE: &str = "free"; - - let account_info = self - .client - .storage() - .at(hash) - .fetch_or_default(&dynamic::storage( - SYSTEM, - ACCOUNT, - vec![AsRef::<[u8; 32]>::as_ref(account)], - )) - .await - .context("failed to fetch account info from the chain")? - .to_value() - .context("failed to decode account info")?; - let encoded_balance = account_info - .at(ACCOUNT_BALANCES) - .with_context(|| format!("{ACCOUNT_BALANCES} field wasn't found in account info"))? - .at(FREE_BALANCE) - .with_context(|| format!("{FREE_BALANCE} wasn't found in account balance info"))?; - - encoded_balance.as_u128().with_context(|| { - format!("expected `u128` as the type of a free balance, got {encoded_balance}") - }) - } -} - -pub struct Processor { - api: Api, - scanner: Scanner, - methods: Arc>, - database: Arc, - backend: Arc>, - shutdown_notification: CancellationToken, -} - -impl Processor { - pub fn new( - ApiConfig { - api, - methods, - backend, - }: ApiConfig, - database: Arc, - shutdown_notification: CancellationToken, - ) -> Result { - let scanner = OnlineClient::from_backend_with( - api.genesis_hash(), - api.runtime_version(), - api.metadata(), - backend.clone(), - ) - .context("failed to initialize the scanner client")?; - - Ok(Processor { - api: Api { - tx: api.tx(), - blocks: api.blocks(), - }, - scanner: Scanner { - blocks: scanner.blocks(), - storage: scanner.storage(), - client: scanner, - }, - methods, - database, - shutdown_notification, - backend, - }) - } - - pub async fn ignite( - self, - latest_saved_block: Option, - task_tracker: TaskTracker, - error_tx: UnboundedSender, - ) -> Result<&'static str> { - self.execute(latest_saved_block, task_tracker, error_tx) - .await - .or_else(|error| { - error - .downcast() - .map(|Shutdown| "The RPC module is shut down.") - }) - } - - async fn execute( - mut self, - latest_saved_block: Option, - task_tracker: TaskTracker, - error_tx: UnboundedSender, - ) -> Result<&'static str> { - task_tracker.spawn(shutdown( - ProcessorFinalized { - database: self.database.clone(), - client: self.scanner.client.clone(), - backend: self.backend.clone(), - methods: self.methods.clone(), - shutdown_notification: self.shutdown_notification.clone(), - } - .ignite(), - error_tx, - )); - - let (mut head_number, head_hash) = self - .finalized_head_number_and_hash() - .await - .context("failed to get the chain head")?; - - let mut next_unscanned_number; - let mut subscription; - - if let Some(latest_saved) = latest_saved_block { - let latest_saved_hash = self - .methods - .chain_get_block_hash(Some(latest_saved.into())) - .await - .context("failed to get the hash of the last saved block")? - .context("received nothing after requesting the hash of the last saved block")?; - - self.set_scanner_metadata(latest_saved_hash).await?; - - next_unscanned_number = latest_saved - .checked_add(1) - .context(MAX_BLOCK_NUMBER_ERROR)?; - - let mut unscanned_amount = head_number.saturating_sub(next_unscanned_number); - - if unscanned_amount >= SCANNER_TO_LISTENER_SWITCH_POINT { - log::info!( - "Detected {unscanned_amount} unscanned blocks! Catching up may take a while." - ); - - while unscanned_amount >= SCANNER_TO_LISTENER_SWITCH_POINT { - self.process_skipped(&mut next_unscanned_number, head_number) - .await - .context("failed to process a skipped gap in the scanning mode")?; - - (head_number, _) = self - .finalized_head_number_and_hash() - .await - .context("failed to get a new chain head")?; - unscanned_amount = head_number.saturating_sub(next_unscanned_number); - } - - log::info!( - "Scanning of skipped blocks has been completed! Switching to the listening mode..." - ); - } - - subscription = self.finalized_heads().await?; - } else { - self.set_scanner_metadata(head_hash).await?; - - next_unscanned_number = head_number.checked_add(1).context(MAX_BLOCK_NUMBER_ERROR)?; - subscription = self.finalized_heads().await?; - } - - // Skip all already scanned blocks in cases like the first startup (we always skip the first - // block to fetch right metadata), an instant daemon restart, or a connection to a lagging - // endpoint. - 'skipping: loop { - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Err(Shutdown.into()); - } - header_result_option = subscription.next() => { - if let Some(header_result) = header_result_option { - let header = header_result.context( - "received an error from the RPC client while skipping saved finalized heads" - )?; - - if header.number >= next_unscanned_number { - break 'skipping; - } - } else { - break; - } - } - } - } - - log::warn!("Lost the connection while skipping already scanned blocks. Retrying..."); - - subscription = self - .finalized_heads() - .await - .context("failed to update the subscription while skipping scanned blocks")?; - } - - loop { - self.process_finalized_heads(subscription, &mut next_unscanned_number) - .await?; - - log::warn!("Lost the connection while processing finalized heads. Retrying..."); - - subscription = self - .finalized_heads() - .await - .context("failed to update the subscription while processing finalized heads")?; - } - } - - async fn finalized_head_number_and_hash(&self) -> Result<(BlockNumber, Hash)> { - let head_hash = self - .methods - .chain_get_finalized_head() - .await - .context("failed to get the finalized head hash")?; - let head = self - .methods - .chain_get_block(Some(head_hash)) - .await - .context("failed to get the finalized head")? - .context("received nothing after requesting the finalized head")?; - - Ok((head.block.header.number, head_hash)) - } - - async fn set_scanner_metadata(&self, at: Hash) -> Result<()> { - let metadata = fetch_metadata(&*self.backend, at) - .await - .context("failed to fetch metadata for the scanner client")?; - - self.scanner.client.set_metadata(metadata); - - Ok(()) - } - - async fn finalized_heads(&self) -> Result::Header>> { - self.methods - .chain_subscribe_finalized_heads() - .await - .context("failed to subscribe to finalized heads") - } - - async fn process_skipped( - &self, - next_unscanned: &mut BlockNumber, - head: BlockNumber, - ) -> Result<()> { - for skipped_number in *next_unscanned..head { - if self.shutdown_notification.is_cancelled() { - return Err(Shutdown.into()); - } - - let skipped_hash = self - .methods - .chain_get_block_hash(Some(skipped_number.into())) - .await - .context("failed to get the hash of a skipped block")? - .context("received nothing after requesting the hash of a skipped block")?; - - self.process_block(skipped_number, skipped_hash).await?; - } - - *next_unscanned = head; - - Ok(()) - } - - async fn process_finalized_heads( - &mut self, - mut subscription: RpcSubscription<::Header>, - next_unscanned: &mut BlockNumber, - ) -> Result<()> { - loop { - tokio::select! { - biased; - () = self.shutdown_notification.cancelled() => { - return Err(Shutdown.into()); - } - head_result_option = subscription.next() => { - if let Some(head_result) = head_result_option { - let head = head_result.context( - "received an error from the RPC client while processing finalized heads" - )?; - - self - .process_skipped(next_unscanned, head.number) - .await - .context("failed to process a skipped gap in the listening mode")?; - self.process_block(head.number, head.hash()).await?; - - *next_unscanned = head.number - .checked_add(1) - .context(MAX_BLOCK_NUMBER_ERROR)?; - } else { - break; - } - } - } - } - - Ok(()) - } - - async fn process_block(&self, number: BlockNumber, hash: Hash) -> Result<()> { - log::info!("Processing the block: {number}."); - - let block = self - .scanner - .blocks - .at(hash) - .await - .context("failed to obtain a block for processing")?; - let events = block - .events() - .await - .context("failed to obtain block events")?; - - let read_tx = self.database.read()?; - let read_invoices = read_tx.invoices()?; - - let mut update = false; - let mut invoices_changes = HashMap::new(); - - for event_result in events.iter() { - const UPDATE: &str = "CodeUpdated"; - const TRANSFER: &str = "Transfer"; - - let event = event_result.context("failed to decode an event")?; - let metadata = event.event_metadata(); - - match (metadata.pallet.name(), &*metadata.variant.name) { - (SYSTEM, UPDATE) => update = true, - (BALANCES, TRANSFER) => Transfer::deserialize( - event - .field_values() - .context("failed to decode event's fields")?, - ) - .context("failed to deserialize a transfer event")? - .process(&mut invoices_changes, &read_invoices)?, - _ => {} - } - } - - for (invoice, changes) in invoices_changes { - let price = match changes.invoice.status { - InvoiceStatus::Unpaid(price) | InvoiceStatus::Paid(price) => price, - }; - - self.process_unpaid(&block, changes, hash, invoice, price) - .await - .context("failed to process an unpaid invoice")?; - } - - if update { - self.set_scanner_metadata(hash) - .await - .context("failed to update metadata in the scanner client")?; - - log::info!("A metadata update has been found and applied for the scanner client."); - } - - let write_tx = self.database.write()?; - - write_tx.root()?.save_last_block(number)?; - write_tx.commit()?; - - Ok(()) - } - - async fn balance(&self, hash: Hash, account: &Account) -> Result { - const ACCOUNT: &str = "Account"; - const ACCOUNT_BALANCES: &str = "data"; - const FREE_BALANCE: &str = "free"; - - let account_info = self - .scanner - .storage - .at(hash) - .fetch_or_default(&dynamic::storage( - SYSTEM, - ACCOUNT, - vec![AsRef::<[u8; 32]>::as_ref(account)], - )) - .await - .context("failed to fetch account info from the chain")? - .to_value() - .context("failed to decode account info")?; - let encoded_balance = account_info - .at(ACCOUNT_BALANCES) - .with_context(|| format!("{ACCOUNT_BALANCES} field wasn't found in account info"))? - .at(FREE_BALANCE) - .with_context(|| format!("{FREE_BALANCE} wasn't found in account balance info"))?; - - encoded_balance.as_u128().with_context(|| { - format!("expected `u128` as the type of a free balance, got {encoded_balance}") - }) - } - - async fn batch_transfer( - &self, - nonce: Nonce, - block_hash_count: BlockNumber, - signer: &PairSigner, - transfers: Vec, - ) -> Result> { - const FORCE_BATCH: &str = "force_batch"; - - let call = dynamic::tx(UTILITY, FORCE_BATCH, vec![Value::from(transfers)]); - let (number, hash) = self - .finalized_head_number_and_hash() - .await - .context("failed to get the chain head while constructing a transaction")?; - let extensions = ( - (), - (), - (), - (), - CheckMortalityParams::mortal(block_hash_count.into(), number.into(), hash), - ChargeTransactionPaymentParams::no_tip(), - ); - - self.api - .tx - .create_signed_with_nonce(&call, signer, nonce, extensions) - .context("failed to create a transfer transaction") - } - - async fn current_nonce(&self, account: &Account) -> Result { - self.api - .blocks - .at(fetch_best_block(&self.methods).await?) - .await - .context("failed to obtain the best block for fetching an account nonce")? - .account_nonce(account) - .await - .context("failed to fetch an account nonce by the API client") - } - - async fn process_unpaid( - &self, - block: &Block, - mut changes: InvoiceChanges, - hash: Hash, - invoice: Account, - price: Balance, - ) -> Result<()> { - let balance = self.balance(hash, &invoice).await?; - - if let Some(_remaining) = balance.checked_sub(price) { - changes.invoice.status = InvoiceStatus::Paid(price); - - let block_nonce = block - .account_nonce(&invoice) - .await - .context(BLOCK_NONCE_ERROR)?; - let current_nonce = self.current_nonce(&invoice).await?; - - if current_nonce <= block_nonce { - let properties = self.database.properties().await; - let block_hash_count = properties.block_hash_count; - let signer = changes.invoice.signer(self.database.pair())?; - - let transfers = vec![construct_transfer(&changes.invoice.recipient, price)]; - let tx = self - .batch_transfer(current_nonce, block_hash_count, &signer, transfers.clone()) - .await?; - self.methods - .author_submit_extrinsic(tx.encoded()) - .await - .context("failed to submit an extrinsic")?; - } - } - - Ok(()) - } -} - -fn construct_transfer(to: &Account, _amount: Balance) -> Value { - const TRANSFER_ALL: &str = "transfer_all"; - - dynamic::tx( - BALANCES, - TRANSFER_ALL, - vec![scale_value::value!(Id(Value::from_bytes(to))), false.into()], - ) - .into_value() -} - -struct InvoiceChanges { - invoice: Invoice, - incoming: HashMap, -} - -#[derive(Deserialize)] -struct Transfer { - // The implementation of `Deserialize` for `AccountId32` works only with strings. - #[serde(deserialize_with = "account_deserializer")] - from: AccountId32, - #[serde(deserialize_with = "account_deserializer")] - to: AccountId32, - amount: Balance, -} - -fn account_deserializer<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - <([u8; 32],)>::deserialize(deserializer).map(|address| AccountId32::new(address.0)) -} - -impl Transfer { - fn process( - self, - invoices_changes: &mut HashMap, - invoices: &ReadInvoices<'_>, - ) -> Result<()> { - if self.from == self.to || self.amount == 0 { - return Ok(()); - } - - match invoices_changes.entry(self.to) { - Entry::Occupied(entry) => { - entry - .into_mut() - .incoming - .entry(self.from) - .and_modify(|amount| *amount = amount.saturating_add(self.amount)) - .or_insert(self.amount); - } - Entry::Vacant(entry) => { - if let (None, Some(encoded_invoice)) = - (invoices.get(&self.from)?, invoices.get(entry.key())?) - { - entry.insert(InvoiceChanges { - invoice: encoded_invoice.value(), - incoming: [(self.from, self.amount)].into(), - }); - } - } - } - - Ok(()) - } -} diff --git a/src/server.rs b/src/server.rs index 057c332..0917b11 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,209 +1,254 @@ use crate::{ - database::{Database, Invoice, InvoiceStatus}, - Account, + definitions::api_v2::*, + error::{Error, ForceWithdrawalError, OrderError, ServerError}, + state::State, }; -use anyhow::{Context, Result}; use axum::{ - extract::{Path, State}, - routing::get, - Json, Router, + extract::{self, rejection::RawPathParamsRejection, MatchedPath, Query, RawPathParams}, + http::{header, HeaderName, StatusCode}, + response::{IntoResponse, Response}, + routing, Json, Router, }; -use serde::Serialize; -use std::{future::Future, net::SocketAddr, sync::Arc}; -use subxt::ext::sp_core::{hexdisplay::HexDisplay, DeriveJunction, Pair}; +use axum_macros::debug_handler; +use serde::{Serialize, Serializer}; +use std::{borrow::Cow, collections::HashMap, future::Future, net::SocketAddr}; + use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; -pub(crate) const MODULE: &str = module_path!(); - -#[derive(Serialize)] -#[serde(untagged)] -pub enum Response { - Error(Error), - Success(Success), -} - -#[derive(Serialize)] -pub struct Error { - error: String, - wss: String, - mul: u64, - version: String, -} - -#[derive(Serialize)] -pub struct Success { - pay_account: String, - price: f64, - recipient: String, - order: String, - wss: String, - mul: u64, - result: String, - version: String, -} +pub const MODULE: &str = module_path!(); -pub(crate) async fn new( +pub async fn new( shutdown_notification: CancellationToken, host: SocketAddr, - database: Arc, -) -> Result>> { + state: State, +) -> Result, Error>>, ServerError> { + let v2: Router = Router::new() + .route("/order/:order_id", routing::post(order)) + .route( + "/order/:order_id/forceWithdrawal", + routing::post(force_withdrawal), + ) + .route("/status", routing::get(status)) + .route("/health", routing::get(health)) + .route("/audit", routing::get(audit)) + .route("/order/:order_id/investigate", routing::post(investigate)); let app = Router::new() .route( - "/recipient/:recipient/order/:order/price/:price", - get(handler_recip), + "/public/v2/payment/:paymentAccount", + routing::post(public_payment_account), ) - .route("/order/:order/price/:price", get(handler)) - .with_state(database); + .nest("/v2", v2) + .with_state(state); let listener = TcpListener::bind(host) .await - .context("failed to bind the TCP listener")?; - - log::info!("The server is listening on {host:?}."); + .map_err(|_| ServerError::TcpListenerBind(host))?; - Ok(async move { + Ok(async { axum::serve(listener, app) .with_graceful_shutdown(shutdown_notification.cancelled_owned()) .await - .context("failed to fire up the server")?; + .map_err(|_| ServerError::ThreadError)?; - Ok("The server module is shut down.") + Ok("The server module is shut down.".into()) }) } -async fn handler_recip( - State(database): State>, - Path((recipient, order, price)): Path<(String, String, f64)>, -) -> Json { - let wss = database.rpc().to_string(); - let mul = database.properties().await.decimals; - - match abcd(database, Some(recipient), order, price).await { - Ok(re) => Response::Success(re), - Err(error) => Response::Error(Error { - wss, - mul, - version: env!("CARGO_PKG_VERSION").into(), - error: error.to_string(), - }), +#[derive(Debug, Serialize)] +struct InvalidParameter { + parameter: String, + message: String, +} + +async fn process_order( + state: State, + matched_path: &MatchedPath, + path_result: Result, + query: &HashMap, +) -> Result { + const ORDER_ID: &str = "order_id"; + + let path_parameters = + path_result.map_err(|_| OrderError::InvalidParameter(matched_path.as_str().to_owned()))?; + let order = path_parameters + .iter() + .find_map(|(key, value)| (key == ORDER_ID).then_some(value)) + .ok_or_else(|| OrderError::MissingParameter(ORDER_ID.into()))? + .to_owned(); + + if query.is_empty() { + state + .order_status(&order) + .await + .map_err(|_| OrderError::InternalError) + } else { + let get_parameter = |parameter: &str| { + query + .get(parameter) + .ok_or_else(|| OrderError::MissingParameter(parameter.into())) + }; + + let currency = get_parameter(CURRENCY)?.to_owned(); + let callback = get_parameter(CALLBACK)?.to_owned(); + let amount = get_parameter(AMOUNT)? + .parse() + .map_err(|_| OrderError::InvalidParameter(AMOUNT.into()))?; + + if amount < 0.07 { + return Err(OrderError::LessThanExistentialDeposit(0.07)); + } + + state + .create_order(OrderQuery { + order, + amount, + callback, + currency, + }) + .await + .map_err(|_| OrderError::InternalError) } - .into() } -async fn handler( - State(database): State>, - Path((order, price)): Path<(String, f64)>, -) -> Json { - let wss = database.rpc().to_string(); - let mul = database.properties().await.decimals; - let recipient = database - .destination() - .as_ref() - .map(|d| format!("0x{}", HexDisplay::from(AsRef::<[u8; 32]>::as_ref(&d)))); - - match abcd(database, recipient, order, price).await { - Ok(re) => Response::Success(re), - Err(error) => Response::Error(Error { - wss, - mul, - version: env!("CARGO_PKG_VERSION").into(), - error: error.to_string(), - }), +#[debug_handler] +async fn order( + extract::State(state): extract::State, + matched_path: MatchedPath, + path_result: Result, + query: Query>, +) -> Response { + match process_order(state, &matched_path, path_result, &query).await { + Ok(order) => match order { + OrderResponse::NewOrder(order_status) => (StatusCode::CREATED, Json(order_status)).into_response(), + OrderResponse::FoundOrder(order_status) => (StatusCode::OK, Json(order_status)).into_response(), + OrderResponse::ModifiedOrder(order_status) => (StatusCode::OK, Json(order_status)).into_response(), + OrderResponse::CollidedOrder(order_status) => (StatusCode::CONFLICT, Json(order_status)).into_response(), + OrderResponse::NotFound => (StatusCode::NOT_FOUND, "").into_response(), + }, + Err(error) => match error { + OrderError::LessThanExistentialDeposit(existential_deposit) => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter: AMOUNT.into(), + message: format!("provided amount is less than the currency's existential deposit ({existential_deposit})"), + }]), + ) + .into_response(), + OrderError::UnknownCurrency => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter: CURRENCY.into(), + message: "provided currency isn't supported".into(), + }]), + ) + .into_response(), + OrderError::MissingParameter(parameter) => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter, + message: "parameter wasn't found".into(), + }]), + ) + .into_response(), + OrderError::InvalidParameter(parameter) => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter, + message: "parameter's format is invalid".into(), + }]), + ) + .into_response(), + OrderError::InternalError => StatusCode::INTERNAL_SERVER_ERROR.into_response(), + }, } - .into() } -async fn abcd( - database: Arc, - recipient_option: Option, - order: String, - price_f64: f64, -) -> Result { - let recipient = recipient_option.context("destionation address isn't set")?; - let decoded_recip = hex::decode(&recipient[2..])?; - let recipient_account = Account::try_from(decoded_recip.as_ref()) - .map_err(|()| anyhow::anyhow!("Unknown address length"))?; - let properties = database.properties().await; - #[allow(clippy::cast_precision_loss)] - let mul = 10u128.pow(properties.decimals.try_into()?) as f64; - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - let price = (price_f64 * mul).round() as u128; - let order_encoded = DeriveJunction::hard(&order).unwrap_inner(); - let invoice_account: Account = database - .pair() - .derive( - [ - DeriveJunction::Hard(<[u8; 32]>::from(recipient_account.clone())), - DeriveJunction::Hard(order_encoded), - ] - .into_iter(), - None, - )? - .0 - .public() - .into(); - - if let Some(encoded_invoice) = database.read()?.invoices()?.get(&invoice_account)? { - let invoice = encoded_invoice.value(); - - if let InvoiceStatus::Unpaid(saved_price) = invoice.status { - if saved_price != price { - anyhow::bail!("The invoice was created with different price ({price})."); - } +async fn process_force_withdrawal( + state: State, + matched_path: &MatchedPath, + path_result: Result, +) -> Result { + const ORDER_ID: &str = "order_id"; + + let path_parameters = path_result + .map_err(|_| ForceWithdrawalError::InvalidParameter(matched_path.as_str().to_owned()))?; + let order = path_parameters + .iter() + .find_map(|(key, value)| (key == ORDER_ID).then_some(value)) + .ok_or_else(|| ForceWithdrawalError::MissingParameter(ORDER_ID.into()))? + .to_owned(); + state + .force_withdrawal(order) + .await + .map_err(|e| ForceWithdrawalError::WithdrawalError(e.into())) +} + +#[debug_handler] +async fn force_withdrawal( + extract::State(state): extract::State, + matched_path: MatchedPath, + path_result: Result, +) -> Response { + match process_force_withdrawal(state, &matched_path, path_result).await { + Ok(a) => (StatusCode::CREATED, Json(a)).into_response(), + Err(ForceWithdrawalError::WithdrawalError(a)) => { + (StatusCode::BAD_REQUEST, Json(a)).into_response() } + Err(ForceWithdrawalError::MissingParameter(parameter)) => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter, + message: "parameter wasn't found".into(), + }]), + ) + .into_response(), + Err(ForceWithdrawalError::InvalidParameter(parameter)) => ( + StatusCode::BAD_REQUEST, + Json([InvalidParameter { + parameter, + message: "parameter's format is invalid".into(), + }]), + ) + .into_response(), + } +} - Ok(Success { - pay_account: format!("0x{}", HexDisplay::from(&invoice_account.as_ref())), - price: match invoice.status { - InvoiceStatus::Unpaid(invoice_price) => { - convert(properties.decimals, invoice_price)? - } - InvoiceStatus::Paid(invoice_price) => convert(properties.decimals, invoice_price)?, - }, - wss: database.rpc().to_string(), - mul: properties.decimals, - recipient, - order, - result: match invoice.status { - InvoiceStatus::Unpaid(_) => "waiting", - InvoiceStatus::Paid(_) => "paid", - } - .into(), - version: env!("CARGO_PKG_VERSION").into(), - }) - } else { - let tx = database.write()?; - - tx.invoices()?.save( - &invoice_account, - &Invoice { - recipient: recipient_account, - order: order_encoded, - status: InvoiceStatus::Unpaid(price), - }, - )?; - - tx.commit()?; - - Ok(Success { - pay_account: format!("0x{}", HexDisplay::from(&invoice_account.as_ref())), - price: price_f64, - wss: database.rpc().to_string(), - mul: properties.decimals, - recipient, - order, - version: env!("CARGO_PKG_VERSION").into(), - result: "waiting".into(), - }) +async fn status( + extract::State(state): extract::State, +) -> ([(HeaderName, &'static str); 1], Json) { + match state.server_status().await { + Ok(status) => ([(header::CACHE_CONTROL, "no-store")], status.into()), + Err(_e) => panic!("db connection is down, state is lost"), //TODO tell this to client } } -fn convert(dec: u64, num: u128) -> Result { - #[allow(clippy::cast_precision_loss)] - let numfl = num as f64; - #[allow(clippy::cast_precision_loss)] - let mul = 10u128.pow(dec.try_into()?) as f64; +async fn health( + extract::State(state): extract::State, +) -> ([(HeaderName, &'static str); 1], Json) { + todo!(); +} + +async fn audit(extract::State(state): extract::State) -> Response { + StatusCode::NOT_IMPLEMENTED.into_response() +} + +#[debug_handler] +async fn investigate( + extract::State(state): extract::State, + matched_path: MatchedPath, + path_result: Result, + query: Query>, +) -> Response { + todo!() +} - Ok(numfl / mul) +#[debug_handler] +async fn public_payment_account( + extract::State(state): extract::State, + matched_path: MatchedPath, + path_result: Result, + query: Query>, +) -> Response { + todo!() } diff --git a/src/signer.rs b/src/signer.rs new file mode 100644 index 0000000..0017fba --- /dev/null +++ b/src/signer.rs @@ -0,0 +1,157 @@ +//! This is a tiny worker to hold secret key +//! We use it to avoid sending it back and forth through async pipes +//! so that we can be sure that zeroizing at least tries to do its thing +//! +//! Keep in mind, that leaking secrets in a system like Kalatori is a serious threat +//! with delayed attacks taken into account. Of course, some secret rotation scheme must +//! be implemented, but it seems likely that it would be neglected occasionally. +//! So we go to trouble of running this separate process. +//! +//! Also this abstraction could be used to implement off-system signer + +use crate::{ + definitions::Entropy, + error::{Error, SignerError}, + TaskTracker, +}; + +use std::env; + +use mnemonic_external::{regular::InternalWordList, WordSet}; +use substrate_crypto_light::{ + common::{AccountId32, AsBase58, DeriveJunction, FullDerivation}, + sr25519::{Pair, Signature}, +}; +use tokio::sync::{mpsc, oneshot}; +use zeroize::Zeroize; + +/// Signer handle +pub struct Signer { + tx: mpsc::Sender, +} + +impl Signer { + /// Run once to initialize; this should do **all** secret management + pub fn init( + recipient: AccountId32, + task_tracker: TaskTracker, + seed: String, + ) -> Result { + let (tx, mut rx) = mpsc::channel(16); + task_tracker.spawn("Signer", async move { + let mut seed_entropy = entropy_from_phrase(&seed)?; // TODO: shutdown on failure + while let Some(request) = rx.recv().await { + match request { + SignerRequest::PublicKey(request) => { + let _unused = request.res.send( + match Pair::from_entropy_and_full_derivation( + &seed_entropy, + // api spec says use "2" for communication, let's use it here too + derivations(&recipient.to_base58_string(2), &request.id), + ) { + Ok(a) => Ok(a.public().to_base58_string(request.ss58)), + Err(e) => Err(e.into()), + }, + ); + } + SignerRequest::Sign(request) => { + let _unused = request.res.send( + match Pair::from_entropy_and_full_derivation( + &seed_entropy, + // api spec says use "2" for communication, let's use it here too + derivations(&recipient.to_base58_string(2), &request.id), + ) { + Ok(a) => Ok(a.sign(&request.signable)), + Err(e) => Err(e.into()), + }, + ); + } + SignerRequest::Shutdown(res) => { + seed_entropy.zeroize(); + let _ = res.send(()); + break; + } + } + } + Ok("Signer module cleared and is shutting down!".into()) + }); + + Ok(Self { tx }) + } + + pub async fn public(&self, id: String, ss58: u16) -> Result { + let (res, rx) = oneshot::channel(); + self.tx + .send(SignerRequest::PublicKey(PublicKeyRequest { id, ss58, res })) + .await + .map_err(|_| SignerError::SignerDown)?; + rx.await.map_err(|_| SignerError::SignerDown)? + } + + pub async fn sign(&self, id: String, signable: Vec) -> Result { + let (res, rx) = oneshot::channel(); + self.tx + .send(SignerRequest::Sign(Sign { id, signable, res })) + .await + .map_err(|_| SignerError::SignerDown)?; + rx.await.map_err(|_| SignerError::SignerDown)? + } + + pub async fn shutdown(&self) { + let (tx, rx) = oneshot::channel(); + let _unused = self.tx.send(SignerRequest::Shutdown(tx)).await; + // let _ = rx.await; + } + + /// Clone wrapper in case we need to make it more complex later + pub fn interface(&self) -> Self { + Signer { + tx: self.tx.clone(), + } + } +} + +/// Messages sent to signer; signer never initiates anything on its own. +enum SignerRequest { + /// Generate public key for order + PublicKey(PublicKeyRequest), + + /// Sign a transaction + Sign(Sign), + + /// Safe termination + Shutdown(oneshot::Sender<()>), +} + +/// Information required to generate public invoice address, with callback +struct PublicKeyRequest { + id: String, + ss58: u16, + res: oneshot::Sender>, +} + +/// Bytes to sign, with callback +struct Sign { + id: String, + signable: Vec, + res: oneshot::Sender>, +} + +/// Convert seed phrase to entropy +/// +/// TODO: handle also old seeds and do something about them +pub fn entropy_from_phrase(seed: &str) -> Result { + let mut word_set = WordSet::new(); + for word in seed.split(' ') { + word_set.add_word(&word, &InternalWordList)?; + } + Ok(word_set.to_entropy()?) +} + +/// Standardized derivation protocol +pub fn derivations<'a>(recipient: &'a str, order: &'a str) -> FullDerivation<'a> { + FullDerivation { + junctions: vec![DeriveJunction::hard(recipient), DeriveJunction::hard(order)], + password: None, + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..628970e --- /dev/null +++ b/src/state.rs @@ -0,0 +1,328 @@ +use crate::{ + chain::ChainManager, + database::Database, + definitions::{ + api_v2::{ + CurrencyProperties, OrderCreateResponse, OrderInfo, OrderQuery, OrderResponse, + OrderStatus, ServerInfo, ServerStatus, + }, + Entropy, + }, + error::{Error, OrderError}, + signer::Signer, + ConfigWoChains, TaskTracker, +}; + +use std::collections::HashMap; + +use substrate_crypto_light::common::{AccountId32, AsBase58}; +use tokio::sync::oneshot; +use tokio_util::sync::CancellationToken; + +/// Struct to store state of daemon. If something requires cooperation of more than one component, +/// it should go through here. +#[derive(Clone, Debug)] +pub struct State { + tx: tokio::sync::mpsc::Sender, +} + +impl State { + pub fn initialise( + signer: Signer, + ConfigWoChains { + recipient, + debug, + remark, + }: ConfigWoChains, + db: Database, + chain_manager: oneshot::Receiver, + instance_id: String, + task_tracker: TaskTracker, + shutdown_notification: CancellationToken, + ) -> Result { + /* + currencies: HashMap, + recipient: AccountId, + pair: Pair, + depth: Option, + account_lifetime: Timestamp, + debug: bool, + remark: String, + invoices: RwLock>, + rpc: String, + */ + let (tx, mut rx) = tokio::sync::mpsc::channel(1024); + + let server_info = ServerInfo { + // TODO + version: env!("CARGO_PKG_VERSION").to_string(), + instance_id: instance_id.clone(), + debug: debug.unwrap_or_default(), + kalatori_remark: remark.clone(), + }; + + // Remember to always spawn async here or things might deadlock + task_tracker.clone().spawn("State Handler", async move { + let chain_manager = chain_manager.await.map_err(|_| Error::Fatal)?; + let db_wakeup = db.clone(); + let chain_manager_wakeup = chain_manager.clone(); + let currencies = HashMap::new(); + let mut state = StateData { + currencies, + recipient, + server_info, + db, + chain_manager, + signer, + }; + + // TODO: consider doing this even more lazy + let order_list = db_wakeup.order_list().await?; + task_tracker.spawn("Restore saved orders", async move { + for (order, order_details) in order_list { + chain_manager_wakeup + .add_invoice(order, order_details, state.recipient) + .await; + } + Ok("All saved orders restored".into()) + }); + + loop { + tokio::select! { + biased; + request_option = rx.recv() => { + let Some(request) = request_option else { + break; + }; + + match request { + StateAccessRequest::ConnectChain(assets) => { + // it MUST be asserted in chain tracker that assets are those and only + // those that user requested + state.update_currencies(assets); + } + StateAccessRequest::GetInvoiceStatus(request) => { + request + .res + .send(state.get_invoice_status(request.order).await) + .map_err(|_| Error::Fatal)?; + } + StateAccessRequest::CreateInvoice(request) => { + request + .res + .send(state.create_invoice(request.order_query).await) + .map_err(|_| Error::Fatal)?; + } + StateAccessRequest::ServerStatus(res) => { + let server_status = ServerStatus { + server_info: state.server_info.clone(), + supported_currencies: state.currencies.clone(), + }; + res.send(server_status).map_err(|_| Error::Fatal)?; + } + StateAccessRequest::OrderPaid(id) => { + // Only perform actions if the record is saved in ledger + match state.db.mark_paid(id.clone()).await { + Ok(order) => { + // TODO: callback here + drop(state.chain_manager.reap(id, order, state.recipient).await); + } + Err(e) => { + tracing::error!( + "Order was paid but this could not be recorded! {e:?}" + ) + } + } + } + }; + } + // Orchestrate shutdown from here + () = shutdown_notification.cancelled() => { + // Web server shuts down on its own; it does not matter what it sends now. + + // First shut down active actions for external world. If something yet + // happens, we should record it in db. + state.chain_manager.shutdown().await; + + // Now that nothing happens we can wind down the ledger + state.db.shutdown().await; + + // Try to zeroize secrets + state.signer.shutdown().await; + + // And shut down finally + break; + } + } + } + + Ok("State handler is shutting down".into()) + }); + + Ok(Self { tx }) + } + + pub async fn connect_chain(&self, assets: HashMap) { + self.tx.send(StateAccessRequest::ConnectChain(assets)).await; + } + + pub async fn order_status(&self, order: &str) -> Result { + let (res, rx) = oneshot::channel(); + self.tx + .send(StateAccessRequest::GetInvoiceStatus(GetInvoiceStatus { + order: order.to_string(), + res, + })) + .await + .map_err(|_| Error::Fatal)?; + rx.await.map_err(|_| Error::Fatal)? + } + + pub async fn server_status(&self) -> Result { + let (res, rx) = oneshot::channel(); + self.tx + .send(StateAccessRequest::ServerStatus(res)) + .await + .map_err(|_| Error::Fatal)?; + rx.await.map_err(|_| Error::Fatal) + } + + pub async fn create_order(&self, order_query: OrderQuery) -> Result { + let (res, rx) = oneshot::channel(); + /* + Invoicee { + callback: callback.clone(), + amount: Balance::parse(amount, 6), + paid: false, + paym_acc: pay_acc.clone(), + }, + */ + self.tx + .send(StateAccessRequest::CreateInvoice(CreateInvoice { + order_query, + res, + })) + .await + .map_err(|_| Error::Fatal)?; + rx.await.map_err(|_| Error::Fatal)? + } + + pub async fn order_paid(&self, order: String) { + if self + .tx + .send(StateAccessRequest::OrderPaid(order)) + .await + .is_err() + { + tracing::warn!("Data race on shutdown; please restart the daemon for cleaning up"); + }; + } + + pub async fn force_withdrawal(&self, order: String) -> Result { + todo!() + } + + pub fn interface(&self) -> Self { + State { + tx: self.tx.clone(), + } + } +} + +enum StateAccessRequest { + ConnectChain(HashMap), + GetInvoiceStatus(GetInvoiceStatus), + CreateInvoice(CreateInvoice), + ServerStatus(oneshot::Sender), + OrderPaid(String), +} + +struct GetInvoiceStatus { + pub order: String, + pub res: oneshot::Sender>, +} + +struct CreateInvoice { + pub order_query: OrderQuery, + pub res: oneshot::Sender>, +} + +struct StateData { + currencies: HashMap, + recipient: AccountId32, + server_info: ServerInfo, + db: Database, + chain_manager: ChainManager, + signer: Signer, +} + +impl StateData { + fn update_currencies(&mut self, currencies: HashMap) { + self.currencies.extend(currencies); + } + + async fn get_invoice_status(&self, order: String) -> Result { + if let Some(order_info) = self.db.read_order(order.clone()).await? { + let message = String::new(); //TODO + Ok(OrderResponse::FoundOrder(OrderStatus { + order, + message, + recipient: self.recipient.clone().to_base58_string(2), // TODO maybe but spec says use "2" + server_info: self.server_info.clone(), + order_info, + payment_page: String::new(), + redirect_url: String::new(), + })) + } else { + Ok(OrderResponse::NotFound) + } + } + + async fn create_invoice(&self, order_query: OrderQuery) -> Result { + let order = order_query.order.clone(); + let currency = self + .currencies + .get(&order_query.currency) + .ok_or(OrderError::UnknownCurrency)?; + let currency = currency.info(order_query.currency.clone()); + let payment_account = self.signer.public(order.clone(), currency.ss58).await?; + match self + .db + .create_order(order.clone(), order_query, currency, payment_account) + .await? + { + OrderCreateResponse::New(new_order_info) => { + self.chain_manager + .add_invoice(order.clone(), new_order_info.clone(), self.recipient) + .await?; + Ok(OrderResponse::NewOrder(self.order_status( + order, + new_order_info, + String::new(), + ))) + } + OrderCreateResponse::Modified(order_info) => Ok(OrderResponse::ModifiedOrder( + self.order_status(order, order_info, String::new()), + )), + OrderCreateResponse::Collision(order_status) => { + Ok(OrderResponse::CollidedOrder(self.order_status( + order, + order_status, + String::from("Order with this ID was already processed"), + ))) + } + } + } + + fn order_status(&self, order: String, order_info: OrderInfo, message: String) -> OrderStatus { + OrderStatus { + order, + message, + recipient: self.recipient.clone().to_base58_string(2), // TODO maybe but spec says use "2" + server_info: self.server_info.clone(), + order_info, + payment_page: String::new(), + redirect_url: String::new(), + } + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..8b931f5 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,9 @@ +use crate::error::{NotHex, UtilError}; + +pub fn unhex(hex_data: &str, what_is_hex: NotHex) -> Result, UtilError> { + if let Some(stripped) = hex_data.strip_prefix("0x") { + hex::decode(stripped).map_err(|_| UtilError::NotHex(what_is_hex)) + } else { + hex::decode(hex_data).map_err(|_| UtilError::NotHex(what_is_hex)) + } +} diff --git a/start-ah.sh b/start-ah.sh deleted file mode 100755 index 6f54303..0000000 --- a/start-ah.sh +++ /dev/null @@ -1,8 +0,0 @@ -KALATORI_HOST="127.0.0.1:16726" \ -KALATORI_SEED="" \ -KALATORI_DECIMALS="6" \ -KALATORI_DESTINATION="0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" \ -KALATORI_LOG="subxt::events::events_type=off" \ -KALATORI_RPC="wss://polkadot-asset-hub-rpc.polkadot.io" \ -KALATORI_USD_ASSET="USDT" \ -cargo r -p kalatori-ah diff --git a/start.sh b/start.sh index 2722f51..bb35c7b 100755 --- a/start.sh +++ b/start.sh @@ -1,6 +1,5 @@ KALATORI_HOST="127.0.0.1:16726" \ -KALATORI_SEED="" \ -KALATORI_DECIMALS="12" \ -KALATORI_DESTINATION="0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" \ -KALATORI_LOG="subxt::events::events_type=off" \ +KALATORI_SEED="bottom drive obey lake curtain smoke basket hold race lonely fit walk" \ +KALATORI_RECIPIENT="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" \ +KALATORI_REMARK="test" \ cargo r diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..2e942d3 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,179 @@ +// ensure you have chopsticks installed: npm install -g @acala-network/chopsticks +// if running locally, ensure that you have no dangling processes (kalatori daemon, chopsticks) +// pkill -f kalatori; pkill -f chopsticks + +use kalatori::definitions::api_v2::{ServerStatus, TokenKind}; +use lazy_static::lazy_static; +use reqwest::Client; +use std::env; +use std::process::{Child, Command}; +use std::sync::{Mutex, Once}; +use tokio::time::{sleep, Duration}; + +static INIT: Once = Once::new(); +lazy_static! { + static ref CHOPSTICKS: Mutex> = Mutex::new(None); +} + +async fn start_chopsticks() -> std::io::Result { + let mut command = Command::new("npx"); + command.args(&[ + "@acala-network/chopsticks@latest", + "-c", + "chopsticks/pd-ah.yml", + ]); + let chopsticks = command.spawn()?; + sleep(Duration::from_secs(10)).await; // Give Chopsticks some time to start + Ok(chopsticks) +} + +async fn stop_chopsticks(chopsticks: &mut Child) -> std::io::Result<()> { + chopsticks.kill()?; + chopsticks.wait()?; + Ok(()) +} + +const KALATORI_REMARK: &str = "TEST_REMARK"; +const KALATORI_CARGO_PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn load_chain_config() { + env::set_var("KALATORI_CONFIG", "configs/chopsticks.toml"); + env::set_var("KALATORI_HOST", "127.0.0.1:16726"); + env::set_var( + "KALATORI_SEED", + "bottom drive obey lake curtain smoke basket hold race lonely fit walk", + ); + env::set_var("KALATORI_RPC", "ws://localhost:8000"); + env::set_var("KALATORI_DECIMALS", "12"); + env::set_var( + "KALATORI_RECIPIENT", + "5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV", + ); + env::set_var("KALATORI_REMARK", KALATORI_REMARK.to_string()); + // env::set_var("RUST_BACKTRACE", "1"); +} + +async fn start_daemon() -> std::io::Result { + let daemon = Command::new("target/debug/kalatori").spawn()?; + sleep(Duration::from_secs(10)).await; // Give the daemon some time to start + Ok(daemon) +} + +async fn stop_daemon(daemon: &mut Child) -> std::io::Result<()> { + daemon.kill()?; + daemon.wait()?; + Ok(()) +} + +struct TestContext { + daemon: Option, +} + +impl TestContext { + async fn new() -> Self { + // Start Chopsticks if not already started + INIT.call_once(|| { + tokio::spawn(async { + let chopsticks = start_chopsticks() + .await + .expect("Failed to start Chopsticks"); + let mut guard = CHOPSTICKS.lock().unwrap(); + *guard = Some(chopsticks); + }); + }); + + // Wait for Chopsticks to start + sleep(Duration::from_secs(3)).await; + + // Then start the daemon + load_chain_config(); + let daemon = start_daemon() + .await + .expect("Failed to start kalatori daemon"); + + TestContext { + daemon: Some(daemon), + } + } + + async fn drop_async(&mut self) { + if let Some(mut daemon) = self.daemon.take() { + let _ = stop_daemon(&mut daemon).await; + } + } +} +#[tokio::test] +async fn test_daemon_status_call() { + let mut context = TestContext::new().await; + + let client = Client::new(); + + let resp = client + .get("http://127.0.0.1:16726/v2/status") + .send() + .await + .expect("Failed to send request"); + + // Assert that the response status is 200 OK + assert!(resp.status().is_success()); + + let body = resp + .json::() + .await + .expect("Failed to parse response"); + + let body_str = body.to_string(); + let server_status: ServerStatus = + serde_json::from_str(&body_str).expect("Failed to deserialize ServerStatus"); + + assert_eq!( + server_status.server_info.version, + KALATORI_CARGO_PACKAGE_VERSION + ); + assert!(!server_status.server_info.instance_id.is_empty()); + assert_eq!(server_status.server_info.debug, true); + assert_eq!( + server_status.server_info.kalatori_remark, + Some(KALATORI_REMARK.into()) + ); + + assert!(!server_status.supported_currencies.is_empty()); + for (currency, properties) in server_status.supported_currencies { + assert!(!currency.is_empty()); + assert!(!properties.chain_name.is_empty()); + assert!(matches!( + properties.kind, + TokenKind::Balances | TokenKind::Asset + )); + assert!(properties.decimals > 0); + assert!(!properties.rpc_url.is_empty()); + + if currency == "DOT" { + assert_eq!(properties.chain_name, "statemint"); + assert_eq!(properties.kind, TokenKind::Balances); + assert_eq!(properties.decimals, 10); + assert_eq!(properties.rpc_url, "ws://localhost:8000"); + // those are wrong atm + } + } + + context.drop_async().await; +} + +// #[tokio::test] +// async fn test_daemon_health_call() { +// let mut context = TestContext::new().await; +// +// let client = Client::new(); +// +// let resp = client +// .get("http://127.0.0.1:16726/v2/health") +// .send() +// .await +// .expect("Failed to send request"); +// +// // Assert that the response status is 200 OK +// assert!(resp.status().is_success()); +// +// context.drop_async().await; +// }